Compare commits
2 Commits
extend-slk
...
update-wor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff739d2dea | ||
|
|
858a79d311 |
7
.github/pull_request_template.md
vendored
7
.github/pull_request_template.md
vendored
@@ -1,14 +1,11 @@
|
||||
[master < Epic] PR
|
||||
- [ ] Check, and update documentation if necessary
|
||||
- [ ] Update [changelog](https://docs.memgraph.com/memgraph/changelog)
|
||||
- [ ] Write E2E tests
|
||||
- [ ] Compare the [benchmarking results](https://bench-graph.memgraph.com/) between the master branch and the Epic branch
|
||||
- [ ] Provide the full content or a guide for the final git message
|
||||
|
||||
[master < Task] PR
|
||||
- [ ] Check, and update documentation if necessary
|
||||
- [ ] Update [changelog](https://docs.memgraph.com/memgraph/changelog)
|
||||
- [ ] Provide the full content or a guide for the final git message
|
||||
|
||||
|
||||
To keep docs changelog up to date, one more thing to do:
|
||||
- [ ] Write a release note here
|
||||
- [ ] Tag someone from docs team in the comments
|
||||
|
||||
2
.github/workflows/daily_benchmark.yaml
vendored
2
.github/workflows/daily_benchmark.yaml
vendored
@@ -3,7 +3,7 @@ name: Daily Benchmark
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 22 * * *"
|
||||
- cron: "0 1 * * *"
|
||||
|
||||
jobs:
|
||||
release_benchmarks:
|
||||
|
||||
2
.github/workflows/diff.yaml
vendored
2
.github/workflows/diff.yaml
vendored
@@ -14,7 +14,7 @@ on:
|
||||
- "**/*.md"
|
||||
- ".clang-format"
|
||||
- "CODEOWNERS"
|
||||
- "licenses/*"
|
||||
- licenses/**
|
||||
|
||||
jobs:
|
||||
community_build:
|
||||
|
||||
34
.github/workflows/package_all.yaml
vendored
34
.github/workflows/package_all.yaml
vendored
@@ -160,23 +160,6 @@ 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
|
||||
@@ -193,20 +176,3 @@ jobs:
|
||||
with:
|
||||
name: debian-11-arm
|
||||
path: build/output/debian-11-arm/memgraph*.deb
|
||||
|
||||
ubuntu-2204-arm:
|
||||
runs-on: [self-hosted, DockerMgBuild, ARM64, strange]
|
||||
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 ubuntu-22.04-arm
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ubuntu-22.04-arm
|
||||
path: build/output/ubuntu-22.04-arm/memgraph*.deb
|
||||
|
||||
2
.github/workflows/release_centos8.yaml
vendored
2
.github/workflows/release_centos8.yaml
vendored
@@ -3,7 +3,7 @@ name: Release CentOS 8
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 22 * * *"
|
||||
- cron: "0 1 * * *"
|
||||
|
||||
jobs:
|
||||
community_build:
|
||||
|
||||
2
.github/workflows/release_debian10.yaml
vendored
2
.github/workflows/release_debian10.yaml
vendored
@@ -3,7 +3,7 @@ name: Release Debian 10
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 22 * * *"
|
||||
- cron: "0 1 * * *"
|
||||
|
||||
jobs:
|
||||
community_build:
|
||||
|
||||
2
.github/workflows/release_ubuntu2004.yaml
vendored
2
.github/workflows/release_ubuntu2004.yaml
vendored
@@ -3,7 +3,7 @@ name: Release Ubuntu 20.04
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 22 * * *"
|
||||
- cron: "0 1 * * *"
|
||||
|
||||
jobs:
|
||||
community_build:
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
rev: v2.3.0
|
||||
hooks:
|
||||
- id: check-yaml
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.1.0
|
||||
rev: 22.10.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.12.0
|
||||
rev: 5.10.1
|
||||
hooks:
|
||||
- id: isort
|
||||
name: isort (python)
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: v15.0.7
|
||||
rev: v13.0.0
|
||||
hooks:
|
||||
- id: clang-format
|
||||
|
||||
64
README.md
64
README.md
@@ -4,6 +4,10 @@
|
||||
|
||||
---
|
||||
|
||||
<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"/>
|
||||
@@ -18,7 +22,7 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/memgraph/memgraph">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/memgraph/memgraph/release_debian10.yaml?branch=master&label=build%20and%20test&logo=github"/>
|
||||
<img src="https://img.shields.io/github/workflow/status/memgraph/memgraph/Release%20Ubuntu%2020.04/master" alt="build" title="build"/>
|
||||
</a>
|
||||
<a href="https://memgraph.com/docs/" alt="Documentation">
|
||||
<img src="https://img.shields.io/badge/documentation-Memgraph-orange" />
|
||||
@@ -52,17 +56,6 @@ to ensure that you’re getting the [best possible
|
||||
performance](http://memgraph.com/benchgraph) consistently and without surprises.
|
||||
It’s also ACID-compliant and highly available.
|
||||
|
||||
## :zap: Features
|
||||
|
||||
- Run Python, Rust, and C/C++ code natively, check out the
|
||||
[MAGE](https://github.com/memgraph/mage) graph algorithm library
|
||||
- Native support for machine learning
|
||||
- Streaming support
|
||||
- Replication
|
||||
- Authentication and authorization
|
||||
- ACID compliance
|
||||
|
||||
|
||||
## :video_game: Memgraph Playground
|
||||
|
||||
You don't need to install anything to try out Memgraph. Check out
|
||||
@@ -85,49 +78,28 @@ your browser.
|
||||
### macOS
|
||||
|
||||
[](https://memgraph.com/docs/memgraph/install-memgraph-on-macos-docker)
|
||||
[](https://memgraph.com/docs/memgraph/install-memgraph-on-ubuntu)
|
||||
|
||||
### Linux
|
||||
|
||||
[](https://memgraph.com/docs/memgraph/install-memgraph-on-linux-docker)
|
||||
[](https://memgraph.com/docs/memgraph/install-memgraph-on-debian)
|
||||
[](https://memgraph.com/docs/memgraph/install-memgraph-on-ubuntu)
|
||||
[](https://memgraph.com/docs/memgraph/install-memgraph-from-rpm)
|
||||
[](https://memgraph.com/docs/memgraph/install-memgraph-from-rpm)
|
||||
[](https://memgraph.com/docs/memgraph/install-memgraph-from-rpm)
|
||||
[](https://memgraph.com/docs/memgraph/install-memgraph-from-rpm)
|
||||
|
||||
You can find the binaries and Docker images on the [Download
|
||||
Hub](https://memgraph.com/download) and the installation instructions in the
|
||||
[official documentation](https://memgraph.com/docs/memgraph/installation).
|
||||
|
||||
## :zap: Features
|
||||
|
||||
## :cloud: Memgraph Cloud
|
||||
|
||||
Check out [Memgraph Cloud](https://memgraph.com/docs/memgraph-cloud) - a cloud service fully managed on AWS and available in 6 geographic regions around the world. Memgraph Cloud allows you to create projects with Enterprise instances of MemgraphDB from your browser.
|
||||
|
||||
<p align="left">
|
||||
<a href="https://memgraph.com/docs/memgraph-cloud">
|
||||
<img width="450px" alt="Memgraph Cloud" src="https://public-assets.memgraph.com/memgraph-gifs%2Fcloud.gif">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## :link: Connect to Memgraph
|
||||
|
||||
[Connect to the database](https://memgraph.com/docs/memgraph/connect-to-memgraph) using Memgraph Lab, mgconsole, various drivers (Python, C/C++ and others) and WebSocket.
|
||||
|
||||
### :microscope: Memgraph Lab
|
||||
|
||||
Visualize graphs and play with queries to understand your data. [Memgraph Lab](https://memgraph.com/docs/memgraph-lab) is a user interface that helps you explore and manipulate the data stored in Memgraph. Visualize graphs, execute ad hoc queries, and optimize their performance.
|
||||
|
||||
<p align="left">
|
||||
<a href="https://memgraph.com/docs/memgraph-lab">
|
||||
<img width="450px" alt="Memgraph Cloud" src="https://public-assets.memgraph.com/memgraph-gifs%2Flab.gif">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## :file_folder: Import data
|
||||
|
||||
[Import data](https://memgraph.com/docs/memgraph/import-data) into Memgraph using Kafka, RedPanda or Pulsar streams, CSV and JSON files, or Cypher commands.
|
||||
- Run Python, Rust, and C/C++ code natively, check out the
|
||||
[MAGE](https://github.com/memgraph/mage) graph algorithm library
|
||||
- Native support for machine learning
|
||||
- Streaming support
|
||||
- Replication
|
||||
- Authentication and authorization
|
||||
- ACID compliance
|
||||
|
||||
## :bookmark_tabs: Documentation
|
||||
|
||||
@@ -171,17 +143,17 @@ Memgraph Community is available under the [BSL
|
||||
license](./licenses/BSL.txt).</br> Memgraph Enterprise is available under the
|
||||
[MEL license](./licenses/MEL.txt).
|
||||
|
||||
## :busts_in_silhouette: Community
|
||||
## 🙋 Community
|
||||
|
||||
- :purple_heart: [**Discord**](https://discord.gg/memgraph)
|
||||
- :ocean: [**Stack Overflow**](https://stackoverflow.com/questions/tagged/memgraphdb)
|
||||
- :busts_in_silhouette: [**Discourse forum**](https://discourse.memgraph.com/)
|
||||
- :open_file_folder: [**Memgraph GitHub**](https://github.com/memgraph)
|
||||
- :bird: [**Twitter**](https://twitter.com/memgraphdb)
|
||||
- :movie_camera:
|
||||
[**YouTube**](https://www.youtube.com/channel/UCZ3HOJvHGxtQ_JHxOselBYg)
|
||||
|
||||
<p align="center">
|
||||
<a href="#">
|
||||
<img src="https://img.shields.io/badge/⬆️ back_to_top_⬆️-white" alt="Back to top" title="Back to top"/>
|
||||
<img src="https://img.shields.io/badge/⬆️back_to_top_⬆️-white" alt="Back to top" title="Back to top"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -26,7 +26,6 @@ TOOLCHAIN_BUILD_DEPS=(
|
||||
diffutils
|
||||
libipt libipt-devel # intel
|
||||
patch
|
||||
perl # for openssl
|
||||
)
|
||||
|
||||
TOOLCHAIN_RUN_DEPS=(
|
||||
@@ -37,6 +36,7 @@ TOOLCHAIN_RUN_DEPS=(
|
||||
readline # for cmake and llvm
|
||||
libffi libxml2 # for llvm
|
||||
openssl-devel
|
||||
perl # for openssl
|
||||
)
|
||||
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
@@ -64,10 +64,6 @@ list() {
|
||||
|
||||
check() {
|
||||
local missing=""
|
||||
# On Fedora yum/dnf and python10 use newer glibc which is not compatible
|
||||
# with ours, so we need to momentarely disable env
|
||||
local OLD_LD_LIBRARY_PATH=${LD_LIBRARY_PATH}
|
||||
LD_LIBRARY_PATH=""
|
||||
for pkg in $1; do
|
||||
if ! dnf list installed "$pkg" >/dev/null 2>/dev/null; then
|
||||
missing="$pkg $missing"
|
||||
@@ -77,7 +73,6 @@ check() {
|
||||
echo "MISSING PACKAGES: $missing"
|
||||
exit 1
|
||||
fi
|
||||
LD_LIBRARY_PATH=${OLD_LD_LIBRARY_PATH}
|
||||
}
|
||||
|
||||
install() {
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
#!/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,19 +51,11 @@ CPPCHECK_VERSION=2.6
|
||||
LLVM_VERSION=13.0.0
|
||||
SWIG_VERSION=4.0.2 # used only for LLVM compilation
|
||||
|
||||
# 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 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
|
||||
|
||||
# check installation directory
|
||||
NAME=toolchain-v$TOOLCHAIN_VERSION
|
||||
@@ -387,34 +379,6 @@ if [ ! -f $PREFIX/bin/gdb ]; then
|
||||
--without-babeltrace \
|
||||
--enable-tui \
|
||||
--with-python=python3
|
||||
elif [[ "${DISTRO}" == fedora* ]]; then
|
||||
# Remove readline, gdb does not compile
|
||||
env \
|
||||
CC=gcc \
|
||||
CXX=g++ \
|
||||
CFLAGS="-g -O2 -fstack-protector-strong -Wformat -Werror=format-security" \
|
||||
CXXFLAGS="-g -O2 -fstack-protector-strong -Wformat -Werror=format-security" \
|
||||
CPPFLAGS="-Wdate-time -D_FORTIFY_SOURCE=2 -fPIC" \
|
||||
LDFLAGS="-Wl,-z,relro" \
|
||||
PYTHON="" \
|
||||
../configure \
|
||||
--build=x86_64-linux-gnu \
|
||||
--host=x86_64-linux-gnu \
|
||||
--prefix=$PREFIX \
|
||||
--disable-maintainer-mode \
|
||||
--disable-dependency-tracking \
|
||||
--disable-silent-rules \
|
||||
--disable-gdbtk \
|
||||
--disable-shared \
|
||||
--without-guile \
|
||||
--with-system-gdbinit=$PREFIX/etc/gdb/gdbinit \
|
||||
--with-expat \
|
||||
--with-system-zlib \
|
||||
--with-lzma \
|
||||
--with-babeltrace \
|
||||
--with-intel-pt \
|
||||
--enable-tui \
|
||||
--with-python=python3
|
||||
else
|
||||
# https://buildd.debian.org/status/fetch.php?pkg=gdb&arch=amd64&ver=8.2.1-2&stamp=1550831554&raw=0
|
||||
env \
|
||||
@@ -630,7 +594,7 @@ In order to be able to run all of these tools you should install the following
|
||||
packages:
|
||||
|
||||
\`\`\`
|
||||
$($DIR/../os/$ENV_SCRIPT.sh list TOOLCHAIN_RUN_DEPS)
|
||||
$($DIR/../os/$DISTRO.sh list TOOLCHAIN_RUN_DEPS)
|
||||
\`\`\`
|
||||
|
||||
## Usage
|
||||
@@ -687,7 +651,6 @@ export PS1="($NAME) \$PS1"
|
||||
export LD_LIBRARY_PATH=$PREFIX/lib:$PREFIX/lib64
|
||||
export CXXFLAGS=-isystem\ $PREFIX/include\ \$CXXFLAGS
|
||||
export CFLAGS=-isystem\ $PREFIX/include\ \$CFLAGS
|
||||
export VENV=$PREFIX
|
||||
|
||||
# disable root
|
||||
function su () {
|
||||
@@ -739,7 +702,7 @@ PROXYGEN_SHA256=5360a8ccdfb2f5a6c7b3eed331ec7ab0e2c792d579c6fff499c85c516c11fe14
|
||||
SNAPPY_SHA256=75c1fbb3d618dd3a0483bff0e26d0a92b495bbe5059c8b4f1c962b478b6e06e7
|
||||
SNAPPY_VERSION=1.1.9
|
||||
XZ_VERSION=5.2.5 # for LZMA
|
||||
ZLIB_VERSION=1.2.13
|
||||
ZLIB_VERSION=1.2.12
|
||||
ZSTD_VERSION=1.5.0
|
||||
WANGLE_SHA256=1002e9c32b6f4837f6a760016e3b3e22f3509880ef3eaad191c80dc92655f23f
|
||||
|
||||
@@ -1263,7 +1226,7 @@ popd
|
||||
# create toolchain archive
|
||||
if [ ! -f $NAME-binaries-$DISTRO.tar.gz ]; then
|
||||
DISTRO_FULL_NAME=${DISTRO}
|
||||
if [[ "${DISTRO}" == centos* ]] || [[ "${DISTRO}" == fedora* ]]; then
|
||||
if [[ "${DISTRO}" == centos* ]]; then
|
||||
if [[ "$for_arm" = "true" ]]; then
|
||||
DISTRO_FULL_NAME="$DISTRO_FULL_NAME-aarch64"
|
||||
else
|
||||
|
||||
@@ -19,16 +19,13 @@ function architecture() {
|
||||
}
|
||||
|
||||
check_architecture() {
|
||||
local ARCH=$(architecture)
|
||||
for arch in "$@"; do
|
||||
if [ "${ARCH}" = "$arch" ]; then
|
||||
if [ "$(architecture)" = "$arch" ]; then
|
||||
echo "The right architecture!"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
echo "Not the right architecture!"
|
||||
echo "Expected: $@"
|
||||
echo "Actual: ${ARCH}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -378,10 +378,6 @@ inline mgp_value *vertex_get_property(mgp_vertex *v, const char *property_name,
|
||||
return MgInvoke<mgp_value *>(mgp_vertex_get_property, v, property_name, memory);
|
||||
}
|
||||
|
||||
inline void vertex_set_property(mgp_vertex *v, const char *property_name, mgp_value *property_value) {
|
||||
MgInvokeVoid(mgp_vertex_set_property, v, property_name, property_value);
|
||||
}
|
||||
|
||||
inline mgp_properties_iterator *vertex_iter_properties(mgp_vertex *v, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_properties_iterator *>(mgp_vertex_iter_properties, v, memory);
|
||||
}
|
||||
@@ -414,10 +410,6 @@ inline mgp_value *edge_get_property(mgp_edge *e, const char *property_name, mgp_
|
||||
return MgInvoke<mgp_value *>(mgp_edge_get_property, e, property_name, memory);
|
||||
}
|
||||
|
||||
inline void edge_set_property(mgp_edge *e, const char *property_name, mgp_value *property_value) {
|
||||
MgInvokeVoid(mgp_edge_set_property, e, property_name, property_value);
|
||||
}
|
||||
|
||||
inline mgp_properties_iterator *edge_iter_properties(mgp_edge *e, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_properties_iterator *>(mgp_edge_iter_properties, e, memory);
|
||||
}
|
||||
|
||||
@@ -1,343 +0,0 @@
|
||||
import typing
|
||||
from enum import Enum
|
||||
|
||||
import networkx as nx
|
||||
|
||||
NX_LABEL_ATTR = "labels"
|
||||
NX_TYPE_ATTR = "type"
|
||||
|
||||
SOURCE_TYPE_KAFKA = "SOURCE_TYPE_KAFKA"
|
||||
SOURCE_TYPE_PULSAR = "SOURCE_TYPE_PULSAR"
|
||||
|
||||
"""
|
||||
This module provides helpers for the mock Python API, much like _mgp.py does for mgp.py.
|
||||
"""
|
||||
|
||||
|
||||
class InvalidArgumentError(Exception):
|
||||
"""
|
||||
Signals that some of the arguments have invalid values.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ImmutableObjectError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class LogicErrorError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class DeletedObjectError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class EdgeConstants(Enum):
|
||||
I_START = 0
|
||||
I_END = 1
|
||||
I_KEY = 2
|
||||
|
||||
|
||||
class Graph:
|
||||
"""Wrapper around a NetworkX MultiDiGraph instance."""
|
||||
|
||||
__slots__ = ("nx", "_highest_vertex_id", "_highest_edge_id", "_valid")
|
||||
|
||||
def __init__(self, graph: nx.MultiDiGraph) -> None:
|
||||
if not isinstance(graph, nx.MultiDiGraph):
|
||||
raise TypeError(f"Expected 'networkx.classes.multidigraph.MultiDiGraph', got '{type(graph)}'")
|
||||
|
||||
self.nx = graph
|
||||
self._highest_vertex_id = None
|
||||
self._highest_edge_id = None
|
||||
self._valid = True
|
||||
|
||||
@property
|
||||
def vertex_ids(self):
|
||||
return self.nx.nodes
|
||||
|
||||
def vertex_is_isolate(self, vertex_id: int) -> bool:
|
||||
return nx.is_isolate(self.nx, vertex_id)
|
||||
|
||||
@property
|
||||
def vertices(self):
|
||||
return (Vertex(node_id, self) for node_id in self.nx.nodes)
|
||||
|
||||
def has_node(self, node_id):
|
||||
return self.nx.has_node(node_id)
|
||||
|
||||
@property
|
||||
def edges(self):
|
||||
return self.nx.edges
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
return self._valid
|
||||
|
||||
def get_vertex_by_id(self, vertex_id: int) -> "Vertex":
|
||||
return Vertex(vertex_id, self)
|
||||
|
||||
def invalidate(self):
|
||||
self._valid = False
|
||||
|
||||
def is_immutable(self) -> bool:
|
||||
return nx.is_frozen(self.nx)
|
||||
|
||||
def make_immutable(self):
|
||||
self.nx = nx.freeze(self.nx)
|
||||
|
||||
def _new_vertex_id(self):
|
||||
if self._highest_vertex_id is None:
|
||||
self._highest_vertex_id = max(vertex_id for vertex_id in self.nx.nodes)
|
||||
|
||||
return self._highest_vertex_id + 1
|
||||
|
||||
def _new_edge_id(self):
|
||||
if self._highest_edge_id is None:
|
||||
self._highest_edge_id = max(edge[EdgeConstants.I_KEY.value] for edge in self.nx.edges(keys=True))
|
||||
|
||||
return self._highest_edge_id + 1
|
||||
|
||||
def create_vertex(self) -> "Vertex":
|
||||
vertex_id = self._new_vertex_id()
|
||||
|
||||
self.nx.add_node(vertex_id)
|
||||
self._highest_vertex_id = vertex_id
|
||||
|
||||
return Vertex(vertex_id, self)
|
||||
|
||||
def create_edge(self, from_vertex: "Vertex", to_vertex: "Vertex", edge_type: str) -> "Edge":
|
||||
if from_vertex.is_deleted() or to_vertex.is_deleted():
|
||||
raise DeletedObjectError("Accessing deleted object.")
|
||||
|
||||
edge_id = self._new_edge_id()
|
||||
|
||||
from_id = from_vertex.id
|
||||
to_id = to_vertex.id
|
||||
|
||||
self.nx.add_edge(from_id, to_id, key=edge_id, type=edge_type)
|
||||
self._highest_edge_id = edge_id
|
||||
|
||||
return Edge((from_id, to_id, edge_id), self)
|
||||
|
||||
def delete_vertex(self, vertex_id: int):
|
||||
self.nx.remove_node(vertex_id)
|
||||
|
||||
def delete_edge(self, from_vertex_id: int, to_vertex_id: int, edge_id: int):
|
||||
self.nx.remove_edge(from_vertex_id, to_vertex_id, edge_id)
|
||||
|
||||
@property
|
||||
def highest_vertex_id(self) -> int:
|
||||
if self._highest_vertex_id is None:
|
||||
self._highest_vertex_id = max(vertex_id for vertex_id in self.nx.nodes) + 1
|
||||
|
||||
return self._highest_vertex_id
|
||||
|
||||
@property
|
||||
def highest_edge_id(self) -> int:
|
||||
if self._highest_edge_id is None:
|
||||
self._highest_edge_id = max(edge[EdgeConstants.I_KEY.value] for edge in self.nx.edges(keys=True))
|
||||
|
||||
return self._highest_edge_id + 1
|
||||
|
||||
|
||||
class Vertex:
|
||||
"""Represents a graph vertex."""
|
||||
|
||||
__slots__ = ("_id", "_graph")
|
||||
|
||||
def __init__(self, id: int, graph: Graph) -> None:
|
||||
if not isinstance(id, int):
|
||||
raise TypeError(f"Expected 'int', got '{type(id)}'")
|
||||
|
||||
if not isinstance(graph, Graph):
|
||||
raise TypeError(f"Expected '_mgp_mock.Graph', got '{type(graph)}'")
|
||||
|
||||
if not graph.nx.has_node(id):
|
||||
raise IndexError(f"Unable to find vertex with ID {id}.")
|
||||
|
||||
self._id = id
|
||||
self._graph = graph
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
return self._graph.is_valid()
|
||||
|
||||
def is_deleted(self) -> bool:
|
||||
return not self._graph.nx.has_node(self._id) and self._id <= self._graph.highest_vertex_id
|
||||
|
||||
@property
|
||||
def underlying_graph(self) -> Graph:
|
||||
return self._graph
|
||||
|
||||
def underlying_graph_is_mutable(self) -> bool:
|
||||
return not nx.is_frozen(self._graph.nx)
|
||||
|
||||
@property
|
||||
def labels(self) -> typing.List[int]:
|
||||
return self._graph.nx.nodes[self._id][NX_LABEL_ATTR].split(":")
|
||||
|
||||
def add_label(self, label: str) -> None:
|
||||
if nx.is_frozen(self._graph.nx):
|
||||
raise ImmutableObjectError("Cannot modify immutable object.")
|
||||
|
||||
self._graph.nx.nodes[self._id][NX_LABEL_ATTR] += f":{label}"
|
||||
|
||||
def remove_label(self, label: str) -> None:
|
||||
if nx.is_frozen(self._graph.nx):
|
||||
raise ImmutableObjectError("Cannot modify immutable object.")
|
||||
|
||||
labels = self._graph.nx.nodes[self._id][NX_LABEL_ATTR]
|
||||
if labels.startswith(f"{label}:"):
|
||||
labels = "\n" + labels # pseudo-string starter
|
||||
self._graph.nx.nodes[self._id][NX_LABEL_ATTR] = labels.replace(f"\n{label}:", "")
|
||||
elif labels.endswith(f":{label}"):
|
||||
labels += "\n" # pseudo-string terminator
|
||||
self._graph.nx.nodes[self._id][NX_LABEL_ATTR] = labels.replace(f":{label}\n", "")
|
||||
else:
|
||||
self._graph.nx.nodes[self._id][NX_LABEL_ATTR] = labels.replace(f":{label}:", ":")
|
||||
|
||||
@property
|
||||
def id(self) -> int:
|
||||
return self._id
|
||||
|
||||
@property
|
||||
def properties(self):
|
||||
return (
|
||||
(key, value)
|
||||
for key, value in self._graph.nx.nodes[self._id].items()
|
||||
if key not in (NX_LABEL_ATTR, NX_TYPE_ATTR)
|
||||
)
|
||||
|
||||
def get_property(self, property_name: str):
|
||||
return self._graph.nx.nodes[self._id][property_name]
|
||||
|
||||
def set_property(self, property_name: str, value: object):
|
||||
self._graph.nx.nodes[self._id][property_name] = value
|
||||
|
||||
@property
|
||||
def in_edges(self) -> typing.Iterable["Edge"]:
|
||||
return [Edge(edge, self._graph) for edge in self._graph.nx.in_edges(self._id, keys=True)]
|
||||
|
||||
@property
|
||||
def out_edges(self) -> typing.Iterable["Edge"]:
|
||||
return [Edge(edge, self._graph) for edge in self._graph.nx.out_edges(self._id, keys=True)]
|
||||
|
||||
|
||||
class Edge:
|
||||
"""Represents a graph edge."""
|
||||
|
||||
__slots__ = ("_edge", "_graph")
|
||||
|
||||
def __init__(self, edge: typing.Tuple[int, int, int], graph: Graph) -> None:
|
||||
if not isinstance(edge, typing.Tuple):
|
||||
raise TypeError(f"Expected 'Tuple', got '{type(edge)}'")
|
||||
|
||||
if not isinstance(graph, Graph):
|
||||
raise TypeError(f"Expected '_mgp_mock.Graph', got '{type(graph)}'")
|
||||
|
||||
if not graph.nx.has_edge(*edge):
|
||||
raise IndexError(f"Unable to find edge with ID {edge[EdgeConstants.I_KEY.value]}.")
|
||||
|
||||
self._edge = edge
|
||||
self._graph = graph
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
return self._graph.is_valid()
|
||||
|
||||
def is_deleted(self) -> bool:
|
||||
return (
|
||||
not self._graph.nx.has_edge(*self._edge)
|
||||
and self._edge[EdgeConstants.I_KEY.value] <= self._graph.highest_edge_id
|
||||
)
|
||||
|
||||
def underlying_graph_is_mutable(self) -> bool:
|
||||
return not nx.is_frozen(self._graph.nx)
|
||||
|
||||
@property
|
||||
def id(self) -> int:
|
||||
return self._edge[EdgeConstants.I_KEY.value]
|
||||
|
||||
@property
|
||||
def edge(self) -> typing.Tuple[int, int, int]:
|
||||
return self._edge
|
||||
|
||||
@property
|
||||
def start_id(self) -> int:
|
||||
return self._edge[EdgeConstants.I_START.value]
|
||||
|
||||
@property
|
||||
def end_id(self) -> int:
|
||||
return self._edge[EdgeConstants.I_END.value]
|
||||
|
||||
def get_type_name(self):
|
||||
return self._graph.nx.get_edge_data(*self._edge)[NX_TYPE_ATTR]
|
||||
|
||||
def from_vertex(self) -> Vertex:
|
||||
return Vertex(self.start_id, self._graph)
|
||||
|
||||
def to_vertex(self) -> Vertex:
|
||||
return Vertex(self.end_id, self._graph)
|
||||
|
||||
@property
|
||||
def properties(self):
|
||||
return (
|
||||
(key, value)
|
||||
for key, value in self._graph.nx.edges[self._edge].items()
|
||||
if key not in (NX_LABEL_ATTR, NX_TYPE_ATTR)
|
||||
)
|
||||
|
||||
def get_property(self, property_name: str):
|
||||
return self._graph.nx.edges[self._edge][property_name]
|
||||
|
||||
def set_property(self, property_name: str, value: object):
|
||||
self._graph.nx.edges[self._edge][property_name] = value
|
||||
|
||||
|
||||
class Path:
|
||||
"""Represents a path comprised of `Vertex` and `Edge` instances."""
|
||||
|
||||
__slots__ = ("_vertices", "_edges", "_graph")
|
||||
__create_key = object()
|
||||
|
||||
def __init__(self, create_key, vertex_id: int, graph: Graph) -> None:
|
||||
assert create_key == Path.__create_key, "Path objects must be created using Path.make_with_start"
|
||||
|
||||
self._vertices = [vertex_id]
|
||||
self._edges = []
|
||||
self._graph = graph
|
||||
|
||||
@classmethod
|
||||
def make_with_start(cls, vertex: Vertex) -> "Path":
|
||||
if not isinstance(vertex, Vertex):
|
||||
raise TypeError(f"Expected 'Vertex', got '{type(vertex)}'")
|
||||
|
||||
if not isinstance(vertex.underlying_graph, Graph):
|
||||
raise TypeError(f"Expected '_mgp_mock.Graph', got '{type(vertex.underlying_graph)}'")
|
||||
|
||||
if not vertex.underlying_graph.nx.has_node(vertex._id):
|
||||
raise IndexError(f"Unable to find vertex with ID {vertex._id}.")
|
||||
|
||||
return Path(cls.__create_key, vertex._id, vertex.underlying_graph)
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
return self._graph.is_valid()
|
||||
|
||||
def underlying_graph_is_mutable(self) -> bool:
|
||||
return not nx.is_frozen(self._graph.nx)
|
||||
|
||||
def expand(self, edge: Edge):
|
||||
if edge.start_id != self._vertices[-1]:
|
||||
raise LogicErrorError("Logic error.")
|
||||
|
||||
self._vertices.append(edge.end_id)
|
||||
self._edges.append((edge.start_id, edge.end_id, edge.id))
|
||||
|
||||
def vertex_at(self, index: int) -> Vertex:
|
||||
return Vertex(self._vertices[index], self._graph)
|
||||
|
||||
def edge_at(self, index: int) -> Edge:
|
||||
return Edge(self._edges[index], self._graph)
|
||||
|
||||
def size(self) -> int:
|
||||
return len(self._edges)
|
||||
216
include/mgp.hpp
216
include/mgp.hpp
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -66,12 +66,9 @@ struct MapItem;
|
||||
class Duration;
|
||||
class Value;
|
||||
|
||||
struct StealType {};
|
||||
inline constexpr StealType steal{};
|
||||
|
||||
inline mgp_memory *memory{nullptr};
|
||||
|
||||
/* #region Graph (Id, Graph, Nodes, GraphRelationships, Relationships & Labels) */
|
||||
/* #region Graph (Id, Graph, Nodes, GraphRelationships, Relationships, Properties & Labels) */
|
||||
|
||||
/// Wrapper for int64_t IDs to prevent dangerous implicit conversions.
|
||||
class Id {
|
||||
@@ -162,7 +159,7 @@ class Nodes {
|
||||
|
||||
explicit Iterator(mgp_vertices_iterator *nodes_iterator);
|
||||
|
||||
Iterator(const Iterator &other) noexcept;
|
||||
Iterator(const Iterator &other);
|
||||
Iterator &operator=(const Iterator &other) = delete;
|
||||
|
||||
~Iterator();
|
||||
@@ -210,7 +207,7 @@ class GraphRelationships {
|
||||
|
||||
explicit Iterator(mgp_vertices_iterator *nodes_iterator);
|
||||
|
||||
Iterator(const Iterator &other) noexcept;
|
||||
Iterator(const Iterator &other);
|
||||
Iterator &operator=(const Iterator &other) = delete;
|
||||
|
||||
~Iterator();
|
||||
@@ -256,7 +253,7 @@ class Relationships {
|
||||
|
||||
explicit Iterator(mgp_edges_iterator *relationships_iterator);
|
||||
|
||||
Iterator(const Iterator &other) noexcept;
|
||||
Iterator(const Iterator &other);
|
||||
Iterator &operator=(const Iterator &other) = delete;
|
||||
|
||||
~Iterator();
|
||||
@@ -284,12 +281,46 @@ class Relationships {
|
||||
mgp_edges_iterator *relationships_iterator_ = nullptr;
|
||||
};
|
||||
|
||||
/// @brief View of node properties.
|
||||
class Properties {
|
||||
public:
|
||||
explicit Properties(mgp_properties_iterator *properties_iterator);
|
||||
|
||||
/// @brief Returns the size of the properties map.
|
||||
size_t Size() const;
|
||||
/// @brief Returns whether the properties map is empty.
|
||||
bool Empty() const;
|
||||
|
||||
/// @brief Returns the value associated with the given `key`. If there’s no such value, the behavior is undefined.
|
||||
/// @note Each key-value pair needs to be checked, ensuing O(n) time complexity.
|
||||
Value operator[](const std::string_view key) const;
|
||||
|
||||
std::map<std::string_view, Value>::const_iterator begin() const;
|
||||
std::map<std::string_view, Value>::const_iterator end() const;
|
||||
|
||||
std::map<std::string_view, Value>::const_iterator cbegin() const;
|
||||
std::map<std::string_view, Value>::const_iterator cend() const;
|
||||
|
||||
/// @brief Returns the key-value iterator for the given `key`. If there’s no such pair, returns the end of the
|
||||
/// iterator.
|
||||
/// @note Each key-value pair needs to be checked, ensuing O(n) time complexity.
|
||||
std::map<std::string_view, Value>::const_iterator find(const std::string_view key) const;
|
||||
|
||||
/// @exception std::runtime_error Map contains value(s) of unknown type.
|
||||
bool operator==(const Properties &other) const;
|
||||
/// @exception std::runtime_error Map contains value(s) of unknown type.
|
||||
bool operator!=(const Properties &other) const;
|
||||
|
||||
private:
|
||||
std::map<const std::string_view, Value> property_map_;
|
||||
};
|
||||
|
||||
/// @brief View of node labels.
|
||||
class Labels {
|
||||
public:
|
||||
explicit Labels(mgp_vertex *node_ptr);
|
||||
|
||||
Labels(const Labels &other) noexcept;
|
||||
Labels(const Labels &other);
|
||||
Labels(Labels &&other) noexcept;
|
||||
|
||||
Labels &operator=(const Labels &other) noexcept;
|
||||
@@ -332,7 +363,6 @@ class Labels {
|
||||
private:
|
||||
mgp_vertex *node_ptr_;
|
||||
};
|
||||
|
||||
/* #endregion */
|
||||
|
||||
/* #region Types */
|
||||
@@ -367,7 +397,7 @@ class List {
|
||||
/// @brief Creates a List from the given initializer_list.
|
||||
explicit List(const std::initializer_list<Value> list);
|
||||
|
||||
List(const List &other) noexcept;
|
||||
List(const List &other);
|
||||
List(List &&other) noexcept;
|
||||
|
||||
List &operator=(const List &other) noexcept;
|
||||
@@ -459,7 +489,7 @@ class Map {
|
||||
/// @brief Creates a Map from the given initializer_list (map items correspond to initializer list pairs).
|
||||
Map(const std::initializer_list<std::pair<std::string_view, Value>> items);
|
||||
|
||||
Map(const Map &other) noexcept;
|
||||
Map(const Map &other);
|
||||
Map(Map &&other) noexcept;
|
||||
|
||||
Map &operator=(const Map &other) noexcept;
|
||||
@@ -489,7 +519,7 @@ class Map {
|
||||
|
||||
explicit Iterator(mgp_map_items_iterator *map_items_iterator);
|
||||
|
||||
Iterator(const Iterator &other) noexcept;
|
||||
Iterator(const Iterator &other);
|
||||
Iterator &operator=(const Iterator &other) = delete;
|
||||
|
||||
~Iterator();
|
||||
@@ -548,7 +578,7 @@ class Node {
|
||||
/// @brief Creates a Node from the copy of the given @ref mgp_vertex.
|
||||
explicit Node(const mgp_vertex *const_ptr);
|
||||
|
||||
Node(const Node &other) noexcept;
|
||||
Node(const Node &other);
|
||||
Node(Node &&other) noexcept;
|
||||
|
||||
Node &operator=(const Node &other) noexcept;
|
||||
@@ -560,19 +590,16 @@ class Node {
|
||||
mgp::Id Id() const;
|
||||
|
||||
/// @brief Returns an iterable & indexable structure of the node’s labels.
|
||||
mgp::Labels Labels() const;
|
||||
class Labels Labels() const;
|
||||
|
||||
/// @brief Returns whether the node has the given `label`.
|
||||
bool HasLabel(std::string_view label) const;
|
||||
|
||||
/// @brief Returns an std::map of the node’s properties.
|
||||
std::map<std::string, Value> Properties() const;
|
||||
/// @brief Returns an iterable & indexable structure of the node’s properties.
|
||||
class Properties Properties() const;
|
||||
|
||||
/// @brief Sets the chosen property to the given value.
|
||||
void SetProperty(std::string property, Value value);
|
||||
|
||||
/// @brief Retrieves the value of the chosen property.
|
||||
Value GetProperty(const std::string &property) const;
|
||||
/// @brief Returns the value of the node’s `property_name` property.
|
||||
Value operator[](const std::string_view property_name) const;
|
||||
|
||||
/// @brief Returns an iterable structure of the node’s inbound relationships.
|
||||
Relationships InRelationships() const;
|
||||
@@ -608,7 +635,7 @@ class Relationship {
|
||||
/// @brief Creates a Relationship from the copy of the given @ref mgp_edge.
|
||||
explicit Relationship(const mgp_edge *const_ptr);
|
||||
|
||||
Relationship(const Relationship &other) noexcept;
|
||||
Relationship(const Relationship &other);
|
||||
Relationship(Relationship &&other) noexcept;
|
||||
|
||||
Relationship &operator=(const Relationship &other) noexcept;
|
||||
@@ -622,14 +649,11 @@ class Relationship {
|
||||
/// @brief Returns the relationship’s type.
|
||||
std::string_view Type() const;
|
||||
|
||||
/// @brief Returns an std::map of the relationship’s properties.
|
||||
std::map<std::string, Value> Properties() const;
|
||||
/// @brief Returns an iterable & indexable structure of the relationship’s properties.
|
||||
class Properties Properties() const;
|
||||
|
||||
/// @brief Sets the chosen property to the given value.
|
||||
void SetProperty(std::string property, Value value);
|
||||
|
||||
/// @brief Retrieves the value of the chosen property.
|
||||
Value GetProperty(const std::string &property) const;
|
||||
/// @brief Returns the value of the relationship’s `property_name` property.
|
||||
Value operator[](const std::string_view property_name) const;
|
||||
|
||||
/// @brief Returns the relationship’s source node.
|
||||
Node From() const;
|
||||
@@ -664,7 +688,7 @@ class Path {
|
||||
/// @brief Creates a Path starting with the given `start_node`.
|
||||
explicit Path(const Node &start_node);
|
||||
|
||||
Path(const Path &other) noexcept;
|
||||
Path(const Path &other);
|
||||
Path(Path &&other) noexcept;
|
||||
|
||||
Path &operator=(const Path &other) noexcept;
|
||||
@@ -720,7 +744,7 @@ class Date {
|
||||
/// @brief Creates a Date object with the given `year`, `month`, and `day` properties.
|
||||
Date(int year, int month, int day);
|
||||
|
||||
Date(const Date &other) noexcept;
|
||||
Date(const Date &other);
|
||||
Date(Date &&other) noexcept;
|
||||
|
||||
Date &operator=(const Date &other) noexcept;
|
||||
@@ -775,7 +799,7 @@ class LocalTime {
|
||||
/// properties.
|
||||
LocalTime(int hour, int minute, int second, int millisecond, int microsecond);
|
||||
|
||||
LocalTime(const LocalTime &other) noexcept;
|
||||
LocalTime(const LocalTime &other);
|
||||
LocalTime(LocalTime &&other) noexcept;
|
||||
|
||||
LocalTime &operator=(const LocalTime &other) noexcept;
|
||||
@@ -834,7 +858,7 @@ class LocalDateTime {
|
||||
/// `millisecond`, and `microsecond` properties.
|
||||
LocalDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond);
|
||||
|
||||
LocalDateTime(const LocalDateTime &other) noexcept;
|
||||
LocalDateTime(const LocalDateTime &other);
|
||||
LocalDateTime(LocalDateTime &&other) noexcept;
|
||||
|
||||
LocalDateTime &operator=(const LocalDateTime &other) noexcept;
|
||||
@@ -905,7 +929,7 @@ class Duration {
|
||||
/// `microsecond` properties.
|
||||
Duration(double day, double hour, double minute, double second, double millisecond, double microsecond);
|
||||
|
||||
Duration(const Duration &other) noexcept;
|
||||
Duration(const Duration &other);
|
||||
Duration(Duration &&other) noexcept;
|
||||
|
||||
Duration &operator=(const Duration &other) noexcept;
|
||||
@@ -962,8 +986,6 @@ class Value {
|
||||
|
||||
explicit Value(mgp_value *ptr);
|
||||
|
||||
explicit Value(StealType /*steal*/, mgp_value *ptr);
|
||||
|
||||
// Null constructor:
|
||||
explicit Value();
|
||||
|
||||
@@ -1034,7 +1056,7 @@ class Value {
|
||||
/// @note The behavior of accessing `duration` after performing this operation is undefined.
|
||||
explicit Value(Duration &&duration);
|
||||
|
||||
Value(const Value &other) noexcept;
|
||||
Value(const Value &other);
|
||||
Value(Value &&other) noexcept;
|
||||
|
||||
Value &operator=(const Value &other) noexcept;
|
||||
@@ -1682,7 +1704,7 @@ inline Nodes::Iterator::Iterator(mgp_vertices_iterator *nodes_iterator) : nodes_
|
||||
}
|
||||
}
|
||||
|
||||
inline Nodes::Iterator::Iterator(const Iterator &other) noexcept : Iterator(other.nodes_iterator_) {}
|
||||
inline Nodes::Iterator::Iterator(const Iterator &other) : Iterator(other.nodes_iterator_) {}
|
||||
|
||||
inline Nodes::Iterator::~Iterator() {
|
||||
if (nodes_iterator_ != nullptr) {
|
||||
@@ -1773,7 +1795,7 @@ inline GraphRelationships::Iterator::Iterator(mgp_vertices_iterator *nodes_itera
|
||||
}
|
||||
}
|
||||
|
||||
inline GraphRelationships::Iterator::Iterator(const Iterator &other) noexcept : Iterator(other.nodes_iterator_) {}
|
||||
inline GraphRelationships::Iterator::Iterator(const Iterator &other) : Iterator(other.nodes_iterator_) {}
|
||||
|
||||
inline GraphRelationships::Iterator::~Iterator() {
|
||||
if (nodes_iterator_ != nullptr) {
|
||||
@@ -1792,12 +1814,10 @@ inline GraphRelationships::Iterator &GraphRelationships::Iterator::operator++()
|
||||
if (out_relationships_iterator_ != nullptr) {
|
||||
auto next = mgp::edges_iterator_next(out_relationships_iterator_);
|
||||
|
||||
if (next != nullptr) {
|
||||
return *this;
|
||||
if (next == nullptr) {
|
||||
mgp::edges_iterator_destroy(out_relationships_iterator_);
|
||||
out_relationships_iterator_ = nullptr;
|
||||
}
|
||||
|
||||
mgp::edges_iterator_destroy(out_relationships_iterator_);
|
||||
out_relationships_iterator_ = nullptr;
|
||||
}
|
||||
|
||||
// 2. Move onto the next nodes
|
||||
@@ -1884,7 +1904,7 @@ inline Relationships::Iterator::Iterator(mgp_edges_iterator *relationships_itera
|
||||
}
|
||||
}
|
||||
|
||||
inline Relationships::Iterator::Iterator(const Iterator &other) noexcept : Iterator(other.relationships_iterator_) {}
|
||||
inline Relationships::Iterator::Iterator(const Iterator &other) : Iterator(other.relationships_iterator_) {}
|
||||
|
||||
inline Relationships::Iterator::~Iterator() {
|
||||
if (relationships_iterator_ != nullptr) {
|
||||
@@ -1943,11 +1963,40 @@ inline Relationships::Iterator Relationships::cbegin() const { return Iterator(r
|
||||
|
||||
inline Relationships::Iterator Relationships::cend() const { return Iterator(nullptr); }
|
||||
|
||||
// Properties:
|
||||
|
||||
inline Properties::Properties(mgp_properties_iterator *properties_iterator) {
|
||||
for (auto property = mgp::properties_iterator_get(properties_iterator); property;
|
||||
property = mgp::properties_iterator_next(properties_iterator)) {
|
||||
auto value = Value(property->value);
|
||||
property_map_.emplace(property->name, value);
|
||||
}
|
||||
mgp::properties_iterator_destroy(properties_iterator);
|
||||
}
|
||||
|
||||
inline size_t Properties::Size() const { return property_map_.size(); }
|
||||
|
||||
inline bool Properties::Empty() const { return Size() == 0; }
|
||||
|
||||
inline Value Properties::operator[](const std::string_view key) const { return property_map_.at(key); }
|
||||
|
||||
inline std::map<std::string_view, Value>::const_iterator Properties::begin() const { return property_map_.begin(); }
|
||||
|
||||
inline std::map<std::string_view, Value>::const_iterator Properties::end() const { return property_map_.end(); }
|
||||
|
||||
inline std::map<std::string_view, Value>::const_iterator Properties::cbegin() const { return property_map_.cbegin(); }
|
||||
|
||||
inline std::map<std::string_view, Value>::const_iterator Properties::cend() const { return property_map_.cend(); }
|
||||
|
||||
inline bool Properties::operator==(const Properties &other) const { return property_map_ == other.property_map_; }
|
||||
|
||||
inline bool Properties::operator!=(const Properties &other) const { return !(*this == other); }
|
||||
|
||||
// Labels:
|
||||
|
||||
inline Labels::Labels(mgp_vertex *node_ptr) : node_ptr_(mgp::vertex_copy(node_ptr, memory)) {}
|
||||
|
||||
inline Labels::Labels(const Labels &other) noexcept : Labels(other.node_ptr_) {}
|
||||
inline Labels::Labels(const Labels &other) : Labels(other.node_ptr_) {}
|
||||
|
||||
inline Labels::Labels(Labels &&other) noexcept : node_ptr_(other.node_ptr_) { other.node_ptr_ = nullptr; }
|
||||
|
||||
@@ -2037,7 +2086,7 @@ inline List::List(const std::initializer_list<Value> values) : ptr_(mgp::list_ma
|
||||
}
|
||||
}
|
||||
|
||||
inline List::List(const List &other) noexcept : List(other.ptr_) {}
|
||||
inline List::List(const List &other) : List(other.ptr_) {}
|
||||
|
||||
inline List::List(List &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
|
||||
|
||||
@@ -2145,7 +2194,7 @@ inline Map::Map(const std::initializer_list<std::pair<std::string_view, Value>>
|
||||
}
|
||||
}
|
||||
|
||||
inline Map::Map(const Map &other) noexcept : Map(other.ptr_) {}
|
||||
inline Map::Map(const Map &other) : Map(other.ptr_) {}
|
||||
|
||||
inline Map::Map(Map &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
|
||||
|
||||
@@ -2190,7 +2239,7 @@ inline Map::Iterator::Iterator(mgp_map_items_iterator *map_items_iterator) : map
|
||||
}
|
||||
}
|
||||
|
||||
inline Map::Iterator::Iterator(const Iterator &other) noexcept : Iterator(other.map_items_iterator_) {}
|
||||
inline Map::Iterator::Iterator(const Iterator &other) : Iterator(other.map_items_iterator_) {}
|
||||
|
||||
inline Map::Iterator::~Iterator() {
|
||||
if (map_items_iterator_ != nullptr) {
|
||||
@@ -2257,6 +2306,10 @@ inline void Map::Insert(std::string_view key, Value &&value) {
|
||||
value.ptr_ = nullptr;
|
||||
}
|
||||
|
||||
inline std::map<std::string_view, Value>::const_iterator Properties::find(const std::string_view key) const {
|
||||
return property_map_.find(key);
|
||||
}
|
||||
|
||||
inline bool Map::operator==(const Map &other) const { return util::MapsEqual(ptr_, other.ptr_); }
|
||||
|
||||
inline bool Map::operator!=(const Map &other) const { return !(*this == other); }
|
||||
@@ -2271,7 +2324,7 @@ inline Node::Node(mgp_vertex *ptr) : ptr_(mgp::vertex_copy(ptr, memory)) {}
|
||||
|
||||
inline Node::Node(const mgp_vertex *const_ptr) : ptr_(mgp::vertex_copy(const_cast<mgp_vertex *>(const_ptr), memory)) {}
|
||||
|
||||
inline Node::Node(const Node &other) noexcept : Node(other.ptr_) {}
|
||||
inline Node::Node(const Node &other) : Node(other.ptr_) {}
|
||||
|
||||
inline Node::Node(Node &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
|
||||
|
||||
@@ -2302,7 +2355,7 @@ inline Node::~Node() {
|
||||
|
||||
inline mgp::Id Node::Id() const { return Id::FromInt(mgp::vertex_get_id(ptr_).as_int); }
|
||||
|
||||
inline mgp::Labels Node::Labels() const { return mgp::Labels(ptr_); }
|
||||
inline class Labels Node::Labels() const { return mgp::Labels(ptr_); }
|
||||
|
||||
inline bool Node::HasLabel(std::string_view label) const {
|
||||
for (const auto node_label : Labels()) {
|
||||
@@ -2313,6 +2366,10 @@ inline bool Node::HasLabel(std::string_view label) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
inline class Properties Node::Properties() const { return mgp::Properties(mgp::vertex_iter_properties(ptr_, memory)); }
|
||||
|
||||
inline Value Node::operator[](const std::string_view property_name) const { return Properties()[property_name]; }
|
||||
|
||||
inline Relationships Node::InRelationships() const {
|
||||
auto relationship_iterator = mgp::vertex_iter_in_edges(ptr_, memory);
|
||||
if (relationship_iterator == nullptr) {
|
||||
@@ -2333,26 +2390,6 @@ inline void Node::AddLabel(const std::string_view label) {
|
||||
mgp::vertex_add_label(this->ptr_, mgp_label{.name = label.data()});
|
||||
}
|
||||
|
||||
inline std::map<std::string, Value> Node::Properties() const {
|
||||
mgp_properties_iterator *properties_iterator = mgp::vertex_iter_properties(ptr_, memory);
|
||||
std::map<std::string, Value> property_map;
|
||||
for (auto *property = mgp::properties_iterator_get(properties_iterator); property;
|
||||
property = mgp::properties_iterator_next(properties_iterator)) {
|
||||
property_map.emplace(std::string(property->name), Value(property->value));
|
||||
}
|
||||
mgp::properties_iterator_destroy(properties_iterator);
|
||||
return property_map;
|
||||
}
|
||||
|
||||
inline void Node::SetProperty(std::string property, Value value) {
|
||||
mgp::vertex_set_property(ptr_, property.data(), value.ptr());
|
||||
}
|
||||
|
||||
inline Value Node::GetProperty(const std::string &property) const {
|
||||
mgp_value *vertex_prop = mgp::vertex_get_property(ptr_, property.data(), memory);
|
||||
return Value(steal, vertex_prop);
|
||||
}
|
||||
|
||||
inline bool Node::operator<(const Node &other) const { return Id() < other.Id(); }
|
||||
|
||||
inline bool Node::operator==(const Node &other) const { return util::NodesEqual(ptr_, other.ptr_); }
|
||||
@@ -2366,7 +2403,7 @@ inline Relationship::Relationship(mgp_edge *ptr) : ptr_(mgp::edge_copy(ptr, memo
|
||||
inline Relationship::Relationship(const mgp_edge *const_ptr)
|
||||
: ptr_(mgp::edge_copy(const_cast<mgp_edge *>(const_ptr), memory)) {}
|
||||
|
||||
inline Relationship::Relationship(const Relationship &other) noexcept : Relationship(other.ptr_) {}
|
||||
inline Relationship::Relationship(const Relationship &other) : Relationship(other.ptr_) {}
|
||||
|
||||
inline Relationship::Relationship(Relationship &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
|
||||
|
||||
@@ -2399,24 +2436,12 @@ inline mgp::Id Relationship::Id() const { return Id::FromInt(mgp::edge_get_id(pt
|
||||
|
||||
inline std::string_view Relationship::Type() const { return mgp::edge_get_type(ptr_).name; }
|
||||
|
||||
inline std::map<std::string, Value> Relationship::Properties() const {
|
||||
mgp_properties_iterator *properties_iterator = mgp::edge_iter_properties(ptr_, memory);
|
||||
std::map<std::string, Value> property_map;
|
||||
for (mgp_property *property = mgp::properties_iterator_get(properties_iterator); property;
|
||||
property = mgp::properties_iterator_next(properties_iterator)) {
|
||||
property_map.emplace(property->name, Value(property->value));
|
||||
}
|
||||
mgp::properties_iterator_destroy(properties_iterator);
|
||||
return property_map;
|
||||
inline class Properties Relationship::Properties() const {
|
||||
return mgp::Properties(mgp::edge_iter_properties(ptr_, memory));
|
||||
}
|
||||
|
||||
inline void Relationship::SetProperty(std::string property, Value value) {
|
||||
mgp::edge_set_property(ptr_, property.data(), value.ptr());
|
||||
}
|
||||
|
||||
inline Value Relationship::GetProperty(const std::string &property) const {
|
||||
mgp_value *edge_prop = mgp::edge_get_property(ptr_, property.data(), memory);
|
||||
return Value(steal, edge_prop);
|
||||
inline Value Relationship::operator[](const std::string_view property_name) const {
|
||||
return Properties()[property_name];
|
||||
}
|
||||
|
||||
inline Node Relationship::From() const { return Node(mgp::edge_get_from(ptr_)); }
|
||||
@@ -2439,7 +2464,7 @@ inline Path::Path(const mgp_path *const_ptr) : ptr_(mgp::path_copy(const_cast<mg
|
||||
|
||||
inline Path::Path(const Node &start_node) : ptr_(mgp::path_make_with_start(start_node.ptr_, memory)) {}
|
||||
|
||||
inline Path::Path(const Path &other) noexcept : Path(other.ptr_) {}
|
||||
inline Path::Path(const Path &other) : Path(other.ptr_) {}
|
||||
|
||||
inline Path::Path(Path &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
|
||||
|
||||
@@ -2508,7 +2533,7 @@ inline Date::Date(int year, int month, int day) {
|
||||
ptr_ = mgp::date_from_parameters(¶ms, memory);
|
||||
}
|
||||
|
||||
inline Date::Date(const Date &other) noexcept : Date(other.ptr_) {}
|
||||
inline Date::Date(const Date &other) : Date(other.ptr_) {}
|
||||
|
||||
inline Date::Date(Date &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
|
||||
|
||||
@@ -2602,7 +2627,7 @@ inline LocalTime::LocalTime(int hour, int minute, int second, int millisecond, i
|
||||
ptr_ = mgp::local_time_from_parameters(¶ms, memory);
|
||||
}
|
||||
|
||||
inline LocalTime::LocalTime(const LocalTime &other) noexcept : LocalTime(other.ptr_) {}
|
||||
inline LocalTime::LocalTime(const LocalTime &other) : LocalTime(other.ptr_) {}
|
||||
|
||||
inline LocalTime::LocalTime(LocalTime &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; };
|
||||
|
||||
@@ -2707,7 +2732,7 @@ inline LocalDateTime::LocalDateTime(int year, int month, int day, int hour, int
|
||||
ptr_ = mgp::local_date_time_from_parameters(¶ms, memory);
|
||||
}
|
||||
|
||||
inline LocalDateTime::LocalDateTime(const LocalDateTime &other) noexcept : LocalDateTime(other.ptr_) {}
|
||||
inline LocalDateTime::LocalDateTime(const LocalDateTime &other) : LocalDateTime(other.ptr_) {}
|
||||
|
||||
inline LocalDateTime::LocalDateTime(LocalDateTime &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; };
|
||||
|
||||
@@ -2820,7 +2845,7 @@ inline Duration::Duration(double day, double hour, double minute, double second,
|
||||
ptr_ = mgp::duration_from_parameters(¶ms, memory);
|
||||
}
|
||||
|
||||
inline Duration::Duration(const Duration &other) noexcept : Duration(other.ptr_) {}
|
||||
inline Duration::Duration(const Duration &other) : Duration(other.ptr_) {}
|
||||
|
||||
inline Duration::Duration(Duration &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; };
|
||||
|
||||
@@ -2892,7 +2917,6 @@ inline bool Duration::operator<(const Duration &other) const {
|
||||
/* #region Value */
|
||||
|
||||
inline Value::Value(mgp_value *ptr) : ptr_(mgp::value_copy(ptr, memory)) {}
|
||||
inline Value::Value(StealType /*steal*/, mgp_value *ptr) : ptr_{ptr} {}
|
||||
|
||||
inline Value::Value() : ptr_(mgp::value_make_null(memory)) {}
|
||||
|
||||
@@ -2973,7 +2997,7 @@ inline Value::Value(Duration &&duration) {
|
||||
duration.ptr_ = nullptr;
|
||||
}
|
||||
|
||||
inline Value::Value(const Value &other) noexcept : Value(other.ptr_) {}
|
||||
inline Value::Value(const Value &other) : Value(other.ptr_) {}
|
||||
|
||||
inline Value::Value(Value &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
|
||||
|
||||
|
||||
@@ -1283,7 +1283,7 @@ class Graph:
|
||||
raise InvalidContextError()
|
||||
self._graph.detach_delete_vertex(vertex._vertex)
|
||||
|
||||
def create_edge(self, from_vertex: Vertex, to_vertex: Vertex, edge_type: EdgeType) -> Edge:
|
||||
def create_edge(self, from_vertex: Vertex, to_vertex: Vertex, edge_type: EdgeType) -> None:
|
||||
"""
|
||||
Create an edge.
|
||||
|
||||
@@ -1292,16 +1292,13 @@ class Graph:
|
||||
to_vertex: `Vertex' to where edge is directed.
|
||||
edge_type: `EdgeType` defines the type of edge.
|
||||
|
||||
Returns:
|
||||
Created `Edge`.
|
||||
|
||||
Raises:
|
||||
ImmutableObjectError: If `graph` is immutable.
|
||||
UnableToAllocateError: If unable to allocate an edge.
|
||||
DeletedObjectError: If `from_vertex` or `to_vertex` has been deleted.
|
||||
SerializationError: If `from_vertex` or `to_vertex` has been modified by another transaction.
|
||||
Examples:
|
||||
```edge = graph.create_edge(from_vertex, vertex, edge_type)```
|
||||
```graph.create_edge(from_vertex, vertex, edge_type)```
|
||||
"""
|
||||
if not self.is_valid():
|
||||
raise InvalidContextError()
|
||||
|
||||
1655
include/mgp_mock.py
1655
include/mgp_mock.py
File diff suppressed because it is too large
Load Diff
8
init
8
init
@@ -113,8 +113,8 @@ if [[ "$setup_libs" == "true" ]]; then
|
||||
fi
|
||||
|
||||
# Fix for centos 7 during release
|
||||
if [ "${DISTRO}" = "centos-7" ] || [ "${DISTRO}" = "debian-11" ]; then
|
||||
python3 -m pip uninstall -y virtualenv
|
||||
if [ "${ARCHITECTURE}" = "centos-7" ]; then
|
||||
python3 -m pip uninstall virtualenv
|
||||
python3 -m pip install virtualenv
|
||||
fi
|
||||
|
||||
@@ -149,9 +149,9 @@ python3 -m pre_commit install
|
||||
|
||||
# Install py format tools
|
||||
echo "Install black formatter"
|
||||
python3 -m pip install black==23.1.*
|
||||
python3 -m pip install black==22.*
|
||||
echo "Install isort"
|
||||
python3 -m pip install isort==5.12.*
|
||||
python3 -m pip install isort==5.*
|
||||
|
||||
# Link `include/mgp.py` with `release/mgp/mgp.py`
|
||||
ln -v -f include/mgp.py release/mgp/mgp.py
|
||||
|
||||
@@ -36,7 +36,7 @@ ADDITIONAL USE GRANT: You may use the Licensed Work in accordance with the
|
||||
3. using the Licensed Work to create a work or solution
|
||||
which competes (or might reasonably be expected to
|
||||
compete) with the Licensed Work.
|
||||
CHANGE DATE: 2027-08-03
|
||||
CHANGE DATE: 2026-07-11
|
||||
CHANGE LICENSE: Apache License, Version 2.0
|
||||
|
||||
For information about alternative licensing arrangements, please visit: https://memgraph.com/legal.
|
||||
|
||||
@@ -67,7 +67,7 @@ It aims to deliver developers the speed, simplicity and scale required to build
|
||||
the next generation of applications driver by real-time connected data.")
|
||||
# Add `openssl` package to dependencies list. Used to generate SSL certificates.
|
||||
# We also depend on `python3` because we embed it in Memgraph.
|
||||
set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.0.0, curl >= 7.29.0, python3 >= 3.5.0, libstdc++ >= 6, logrotate")
|
||||
set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.0.0, curl >= 7.29.0, python3 >= 3.5.0, libstdc >= 6, logrotate")
|
||||
|
||||
# All variables must be set before including.
|
||||
include(CPack)
|
||||
|
||||
1
release/mgp/.gitignore
vendored
1
release/mgp/.gitignore
vendored
@@ -1,4 +1,3 @@
|
||||
.venv
|
||||
dist
|
||||
mgp.py
|
||||
poetry.lock
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "mgp"
|
||||
version = "1.1.1"
|
||||
version = "1.1.0"
|
||||
description = "Memgraph's module for developing MAGE modules. Used only for type hinting!"
|
||||
authors = [
|
||||
"katarinasupe <katarina.supe@memgraph.io>",
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
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"
|
||||
@@ -28,7 +28,3 @@ services:
|
||||
build:
|
||||
context: ubuntu-22.04
|
||||
container_name: "mgbuild_ubuntu-22.04"
|
||||
mgbuild_fedora-36:
|
||||
build:
|
||||
context: fedora-36
|
||||
container_name: "mgbuild_fedora-36"
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
FROM fedora:36
|
||||
|
||||
ARG TOOLCHAIN_VERSION
|
||||
|
||||
# Stops tzdata interactive configuration.
|
||||
RUN yum -y update \
|
||||
&& yum install -y 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-fedora-36-x86_64.tar.gz \
|
||||
-O ${TOOLCHAIN_VERSION}-binaries-fedora-36-x86_64.tar.gz \
|
||||
&& tar xzvf ${TOOLCHAIN_VERSION}-binaries-fedora-36-x86_64.tar.gz -C /opt
|
||||
|
||||
ENTRYPOINT ["sleep", "infinity"]
|
||||
@@ -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 ubuntu-22.04-arm)
|
||||
SUPPORTED_OS=(centos-7 centos-9 debian-10 debian-11 ubuntu-18.04 ubuntu-20.04 ubuntu-22.04 debian-11-arm)
|
||||
PROJECT_ROOT="$SCRIPT_DIR/../.."
|
||||
TOOLCHAIN_VERSION="toolchain-v4"
|
||||
ACTIVATE_TOOLCHAIN="source /opt/${TOOLCHAIN_VERSION}/activate"
|
||||
@@ -23,9 +23,9 @@ make_package () {
|
||||
echo "Building Memgraph for $os on $build_container..."
|
||||
|
||||
package_command=""
|
||||
if [[ "$os" =~ ^"centos".* ]] || [[ "$os" =~ ^"fedora".* ]]; then
|
||||
if [[ "$os" =~ ^"centos".* ]]; then
|
||||
docker exec "$build_container" bash -c "yum -y update"
|
||||
package_command=" cpack -G RPM --config ../CPackConfig.cmake && rpmlint --file='../../release/rpm/rpmlintrc' memgraph*.rpm "
|
||||
package_command=" cpack -G RPM --config ../CPackConfig.cmake && rpmlint memgraph*.rpm "
|
||||
fi
|
||||
if [[ "$os" =~ ^"debian".* ]]; then
|
||||
docker exec "$build_container" bash -c "apt update"
|
||||
@@ -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" =~ "-arm" ]]; then
|
||||
if [[ "$os" == "debian-11-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 .."
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
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"]
|
||||
@@ -30,7 +30,7 @@ BuildRequires: systemd
|
||||
# This is needed to prevent Python compilation errors when building the RPM
|
||||
# package
|
||||
# https://github.com/scylladb/scylla/issues/2235
|
||||
%if 0%{?rhel} && 0%{?rhel} < 8
|
||||
%if 0%{?rhel} < 8
|
||||
%global __os_install_post \
|
||||
/usr/lib/rpm/redhat/brp-compress \
|
||||
%{!?__debug_package:\
|
||||
@@ -40,9 +40,7 @@ BuildRequires: systemd
|
||||
/usr/lib/rpm/redhat/brp-strip-static-archive %{__strip} \
|
||||
%{!?__jar_repack:/usr/lib/rpm/redhat/brp-java-repack-jars} \
|
||||
%{nil}
|
||||
%endif
|
||||
|
||||
%if 0%{?fedora} && 0%{?fedora} < 35
|
||||
%else
|
||||
%global __os_install_post \
|
||||
/usr/lib/rpm/brp-compress \
|
||||
%{!?__debug_package:\
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# from https://github.com/google/earthenterprise/blob/master/earth_enterprise/rpmlintrc
|
||||
|
||||
# We are not packaging log dir
|
||||
addFilter("E: logrotate-log-dir-not-packaged")
|
||||
@@ -106,10 +106,6 @@ install(PROGRAMS $<TARGET_FILE:memgraph>
|
||||
# Install Python source for supporting our embedded Python.
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/include/mgp.py
|
||||
DESTINATION lib/memgraph/python_support)
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/include/mgp_mock.py
|
||||
DESTINATION lib/memgraph/python_support)
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/include/_mgp_mock.py
|
||||
DESTINATION lib/memgraph/python_support)
|
||||
|
||||
# Install the includes file for writing custom procedures in C and C++>
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/include/mg_procedure.h
|
||||
|
||||
@@ -146,10 +146,9 @@ QueryData Client::Execute(const std::string &query, const std::map<std::string,
|
||||
throw ServerMalformedDataException();
|
||||
}
|
||||
|
||||
auto &header = fields.ValueMap();
|
||||
|
||||
QueryData ret{{}, std::move(records), std::move(metadata.ValueMap())};
|
||||
|
||||
auto &header = fields.ValueMap();
|
||||
if (header.find("fields") == header.end()) {
|
||||
throw ServerMalformedDataException();
|
||||
}
|
||||
@@ -165,10 +164,6 @@ QueryData Client::Execute(const std::string &query, const std::map<std::string,
|
||||
ret.fields.emplace_back(std::move(field_item.ValueString()));
|
||||
}
|
||||
|
||||
if (header.contains("qid")) {
|
||||
ret.metadata["qid"] = header["qid"];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -73,6 +73,40 @@ inline std::pair<std::string, std::string> ExceptionToErrorMessage(const std::ex
|
||||
|
||||
namespace details {
|
||||
|
||||
template <typename TSession>
|
||||
State HandleRun(TSession &session, const State state, const Value &query, const Value ¶ms) {
|
||||
if (state != State::Idle) {
|
||||
// Client could potentially recover if we move to error state, but there is
|
||||
// no legitimate situation in which well working client would end up in this
|
||||
// situation.
|
||||
spdlog::trace("Unexpected RUN command!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
|
||||
|
||||
spdlog::debug("[Run] '{}'", query.ValueString());
|
||||
|
||||
try {
|
||||
// Interpret can throw.
|
||||
const auto [header, qid] = session.Interpret(query.ValueString(), params.ValueMap());
|
||||
// Convert std::string to Value
|
||||
std::vector<Value> vec;
|
||||
std::map<std::string, Value> data;
|
||||
vec.reserve(header.size());
|
||||
for (auto &i : header) vec.emplace_back(std::move(i));
|
||||
data.emplace("fields", std::move(vec));
|
||||
// Send the header.
|
||||
if (!session.encoder_.MessageSuccess(data)) {
|
||||
spdlog::trace("Couldn't send query header!");
|
||||
return State::Close;
|
||||
}
|
||||
return State::Result;
|
||||
} catch (const std::exception &e) {
|
||||
return HandleFailure(session, e);
|
||||
}
|
||||
}
|
||||
|
||||
template <bool is_pull, typename TSession>
|
||||
State HandlePullDiscard(TSession &session, std::optional<int> n, std::optional<int> qid) {
|
||||
try {
|
||||
@@ -195,36 +229,7 @@ State HandleRunV1(TSession &session, const State state, const Marker marker) {
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (state != State::Idle) {
|
||||
// Client could potentially recover if we move to error state, but there is
|
||||
// no legitimate situation in which well working client would end up in this
|
||||
// situation.
|
||||
spdlog::trace("Unexpected RUN command!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
|
||||
|
||||
spdlog::debug("[Run] '{}'", query.ValueString());
|
||||
|
||||
try {
|
||||
// Interpret can throw.
|
||||
const auto [header, qid] = session.Interpret(query.ValueString(), params.ValueMap());
|
||||
// Convert std::string to Value
|
||||
std::vector<Value> vec;
|
||||
std::map<std::string, Value> data;
|
||||
vec.reserve(header.size());
|
||||
for (auto &i : header) vec.emplace_back(std::move(i));
|
||||
data.emplace("fields", std::move(vec));
|
||||
// Send the header.
|
||||
if (!session.encoder_.MessageSuccess(data)) {
|
||||
spdlog::trace("Couldn't send query header!");
|
||||
return State::Close;
|
||||
}
|
||||
return State::Result;
|
||||
} catch (const std::exception &e) {
|
||||
return HandleFailure(session, e);
|
||||
}
|
||||
return details::HandleRun(session, state, query, params);
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
@@ -252,40 +257,7 @@ State HandleRunV4(TSession &session, const State state, const Marker marker) {
|
||||
spdlog::trace("Couldn't read extra field!");
|
||||
}
|
||||
|
||||
if (state != State::Idle) {
|
||||
// Client could potentially recover if we move to error state, but there is
|
||||
// no legitimate situation in which well working client would end up in this
|
||||
// situation.
|
||||
spdlog::trace("Unexpected RUN command!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
|
||||
|
||||
spdlog::debug("[Run] '{}'", query.ValueString());
|
||||
|
||||
try {
|
||||
// Interpret can throw.
|
||||
const auto [header, qid] = session.Interpret(query.ValueString(), params.ValueMap());
|
||||
// Convert std::string to Value
|
||||
std::vector<Value> vec;
|
||||
std::map<std::string, Value> data;
|
||||
vec.reserve(header.size());
|
||||
for (auto &i : header) vec.emplace_back(std::move(i));
|
||||
data.emplace("fields", std::move(vec));
|
||||
if (qid.has_value()) {
|
||||
data.emplace("qid", Value{*qid});
|
||||
}
|
||||
|
||||
// Send the header.
|
||||
if (!session.encoder_.MessageSuccess(data)) {
|
||||
spdlog::trace("Couldn't send query header!");
|
||||
return State::Close;
|
||||
}
|
||||
return State::Result;
|
||||
} catch (const std::exception &e) {
|
||||
return HandleFailure(session, e);
|
||||
}
|
||||
return details::HandleRun(session, state, query, params);
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -107,37 +107,5 @@ 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 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -71,10 +71,6 @@ 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());
|
||||
}
|
||||
@@ -129,10 +125,6 @@ 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());
|
||||
}
|
||||
|
||||
@@ -301,7 +301,6 @@ cpp<#
|
||||
|
||||
(lcp:define-class expression (tree "::utils::Visitable<HierarchicalTreeVisitor>"
|
||||
"::utils::Visitable<ExpressionVisitor<TypedValue>>"
|
||||
"::utils::Visitable<ExpressionVisitor<TypedValue*>>"
|
||||
"::utils::Visitable<ExpressionVisitor<void>>")
|
||||
()
|
||||
(:abstractp t)
|
||||
@@ -309,7 +308,6 @@ cpp<#
|
||||
#>cpp
|
||||
using utils::Visitable<HierarchicalTreeVisitor>::Accept;
|
||||
using utils::Visitable<ExpressionVisitor<TypedValue>>::Accept;
|
||||
using utils::Visitable<ExpressionVisitor<TypedValue*>>::Accept;
|
||||
using utils::Visitable<ExpressionVisitor<void>>::Accept;
|
||||
|
||||
Expression() = default;
|
||||
@@ -409,7 +407,6 @@ cpp<#
|
||||
(:public
|
||||
#>cpp
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
@@ -441,7 +438,6 @@ cpp<#
|
||||
(:public
|
||||
#>cpp
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
@@ -489,7 +485,6 @@ cpp<#
|
||||
}
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
@@ -543,7 +538,6 @@ cpp<#
|
||||
ListSlicingOperator() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
@@ -587,7 +581,6 @@ cpp<#
|
||||
IfOperator() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
@@ -635,7 +628,6 @@ cpp<#
|
||||
PrimitiveLiteral() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
DEFVISITABLE(HierarchicalTreeVisitor);
|
||||
cpp<#)
|
||||
@@ -664,7 +656,6 @@ cpp<#
|
||||
ListLiteral() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
@@ -697,7 +688,6 @@ cpp<#
|
||||
MapLiteral() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
@@ -730,7 +720,6 @@ cpp<#
|
||||
Identifier() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
DEFVISITABLE(HierarchicalTreeVisitor);
|
||||
|
||||
@@ -768,7 +757,6 @@ cpp<#
|
||||
PropertyLookup() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
@@ -809,7 +797,6 @@ cpp<#
|
||||
LabelsTest() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
@@ -850,7 +837,6 @@ cpp<#
|
||||
Function() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
@@ -906,7 +892,6 @@ cpp<#
|
||||
Reduce() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
@@ -945,7 +930,6 @@ cpp<#
|
||||
Coalesce() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
@@ -985,7 +969,6 @@ cpp<#
|
||||
Extract() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
@@ -1022,7 +1005,6 @@ cpp<#
|
||||
All() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
@@ -1064,7 +1046,6 @@ cpp<#
|
||||
Single() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
@@ -1106,7 +1087,6 @@ cpp<#
|
||||
Any() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
@@ -1148,7 +1128,6 @@ cpp<#
|
||||
None() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
@@ -1180,7 +1159,6 @@ cpp<#
|
||||
ParameterLookup() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
DEFVISITABLE(HierarchicalTreeVisitor);
|
||||
cpp<#)
|
||||
@@ -1208,7 +1186,6 @@ cpp<#
|
||||
RegexMatch() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
@@ -1228,7 +1205,6 @@ cpp<#
|
||||
|
||||
(lcp:define-class named-expression (tree "::utils::Visitable<HierarchicalTreeVisitor>"
|
||||
"::utils::Visitable<ExpressionVisitor<TypedValue>>"
|
||||
"::utils::Visitable<ExpressionVisitor<TypedValue*>>"
|
||||
"::utils::Visitable<ExpressionVisitor<void>>")
|
||||
((name "std::string" :scope :public)
|
||||
(expression "Expression *" :initval "nullptr" :scope :public
|
||||
@@ -1247,7 +1223,6 @@ cpp<#
|
||||
NamedExpression() = default;
|
||||
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue>);
|
||||
DEFVISITABLE(ExpressionVisitor<TypedValue*>);
|
||||
DEFVISITABLE(ExpressionVisitor<void>);
|
||||
bool Accept(HierarchicalTreeVisitor &visitor) override {
|
||||
if (visitor.PreVisit(*this)) {
|
||||
@@ -2711,41 +2686,5 @@ 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 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -95,7 +95,6 @@ class SettingQuery;
|
||||
class VersionQuery;
|
||||
class Foreach;
|
||||
class ShowConfigQuery;
|
||||
class Exists;
|
||||
|
||||
using TreeCompositeVisitor = utils::CompositeVisitor<
|
||||
SingleQuery, CypherUnion, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator, AdditionOperator,
|
||||
@@ -104,7 +103,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, Exists>;
|
||||
RemoveProperty, RemoveLabels, Merge, Unwind, RegexMatch, LoadCsv, Foreach>;
|
||||
|
||||
using TreeLeafVisitor = utils::LeafVisitor<Identifier, PrimitiveLiteral, ParameterLookup>;
|
||||
|
||||
@@ -124,7 +123,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, Exists> {};
|
||||
None, ParameterLookup, Identifier, PrimitiveLiteral, RegexMatch> {};
|
||||
|
||||
template <class TResult>
|
||||
class QueryVisitor : public utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -2160,10 +2160,7 @@ 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());
|
||||
@@ -2207,17 +2204,6 @@ 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 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -763,11 +763,6 @@ 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 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -60,7 +60,6 @@ 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;
|
||||
@@ -265,8 +264,6 @@ 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,7 +236,6 @@ atom : literal
|
||||
| ( ANY '(' filterExpression ')' )
|
||||
| ( NONE '(' filterExpression ')' )
|
||||
| ( SINGLE '(' filterExpression ')' )
|
||||
| ( EXISTS '(' existsExpression ')' )
|
||||
| relationshipsPattern
|
||||
| parenthesizedExpression
|
||||
| functionInvocation
|
||||
@@ -276,8 +275,6 @@ reduceExpression : accumulator=variable '=' initial=expression ',' idInColl '|'
|
||||
|
||||
extractExpression : idInColl '|' expression ;
|
||||
|
||||
existsExpression : patternPart ;
|
||||
|
||||
idInColl : variable IN expression ;
|
||||
|
||||
functionInvocation : functionName '(' ( DISTINCT )? ( expression ( ',' expression )* )? ')' ;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -64,9 +64,12 @@ 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::GetOrCreateSymbolLocalScope(const std::string &name, bool user_declared, Symbol::Type type) {
|
||||
auto &scope = scopes_.back();
|
||||
if (auto maybe_symbol = FindSymbolInScope(name, scope, type); maybe_symbol) {
|
||||
return *maybe_symbol;
|
||||
}
|
||||
return CreateSymbol(name, user_declared, type);
|
||||
}
|
||||
|
||||
auto SymbolGenerator::GetOrCreateSymbol(const std::string &name, bool user_declared, Symbol::Type type) {
|
||||
@@ -203,7 +206,7 @@ bool SymbolGenerator::PreVisit(CallProcedure &call_proc) {
|
||||
|
||||
bool SymbolGenerator::PostVisit(CallProcedure &call_proc) {
|
||||
for (auto *ident : call_proc.result_identifiers_) {
|
||||
if (HasSymbol(ident->name_)) {
|
||||
if (HasSymbolLocalScope(ident->name_)) {
|
||||
throw RedeclareVariableError(ident->name_);
|
||||
}
|
||||
ident->MapTo(CreateSymbol(ident->name_, true));
|
||||
@@ -214,7 +217,7 @@ bool SymbolGenerator::PostVisit(CallProcedure &call_proc) {
|
||||
bool SymbolGenerator::PreVisit(LoadCsv &load_csv) { return false; }
|
||||
|
||||
bool SymbolGenerator::PostVisit(LoadCsv &load_csv) {
|
||||
if (HasSymbol(load_csv.row_var_->name_)) {
|
||||
if (HasSymbolLocalScope(load_csv.row_var_->name_)) {
|
||||
throw RedeclareVariableError(load_csv.row_var_->name_);
|
||||
}
|
||||
load_csv.row_var_->MapTo(CreateSymbol(load_csv.row_var_->name_, true));
|
||||
@@ -262,7 +265,7 @@ bool SymbolGenerator::PostVisit(Merge &) {
|
||||
|
||||
bool SymbolGenerator::PostVisit(Unwind &unwind) {
|
||||
const auto &name = unwind.named_expression_->name_;
|
||||
if (HasSymbol(name)) {
|
||||
if (HasSymbolLocalScope(name)) {
|
||||
throw RedeclareVariableError(name);
|
||||
}
|
||||
unwind.named_expression_->MapTo(CreateSymbol(name, true));
|
||||
@@ -279,7 +282,7 @@ bool SymbolGenerator::PostVisit(Match &) {
|
||||
// Check variables in property maps after visiting Match, so that they can
|
||||
// reference symbols out of bind order.
|
||||
for (auto &ident : scope.identifiers_in_match) {
|
||||
if (!HasSymbol(ident->name_) && !ConsumePredefinedIdentifier(ident->name_))
|
||||
if (!HasSymbolLocalScope(ident->name_) && !ConsumePredefinedIdentifier(ident->name_))
|
||||
throw UnboundVariableError(ident->name_);
|
||||
ident->MapTo(scope.symbols[ident->name_]);
|
||||
}
|
||||
@@ -307,19 +310,11 @@ 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
|
||||
// identifier is the pattern name.
|
||||
symbol = GetOrCreateSymbol(ident.name_, ident.user_declared_, Symbol::Type::PATH);
|
||||
symbol = GetOrCreateSymbolLocalScope(ident.name_, ident.user_declared_, Symbol::Type::PATH);
|
||||
} else if (scope.in_pattern && scope.in_pattern_atom_identifier) {
|
||||
// Patterns used to create nodes and edges cannot redeclare already
|
||||
// established bindings. Declaration only happens in single node
|
||||
@@ -327,22 +322,21 @@ SymbolGenerator::ReturnType SymbolGenerator::Visit(Identifier &ident) {
|
||||
// `MATCH (n) CREATE (n)` should throw an error that `n` is already
|
||||
// declared. While `MATCH (n) CREATE (n) -[:R]-> (n)` is allowed,
|
||||
// since `n` now references the bound node instead of declaring it.
|
||||
if ((scope.in_create_node || scope.in_create_edge) && HasSymbol(ident.name_)) {
|
||||
if ((scope.in_create_node || scope.in_create_edge) && HasSymbolLocalScope(ident.name_)) {
|
||||
throw RedeclareVariableError(ident.name_);
|
||||
}
|
||||
auto type = Symbol::Type::VERTEX;
|
||||
if (scope.visiting_edge) {
|
||||
// Edge referencing is not allowed (like in Neo4j):
|
||||
// `MATCH (n) - [r] -> (n) - [r] -> (n) RETURN r` is not allowed.
|
||||
if (HasSymbol(ident.name_)) {
|
||||
if (HasSymbolLocalScope(ident.name_)) {
|
||||
throw RedeclareVariableError(ident.name_);
|
||||
}
|
||||
type = scope.visiting_edge->IsVariable() ? Symbol::Type::EDGE_LIST : Symbol::Type::EDGE;
|
||||
}
|
||||
symbol = GetOrCreateSymbol(ident.name_, ident.user_declared_, type);
|
||||
symbol = GetOrCreateSymbolLocalScope(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 && scope.visiting_edge->identifier_ &&
|
||||
scope.visiting_edge->identifier_->name_ == ident.name_) {
|
||||
if (scope.in_edge_range && 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_);
|
||||
@@ -444,46 +438,6 @@ 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) {
|
||||
@@ -493,7 +447,6 @@ 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;
|
||||
}
|
||||
|
||||
@@ -508,7 +461,7 @@ bool SymbolGenerator::PreVisit(NodeAtom &node_atom) {
|
||||
auto &scope = scopes_.back();
|
||||
auto check_node_semantic = [&node_atom, &scope, this](const bool props_or_labels) {
|
||||
const auto &node_name = node_atom.identifier_->name_;
|
||||
if ((scope.in_create || scope.in_merge) && props_or_labels && HasSymbol(node_name)) {
|
||||
if ((scope.in_create || scope.in_merge) && props_or_labels && HasSymbolLocalScope(node_name)) {
|
||||
throw SemanticException("Cannot create node '" + node_name +
|
||||
"' with labels or properties, because it is already declared.");
|
||||
}
|
||||
@@ -602,11 +555,11 @@ bool SymbolGenerator::PreVisit(EdgeAtom &edge_atom) {
|
||||
edge_atom.identifier_->Accept(*this);
|
||||
scope.in_pattern_atom_identifier = false;
|
||||
if (edge_atom.total_weight_) {
|
||||
if (HasSymbol(edge_atom.total_weight_->name_)) {
|
||||
if (HasSymbolLocalScope(edge_atom.total_weight_->name_)) {
|
||||
throw RedeclareVariableError(edge_atom.total_weight_->name_);
|
||||
}
|
||||
edge_atom.total_weight_->MapTo(GetOrCreateSymbol(edge_atom.total_weight_->name_,
|
||||
edge_atom.total_weight_->user_declared_, Symbol::Type::NUMBER));
|
||||
edge_atom.total_weight_->MapTo(GetOrCreateSymbolLocalScope(
|
||||
edge_atom.total_weight_->name_, edge_atom.total_weight_->user_declared_, Symbol::Type::NUMBER));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -649,6 +602,10 @@ bool SymbolGenerator::HasSymbol(const std::string &name) const {
|
||||
return std::ranges::any_of(scopes_, [&name](const auto &scope) { return scope.symbols.contains(name); });
|
||||
}
|
||||
|
||||
bool SymbolGenerator::HasSymbolLocalScope(const std::string &name) const {
|
||||
return scopes_.back().symbols.contains(name);
|
||||
}
|
||||
|
||||
bool SymbolGenerator::ConsumePredefinedIdentifier(const std::string &name) {
|
||||
auto it = predefined_identifiers_.find(name);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -64,8 +64,6 @@ 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;
|
||||
@@ -81,8 +79,6 @@ 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;
|
||||
@@ -117,8 +113,6 @@ 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};
|
||||
@@ -140,6 +134,7 @@ class SymbolGenerator : public HierarchicalTreeVisitor {
|
||||
static std::optional<Symbol> FindSymbolInScope(const std::string &name, const Scope &scope, Symbol::Type type);
|
||||
|
||||
bool HasSymbol(const std::string &name) const;
|
||||
bool HasSymbolLocalScope(const std::string &name) const;
|
||||
|
||||
// @return true if it added a predefined identifier with that name
|
||||
bool ConsumePredefinedIdentifier(const std::string &name);
|
||||
@@ -149,12 +144,10 @@ 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.
|
||||
auto GetOrCreateSymbolLocalScope(const std::string &name, bool user_declared, Symbol::Type type = Symbol::Type::ANY);
|
||||
|
||||
void VisitReturnBody(ReturnBody &body, Where *where = nullptr);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -51,7 +51,6 @@ 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()); }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -31,72 +31,6 @@
|
||||
|
||||
namespace memgraph::query {
|
||||
|
||||
class ReferenceExpressionEvaluator : public ExpressionVisitor<TypedValue *> {
|
||||
public:
|
||||
ReferenceExpressionEvaluator(Frame *frame, const SymbolTable *symbol_table, const EvaluationContext *ctx)
|
||||
: frame_(frame), symbol_table_(symbol_table), ctx_(ctx) {}
|
||||
|
||||
using ExpressionVisitor<TypedValue *>::Visit;
|
||||
|
||||
utils::MemoryResource *GetMemoryResource() const { return ctx_->memory; }
|
||||
|
||||
#define UNSUCCESSFUL_VISIT(expr_name) \
|
||||
TypedValue *Visit(expr_name &expr) override { return nullptr; }
|
||||
|
||||
TypedValue *Visit(Identifier &ident) override { return &frame_->at(symbol_table_->at(ident)); }
|
||||
|
||||
UNSUCCESSFUL_VISIT(NamedExpression);
|
||||
UNSUCCESSFUL_VISIT(OrOperator);
|
||||
UNSUCCESSFUL_VISIT(XorOperator);
|
||||
UNSUCCESSFUL_VISIT(AdditionOperator);
|
||||
UNSUCCESSFUL_VISIT(SubtractionOperator);
|
||||
UNSUCCESSFUL_VISIT(MultiplicationOperator);
|
||||
UNSUCCESSFUL_VISIT(DivisionOperator);
|
||||
UNSUCCESSFUL_VISIT(ModOperator);
|
||||
UNSUCCESSFUL_VISIT(NotEqualOperator);
|
||||
UNSUCCESSFUL_VISIT(EqualOperator);
|
||||
UNSUCCESSFUL_VISIT(LessOperator);
|
||||
UNSUCCESSFUL_VISIT(GreaterOperator);
|
||||
UNSUCCESSFUL_VISIT(LessEqualOperator);
|
||||
UNSUCCESSFUL_VISIT(GreaterEqualOperator);
|
||||
|
||||
UNSUCCESSFUL_VISIT(NotOperator);
|
||||
UNSUCCESSFUL_VISIT(UnaryPlusOperator);
|
||||
UNSUCCESSFUL_VISIT(UnaryMinusOperator);
|
||||
|
||||
UNSUCCESSFUL_VISIT(AndOperator);
|
||||
UNSUCCESSFUL_VISIT(IfOperator);
|
||||
UNSUCCESSFUL_VISIT(InListOperator);
|
||||
|
||||
UNSUCCESSFUL_VISIT(SubscriptOperator);
|
||||
|
||||
UNSUCCESSFUL_VISIT(ListSlicingOperator);
|
||||
UNSUCCESSFUL_VISIT(IsNullOperator);
|
||||
UNSUCCESSFUL_VISIT(PropertyLookup);
|
||||
UNSUCCESSFUL_VISIT(LabelsTest);
|
||||
|
||||
UNSUCCESSFUL_VISIT(PrimitiveLiteral);
|
||||
UNSUCCESSFUL_VISIT(ListLiteral);
|
||||
UNSUCCESSFUL_VISIT(MapLiteral);
|
||||
UNSUCCESSFUL_VISIT(Aggregation);
|
||||
UNSUCCESSFUL_VISIT(Coalesce);
|
||||
UNSUCCESSFUL_VISIT(Function);
|
||||
UNSUCCESSFUL_VISIT(Reduce);
|
||||
UNSUCCESSFUL_VISIT(Extract);
|
||||
UNSUCCESSFUL_VISIT(All);
|
||||
UNSUCCESSFUL_VISIT(Single);
|
||||
UNSUCCESSFUL_VISIT(Any);
|
||||
UNSUCCESSFUL_VISIT(None);
|
||||
UNSUCCESSFUL_VISIT(ParameterLookup);
|
||||
UNSUCCESSFUL_VISIT(RegexMatch);
|
||||
UNSUCCESSFUL_VISIT(Exists);
|
||||
|
||||
private:
|
||||
Frame *frame_;
|
||||
const SymbolTable *symbol_table_;
|
||||
const EvaluationContext *ctx_;
|
||||
};
|
||||
|
||||
class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
public:
|
||||
ExpressionEvaluator(Frame *frame, const SymbolTable &symbol_table, const EvaluationContext &ctx, DbAccessor *dba,
|
||||
@@ -225,53 +159,50 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
}
|
||||
|
||||
TypedValue Visit(SubscriptOperator &list_indexing) override {
|
||||
ReferenceExpressionEvaluator referenceExpressionEvaluator(frame_, symbol_table_, ctx_);
|
||||
|
||||
TypedValue *lhs_ptr = list_indexing.expression1_->Accept(referenceExpressionEvaluator);
|
||||
TypedValue lhs;
|
||||
const auto referenced = nullptr != lhs_ptr;
|
||||
if (!referenced) {
|
||||
lhs = list_indexing.expression1_->Accept(*this);
|
||||
lhs_ptr = &lhs;
|
||||
}
|
||||
auto lhs = list_indexing.expression1_->Accept(*this);
|
||||
auto index = list_indexing.expression2_->Accept(*this);
|
||||
if (!lhs_ptr->IsList() && !lhs_ptr->IsMap() && !lhs_ptr->IsVertex() && !lhs_ptr->IsEdge() && !lhs_ptr->IsNull())
|
||||
if (!lhs.IsList() && !lhs.IsMap() && !lhs.IsVertex() && !lhs.IsEdge() && !lhs.IsNull())
|
||||
throw QueryRuntimeException(
|
||||
"Expected a list, a map, a node or an edge to index with '[]', got "
|
||||
"{}.",
|
||||
lhs_ptr->type());
|
||||
if (lhs_ptr->IsNull() || index.IsNull()) return TypedValue(ctx_->memory);
|
||||
if (lhs_ptr->IsList()) {
|
||||
lhs.type());
|
||||
if (lhs.IsNull() || index.IsNull()) return TypedValue(ctx_->memory);
|
||||
if (lhs.IsList()) {
|
||||
if (!index.IsInt()) throw QueryRuntimeException("Expected an integer as a list index, got {}.", index.type());
|
||||
auto index_int = index.ValueInt();
|
||||
auto &list = lhs_ptr->ValueList();
|
||||
// NOTE: Take non-const reference to list, so that we can move out the
|
||||
// indexed element as the result.
|
||||
auto &list = lhs.ValueList();
|
||||
if (index_int < 0) {
|
||||
index_int += static_cast<int64_t>(list.size());
|
||||
}
|
||||
if (index_int >= static_cast<int64_t>(list.size()) || index_int < 0) return TypedValue(ctx_->memory);
|
||||
return referenced ? TypedValue(list[index_int], ctx_->memory)
|
||||
: TypedValue(std::move(list[index_int]), ctx_->memory);
|
||||
// NOTE: Explicit move is needed, so that we return the move constructed
|
||||
// value and preserve the correct MemoryResource.
|
||||
return std::move(list[index_int]);
|
||||
}
|
||||
|
||||
if (lhs_ptr->IsMap()) {
|
||||
if (lhs.IsMap()) {
|
||||
if (!index.IsString()) throw QueryRuntimeException("Expected a string as a map index, got {}.", index.type());
|
||||
// NOTE: Take non-const reference to map, so that we can move out the
|
||||
// looked-up element as the result.
|
||||
auto &map = lhs_ptr->ValueMap();
|
||||
auto &map = lhs.ValueMap();
|
||||
auto found = map.find(index.ValueString());
|
||||
if (found == map.end()) return TypedValue(ctx_->memory);
|
||||
return referenced ? TypedValue(found->second, ctx_->memory) : TypedValue(std::move(found->second), ctx_->memory);
|
||||
// NOTE: Explicit move is needed, so that we return the move constructed
|
||||
// value and preserve the correct MemoryResource.
|
||||
return std::move(found->second);
|
||||
}
|
||||
|
||||
if (lhs_ptr->IsVertex()) {
|
||||
if (lhs.IsVertex()) {
|
||||
if (!index.IsString()) throw QueryRuntimeException("Expected a string as a property name, got {}.", index.type());
|
||||
return {GetProperty(lhs_ptr->ValueVertex(), index.ValueString()), ctx_->memory};
|
||||
return TypedValue(GetProperty(lhs.ValueVertex(), index.ValueString()), ctx_->memory);
|
||||
}
|
||||
|
||||
if (lhs_ptr->IsEdge()) {
|
||||
if (lhs.IsEdge()) {
|
||||
if (!index.IsString()) throw QueryRuntimeException("Expected a string as a property name, got {}.", index.type());
|
||||
return {GetProperty(lhs_ptr->ValueEdge(), index.ValueString()), ctx_->memory};
|
||||
};
|
||||
return TypedValue(GetProperty(lhs.ValueEdge(), index.ValueString()), ctx_->memory);
|
||||
}
|
||||
|
||||
// lhs is Null
|
||||
return TypedValue(ctx_->memory);
|
||||
@@ -327,15 +258,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
}
|
||||
|
||||
TypedValue Visit(PropertyLookup &property_lookup) override {
|
||||
ReferenceExpressionEvaluator referenceExpressionEvaluator(frame_, symbol_table_, ctx_);
|
||||
|
||||
TypedValue *expression_result_ptr = property_lookup.expression_->Accept(referenceExpressionEvaluator);
|
||||
TypedValue expression_result;
|
||||
|
||||
if (nullptr == expression_result_ptr) {
|
||||
expression_result = property_lookup.expression_->Accept(*this);
|
||||
expression_result_ptr = &expression_result;
|
||||
}
|
||||
auto expression_result = property_lookup.expression_->Accept(*this);
|
||||
auto maybe_date = [this](const auto &date, const auto &prop_name) -> std::optional<TypedValue> {
|
||||
if (prop_name == "year") {
|
||||
return TypedValue(date.year, ctx_->memory);
|
||||
@@ -409,38 +332,42 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
}
|
||||
return std::nullopt;
|
||||
};
|
||||
switch (expression_result_ptr->type()) {
|
||||
switch (expression_result.type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue(ctx_->memory);
|
||||
case TypedValue::Type::Vertex:
|
||||
return TypedValue(GetProperty(expression_result_ptr->ValueVertex(), property_lookup.property_), ctx_->memory);
|
||||
return TypedValue(GetProperty(expression_result.ValueVertex(), property_lookup.property_), ctx_->memory);
|
||||
case TypedValue::Type::Edge:
|
||||
return TypedValue(GetProperty(expression_result_ptr->ValueEdge(), property_lookup.property_), ctx_->memory);
|
||||
return TypedValue(GetProperty(expression_result.ValueEdge(), property_lookup.property_), ctx_->memory);
|
||||
case TypedValue::Type::Map: {
|
||||
auto &map = expression_result_ptr->ValueMap();
|
||||
// NOTE: Take non-const reference to map, so that we can move out the
|
||||
// looked-up element as the result.
|
||||
auto &map = expression_result.ValueMap();
|
||||
auto found = map.find(property_lookup.property_.name.c_str());
|
||||
if (found == map.end()) return TypedValue(ctx_->memory);
|
||||
return TypedValue(found->second, ctx_->memory);
|
||||
// NOTE: Explicit move is needed, so that we return the move constructed
|
||||
// value and preserve the correct MemoryResource.
|
||||
return std::move(found->second);
|
||||
}
|
||||
case TypedValue::Type::Duration: {
|
||||
const auto &prop_name = property_lookup.property_.name;
|
||||
const auto &dur = expression_result_ptr->ValueDuration();
|
||||
const auto &dur = expression_result.ValueDuration();
|
||||
if (auto dur_field = maybe_duration(dur, prop_name); dur_field) {
|
||||
return TypedValue(*dur_field, ctx_->memory);
|
||||
return std::move(*dur_field);
|
||||
}
|
||||
throw QueryRuntimeException("Invalid property name {} for Duration", prop_name);
|
||||
}
|
||||
case TypedValue::Type::Date: {
|
||||
const auto &prop_name = property_lookup.property_.name;
|
||||
const auto &date = expression_result_ptr->ValueDate();
|
||||
const auto &date = expression_result.ValueDate();
|
||||
if (auto date_field = maybe_date(date, prop_name); date_field) {
|
||||
return TypedValue(*date_field, ctx_->memory);
|
||||
return std::move(*date_field);
|
||||
}
|
||||
throw QueryRuntimeException("Invalid property name {} for Date", prop_name);
|
||||
}
|
||||
case TypedValue::Type::LocalTime: {
|
||||
const auto &prop_name = property_lookup.property_.name;
|
||||
const auto < = expression_result_ptr->ValueLocalTime();
|
||||
const auto < = expression_result.ValueLocalTime();
|
||||
if (auto lt_field = maybe_local_time(lt, prop_name); lt_field) {
|
||||
return std::move(*lt_field);
|
||||
}
|
||||
@@ -448,20 +375,20 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
}
|
||||
case TypedValue::Type::LocalDateTime: {
|
||||
const auto &prop_name = property_lookup.property_.name;
|
||||
const auto &ldt = expression_result_ptr->ValueLocalDateTime();
|
||||
const auto &ldt = expression_result.ValueLocalDateTime();
|
||||
if (auto date_field = maybe_date(ldt.date, prop_name); date_field) {
|
||||
return std::move(*date_field);
|
||||
}
|
||||
if (auto lt_field = maybe_local_time(ldt.local_time, prop_name); lt_field) {
|
||||
return TypedValue(*lt_field, ctx_->memory);
|
||||
return std::move(*lt_field);
|
||||
}
|
||||
throw QueryRuntimeException("Invalid property name {} for LocalDateTime", prop_name);
|
||||
}
|
||||
case TypedValue::Type::Graph: {
|
||||
const auto &prop_name = property_lookup.property_.name;
|
||||
const auto &graph = expression_result_ptr->ValueGraph();
|
||||
const auto &graph = expression_result.ValueGraph();
|
||||
if (auto graph_field = maybe_graph(graph, prop_name); graph_field) {
|
||||
return TypedValue(*graph_field, ctx_->memory);
|
||||
return std::move(*graph_field);
|
||||
}
|
||||
throw QueryRuntimeException("Invalid property name {} for Graph", prop_name);
|
||||
}
|
||||
@@ -620,8 +547,6 @@ 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()) {
|
||||
|
||||
@@ -1044,7 +1044,7 @@ std::optional<plan::ProfilingStatsWithTotalTime> PullPlan::Pull(AnyStream *strea
|
||||
// Also, we want to throw only when the query engine requests more memory and not the storage
|
||||
// so we add the exception to the allocator.
|
||||
// TODO (mferencevic): Tune the parameters accordingly.
|
||||
utils::PoolResource pool_memory(128, 1024, &monotonic_memory, utils::NewDeleteResource());
|
||||
utils::PoolResource pool_memory(128, 1024, &monotonic_memory);
|
||||
std::optional<utils::LimitedMemoryResource> maybe_limited_resource;
|
||||
|
||||
if (memory_limit_) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -25,7 +25,6 @@
|
||||
|
||||
#include <cppitertools/chain.hpp>
|
||||
#include <cppitertools/imap.hpp>
|
||||
#include "query/common.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
#include "license/license.hpp"
|
||||
@@ -116,7 +115,6 @@ 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 {
|
||||
@@ -210,18 +208,17 @@ 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) {
|
||||
properties.emplace(key, value_expression->Accept(evaluator));
|
||||
PropsSetChecked(&new_node, 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()) {
|
||||
properties.emplace(dba.NameToProperty(key), value);
|
||||
auto property_id = dba.NameToProperty(key);
|
||||
PropsSetChecked(&new_node, property_id, value);
|
||||
}
|
||||
}
|
||||
MultiPropsInitChecked(&new_node, properties);
|
||||
|
||||
(*frame)[node_info.symbol] = new_node;
|
||||
return (*frame)[node_info.symbol].ValueVertex();
|
||||
@@ -302,18 +299,17 @@ 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;
|
||||
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));
|
||||
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));
|
||||
}
|
||||
} else {
|
||||
auto property_map = evaluator->Visit(*std::get<ParameterLookup *>(edge_info.properties));
|
||||
for (const auto &[key, value] : property_map.ValueMap()) {
|
||||
properties.emplace(dba->NameToProperty(key), value);
|
||||
auto property_id = dba->NameToProperty(key);
|
||||
PropsSetChecked(&edge, property_id, value);
|
||||
}
|
||||
}
|
||||
if (!properties.empty()) MultiPropsInitChecked(&edge, properties);
|
||||
|
||||
(*frame)[edge_info.symbol] = edge;
|
||||
} else {
|
||||
@@ -414,11 +410,10 @@ VertexAccessor &CreateExpand::CreateExpandCursor::OtherVertex(Frame &frame, Exec
|
||||
template <class TVerticesFun>
|
||||
class ScanAllCursor : public Cursor {
|
||||
public:
|
||||
explicit ScanAllCursor(Symbol output_symbol, UniqueCursorPtr input_cursor, storage::View view,
|
||||
TVerticesFun get_vertices, const char *op_name)
|
||||
explicit ScanAllCursor(Symbol output_symbol, UniqueCursorPtr input_cursor, TVerticesFun get_vertices,
|
||||
const char *op_name)
|
||||
: output_symbol_(output_symbol),
|
||||
input_cursor_(std::move(input_cursor)),
|
||||
view_(view),
|
||||
get_vertices_(std::move(get_vertices)),
|
||||
op_name_(op_name) {}
|
||||
|
||||
@@ -453,7 +448,7 @@ class ScanAllCursor : public Cursor {
|
||||
#ifdef MG_ENTERPRISE
|
||||
bool FindNextVertex(const ExecutionContext &context) {
|
||||
while (vertices_it_.value() != vertices_.value().end()) {
|
||||
if (context.auth_checker->Has(*vertices_it_.value(), view_,
|
||||
if (context.auth_checker->Has(*vertices_it_.value(), memgraph::storage::View::OLD,
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege::READ)) {
|
||||
return true;
|
||||
}
|
||||
@@ -474,7 +469,6 @@ class ScanAllCursor : public Cursor {
|
||||
private:
|
||||
const Symbol output_symbol_;
|
||||
const UniqueCursorPtr input_cursor_;
|
||||
storage::View view_;
|
||||
TVerticesFun get_vertices_;
|
||||
std::optional<typename std::result_of<TVerticesFun(Frame &, ExecutionContext &)>::type::value_type> vertices_;
|
||||
std::optional<decltype(vertices_.value().begin())> vertices_it_;
|
||||
@@ -493,7 +487,7 @@ UniqueCursorPtr ScanAll::MakeCursor(utils::MemoryResource *mem) const {
|
||||
auto *db = context.db_accessor;
|
||||
return std::make_optional(db->Vertices(view_));
|
||||
};
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem), view_,
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem),
|
||||
std::move(vertices), "ScanAll");
|
||||
}
|
||||
|
||||
@@ -516,7 +510,7 @@ UniqueCursorPtr ScanAllByLabel::MakeCursor(utils::MemoryResource *mem) const {
|
||||
auto *db = context.db_accessor;
|
||||
return std::make_optional(db->Vertices(view_, label_));
|
||||
};
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem), view_,
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem),
|
||||
std::move(vertices), "ScanAllByLabel");
|
||||
}
|
||||
|
||||
@@ -581,7 +575,7 @@ UniqueCursorPtr ScanAllByLabelPropertyRange::MakeCursor(utils::MemoryResource *m
|
||||
if (maybe_upper && maybe_upper->value().IsNull()) return std::nullopt;
|
||||
return std::make_optional(db->Vertices(view_, label_, property_, maybe_lower, maybe_upper));
|
||||
};
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem), view_,
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem),
|
||||
std::move(vertices), "ScanAllByLabelPropertyRange");
|
||||
}
|
||||
|
||||
@@ -613,7 +607,7 @@ UniqueCursorPtr ScanAllByLabelPropertyValue::MakeCursor(utils::MemoryResource *m
|
||||
}
|
||||
return std::make_optional(db->Vertices(view_, label_, property_, storage::PropertyValue(value)));
|
||||
};
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem), view_,
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem),
|
||||
std::move(vertices), "ScanAllByLabelPropertyValue");
|
||||
}
|
||||
|
||||
@@ -631,7 +625,7 @@ UniqueCursorPtr ScanAllByLabelProperty::MakeCursor(utils::MemoryResource *mem) c
|
||||
auto *db = context.db_accessor;
|
||||
return std::make_optional(db->Vertices(view_, label_, property_));
|
||||
};
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem), view_,
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem),
|
||||
std::move(vertices), "ScanAllByLabelProperty");
|
||||
}
|
||||
|
||||
@@ -657,7 +651,7 @@ UniqueCursorPtr ScanAllById::MakeCursor(utils::MemoryResource *mem) const {
|
||||
if (!maybe_vertex) return std::nullopt;
|
||||
return std::vector<VertexAccessor>{*maybe_vertex};
|
||||
};
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem), view_,
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem),
|
||||
std::move(vertices), "ScanAllById");
|
||||
}
|
||||
|
||||
@@ -2026,7 +2020,7 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
|
||||
next_edges_.clear();
|
||||
traversal_stack_.clear();
|
||||
|
||||
expand_from_vertex(*start_vertex, TypedValue(), 0);
|
||||
pq_.push({TypedValue(), 0, *start_vertex, std::nullopt});
|
||||
visited_cost_.emplace(*start_vertex, 0);
|
||||
frame[self_.common_.edge_symbol] = TypedValue::TVector(memory);
|
||||
}
|
||||
@@ -2035,28 +2029,33 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
|
||||
while (!pq_.empty()) {
|
||||
if (MustAbort(context)) throw HintedAbortError();
|
||||
|
||||
const auto [current_weight, current_depth, current_vertex, directed_edge] = pq_.top();
|
||||
auto [current_weight, current_depth, current_vertex, maybe_directed_edge] = pq_.top();
|
||||
pq_.pop();
|
||||
|
||||
const auto &[current_edge, direction, weight] = directed_edge;
|
||||
if (expanded_.contains(current_edge)) continue;
|
||||
expanded_.emplace(current_edge);
|
||||
|
||||
// Expand only if what we've just expanded is less than max depth.
|
||||
if (current_depth < upper_bound_) {
|
||||
if (maybe_directed_edge) {
|
||||
auto &[current_edge, direction, weight] = *maybe_directed_edge;
|
||||
if (expanded_.find(current_edge) != expanded_.end()) continue;
|
||||
expanded_.emplace(current_edge);
|
||||
}
|
||||
expand_from_vertex(current_vertex, current_weight, current_depth);
|
||||
}
|
||||
|
||||
// Searching for a previous vertex in the expansion
|
||||
auto prev_vertex = direction == EdgeAtom::Direction::IN ? current_edge.To() : current_edge.From();
|
||||
// if current vertex is not starting vertex, maybe_directed_edge will not be nullopt
|
||||
if (maybe_directed_edge) {
|
||||
auto &[current_edge, direction, weight] = *maybe_directed_edge;
|
||||
// Searching for a previous vertex in the expansion
|
||||
auto prev_vertex = direction == EdgeAtom::Direction::IN ? current_edge.To() : current_edge.From();
|
||||
|
||||
// Update the parent
|
||||
if (next_edges_.find({prev_vertex, current_depth - 1}) == next_edges_.end()) {
|
||||
utils::pmr::list<DirectedEdge> empty(memory);
|
||||
next_edges_[{prev_vertex, current_depth - 1}] = std::move(empty);
|
||||
// Update the parent
|
||||
if (next_edges_.find({prev_vertex, current_depth - 1}) == next_edges_.end()) {
|
||||
utils::pmr::list<DirectedEdge> empty(memory);
|
||||
next_edges_[{prev_vertex, current_depth - 1}] = std::move(empty);
|
||||
}
|
||||
|
||||
next_edges_.at({prev_vertex, current_depth - 1}).emplace_back(*maybe_directed_edge);
|
||||
}
|
||||
|
||||
next_edges_.at({prev_vertex, current_depth - 1}).emplace_back(directed_edge);
|
||||
}
|
||||
|
||||
if (start_vertex && next_edges_.find({*start_vertex, 0}) != next_edges_.end()) {
|
||||
@@ -2113,8 +2112,8 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
|
||||
// Priority queue comparator. Keep lowest weight on top of the queue.
|
||||
class PriorityQueueComparator {
|
||||
public:
|
||||
bool operator()(const std::tuple<TypedValue, int64_t, VertexAccessor, DirectedEdge> &lhs,
|
||||
const std::tuple<TypedValue, int64_t, VertexAccessor, DirectedEdge> &rhs) {
|
||||
bool operator()(const std::tuple<TypedValue, int64_t, VertexAccessor, std::optional<DirectedEdge>> &lhs,
|
||||
const std::tuple<TypedValue, int64_t, VertexAccessor, std::optional<DirectedEdge>> &rhs) {
|
||||
const auto &lhs_weight = std::get<0>(lhs);
|
||||
const auto &rhs_weight = std::get<0>(rhs);
|
||||
// Null defines minimum value for all types
|
||||
@@ -2133,8 +2132,8 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
|
||||
|
||||
// Priority queue - core element of the algorithm.
|
||||
// Stores: {weight, depth, next vertex, edge and direction}
|
||||
std::priority_queue<std::tuple<TypedValue, int64_t, VertexAccessor, DirectedEdge>,
|
||||
utils::pmr::vector<std::tuple<TypedValue, int64_t, VertexAccessor, DirectedEdge>>,
|
||||
std::priority_queue<std::tuple<TypedValue, int64_t, VertexAccessor, std::optional<DirectedEdge>>,
|
||||
utils::pmr::vector<std::tuple<TypedValue, int64_t, VertexAccessor, std::optional<DirectedEdge>>>,
|
||||
PriorityQueueComparator>
|
||||
pq_;
|
||||
|
||||
@@ -2258,19 +2257,10 @@ std::vector<Symbol> ConstructNamedPath::ModifiedSymbols(const SymbolTable &table
|
||||
return symbols;
|
||||
}
|
||||
|
||||
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) {}
|
||||
Filter::Filter(const std::shared_ptr<LogicalOperator> &input, Expression *expression)
|
||||
: input_(input ? input : std::make_shared<Once>()), expression_(expression) {}
|
||||
|
||||
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);
|
||||
}
|
||||
ACCEPT_WITH_INPUT(Filter)
|
||||
|
||||
UniqueCursorPtr Filter::MakeCursor(utils::MemoryResource *mem) const {
|
||||
EventCounter::IncrementCounter(EventCounter::FilterOperator);
|
||||
@@ -2280,24 +2270,8 @@ 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)),
|
||||
pattern_filter_cursors_(MakeCursorVector(self_.pattern_filters_, mem)) {}
|
||||
: self_(self), input_cursor_(self_.input_->MakeCursor(mem)) {}
|
||||
|
||||
bool Filter::FilterCursor::Pull(Frame &frame, ExecutionContext &context) {
|
||||
SCOPED_PROFILE_OP("Filter");
|
||||
@@ -2307,10 +2281,6 @@ 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;
|
||||
@@ -2320,39 +2290,6 @@ 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) {}
|
||||
|
||||
@@ -4591,24 +4528,24 @@ auto ToOptionalString(ExpressionEvaluator *evaluator, Expression *expression) ->
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
TypedValue CsvRowToTypedList(csv::Reader::Row &row) {
|
||||
TypedValue CsvRowToTypedList(csv::Reader::Row row) {
|
||||
auto *mem = row.get_allocator().GetMemoryResource();
|
||||
auto typed_columns = utils::pmr::vector<TypedValue>(mem);
|
||||
typed_columns.reserve(row.size());
|
||||
for (auto &column : row) {
|
||||
typed_columns.emplace_back(std::move(column));
|
||||
}
|
||||
return {std::move(typed_columns), mem};
|
||||
return TypedValue(typed_columns, mem);
|
||||
}
|
||||
|
||||
TypedValue CsvRowToTypedMap(csv::Reader::Row &row, csv::Reader::Header header) {
|
||||
TypedValue CsvRowToTypedMap(csv::Reader::Row row, csv::Reader::Header header) {
|
||||
// a valid row has the same number of elements as the header
|
||||
auto *mem = row.get_allocator().GetMemoryResource();
|
||||
utils::pmr::map<utils::pmr::string, TypedValue> m(mem);
|
||||
for (auto i = 0; i < row.size(); ++i) {
|
||||
m.emplace(std::move(header[i]), std::move(row[i]));
|
||||
}
|
||||
return {std::move(m), mem};
|
||||
return TypedValue(m, mem);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -4647,17 +4584,18 @@ class LoadCsvCursor : public Cursor {
|
||||
// have to read at most cardinality(n) rows (but we can read less and stop
|
||||
// pulling MATCH).
|
||||
if (!input_is_once_ && !input_pulled) return false;
|
||||
auto row = reader_->GetNextRow(context.evaluation_context.memory);
|
||||
if (!row) {
|
||||
return false;
|
||||
|
||||
if (auto row = reader_->GetNextRow(context.evaluation_context.memory)) {
|
||||
if (!reader_->HasHeader()) {
|
||||
frame[self_->row_var_] = CsvRowToTypedList(std::move(*row));
|
||||
} else {
|
||||
frame[self_->row_var_] = CsvRowToTypedMap(
|
||||
std::move(*row), csv::Reader::Header(reader_->GetHeader(), context.evaluation_context.memory));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!reader_->HasHeader()) {
|
||||
frame[self_->row_var_] = CsvRowToTypedList(*row);
|
||||
} else {
|
||||
frame[self_->row_var_] =
|
||||
CsvRowToTypedMap(*row, csv::Reader::Header(reader_->GetHeader(), context.evaluation_context.memory));
|
||||
}
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Reset() override { input_cursor_->Reset(); }
|
||||
|
||||
@@ -133,7 +133,6 @@ class CallProcedure;
|
||||
class LoadCsv;
|
||||
class Foreach;
|
||||
class EmptyResult;
|
||||
class EvaluatePatternFilter;
|
||||
|
||||
using LogicalOperatorCompositeVisitor = utils::CompositeVisitor<
|
||||
Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel,
|
||||
@@ -142,7 +141,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, EvaluatePatternFilter>;
|
||||
Optional, Unwind, Distinct, Union, Cartesian, CallProcedure, LoadCsv, Foreach, EmptyResult>;
|
||||
|
||||
using LogicalOperatorLeafVisitor = utils::LeafVisitor<Once>;
|
||||
|
||||
@@ -1123,9 +1122,6 @@ 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")))
|
||||
@@ -1140,7 +1136,6 @@ 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;
|
||||
@@ -1164,7 +1159,6 @@ a boolean value.")
|
||||
private:
|
||||
const Filter &self_;
|
||||
const UniqueCursorPtr input_cursor_;
|
||||
const std::vector<UniqueCursorPtr> pattern_filter_cursors_;
|
||||
};
|
||||
cpp<#)
|
||||
(:serialize (:slk))
|
||||
@@ -1783,45 +1777,6 @@ 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 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -84,6 +84,56 @@ 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.
|
||||
@@ -469,8 +519,6 @@ 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));
|
||||
}
|
||||
@@ -480,78 +528,6 @@ 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 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -70,26 +70,7 @@ class UsedSymbolsCollector : public HierarchicalTreeVisitor {
|
||||
}
|
||||
|
||||
bool Visit(Identifier &ident) override {
|
||||
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;
|
||||
symbols_.insert(symbol_table_.at(ident));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -98,9 +79,6 @@ 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.
|
||||
@@ -121,93 +99,6 @@ 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:
|
||||
@@ -262,7 +153,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, Pattern };
|
||||
enum class Type { Generic, Label, Property, Id };
|
||||
|
||||
Type type;
|
||||
/// The original filter expression which must be satisfied.
|
||||
@@ -275,8 +166,6 @@ 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.
|
||||
@@ -398,13 +287,6 @@ 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 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
#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 {
|
||||
@@ -149,6 +148,7 @@ bool PlanPrinter::PreVisit(query::plan::Produce &op) {
|
||||
}
|
||||
|
||||
PRE_VISIT(ConstructNamedPath);
|
||||
PRE_VISIT(Filter);
|
||||
PRE_VISIT(SetProperty);
|
||||
PRE_VISIT(SetProperties);
|
||||
PRE_VISIT(SetLabels);
|
||||
@@ -157,7 +157,6 @@ 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) {
|
||||
@@ -252,15 +251,6 @@ 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() {
|
||||
@@ -599,13 +589,6 @@ 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;
|
||||
}
|
||||
@@ -925,7 +908,6 @@ bool PlanToJsonVisitor::PreVisit(Cartesian &op) {
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(Foreach &op) {
|
||||
json self;
|
||||
self["name"] = "Foreach";
|
||||
@@ -942,18 +924,6 @@ 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 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -74,7 +74,6 @@ 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;
|
||||
@@ -187,7 +186,6 @@ 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;
|
||||
|
||||
|
||||
@@ -91,25 +91,18 @@ bool ReadWriteTypeChecker::PreVisit([[maybe_unused]] Foreach &op) {
|
||||
bool ReadWriteTypeChecker::Visit(Once &) { return false; } // NOLINT(hicpp-named-parameter)
|
||||
|
||||
void ReadWriteTypeChecker::UpdateType(RWType op_type) {
|
||||
// Update type only if it's not the NONE type and the current operator's type
|
||||
// is different than the one that's currently inferred.
|
||||
if (type != RWType::NONE && type != op_type) {
|
||||
type = RWType::RW;
|
||||
}
|
||||
// Stop inference because RW is the most "dominant" type, i.e. it isn't
|
||||
// affected by the type of nodes in the plan appearing after the node for
|
||||
// which the type is set to RW.
|
||||
if (type == RWType::RW) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if op_type is NONE, type doesn't change.
|
||||
if (op_type == RWType::NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update type only if it's not the NONE type and the current operator's type
|
||||
// is different than the one that's currently inferred.
|
||||
if (type != RWType::NONE && type != op_type) {
|
||||
type = RWType::RW;
|
||||
}
|
||||
|
||||
if (type == RWType::NONE) {
|
||||
if (type == RWType::NONE && op_type != RWType::NONE) {
|
||||
type = op_type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
namespace memgraph::query::plan {
|
||||
|
||||
struct ReadWriteTypeChecker : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
class ReadWriteTypeChecker : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
public:
|
||||
ReadWriteTypeChecker() = default;
|
||||
|
||||
@@ -89,6 +89,7 @@ struct ReadWriteTypeChecker : public virtual HierarchicalLogicalOperatorVisitor
|
||||
|
||||
bool Visit(Once &) override;
|
||||
|
||||
private:
|
||||
void UpdateType(RWType op_type);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -444,8 +444,6 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
|
||||
|
||||
bool PreVisit(Foreach &op) override {
|
||||
prev_ops_.push_back(&op);
|
||||
op.input()->Accept(*this);
|
||||
RewriteBranch(&op.update_clauses_);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -454,16 +452,6 @@ 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 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -25,6 +25,15 @@ 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:
|
||||
//
|
||||
@@ -486,8 +495,7 @@ 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), std::vector<std::shared_ptr<LogicalOperator>>{},
|
||||
body.where()->expression_);
|
||||
last_op = std::make_unique<Filter>(std::move(last_op), body.where()->expression_);
|
||||
}
|
||||
return last_op;
|
||||
}
|
||||
@@ -496,12 +504,6 @@ 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();) {
|
||||
@@ -515,6 +517,16 @@ 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) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -81,8 +81,8 @@ namespace impl {
|
||||
// removed from `Filters`.
|
||||
Expression *ExtractFilters(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);
|
||||
std::unique_ptr<LogicalOperator> GenFilters(std::unique_ptr<LogicalOperator>, const std::unordered_set<Symbol> &,
|
||||
Filters &, AstStorage &);
|
||||
|
||||
/// Utility function for iterating pattern atoms and accumulating a result.
|
||||
///
|
||||
@@ -174,11 +174,7 @@ class RuleBasedPlanner {
|
||||
input_op = PlanMatching(match_ctx, std::move(input_op));
|
||||
for (const auto &matching : query_part.optional_matching) {
|
||||
MatchContext opt_ctx{matching, *context.symbol_table, context.bound_symbols};
|
||||
|
||||
std::vector<Symbol> bound_symbols(context_->bound_symbols.begin(), context_->bound_symbols.end());
|
||||
auto once_with_symbols = std::make_unique<Once>(bound_symbols);
|
||||
|
||||
auto match_op = PlanMatching(opt_ctx, std::move(once_with_symbols));
|
||||
auto match_op = PlanMatching(opt_ctx, nullptr);
|
||||
if (match_op) {
|
||||
input_op = std::make_unique<Optional>(std::move(input_op), std::move(match_op), opt_ctx.new_symbols);
|
||||
}
|
||||
@@ -398,11 +394,119 @@ 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 = GenFilters(std::move(input_op), bound_symbols, filters, storage, symbol_table);
|
||||
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;
|
||||
|
||||
last_op = HandleExpansion(std::move(last_op), matching, symbol_table, storage, bound_symbols,
|
||||
match_context.new_symbols, named_paths, filters, match_context.view);
|
||||
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(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) {
|
||||
@@ -439,143 +543,6 @@ 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,
|
||||
@@ -596,64 +563,6 @@ 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 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -155,18 +155,6 @@ 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)
|
||||
@@ -221,31 +209,11 @@ 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)),
|
||||
filter_matchings_(VaryFilterMatchingStarts(query_part_.matching, symbol_table)) {}
|
||||
merge_matchings_(VaryMultiMatchingStarts(query_part_.merge_matching, symbol_table)) {}
|
||||
|
||||
VaryQueryPartMatching::iterator::iterator(const SingleQueryPart &query_part,
|
||||
VaryMatchingStart::iterator matchings_begin,
|
||||
@@ -253,9 +221,7 @@ 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 filter_begin,
|
||||
CartesianProduct<VaryMatchingStart>::iterator filter_end)
|
||||
CartesianProduct<VaryMatchingStart>::iterator merge_end)
|
||||
: current_query_part_(query_part),
|
||||
matchings_it_(matchings_begin),
|
||||
matchings_end_(matchings_end),
|
||||
@@ -264,10 +230,7 @@ VaryQueryPartMatching::iterator::iterator(const SingleQueryPart &query_part,
|
||||
optional_end_(optional_end),
|
||||
merge_it_(merge_begin),
|
||||
merge_begin_(merge_begin),
|
||||
merge_end_(merge_end),
|
||||
filter_it_(filter_begin),
|
||||
filter_begin_(filter_begin),
|
||||
filter_end_(filter_end) {
|
||||
merge_end_(merge_end) {
|
||||
if (matchings_it_ != matchings_end_) {
|
||||
// Fill the query part with the first variation of matchings
|
||||
SetCurrentQueryPart();
|
||||
@@ -279,37 +242,26 @@ 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), (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_) {
|
||||
// * (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_) {
|
||||
merge_it_ = merge_begin_;
|
||||
if (optional_it_ != optional_end_) ++optional_it_;
|
||||
}
|
||||
|
||||
if (optional_it_ == optional_end_ && merge_it_ == merge_begin_ && filter_it_ == filter_begin_) {
|
||||
// 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_) {
|
||||
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.
|
||||
@@ -331,28 +283,6 @@ 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 {
|
||||
@@ -361,8 +291,7 @@ 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_ &&
|
||||
filter_it_ == other.filter_it_;
|
||||
return matchings_it_ == other.matchings_it_ && optional_it_ == other.optional_it_ && merge_it_ == other.merge_it_;
|
||||
}
|
||||
|
||||
} // namespace memgraph::query::plan::impl
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -230,8 +230,6 @@ 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 {
|
||||
@@ -247,7 +245,6 @@ 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);
|
||||
|
||||
@@ -269,20 +266,15 @@ 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(),
|
||||
filter_matchings_.begin(), filter_matchings_.end());
|
||||
optional_matchings_.end(), merge_matchings_.begin(), merge_matchings_.end());
|
||||
}
|
||||
auto end() {
|
||||
return iterator(query_part_, matchings_.end(), matchings_.end(), optional_matchings_.end(),
|
||||
optional_matchings_.end(), merge_matchings_.end(), merge_matchings_.end(), filter_matchings_.end(),
|
||||
filter_matchings_.end());
|
||||
optional_matchings_.end(), merge_matchings_.end(), merge_matchings_.end());
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -294,7 +286,6 @@ 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 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -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)
|
||||
mgp_date(const std::string_view string, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), date(memgraph::utils::ParseDateParameters(string).first) {}
|
||||
|
||||
mgp_date(const mgp_date_parameters *parameters, memgraph::utils::MemoryResource *memory)
|
||||
mgp_date(const mgp_date_parameters *parameters, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: 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)
|
||||
mgp_local_time(const std::string_view string, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), local_time(memgraph::utils::ParseLocalTimeParameters(string).first) {}
|
||||
|
||||
mgp_local_time(const mgp_local_time_parameters *parameters, memgraph::utils::MemoryResource *memory)
|
||||
mgp_local_time(const mgp_local_time_parameters *parameters, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), local_time(MapLocalTimeParameters(parameters)) {}
|
||||
|
||||
mgp_local_time(const memgraph::utils::LocalTime &local_time, memgraph::utils::MemoryResource *memory) noexcept
|
||||
@@ -250,7 +250,8 @@ 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)
|
||||
mgp_local_date_time(const mgp_local_date_time_parameters *parameters,
|
||||
memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory),
|
||||
local_date_time(MapDateParameters(parameters->date_parameters),
|
||||
MapLocalTimeParameters(parameters->local_time_parameters)) {}
|
||||
@@ -300,7 +301,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)
|
||||
mgp_duration(const std::string_view string, memgraph::utils::MemoryResource *memory) noexcept
|
||||
: memory(memory), duration(memgraph::utils::ParseDurationParameters(string)) {}
|
||||
|
||||
mgp_duration(const mgp_duration_parameters *parameters, memgraph::utils::MemoryResource *memory) noexcept
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -35,7 +35,7 @@ namespace memgraph::query::procedure {
|
||||
|
||||
constexpr const char *func_code =
|
||||
"import ast\n\n"
|
||||
"no_removals = ['collections', 'abc', 'sys', 'torch', 'torch_geometric', 'igraph']\n"
|
||||
"no_removals = ['collections', 'abc', 'sys']\n"
|
||||
"modules = set()\n\n"
|
||||
"def visit_Import(node):\n"
|
||||
" for name in node.names:\n"
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
// 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
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "slk/streams.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
namespace memgraph::slk {
|
||||
|
||||
/// Class used for basic SLK use-cases.
|
||||
/// It creates a `memgraph::slk::Builder` that can be written to. After you
|
||||
/// have written the data to the builder, you can get a `memgraph::slk::Reader`
|
||||
/// and try to decode the encoded data.
|
||||
class Loopback {
|
||||
public:
|
||||
Loopback() = default;
|
||||
Loopback(const Loopback &) = delete;
|
||||
Loopback &operator=(const Loopback &) = delete;
|
||||
Loopback(Loopback &&) = delete;
|
||||
Loopback &operator=(Loopback &&) = delete;
|
||||
|
||||
~Loopback() {
|
||||
MG_ASSERT(builder_, "You haven't created a builder!");
|
||||
MG_ASSERT(reader_, "You haven't created a reader!");
|
||||
reader_->Finalize();
|
||||
}
|
||||
|
||||
memgraph::slk::Builder *GetBuilder() {
|
||||
MG_ASSERT(!builder_, "You have already allocated a builder!");
|
||||
builder_ = std::make_unique<memgraph::slk::Builder>(
|
||||
[this](const uint8_t *data, size_t size, bool have_more) { Write(data, size, have_more); });
|
||||
return builder_.get();
|
||||
}
|
||||
|
||||
memgraph::slk::Reader *GetReader() {
|
||||
MG_ASSERT(builder_, "You must first get a builder before getting a reader!");
|
||||
MG_ASSERT(!reader_, "You have already allocated a reader!");
|
||||
builder_->Finalize();
|
||||
auto ret = memgraph::slk::CheckStreamComplete(data_.data(), data_.size());
|
||||
MG_ASSERT(ret.status == memgraph::slk::StreamStatus::COMPLETE);
|
||||
MG_ASSERT(ret.stream_size == data_.size());
|
||||
size_ = ret.encoded_data_size;
|
||||
reader_ = std::make_unique<memgraph::slk::Reader>(data_.data(), data_.size());
|
||||
return reader_.get();
|
||||
}
|
||||
|
||||
size_t size() const { return size_; }
|
||||
|
||||
private:
|
||||
void Write(const uint8_t *data, size_t size, bool have_more) {
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
data_.push_back(data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> data_;
|
||||
std::unique_ptr<memgraph::slk::Builder> builder_;
|
||||
std::unique_ptr<memgraph::slk::Reader> reader_;
|
||||
size_t size_{0};
|
||||
};
|
||||
|
||||
} // namespace memgraph::slk
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -46,7 +46,7 @@ static_assert(kSegmentMaxDataSize <= std::numeric_limits<SegmentSize>::max(),
|
||||
/// Builder used to create a SLK segment stream.
|
||||
class Builder {
|
||||
public:
|
||||
explicit Builder(std::function<void(const uint8_t *, size_t, bool)> write_func);
|
||||
Builder(std::function<void(const uint8_t *, size_t, bool)> write_func);
|
||||
|
||||
/// Function used internally by SLK to serialize the data.
|
||||
void Save(const uint8_t *data, uint64_t size);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "storage/v2/edge_accessor.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
|
||||
#include "storage/v2/mvcc.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
@@ -22,47 +21,8 @@
|
||||
namespace memgraph::storage {
|
||||
|
||||
bool EdgeAccessor::IsVisible(const View view) const {
|
||||
bool exists = true;
|
||||
bool deleted = true;
|
||||
// When edges don't have properties, their isolation level is still dictated by MVCC ->
|
||||
// iterate over the deltas of the from_vertex_ and see which deltas can be applied on edges.
|
||||
if (!config_.properties_on_edges) {
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(from_vertex_->lock);
|
||||
// Initialize deleted by checking if out edges contain edge_
|
||||
deleted = std::find_if(from_vertex_->out_edges.begin(), from_vertex_->out_edges.end(), [&](const auto &out_edge) {
|
||||
return std::get<2>(out_edge) == edge_;
|
||||
}) == from_vertex_->out_edges.end();
|
||||
delta = from_vertex_->delta;
|
||||
}
|
||||
ApplyDeltasForRead(transaction_, delta, view, [&](const Delta &delta) {
|
||||
switch (delta.action) {
|
||||
case Delta::Action::ADD_LABEL:
|
||||
case Delta::Action::REMOVE_LABEL:
|
||||
case Delta::Action::SET_PROPERTY:
|
||||
case Delta::Action::REMOVE_IN_EDGE:
|
||||
case Delta::Action::ADD_IN_EDGE:
|
||||
case Delta::Action::RECREATE_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT:
|
||||
break;
|
||||
case Delta::Action::ADD_OUT_EDGE: { // relevant for the from_vertex_ -> we just deleted the edge
|
||||
if (delta.vertex_edge.edge == edge_) {
|
||||
deleted = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Delta::Action::REMOVE_OUT_EDGE: { // also relevant for the from_vertex_ -> we just added the edge
|
||||
if (delta.vertex_edge.edge == edge_) {
|
||||
exists = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
return exists && (for_deleted_ || !deleted);
|
||||
}
|
||||
|
||||
bool exists = true;
|
||||
Delta *delta = nullptr;
|
||||
{
|
||||
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
||||
@@ -89,6 +49,7 @@ bool EdgeAccessor::IsVisible(const View view) const {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return exists && (for_deleted_ || !deleted);
|
||||
}
|
||||
|
||||
@@ -123,24 +84,6 @@ 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 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -58,11 +58,6 @@ 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 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -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,64 +1144,6 @@ 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 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -59,12 +59,6 @@ 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,11 +109,9 @@ void Storage::ReplicationClient::InitializeClient() {
|
||||
}
|
||||
if (branching_point) {
|
||||
spdlog::error(
|
||||
"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_);
|
||||
"Replica {} cannot be used with this instance. Please start a clean "
|
||||
"instance of Memgraph server on the specified endpoint.",
|
||||
name_);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -230,23 +230,6 @@ 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 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -68,11 +68,6 @@ 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();
|
||||
|
||||
@@ -40,7 +40,7 @@ std::optional<utils::pmr::string> Reader::GetNextLine(utils::MemoryResource *mem
|
||||
return std::nullopt;
|
||||
}
|
||||
++line_count_;
|
||||
return std::move(line);
|
||||
return line;
|
||||
}
|
||||
|
||||
Reader::ParsingResult Reader::ParseHeader() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -51,7 +51,6 @@
|
||||
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.") \
|
||||
|
||||
@@ -251,10 +251,9 @@ void Pool::Release() {
|
||||
|
||||
} // namespace impl
|
||||
|
||||
PoolResource::PoolResource(size_t max_blocks_per_chunk, size_t max_block_size, MemoryResource *memory_pools,
|
||||
MemoryResource *memory_unpooled)
|
||||
: pools_(memory_pools),
|
||||
unpooled_(memory_unpooled),
|
||||
PoolResource::PoolResource(size_t max_blocks_per_chunk, size_t max_block_size, MemoryResource *memory)
|
||||
: pools_(memory),
|
||||
unpooled_(memory),
|
||||
max_blocks_per_chunk_(std::min(max_blocks_per_chunk, static_cast<size_t>(impl::Pool::MaxBlocksInChunk()))),
|
||||
max_block_size_(max_block_size) {
|
||||
MG_ASSERT(max_blocks_per_chunk_ > 0U, "Invalid number of blocks per chunk");
|
||||
@@ -274,14 +273,14 @@ void *PoolResource::DoAllocate(size_t bytes, size_t alignment) {
|
||||
if (block_size % alignment != 0) throw BadAlloc("Requested bytes must be a multiple of alignment");
|
||||
if (block_size > max_block_size_) {
|
||||
// Allocate a big block.
|
||||
BigBlock big_block{bytes, alignment, GetUpstreamResourceBlocks()->Allocate(bytes, alignment)};
|
||||
BigBlock big_block{bytes, alignment, GetUpstreamResource()->Allocate(bytes, alignment)};
|
||||
// Insert the big block in the sorted position.
|
||||
auto it = std::lower_bound(unpooled_.begin(), unpooled_.end(), big_block,
|
||||
[](const auto &a, const auto &b) { return a.data < b.data; });
|
||||
try {
|
||||
unpooled_.insert(it, big_block);
|
||||
} catch (...) {
|
||||
GetUpstreamResourceBlocks()->Deallocate(big_block.data, bytes, alignment);
|
||||
GetUpstreamResource()->Deallocate(big_block.data, bytes, alignment);
|
||||
throw;
|
||||
}
|
||||
return big_block.data;
|
||||
@@ -319,7 +318,7 @@ void PoolResource::DoDeallocate(void *p, size_t bytes, size_t alignment) {
|
||||
MG_ASSERT(it != unpooled_.end(), "Failed deallocation");
|
||||
MG_ASSERT(it->data == p && it->bytes == bytes && it->alignment == alignment, "Failed deallocation");
|
||||
unpooled_.erase(it);
|
||||
GetUpstreamResourceBlocks()->Deallocate(p, bytes, alignment);
|
||||
GetUpstreamResource()->Deallocate(p, bytes, alignment);
|
||||
return;
|
||||
}
|
||||
// Deallocate a regular block, first check if last_dealloc_pool_ is suitable.
|
||||
@@ -340,7 +339,7 @@ void PoolResource::Release() {
|
||||
for (auto &pool : pools_) pool.Release();
|
||||
pools_.clear();
|
||||
for (auto &big_block : unpooled_)
|
||||
GetUpstreamResourceBlocks()->Deallocate(big_block.data, big_block.bytes, big_block.alignment);
|
||||
GetUpstreamResource()->Deallocate(big_block.data, big_block.bytes, big_block.alignment);
|
||||
unpooled_.clear();
|
||||
last_alloc_pool_ = nullptr;
|
||||
last_dealloc_pool_ = nullptr;
|
||||
|
||||
@@ -469,8 +469,7 @@ class PoolResource final : public MemoryResource {
|
||||
/// impl::Pool::MaxBlocksInChunk()) as the real maximum number of blocks per
|
||||
/// chunk. Allocation requests exceeding max_block_size are simply forwarded
|
||||
/// to upstream memory.
|
||||
PoolResource(size_t max_blocks_per_chunk, size_t max_block_size, MemoryResource *memory_pools = NewDeleteResource(),
|
||||
MemoryResource *memory_unpooled = NewDeleteResource());
|
||||
PoolResource(size_t max_blocks_per_chunk, size_t max_block_size, MemoryResource *memory = NewDeleteResource());
|
||||
|
||||
PoolResource(const PoolResource &) = delete;
|
||||
PoolResource &operator=(const PoolResource &) = delete;
|
||||
@@ -481,7 +480,6 @@ class PoolResource final : public MemoryResource {
|
||||
~PoolResource() override { Release(); }
|
||||
|
||||
MemoryResource *GetUpstreamResource() const { return pools_.get_allocator().GetMemoryResource(); }
|
||||
MemoryResource *GetUpstreamResourceBlocks() const { return unpooled_.get_allocator().GetMemoryResource(); }
|
||||
|
||||
/// Release all allocated memory.
|
||||
void Release();
|
||||
|
||||
@@ -44,7 +44,6 @@ add_subdirectory(module_file_manager)
|
||||
add_subdirectory(monitoring_server)
|
||||
add_subdirectory(lba_procedures)
|
||||
add_subdirectory(python_query_modules_reloading)
|
||||
add_subdirectory(mock_api)
|
||||
|
||||
copy_e2e_python_files(pytest_runner pytest_runner.sh "")
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/memgraph-selfsigned.crt DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
@@ -9,12 +9,13 @@
|
||||
# by the Apache License, Version 2.0, included in the file
|
||||
# licenses/APL.txt.
|
||||
|
||||
import pytest
|
||||
import sys
|
||||
|
||||
import common
|
||||
import pytest
|
||||
from mgclient import DatabaseError
|
||||
|
||||
import common
|
||||
|
||||
|
||||
def test_create_node_all_labels_granted():
|
||||
admin_connection = common.connect(username="admin", password="test")
|
||||
@@ -512,21 +513,5 @@ def test_remove_label_when_label_denied():
|
||||
common.execute_and_fetch_all(user_connection.cursor(), "MATCH (p:test_delete) REMOVE p:test_delete;")
|
||||
|
||||
|
||||
def test_merge_nodes_pass_when_having_create_delete():
|
||||
admin_connection = common.connect(username="admin", password="test")
|
||||
user_connection = common.connect(username="user", password="test")
|
||||
|
||||
common.reset_and_prepare(admin_connection.cursor())
|
||||
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
|
||||
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON EDGE_TYPES * TO user;")
|
||||
|
||||
results = common.execute_and_fetch_all(
|
||||
user_connection.cursor(),
|
||||
"UNWIND [{id: '1', lat: 10, lng: 10}, {id: '2', lat: 10, lng: 10}, {id: '3', lat: 10, lng: 10}] AS row MERGE (o:Location {id: row.id}) RETURN o;",
|
||||
)
|
||||
|
||||
assert len(results) == 3
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-rA"]))
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
function(copy_mock_python_api_e2e_files FILE_NAME)
|
||||
copy_e2e_python_files(mock_python_api ${FILE_NAME})
|
||||
endfunction()
|
||||
|
||||
add_subdirectory(procedures)
|
||||
|
||||
copy_mock_python_api_e2e_files(common.py)
|
||||
copy_mock_python_api_e2e_files(test_compare_mock.py)
|
||||
@@ -1,14 +0,0 @@
|
||||
import typing
|
||||
|
||||
import mgclient
|
||||
|
||||
|
||||
def connect(**kwargs) -> mgclient.Connection:
|
||||
connection = mgclient.connect(host="localhost", port=7687, **kwargs)
|
||||
connection.autocommit = True
|
||||
return connection
|
||||
|
||||
|
||||
def execute_and_fetch_results_dict(cursor, query) -> typing.Dict:
|
||||
cursor.execute(query)
|
||||
return cursor.fetchall()[0][0]
|
||||
@@ -1,10 +0,0 @@
|
||||
copy_mock_python_api_e2e_files(test_utils.py)
|
||||
copy_mock_python_api_e2e_files(edge_type.py)
|
||||
copy_mock_python_api_e2e_files(edge.py)
|
||||
copy_mock_python_api_e2e_files(graph.py)
|
||||
copy_mock_python_api_e2e_files(label.py)
|
||||
copy_mock_python_api_e2e_files(path.py)
|
||||
copy_mock_python_api_e2e_files(properties.py)
|
||||
copy_mock_python_api_e2e_files(record.py)
|
||||
copy_mock_python_api_e2e_files(vertex.py)
|
||||
copy_mock_python_api_e2e_files(vertices.py)
|
||||
@@ -1,85 +0,0 @@
|
||||
import mgp
|
||||
import mgp_mock
|
||||
import test_utils
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def compare_apis(ctx: mgp.ProcCtx) -> mgp.Record(results_dict=mgp.Map):
|
||||
mock_ctx = test_utils.get_mock_proc_ctx(is_write=False)
|
||||
results = dict()
|
||||
|
||||
TARGET_EDGE_1_ID = 9
|
||||
TARGET_EDGE_2_ID = 37
|
||||
|
||||
target_edge_1 = test_utils.get_edge(ctx, permanent_id=TARGET_EDGE_1_ID)
|
||||
target_edge_2 = test_utils.get_edge(ctx, permanent_id=TARGET_EDGE_2_ID)
|
||||
target_mock_edge_1 = test_utils.get_mock_edge(mock_ctx, id=TARGET_EDGE_1_ID)
|
||||
target_mock_edge_2 = test_utils.get_mock_edge(mock_ctx, id=TARGET_EDGE_2_ID)
|
||||
|
||||
results["is_valid"] = test_utils.all_equal(
|
||||
target_edge_1.is_valid(),
|
||||
target_mock_edge_1.is_valid(),
|
||||
True,
|
||||
)
|
||||
|
||||
results["underlying_graph_is_mutable"] = test_utils.all_equal(
|
||||
target_edge_1.underlying_graph_is_mutable(),
|
||||
target_mock_edge_1.underlying_graph_is_mutable(),
|
||||
False,
|
||||
)
|
||||
|
||||
results["id"] = test_utils.all_equal(
|
||||
isinstance(target_edge_1.id, int),
|
||||
isinstance(target_mock_edge_1.id, int),
|
||||
True,
|
||||
)
|
||||
|
||||
results["type"] = test_utils.all_equal(
|
||||
target_edge_1.type.name,
|
||||
target_mock_edge_1.type.name,
|
||||
"HAS_TEAM",
|
||||
)
|
||||
|
||||
results["from_vertex"] = test_utils.all_equal(
|
||||
isinstance(target_edge_1.from_vertex, mgp.Vertex),
|
||||
isinstance(target_mock_edge_1.from_vertex, mgp_mock.Vertex),
|
||||
True,
|
||||
)
|
||||
|
||||
results["to_vertex"] = test_utils.all_equal(
|
||||
isinstance(target_edge_1.to_vertex, mgp.Vertex),
|
||||
isinstance(target_mock_edge_1.to_vertex, mgp_mock.Vertex),
|
||||
True,
|
||||
)
|
||||
|
||||
results["properties"] = test_utils.all_equal(
|
||||
isinstance(target_edge_1.properties, mgp.Properties),
|
||||
isinstance(target_mock_edge_1.properties, mgp_mock.Properties),
|
||||
True,
|
||||
) and test_utils.all_equal(
|
||||
{prop.name: prop.value for prop in target_edge_1.properties.items()},
|
||||
{prop.name: prop.value for prop in target_mock_edge_1.properties.items()},
|
||||
{"permanent_id": 9},
|
||||
)
|
||||
|
||||
results["__eq__"] = test_utils.all_equal(
|
||||
target_edge_1 == target_edge_1,
|
||||
target_mock_edge_1 == target_mock_edge_1,
|
||||
True,
|
||||
) and test_utils.all_equal(
|
||||
target_edge_1 != target_edge_1,
|
||||
target_mock_edge_1 != target_mock_edge_1,
|
||||
False,
|
||||
)
|
||||
|
||||
results["__ne__"] = test_utils.all_equal(
|
||||
target_edge_1 != target_edge_2,
|
||||
target_mock_edge_1 != target_mock_edge_2,
|
||||
True,
|
||||
) and test_utils.all_equal(
|
||||
target_edge_1 == target_edge_2,
|
||||
target_mock_edge_1 == target_mock_edge_2,
|
||||
False,
|
||||
)
|
||||
|
||||
return mgp.Record(results_dict=results)
|
||||
@@ -1,33 +0,0 @@
|
||||
import mgp
|
||||
import test_utils
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def compare_apis(ctx: mgp.ProcCtx) -> mgp.Record(results_dict=mgp.Map):
|
||||
mock_ctx = test_utils.get_mock_proc_ctx(is_write=False)
|
||||
results = dict()
|
||||
|
||||
TARGET_EDGE_ID = 0
|
||||
|
||||
target_edge_type = test_utils.get_edge(ctx, permanent_id=TARGET_EDGE_ID).type
|
||||
target_mock_edge_type = test_utils.get_mock_edge(mock_ctx, id=TARGET_EDGE_ID).type
|
||||
|
||||
results["name"] = test_utils.all_equal(
|
||||
target_edge_type.name,
|
||||
target_mock_edge_type.name,
|
||||
"IS_PART_OF",
|
||||
)
|
||||
|
||||
results["__eq__"] = test_utils.all_equal(
|
||||
target_edge_type == target_edge_type,
|
||||
target_edge_type == "IS_PART_OF",
|
||||
target_mock_edge_type == target_mock_edge_type,
|
||||
target_mock_edge_type == "IS_PART_OF",
|
||||
)
|
||||
|
||||
results["__ne__"] = test_utils.all_equal(
|
||||
target_edge_type != "HAS_TEAM",
|
||||
target_mock_edge_type != "HAS_TEAM",
|
||||
)
|
||||
|
||||
return mgp.Record(results_dict=results)
|
||||
@@ -1,102 +0,0 @@
|
||||
import mgp
|
||||
import mgp_mock
|
||||
import test_utils
|
||||
|
||||
|
||||
@mgp.write_proc
|
||||
def compare_apis(ctx: mgp.ProcCtx) -> mgp.Record(results_dict=mgp.Map):
|
||||
VERTEX_ID = 6
|
||||
|
||||
mock_ctx = test_utils.get_mock_proc_ctx(is_write=True)
|
||||
results = dict()
|
||||
|
||||
results["is_valid"] = test_utils.all_equal(
|
||||
ctx.graph.is_valid(),
|
||||
mock_ctx.graph.is_valid(),
|
||||
True,
|
||||
)
|
||||
|
||||
results["get_vertex_by_id"] = test_utils.all_equal(
|
||||
test_utils.get_vertex(ctx, permanent_id=VERTEX_ID).properties["permanent_id"],
|
||||
mock_ctx.graph.get_vertex_by_id(VERTEX_ID).properties["permanent_id"],
|
||||
VERTEX_ID,
|
||||
)
|
||||
|
||||
results["vertices"] = test_utils.all_equal(
|
||||
len(ctx.graph.vertices),
|
||||
len(mock_ctx.graph.vertices),
|
||||
27,
|
||||
)
|
||||
|
||||
results["is_mutable"] = test_utils.all_equal(
|
||||
ctx.graph.is_mutable(),
|
||||
mock_ctx.graph.is_mutable(),
|
||||
True,
|
||||
)
|
||||
|
||||
new_mock_vertex = mock_ctx.graph.create_vertex()
|
||||
new_mock_vertex_id = new_mock_vertex.id
|
||||
results["create_vertex"] = test_utils.all_equal(
|
||||
new_mock_vertex_id in [v.id for v in mock_ctx.graph.vertices],
|
||||
True,
|
||||
)
|
||||
|
||||
mock_ctx.graph.delete_vertex(new_mock_vertex)
|
||||
results["delete_vertex"] = test_utils.all_equal(
|
||||
new_mock_vertex_id not in [v.id for v in mock_ctx.graph.vertices],
|
||||
True,
|
||||
)
|
||||
|
||||
mock_vertex_to_delete = mock_ctx.graph.get_vertex_by_id(VERTEX_ID)
|
||||
mock_ctx.graph.detach_delete_vertex(mock_vertex_to_delete)
|
||||
results["detach_delete_vertex"] = test_utils.all_equal(
|
||||
VERTEX_ID not in [v.properties["permanent_id"] for v in mock_ctx.graph.vertices],
|
||||
True,
|
||||
)
|
||||
|
||||
MAX_EDGE_ID = 37
|
||||
|
||||
START_ID = 10
|
||||
END1_ID = 13
|
||||
END2_ID = 14
|
||||
start_mock_vertex, end1_mock_vertex, end2_mock_vertex = (
|
||||
mock_ctx.graph.get_vertex_by_id(START_ID),
|
||||
mock_ctx.graph.get_vertex_by_id(END1_ID),
|
||||
mock_ctx.graph.get_vertex_by_id(END2_ID),
|
||||
)
|
||||
EDGE_TYPE = "CONNECTED_TO"
|
||||
mock_edge_type = mgp_mock.EdgeType(EDGE_TYPE)
|
||||
new_mock_edge = mock_ctx.graph.create_edge(start_mock_vertex, end1_mock_vertex, mock_edge_type)
|
||||
new_mock_edge_id = new_mock_edge.id
|
||||
results["create_edge"] = test_utils.all_equal(
|
||||
new_mock_edge_id,
|
||||
MAX_EDGE_ID + 1,
|
||||
)
|
||||
|
||||
mock_ctx.graph.delete_edge(new_mock_edge)
|
||||
results["delete_edge"] = test_utils.all_equal(
|
||||
new_mock_edge_id not in [e.id for e in start_mock_vertex.out_edges],
|
||||
True,
|
||||
)
|
||||
|
||||
another_mock_edge = mock_ctx.graph.create_edge(start_mock_vertex, end2_mock_vertex, mock_edge_type)
|
||||
results["edge_id_assignment"] = test_utils.all_equal(
|
||||
another_mock_edge.id,
|
||||
MAX_EDGE_ID + 2,
|
||||
)
|
||||
|
||||
return mgp.Record(results_dict=results)
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def test_read_proc_mutability(ctx: mgp.ProcCtx) -> mgp.Record(results_dict=mgp.Map):
|
||||
mock_ctx = test_utils.get_mock_proc_ctx(is_write=False)
|
||||
results = dict()
|
||||
|
||||
results["is_not_mutable"] = test_utils.all_equal(
|
||||
ctx.graph.is_mutable(),
|
||||
mock_ctx.graph.is_mutable(),
|
||||
False,
|
||||
)
|
||||
|
||||
return mgp.Record(results_dict=results)
|
||||
@@ -1,52 +0,0 @@
|
||||
import mgp
|
||||
import test_utils
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def compare_apis(ctx: mgp.ProcCtx) -> mgp.Record(results_dict=mgp.Map):
|
||||
mock_ctx = test_utils.get_mock_proc_ctx(is_write=False)
|
||||
results = dict()
|
||||
|
||||
TARGET_LABELLED_NODE_ID = 5
|
||||
|
||||
target_vertex = test_utils.get_vertex(ctx, permanent_id=TARGET_LABELLED_NODE_ID)
|
||||
target_mock_vertex = mock_ctx.graph.get_vertex_by_id(TARGET_LABELLED_NODE_ID)
|
||||
|
||||
label_1, label_2 = sorted(target_vertex.labels, key=lambda l: l.name) # ("Company", "Startup")
|
||||
mock_label_1, mock_label_2 = sorted(target_mock_vertex.labels, key=lambda l: l.name) # ditto
|
||||
|
||||
results["name"] = test_utils.all_equal(
|
||||
(label_1.name, label_2.name),
|
||||
(mock_label_1.name, mock_label_2.name),
|
||||
("Company", "Startup"),
|
||||
)
|
||||
|
||||
results["__eq__"] = test_utils.all_equal(
|
||||
label_1 == label_1,
|
||||
label_1 == "Company",
|
||||
mock_label_1 == mock_label_1,
|
||||
mock_label_1 == "Company",
|
||||
True,
|
||||
) and test_utils.all_equal(
|
||||
label_1 == label_2,
|
||||
label_1 == "Startup",
|
||||
mock_label_1 == mock_label_2,
|
||||
mock_label_1 == "Startup",
|
||||
False,
|
||||
)
|
||||
|
||||
results["__ne__"] = test_utils.all_equal(
|
||||
label_1 != label_2,
|
||||
label_1 != "Startup",
|
||||
mock_label_1 != mock_label_2,
|
||||
mock_label_1 != "Startup",
|
||||
True,
|
||||
) and test_utils.all_equal(
|
||||
label_1 != label_1,
|
||||
label_1 != "Company",
|
||||
mock_label_1 != mock_label_1,
|
||||
mock_label_1 != "Company",
|
||||
False,
|
||||
)
|
||||
|
||||
return mgp.Record(results_dict=results)
|
||||
@@ -1,65 +0,0 @@
|
||||
import copy
|
||||
|
||||
import mgp
|
||||
import mgp_mock
|
||||
import test_utils
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def compare_apis(ctx: mgp.ProcCtx) -> mgp.Record(results_dict=mgp.Map):
|
||||
mock_ctx = test_utils.get_mock_proc_ctx(is_write=False)
|
||||
results = dict()
|
||||
|
||||
START_ID = 0
|
||||
start_vertex = test_utils.get_vertex(ctx, permanent_id=START_ID)
|
||||
mock_start_vertex = mock_ctx.graph.get_vertex_by_id(START_ID)
|
||||
path = mgp.Path(start_vertex)
|
||||
mock_path = mgp_mock.Path(mock_start_vertex)
|
||||
|
||||
results["is_valid"] = test_utils.all_equal(
|
||||
path.is_valid(),
|
||||
mock_path.is_valid(),
|
||||
True,
|
||||
)
|
||||
|
||||
EDGE_ID = 0
|
||||
edge_to_add = test_utils.get_edge(ctx, permanent_id=EDGE_ID)
|
||||
mock_edge_to_add = test_utils.get_mock_edge(mock_ctx, id=EDGE_ID)
|
||||
path.expand(edge_to_add)
|
||||
mock_path.expand(mock_edge_to_add)
|
||||
results["expand"] = test_utils.all_equal(
|
||||
(len(path.vertices), len(path.edges)),
|
||||
(len(mock_path.vertices), len(mock_path.edges)),
|
||||
(2, 1),
|
||||
)
|
||||
|
||||
NEXT_ID = 1
|
||||
results["vertices"] = test_utils.all_equal(
|
||||
all(isinstance(vertex, mgp.Vertex) for vertex in path.vertices),
|
||||
all(isinstance(vertex, mgp_mock.Vertex) for vertex in mock_path.vertices),
|
||||
True,
|
||||
) and test_utils.all_equal(
|
||||
[vertex.properties["permanent_id"] for vertex in path.vertices],
|
||||
[vertex.properties["permanent_id"] for vertex in mock_path.vertices],
|
||||
[START_ID, NEXT_ID],
|
||||
)
|
||||
|
||||
results["edges"] = test_utils.all_equal(
|
||||
all(isinstance(edge, mgp.Edge) for edge in path.edges),
|
||||
all(isinstance(edge, mgp_mock.Edge) for edge in mock_path.edges),
|
||||
True,
|
||||
) and test_utils.all_equal(
|
||||
[edge.properties["permanent_id"] for edge in path.edges],
|
||||
[edge.properties["permanent_id"] for edge in mock_path.edges],
|
||||
[0],
|
||||
)
|
||||
|
||||
path_copy = copy.copy(path)
|
||||
mock_path_copy = copy.copy(mock_path)
|
||||
results["__copy__"] = test_utils.all_equal(
|
||||
path_copy.is_valid(),
|
||||
mock_path_copy.is_valid(),
|
||||
True,
|
||||
)
|
||||
|
||||
return mgp.Record(results_dict=results)
|
||||
@@ -1,184 +0,0 @@
|
||||
import mgp
|
||||
import test_utils
|
||||
|
||||
|
||||
@mgp.write_proc
|
||||
def compare_apis_on_vertex(ctx: mgp.ProcCtx) -> mgp.Record(results_dict=mgp.Map):
|
||||
mock_ctx = test_utils.get_mock_proc_ctx(is_write=True)
|
||||
results = dict()
|
||||
|
||||
TARGET_ID = 0
|
||||
target_vertex = test_utils.get_vertex(ctx, permanent_id=TARGET_ID)
|
||||
target_mock_vertex = mock_ctx.graph.get_vertex_by_id(TARGET_ID)
|
||||
|
||||
properties = target_vertex.properties
|
||||
mock_properties = target_mock_vertex.properties
|
||||
|
||||
results["get"] = test_utils.all_equal(
|
||||
properties.get("name"),
|
||||
mock_properties.get("name"),
|
||||
"Peter",
|
||||
)
|
||||
results["get[default]"] = test_utils.all_equal(
|
||||
properties.get("YoE", default="N/A"),
|
||||
mock_properties.get("YoE", default="N/A"),
|
||||
"N/A",
|
||||
)
|
||||
|
||||
properties.set("education", "PhD")
|
||||
mock_properties.set("education", "PhD")
|
||||
results["set"] = test_utils.all_equal(
|
||||
properties.get("education"),
|
||||
mock_properties.get("education"),
|
||||
"PhD",
|
||||
)
|
||||
|
||||
results["items"] = test_utils.all_equal(
|
||||
{prop.name: prop.value for prop in properties.items()},
|
||||
{prop.name: prop.value for prop in mock_properties.items()},
|
||||
{"name": "Peter", "surname": "Yang", "education": "PhD", "permanent_id": 0},
|
||||
)
|
||||
|
||||
results["keys"] = test_utils.all_equal(
|
||||
{key for key in properties.keys()},
|
||||
{key for key in mock_properties.keys()},
|
||||
{"name", "surname", "education", "permanent_id"},
|
||||
)
|
||||
|
||||
results["values"] = test_utils.all_equal(
|
||||
{val for val in properties.values()},
|
||||
{val for val in mock_properties.values()},
|
||||
{"Peter", "Yang", "PhD", 0},
|
||||
)
|
||||
|
||||
results["__len__"] = test_utils.all_equal(
|
||||
len(properties),
|
||||
len(mock_properties),
|
||||
4,
|
||||
)
|
||||
|
||||
results["__iter__"] = test_utils.all_equal(
|
||||
{name for name in properties},
|
||||
{name for name in mock_properties},
|
||||
{"name", "surname", "education", "permanent_id"},
|
||||
)
|
||||
|
||||
results["__getitem__"] = test_utils.all_equal(
|
||||
{properties[name] for name in properties},
|
||||
{mock_properties[name] for name in mock_properties},
|
||||
{"Peter", "Yang", "PhD", 0},
|
||||
)
|
||||
|
||||
properties["YoE"] = 6
|
||||
mock_properties["YoE"] = 6
|
||||
results["__setitem__"] = test_utils.all_equal(
|
||||
properties["YoE"],
|
||||
mock_properties["YoE"],
|
||||
6,
|
||||
)
|
||||
|
||||
results["__contains__"] = test_utils.all_equal(
|
||||
"YoE" in properties,
|
||||
"age" not in properties,
|
||||
"YoE" in mock_properties,
|
||||
"age" not in mock_properties,
|
||||
True,
|
||||
) and test_utils.all_equal(
|
||||
"YoE" not in properties,
|
||||
"age" in properties,
|
||||
"YoE" not in mock_properties,
|
||||
"age" in mock_properties,
|
||||
False,
|
||||
)
|
||||
|
||||
return mgp.Record(results_dict=results)
|
||||
|
||||
|
||||
@mgp.write_proc
|
||||
def compare_apis_on_edge(ctx: mgp.ProcCtx) -> mgp.Record(results_dict=mgp.Map):
|
||||
mock_ctx = test_utils.get_mock_proc_ctx(is_write=True)
|
||||
results = dict()
|
||||
|
||||
TARGET_EDGE_ID = 37
|
||||
|
||||
target_edge_properties = test_utils.get_edge(ctx, permanent_id=TARGET_EDGE_ID).properties
|
||||
target_mock_edge_properties = test_utils.get_mock_edge(mock_ctx, id=TARGET_EDGE_ID).properties
|
||||
|
||||
results["get"] = test_utils.all_equal(
|
||||
target_edge_properties.get("importance"),
|
||||
target_mock_edge_properties.get("importance"),
|
||||
"HIGH",
|
||||
)
|
||||
results["get[default]"] = test_utils.all_equal(
|
||||
target_edge_properties.get("priority", default="N/A"),
|
||||
target_mock_edge_properties.get("priority", default="N/A"),
|
||||
"N/A",
|
||||
)
|
||||
|
||||
target_edge_properties.set("priority", "MEDIUM")
|
||||
target_mock_edge_properties.set("priority", "MEDIUM")
|
||||
results["set"] = test_utils.all_equal(
|
||||
target_edge_properties.get("priority"),
|
||||
target_mock_edge_properties.get("priority"),
|
||||
"MEDIUM",
|
||||
)
|
||||
|
||||
results["items"] = test_utils.all_equal(
|
||||
{prop.name: prop.value for prop in target_edge_properties.items()},
|
||||
{prop.name: prop.value for prop in target_mock_edge_properties.items()},
|
||||
{"importance": "HIGH", "priority": "MEDIUM", "permanent_id": 37},
|
||||
)
|
||||
|
||||
results["keys"] = test_utils.all_equal(
|
||||
{key for key in target_edge_properties.keys()},
|
||||
{key for key in target_mock_edge_properties.keys()},
|
||||
{"importance", "priority", "permanent_id"},
|
||||
)
|
||||
|
||||
results["values"] = test_utils.all_equal(
|
||||
{val for val in target_edge_properties.values()},
|
||||
{val for val in target_mock_edge_properties.values()},
|
||||
{"HIGH", "MEDIUM", 37},
|
||||
)
|
||||
|
||||
results["__len__"] = test_utils.all_equal(
|
||||
len(target_edge_properties),
|
||||
len(target_mock_edge_properties),
|
||||
3,
|
||||
)
|
||||
|
||||
results["__iter__"] = test_utils.all_equal(
|
||||
{name for name in target_edge_properties},
|
||||
{name for name in target_mock_edge_properties},
|
||||
{"importance", "priority", "permanent_id"},
|
||||
)
|
||||
|
||||
results["__getitem__"] = test_utils.all_equal(
|
||||
{target_edge_properties[name] for name in target_edge_properties},
|
||||
{target_mock_edge_properties[name] for name in target_mock_edge_properties},
|
||||
{"HIGH", "MEDIUM", 37},
|
||||
)
|
||||
|
||||
target_edge_properties["priority"] = "LOW"
|
||||
target_mock_edge_properties["priority"] = "LOW"
|
||||
results["__setitem__"] = test_utils.all_equal(
|
||||
target_edge_properties["priority"],
|
||||
target_mock_edge_properties["priority"],
|
||||
"LOW",
|
||||
)
|
||||
|
||||
results["__contains__"] = test_utils.all_equal(
|
||||
"priority" in target_edge_properties,
|
||||
"status" not in target_edge_properties,
|
||||
"priority" in target_mock_edge_properties,
|
||||
"status" not in target_mock_edge_properties,
|
||||
True,
|
||||
) and test_utils.all_equal(
|
||||
"priority" not in target_edge_properties,
|
||||
"status" in target_edge_properties,
|
||||
"priority" not in target_mock_edge_properties,
|
||||
"status" in target_mock_edge_properties,
|
||||
False,
|
||||
)
|
||||
|
||||
return mgp.Record(results_dict=results)
|
||||
@@ -1,18 +0,0 @@
|
||||
import mgp
|
||||
import mgp_mock
|
||||
import test_utils
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def compare_apis(ctx: mgp.ProcCtx) -> mgp.Record(results_dict=mgp.Map):
|
||||
results = dict()
|
||||
|
||||
record = mgp.Record(a=1, b=2.0, c="3")
|
||||
mock_record = mgp_mock.Record(a=1, b=2.0, c="3")
|
||||
|
||||
results["fields"] = test_utils.all_equal(
|
||||
record.fields,
|
||||
mock_record.fields,
|
||||
)
|
||||
|
||||
return mgp.Record(results_dict=results)
|
||||
@@ -1,160 +0,0 @@
|
||||
from itertools import groupby
|
||||
|
||||
import _mgp_mock
|
||||
import mgp
|
||||
import mgp_mock
|
||||
import networkx as nx
|
||||
|
||||
|
||||
def all_equal(*args):
|
||||
"""Returns True if all the elements are equal to each other
|
||||
(source: https://docs.python.org/3/library/itertools.html#itertools-recipes)"""
|
||||
g = groupby(args)
|
||||
return next(g, True) and not next(g, False)
|
||||
|
||||
|
||||
def get_mock_proc_ctx(is_write: bool) -> mgp_mock.ProcCtx:
|
||||
GRAPH_DATA = [
|
||||
(0, 1, 0),
|
||||
(5, 1, 9),
|
||||
(5, 1, 37),
|
||||
(10, 1, 15),
|
||||
(1, 2, 4),
|
||||
(1, 3, 5),
|
||||
(1, 4, 6),
|
||||
(0, 5, 1),
|
||||
(10, 5, 16),
|
||||
(22, 5, 33),
|
||||
(1, 6, 7),
|
||||
(6, 7, 12),
|
||||
(11, 7, 18),
|
||||
(13, 7, 26),
|
||||
(26, 7, 35),
|
||||
(6, 8, 13),
|
||||
(26, 8, 36),
|
||||
(0, 9, 2),
|
||||
(10, 9, 17),
|
||||
(22, 9, 34),
|
||||
(1, 11, 8),
|
||||
(9, 12, 14),
|
||||
(14, 13, 27),
|
||||
(0, 14, 3),
|
||||
(5, 14, 11),
|
||||
(12, 14, 22),
|
||||
(13, 15, 23),
|
||||
(13, 16, 24),
|
||||
(13, 17, 25),
|
||||
(11, 18, 19),
|
||||
(11, 19, 20),
|
||||
(11, 20, 21),
|
||||
(5, 21, 10),
|
||||
(22, 21, 32),
|
||||
(21, 23, 28),
|
||||
(21, 24, 29),
|
||||
(21, 25, 30),
|
||||
(21, 26, 31),
|
||||
]
|
||||
NODE_INFO = {
|
||||
0: {"labels": "Person", "name": "Peter", "surname": "Yang", "permanent_id": 0},
|
||||
1: {"labels": "Team", "name": "Engineering", "permanent_id": 1},
|
||||
2: {"labels": "Repository", "name": "Memgraph", "permanent_id": 2},
|
||||
3: {"labels": "Repository", "name": "MAGE", "permanent_id": 3},
|
||||
4: {"labels": "Repository", "name": "GQLAlchemy", "permanent_id": 4},
|
||||
5: {"labels": "Company:Startup", "name": "Memgraph", "permanent_id": 5},
|
||||
6: {"labels": "File", "name": "welcome_to_engineering.txt", "permanent_id": 6},
|
||||
7: {"labels": "Storage", "name": "Google Drive", "permanent_id": 7},
|
||||
8: {"labels": "Storage", "name": "Notion", "permanent_id": 8},
|
||||
9: {"labels": "File", "name": "welcome_to_memgraph.txt", "permanent_id": 9},
|
||||
10: {"labels": "Person", "name": "Carl", "permanent_id": 10},
|
||||
11: {"labels": "Folder", "name": "engineering_folder", "permanent_id": 11},
|
||||
12: {"labels": "Person", "name": "Anna", "permanent_id": 12},
|
||||
13: {"labels": "Folder", "name": "operations_folder", "permanent_id": 13},
|
||||
14: {"labels": "Team", "name": "Operations", "permanent_id": 14},
|
||||
15: {"labels": "File", "name": "operations101.txt", "permanent_id": 15},
|
||||
16: {"labels": "File", "name": "expenses2022.csv", "permanent_id": 16},
|
||||
17: {"labels": "File", "name": "salaries2022.csv", "permanent_id": 17},
|
||||
18: {"labels": "File", "name": "engineering101.txt", "permanent_id": 18},
|
||||
19: {"labels": "File", "name": "working_with_github.txt", "permanent_id": 19},
|
||||
20: {"labels": "File", "name": "working_with_notion.txt", "permanent_id": 20},
|
||||
21: {"labels": "Team", "name": "Marketing", "permanent_id": 21},
|
||||
22: {"labels": "Person", "name": "Julie", "permanent_id": 22},
|
||||
23: {"labels": "Account", "name": "Facebook", "permanent_id": 23},
|
||||
24: {"labels": "Account", "name": "LinkedIn", "permanent_id": 24},
|
||||
25: {"labels": "Account", "name": "HackerNews", "permanent_id": 25},
|
||||
26: {"labels": "File", "name": "welcome_to_marketing.txt", "permanent_id": 26},
|
||||
}
|
||||
EDGE_INFO = {
|
||||
(0, 1, 0): {"type": "IS_PART_OF", "permanent_id": 0},
|
||||
(0, 5, 1): {"type": "IS_PART_OF", "permanent_id": 1},
|
||||
(0, 9, 2): {"type": "HAS_ACCESS_TO", "permanent_id": 2},
|
||||
(0, 14, 3): {"type": "IS_PART_OF", "permanent_id": 3},
|
||||
(1, 2, 4): {"type": "HAS_ACCESS_TO", "permanent_id": 4},
|
||||
(1, 3, 5): {"type": "HAS_ACCESS_TO", "permanent_id": 5},
|
||||
(1, 4, 6): {"type": "HAS_ACCESS_TO", "permanent_id": 6},
|
||||
(1, 6, 7): {"type": "HAS_ACCESS_TO", "permanent_id": 7},
|
||||
(1, 11, 8): {"type": "HAS_ACCESS_TO", "permanent_id": 8},
|
||||
(5, 1, 9): {"type": "HAS_TEAM", "permanent_id": 9},
|
||||
(5, 1, 37): {"type": "HAS_TEAM_2", "importance": "HIGH", "permanent_id": 37},
|
||||
(5, 14, 11): {"type": "HAS_TEAM", "permanent_id": 11},
|
||||
(5, 21, 10): {"type": "HAS_TEAM", "permanent_id": 10},
|
||||
(6, 7, 12): {"type": "IS_STORED_IN", "permanent_id": 12},
|
||||
(6, 8, 13): {"type": "IS_STORED_IN", "permanent_id": 13},
|
||||
(9, 12, 14): {"type": "CREATED_BY", "permanent_id": 14},
|
||||
(10, 1, 15): {"type": "IS_PART_OF", "permanent_id": 15},
|
||||
(10, 5, 16): {"type": "IS_PART_OF", "permanent_id": 16},
|
||||
(10, 9, 17): {"type": "HAS_ACCESS_TO", "permanent_id": 17},
|
||||
(11, 7, 18): {"type": "IS_STORED_IN", "permanent_id": 18},
|
||||
(11, 18, 19): {"type": "HAS_ACCESS_TO", "permanent_id": 19},
|
||||
(11, 19, 20): {"type": "HAS_ACCESS_TO", "permanent_id": 20},
|
||||
(11, 20, 21): {"type": "HAS_ACCESS_TO", "permanent_id": 21},
|
||||
(12, 14, 22): {"type": "IS_PART_OF", "permanent_id": 22},
|
||||
(13, 7, 26): {"type": "IS_STORED_IN", "permanent_id": 26},
|
||||
(13, 15, 23): {"type": "HAS_ACCESS_TO", "permanent_id": 23},
|
||||
(13, 16, 24): {"type": "HAS_ACCESS_TO", "permanent_id": 24},
|
||||
(13, 17, 25): {"type": "HAS_ACCESS_TO", "permanent_id": 25},
|
||||
(14, 13, 27): {"type": "HAS_ACCESS_TO", "permanent_id": 27},
|
||||
(21, 23, 28): {"type": "HAS_ACCESS_TO", "permanent_id": 28},
|
||||
(21, 24, 29): {"type": "HAS_ACCESS_TO", "permanent_id": 29},
|
||||
(21, 25, 30): {"type": "HAS_ACCESS_TO", "permanent_id": 30},
|
||||
(21, 26, 31): {"type": "HAS_ACCESS_TO", "permanent_id": 31},
|
||||
(22, 5, 33): {"type": "IS_PART_OF", "permanent_id": 33},
|
||||
(22, 9, 34): {"type": "HAS_ACCESS_TO", "permanent_id": 34},
|
||||
(22, 21, 32): {"type": "IS_PART_OF", "permanent_id": 32},
|
||||
(26, 7, 35): {"type": "IS_STORED_IN", "permanent_id": 35},
|
||||
(26, 8, 36): {"type": "IS_STORED_IN", "permanent_id": 36},
|
||||
}
|
||||
|
||||
example_graph = nx.MultiDiGraph(GRAPH_DATA)
|
||||
nx.set_node_attributes(example_graph, NODE_INFO)
|
||||
nx.set_edge_attributes(example_graph, EDGE_INFO)
|
||||
|
||||
if not is_write:
|
||||
example_graph = nx.freeze(example_graph)
|
||||
|
||||
return mgp_mock.ProcCtx(_mgp_mock.Graph(example_graph))
|
||||
|
||||
|
||||
def get_vertex(ctx, permanent_id: int) -> mgp.Vertex:
|
||||
for vertex in ctx.graph.vertices:
|
||||
if vertex.properties["permanent_id"] == permanent_id:
|
||||
return vertex
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_edge(ctx: mgp.ProcCtx, permanent_id: int) -> mgp.Edge:
|
||||
for vertex in ctx.graph.vertices:
|
||||
for edge in vertex.out_edges:
|
||||
if edge.properties["permanent_id"] == permanent_id:
|
||||
return edge
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_mock_edge(ctx: mgp_mock.ProcCtx, id: int) -> mgp_mock.Edge:
|
||||
for vertex in ctx.graph.vertices:
|
||||
for edge in vertex.out_edges:
|
||||
if edge.id == id:
|
||||
return edge
|
||||
|
||||
return None
|
||||
@@ -1,101 +0,0 @@
|
||||
import typing
|
||||
|
||||
import mgp
|
||||
import mgp_mock
|
||||
import test_utils
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def compare_apis(ctx: mgp.ProcCtx) -> mgp.Record(results_dict=mgp.Map):
|
||||
mock_ctx = test_utils.get_mock_proc_ctx(is_write=False)
|
||||
results = dict()
|
||||
|
||||
ID = 1
|
||||
|
||||
target_vertex = test_utils.get_vertex(ctx, permanent_id=ID)
|
||||
target_mock_vertex = mock_ctx.graph.get_vertex_by_id(ID)
|
||||
|
||||
results["is_valid"] = test_utils.all_equal(
|
||||
target_vertex.is_valid(),
|
||||
target_mock_vertex.is_valid(),
|
||||
True,
|
||||
)
|
||||
|
||||
results["underlying_graph_is_mutable"] = test_utils.all_equal(
|
||||
target_vertex.underlying_graph_is_mutable(),
|
||||
target_mock_vertex.underlying_graph_is_mutable(),
|
||||
False,
|
||||
)
|
||||
|
||||
results["id"] = test_utils.all_equal(
|
||||
isinstance(target_vertex.id, int),
|
||||
isinstance(target_mock_vertex.id, int),
|
||||
True,
|
||||
)
|
||||
|
||||
results["labels"] = test_utils.all_equal(
|
||||
isinstance(target_vertex.labels, typing.Tuple),
|
||||
isinstance(target_mock_vertex.labels, typing.Tuple),
|
||||
True,
|
||||
) and test_utils.all_equal(
|
||||
{label.name for label in target_vertex.labels},
|
||||
{mock_label.name for mock_label in target_mock_vertex.labels},
|
||||
{"Team"},
|
||||
)
|
||||
|
||||
results["properties"] = test_utils.all_equal(
|
||||
isinstance(target_vertex.properties, mgp.Properties),
|
||||
isinstance(target_mock_vertex.properties, mgp_mock.Properties),
|
||||
True,
|
||||
) and test_utils.all_equal(
|
||||
{prop for prop in target_vertex.properties},
|
||||
{mock_prop for mock_prop in target_mock_vertex.properties},
|
||||
{"name", "permanent_id"},
|
||||
)
|
||||
|
||||
results["in_edges"] = test_utils.all_equal(
|
||||
all(isinstance(edge, mgp.Edge) for edge in target_vertex.in_edges),
|
||||
all(isinstance(edge, mgp_mock.Edge) for edge in target_mock_vertex.in_edges),
|
||||
True,
|
||||
) and test_utils.all_equal(
|
||||
{edge.properties["permanent_id"] for edge in target_vertex.in_edges},
|
||||
{edge.properties["permanent_id"] for edge in target_mock_vertex.in_edges},
|
||||
{0, 9, 15, 37},
|
||||
)
|
||||
|
||||
results["out_edges"] = test_utils.all_equal(
|
||||
all(isinstance(edge, mgp.Edge) for edge in target_vertex.out_edges),
|
||||
all(isinstance(edge, mgp_mock.Edge) for edge in target_mock_vertex.out_edges),
|
||||
True,
|
||||
) and test_utils.all_equal(
|
||||
{edge.properties["permanent_id"] for edge in target_vertex.out_edges},
|
||||
{edge.properties["permanent_id"] for edge in target_mock_vertex.out_edges},
|
||||
{4, 5, 6, 7, 8},
|
||||
)
|
||||
|
||||
ID_2 = 2
|
||||
|
||||
target_vertex_2 = test_utils.get_vertex(ctx, permanent_id=ID_2)
|
||||
target_mock_vertex_2 = mock_ctx.graph.get_vertex_by_id(ID_2)
|
||||
|
||||
results["__eq__"] = test_utils.all_equal(
|
||||
target_vertex == target_vertex,
|
||||
target_mock_vertex == target_mock_vertex,
|
||||
True,
|
||||
) and test_utils.all_equal(
|
||||
target_vertex == target_vertex_2,
|
||||
target_mock_vertex == target_mock_vertex_2,
|
||||
False,
|
||||
)
|
||||
|
||||
results["__ne__"] = test_utils.all_equal(
|
||||
target_vertex != target_vertex_2,
|
||||
target_mock_vertex != target_mock_vertex_2,
|
||||
True,
|
||||
) and test_utils.all_equal(
|
||||
target_vertex != target_vertex,
|
||||
target_mock_vertex != target_mock_vertex,
|
||||
False,
|
||||
)
|
||||
|
||||
return mgp.Record(results_dict=results)
|
||||
@@ -1,32 +0,0 @@
|
||||
import mgp
|
||||
import mgp_mock
|
||||
import test_utils
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def compare_apis(ctx: mgp.ProcCtx) -> mgp.Record(results_dict=mgp.Map):
|
||||
mock_ctx = test_utils.get_mock_proc_ctx(is_write=False)
|
||||
results = dict()
|
||||
|
||||
vertices = ctx.graph.vertices
|
||||
mock_vertices = mock_ctx.graph.vertices
|
||||
|
||||
results["is_valid"] = test_utils.all_equal(
|
||||
vertices.is_valid(),
|
||||
mock_vertices.is_valid(),
|
||||
True,
|
||||
)
|
||||
|
||||
results["__iter__"] = test_utils.all_equal(
|
||||
all(isinstance(vertex, mgp.Vertex) for vertex in vertices),
|
||||
all(isinstance(vertex, mgp_mock.Vertex) for vertex in mock_vertices),
|
||||
True,
|
||||
)
|
||||
|
||||
results["__len__"] = test_utils.all_equal(
|
||||
len(vertices),
|
||||
len(mock_vertices),
|
||||
27,
|
||||
)
|
||||
|
||||
return mgp.Record(results_dict=results)
|
||||
@@ -1,195 +0,0 @@
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
from common import connect, execute_and_fetch_results_dict
|
||||
|
||||
|
||||
def test_label():
|
||||
expected_results = {
|
||||
"name": True,
|
||||
"__eq__": True,
|
||||
"__ne__": True,
|
||||
}
|
||||
|
||||
cursor = connect().cursor()
|
||||
results = execute_and_fetch_results_dict(
|
||||
cursor, "CALL label.compare_apis() YIELD results_dict RETURN results_dict;"
|
||||
)
|
||||
|
||||
assert results == expected_results
|
||||
|
||||
|
||||
def test_properties_on_vertex():
|
||||
expected_results = {
|
||||
"get": True,
|
||||
"get[default]": True,
|
||||
"set": True,
|
||||
"items": True,
|
||||
"keys": True,
|
||||
"values": True,
|
||||
"__len__": True,
|
||||
"__iter__": True,
|
||||
"__getitem__": True,
|
||||
"__setitem__": True,
|
||||
"__contains__": True,
|
||||
}
|
||||
|
||||
cursor = connect().cursor()
|
||||
results = execute_and_fetch_results_dict(
|
||||
cursor, "CALL properties.compare_apis_on_vertex() YIELD results_dict RETURN results_dict;"
|
||||
)
|
||||
|
||||
assert results == expected_results
|
||||
|
||||
|
||||
def test_properties_on_edge():
|
||||
expected_results = {
|
||||
"get": True,
|
||||
"get[default]": True,
|
||||
"set": True,
|
||||
"items": True,
|
||||
"keys": True,
|
||||
"values": True,
|
||||
"__len__": True,
|
||||
"__iter__": True,
|
||||
"__getitem__": True,
|
||||
"__setitem__": True,
|
||||
"__contains__": True,
|
||||
}
|
||||
|
||||
cursor = connect().cursor()
|
||||
results = execute_and_fetch_results_dict(
|
||||
cursor, "CALL properties.compare_apis_on_edge() YIELD results_dict RETURN results_dict;"
|
||||
)
|
||||
|
||||
assert results == expected_results
|
||||
|
||||
|
||||
def test_edge_type():
|
||||
expected_results = {
|
||||
"name": True,
|
||||
"__eq__": True,
|
||||
"__ne__": True,
|
||||
}
|
||||
|
||||
cursor = connect().cursor()
|
||||
results = execute_and_fetch_results_dict(
|
||||
cursor, "CALL edge_type.compare_apis() YIELD results_dict RETURN results_dict;"
|
||||
)
|
||||
|
||||
assert results == expected_results
|
||||
|
||||
|
||||
def test_edge():
|
||||
expected_results = {
|
||||
"is_valid": True,
|
||||
"underlying_graph_is_mutable": True,
|
||||
"id": True,
|
||||
"type": True,
|
||||
"from_vertex": True,
|
||||
"to_vertex": True,
|
||||
"properties": True,
|
||||
"__eq__": True,
|
||||
"__ne__": True,
|
||||
}
|
||||
|
||||
cursor = connect().cursor()
|
||||
results = execute_and_fetch_results_dict(cursor, "CALL edge.compare_apis() YIELD results_dict RETURN results_dict;")
|
||||
|
||||
assert results == expected_results
|
||||
|
||||
|
||||
def test_vertex():
|
||||
expected_results = {
|
||||
"is_valid": True,
|
||||
"underlying_graph_is_mutable": True,
|
||||
"id": True,
|
||||
"labels": True,
|
||||
"properties": True,
|
||||
"in_edges": True,
|
||||
"out_edges": True,
|
||||
"__eq__": True,
|
||||
"__ne__": True,
|
||||
}
|
||||
|
||||
cursor = connect().cursor()
|
||||
results = execute_and_fetch_results_dict(
|
||||
cursor, "CALL vertex.compare_apis() YIELD results_dict RETURN results_dict;"
|
||||
)
|
||||
|
||||
assert results == expected_results
|
||||
|
||||
|
||||
def test_path():
|
||||
expected_results = {
|
||||
"__copy__": True,
|
||||
"is_valid": True,
|
||||
"expand": True,
|
||||
"vertices": True,
|
||||
"edges": True,
|
||||
}
|
||||
|
||||
cursor = connect().cursor()
|
||||
results = execute_and_fetch_results_dict(cursor, "CALL path.compare_apis() YIELD results_dict RETURN results_dict;")
|
||||
|
||||
assert results == expected_results
|
||||
|
||||
|
||||
def test_record():
|
||||
expected_results = {
|
||||
"fields": True,
|
||||
}
|
||||
|
||||
cursor = connect().cursor()
|
||||
results = execute_and_fetch_results_dict(
|
||||
cursor, "CALL record.compare_apis() YIELD results_dict RETURN results_dict;"
|
||||
)
|
||||
|
||||
assert results == expected_results
|
||||
|
||||
|
||||
def test_vertices():
|
||||
expected_results = {
|
||||
"is_valid": True,
|
||||
"__iter__": True,
|
||||
"__len__": True,
|
||||
}
|
||||
|
||||
cursor = connect().cursor()
|
||||
results = execute_and_fetch_results_dict(
|
||||
cursor, "CALL vertices.compare_apis() YIELD results_dict RETURN results_dict;"
|
||||
)
|
||||
|
||||
assert results == expected_results
|
||||
|
||||
|
||||
def test_graph():
|
||||
expected_results = {
|
||||
"create_edge": True,
|
||||
"create_vertex": True,
|
||||
"delete_edge": True,
|
||||
"delete_vertex": True,
|
||||
"detach_delete_vertex": True,
|
||||
"edge_id_assignment": True,
|
||||
"get_vertex_by_id": True,
|
||||
"is_mutable": True,
|
||||
"is_not_mutable": True,
|
||||
"is_valid": True,
|
||||
"vertices": True,
|
||||
}
|
||||
|
||||
cursor = connect().cursor()
|
||||
results = execute_and_fetch_results_dict(
|
||||
cursor, "CALL graph.compare_apis() YIELD results_dict RETURN results_dict;"
|
||||
)
|
||||
results.update(
|
||||
execute_and_fetch_results_dict(
|
||||
cursor, "CALL graph.test_read_proc_mutability() YIELD results_dict RETURN results_dict;"
|
||||
)
|
||||
)
|
||||
|
||||
assert results == expected_results
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-rA"]))
|
||||
@@ -1,83 +0,0 @@
|
||||
compare_mock: &compare_mock
|
||||
cluster:
|
||||
main:
|
||||
args: ["--bolt-port", "7687", "--log-level=TRACE", "--also-log-to-stderr"]
|
||||
log_file: "test-compare-mock-e2e.log"
|
||||
setup_queries:
|
||||
- "CREATE INDEX ON :__mg_vertex__(__mg_id__);"
|
||||
- "CREATE (:__mg_vertex__:`Person` {__mg_id__: 0, `name`: 'Peter', `surname`: 'Yang'});"
|
||||
- "CREATE (:__mg_vertex__:`Team` {__mg_id__: 1, `name`: 'Engineering'});"
|
||||
- "CREATE (:__mg_vertex__:`Repository` {__mg_id__: 2, `name`: 'Memgraph'});"
|
||||
- "CREATE (:__mg_vertex__:`Repository` {__mg_id__: 3, `name`: 'MAGE'});"
|
||||
- "CREATE (:__mg_vertex__:`Repository` {__mg_id__: 4, `name`: 'GQLAlchemy'});"
|
||||
- "CREATE (:__mg_vertex__:`Company`:`Startup` {__mg_id__: 5, `name`: 'Memgraph'});"
|
||||
- "CREATE (:__mg_vertex__:`File` {__mg_id__: 6, `name`: 'welcome_to_engineering.txt'});"
|
||||
- "CREATE (:__mg_vertex__:`Storage` {__mg_id__: 7, `name`: 'Google Drive'});"
|
||||
- "CREATE (:__mg_vertex__:`Storage` {__mg_id__: 8, `name`: 'Notion'});"
|
||||
- "CREATE (:__mg_vertex__:`File` {__mg_id__: 9, `name`: 'welcome_to_memgraph.txt'});"
|
||||
- "CREATE (:__mg_vertex__:`Person` {__mg_id__: 10, `name`: 'Carl'});"
|
||||
- "CREATE (:__mg_vertex__:`Folder` {__mg_id__: 11, `name`: 'engineering_folder'});"
|
||||
- "CREATE (:__mg_vertex__:`Person` {__mg_id__: 12, `name`: 'Anna'});"
|
||||
- "CREATE (:__mg_vertex__:`Folder` {__mg_id__: 13, `name`: 'operations_folder'});"
|
||||
- "CREATE (:__mg_vertex__:`Team` {__mg_id__: 14, `name`: 'Operations'});"
|
||||
- "CREATE (:__mg_vertex__:`File` {__mg_id__: 15, `name`: 'operations101.txt'});"
|
||||
- "CREATE (:__mg_vertex__:`File` {__mg_id__: 16, `name`: 'expenses2022.csv'});"
|
||||
- "CREATE (:__mg_vertex__:`File` {__mg_id__: 17, `name`: 'salaries2022.csv'});"
|
||||
- "CREATE (:__mg_vertex__:`File` {__mg_id__: 18, `name`: 'engineering101.txt'});"
|
||||
- "CREATE (:__mg_vertex__:`File` {__mg_id__: 19, `name`: 'working_with_github.txt'});"
|
||||
- "CREATE (:__mg_vertex__:`File` {__mg_id__: 20, `name`: 'working_with_notion.txt'});"
|
||||
- "CREATE (:__mg_vertex__:`Team` {__mg_id__: 21, `name`: 'Marketing'});"
|
||||
- "CREATE (:__mg_vertex__:`Person` {__mg_id__: 22, `name`: 'Julie'});"
|
||||
- "CREATE (:__mg_vertex__:`Account` {__mg_id__: 23, `name`: 'Facebook'});"
|
||||
- "CREATE (:__mg_vertex__:`Account` {__mg_id__: 24, `name`: 'LinkedIn'});"
|
||||
- "CREATE (:__mg_vertex__:`Account` {__mg_id__: 25, `name`: 'HackerNews'});"
|
||||
- "CREATE (:__mg_vertex__:`File` {__mg_id__: 26, `name`: 'welcome_to_marketing.txt'});"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND v.__mg_id__ = 1 CREATE (u)-[:`IS_PART_OF` {`permanent_id`: 0}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND v.__mg_id__ = 5 CREATE (u)-[:`IS_PART_OF` {`permanent_id`: 1}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND v.__mg_id__ = 9 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 2}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 0 AND v.__mg_id__ = 14 CREATE (u)-[:`IS_PART_OF` {`permanent_id`: 3}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 2 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 4}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 3 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 5}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 4 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 6}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 6 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 7}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 1 AND v.__mg_id__ = 11 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 8}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 5 AND v.__mg_id__ = 1 CREATE (u)-[:`HAS_TEAM` {`permanent_id`: 9}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 5 AND v.__mg_id__ = 21 CREATE (u)-[:`HAS_TEAM` {`permanent_id`: 10}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 5 AND v.__mg_id__ = 14 CREATE (u)-[:`HAS_TEAM` {`permanent_id`: 11}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 6 AND v.__mg_id__ = 7 CREATE (u)-[:`IS_STORED_IN` {`permanent_id`: 12}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 6 AND v.__mg_id__ = 8 CREATE (u)-[:`IS_STORED_IN` {`permanent_id`: 13}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 9 AND v.__mg_id__ = 12 CREATE (u)-[:`CREATED_BY` {`permanent_id`: 14}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 10 AND v.__mg_id__ = 1 CREATE (u)-[:`IS_PART_OF` {`permanent_id`: 15}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 10 AND v.__mg_id__ = 5 CREATE (u)-[:`IS_PART_OF` {`permanent_id`: 16}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 10 AND v.__mg_id__ = 9 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 17}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 11 AND v.__mg_id__ = 7 CREATE(u)-[:`IS_STORED_IN` {`permanent_id`: 18}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 11 AND v.__mg_id__ = 18 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 19}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 11 AND v.__mg_id__ = 19 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 20}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 11 AND v.__mg_id__ = 20 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 21}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 12 AND v.__mg_id__ = 14 CREATE (u)-[:`IS_PART_OF` {`permanent_id`: 22}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 13 AND v.__mg_id__ = 15 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 23}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 13 AND v.__mg_id__ = 16 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 24}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 13 AND v.__mg_id__ = 17 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 25}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 13 AND v.__mg_id__ = 7 CREATE (u)-[:`IS_STORED_IN` {`permanent_id`: 26}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 14 AND v.__mg_id__ = 13 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 27}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 21 AND v.__mg_id__ = 23 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 28}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 21 AND v.__mg_id__ = 24 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 29}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 21 AND v.__mg_id__ = 25 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 30}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 21 AND v.__mg_id__ = 26 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 31}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 22 AND v.__mg_id__ = 21 CREATE (u)-[:`IS_PART_OF` {`permanent_id`: 32}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 22 AND v.__mg_id__ = 5 CREATE (u)-[:`IS_PART_OF` {`permanent_id`: 33}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 22 AND v.__mg_id__ = 9 CREATE (u)-[:`HAS_ACCESS_TO` {`permanent_id`: 34}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 26 AND v.__mg_id__ = 7 CREATE (u)-[:`IS_STORED_IN` {`permanent_id`: 35}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 26 AND v.__mg_id__ = 8 CREATE (u)-[:`IS_STORED_IN` {`permanent_id`: 36}]->(v);"
|
||||
- "MATCH (u:__mg_vertex__), (v:__mg_vertex__) WHERE u.__mg_id__ = 5 AND v.__mg_id__ = 1 CREATE (u)-[:`HAS_TEAM_2` {`importance`: 'HIGH', `permanent_id`: 37}]->(v);"
|
||||
- "DROP INDEX ON :__mg_vertex__(__mg_id__);"
|
||||
- "MATCH (u) SET u.permanent_id = u.__mg_id__;"
|
||||
- "MATCH (u) REMOVE u:__mg_vertex__, u.__mg_id__;"
|
||||
validation_queries: []
|
||||
|
||||
workloads:
|
||||
- name: "test-compare-mock" # should be the same as the python file
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
proc: "tests/e2e/mock_api/procedures/"
|
||||
args: ["mock_api/test_compare_mock.py"]
|
||||
<<: *compare_mock
|
||||
@@ -20,10 +20,3 @@ add_subdirectory(procedures)
|
||||
add_dependencies(memgraph__e2e__triggers__on_create memgraph__e2e__triggers__write.py)
|
||||
add_dependencies(memgraph__e2e__triggers__on_update memgraph__e2e__triggers__write.py)
|
||||
add_dependencies(memgraph__e2e__triggers__on_delete memgraph__e2e__triggers__write.py)
|
||||
|
||||
function(copy_triggers_e2e_python_files FILE_NAME)
|
||||
copy_e2e_python_files(triggers ${FILE_NAME})
|
||||
endfunction()
|
||||
|
||||
copy_triggers_e2e_python_files(common.py)
|
||||
copy_triggers_e2e_python_files(triggers_properties_false.py)
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# Copyright 2022 Memgraph Ltd.
|
||||
#
|
||||
# Use of this software is governed by the Business Source License
|
||||
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
# License, and you may not use this file except in compliance with the Business Source License.
|
||||
#
|
||||
# As of the Change Date specified in that file, in accordance with
|
||||
# the Business Source License, use of this software will be governed
|
||||
# by the Apache License, Version 2.0, included in the file
|
||||
# licenses/APL.txt.
|
||||
|
||||
import typing
|
||||
|
||||
import mgclient
|
||||
import pytest
|
||||
|
||||
|
||||
def execute_and_fetch_all(cursor: mgclient.Cursor, query: str, params: dict = {}) -> typing.List[tuple]:
|
||||
cursor.execute(query, params)
|
||||
return cursor.fetchall()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def connect(**kwargs) -> mgclient.Connection:
|
||||
connection = mgclient.connect(host="localhost", port=7687, **kwargs)
|
||||
connection.autocommit = True
|
||||
triggers_list = execute_and_fetch_all(connection.cursor(), "SHOW TRIGGERS;")
|
||||
for trigger in triggers_list:
|
||||
execute_and_fetch_all(connection.cursor(), f"DROP TRIGGER {trigger[0]}")
|
||||
execute_and_fetch_all(connection.cursor(), "MATCH (n) DETACH DELETE n")
|
||||
yield connection
|
||||
for trigger in triggers_list:
|
||||
execute_and_fetch_all(connection.cursor(), f"DROP TRIGGER {trigger[0]}")
|
||||
execute_and_fetch_all(connection.cursor(), "MATCH (n) DETACH DELETE n")
|
||||
@@ -1,170 +0,0 @@
|
||||
# Copyright 2022 Memgraph Ltd.
|
||||
#
|
||||
# Use of this software is governed by the Business Source License
|
||||
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
# License, and you may not use this file except in compliance with the Business Source License.
|
||||
#
|
||||
# As of the Change Date specified in that file, in accordance with
|
||||
# the Business Source License, use of this software will be governed
|
||||
# by the Apache License, Version 2.0, included in the file
|
||||
# licenses/APL.txt.
|
||||
|
||||
import sys
|
||||
|
||||
import mgclient
|
||||
import pytest
|
||||
from common import connect, execute_and_fetch_all
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ba_commit", ["BEFORE COMMIT", "AFTER COMMIT"])
|
||||
def test_create_on_create(ba_commit, connect):
|
||||
"""
|
||||
Args:
|
||||
ba_commit (str): BEFORE OR AFTER commit
|
||||
"""
|
||||
cursor = connect.cursor()
|
||||
QUERY_TRIGGER_CREATE = f"""
|
||||
CREATE TRIGGER CreateTriggerEdgesCount
|
||||
ON --> CREATE
|
||||
{ba_commit}
|
||||
EXECUTE
|
||||
CREATE (n:CreatedEdge {{count: size(createdEdges)}})
|
||||
"""
|
||||
execute_and_fetch_all(cursor, QUERY_TRIGGER_CREATE)
|
||||
execute_and_fetch_all(cursor, "CREATE (n:Node {id: 1})")
|
||||
execute_and_fetch_all(cursor, "CREATE (n:Node {id: 2})")
|
||||
res = execute_and_fetch_all(cursor, "MATCH (n:Node) RETURN n")
|
||||
assert len(res) == 2
|
||||
res2 = execute_and_fetch_all(cursor, "MATCH (n:CreatedEdge) RETURN n")
|
||||
assert len(res2) == 0
|
||||
QUERY_CREATE_EDGE = """
|
||||
MATCH (n:Node {id: 1}), (m:Node {id: 2})
|
||||
CREATE (n)-[r:TYPE]->(m);
|
||||
"""
|
||||
execute_and_fetch_all(cursor, QUERY_CREATE_EDGE)
|
||||
# See if trigger was triggered
|
||||
nodes = execute_and_fetch_all(cursor, "MATCH (n:Node) RETURN n")
|
||||
assert len(nodes) == 2
|
||||
created_edges = execute_and_fetch_all(cursor, "MATCH (n:CreatedEdge) RETURN n")
|
||||
assert len(created_edges) == 1
|
||||
# execute_and_fetch_all(cursor, "DROP TRIGGER CreateTriggerEdgesCount")
|
||||
# execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ba_commit", ["AFTER COMMIT", "BEFORE COMMIT"])
|
||||
def test_create_on_delete(ba_commit, connect):
|
||||
"""
|
||||
Args:
|
||||
ba_commit (str): BEFORE OR AFTER commit
|
||||
"""
|
||||
cursor = connect.cursor()
|
||||
QUERY_TRIGGER_CREATE = f"""
|
||||
CREATE TRIGGER DeleteTriggerEdgesCount
|
||||
ON --> DELETE
|
||||
{ba_commit}
|
||||
EXECUTE
|
||||
CREATE (n:DeletedEdge {{count: size(deletedEdges)}})
|
||||
"""
|
||||
# Setup queries
|
||||
execute_and_fetch_all(cursor, QUERY_TRIGGER_CREATE)
|
||||
execute_and_fetch_all(cursor, "CREATE (n:Node {id: 1})")
|
||||
execute_and_fetch_all(cursor, "CREATE (n:Node {id: 2})")
|
||||
execute_and_fetch_all(cursor, "CREATE (n:Node {id: 3})")
|
||||
execute_and_fetch_all(cursor, "CREATE (n:Node {id: 4})")
|
||||
res = execute_and_fetch_all(cursor, "MATCH (n:Node) RETURN n")
|
||||
assert len(res) == 4
|
||||
res2 = execute_and_fetch_all(cursor, "MATCH (n:DeletedEdge) RETURN n")
|
||||
assert len(res2) == 0
|
||||
# create an edge that will be deleted
|
||||
QUERY_CREATE_EDGE = """
|
||||
MATCH (n:Node {id: 1}), (m:Node {id: 2})
|
||||
CREATE (n)-[r:TYPE]->(m);
|
||||
"""
|
||||
execute_and_fetch_all(cursor, QUERY_CREATE_EDGE)
|
||||
# create an edge that won't be deleted
|
||||
QUERY_CREATE_EDGE_NO_DELETE = """
|
||||
MATCH (n:Node {id: 3}), (m:Node {id: 4})
|
||||
CREATE (n)-[r:NO_DELETE_EDGE]->(m);
|
||||
"""
|
||||
execute_and_fetch_all(cursor, QUERY_CREATE_EDGE_NO_DELETE)
|
||||
# Delete only one type of the edger
|
||||
QUERY_DELETE_EDGE = """
|
||||
MATCH ()-[r:TYPE]->()
|
||||
DELETE r;
|
||||
"""
|
||||
execute_and_fetch_all(cursor, QUERY_DELETE_EDGE)
|
||||
# See if trigger was triggered
|
||||
nodes = execute_and_fetch_all(cursor, "MATCH (n:Node) RETURN n")
|
||||
assert len(nodes) == 4
|
||||
# Check how many edges got deleted
|
||||
deleted_edges = execute_and_fetch_all(cursor, "MATCH (n:DeletedEdge) RETURN n")
|
||||
assert len(deleted_edges) == 1
|
||||
# execute_and_fetch_all(cursor, "DROP TRIGGER DeleteTriggerEdgesCount")
|
||||
# execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n")``
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ba_commit", ["BEFORE COMMIT", "AFTER COMMIT"])
|
||||
def test_create_on_delete_explicit_transaction(ba_commit):
|
||||
"""
|
||||
Args:
|
||||
ba_commit (str): BEFORE OR AFTER commit
|
||||
"""
|
||||
connection_with_autocommit = mgclient.connect(host="localhost", port=7687)
|
||||
connection_with_autocommit.autocommit = True
|
||||
cursor_autocommit = connection_with_autocommit.cursor()
|
||||
QUERY_TRIGGER_CREATE = f"""
|
||||
CREATE TRIGGER DeleteTriggerEdgesCountExplicit
|
||||
ON --> DELETE
|
||||
{ba_commit}
|
||||
EXECUTE
|
||||
CREATE (n:DeletedEdge {{count: size(deletedEdges)}})
|
||||
"""
|
||||
# Setup queries
|
||||
execute_and_fetch_all(cursor_autocommit, QUERY_TRIGGER_CREATE)
|
||||
# Start explicit transaction on the execution of the first command
|
||||
connection_without_autocommit = mgclient.connect(host="localhost", port=7687)
|
||||
connection_without_autocommit.autocommit = False
|
||||
cursor = connection_without_autocommit.cursor()
|
||||
execute_and_fetch_all(cursor, "CREATE (n:Node {id: 1})")
|
||||
execute_and_fetch_all(cursor, "CREATE (n:Node {id: 2})")
|
||||
execute_and_fetch_all(cursor, "CREATE (n:Node {id: 3})")
|
||||
execute_and_fetch_all(cursor, "CREATE (n:Node {id: 4})")
|
||||
res = execute_and_fetch_all(cursor, "MATCH (n:Node) RETURN n")
|
||||
assert len(res) == 4
|
||||
res2 = execute_and_fetch_all(cursor, "MATCH (n:DeletedEdge) RETURN n;")
|
||||
assert len(res2) == 0
|
||||
QUERY_CREATE_EDGE = """
|
||||
MATCH (n:Node {id: 1}), (m:Node {id: 2})
|
||||
CREATE (n)-[r:TYPE]->(m);
|
||||
"""
|
||||
execute_and_fetch_all(cursor, QUERY_CREATE_EDGE)
|
||||
# create an edge that won't be deleted
|
||||
QUERY_CREATE_EDGE_NO_DELETE = """
|
||||
MATCH (n:Node {id: 3}), (m:Node {id: 4})
|
||||
CREATE (n)-[r:NO_DELETE_EDGE]->(m);
|
||||
"""
|
||||
execute_and_fetch_all(cursor, QUERY_CREATE_EDGE_NO_DELETE)
|
||||
# Delete only one type of the edger
|
||||
QUERY_DELETE_EDGE = """
|
||||
MATCH ()-[r:TYPE]->()
|
||||
DELETE r;
|
||||
"""
|
||||
execute_and_fetch_all(cursor, QUERY_DELETE_EDGE)
|
||||
connection_without_autocommit.commit() # finish explicit transaction
|
||||
nodes = execute_and_fetch_all(cursor, "MATCH (n:Node) RETURN n")
|
||||
assert len(nodes) == 4
|
||||
# Check how many edges got deleted
|
||||
deleted_nodes_edges = execute_and_fetch_all(cursor, "MATCH (n:DeletedEdge) RETURN n")
|
||||
assert len(deleted_nodes_edges) == 0
|
||||
# Delete with the original cursor because triggers aren't allowed in multi-transaction environment
|
||||
execute_and_fetch_all(cursor_autocommit, "DROP TRIGGER DeleteTriggerEdgesCountExplicit")
|
||||
execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n")
|
||||
connection_without_autocommit.commit() # finish explicit transaction
|
||||
nodes = execute_and_fetch_all(cursor_autocommit, "MATCH (n) RETURN n")
|
||||
assert len(nodes) == 0
|
||||
connection_with_autocommit.close()
|
||||
connection_without_autocommit.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-rA"]))
|
||||
@@ -2,14 +2,7 @@ bolt_port: &bolt_port "7687"
|
||||
template_cluster: &template_cluster
|
||||
cluster:
|
||||
main:
|
||||
args: ["--bolt-port", *bolt_port, "--log-level=TRACE", "--storage-properties-on-edges=True"]
|
||||
log_file: "triggers-e2e.log"
|
||||
setup_queries: []
|
||||
validation_queries: []
|
||||
storage_properties_edges_false: &storage_properties_edges_false
|
||||
cluster:
|
||||
main:
|
||||
args: ["--bolt-port", *bolt_port, "--log-level=TRACE", "--also-log-to-stderr", "--storage-properties-on-edges=False"]
|
||||
args: ["--bolt-port", *bolt_port, "--log-level=TRACE"]
|
||||
log_file: "triggers-e2e.log"
|
||||
setup_queries: []
|
||||
validation_queries: []
|
||||
@@ -25,7 +18,7 @@ workloads:
|
||||
args: ["--bolt-port", *bolt_port]
|
||||
proc: "tests/e2e/triggers/procedures/"
|
||||
<<: *template_cluster
|
||||
- name: "ON DELETE Triggers Storage Properties On Edges True"
|
||||
- name: "ON DELETE Triggers"
|
||||
binary: "tests/e2e/triggers/memgraph__e2e__triggers__on_delete"
|
||||
args: ["--bolt-port", *bolt_port]
|
||||
proc: "tests/e2e/triggers/procedures/"
|
||||
@@ -34,8 +27,5 @@ workloads:
|
||||
binary: "tests/e2e/triggers/memgraph__e2e__triggers__privileges"
|
||||
args: ["--bolt-port", *bolt_port]
|
||||
<<: *template_cluster
|
||||
- name: "ON DELETE Triggers Storage Properties On Edges False" # should be the same as the python file
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
proc: "tests/e2e/triggers/procedures/"
|
||||
args: ["triggers/triggers_properties_false.py"]
|
||||
<<: *storage_properties_edges_false
|
||||
|
||||
|
||||
|
||||
@@ -66,13 +66,29 @@ Feature: Foreach
|
||||
| 4 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Foreach shadowing in create
|
||||
Given an empty graph
|
||||
And having executed
|
||||
"""
|
||||
FOREACH (i IN [1] | FOREACH (j IN [3,4] | CREATE (i {prop: j})));
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n) RETURN n.prop
|
||||
"""
|
||||
Then the result should be:
|
||||
| n.prop |
|
||||
| 3 |
|
||||
| 4 |
|
||||
And no side effects
|
||||
|
||||
Scenario: Foreach set
|
||||
Given an empty graph
|
||||
And having executed
|
||||
"""
|
||||
CREATE (n1 { marked: false })-[:RELATES]->(n2 { marked: false })
|
||||
"""
|
||||
And having executed
|
||||
And having executed
|
||||
"""
|
||||
MATCH p=(n1)-[*]->(n2)
|
||||
FOREACH (n IN nodes(p) | SET n.marked = true)
|
||||
@@ -94,7 +110,7 @@ Feature: Foreach
|
||||
"""
|
||||
CREATE (n1 { marked: false })-[:RELATES]->(n2 { marked: false })
|
||||
"""
|
||||
And having executed
|
||||
And having executed
|
||||
"""
|
||||
MATCH p=(n1)-[*]->(n2)
|
||||
FOREACH (n IN nodes(p) | REMOVE n.marked)
|
||||
@@ -116,7 +132,7 @@ Feature: Foreach
|
||||
"""
|
||||
CREATE (n1 { marked: false })-[:RELATES]->(n2 { marked: false })
|
||||
"""
|
||||
And having executed
|
||||
And having executed
|
||||
"""
|
||||
MATCH p=(n1)-[*]->(n2)
|
||||
FOREACH (n IN nodes(p) | DETACH delete n)
|
||||
@@ -132,7 +148,7 @@ Feature: Foreach
|
||||
|
||||
Scenario: Foreach merge
|
||||
Given an empty graph
|
||||
And having executed
|
||||
And having executed
|
||||
"""
|
||||
FOREACH (i IN [1, 2, 3] | MERGE (n { age : i }))
|
||||
"""
|
||||
@@ -150,7 +166,7 @@ Feature: Foreach
|
||||
|
||||
Scenario: Foreach nested
|
||||
Given an empty graph
|
||||
And having executed
|
||||
And having executed
|
||||
"""
|
||||
FOREACH (i IN [1, 2, 3] | FOREACH( j IN [1] | CREATE (k { prop : j })))
|
||||
"""
|
||||
@@ -167,11 +183,11 @@ Feature: Foreach
|
||||
|
||||
Scenario: Foreach multiple update clauses
|
||||
Given an empty graph
|
||||
And having executed
|
||||
And having executed
|
||||
"""
|
||||
CREATE (n1 { marked1: false, marked2: false })-[:RELATES]->(n2 { marked1: false, marked2: false })
|
||||
"""
|
||||
And having executed
|
||||
And having executed
|
||||
"""
|
||||
MATCH p=(n1)-[*]->(n2)
|
||||
FOREACH (n IN nodes(p) | SET n.marked1 = true SET n.marked2 = true)
|
||||
@@ -189,11 +205,11 @@ Feature: Foreach
|
||||
|
||||
Scenario: Foreach multiple nested update clauses
|
||||
Given an empty graph
|
||||
And having executed
|
||||
And having executed
|
||||
"""
|
||||
CREATE (n1 { marked1: false, marked2: false })-[:RELATES]->(n2 { marked1: false, marked2: false })
|
||||
"""
|
||||
And having executed
|
||||
And having executed
|
||||
"""
|
||||
MATCH p=(n1)-[*]->(n2)
|
||||
FOREACH (n IN nodes(p) | FOREACH (j IN [1] | SET n.marked1 = true SET n.marked2 = true))
|
||||
@@ -211,7 +227,7 @@ Feature: Foreach
|
||||
|
||||
Scenario: Foreach match foreach return
|
||||
Given an empty graph
|
||||
And having executed
|
||||
And having executed
|
||||
"""
|
||||
CREATE (n {prop: [[], [1,2]]});
|
||||
"""
|
||||
@@ -226,7 +242,7 @@ Feature: Foreach
|
||||
|
||||
Scenario: Foreach on null value
|
||||
Given an empty graph
|
||||
And having executed
|
||||
And having executed
|
||||
"""
|
||||
CREATE (n);
|
||||
"""
|
||||
@@ -238,9 +254,9 @@ Feature: Foreach
|
||||
| |
|
||||
And no side effects
|
||||
|
||||
Scenario: Foreach nested merge
|
||||
Scenario: Foreach nested merge
|
||||
Given an empty graph
|
||||
And having executed
|
||||
And having executed
|
||||
"""
|
||||
FOREACH(i in [1, 2, 3] | foreach(j in [1] | MERGE (n { age : i })));
|
||||
"""
|
||||
|
||||
@@ -585,12 +585,12 @@ Feature: Functions
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH ()-[r]->() RETURN PROPERTIES(r) AS p ORDER BY p.prop;
|
||||
MATCH ()-[r]->() RETURN PROPERTIES(r) AS p
|
||||
"""
|
||||
Then the result should be:
|
||||
| p |
|
||||
| {} |
|
||||
| {b: true} |
|
||||
| {} |
|
||||
| {c: 123} |
|
||||
|
||||
Scenario: Properties test2:
|
||||
|
||||
@@ -15,26 +15,6 @@ Feature: All Shortest Path
|
||||
| '1' |
|
||||
| '3' |
|
||||
|
||||
Scenario: Test match allShortest upper bound 2
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
"""
|
||||
CREATE (a {a:'0'})-[:r {w: 2}]->(b {a:'1'})-[:r {w: 3}]->(c {a:'2'}),
|
||||
(a)-[:re {w: 2}]->(b),
|
||||
(b)-[:re {w:3}]->(c),
|
||||
({a: '4'})<-[:r {w: 1}]-(a),
|
||||
({a: '5'})<-[:r {w: 1}]-(a),
|
||||
(c)-[:r {w: 1}]->({a: '6'}),
|
||||
(c)-[:r {w: 1}]->({a: '7'})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH path=(n {a:'0'})-[r *allShortest ..2 (e, n | 1 ) w]->(m {a:'2'}) RETURN COUNT(path) AS c
|
||||
"""
|
||||
Then the result should be:
|
||||
| c |
|
||||
| 4 |
|
||||
|
||||
Scenario: Test match allShortest filtered
|
||||
Given an empty graph
|
||||
And having executed:
|
||||
|
||||
@@ -1,529 +0,0 @@
|
||||
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
|
||||
@@ -45,21 +45,6 @@ class BoltClient : public ::testing::Test {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExecuteAndCheckQid(const std::string &query, int qid, const std::string &message = "") {
|
||||
try {
|
||||
auto ret = client_.Execute(query, {});
|
||||
if (ret.metadata["qid"].ValueInt() != qid) {
|
||||
return false;
|
||||
}
|
||||
} catch (const ClientQueryException &e) {
|
||||
if (message != "") {
|
||||
EXPECT_EQ(e.what(), message);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int64_t GetCount() {
|
||||
auto ret = client_.Execute("match (n) return count(n)", {});
|
||||
EXPECT_EQ(ret.records.size(), 1);
|
||||
@@ -476,18 +461,6 @@ TEST_F(BoltClient, MixedCaseAndWhitespace) {
|
||||
EXPECT_FALSE(TransactionActive());
|
||||
}
|
||||
|
||||
TEST_F(BoltClient, TestQid) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
EXPECT_TRUE(Execute("match (n) return count(n)"));
|
||||
}
|
||||
EXPECT_TRUE(Execute("begin"));
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
EXPECT_TRUE(ExecuteAndCheckQid("match (n) return count(n)", i + 1));
|
||||
}
|
||||
EXPECT_TRUE(Execute("commit"));
|
||||
EXPECT_FALSE(TransactionActive());
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user