Compare commits
3 Commits
update-wor
...
release/2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac4c13bc23 | ||
|
|
64e3aff65b | ||
|
|
2462b0ba27 |
2
.github/workflows/daily_benchmark.yaml
vendored
2
.github/workflows/daily_benchmark.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
|
||||
63
.github/workflows/diff.yaml
vendored
63
.github/workflows/diff.yaml
vendored
@@ -14,7 +14,6 @@ on:
|
||||
- "**/*.md"
|
||||
- ".clang-format"
|
||||
- "CODEOWNERS"
|
||||
- licenses/**
|
||||
|
||||
jobs:
|
||||
community_build:
|
||||
@@ -27,7 +26,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -65,7 +64,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -76,7 +75,7 @@ jobs:
|
||||
- name: Fetch all history for all tags and branches
|
||||
run: git fetch
|
||||
|
||||
- name: Initialize deps
|
||||
- name: Build combined ASAN, UBSAN and coverage binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
@@ -84,32 +83,6 @@ jobs:
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
- name: Set base branch
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: |
|
||||
echo "BASE_BRANCH=origin/${{ github.base_ref }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set base branch # if we manually dispatch or push to master
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
run: |
|
||||
echo "BASE_BRANCH=origin/master" >> $GITHUB_ENV
|
||||
|
||||
- name: Python code analysis
|
||||
run: |
|
||||
CHANGED_FILES=$(git diff -U0 ${{ env.BASE_BRANCH }}... --name-only)
|
||||
for file in ${CHANGED_FILES}; do
|
||||
echo ${file}
|
||||
if [[ ${file} == *.py ]]; then
|
||||
python3 -m black --check --diff ${file}
|
||||
python3 -m isort --check-only --diff ${file}
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Build combined ASAN, UBSAN and coverage binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
cd build
|
||||
cmake -DTEST_COVERAGE=ON -DASAN=ON -DUBSAN=ON ..
|
||||
make -j$THREADS memgraph__unit
|
||||
@@ -137,11 +110,21 @@ jobs:
|
||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||
|
||||
- name: Save code coverage
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/generated/code_coverage.tar.gz
|
||||
|
||||
- name: Set base branch
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: |
|
||||
echo "BASE_BRANCH=origin/${{ github.base_ref }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set base branch # if we manually dispatch or push to master
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
run: |
|
||||
echo "BASE_BRANCH=origin/master" >> $GITHUB_ENV
|
||||
|
||||
- name: Run clang-tidy
|
||||
run: |
|
||||
source /opt/toolchain-v4/activate
|
||||
@@ -162,7 +145,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -223,7 +206,7 @@ jobs:
|
||||
./cppcheck_and_clang_format diff
|
||||
|
||||
- name: Save cppcheck and clang-format errors
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/cppcheck_and_clang_format.txt
|
||||
@@ -238,7 +221,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -263,7 +246,7 @@ jobs:
|
||||
./continuous_integration
|
||||
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
@@ -320,13 +303,13 @@ jobs:
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save enterprise DEB package
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Enterprise DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
|
||||
- name: Save test data
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
if: always()
|
||||
with:
|
||||
name: "Test data"
|
||||
@@ -345,7 +328,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -370,7 +353,7 @@ jobs:
|
||||
./run.sh test --binary ../../build/memgraph --run-args "test-all --node-configs resources/node-config.edn" --ignore-run-stdout-logs --ignore-run-stderr-logs
|
||||
|
||||
- name: Save Jepsen report
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: "Jepsen Report"
|
||||
@@ -386,7 +369,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
|
||||
2
.github/workflows/full_clang_tidy.yaml
vendored
2
.github/workflows/full_clang_tidy.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
|
||||
20
.github/workflows/package_all.yaml
vendored
20
.github/workflows/package_all.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
run: |
|
||||
./release/package/run.sh package centos-7
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: centos-7
|
||||
path: build/output/centos-7/memgraph*.rpm
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
run: |
|
||||
./release/package/run.sh package centos-9
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: centos-9
|
||||
path: build/output/centos-9/memgraph*.rpm
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
run: |
|
||||
./release/package/run.sh package debian-10
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: debian-10
|
||||
path: build/output/debian-10/memgraph*.deb
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
run: |
|
||||
./release/package/run.sh package debian-11
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: debian-11
|
||||
path: build/output/debian-11/memgraph*.deb
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
./run.sh package debian-11 --for-docker
|
||||
./run.sh docker
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: docker
|
||||
path: build/output/docker/memgraph*.tar.gz
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
run: |
|
||||
./release/package/run.sh package ubuntu-18.04
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ubuntu-1804
|
||||
path: build/output/ubuntu-18.04/memgraph*.deb
|
||||
@@ -121,7 +121,7 @@ jobs:
|
||||
run: |
|
||||
./release/package/run.sh package ubuntu-20.04
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ubuntu-2004
|
||||
path: build/output/ubuntu-20.04/memgraph*.deb
|
||||
@@ -138,7 +138,7 @@ jobs:
|
||||
run: |
|
||||
./release/package/run.sh package ubuntu-22.04
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ubuntu-2204
|
||||
path: build/output/ubuntu-22.04/memgraph*.deb
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
run: |
|
||||
./release/package/run.sh package debian-11 --for-platform
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: debian-11-platform
|
||||
path: build/output/debian-11/memgraph*.deb
|
||||
@@ -172,7 +172,7 @@ jobs:
|
||||
run: |
|
||||
./release/package/run.sh package debian-11-arm
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: debian-11-arm
|
||||
path: build/output/debian-11-arm/memgraph*.deb
|
||||
|
||||
16
.github/workflows/release_centos8.yaml
vendored
16
.github/workflows/release_centos8.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||
|
||||
- name: Save code coverage
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/generated/code_coverage.tar.gz
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -173,7 +173,7 @@ jobs:
|
||||
./cppcheck_and_clang_format diff
|
||||
|
||||
- name: Save cppcheck and clang-format errors
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/cppcheck_and_clang_format.txt
|
||||
@@ -189,7 +189,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -225,7 +225,7 @@ jobs:
|
||||
rpmlint memgraph*.rpm
|
||||
|
||||
- name: Save enterprise RPM package
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Enterprise RPM package"
|
||||
path: build/output/memgraph*.rpm
|
||||
@@ -262,7 +262,7 @@ jobs:
|
||||
./continuous_integration
|
||||
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
|
||||
20
.github/workflows/release_debian10.yaml
vendored
20
.github/workflows/release_debian10.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||
|
||||
- name: Save code coverage
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/generated/code_coverage.tar.gz
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -173,7 +173,7 @@ jobs:
|
||||
./cppcheck_and_clang_format diff
|
||||
|
||||
- name: Save cppcheck and clang-format errors
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/cppcheck_and_clang_format.txt
|
||||
@@ -189,7 +189,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -224,7 +224,7 @@ jobs:
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save enterprise DEB package
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Enterprise DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
@@ -261,7 +261,7 @@ jobs:
|
||||
./continuous_integration
|
||||
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
@@ -324,7 +324,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -349,7 +349,7 @@ jobs:
|
||||
./run.sh test --binary ../../build/memgraph --run-args "test-all --node-configs resources/node-config.edn" --ignore-run-stdout-logs --ignore-run-stderr-logs
|
||||
|
||||
- name: Save Jepsen report
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: "Jepsen Report"
|
||||
|
||||
8
.github/workflows/release_docker.yaml
vendored
8
.github/workflows/release_docker.yaml
vendored
@@ -19,17 +19,17 @@ jobs:
|
||||
DOCKER_REPOSITORY_NAME: memgraph
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
16
.github/workflows/release_ubuntu2004.yaml
vendored
16
.github/workflows/release_ubuntu2004.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||
|
||||
- name: Save code coverage
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/generated/code_coverage.tar.gz
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -173,7 +173,7 @@ jobs:
|
||||
./cppcheck_and_clang_format diff
|
||||
|
||||
- name: Save cppcheck and clang-format errors
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/cppcheck_and_clang_format.txt
|
||||
@@ -189,7 +189,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -224,7 +224,7 @@ jobs:
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save enterprise DEB package
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Enterprise DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
@@ -261,7 +261,7 @@ jobs:
|
||||
./continuous_integration
|
||||
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
|
||||
@@ -6,14 +6,18 @@ repos:
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.10.0
|
||||
rev: 22.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.10.1
|
||||
hooks:
|
||||
- id: isort
|
||||
name: isort (python)
|
||||
args: # arguments to configure black
|
||||
- --line-length=120
|
||||
- --include='\.pyi?$'
|
||||
# these folders wont be formatted by black
|
||||
- --exclude="""\.git |
|
||||
\.__pycache__|
|
||||
build|
|
||||
libs|
|
||||
.cache"""
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: v13.0.0
|
||||
hooks:
|
||||
|
||||
@@ -54,7 +54,7 @@ option(MG_ENTERPRISE "Build Memgraph Enterprise Edition" ON)
|
||||
# Set the current version here to override the automatic version detection. The
|
||||
# version must be specified as `X.Y.Z`. Primarily used when building new patch
|
||||
# versions.
|
||||
set(MEMGRAPH_OVERRIDE_VERSION "")
|
||||
set(MEMGRAPH_OVERRIDE_VERSION "2.4.2")
|
||||
|
||||
# Custom suffix that this version should have. The suffix can be any arbitrary
|
||||
# string. Primarily used when building a version for a specific customer.
|
||||
|
||||
21
README.md
21
README.md
@@ -37,10 +37,9 @@ Build modern, graph-based applications on top of your streaming data in minutes.
|
||||
|
||||
## :clipboard: Description
|
||||
|
||||
Memgraph is an open source graph database built for real-time streaming and
|
||||
compatible with Neo4j. Whether you're a developer or a data scientist with
|
||||
interconnected data, Memgraph will get you the immediate actionable insights
|
||||
fast.
|
||||
Memgraph is a streaming graph application platform that helps you wrangle your
|
||||
streaming data, build sophisticated models that you can query in real-time, and
|
||||
develop graph applications.
|
||||
|
||||
Memgraph directly connects to your streaming infrastructure. You can ingest data
|
||||
from sources like Kafka, SQL, or plain CSV files. Memgraph provides a standard
|
||||
@@ -52,9 +51,8 @@ natural and effective way to model many real-world problems without relying on
|
||||
complex SQL schemas.
|
||||
|
||||
Memgraph is implemented in C/C++ and leverages an in-memory first architecture
|
||||
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.
|
||||
to ensure that you’re getting the best possible performance consistently and
|
||||
without surprises. It’s also ACID-compliant and highly available.
|
||||
|
||||
## :video_game: Memgraph Playground
|
||||
|
||||
@@ -143,15 +141,6 @@ Memgraph Community is available under the [BSL
|
||||
license](./licenses/BSL.txt).</br> Memgraph Enterprise is available under the
|
||||
[MEL license](./licenses/MEL.txt).
|
||||
|
||||
## 🙋 Community
|
||||
|
||||
- :purple_heart: [**Discord**](https://discord.gg/memgraph)
|
||||
- :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"/>
|
||||
|
||||
@@ -5,10 +5,12 @@ import os
|
||||
import subprocess
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
CONFIG_FILE = os.path.join(SCRIPT_DIR, "flags.yaml")
|
||||
WIDTH = 80
|
||||
@@ -16,13 +18,14 @@ WIDTH = 80
|
||||
|
||||
def wrap_text(s, initial_indent="# "):
|
||||
return "\n#\n".join(
|
||||
map(lambda x: textwrap.fill(x, WIDTH, initial_indent=initial_indent, subsequent_indent="# "), s.split("\n"))
|
||||
)
|
||||
map(lambda x: textwrap.fill(x, WIDTH, initial_indent=initial_indent,
|
||||
subsequent_indent="# "), s.split("\n")))
|
||||
|
||||
|
||||
def extract_flags(binary_path):
|
||||
ret = {}
|
||||
data = subprocess.run([binary_path, "--help-xml"], stdout=subprocess.PIPE).stdout.decode("utf-8")
|
||||
data = subprocess.run([binary_path, "--help-xml"],
|
||||
stdout=subprocess.PIPE).stdout.decode("utf-8")
|
||||
root = ET.fromstring(data)
|
||||
for child in root:
|
||||
if child.tag == "usage" and child.text.lower().count("warning"):
|
||||
@@ -43,7 +46,8 @@ def apply_config_to_flags(config, flags):
|
||||
for modification in config["modifications"]:
|
||||
name = modification["name"]
|
||||
if name not in flags:
|
||||
print("WARNING: Flag '" + name + "' missing from binary!", file=sys.stderr)
|
||||
print("WARNING: Flag '" + name + "' missing from binary!",
|
||||
file=sys.stderr)
|
||||
continue
|
||||
flags[name]["default"] = modification["value"]
|
||||
flags[name]["override"] = modification["override"]
|
||||
@@ -71,9 +75,8 @@ def extract_sections(flags):
|
||||
else:
|
||||
sections.append((current_section, current_flags))
|
||||
sections.append(("other", other))
|
||||
assert set(sum(map(lambda x: x[1], sections), [])) == set(
|
||||
flags.keys()
|
||||
), "The section extraction algorithm lost some flags!"
|
||||
assert set(sum(map(lambda x: x[1], sections), [])) == set(flags.keys()), \
|
||||
"The section extraction algorithm lost some flags!"
|
||||
return sections
|
||||
|
||||
|
||||
@@ -86,7 +89,8 @@ def generate_config_file(sections, flags):
|
||||
helpstr = flag["meaning"] + " [" + flag["type"] + "]"
|
||||
ret += wrap_text(helpstr) + "\n"
|
||||
prefix = "# " if not flag["override"] else ""
|
||||
ret += prefix + "--" + flag["name"].replace("_", "-") + "=" + flag["default"] + "\n\n"
|
||||
ret += prefix + "--" + flag["name"].replace("_", "-") + \
|
||||
"=" + flag["default"] + "\n\n"
|
||||
ret += "\n"
|
||||
ret += wrap_text(config["footer"])
|
||||
return ret.strip() + "\n"
|
||||
@@ -94,9 +98,13 @@ def generate_config_file(sections, flags):
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("memgraph_binary", help="path to Memgraph binary")
|
||||
parser.add_argument("output_file", help="path where to store the generated Memgraph " "configuration file")
|
||||
parser.add_argument("--config-file", default=CONFIG_FILE, help="path to generator configuration file")
|
||||
parser.add_argument("memgraph_binary",
|
||||
help="path to Memgraph binary")
|
||||
parser.add_argument("output_file",
|
||||
help="path where to store the generated Memgraph "
|
||||
"configuration file")
|
||||
parser.add_argument("--config-file", default=CONFIG_FILE,
|
||||
help="path to generator configuration file")
|
||||
|
||||
args = parser.parse_args()
|
||||
flags = extract_flags(args.memgraph_binary)
|
||||
|
||||
439
include/_mgp.hpp
439
include/_mgp.hpp
@@ -22,7 +22,7 @@
|
||||
namespace mgp {
|
||||
|
||||
namespace {
|
||||
inline void MgExceptionHandle(mgp_error result_code) {
|
||||
void MgExceptionHandle(mgp_error result_code) {
|
||||
switch (result_code) {
|
||||
case mgp_error::MGP_ERROR_UNKNOWN_ERROR:
|
||||
throw mg_exception::UnknownException();
|
||||
@@ -62,7 +62,7 @@ TResult MgInvoke(TFunc func, TArgs... args) {
|
||||
}
|
||||
|
||||
template <typename TFunc, typename... TArgs>
|
||||
inline void MgInvokeVoid(TFunc func, TArgs... args) {
|
||||
void MgInvokeVoid(TFunc func, TArgs... args) {
|
||||
auto result_code = func(args...);
|
||||
MgExceptionHandle(result_code);
|
||||
}
|
||||
@@ -72,221 +72,211 @@ inline void MgInvokeVoid(TFunc func, TArgs... args) {
|
||||
|
||||
// Make value
|
||||
|
||||
inline mgp_value *value_make_null(mgp_memory *memory) { return MgInvoke<mgp_value *>(mgp_value_make_null, memory); }
|
||||
mgp_value *value_make_null(mgp_memory *memory) { return MgInvoke<mgp_value *>(mgp_value_make_null, memory); }
|
||||
|
||||
inline mgp_value *value_make_bool(int val, mgp_memory *memory) {
|
||||
mgp_value *value_make_bool(int val, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_value *>(mgp_value_make_bool, val, memory);
|
||||
}
|
||||
|
||||
inline mgp_value *value_make_int(int64_t val, mgp_memory *memory) {
|
||||
mgp_value *value_make_int(int64_t val, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_value *>(mgp_value_make_int, val, memory);
|
||||
}
|
||||
|
||||
inline mgp_value *value_make_double(double val, mgp_memory *memory) {
|
||||
mgp_value *value_make_double(double val, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_value *>(mgp_value_make_double, val, memory);
|
||||
}
|
||||
|
||||
inline mgp_value *value_make_string(const char *val, mgp_memory *memory) {
|
||||
mgp_value *value_make_string(const char *val, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_value *>(mgp_value_make_string, val, memory);
|
||||
}
|
||||
|
||||
inline mgp_value *value_make_list(mgp_list *val) { return MgInvoke<mgp_value *>(mgp_value_make_list, val); }
|
||||
mgp_value *value_make_list(mgp_list *val) { return MgInvoke<mgp_value *>(mgp_value_make_list, val); }
|
||||
|
||||
inline mgp_value *value_make_map(mgp_map *val) { return MgInvoke<mgp_value *>(mgp_value_make_map, val); }
|
||||
mgp_value *value_make_map(mgp_map *val) { return MgInvoke<mgp_value *>(mgp_value_make_map, val); }
|
||||
|
||||
inline mgp_value *value_make_vertex(mgp_vertex *val) { return MgInvoke<mgp_value *>(mgp_value_make_vertex, val); }
|
||||
mgp_value *value_make_vertex(mgp_vertex *val) { return MgInvoke<mgp_value *>(mgp_value_make_vertex, val); }
|
||||
|
||||
inline mgp_value *value_make_edge(mgp_edge *val) { return MgInvoke<mgp_value *>(mgp_value_make_edge, val); }
|
||||
mgp_value *value_make_edge(mgp_edge *val) { return MgInvoke<mgp_value *>(mgp_value_make_edge, val); }
|
||||
|
||||
inline mgp_value *value_make_path(mgp_path *val) { return MgInvoke<mgp_value *>(mgp_value_make_path, val); }
|
||||
mgp_value *value_make_path(mgp_path *val) { return MgInvoke<mgp_value *>(mgp_value_make_path, val); }
|
||||
|
||||
inline mgp_value *value_make_date(mgp_date *val) { return MgInvoke<mgp_value *>(mgp_value_make_date, val); }
|
||||
mgp_value *value_make_date(mgp_date *val) { return MgInvoke<mgp_value *>(mgp_value_make_date, val); }
|
||||
|
||||
inline mgp_value *value_make_local_time(mgp_local_time *val) {
|
||||
return MgInvoke<mgp_value *>(mgp_value_make_local_time, val);
|
||||
}
|
||||
mgp_value *value_make_local_time(mgp_local_time *val) { return MgInvoke<mgp_value *>(mgp_value_make_local_time, val); }
|
||||
|
||||
inline mgp_value *value_make_local_date_time(mgp_local_date_time *val) {
|
||||
mgp_value *value_make_local_date_time(mgp_local_date_time *val) {
|
||||
return MgInvoke<mgp_value *>(mgp_value_make_local_date_time, val);
|
||||
}
|
||||
|
||||
inline mgp_value *value_make_duration(mgp_duration *val) { return MgInvoke<mgp_value *>(mgp_value_make_duration, val); }
|
||||
mgp_value *value_make_duration(mgp_duration *val) { return MgInvoke<mgp_value *>(mgp_value_make_duration, val); }
|
||||
|
||||
// Copy value
|
||||
|
||||
// TODO: implement within MGP API
|
||||
// with primitive types ({bool, int, double, string}), create a new identical value
|
||||
// otherwise call mgp_##TYPE_copy and convert tpye
|
||||
inline mgp_value *value_copy(mgp_value *val, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_value *>(mgp_value_copy, val, memory);
|
||||
}
|
||||
mgp_value *value_copy(mgp_value *val, mgp_memory *memory) { return MgInvoke<mgp_value *>(mgp_value_copy, val, memory); }
|
||||
|
||||
// Destroy value
|
||||
|
||||
inline void value_destroy(mgp_value *val) { mgp_value_destroy(val); }
|
||||
void value_destroy(mgp_value *val) { mgp_value_destroy(val); }
|
||||
|
||||
// Get value of type
|
||||
|
||||
inline mgp_value_type value_get_type(mgp_value *val) { return MgInvoke<mgp_value_type>(mgp_value_get_type, val); }
|
||||
mgp_value_type value_get_type(mgp_value *val) { return MgInvoke<mgp_value_type>(mgp_value_get_type, val); }
|
||||
|
||||
inline bool value_get_bool(mgp_value *val) { return MgInvoke<int>(mgp_value_get_bool, val); }
|
||||
bool value_get_bool(mgp_value *val) { return MgInvoke<int>(mgp_value_get_bool, val); }
|
||||
|
||||
inline int64_t value_get_int(mgp_value *val) { return MgInvoke<int64_t>(mgp_value_get_int, val); }
|
||||
int64_t value_get_int(mgp_value *val) { return MgInvoke<int64_t>(mgp_value_get_int, val); }
|
||||
|
||||
inline double value_get_double(mgp_value *val) { return MgInvoke<double>(mgp_value_get_double, val); }
|
||||
double value_get_double(mgp_value *val) { return MgInvoke<double>(mgp_value_get_double, val); }
|
||||
|
||||
inline const char *value_get_string(mgp_value *val) { return MgInvoke<const char *>(mgp_value_get_string, val); }
|
||||
const char *value_get_string(mgp_value *val) { return MgInvoke<const char *>(mgp_value_get_string, val); }
|
||||
|
||||
inline mgp_list *value_get_list(mgp_value *val) { return MgInvoke<mgp_list *>(mgp_value_get_list, val); }
|
||||
mgp_list *value_get_list(mgp_value *val) { return MgInvoke<mgp_list *>(mgp_value_get_list, val); }
|
||||
|
||||
inline mgp_map *value_get_map(mgp_value *val) { return MgInvoke<mgp_map *>(mgp_value_get_map, val); }
|
||||
mgp_map *value_get_map(mgp_value *val) { return MgInvoke<mgp_map *>(mgp_value_get_map, val); }
|
||||
|
||||
inline mgp_vertex *value_get_vertex(mgp_value *val) { return MgInvoke<mgp_vertex *>(mgp_value_get_vertex, val); }
|
||||
mgp_vertex *value_get_vertex(mgp_value *val) { return MgInvoke<mgp_vertex *>(mgp_value_get_vertex, val); }
|
||||
|
||||
inline mgp_edge *value_get_edge(mgp_value *val) { return MgInvoke<mgp_edge *>(mgp_value_get_edge, val); }
|
||||
mgp_edge *value_get_edge(mgp_value *val) { return MgInvoke<mgp_edge *>(mgp_value_get_edge, val); }
|
||||
|
||||
inline mgp_path *value_get_path(mgp_value *val) { return MgInvoke<mgp_path *>(mgp_value_get_path, val); }
|
||||
mgp_path *value_get_path(mgp_value *val) { return MgInvoke<mgp_path *>(mgp_value_get_path, val); }
|
||||
|
||||
inline mgp_date *value_get_date(mgp_value *val) { return MgInvoke<mgp_date *>(mgp_value_get_date, val); }
|
||||
mgp_date *value_get_date(mgp_value *val) { return MgInvoke<mgp_date *>(mgp_value_get_date, val); }
|
||||
|
||||
inline mgp_local_time *value_get_local_time(mgp_value *val) {
|
||||
mgp_local_time *value_get_local_time(mgp_value *val) {
|
||||
return MgInvoke<mgp_local_time *>(mgp_value_get_local_time, val);
|
||||
}
|
||||
|
||||
inline mgp_local_date_time *value_get_local_date_time(mgp_value *val) {
|
||||
mgp_local_date_time *value_get_local_date_time(mgp_value *val) {
|
||||
return MgInvoke<mgp_local_date_time *>(mgp_value_get_local_date_time, val);
|
||||
}
|
||||
|
||||
inline mgp_duration *value_get_duration(mgp_value *val) {
|
||||
return MgInvoke<mgp_duration *>(mgp_value_get_duration, val);
|
||||
}
|
||||
mgp_duration *value_get_duration(mgp_value *val) { return MgInvoke<mgp_duration *>(mgp_value_get_duration, val); }
|
||||
|
||||
// Check type of value
|
||||
|
||||
inline bool value_is_null(mgp_value *val) { return MgInvoke<int>(mgp_value_is_null, val); }
|
||||
bool value_is_null(mgp_value *val) { return MgInvoke<int>(mgp_value_is_null, val); }
|
||||
|
||||
inline bool value_is_bool(mgp_value *val) { return MgInvoke<int>(mgp_value_is_bool, val); }
|
||||
bool value_is_bool(mgp_value *val) { return MgInvoke<int>(mgp_value_is_bool, val); }
|
||||
|
||||
inline bool value_is_int(mgp_value *val) { return MgInvoke<int>(mgp_value_is_int, val); }
|
||||
bool value_is_int(mgp_value *val) { return MgInvoke<int>(mgp_value_is_int, val); }
|
||||
|
||||
inline bool value_is_double(mgp_value *val) { return MgInvoke<int>(mgp_value_is_double, val); }
|
||||
bool value_is_double(mgp_value *val) { return MgInvoke<int>(mgp_value_is_double, val); }
|
||||
|
||||
inline bool value_is_string(mgp_value *val) { return MgInvoke<int>(mgp_value_is_string, val); }
|
||||
bool value_is_string(mgp_value *val) { return MgInvoke<int>(mgp_value_is_string, val); }
|
||||
|
||||
inline bool value_is_list(mgp_value *val) { return MgInvoke<int>(mgp_value_is_list, val); }
|
||||
bool value_is_list(mgp_value *val) { return MgInvoke<int>(mgp_value_is_list, val); }
|
||||
|
||||
inline bool value_is_map(mgp_value *val) { return MgInvoke<int>(mgp_value_is_map, val); }
|
||||
bool value_is_map(mgp_value *val) { return MgInvoke<int>(mgp_value_is_map, val); }
|
||||
|
||||
inline bool value_is_vertex(mgp_value *val) { return MgInvoke<int>(mgp_value_is_vertex, val); }
|
||||
bool value_is_vertex(mgp_value *val) { return MgInvoke<int>(mgp_value_is_vertex, val); }
|
||||
|
||||
inline bool value_is_edge(mgp_value *val) { return MgInvoke<int>(mgp_value_is_edge, val); }
|
||||
bool value_is_edge(mgp_value *val) { return MgInvoke<int>(mgp_value_is_edge, val); }
|
||||
|
||||
inline bool value_is_path(mgp_value *val) { return MgInvoke<int>(mgp_value_is_path, val); }
|
||||
bool value_is_path(mgp_value *val) { return MgInvoke<int>(mgp_value_is_path, val); }
|
||||
|
||||
inline bool value_is_date(mgp_value *val) { return MgInvoke<int>(mgp_value_is_date, val); }
|
||||
bool value_is_date(mgp_value *val) { return MgInvoke<int>(mgp_value_is_date, val); }
|
||||
|
||||
inline bool value_is_local_time(mgp_value *val) { return MgInvoke<int>(mgp_value_is_local_time, val); }
|
||||
bool value_is_local_time(mgp_value *val) { return MgInvoke<int>(mgp_value_is_local_time, val); }
|
||||
|
||||
inline bool value_is_local_date_time(mgp_value *val) { return MgInvoke<int>(mgp_value_is_local_date_time, val); }
|
||||
bool value_is_local_date_time(mgp_value *val) { return MgInvoke<int>(mgp_value_is_local_date_time, val); }
|
||||
|
||||
inline bool value_is_duration(mgp_value *val) { return MgInvoke<int>(mgp_value_is_duration, val); }
|
||||
bool value_is_duration(mgp_value *val) { return MgInvoke<int>(mgp_value_is_duration, val); }
|
||||
|
||||
// Get type
|
||||
|
||||
inline mgp_type *type_any() { return MgInvoke<mgp_type *>(mgp_type_any); }
|
||||
mgp_type *type_any() { return MgInvoke<mgp_type *>(mgp_type_any); }
|
||||
|
||||
inline mgp_type *type_bool() { return MgInvoke<mgp_type *>(mgp_type_bool); }
|
||||
mgp_type *type_bool() { return MgInvoke<mgp_type *>(mgp_type_bool); }
|
||||
|
||||
inline mgp_type *type_string() { return MgInvoke<mgp_type *>(mgp_type_string); }
|
||||
mgp_type *type_string() { return MgInvoke<mgp_type *>(mgp_type_string); }
|
||||
|
||||
inline mgp_type *type_int() { return MgInvoke<mgp_type *>(mgp_type_int); }
|
||||
mgp_type *type_int() { return MgInvoke<mgp_type *>(mgp_type_int); }
|
||||
|
||||
inline mgp_type *type_float() { return MgInvoke<mgp_type *>(mgp_type_float); }
|
||||
mgp_type *type_float() { return MgInvoke<mgp_type *>(mgp_type_float); }
|
||||
|
||||
inline mgp_type *type_number() { return MgInvoke<mgp_type *>(mgp_type_number); }
|
||||
mgp_type *type_number() { return MgInvoke<mgp_type *>(mgp_type_number); }
|
||||
|
||||
inline mgp_type *type_list(mgp_type *element_type) { return MgInvoke<mgp_type *>(mgp_type_list, element_type); }
|
||||
mgp_type *type_list(mgp_type *element_type) { return MgInvoke<mgp_type *>(mgp_type_list, element_type); }
|
||||
|
||||
inline mgp_type *type_map() { return MgInvoke<mgp_type *>(mgp_type_map); }
|
||||
mgp_type *type_map() { return MgInvoke<mgp_type *>(mgp_type_map); }
|
||||
|
||||
inline mgp_type *type_node() { return MgInvoke<mgp_type *>(mgp_type_node); }
|
||||
mgp_type *type_node() { return MgInvoke<mgp_type *>(mgp_type_node); }
|
||||
|
||||
inline mgp_type *type_relationship() { return MgInvoke<mgp_type *>(mgp_type_relationship); }
|
||||
mgp_type *type_relationship() { return MgInvoke<mgp_type *>(mgp_type_relationship); }
|
||||
|
||||
inline mgp_type *type_path() { return MgInvoke<mgp_type *>(mgp_type_path); }
|
||||
mgp_type *type_path() { return MgInvoke<mgp_type *>(mgp_type_path); }
|
||||
|
||||
inline mgp_type *type_date() { return MgInvoke<mgp_type *>(mgp_type_date); }
|
||||
mgp_type *type_date() { return MgInvoke<mgp_type *>(mgp_type_date); }
|
||||
|
||||
inline mgp_type *type_local_time() { return MgInvoke<mgp_type *>(mgp_type_local_time); }
|
||||
mgp_type *type_local_time() { return MgInvoke<mgp_type *>(mgp_type_local_time); }
|
||||
|
||||
inline mgp_type *type_local_date_time() { return MgInvoke<mgp_type *>(mgp_type_local_date_time); }
|
||||
mgp_type *type_local_date_time() { return MgInvoke<mgp_type *>(mgp_type_local_date_time); }
|
||||
|
||||
inline mgp_type *type_duration() { return MgInvoke<mgp_type *>(mgp_type_duration); }
|
||||
mgp_type *type_duration() { return MgInvoke<mgp_type *>(mgp_type_duration); }
|
||||
|
||||
inline mgp_type *type_nullable(mgp_type *type) { return MgInvoke<mgp_type *>(mgp_type_nullable, type); }
|
||||
mgp_type *type_nullable(mgp_type *type) { return MgInvoke<mgp_type *>(mgp_type_nullable, type); }
|
||||
|
||||
// mgp_graph
|
||||
|
||||
inline bool graph_is_mutable(mgp_graph *graph) { return MgInvoke<int>(mgp_graph_is_mutable, graph); }
|
||||
bool graph_is_mutable(mgp_graph *graph) { return MgInvoke<int>(mgp_graph_is_mutable, graph); }
|
||||
|
||||
inline mgp_vertex *graph_create_vertex(mgp_graph *graph, mgp_memory *memory) {
|
||||
mgp_vertex *graph_create_vertex(mgp_graph *graph, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_vertex *>(mgp_graph_create_vertex, graph, memory);
|
||||
}
|
||||
|
||||
inline void graph_delete_vertex(mgp_graph *graph, mgp_vertex *vertex) {
|
||||
MgInvokeVoid(mgp_graph_delete_vertex, graph, vertex);
|
||||
}
|
||||
void graph_delete_vertex(mgp_graph *graph, mgp_vertex *vertex) { MgInvokeVoid(mgp_graph_delete_vertex, graph, vertex); }
|
||||
|
||||
inline void graph_detach_delete_vertex(mgp_graph *graph, mgp_vertex *vertex) {
|
||||
void graph_detach_delete_vertex(mgp_graph *graph, mgp_vertex *vertex) {
|
||||
MgInvokeVoid(mgp_graph_detach_delete_vertex, graph, vertex);
|
||||
}
|
||||
|
||||
inline mgp_edge *graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *to, mgp_edge_type type,
|
||||
mgp_memory *memory) {
|
||||
mgp_edge *graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *to, mgp_edge_type type,
|
||||
mgp_memory *memory) {
|
||||
return MgInvoke<mgp_edge *>(mgp_graph_create_edge, graph, from, to, type, memory);
|
||||
}
|
||||
|
||||
inline void graph_delete_edge(mgp_graph *graph, mgp_edge *edge) { MgInvokeVoid(mgp_graph_delete_edge, graph, edge); }
|
||||
void graph_delete_edge(mgp_graph *graph, mgp_edge *edge) { MgInvokeVoid(mgp_graph_delete_edge, graph, edge); }
|
||||
|
||||
inline mgp_vertex *graph_get_vertex_by_id(mgp_graph *g, mgp_vertex_id id, mgp_memory *memory) {
|
||||
mgp_vertex *graph_get_vertex_by_id(mgp_graph *g, mgp_vertex_id id, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_vertex *>(mgp_graph_get_vertex_by_id, g, id, memory);
|
||||
}
|
||||
|
||||
inline mgp_vertices_iterator *graph_iter_vertices(mgp_graph *g, mgp_memory *memory) {
|
||||
mgp_vertices_iterator *graph_iter_vertices(mgp_graph *g, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_vertices_iterator *>(mgp_graph_iter_vertices, g, memory);
|
||||
}
|
||||
|
||||
// mgp_vertices_iterator
|
||||
|
||||
inline void vertices_iterator_destroy(mgp_vertices_iterator *it) { mgp_vertices_iterator_destroy(it); }
|
||||
void vertices_iterator_destroy(mgp_vertices_iterator *it) { mgp_vertices_iterator_destroy(it); }
|
||||
|
||||
inline mgp_vertex *vertices_iterator_get(mgp_vertices_iterator *it) {
|
||||
mgp_vertex *vertices_iterator_get(mgp_vertices_iterator *it) {
|
||||
return MgInvoke<mgp_vertex *>(mgp_vertices_iterator_get, it);
|
||||
}
|
||||
|
||||
inline mgp_vertex *vertices_iterator_next(mgp_vertices_iterator *it) {
|
||||
mgp_vertex *vertices_iterator_next(mgp_vertices_iterator *it) {
|
||||
return MgInvoke<mgp_vertex *>(mgp_vertices_iterator_next, it);
|
||||
}
|
||||
|
||||
// mgp_edges_iterator
|
||||
|
||||
inline void edges_iterator_destroy(mgp_edges_iterator *it) { mgp_edges_iterator_destroy(it); }
|
||||
void edges_iterator_destroy(mgp_edges_iterator *it) { mgp_edges_iterator_destroy(it); }
|
||||
|
||||
inline mgp_edge *edges_iterator_get(mgp_edges_iterator *it) { return MgInvoke<mgp_edge *>(mgp_edges_iterator_get, it); }
|
||||
mgp_edge *edges_iterator_get(mgp_edges_iterator *it) { return MgInvoke<mgp_edge *>(mgp_edges_iterator_get, it); }
|
||||
|
||||
inline mgp_edge *edges_iterator_next(mgp_edges_iterator *it) {
|
||||
return MgInvoke<mgp_edge *>(mgp_edges_iterator_next, it);
|
||||
}
|
||||
mgp_edge *edges_iterator_next(mgp_edges_iterator *it) { return MgInvoke<mgp_edge *>(mgp_edges_iterator_next, it); }
|
||||
|
||||
// mgp_properties_iterator
|
||||
|
||||
inline void properties_iterator_destroy(mgp_properties_iterator *it) { mgp_properties_iterator_destroy(it); }
|
||||
void properties_iterator_destroy(mgp_properties_iterator *it) { mgp_properties_iterator_destroy(it); }
|
||||
|
||||
inline mgp_property *properties_iterator_get(mgp_properties_iterator *it) {
|
||||
mgp_property *properties_iterator_get(mgp_properties_iterator *it) {
|
||||
return MgInvoke<mgp_property *>(mgp_properties_iterator_get, it);
|
||||
}
|
||||
|
||||
inline mgp_property *properties_iterator_next(mgp_properties_iterator *it) {
|
||||
mgp_property *properties_iterator_next(mgp_properties_iterator *it) {
|
||||
return MgInvoke<mgp_property *>(mgp_properties_iterator_next, it);
|
||||
}
|
||||
|
||||
@@ -294,432 +284,409 @@ inline mgp_property *properties_iterator_next(mgp_properties_iterator *it) {
|
||||
|
||||
// mgp_list
|
||||
|
||||
inline mgp_list *list_make_empty(size_t capacity, mgp_memory *memory) {
|
||||
mgp_list *list_make_empty(size_t capacity, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_list *>(mgp_list_make_empty, capacity, memory);
|
||||
}
|
||||
|
||||
inline mgp_list *list_copy(mgp_list *list, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_list *>(mgp_list_copy, list, memory);
|
||||
}
|
||||
mgp_list *list_copy(mgp_list *list, mgp_memory *memory) { return MgInvoke<mgp_list *>(mgp_list_copy, list, memory); }
|
||||
|
||||
inline void list_destroy(mgp_list *list) { mgp_list_destroy(list); }
|
||||
void list_destroy(mgp_list *list) { mgp_list_destroy(list); }
|
||||
|
||||
inline void list_append(mgp_list *list, mgp_value *val) { MgInvokeVoid(mgp_list_append, list, val); }
|
||||
void list_append(mgp_list *list, mgp_value *val) { MgInvokeVoid(mgp_list_append, list, val); }
|
||||
|
||||
inline void list_append_extend(mgp_list *list, mgp_value *val) { MgInvokeVoid(mgp_list_append_extend, list, val); }
|
||||
void list_append_extend(mgp_list *list, mgp_value *val) { MgInvokeVoid(mgp_list_append_extend, list, val); }
|
||||
|
||||
inline size_t list_size(mgp_list *list) { return MgInvoke<size_t>(mgp_list_size, list); }
|
||||
size_t list_size(mgp_list *list) { return MgInvoke<size_t>(mgp_list_size, list); }
|
||||
|
||||
inline size_t list_capacity(mgp_list *list) { return MgInvoke<size_t>(mgp_list_capacity, list); }
|
||||
size_t list_capacity(mgp_list *list) { return MgInvoke<size_t>(mgp_list_capacity, list); }
|
||||
|
||||
inline mgp_value *list_at(mgp_list *list, size_t index) { return MgInvoke<mgp_value *>(mgp_list_at, list, index); }
|
||||
mgp_value *list_at(mgp_list *list, size_t index) { return MgInvoke<mgp_value *>(mgp_list_at, list, index); }
|
||||
|
||||
// mgp_map
|
||||
|
||||
inline mgp_map *map_make_empty(mgp_memory *memory) { return MgInvoke<mgp_map *>(mgp_map_make_empty, memory); }
|
||||
mgp_map *map_make_empty(mgp_memory *memory) { return MgInvoke<mgp_map *>(mgp_map_make_empty, memory); }
|
||||
|
||||
inline mgp_map *map_copy(mgp_map *map, mgp_memory *memory) { return MgInvoke<mgp_map *>(mgp_map_copy, map, memory); }
|
||||
mgp_map *map_copy(mgp_map *map, mgp_memory *memory) { return MgInvoke<mgp_map *>(mgp_map_copy, map, memory); }
|
||||
|
||||
inline void map_destroy(mgp_map *map) { mgp_map_destroy(map); }
|
||||
void map_destroy(mgp_map *map) { mgp_map_destroy(map); }
|
||||
|
||||
inline void map_insert(mgp_map *map, const char *key, mgp_value *value) {
|
||||
MgInvokeVoid(mgp_map_insert, map, key, value);
|
||||
}
|
||||
void map_insert(mgp_map *map, const char *key, mgp_value *value) { MgInvokeVoid(mgp_map_insert, map, key, value); }
|
||||
|
||||
inline size_t map_size(mgp_map *map) { return MgInvoke<size_t>(mgp_map_size, map); }
|
||||
size_t map_size(mgp_map *map) { return MgInvoke<size_t>(mgp_map_size, map); }
|
||||
|
||||
inline mgp_value *map_at(mgp_map *map, const char *key) { return MgInvoke<mgp_value *>(mgp_map_at, map, key); }
|
||||
mgp_value *map_at(mgp_map *map, const char *key) { return MgInvoke<mgp_value *>(mgp_map_at, map, key); }
|
||||
|
||||
inline const char *map_item_key(mgp_map_item *item) { return MgInvoke<const char *>(mgp_map_item_key, item); }
|
||||
const char *map_item_key(mgp_map_item *item) { return MgInvoke<const char *>(mgp_map_item_key, item); }
|
||||
|
||||
inline mgp_value *map_item_value(mgp_map_item *item) { return MgInvoke<mgp_value *>(mgp_map_item_value, item); }
|
||||
mgp_value *map_item_value(mgp_map_item *item) { return MgInvoke<mgp_value *>(mgp_map_item_value, item); }
|
||||
|
||||
inline mgp_map_items_iterator *map_iter_items(mgp_map *map, mgp_memory *memory) {
|
||||
mgp_map_items_iterator *map_iter_items(mgp_map *map, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_map_items_iterator *>(mgp_map_iter_items, map, memory);
|
||||
}
|
||||
|
||||
inline void map_items_iterator_destroy(mgp_map_items_iterator *it) { mgp_map_items_iterator_destroy(it); }
|
||||
void map_items_iterator_destroy(mgp_map_items_iterator *it) { mgp_map_items_iterator_destroy(it); }
|
||||
|
||||
inline mgp_map_item *map_items_iterator_get(mgp_map_items_iterator *it) {
|
||||
mgp_map_item *map_items_iterator_get(mgp_map_items_iterator *it) {
|
||||
return MgInvoke<mgp_map_item *>(mgp_map_items_iterator_get, it);
|
||||
}
|
||||
|
||||
inline mgp_map_item *map_items_iterator_next(mgp_map_items_iterator *it) {
|
||||
mgp_map_item *map_items_iterator_next(mgp_map_items_iterator *it) {
|
||||
return MgInvoke<mgp_map_item *>(mgp_map_items_iterator_next, it);
|
||||
}
|
||||
|
||||
// mgp_vertex
|
||||
|
||||
inline mgp_vertex_id vertex_get_id(mgp_vertex *v) { return MgInvoke<mgp_vertex_id>(mgp_vertex_get_id, v); }
|
||||
mgp_vertex_id vertex_get_id(mgp_vertex *v) { return MgInvoke<mgp_vertex_id>(mgp_vertex_get_id, v); }
|
||||
|
||||
inline mgp_vertex *vertex_copy(mgp_vertex *v, mgp_memory *memory) {
|
||||
mgp_vertex *vertex_copy(mgp_vertex *v, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_vertex *>(mgp_vertex_copy, v, memory);
|
||||
}
|
||||
|
||||
inline void vertex_destroy(mgp_vertex *v) { mgp_vertex_destroy(v); }
|
||||
void vertex_destroy(mgp_vertex *v) { mgp_vertex_destroy(v); }
|
||||
|
||||
inline bool vertex_equal(mgp_vertex *v1, mgp_vertex *v2) { return MgInvoke<int>(mgp_vertex_equal, v1, v2); }
|
||||
bool vertex_equal(mgp_vertex *v1, mgp_vertex *v2) { return MgInvoke<int>(mgp_vertex_equal, v1, v2); }
|
||||
|
||||
inline size_t vertex_labels_count(mgp_vertex *v) { return MgInvoke<size_t>(mgp_vertex_labels_count, v); }
|
||||
size_t vertex_labels_count(mgp_vertex *v) { return MgInvoke<size_t>(mgp_vertex_labels_count, v); }
|
||||
|
||||
inline mgp_label vertex_label_at(mgp_vertex *v, size_t index) {
|
||||
return MgInvoke<mgp_label>(mgp_vertex_label_at, v, index);
|
||||
}
|
||||
mgp_label vertex_label_at(mgp_vertex *v, size_t index) { return MgInvoke<mgp_label>(mgp_vertex_label_at, v, index); }
|
||||
|
||||
inline bool vertex_has_label(mgp_vertex *v, mgp_label label) { return MgInvoke<int>(mgp_vertex_has_label, v, label); }
|
||||
bool vertex_has_label(mgp_vertex *v, mgp_label label) { return MgInvoke<int>(mgp_vertex_has_label, v, label); }
|
||||
|
||||
inline bool vertex_has_label_named(mgp_vertex *v, const char *label_name) {
|
||||
bool vertex_has_label_named(mgp_vertex *v, const char *label_name) {
|
||||
return MgInvoke<int>(mgp_vertex_has_label_named, v, label_name);
|
||||
}
|
||||
|
||||
inline void vertex_add_label(mgp_vertex *vertex, mgp_label label) { MgInvokeVoid(mgp_vertex_add_label, vertex, label); }
|
||||
void vertex_add_label(mgp_vertex *vertex, mgp_label label) { MgInvokeVoid(mgp_vertex_add_label, vertex, label); }
|
||||
|
||||
inline mgp_value *vertex_get_property(mgp_vertex *v, const char *property_name, mgp_memory *memory) {
|
||||
mgp_value *vertex_get_property(mgp_vertex *v, const char *property_name, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_value *>(mgp_vertex_get_property, v, property_name, memory);
|
||||
}
|
||||
|
||||
inline mgp_properties_iterator *vertex_iter_properties(mgp_vertex *v, mgp_memory *memory) {
|
||||
mgp_properties_iterator *vertex_iter_properties(mgp_vertex *v, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_properties_iterator *>(mgp_vertex_iter_properties, v, memory);
|
||||
}
|
||||
|
||||
inline mgp_edges_iterator *vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory) {
|
||||
mgp_edges_iterator *vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_edges_iterator *>(mgp_vertex_iter_in_edges, v, memory);
|
||||
}
|
||||
|
||||
inline mgp_edges_iterator *vertex_iter_out_edges(mgp_vertex *v, mgp_memory *memory) {
|
||||
mgp_edges_iterator *vertex_iter_out_edges(mgp_vertex *v, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_edges_iterator *>(mgp_vertex_iter_out_edges, v, memory);
|
||||
}
|
||||
|
||||
// mgp_edge
|
||||
|
||||
inline mgp_edge_id edge_get_id(mgp_edge *e) { return MgInvoke<mgp_edge_id>(mgp_edge_get_id, e); }
|
||||
mgp_edge_id edge_get_id(mgp_edge *e) { return MgInvoke<mgp_edge_id>(mgp_edge_get_id, e); }
|
||||
|
||||
inline mgp_edge *edge_copy(mgp_edge *e, mgp_memory *memory) { return MgInvoke<mgp_edge *>(mgp_edge_copy, e, memory); }
|
||||
mgp_edge *edge_copy(mgp_edge *e, mgp_memory *memory) { return MgInvoke<mgp_edge *>(mgp_edge_copy, e, memory); }
|
||||
|
||||
inline void edge_destroy(mgp_edge *e) { mgp_edge_destroy(e); }
|
||||
void edge_destroy(mgp_edge *e) { mgp_edge_destroy(e); }
|
||||
|
||||
inline bool edge_equal(mgp_edge *e1, mgp_edge *e2) { return MgInvoke<int>(mgp_edge_equal, e1, e2); }
|
||||
bool edge_equal(mgp_edge *e1, mgp_edge *e2) { return MgInvoke<int>(mgp_edge_equal, e1, e2); }
|
||||
|
||||
inline mgp_edge_type edge_get_type(mgp_edge *e) { return MgInvoke<mgp_edge_type>(mgp_edge_get_type, e); }
|
||||
mgp_edge_type edge_get_type(mgp_edge *e) { return MgInvoke<mgp_edge_type>(mgp_edge_get_type, e); }
|
||||
|
||||
inline mgp_vertex *edge_get_from(mgp_edge *e) { return MgInvoke<mgp_vertex *>(mgp_edge_get_from, e); }
|
||||
mgp_vertex *edge_get_from(mgp_edge *e) { return MgInvoke<mgp_vertex *>(mgp_edge_get_from, e); }
|
||||
|
||||
inline mgp_vertex *edge_get_to(mgp_edge *e) { return MgInvoke<mgp_vertex *>(mgp_edge_get_to, e); }
|
||||
mgp_vertex *edge_get_to(mgp_edge *e) { return MgInvoke<mgp_vertex *>(mgp_edge_get_to, e); }
|
||||
|
||||
inline mgp_value *edge_get_property(mgp_edge *e, const char *property_name, mgp_memory *memory) {
|
||||
mgp_value *edge_get_property(mgp_edge *e, const char *property_name, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_value *>(mgp_edge_get_property, e, property_name, memory);
|
||||
}
|
||||
|
||||
inline mgp_properties_iterator *edge_iter_properties(mgp_edge *e, mgp_memory *memory) {
|
||||
mgp_properties_iterator *edge_iter_properties(mgp_edge *e, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_properties_iterator *>(mgp_edge_iter_properties, e, memory);
|
||||
}
|
||||
|
||||
// mgp_path
|
||||
|
||||
inline mgp_path *path_make_with_start(mgp_vertex *vertex, mgp_memory *memory) {
|
||||
mgp_path *path_make_with_start(mgp_vertex *vertex, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_path *>(mgp_path_make_with_start, vertex, memory);
|
||||
}
|
||||
|
||||
inline mgp_path *path_copy(mgp_path *path, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_path *>(mgp_path_copy, path, memory);
|
||||
}
|
||||
mgp_path *path_copy(mgp_path *path, mgp_memory *memory) { return MgInvoke<mgp_path *>(mgp_path_copy, path, memory); }
|
||||
|
||||
inline void path_destroy(mgp_path *path) { mgp_path_destroy(path); }
|
||||
void path_destroy(mgp_path *path) { mgp_path_destroy(path); }
|
||||
|
||||
inline void path_expand(mgp_path *path, mgp_edge *edge) { MgInvokeVoid(mgp_path_expand, path, edge); }
|
||||
void path_expand(mgp_path *path, mgp_edge *edge) { MgInvokeVoid(mgp_path_expand, path, edge); }
|
||||
|
||||
inline size_t path_size(mgp_path *path) { return MgInvoke<size_t>(mgp_path_size, path); }
|
||||
size_t path_size(mgp_path *path) { return MgInvoke<size_t>(mgp_path_size, path); }
|
||||
|
||||
inline mgp_vertex *path_vertex_at(mgp_path *path, size_t index) {
|
||||
mgp_vertex *path_vertex_at(mgp_path *path, size_t index) {
|
||||
return MgInvoke<mgp_vertex *>(mgp_path_vertex_at, path, index);
|
||||
}
|
||||
|
||||
inline mgp_edge *path_edge_at(mgp_path *path, size_t index) {
|
||||
return MgInvoke<mgp_edge *>(mgp_path_edge_at, path, index);
|
||||
}
|
||||
mgp_edge *path_edge_at(mgp_path *path, size_t index) { return MgInvoke<mgp_edge *>(mgp_path_edge_at, path, index); }
|
||||
|
||||
inline bool path_equal(mgp_path *p1, mgp_path *p2) { return MgInvoke<int>(mgp_path_equal, p1, p2); }
|
||||
bool path_equal(mgp_path *p1, mgp_path *p2) { return MgInvoke<int>(mgp_path_equal, p1, p2); }
|
||||
|
||||
// Temporal type {mgp_date, mgp_local_time, mgp_local_date_time, mgp_duration} methods
|
||||
|
||||
// mgp_date
|
||||
|
||||
inline mgp_date *date_from_string(const char *string, mgp_memory *memory) {
|
||||
mgp_date *date_from_string(const char *string, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_date *>(mgp_date_from_string, string, memory);
|
||||
}
|
||||
|
||||
inline mgp_date *date_from_parameters(mgp_date_parameters *parameters, mgp_memory *memory) {
|
||||
mgp_date *date_from_parameters(mgp_date_parameters *parameters, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_date *>(mgp_date_from_parameters, parameters, memory);
|
||||
}
|
||||
|
||||
inline mgp_date *date_copy(mgp_date *date, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_date *>(mgp_date_copy, date, memory);
|
||||
}
|
||||
mgp_date *date_copy(mgp_date *date, mgp_memory *memory) { return MgInvoke<mgp_date *>(mgp_date_copy, date, memory); }
|
||||
|
||||
inline void date_destroy(mgp_date *date) { mgp_date_destroy(date); }
|
||||
void date_destroy(mgp_date *date) { mgp_date_destroy(date); }
|
||||
|
||||
inline bool date_equal(mgp_date *first, mgp_date *second) { return MgInvoke<int>(mgp_date_equal, first, second); }
|
||||
bool date_equal(mgp_date *first, mgp_date *second) { return MgInvoke<int>(mgp_date_equal, first, second); }
|
||||
|
||||
inline int date_get_year(mgp_date *date) { return MgInvoke<int>(mgp_date_get_year, date); }
|
||||
int date_get_year(mgp_date *date) { return MgInvoke<int>(mgp_date_get_year, date); }
|
||||
|
||||
inline int date_get_month(mgp_date *date) { return MgInvoke<int>(mgp_date_get_month, date); }
|
||||
int date_get_month(mgp_date *date) { return MgInvoke<int>(mgp_date_get_month, date); }
|
||||
|
||||
inline int date_get_day(mgp_date *date) { return MgInvoke<int>(mgp_date_get_day, date); }
|
||||
int date_get_day(mgp_date *date) { return MgInvoke<int>(mgp_date_get_day, date); }
|
||||
|
||||
inline int64_t date_timestamp(mgp_date *date) { return MgInvoke<int64_t>(mgp_date_timestamp, date); }
|
||||
int64_t date_timestamp(mgp_date *date) { return MgInvoke<int64_t>(mgp_date_timestamp, date); }
|
||||
|
||||
inline mgp_date *date_now(mgp_memory *memory) { return MgInvoke<mgp_date *>(mgp_date_now, memory); }
|
||||
mgp_date *date_now(mgp_memory *memory) { return MgInvoke<mgp_date *>(mgp_date_now, memory); }
|
||||
|
||||
inline mgp_date *date_add_duration(mgp_date *date, mgp_duration *dur, mgp_memory *memory) {
|
||||
mgp_date *date_add_duration(mgp_date *date, mgp_duration *dur, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_date *>(mgp_date_add_duration, date, dur, memory);
|
||||
}
|
||||
|
||||
inline mgp_date *date_sub_duration(mgp_date *date, mgp_duration *dur, mgp_memory *memory) {
|
||||
mgp_date *date_sub_duration(mgp_date *date, mgp_duration *dur, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_date *>(mgp_date_sub_duration, date, dur, memory);
|
||||
}
|
||||
|
||||
inline mgp_duration *date_diff(mgp_date *first, mgp_date *second, mgp_memory *memory) {
|
||||
mgp_duration *date_diff(mgp_date *first, mgp_date *second, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_duration *>(mgp_date_diff, first, second, memory);
|
||||
}
|
||||
|
||||
// mgp_local_time
|
||||
|
||||
inline mgp_local_time *local_time_from_string(const char *string, mgp_memory *memory) {
|
||||
mgp_local_time *local_time_from_string(const char *string, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_time *>(mgp_local_time_from_string, string, memory);
|
||||
}
|
||||
|
||||
inline mgp_local_time *local_time_from_parameters(mgp_local_time_parameters *parameters, mgp_memory *memory) {
|
||||
mgp_local_time *local_time_from_parameters(mgp_local_time_parameters *parameters, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_time *>(mgp_local_time_from_parameters, parameters, memory);
|
||||
}
|
||||
|
||||
inline mgp_local_time *local_time_copy(mgp_local_time *local_time, mgp_memory *memory) {
|
||||
mgp_local_time *local_time_copy(mgp_local_time *local_time, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_time *>(mgp_local_time_copy, local_time, memory);
|
||||
}
|
||||
|
||||
inline void local_time_destroy(mgp_local_time *local_time) { mgp_local_time_destroy(local_time); }
|
||||
void local_time_destroy(mgp_local_time *local_time) { mgp_local_time_destroy(local_time); }
|
||||
|
||||
inline bool local_time_equal(mgp_local_time *first, mgp_local_time *second) {
|
||||
bool local_time_equal(mgp_local_time *first, mgp_local_time *second) {
|
||||
return MgInvoke<int>(mgp_local_time_equal, first, second);
|
||||
}
|
||||
|
||||
inline int local_time_get_hour(mgp_local_time *local_time) {
|
||||
return MgInvoke<int>(mgp_local_time_get_hour, local_time);
|
||||
}
|
||||
int local_time_get_hour(mgp_local_time *local_time) { return MgInvoke<int>(mgp_local_time_get_hour, local_time); }
|
||||
|
||||
inline int local_time_get_minute(mgp_local_time *local_time) {
|
||||
return MgInvoke<int>(mgp_local_time_get_minute, local_time);
|
||||
}
|
||||
int local_time_get_minute(mgp_local_time *local_time) { return MgInvoke<int>(mgp_local_time_get_minute, local_time); }
|
||||
|
||||
inline int local_time_get_second(mgp_local_time *local_time) {
|
||||
return MgInvoke<int>(mgp_local_time_get_second, local_time);
|
||||
}
|
||||
int local_time_get_second(mgp_local_time *local_time) { return MgInvoke<int>(mgp_local_time_get_second, local_time); }
|
||||
|
||||
inline int local_time_get_millisecond(mgp_local_time *local_time) {
|
||||
int local_time_get_millisecond(mgp_local_time *local_time) {
|
||||
return MgInvoke<int>(mgp_local_time_get_millisecond, local_time);
|
||||
}
|
||||
|
||||
inline int local_time_get_microsecond(mgp_local_time *local_time) {
|
||||
int local_time_get_microsecond(mgp_local_time *local_time) {
|
||||
return MgInvoke<int>(mgp_local_time_get_microsecond, local_time);
|
||||
}
|
||||
|
||||
inline int64_t local_time_timestamp(mgp_local_time *local_time) {
|
||||
int64_t local_time_timestamp(mgp_local_time *local_time) {
|
||||
return MgInvoke<int64_t>(mgp_local_time_timestamp, local_time);
|
||||
}
|
||||
|
||||
inline mgp_local_time *local_time_now(mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_time *>(mgp_local_time_now, memory);
|
||||
}
|
||||
mgp_local_time *local_time_now(mgp_memory *memory) { return MgInvoke<mgp_local_time *>(mgp_local_time_now, memory); }
|
||||
|
||||
inline mgp_local_time *local_time_add_duration(mgp_local_time *local_time, mgp_duration *dur, mgp_memory *memory) {
|
||||
mgp_local_time *local_time_add_duration(mgp_local_time *local_time, mgp_duration *dur, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_time *>(mgp_local_time_add_duration, local_time, dur, memory);
|
||||
}
|
||||
|
||||
inline mgp_local_time *local_time_sub_duration(mgp_local_time *local_time, mgp_duration *dur, mgp_memory *memory) {
|
||||
mgp_local_time *local_time_sub_duration(mgp_local_time *local_time, mgp_duration *dur, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_time *>(mgp_local_time_sub_duration, local_time, dur, memory);
|
||||
}
|
||||
|
||||
inline mgp_duration *local_time_diff(mgp_local_time *first, mgp_local_time *second, mgp_memory *memory) {
|
||||
mgp_duration *local_time_diff(mgp_local_time *first, mgp_local_time *second, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_duration *>(mgp_local_time_diff, first, second, memory);
|
||||
}
|
||||
|
||||
// mgp_local_date_time
|
||||
|
||||
inline mgp_local_date_time *local_date_time_from_string(const char *string, mgp_memory *memory) {
|
||||
mgp_local_date_time *local_date_time_from_string(const char *string, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_date_time *>(mgp_local_date_time_from_string, string, memory);
|
||||
}
|
||||
|
||||
inline mgp_local_date_time *local_date_time_from_parameters(mgp_local_date_time_parameters *parameters,
|
||||
mgp_memory *memory) {
|
||||
mgp_local_date_time *local_date_time_from_parameters(mgp_local_date_time_parameters *parameters, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_date_time *>(mgp_local_date_time_from_parameters, parameters, memory);
|
||||
}
|
||||
|
||||
inline mgp_local_date_time *local_date_time_copy(mgp_local_date_time *local_date_time, mgp_memory *memory) {
|
||||
mgp_local_date_time *local_date_time_copy(mgp_local_date_time *local_date_time, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_date_time *>(mgp_local_date_time_copy, local_date_time, memory);
|
||||
}
|
||||
|
||||
inline void local_date_time_destroy(mgp_local_date_time *local_date_time) {
|
||||
mgp_local_date_time_destroy(local_date_time);
|
||||
}
|
||||
void local_date_time_destroy(mgp_local_date_time *local_date_time) { mgp_local_date_time_destroy(local_date_time); }
|
||||
|
||||
inline bool local_date_time_equal(mgp_local_date_time *first, mgp_local_date_time *second) {
|
||||
bool local_date_time_equal(mgp_local_date_time *first, mgp_local_date_time *second) {
|
||||
return MgInvoke<int>(mgp_local_date_time_equal, first, second);
|
||||
}
|
||||
|
||||
inline int local_date_time_get_year(mgp_local_date_time *local_date_time) {
|
||||
int local_date_time_get_year(mgp_local_date_time *local_date_time) {
|
||||
return MgInvoke<int>(mgp_local_date_time_get_year, local_date_time);
|
||||
}
|
||||
|
||||
inline int local_date_time_get_month(mgp_local_date_time *local_date_time) {
|
||||
int local_date_time_get_month(mgp_local_date_time *local_date_time) {
|
||||
return MgInvoke<int>(mgp_local_date_time_get_month, local_date_time);
|
||||
}
|
||||
|
||||
inline int local_date_time_get_day(mgp_local_date_time *local_date_time) {
|
||||
int local_date_time_get_day(mgp_local_date_time *local_date_time) {
|
||||
return MgInvoke<int>(mgp_local_date_time_get_day, local_date_time);
|
||||
}
|
||||
|
||||
inline int local_date_time_get_hour(mgp_local_date_time *local_date_time) {
|
||||
int local_date_time_get_hour(mgp_local_date_time *local_date_time) {
|
||||
return MgInvoke<int>(mgp_local_date_time_get_hour, local_date_time);
|
||||
}
|
||||
|
||||
inline int local_date_time_get_minute(mgp_local_date_time *local_date_time) {
|
||||
int local_date_time_get_minute(mgp_local_date_time *local_date_time) {
|
||||
return MgInvoke<int>(mgp_local_date_time_get_minute, local_date_time);
|
||||
}
|
||||
|
||||
inline int local_date_time_get_second(mgp_local_date_time *local_date_time) {
|
||||
int local_date_time_get_second(mgp_local_date_time *local_date_time) {
|
||||
return MgInvoke<int>(mgp_local_date_time_get_second, local_date_time);
|
||||
}
|
||||
|
||||
inline int local_date_time_get_millisecond(mgp_local_date_time *local_date_time) {
|
||||
int local_date_time_get_millisecond(mgp_local_date_time *local_date_time) {
|
||||
return MgInvoke<int>(mgp_local_date_time_get_millisecond, local_date_time);
|
||||
}
|
||||
|
||||
inline int local_date_time_get_microsecond(mgp_local_date_time *local_date_time) {
|
||||
int local_date_time_get_microsecond(mgp_local_date_time *local_date_time) {
|
||||
return MgInvoke<int>(mgp_local_date_time_get_microsecond, local_date_time);
|
||||
}
|
||||
|
||||
inline int64_t local_date_time_timestamp(mgp_local_date_time *local_date_time) {
|
||||
int64_t local_date_time_timestamp(mgp_local_date_time *local_date_time) {
|
||||
return MgInvoke<int64_t>(mgp_local_date_time_timestamp, local_date_time);
|
||||
}
|
||||
|
||||
inline mgp_local_date_time *local_date_time_now(mgp_memory *memory) {
|
||||
mgp_local_date_time *local_date_time_now(mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_date_time *>(mgp_local_date_time_now, memory);
|
||||
}
|
||||
|
||||
inline mgp_local_date_time *local_date_time_add_duration(mgp_local_date_time *local_date_time, mgp_duration *dur,
|
||||
mgp_memory *memory) {
|
||||
mgp_local_date_time *local_date_time_add_duration(mgp_local_date_time *local_date_time, mgp_duration *dur,
|
||||
mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_date_time *>(mgp_local_date_time_add_duration, local_date_time, dur, memory);
|
||||
}
|
||||
|
||||
inline mgp_local_date_time *local_date_time_sub_duration(mgp_local_date_time *local_date_time, mgp_duration *dur,
|
||||
mgp_memory *memory) {
|
||||
mgp_local_date_time *local_date_time_sub_duration(mgp_local_date_time *local_date_time, mgp_duration *dur,
|
||||
mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_date_time *>(mgp_local_date_time_sub_duration, local_date_time, dur, memory);
|
||||
}
|
||||
|
||||
inline mgp_duration *local_date_time_diff(mgp_local_date_time *first, mgp_local_date_time *second, mgp_memory *memory) {
|
||||
mgp_duration *local_date_time_diff(mgp_local_date_time *first, mgp_local_date_time *second, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_duration *>(mgp_local_date_time_diff, first, second, memory);
|
||||
}
|
||||
|
||||
// mgp_duration
|
||||
|
||||
inline mgp_duration *duration_from_string(const char *string, mgp_memory *memory) {
|
||||
mgp_duration *duration_from_string(const char *string, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_duration *>(mgp_duration_from_string, string, memory);
|
||||
}
|
||||
|
||||
inline mgp_duration *duration_from_parameters(mgp_duration_parameters *parameters, mgp_memory *memory) {
|
||||
mgp_duration *duration_from_parameters(mgp_duration_parameters *parameters, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_duration *>(mgp_duration_from_parameters, parameters, memory);
|
||||
}
|
||||
|
||||
inline mgp_duration *duration_from_microseconds(int64_t microseconds, mgp_memory *memory) {
|
||||
mgp_duration *duration_from_microseconds(int64_t microseconds, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_duration *>(mgp_duration_from_microseconds, microseconds, memory);
|
||||
}
|
||||
|
||||
inline mgp_duration *duration_copy(mgp_duration *duration, mgp_memory *memory) {
|
||||
mgp_duration *duration_copy(mgp_duration *duration, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_duration *>(mgp_duration_copy, duration, memory);
|
||||
}
|
||||
|
||||
inline void duration_destroy(mgp_duration *duration) { mgp_duration_destroy(duration); }
|
||||
void duration_destroy(mgp_duration *duration) { mgp_duration_destroy(duration); }
|
||||
|
||||
inline int64_t duration_get_microseconds(mgp_duration *duration) {
|
||||
int64_t duration_get_microseconds(mgp_duration *duration) {
|
||||
return MgInvoke<int64_t>(mgp_duration_get_microseconds, duration);
|
||||
}
|
||||
|
||||
inline bool duration_equal(mgp_duration *first, mgp_duration *second) {
|
||||
bool duration_equal(mgp_duration *first, mgp_duration *second) {
|
||||
return MgInvoke<int>(mgp_duration_equal, first, second);
|
||||
}
|
||||
|
||||
inline mgp_duration *duration_neg(mgp_duration *duration, mgp_memory *memory) {
|
||||
mgp_duration *duration_neg(mgp_duration *duration, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_duration *>(mgp_duration_neg, duration, memory);
|
||||
}
|
||||
|
||||
inline mgp_duration *duration_add(mgp_duration *first, mgp_duration *second, mgp_memory *memory) {
|
||||
mgp_duration *duration_add(mgp_duration *first, mgp_duration *second, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_duration *>(mgp_duration_add, first, second, memory);
|
||||
}
|
||||
|
||||
inline mgp_duration *duration_sub(mgp_duration *first, mgp_duration *second, mgp_memory *memory) {
|
||||
mgp_duration *duration_sub(mgp_duration *first, mgp_duration *second, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_duration *>(mgp_duration_sub, first, second, memory);
|
||||
}
|
||||
|
||||
// Procedure
|
||||
|
||||
inline mgp_proc *module_add_read_procedure(mgp_module *module, const char *name, mgp_proc_cb cb) {
|
||||
mgp_proc *module_add_read_procedure(mgp_module *module, const char *name, mgp_proc_cb cb) {
|
||||
return MgInvoke<mgp_proc *>(mgp_module_add_read_procedure, module, name, cb);
|
||||
}
|
||||
|
||||
inline mgp_proc *module_add_write_procedure(mgp_module *module, const char *name, mgp_proc_cb cb) {
|
||||
mgp_proc *module_add_write_procedure(mgp_module *module, const char *name, mgp_proc_cb cb) {
|
||||
return MgInvoke<mgp_proc *>(mgp_module_add_write_procedure, module, name, cb);
|
||||
}
|
||||
|
||||
inline void proc_add_arg(mgp_proc *proc, const char *name, mgp_type *type) {
|
||||
void proc_add_arg(mgp_proc *proc, const char *name, mgp_type *type) {
|
||||
MgInvokeVoid(mgp_proc_add_arg, proc, name, type);
|
||||
}
|
||||
|
||||
inline void proc_add_opt_arg(mgp_proc *proc, const char *name, mgp_type *type, mgp_value *default_value) {
|
||||
void proc_add_opt_arg(mgp_proc *proc, const char *name, mgp_type *type, mgp_value *default_value) {
|
||||
MgInvokeVoid(mgp_proc_add_opt_arg, proc, name, type, default_value);
|
||||
}
|
||||
|
||||
inline void proc_add_result(mgp_proc *proc, const char *name, mgp_type *type) {
|
||||
void proc_add_result(mgp_proc *proc, const char *name, mgp_type *type) {
|
||||
MgInvokeVoid(mgp_proc_add_result, proc, name, type);
|
||||
}
|
||||
|
||||
inline void proc_add_deprecated_result(mgp_proc *proc, const char *name, mgp_type *type) {
|
||||
void proc_add_deprecated_result(mgp_proc *proc, const char *name, mgp_type *type) {
|
||||
MgInvokeVoid(mgp_proc_add_deprecated_result, proc, name, type);
|
||||
}
|
||||
|
||||
inline bool must_abort(mgp_graph *graph) { return mgp_must_abort(graph); }
|
||||
bool must_abort(mgp_graph *graph) { return mgp_must_abort(graph); }
|
||||
|
||||
// mgp_result
|
||||
|
||||
inline void result_set_error_msg(mgp_result *res, const char *error_msg) {
|
||||
void result_set_error_msg(mgp_result *res, const char *error_msg) {
|
||||
MgInvokeVoid(mgp_result_set_error_msg, res, error_msg);
|
||||
}
|
||||
|
||||
inline mgp_result_record *result_new_record(mgp_result *res) {
|
||||
mgp_result_record *result_new_record(mgp_result *res) {
|
||||
return MgInvoke<mgp_result_record *>(mgp_result_new_record, res);
|
||||
}
|
||||
|
||||
inline void result_record_insert(mgp_result_record *record, const char *field_name, mgp_value *val) {
|
||||
void result_record_insert(mgp_result_record *record, const char *field_name, mgp_value *val) {
|
||||
MgInvokeVoid(mgp_result_record_insert, record, field_name, val);
|
||||
}
|
||||
|
||||
// Function
|
||||
|
||||
inline mgp_func *module_add_function(mgp_module *module, const char *name, mgp_func_cb cb) {
|
||||
mgp_func *module_add_function(mgp_module *module, const char *name, mgp_func_cb cb) {
|
||||
return MgInvoke<mgp_func *>(mgp_module_add_function, module, name, cb);
|
||||
}
|
||||
|
||||
inline void func_add_arg(mgp_func *func, const char *name, mgp_type *type) {
|
||||
void func_add_arg(mgp_func *func, const char *name, mgp_type *type) {
|
||||
MgInvokeVoid(mgp_func_add_arg, func, name, type);
|
||||
}
|
||||
|
||||
inline void func_add_opt_arg(mgp_func *func, const char *name, mgp_type *type, mgp_value *default_value) {
|
||||
void func_add_opt_arg(mgp_func *func, const char *name, mgp_type *type, mgp_value *default_value) {
|
||||
MgInvokeVoid(mgp_func_add_opt_arg, func, name, type, default_value);
|
||||
}
|
||||
|
||||
inline void func_result_set_error_msg(mgp_func_result *res, const char *msg, mgp_memory *memory) {
|
||||
void func_result_set_error_msg(mgp_func_result *res, const char *msg, mgp_memory *memory) {
|
||||
MgInvokeVoid(mgp_func_result_set_error_msg, res, msg, memory);
|
||||
}
|
||||
|
||||
inline void func_result_set_value(mgp_func_result *res, mgp_value *value, mgp_memory *memory) {
|
||||
void func_result_set_value(mgp_func_result *res, mgp_value *value, mgp_memory *memory) {
|
||||
MgInvokeVoid(mgp_func_result_set_value, res, value, memory);
|
||||
}
|
||||
|
||||
|
||||
1818
include/mgp.hpp
1818
include/mgp.hpp
File diff suppressed because it is too large
Load Diff
6
init
6
init
@@ -147,11 +147,5 @@ done;
|
||||
python3 -m pip install pre-commit
|
||||
python3 -m pre_commit install
|
||||
|
||||
# Install py format tools
|
||||
echo "Install black formatter"
|
||||
python3 -m pip install black==22.*
|
||||
echo "Install isort"
|
||||
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
|
||||
|
||||
@@ -208,7 +208,7 @@ pymgclient_tag="4f85c179e56302d46a1e3e2cf43509db65f062b3" # (2021-01-15)
|
||||
repo_clone_try_double "${primary_urls[pymgclient]}" "${secondary_urls[pymgclient]}" "pymgclient" "$pymgclient_tag"
|
||||
|
||||
# mgconsole
|
||||
mgconsole_tag="v1.3.0" # (2022-11-20)
|
||||
mgconsole_tag="v1.1.0" # (2021-10-07)
|
||||
repo_clone_try_double "${primary_urls[mgconsole]}" "${secondary_urls[mgconsole]}" "mgconsole" "$mgconsole_tag" true
|
||||
|
||||
spdlog_tag="v1.9.2" # (2021-08-12)
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
include = '\.pyi?$'
|
||||
extend-exclude = '''
|
||||
/(
|
||||
| .git
|
||||
| .__pycache__
|
||||
| build
|
||||
| libs
|
||||
| .cache
|
||||
)/
|
||||
'''
|
||||
@@ -69,7 +69,7 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem
|
||||
try {
|
||||
mgp::memory = memory;
|
||||
|
||||
AddProcedure(SampleReadProc, "return_true", mgp::ProcedureType::Read,
|
||||
AddProcedure(SampleReadProc, "return_true", mgp::ProdecureType::Read,
|
||||
{mgp::Parameter("param_1", mgp::Type::Int), mgp::Parameter("param_2", mgp::Type::Double, 2.3)},
|
||||
{mgp::Return("out", mgp::Type::Bool)}, module, memory);
|
||||
} catch (const std::exception &e) {
|
||||
@@ -79,7 +79,7 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem
|
||||
try {
|
||||
mgp::memory = memory;
|
||||
|
||||
mgp::AddProcedure(AddXNodes, "add_x_nodes", mgp::ProcedureType::Write, {mgp::Parameter("param_1", mgp::Type::Int)},
|
||||
mgp::AddProcedure(AddXNodes, "add_x_nodes", mgp::ProdecureType::Write, {mgp::Parameter("param_1", mgp::Type::Int)},
|
||||
{}, module, memory);
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
|
||||
@@ -90,7 +90,7 @@ QueryData Client::Execute(const std::string &query, const std::map<std::string,
|
||||
// It is super critical from performance point of view to send the pull message right after the run message. Otherwise
|
||||
// the performance will degrade multiple magnitudes.
|
||||
encoder_.MessageRun(query, parameters, {});
|
||||
encoder_.MessagePull({{"n", Value(-1)}});
|
||||
encoder_.MessagePull({});
|
||||
|
||||
spdlog::debug("Reading run message response");
|
||||
Signature signature{};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2021 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 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -62,42 +61,3 @@ inline void LoadConfig(const std::string &product_name) {
|
||||
for (int i = 0; i < custom_argc; ++i) free(custom_argv[i]);
|
||||
delete[] custom_argv;
|
||||
}
|
||||
|
||||
std::pair<std::string, std::string> LoadUsernameAndPassword(const std::string &pass_file) {
|
||||
std::ifstream file(pass_file);
|
||||
if (file.fail()) {
|
||||
spdlog::warn("Problem with opening MG_PASSFILE, memgraph server will start without user");
|
||||
return {};
|
||||
}
|
||||
std::vector<std::string> result;
|
||||
|
||||
std::string line;
|
||||
std::getline(file, line);
|
||||
size_t pos = 0;
|
||||
std::string token;
|
||||
static constexpr std::string_view delimiter{":"};
|
||||
while ((pos = line.find(delimiter)) != std::string::npos) {
|
||||
if (line[pos - 1] == '\\') {
|
||||
line.erase(pos - 1, 1);
|
||||
token += line.substr(0, pos);
|
||||
line.erase(0, pos);
|
||||
|
||||
} else {
|
||||
token += line.substr(0, pos);
|
||||
result.push_back(token);
|
||||
line.erase(0, pos + delimiter.length());
|
||||
token = "";
|
||||
}
|
||||
}
|
||||
result.push_back(line);
|
||||
file.close();
|
||||
|
||||
if (result.size() != 2) {
|
||||
spdlog::warn(
|
||||
"Wrong data format. Data should be store in format: username:password, memgraph server will start without "
|
||||
"user");
|
||||
return {};
|
||||
}
|
||||
|
||||
return {result[0], result[1]};
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/format.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include <spdlog/common.h>
|
||||
@@ -99,10 +98,6 @@
|
||||
#include "audit/log.hpp"
|
||||
#endif
|
||||
|
||||
constexpr const char *kMgUser = "MEMGRAPH_USER";
|
||||
constexpr const char *kMgPassword = "MEMGRAPH_PASSWORD";
|
||||
constexpr const char *kMgPassfile = "MEMGRAPH_PASSFILE";
|
||||
|
||||
namespace {
|
||||
std::string GetAllowedEnumValuesString(const auto &mappings) {
|
||||
std::vector<std::string> allowed_values;
|
||||
@@ -139,10 +134,6 @@ std::optional<Enum> StringToEnum(const auto &value, const auto &mappings) {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Short help flag.
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_HIDDEN_bool(h, false, "Print usage and exit.");
|
||||
|
||||
// Bolt server flags.
|
||||
DEFINE_string(bolt_address, "0.0.0.0", "IP address on which the Bolt server should listen.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
@@ -172,11 +163,6 @@ DEFINE_string(bolt_key_file, "", "Key file which should be used for the Bolt ser
|
||||
DEFINE_string(bolt_server_name_for_init, "",
|
||||
"Server name which the database should send to the client in the "
|
||||
"Bolt INIT message.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(init_file, "",
|
||||
"Path to cypherl file that is used for configuring users and database schema before server starts.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(init_data_file, "", "Path to cypherl file that is used for creating data after server starts.");
|
||||
|
||||
// General purpose flags.
|
||||
// NOTE: The `data_directory` flag must be the same here and in
|
||||
@@ -486,33 +472,6 @@ struct SessionData {
|
||||
DEFINE_string(auth_user_or_role_name_regex, memgraph::glue::kDefaultUserRoleRegex.data(),
|
||||
"Set to the regular expression that each user or role name must fulfill.");
|
||||
|
||||
void InitFromCypherlFile(memgraph::query::InterpreterContext &ctx, std::string cypherl_file_path
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
memgraph::audit::Log *audit_log
|
||||
#endif
|
||||
) {
|
||||
memgraph::query::Interpreter interpreter(&ctx);
|
||||
std::ifstream file(cypherl_file_path);
|
||||
if (file.is_open()) {
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
if (!line.empty()) {
|
||||
auto results = interpreter.Prepare(line, {}, {});
|
||||
memgraph::query::DiscardValueResultStream stream;
|
||||
interpreter.Pull(&stream, {}, results.qid);
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
audit_log->Record("", "", line, {});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
class BoltSession final : public memgraph::communication::bolt::Session<memgraph::communication::v2::InputStream,
|
||||
memgraph::communication::v2::OutputStream> {
|
||||
public:
|
||||
@@ -728,11 +687,6 @@ int main(int argc, char **argv) {
|
||||
LoadConfig("memgraph");
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
if (FLAGS_h) {
|
||||
gflags::ShowUsageWithFlags(argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
InitializeLogger();
|
||||
|
||||
// Unhandled exception handler init.
|
||||
@@ -926,29 +880,6 @@ int main(int argc, char **argv) {
|
||||
interpreter_context.auth = &auth_handler;
|
||||
interpreter_context.auth_checker = &auth_checker;
|
||||
|
||||
if (!FLAGS_init_file.empty()) {
|
||||
spdlog::info("Running init file.");
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
InitFromCypherlFile(interpreter_context, FLAGS_init_file, &audit_log);
|
||||
}
|
||||
#else
|
||||
InitFromCypherlFile(interpreter_context, FLAGS_init_file);
|
||||
#endif
|
||||
}
|
||||
|
||||
auto *maybe_username = std::getenv(kMgUser);
|
||||
auto *maybe_password = std::getenv(kMgPassword);
|
||||
auto *maybe_pass_file = std::getenv(kMgPassfile);
|
||||
if (maybe_username && maybe_password) {
|
||||
auth_handler.CreateUser(maybe_username, maybe_password);
|
||||
} else if (maybe_pass_file) {
|
||||
const auto [username, password] = LoadUsernameAndPassword(maybe_pass_file);
|
||||
if (!username.empty() && !password.empty()) {
|
||||
auth_handler.CreateUser(username, password);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Triggers can execute query procedures, so we need to reload the modules first and then
|
||||
// the triggers
|
||||
@@ -1026,17 +957,6 @@ int main(int argc, char **argv) {
|
||||
MG_ASSERT(server.Start(), "Couldn't start the Bolt server!");
|
||||
websocket_server.Start();
|
||||
|
||||
if (!FLAGS_init_data_file.empty()) {
|
||||
spdlog::info("Running init data file.");
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
InitFromCypherlFile(interpreter_context, FLAGS_init_data_file, &audit_log);
|
||||
}
|
||||
#else
|
||||
InitFromCypherlFile(interpreter_context, FLAGS_init_data_file);
|
||||
#endif
|
||||
}
|
||||
|
||||
server.AwaitShutdown();
|
||||
websocket_server.AwaitShutdown();
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ auto SubgraphVertexAccessor::OutEdges(storage::View view) const -> decltype(impl
|
||||
auto maybe_edges = impl_.impl_.OutEdges(view, {});
|
||||
if (maybe_edges.HasError()) return maybe_edges.GetError();
|
||||
auto edges = std::move(*maybe_edges);
|
||||
const auto &graph_edges = graph_->edges();
|
||||
auto graph_edges = graph_->edges();
|
||||
|
||||
std::vector<storage::EdgeAccessor> filteredOutEdges;
|
||||
for (auto &edge : edges) {
|
||||
@@ -132,7 +132,7 @@ auto SubgraphVertexAccessor::InEdges(storage::View view) const -> decltype(impl_
|
||||
auto maybe_edges = impl_.impl_.InEdges(view, {});
|
||||
if (maybe_edges.HasError()) return maybe_edges.GetError();
|
||||
auto edges = std::move(*maybe_edges);
|
||||
const auto &graph_edges = graph_->edges();
|
||||
auto graph_edges = graph_->edges();
|
||||
|
||||
std::vector<storage::EdgeAccessor> filteredOutEdges;
|
||||
for (auto &edge : edges) {
|
||||
|
||||
@@ -200,7 +200,7 @@ class SubgraphVertexAccessor final {
|
||||
return impl_ == v.impl_;
|
||||
}
|
||||
|
||||
auto InEdges(storage::View view) const -> decltype(impl_.InEdges(view));
|
||||
auto InEdges(storage::View view) const -> decltype(impl_.OutEdges(view));
|
||||
|
||||
auto OutEdges(storage::View view) const -> decltype(impl_.OutEdges(view));
|
||||
|
||||
|
||||
@@ -461,8 +461,7 @@ cpp<#
|
||||
(lcp:define-class aggregation (binary-operator)
|
||||
((op "Op" :scope :public)
|
||||
(symbol-pos :int32_t :initval -1 :scope :public
|
||||
:documentation "Symbol table position of the symbol this Aggregation is mapped to.")
|
||||
(distinct :bool :initval "false" :scope :public))
|
||||
:documentation "Symbol table position of the symbol this Aggregation is mapped to."))
|
||||
(:public
|
||||
(lcp:define-enum op
|
||||
(count min max sum avg collect-list collect-map project)
|
||||
@@ -506,8 +505,8 @@ cpp<#
|
||||
|
||||
/// Aggregation's first expression is the value being aggregated. The second
|
||||
/// expression is the key used only in COLLECT_MAP.
|
||||
Aggregation(Expression *expression1, Expression *expression2, Op op, bool distinct)
|
||||
: BinaryOperator(expression1, expression2), op_(op), distinct_(distinct) {
|
||||
Aggregation(Expression *expression1, Expression *expression2, Op op)
|
||||
: BinaryOperator(expression1, expression2), op_(op) {
|
||||
// COUNT without expression denotes COUNT(*) in cypher.
|
||||
DMG_ASSERT(expression1 || op == Aggregation::Op::COUNT,
|
||||
"All aggregations, except COUNT require expression");
|
||||
|
||||
@@ -2106,7 +2106,7 @@ antlrcpp::Any CypherMainVisitor::visitAtom(MemgraphCypher::AtomContext *ctx) {
|
||||
// Here we handle COUNT(*). COUNT(expression) is handled in
|
||||
// visitFunctionInvocation with other aggregations. This is visible in
|
||||
// functionInvocation and atom producions in opencypher grammar.
|
||||
return static_cast<Expression *>(storage_->Create<Aggregation>(nullptr, nullptr, Aggregation::Op::COUNT, false));
|
||||
return static_cast<Expression *>(storage_->Create<Aggregation>(nullptr, nullptr, Aggregation::Op::COUNT));
|
||||
} else if (ctx->ALL()) {
|
||||
auto *ident = storage_->Create<Identifier>(
|
||||
std::any_cast<std::string>(ctx->filterExpression()->idInColl()->variable()->accept(this)));
|
||||
@@ -2222,7 +2222,9 @@ antlrcpp::Any CypherMainVisitor::visitNumberLiteral(MemgraphCypher::NumberLitera
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitFunctionInvocation(MemgraphCypher::FunctionInvocationContext *ctx) {
|
||||
const auto is_distinct = ctx->DISTINCT() != nullptr;
|
||||
if (ctx->DISTINCT()) {
|
||||
throw utils::NotYetImplemented("DISTINCT function call");
|
||||
}
|
||||
auto function_name = std::any_cast<std::string>(ctx->functionName()->accept(this));
|
||||
std::vector<Expression *> expressions;
|
||||
for (auto *expression : ctx->expression()) {
|
||||
@@ -2230,38 +2232,33 @@ antlrcpp::Any CypherMainVisitor::visitFunctionInvocation(MemgraphCypher::Functio
|
||||
}
|
||||
if (expressions.size() == 1U) {
|
||||
if (function_name == Aggregation::kCount) {
|
||||
return static_cast<Expression *>(
|
||||
storage_->Create<Aggregation>(expressions[0], nullptr, Aggregation::Op::COUNT, is_distinct));
|
||||
return static_cast<Expression *>(storage_->Create<Aggregation>(expressions[0], nullptr, Aggregation::Op::COUNT));
|
||||
}
|
||||
if (function_name == Aggregation::kMin) {
|
||||
return static_cast<Expression *>(
|
||||
storage_->Create<Aggregation>(expressions[0], nullptr, Aggregation::Op::MIN, is_distinct));
|
||||
return static_cast<Expression *>(storage_->Create<Aggregation>(expressions[0], nullptr, Aggregation::Op::MIN));
|
||||
}
|
||||
if (function_name == Aggregation::kMax) {
|
||||
return static_cast<Expression *>(
|
||||
storage_->Create<Aggregation>(expressions[0], nullptr, Aggregation::Op::MAX, is_distinct));
|
||||
return static_cast<Expression *>(storage_->Create<Aggregation>(expressions[0], nullptr, Aggregation::Op::MAX));
|
||||
}
|
||||
if (function_name == Aggregation::kSum) {
|
||||
return static_cast<Expression *>(
|
||||
storage_->Create<Aggregation>(expressions[0], nullptr, Aggregation::Op::SUM, is_distinct));
|
||||
return static_cast<Expression *>(storage_->Create<Aggregation>(expressions[0], nullptr, Aggregation::Op::SUM));
|
||||
}
|
||||
if (function_name == Aggregation::kAvg) {
|
||||
return static_cast<Expression *>(
|
||||
storage_->Create<Aggregation>(expressions[0], nullptr, Aggregation::Op::AVG, is_distinct));
|
||||
return static_cast<Expression *>(storage_->Create<Aggregation>(expressions[0], nullptr, Aggregation::Op::AVG));
|
||||
}
|
||||
if (function_name == Aggregation::kCollect) {
|
||||
return static_cast<Expression *>(
|
||||
storage_->Create<Aggregation>(expressions[0], nullptr, Aggregation::Op::COLLECT_LIST, is_distinct));
|
||||
storage_->Create<Aggregation>(expressions[0], nullptr, Aggregation::Op::COLLECT_LIST));
|
||||
}
|
||||
if (function_name == Aggregation::kProject) {
|
||||
return static_cast<Expression *>(
|
||||
storage_->Create<Aggregation>(expressions[0], nullptr, Aggregation::Op::PROJECT, is_distinct));
|
||||
storage_->Create<Aggregation>(expressions[0], nullptr, Aggregation::Op::PROJECT));
|
||||
}
|
||||
}
|
||||
|
||||
if (expressions.size() == 2U && function_name == Aggregation::kCollect) {
|
||||
return static_cast<Expression *>(
|
||||
storage_->Create<Aggregation>(expressions[1], expressions[0], Aggregation::Op::COLLECT_MAP, is_distinct));
|
||||
storage_->Create<Aggregation>(expressions[1], expressions[0], Aggregation::Op::COLLECT_MAP));
|
||||
}
|
||||
|
||||
auto is_user_defined_function = [](const std::string &function_name) {
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
#include "query/procedure/cypher_types.hpp"
|
||||
#include "query/procedure/mg_procedure_impl.hpp"
|
||||
#include "query/procedure/module.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "storage/v2/view.hpp"
|
||||
#include "utils/algorithm.hpp"
|
||||
@@ -114,7 +113,6 @@ extern const Event UnionOperator;
|
||||
extern const Event CartesianOperator;
|
||||
extern const Event CallProcedureOperator;
|
||||
extern const Event ForeachOperator;
|
||||
extern const Event EmptyResultOperator;
|
||||
} // namespace EventCounter
|
||||
|
||||
namespace memgraph::query::plan {
|
||||
@@ -3059,56 +3057,6 @@ void EdgeUniquenessFilter::EdgeUniquenessFilterCursor::Shutdown() { input_cursor
|
||||
|
||||
void EdgeUniquenessFilter::EdgeUniquenessFilterCursor::Reset() { input_cursor_->Reset(); }
|
||||
|
||||
EmptyResult::EmptyResult(const std::shared_ptr<LogicalOperator> &input)
|
||||
: input_(input ? input : std::make_shared<Once>()) {}
|
||||
|
||||
ACCEPT_WITH_INPUT(EmptyResult)
|
||||
|
||||
std::vector<Symbol> EmptyResult::OutputSymbols(const SymbolTable &) const { // NOLINT(hicpp-named-parameter)
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<Symbol> EmptyResult::ModifiedSymbols(const SymbolTable &) const { // NOLINT(hicpp-named-parameter)
|
||||
return {};
|
||||
}
|
||||
|
||||
class EmptyResultCursor : public Cursor {
|
||||
public:
|
||||
EmptyResultCursor(const EmptyResult &self, utils::MemoryResource *mem)
|
||||
: input_cursor_(self.input_->MakeCursor(mem)) {}
|
||||
|
||||
bool Pull(Frame &frame, ExecutionContext &context) override {
|
||||
SCOPED_PROFILE_OP("EmptyResult");
|
||||
|
||||
if (!pulled_all_input_) {
|
||||
while (input_cursor_->Pull(frame, context)) {
|
||||
if (MustAbort(context)) {
|
||||
throw HintedAbortError();
|
||||
}
|
||||
}
|
||||
pulled_all_input_ = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Shutdown() override { input_cursor_->Shutdown(); }
|
||||
|
||||
void Reset() override {
|
||||
input_cursor_->Reset();
|
||||
pulled_all_input_ = false;
|
||||
}
|
||||
|
||||
private:
|
||||
const UniqueCursorPtr input_cursor_;
|
||||
bool pulled_all_input_{false};
|
||||
};
|
||||
|
||||
UniqueCursorPtr EmptyResult::MakeCursor(utils::MemoryResource *mem) const {
|
||||
EventCounter::IncrementCounter(EventCounter::EmptyResultOperator);
|
||||
|
||||
return MakeUniqueCursorPtr<EmptyResultCursor>(mem, *this, mem);
|
||||
}
|
||||
|
||||
Accumulate::Accumulate(const std::shared_ptr<LogicalOperator> &input, const std::vector<Symbol> &symbols,
|
||||
bool advance_command)
|
||||
: input_(input), symbols_(symbols), advance_command_(advance_command) {}
|
||||
@@ -3263,8 +3211,7 @@ class AggregateCursor : public Cursor {
|
||||
// aggregation map. The vectors in an AggregationValue contain one element for
|
||||
// each aggregation in this LogicalOp.
|
||||
struct AggregationValue {
|
||||
explicit AggregationValue(utils::MemoryResource *mem)
|
||||
: counts_(mem), values_(mem), remember_(mem), unique_values_(mem) {}
|
||||
explicit AggregationValue(utils::MemoryResource *mem) : counts_(mem), values_(mem), remember_(mem) {}
|
||||
|
||||
// how many input rows have been aggregated in respective values_ element so
|
||||
// far
|
||||
@@ -3277,10 +3224,6 @@ class AggregateCursor : public Cursor {
|
||||
utils::pmr::vector<TypedValue> values_;
|
||||
// remember values.
|
||||
utils::pmr::vector<TypedValue> remember_;
|
||||
|
||||
using TSet = utils::pmr::unordered_set<TypedValue, TypedValue::Hash, TypedValue::BoolEqual>;
|
||||
|
||||
utils::pmr::vector<TSet> unique_values_;
|
||||
};
|
||||
|
||||
const Aggregate &self_;
|
||||
@@ -3356,7 +3299,6 @@ class AggregateCursor : public Cursor {
|
||||
for (const auto &agg_elem : self_.aggregations_) {
|
||||
auto *mem = agg_value->values_.get_allocator().GetMemoryResource();
|
||||
agg_value->values_.emplace_back(DefaultAggregationOpValue(agg_elem, mem));
|
||||
agg_value->unique_values_.emplace_back(AggregationValue::TSet(mem));
|
||||
}
|
||||
agg_value->counts_.resize(self_.aggregations_.size(), 0);
|
||||
|
||||
@@ -3375,9 +3317,8 @@ class AggregateCursor : public Cursor {
|
||||
|
||||
auto count_it = agg_value->counts_.begin();
|
||||
auto value_it = agg_value->values_.begin();
|
||||
auto unique_values_it = agg_value->unique_values_.begin();
|
||||
auto agg_elem_it = self_.aggregations_.begin();
|
||||
for (; count_it < agg_value->counts_.end(); count_it++, value_it++, unique_values_it++, agg_elem_it++) {
|
||||
for (; count_it < agg_value->counts_.end(); count_it++, value_it++, agg_elem_it++) {
|
||||
// COUNT(*) is the only case where input expression is optional
|
||||
// handle it here
|
||||
auto input_expr_ptr = agg_elem_it->value;
|
||||
@@ -3392,12 +3333,6 @@ class AggregateCursor : public Cursor {
|
||||
// Aggregations skip Null input values.
|
||||
if (input_value.IsNull()) continue;
|
||||
const auto &agg_op = agg_elem_it->op;
|
||||
if (agg_elem_it->distinct) {
|
||||
auto insert_result = unique_values_it->insert(input_value);
|
||||
if (!insert_result.second) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
*count_it += 1;
|
||||
if (*count_it == 1) {
|
||||
// first value, nothing to aggregate. check type, set and continue.
|
||||
|
||||
@@ -132,7 +132,6 @@ class Cartesian;
|
||||
class CallProcedure;
|
||||
class LoadCsv;
|
||||
class Foreach;
|
||||
class EmptyResult;
|
||||
|
||||
using LogicalOperatorCompositeVisitor = utils::CompositeVisitor<
|
||||
Once, CreateNode, CreateExpand, ScanAll, ScanAllByLabel,
|
||||
@@ -141,7 +140,7 @@ using LogicalOperatorCompositeVisitor = utils::CompositeVisitor<
|
||||
Expand, ExpandVariable, ConstructNamedPath, Filter, Produce, Delete,
|
||||
SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels,
|
||||
EdgeUniquenessFilter, Accumulate, Aggregate, Skip, Limit, OrderBy, Merge,
|
||||
Optional, Unwind, Distinct, Union, Cartesian, CallProcedure, LoadCsv, Foreach, EmptyResult>;
|
||||
Optional, Unwind, Distinct, Union, Cartesian, CallProcedure, LoadCsv, Foreach>;
|
||||
|
||||
using LogicalOperatorLeafVisitor = utils::LeafVisitor<Once>;
|
||||
|
||||
@@ -1555,41 +1554,6 @@ edge lists).")
|
||||
(:serialize (:slk))
|
||||
(:clone))
|
||||
|
||||
|
||||
(lcp:define-class empty-result (logical-operator)
|
||||
((input "std::shared_ptr<LogicalOperator>" :scope :public
|
||||
:slk-save #'slk-save-operator-pointer
|
||||
:slk-load #'slk-load-operator-pointer))
|
||||
(:documentation
|
||||
"Pulls everything from the input and discards it.
|
||||
|
||||
On the first Pull from this operator's Cursor the input Cursor will be Pulled
|
||||
until it is empty. The results won't be accumulated in the temporary cache.
|
||||
|
||||
This technique is used for ensuring that the cursor has been exhausted after
|
||||
a WriteHandleClause. A typical use case is a `MATCH--SET` query with RETURN statement
|
||||
missing.
|
||||
@param input Input @c LogicalOperator. ")
|
||||
(:public
|
||||
#>cpp
|
||||
EmptyResult() {}
|
||||
|
||||
EmptyResult(const std::shared_ptr<LogicalOperator> &input);
|
||||
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
||||
UniqueCursorPtr MakeCursor(utils::MemoryResource *) const override;
|
||||
std::vector<Symbol> OutputSymbols(const SymbolTable &) 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<#)
|
||||
(:serialize (:slk))
|
||||
(:clone))
|
||||
|
||||
|
||||
(lcp:define-class accumulate (logical-operator)
|
||||
((input "std::shared_ptr<LogicalOperator>" :scope :public
|
||||
:slk-save #'slk-save-operator-pointer
|
||||
@@ -1693,8 +1657,7 @@ elements are in an undefined state after aggregation.")
|
||||
:slk-save #'slk-save-ast-pointer
|
||||
:slk-load (slk-load-ast-pointer "Expression"))
|
||||
(op "::Aggregation::Op")
|
||||
(output-sym "Symbol")
|
||||
(distinct bool :initval "false" ))
|
||||
(output-sym "Symbol"))
|
||||
(:documentation
|
||||
"An aggregation element, contains:
|
||||
(input data expression, key expression - only used in COLLECT_MAP, type of
|
||||
@@ -2319,9 +2282,9 @@ clauses.
|
||||
(:public
|
||||
#>cpp
|
||||
Foreach() = default;
|
||||
Foreach(std::shared_ptr<LogicalOperator> input,
|
||||
Foreach(std::shared_ptr<LogicalOperator> input,
|
||||
std::shared_ptr<LogicalOperator> updates,
|
||||
Expression *named_expr,
|
||||
Expression *named_expr,
|
||||
Symbol loop_variable_symbol);
|
||||
|
||||
bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override;
|
||||
|
||||
@@ -156,7 +156,6 @@ PRE_VISIT(RemoveProperty);
|
||||
PRE_VISIT(RemoveLabels);
|
||||
PRE_VISIT(EdgeUniquenessFilter);
|
||||
PRE_VISIT(Accumulate);
|
||||
PRE_VISIT(EmptyResult);
|
||||
|
||||
bool PlanPrinter::PreVisit(query::plan::Aggregate &op) {
|
||||
WithPrintLn([&](auto &out) {
|
||||
@@ -402,8 +401,6 @@ json ToJson(const Aggregate::Element &elem) {
|
||||
}
|
||||
json["op"] = utils::ToLowerCase(Aggregation::OpToString(elem.op));
|
||||
json["output_symbol"] = ToJson(elem.output_sym);
|
||||
json["distinct"] = elem.distinct;
|
||||
|
||||
return json;
|
||||
}
|
||||
////////////////////////// END HELPER FUNCTIONS ////////////////////////////////
|
||||
@@ -706,17 +703,6 @@ bool PlanToJsonVisitor::PreVisit(EdgeUniquenessFilter &op) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(EmptyResult &op) {
|
||||
json self;
|
||||
self["name"] = "EmptyResult";
|
||||
|
||||
op.input_->Accept(*this);
|
||||
self["input"] = PopOutput();
|
||||
|
||||
output_ = std::move(self);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PlanToJsonVisitor::PreVisit(Accumulate &op) {
|
||||
json self;
|
||||
self["name"] = "Accumulate";
|
||||
|
||||
@@ -80,7 +80,6 @@ class PlanPrinter : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
bool PreVisit(Optional &) override;
|
||||
bool PreVisit(Cartesian &) override;
|
||||
|
||||
bool PreVisit(EmptyResult &) override;
|
||||
bool PreVisit(Produce &) override;
|
||||
bool PreVisit(Accumulate &) override;
|
||||
bool PreVisit(Aggregate &) override;
|
||||
@@ -196,7 +195,6 @@ class PlanToJsonVisitor : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
bool PreVisit(ScanAllByLabelProperty &) override;
|
||||
bool PreVisit(ScanAllById &) override;
|
||||
|
||||
bool PreVisit(EmptyResult &) override;
|
||||
bool PreVisit(Produce &) override;
|
||||
bool PreVisit(Accumulate &) override;
|
||||
bool PreVisit(Aggregate &) override;
|
||||
|
||||
@@ -11,11 +11,10 @@
|
||||
|
||||
#include "query/plan/read_write_type_checker.hpp"
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define PRE_VISIT(TOp, RWType, continue_visiting) \
|
||||
bool ReadWriteTypeChecker::PreVisit(TOp &) { /*NOLINT(bugprone-macro-parentheses)*/ \
|
||||
UpdateType(RWType); \
|
||||
return continue_visiting; \
|
||||
#define PRE_VISIT(TOp, RWType, continue_visiting) \
|
||||
bool ReadWriteTypeChecker::PreVisit(TOp &op) { \
|
||||
UpdateType(RWType); \
|
||||
return continue_visiting; \
|
||||
}
|
||||
|
||||
namespace memgraph::query::plan {
|
||||
@@ -55,7 +54,6 @@ bool ReadWriteTypeChecker::PreVisit(Cartesian &op) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PRE_VISIT(EmptyResult, RWType::NONE, true)
|
||||
PRE_VISIT(Produce, RWType::NONE, true)
|
||||
PRE_VISIT(Accumulate, RWType::NONE, true)
|
||||
PRE_VISIT(Aggregate, RWType::NONE, true)
|
||||
@@ -88,7 +86,7 @@ bool ReadWriteTypeChecker::PreVisit([[maybe_unused]] Foreach &op) {
|
||||
|
||||
#undef PRE_VISIT
|
||||
|
||||
bool ReadWriteTypeChecker::Visit(Once &) { return false; } // NOLINT(hicpp-named-parameter)
|
||||
bool ReadWriteTypeChecker::Visit(Once &op) { return false; }
|
||||
|
||||
void ReadWriteTypeChecker::UpdateType(RWType op_type) {
|
||||
// Update type only if it's not the NONE type and the current operator's type
|
||||
|
||||
@@ -73,7 +73,6 @@ class ReadWriteTypeChecker : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
bool PreVisit(Optional &) override;
|
||||
bool PreVisit(Cartesian &) override;
|
||||
|
||||
bool PreVisit(EmptyResult &) override;
|
||||
bool PreVisit(Produce &) override;
|
||||
bool PreVisit(Accumulate &) override;
|
||||
bool PreVisit(Aggregate &) override;
|
||||
|
||||
@@ -298,15 +298,6 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(EmptyResult &op) override {
|
||||
prev_ops_.push_back(&op);
|
||||
return true;
|
||||
}
|
||||
bool PostVisit(EmptyResult &) override {
|
||||
prev_ops_.pop_back();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(Delete &op) override {
|
||||
prev_ops_.push_back(&op);
|
||||
return true;
|
||||
|
||||
@@ -344,8 +344,7 @@ class ReturnBodyContext : public HierarchicalTreeVisitor {
|
||||
bool PostVisit(Aggregation &aggr) override {
|
||||
// Aggregation contains a virtual symbol, where the result will be stored.
|
||||
const auto &symbol = symbol_table_.at(aggr);
|
||||
aggregations_.emplace_back(
|
||||
Aggregate::Element{aggr.expression1_, aggr.expression2_, aggr.op_, symbol, aggr.distinct_});
|
||||
aggregations_.emplace_back(Aggregate::Element{aggr.expression1_, aggr.expression2_, aggr.op_, symbol});
|
||||
// Aggregation expression1_ is optional in COUNT(*), and COLLECT_MAP uses
|
||||
// two expressions, so we can have 0, 1 or 2 elements on the
|
||||
// has_aggregation_stack for this Aggregation expression.
|
||||
|
||||
@@ -180,7 +180,7 @@ class RuleBasedPlanner {
|
||||
}
|
||||
}
|
||||
uint64_t merge_id = 0;
|
||||
for (const auto &clause : query_part.remaining_clauses) {
|
||||
for (auto *clause : query_part.remaining_clauses) {
|
||||
MG_ASSERT(!utils::IsSubtype(*clause, Match::kType), "Unexpected Match in remaining clauses");
|
||||
if (auto *ret = utils::Downcast<Return>(clause)) {
|
||||
input_op = impl::GenReturn(*ret, std::move(input_op), *context.symbol_table, is_write, context.bound_symbols,
|
||||
@@ -203,7 +203,6 @@ class RuleBasedPlanner {
|
||||
context.bound_symbols.insert(symbol);
|
||||
input_op =
|
||||
std::make_unique<plan::Unwind>(std::move(input_op), unwind->named_expression_->expression_, symbol);
|
||||
|
||||
} else if (auto *call_proc = utils::Downcast<query::CallProcedure>(clause)) {
|
||||
std::vector<Symbol> result_symbols;
|
||||
result_symbols.reserve(call_proc->result_identifiers_.size());
|
||||
@@ -225,7 +224,6 @@ class RuleBasedPlanner {
|
||||
input_op =
|
||||
std::make_unique<plan::LoadCsv>(std::move(input_op), load_csv->file_, load_csv->with_header_,
|
||||
load_csv->ignore_bad_, load_csv->delimiter_, load_csv->quote_, row_sym);
|
||||
|
||||
} else if (auto *foreach = utils::Downcast<query::Foreach>(clause)) {
|
||||
is_write = true;
|
||||
input_op = HandleForeachClause(foreach, std::move(input_op), *context.symbol_table, context.bound_symbols,
|
||||
@@ -235,10 +233,6 @@ class RuleBasedPlanner {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Is this the only situation that should be covered
|
||||
if (input_op->OutputSymbols(*context.symbol_table).empty()) {
|
||||
input_op = std::make_unique<EmptyResult>(std::move(input_op));
|
||||
}
|
||||
return input_op;
|
||||
}
|
||||
|
||||
@@ -424,8 +418,7 @@ class RuleBasedPlanner {
|
||||
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) {
|
||||
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});
|
||||
|
||||
@@ -33,28 +33,6 @@ extern "C" {
|
||||
|
||||
namespace memgraph::query::procedure {
|
||||
|
||||
constexpr const char *func_code =
|
||||
"import ast\n\n"
|
||||
"no_removals = ['collections', 'abc', 'sys']\n"
|
||||
"modules = set()\n\n"
|
||||
"def visit_Import(node):\n"
|
||||
" for name in node.names:\n"
|
||||
" mod_name = name.name.split('.')[0]\n"
|
||||
" if mod_name not in no_removals:\n"
|
||||
" modules.add(mod_name)\n\n"
|
||||
"def visit_ImportFrom(node):\n"
|
||||
" if node.module is not None and node.level == 0:\n"
|
||||
" mod_name = node.module.split('.')[0]\n"
|
||||
" if mod_name not in no_removals:\n"
|
||||
" modules.add(mod_name)\n"
|
||||
"node_iter = ast.NodeVisitor()\n"
|
||||
"node_iter.visit_Import = visit_Import\n"
|
||||
"node_iter.visit_ImportFrom = visit_ImportFrom\n"
|
||||
"node_iter.visit(ast.parse(code))\n";
|
||||
|
||||
void ProcessFileDependencies(std::filesystem::path file_path_, const char *module_path, const char *func_code,
|
||||
PyObject *sys_mod_ref);
|
||||
|
||||
ModuleRegistry gModuleRegistry;
|
||||
|
||||
Module::~Module() {}
|
||||
@@ -1017,45 +995,11 @@ bool PythonModule::Close() {
|
||||
procedures_.clear();
|
||||
transformations_.clear();
|
||||
functions_.clear();
|
||||
|
||||
// Get the reference to sys.modules dictionary
|
||||
// Delete the module from the `sys.modules` directory so that the module will
|
||||
// be properly imported if imported again.
|
||||
py::Object sys(PyImport_ImportModule("sys"));
|
||||
PyObject *sys_mod_ref = sys.GetAttr("modules").Ptr();
|
||||
|
||||
std::string stem = file_path_.stem().string();
|
||||
|
||||
ProcessFileDependencies(file_path_, file_path_.stem().c_str(), func_code, sys_mod_ref);
|
||||
|
||||
std::vector<std::filesystem::path> submodules;
|
||||
|
||||
for (auto it = std::filesystem::recursive_directory_iterator(file_path_.parent_path());
|
||||
it != std::filesystem::recursive_directory_iterator(); ++it) {
|
||||
std::string dir_entry_stem = it->path().stem().string();
|
||||
if (it->is_regular_file() || dir_entry_stem == "__pycache__") continue;
|
||||
if (dir_entry_stem.find(stem) != std::string_view::npos) {
|
||||
it.disable_recursion_pending();
|
||||
submodules.emplace_back(it->path());
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &submodule : submodules) {
|
||||
if (std::filesystem::exists(submodule)) {
|
||||
std::filesystem::remove_all(submodule / "__pycache__");
|
||||
for (auto const &rec_dir_entry : std::filesystem::recursive_directory_iterator(submodule)) {
|
||||
std::string rec_dir_entry_stem = rec_dir_entry.path().stem().string();
|
||||
if (rec_dir_entry.is_directory() && rec_dir_entry_stem != "__pycache__") {
|
||||
std::filesystem::remove_all(rec_dir_entry.path() / "__pycache__");
|
||||
}
|
||||
std::string rec_dir_entry_ext = rec_dir_entry.path().extension().string();
|
||||
if (!rec_dir_entry.is_regular_file() || rec_dir_entry_ext != ".py") continue;
|
||||
ProcessFileDependencies(rec_dir_entry.path().c_str(), file_path_.stem().c_str(), func_code, sys_mod_ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// first throw out of cache file
|
||||
if (PyDict_DelItemString(sys_mod_ref, file_path_.stem().c_str()) != 0) {
|
||||
spdlog::warn("Failed to remove the module {} from sys.modules", file_path_.stem().c_str());
|
||||
if (PyDict_DelItemString(sys.GetAttr("modules").Ptr(), file_path_.stem().c_str()) != 0) {
|
||||
spdlog::warn("Failed to remove the module from sys.modules");
|
||||
py_module_ = py::Object(nullptr);
|
||||
return false;
|
||||
}
|
||||
@@ -1067,51 +1011,6 @@ bool PythonModule::Close() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProcessFileDependencies(std::filesystem::path file_path_, const char *module_path, const char *func_code,
|
||||
PyObject *sys_mod_ref) {
|
||||
const auto maybe_content =
|
||||
ReadFile(file_path_); // this is already done at Load so it can somehow be optimized but not sure how yet
|
||||
|
||||
if (maybe_content) {
|
||||
const char *content_value = maybe_content->c_str();
|
||||
if (content_value) {
|
||||
PyObject *py_main = PyImport_ImportModule("__main__");
|
||||
PyObject *py_global_dict = PyModule_GetDict(py_main);
|
||||
|
||||
PyDict_SetItemString(py_global_dict, "code", PyUnicode_FromString(content_value));
|
||||
PyRun_String(func_code, Py_file_input, py_global_dict, py_global_dict);
|
||||
PyObject *py_res = PyDict_GetItemString(py_global_dict, "modules");
|
||||
|
||||
PyObject *iterator = PyObject_GetIter(py_res);
|
||||
PyObject *module = nullptr;
|
||||
|
||||
if (iterator != nullptr) {
|
||||
while ((module = PyIter_Next(iterator))) {
|
||||
const char *module_name = PyUnicode_AsUTF8(module);
|
||||
auto module_name_str = std::string(module_name);
|
||||
PyObject *sys_iterator = PyObject_GetIter(PyDict_Keys(sys_mod_ref));
|
||||
if (sys_iterator == nullptr) {
|
||||
spdlog::warn("Cannot get reference to the sys.modules.keys()");
|
||||
break;
|
||||
}
|
||||
PyObject *sys_mod_key = nullptr;
|
||||
while ((sys_mod_key = PyIter_Next(sys_iterator))) {
|
||||
const char *sys_mod_key_name = PyUnicode_AsUTF8(sys_mod_key);
|
||||
auto sys_mod_key_name_str = std::string(sys_mod_key_name);
|
||||
if (sys_mod_key_name_str.rfind(module_name_str, 0) == 0 && sys_mod_key_name_str.compare(module_path) != 0) {
|
||||
PyDict_DelItemString(sys_mod_ref, sys_mod_key_name); // don't test output
|
||||
}
|
||||
Py_DECREF(sys_mod_key);
|
||||
}
|
||||
Py_DECREF(sys_iterator);
|
||||
Py_DECREF(module);
|
||||
}
|
||||
Py_DECREF(iterator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::map<std::string, mgp_proc, std::less<>> *PythonModule::Procedures() const {
|
||||
MG_ASSERT(py_module_,
|
||||
"Attempting to access procedures of a module that has "
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
M(RemovePropertyOperator, "Number of times RemoveProperty operator was used.") \
|
||||
M(RemoveLabelsOperator, "Number of times RemoveLabels operator was used.") \
|
||||
M(EdgeUniquenessFilterOperator, "Number of times EdgeUniquenessFilter operator was used.") \
|
||||
M(EmptyResultOperator, "Number of times EmptyResult operator was used.") \
|
||||
M(AccumulateOperator, "Number of times Accumulate operator was used.") \
|
||||
M(AggregateOperator, "Number of times Aggregate operator was used.") \
|
||||
M(SkipOperator, "Number of times Skip operator was used.") \
|
||||
|
||||
@@ -43,7 +43,6 @@ add_subdirectory(magic_functions)
|
||||
add_subdirectory(module_file_manager)
|
||||
add_subdirectory(monitoring_server)
|
||||
add_subdirectory(lba_procedures)
|
||||
add_subdirectory(python_query_modules_reloading)
|
||||
|
||||
copy_e2e_python_files(pytest_runner pytest_runner.sh "")
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/memgraph-selfsigned.crt DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
# licenses/APL.txt.
|
||||
|
||||
import sys
|
||||
|
||||
import default_config
|
||||
import mgclient
|
||||
import pytest
|
||||
|
||||
import default_config
|
||||
|
||||
|
||||
def test_does_default_config_match():
|
||||
connection = mgclient.connect(host="localhost", port=7687)
|
||||
@@ -24,15 +24,7 @@ def test_does_default_config_match():
|
||||
cursor.execute("SHOW CONFIG")
|
||||
config = cursor.fetchall()
|
||||
|
||||
define_msg = """
|
||||
If this test fails after adding a new DEFINE_* flag,
|
||||
you should decide whether your new flag needs to be
|
||||
returned in the SHOW CONFIG command. If not, please
|
||||
use the DEFINE_HIDDEN_* macro instead of DEFINE_* to
|
||||
prevent SHOW CONFIG from returning it.
|
||||
"""
|
||||
|
||||
assert len(config) == len(default_config.startup_config_dict), define_msg
|
||||
assert len(config) == len(default_config.startup_config_dict)
|
||||
|
||||
for flag in config:
|
||||
flag_name = flag[0]
|
||||
|
||||
@@ -163,10 +163,4 @@ startup_config_dict = {
|
||||
),
|
||||
"query_max_plans": ("1000", "1000", "Maximum number of generated plans for a query."),
|
||||
"flag_file": ("", "", "load flags from file"),
|
||||
"init_file": (
|
||||
"",
|
||||
"",
|
||||
"Path to cypherl file that is used for configuring users and database schema before server starts.",
|
||||
),
|
||||
"init_data_file": ("", "", "Path to cypherl file that is used for creating data after server starts."),
|
||||
}
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
template_cluster: &template_cluster
|
||||
cluster:
|
||||
main:
|
||||
args:
|
||||
[
|
||||
"--log-level=TRACE",
|
||||
"--storage-properties-on-edges=True",
|
||||
"--storage-snapshot-interval-sec",
|
||||
"300",
|
||||
"--storage-wal-enabled=True",
|
||||
]
|
||||
args: ["--log-level=TRACE", "--storage-properties-on-edges=True", "--storage-snapshot-interval-sec", "300", "--storage-wal-enabled=True"]
|
||||
log_file: "configuration-check-e2e.log"
|
||||
setup_queries: []
|
||||
validation_queries: []
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
function(copy_query_modules_reloading_procedures_e2e_python_files FILE_NAME)
|
||||
copy_e2e_python_files(python_query_modules_reloading ${FILE_NAME})
|
||||
endfunction()
|
||||
|
||||
copy_query_modules_reloading_procedures_e2e_python_files(common.py)
|
||||
copy_query_modules_reloading_procedures_e2e_python_files(test_reload_query_module.py)
|
||||
|
||||
add_subdirectory(procedures)
|
||||
@@ -1,25 +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
|
||||
|
||||
|
||||
def execute_and_fetch_all(cursor: mgclient.Cursor, query: str, params: dict = {}) -> typing.List[tuple]:
|
||||
cursor.execute(query, params)
|
||||
return cursor.fetchall()
|
||||
|
||||
|
||||
def connect(**kwargs) -> mgclient.Connection:
|
||||
connection = mgclient.connect(host="localhost", port=7687, **kwargs)
|
||||
connection.autocommit = True
|
||||
return connection
|
||||
@@ -1,6 +0,0 @@
|
||||
copy_query_modules_reloading_procedures_e2e_python_files(test_module.py)
|
||||
copy_query_modules_reloading_procedures_e2e_python_files(new_test_module.py)
|
||||
|
||||
add_subdirectory(mage)
|
||||
add_subdirectory(new_test_module_utils)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
add_subdirectory(test_module)
|
||||
@@ -1,3 +0,0 @@
|
||||
copy_query_modules_reloading_procedures_e2e_python_files(test_functions.py)
|
||||
|
||||
add_subdirectory(test_functions_dir)
|
||||
@@ -1,2 +0,0 @@
|
||||
def test_function(a: int, b: int) -> int:
|
||||
return a + b
|
||||
@@ -1 +0,0 @@
|
||||
copy_query_modules_reloading_procedures_e2e_python_files(test_subfunctions.py)
|
||||
@@ -1,2 +0,0 @@
|
||||
def test_subfunction(a: int, b: int) -> int:
|
||||
return a * b
|
||||
@@ -1,14 +0,0 @@
|
||||
import mgp
|
||||
|
||||
# isort: off
|
||||
# fmt: off
|
||||
from new_test_module_utils.new_test_functions import \
|
||||
test_function as test_function1
|
||||
from new_test_module_utils.new_test_functions_dir.new_test_subfunctions import \
|
||||
test_subfunction as test_function2
|
||||
# fmt: on
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def test(ctx: mgp.ProcCtx, a: mgp.Number, b: mgp.Number) -> mgp.Record(result1=mgp.Number, result2=mgp.Number):
|
||||
return mgp.Record(result1=test_function1(a, b), result2=test_function2(a, b))
|
||||
@@ -1,3 +0,0 @@
|
||||
copy_query_modules_reloading_procedures_e2e_python_files(new_test_functions.py)
|
||||
|
||||
add_subdirectory(new_test_functions_dir)
|
||||
@@ -1,2 +0,0 @@
|
||||
def test_function(a: int, b: int) -> int:
|
||||
return a + b
|
||||
@@ -1 +0,0 @@
|
||||
copy_query_modules_reloading_procedures_e2e_python_files(new_test_subfunctions.py)
|
||||
@@ -1,2 +0,0 @@
|
||||
def test_subfunction(a: int, b: int) -> int:
|
||||
return a * b
|
||||
@@ -1,13 +0,0 @@
|
||||
import mgp
|
||||
from mage.test_module.test_functions import test_function as test_function1
|
||||
|
||||
# isort: off
|
||||
# fmt: off
|
||||
from mage.test_module.test_functions_dir.test_subfunctions import \
|
||||
test_subfunction as test_function2
|
||||
# fmt: on
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def test(ctx: mgp.ProcCtx, a: mgp.Number, b: mgp.Number) -> mgp.Record(result1=mgp.Number, result2=mgp.Number):
|
||||
return mgp.Record(result1=test_function1(a, b), result2=test_function2(a, b))
|
||||
@@ -1,180 +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 os # To be removed
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
from common import connect, execute_and_fetch_all
|
||||
|
||||
COMMON_PATH_PREFIX_TEST1 = "procedures/mage/test_module"
|
||||
COMMON_PATH_PREFIX_TEST2 = "procedures/new_test_module_utils"
|
||||
|
||||
FUNC1_PATH = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
COMMON_PATH_PREFIX_TEST1,
|
||||
"test_functions.py",
|
||||
)
|
||||
|
||||
FUNC2_PATH = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
COMMON_PATH_PREFIX_TEST1,
|
||||
"test_functions_dir/test_subfunctions.py",
|
||||
)
|
||||
|
||||
FUNC3_PATH = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
COMMON_PATH_PREFIX_TEST2,
|
||||
"new_test_functions.py",
|
||||
)
|
||||
|
||||
FUNC4_PATH = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
COMMON_PATH_PREFIX_TEST2,
|
||||
"new_test_functions_dir/new_test_subfunctions.py",
|
||||
)
|
||||
|
||||
|
||||
def preprocess_functions(path1: str, path2: str):
|
||||
with open(path1, "w") as func1_file:
|
||||
func1_file.write(
|
||||
"""def test_function(a: int, b: int) -> int:
|
||||
return a - b
|
||||
"""
|
||||
)
|
||||
|
||||
with open(path2, "w") as func2_file:
|
||||
func2_file.write(
|
||||
"""def test_subfunction(a: int, b: int) -> int:
|
||||
return a / b
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def postprocess_functions(path1: str, path2: str):
|
||||
with open(path1, "w") as func1_file:
|
||||
func1_file.write(
|
||||
"""def test_function(a: int, b: int) -> int:
|
||||
return a + b
|
||||
"""
|
||||
)
|
||||
|
||||
with open(path2, "w") as func2_file:
|
||||
func2_file.write(
|
||||
"""def test_subfunction(a: int, b: int) -> int:
|
||||
return a * b
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def test_mg_load_reload_submodule_root_utils():
|
||||
"""Tests whether mg.load reloads content of some submodule code."""
|
||||
cursor = connect().cursor()
|
||||
# First do a simple experiment
|
||||
test_module_res = execute_and_fetch_all(cursor, "CALL new_test_module.test(10, 2) YIELD * RETURN *;")
|
||||
try:
|
||||
assert test_module_res[0][0] == 12 # + operator
|
||||
assert test_module_res[0][1] == 20 # * operator
|
||||
# Now modify content of test function
|
||||
preprocess_functions(FUNC3_PATH, FUNC4_PATH)
|
||||
# Test that it doesn't work without calling reload
|
||||
test_module_res = execute_and_fetch_all(cursor, "CALL new_test_module.test(10, 2) YIELD * RETURN *;")
|
||||
assert test_module_res[0][0] == 12 # + operator
|
||||
assert test_module_res[0][1] == 20 # * operator
|
||||
# Reload module
|
||||
execute_and_fetch_all(cursor, "CALL mg.load('new_test_module');")
|
||||
test_module_res = execute_and_fetch_all(cursor, "CALL new_test_module.test(10, 2) YIELD * RETURN *;")
|
||||
assert test_module_res[0][0] == 8 # - operator
|
||||
assert test_module_res[0][1] == 5 # / operator
|
||||
finally:
|
||||
# Revert to the original state for the consistency
|
||||
postprocess_functions(FUNC3_PATH, FUNC4_PATH)
|
||||
execute_and_fetch_all(cursor, "CALL mg.load('new_test_module');")
|
||||
|
||||
|
||||
def test_mg_load_all_reload_submodule_root_utils():
|
||||
"""Tests whether mg.load_all reloads content of some submodule code"""
|
||||
cursor = connect().cursor()
|
||||
# First do a simple experiment
|
||||
test_module_res = execute_and_fetch_all(cursor, "CALL new_test_module.test(10, 2) YIELD * RETURN *;")
|
||||
try:
|
||||
assert test_module_res[0][0] == 12 # + operator
|
||||
assert test_module_res[0][1] == 20 # * operator
|
||||
# Now modify content of test function
|
||||
preprocess_functions(FUNC3_PATH, FUNC4_PATH)
|
||||
# Test that it doesn't work without calling reload
|
||||
test_module_res = execute_and_fetch_all(cursor, "CALL new_test_module.test(10, 2) YIELD * RETURN *;")
|
||||
assert test_module_res[0][0] == 12 # + operator
|
||||
assert test_module_res[0][1] == 20 # * operator
|
||||
# Reload module
|
||||
execute_and_fetch_all(cursor, "CALL mg.load_all();")
|
||||
test_module_res = execute_and_fetch_all(cursor, "CALL new_test_module.test(10, 2) YIELD * RETURN *;")
|
||||
assert test_module_res[0][0] == 8 # - operator
|
||||
assert test_module_res[0][1] == 5 # / operator
|
||||
finally:
|
||||
# Revert to the original state for the consistency
|
||||
postprocess_functions(FUNC3_PATH, FUNC4_PATH)
|
||||
execute_and_fetch_all(cursor, "CALL mg.load_all();")
|
||||
|
||||
|
||||
def test_mg_load_reload_submodule():
|
||||
"""Tests whether mg.load reloads content of some submodule code."""
|
||||
cursor = connect().cursor()
|
||||
# First do a simple experiment
|
||||
test_module_res = execute_and_fetch_all(cursor, "CALL test_module.test(10, 2) YIELD * RETURN *;")
|
||||
try:
|
||||
assert test_module_res[0][0] == 12 # + operator
|
||||
assert test_module_res[0][1] == 20 # * operator
|
||||
# Now modify content of test function
|
||||
preprocess_functions(FUNC1_PATH, FUNC2_PATH)
|
||||
# Test that it doesn't work without calling reload
|
||||
test_module_res = execute_and_fetch_all(cursor, "CALL test_module.test(10, 2) YIELD * RETURN *;")
|
||||
assert test_module_res[0][0] == 12 # + operator
|
||||
assert test_module_res[0][1] == 20 # * operator
|
||||
# Reload module
|
||||
execute_and_fetch_all(cursor, "CALL mg.load('test_module');")
|
||||
test_module_res = execute_and_fetch_all(cursor, "CALL test_module.test(10, 2) YIELD * RETURN *;")
|
||||
assert test_module_res[0][0] == 8 # - operator
|
||||
assert test_module_res[0][1] == 5 # / operator
|
||||
finally:
|
||||
# Revert to the original state for the consistency
|
||||
postprocess_functions(FUNC1_PATH, FUNC2_PATH)
|
||||
execute_and_fetch_all(cursor, "CALL mg.load('test_module');")
|
||||
|
||||
|
||||
def test_mg_load_all_reload_submodule():
|
||||
"""Tests whether mg.load_all reloads content of some submodule code"""
|
||||
cursor = connect().cursor()
|
||||
# First do a simple experiment
|
||||
test_module_res = execute_and_fetch_all(cursor, "CALL test_module.test(10, 2) YIELD * RETURN *;")
|
||||
try:
|
||||
assert test_module_res[0][0] == 12 # + operator
|
||||
assert test_module_res[0][1] == 20 # * operator
|
||||
# Now modify content of test function
|
||||
preprocess_functions(FUNC1_PATH, FUNC2_PATH)
|
||||
# Test that it doesn't work without calling reload
|
||||
test_module_res = execute_and_fetch_all(cursor, "CALL test_module.test(10, 2) YIELD * RETURN *;")
|
||||
assert test_module_res[0][0] == 12 # + operator
|
||||
assert test_module_res[0][1] == 20 # * operator
|
||||
# Reload module
|
||||
execute_and_fetch_all(cursor, "CALL mg.load_all();")
|
||||
test_module_res = execute_and_fetch_all(cursor, "CALL test_module.test(10, 2) YIELD * RETURN *;")
|
||||
assert test_module_res[0][0] == 8 # - operator
|
||||
assert test_module_res[0][1] == 5 # / operator
|
||||
finally:
|
||||
# Revert to the original state for the consistency
|
||||
postprocess_functions(FUNC1_PATH, FUNC2_PATH)
|
||||
execute_and_fetch_all(cursor, "CALL mg.load_all();")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-rA"]))
|
||||
@@ -1,14 +0,0 @@
|
||||
test_reload_query_module: &test_reload_query_module
|
||||
cluster:
|
||||
main:
|
||||
args: ["--bolt-port", "7687", "--log-level=TRACE", "--also-log-to-stderr"]
|
||||
log_file: "py-query-modules-reloading-e2e.log"
|
||||
setup_queries: []
|
||||
validation_queries: []
|
||||
|
||||
workloads:
|
||||
- name: "test-reload-query-module" # should be the same as the python file
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
proc: "tests/e2e/python_query_modules_reloading/procedures/"
|
||||
args: ["python_query_modules_reloading/test_reload_query_module.py"]
|
||||
<<: *test_reload_query_module
|
||||
@@ -97,22 +97,6 @@ Feature: Update clauses
|
||||
| a | b | c |
|
||||
| (:q{x: 'y'}) | [:X{x: 'y'}] | ({y: 't'}) |
|
||||
|
||||
Scenario: Match node set properties without return
|
||||
Given an empty graph
|
||||
And having executed
|
||||
"""
|
||||
CREATE (n1:Node {test: 1})
|
||||
CREATE (n2:Node {test: 2})
|
||||
CREATE (n3:Node {test: 3})
|
||||
"""
|
||||
When executing query:
|
||||
"""
|
||||
MATCH (n:Node)
|
||||
SET n.test = 4
|
||||
"""
|
||||
Then the result should be empty
|
||||
|
||||
|
||||
Scenario: Match, set properties from relationship to relationship, return test
|
||||
Given an empty graph
|
||||
When executing query:
|
||||
|
||||
@@ -24,9 +24,3 @@ add_subdirectory(mg_import_csv)
|
||||
|
||||
# license_check test binaries
|
||||
add_subdirectory(license_info)
|
||||
|
||||
#environment variable check binaries
|
||||
add_subdirectory(env_variable_check)
|
||||
|
||||
#flag check binaries
|
||||
add_subdirectory(flag_check)
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
set(target_name memgraph__integration__env_variable_check)
|
||||
set(tester_target_name ${target_name}__tester)
|
||||
set(env_check_target_name ${target_name}__check)
|
||||
|
||||
add_executable(${tester_target_name} tester.cpp)
|
||||
set_target_properties(${tester_target_name} PROPERTIES OUTPUT_NAME tester)
|
||||
target_link_libraries(${tester_target_name} mg-communication)
|
||||
@@ -1,151 +0,0 @@
|
||||
#!/usr/bin/python3 -u
|
||||
|
||||
# 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 argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
SCRIPT_DIR = Path(__file__).absolute()
|
||||
PROJECT_DIR = SCRIPT_DIR.parents[3]
|
||||
|
||||
|
||||
def wait_for_server(port, delay=0.1):
|
||||
cmd = ["nc", "-z", "-w", "1", "127.0.0.1", str(port)]
|
||||
while subprocess.call(cmd) != 0:
|
||||
time.sleep(0.01)
|
||||
time.sleep(delay)
|
||||
|
||||
|
||||
def execute_tester(
|
||||
binary: str,
|
||||
queries: List[str],
|
||||
should_fail: bool = False,
|
||||
failure_message: str = "",
|
||||
username: str = "",
|
||||
password: str = "",
|
||||
check_failure: bool = True,
|
||||
) -> None:
|
||||
args = [binary, "--username", username, "--password", password]
|
||||
if should_fail:
|
||||
args.append("--should-fail")
|
||||
if failure_message:
|
||||
args.extend(["--failure-message", failure_message])
|
||||
if check_failure:
|
||||
args.append("--check-failure")
|
||||
args.extend(queries)
|
||||
subprocess.run(args).check_returncode()
|
||||
|
||||
|
||||
def start_memgraph(memgraph_args: List[any]) -> subprocess:
|
||||
memgraph = subprocess.Popen(list(map(str, memgraph_args)))
|
||||
time.sleep(0.1)
|
||||
assert memgraph.poll() is None, "Memgraph process died prematurely!"
|
||||
wait_for_server(7687)
|
||||
|
||||
return memgraph
|
||||
|
||||
|
||||
def execute_with_user(queries):
|
||||
return execute_tester(
|
||||
tester_binary, queries, should_fail=False, check_failure=True, username="admin", password="admin"
|
||||
)
|
||||
|
||||
|
||||
def cleanup(memgraph):
|
||||
if memgraph.poll() is None:
|
||||
memgraph.terminate()
|
||||
assert memgraph.wait() == 0, "Memgraph process didn't exit cleanly!"
|
||||
|
||||
|
||||
def execute_without_user(queries, should_fail=False, failure_message="", check_failure=True):
|
||||
return execute_tester(tester_binary, queries, should_fail, failure_message, "", "", check_failure)
|
||||
|
||||
|
||||
def test_without_env_variables(memgraph_args: List[any]) -> None:
|
||||
memgraph = start_memgraph(memgraph_args)
|
||||
execute_without_user(["MATCH (n) RETURN n"], False)
|
||||
cleanup(memgraph)
|
||||
|
||||
|
||||
def test_with_user_password_env_variables(memgraph_args: List[any]) -> None:
|
||||
os.environ["MEMGRAPH_USER"] = "admin"
|
||||
os.environ["MEMGRAPH_PASSWORD"] = "admin"
|
||||
memgraph = start_memgraph(memgraph_args)
|
||||
execute_with_user(["MATCH (n) RETURN n"])
|
||||
execute_without_user(["MATCH (n) RETURN n"], True, "Handshake with the server failed!", True)
|
||||
cleanup(memgraph)
|
||||
del os.environ["MEMGRAPH_USER"]
|
||||
del os.environ["MEMGRAPH_PASSWORD"]
|
||||
|
||||
|
||||
def test_with_passfile_env_variable(storage_directory: tempfile.TemporaryDirectory, memgraph_args: List[any]) -> None:
|
||||
with open(os.path.join(storage_directory.name, "passfile.txt"), "w") as temp_file:
|
||||
temp_file.write("admin:admin")
|
||||
|
||||
os.environ["MEMGRAPH_PASSFILE"] = storage_directory.name + "/passfile.txt"
|
||||
memgraph = start_memgraph(memgraph_args)
|
||||
execute_with_user(["MATCH (n) RETURN n"])
|
||||
execute_without_user(["MATCH (n) RETURN n"], True, "Handshake with the server failed!", True)
|
||||
del os.environ["MEMGRAPH_PASSFILE"]
|
||||
cleanup(memgraph)
|
||||
|
||||
|
||||
def execute_test(memgraph_binary: str, tester_binary: str) -> None:
|
||||
storage_directory = tempfile.TemporaryDirectory()
|
||||
memgraph_args = [memgraph_binary, "--data-directory", storage_directory.name]
|
||||
|
||||
return_to_prev_state = {}
|
||||
if "MEMGRAPH_USER" in os.environ:
|
||||
return_to_prev_state["MEMGRAPH_USER"] = os.environ["MEMGRAPH_USER"]
|
||||
del os.environ["MG_USER"]
|
||||
if "MEMGRAPH_PASSWORD" in os.environ:
|
||||
return_to_prev_state["MEMGRAPH_PASSWORD"] = os.environ["MEMGRAPH_PASSWORD"]
|
||||
del os.environ["MEMGRAPH_PASSWORD"]
|
||||
if "MEMGRAPH_PASSFILE" in os.environ:
|
||||
return_to_prev_state["MEMGRAPH_PASSFILE"] = os.environ["MEMGRAPH_PASSFILE"]
|
||||
del os.environ["MEMGRAPH_PASSFILE"]
|
||||
|
||||
# Start the memgraph binary
|
||||
|
||||
# Run the test with all combinations of permissions
|
||||
print("\033[1;36m~~ Starting env variable check test ~~\033[0m")
|
||||
test_without_env_variables(memgraph_args)
|
||||
test_with_user_password_env_variables(memgraph_args)
|
||||
test_with_passfile_env_variable(storage_directory, memgraph_args)
|
||||
print("\033[1;36m~~ Ended env variable check test ~~\033[0m")
|
||||
|
||||
if "MEMGRAPH_USER" in return_to_prev_state:
|
||||
os.environ["MEMGRAPH_USER"] = return_to_prev_state["MEMGRAPH_USER"]
|
||||
if "MEMGRAPH_PASSWORD" in return_to_prev_state:
|
||||
os.environ["MEMGRAPH_PASSWORD"] = return_to_prev_state["MEMGRAPH_PASSWORD"]
|
||||
if "MEMGRAPH_PASSFILE" in return_to_prev_state:
|
||||
os.environ["MEMGRAPH_PASSFILE"] = return_to_prev_state["MEMGRAPH_PASSFILE"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
memgraph_binary = os.path.join(PROJECT_DIR, "build", "memgraph")
|
||||
tester_binary = os.path.join(PROJECT_DIR, "build", "tests", "integration", "env_variable_check", "tester")
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--memgraph", default=memgraph_binary)
|
||||
parser.add_argument("--tester", default=tester_binary)
|
||||
args = parser.parse_args()
|
||||
|
||||
execute_test(args.memgraph, args.tester)
|
||||
|
||||
sys.exit(0)
|
||||
@@ -1,94 +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.
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "communication/bolt/client.hpp"
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "io/network/utils.hpp"
|
||||
|
||||
DEFINE_string(address, "127.0.0.1", "Server address");
|
||||
DEFINE_int32(port, 7687, "Server port");
|
||||
DEFINE_string(username, "", "Username for the database");
|
||||
DEFINE_string(password, "", "Password for the database");
|
||||
DEFINE_bool(use_ssl, false, "Set to true to connect with SSL to the server.");
|
||||
|
||||
DEFINE_bool(check_failure, false, "Set to true to enable failure checking.");
|
||||
DEFINE_bool(should_fail, false, "Set to true to expect a failure.");
|
||||
DEFINE_string(failure_message, "", "Set to the expected failure message.");
|
||||
|
||||
int ProcessException(const std::string &exception_message) {
|
||||
if (FLAGS_should_fail) {
|
||||
if (!FLAGS_failure_message.empty() && exception_message != FLAGS_failure_message) {
|
||||
LOG_FATAL(
|
||||
"The query should have failed with an error message of '{}'' but "
|
||||
"instead it failed with '{}'",
|
||||
FLAGS_failure_message, exception_message);
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
LOG_FATAL(
|
||||
"The query shoudn't have failed but it failed with an "
|
||||
"error message '{}'",
|
||||
exception_message);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Executes queries passed as positional arguments and verifies whether they
|
||||
* succeeded, failed, failed with a specific error message or executed without a
|
||||
* specific error occurring.
|
||||
*/
|
||||
int main(int argc, char **argv) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
memgraph::communication::SSLInit sslInit;
|
||||
|
||||
memgraph::io::network::Endpoint endpoint(memgraph::io::network::ResolveHostname(FLAGS_address), FLAGS_port);
|
||||
|
||||
memgraph::communication::ClientContext context(FLAGS_use_ssl);
|
||||
memgraph::communication::bolt::Client client(context);
|
||||
|
||||
try {
|
||||
client.Connect(endpoint, FLAGS_username, FLAGS_password);
|
||||
} catch (const memgraph::utils::BasicException &e) {
|
||||
return ProcessException(e.what());
|
||||
}
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string query(argv[i]);
|
||||
try {
|
||||
client.Execute(query, {});
|
||||
} catch (const memgraph::communication::bolt::ClientQueryException &e) {
|
||||
if (!FLAGS_check_failure) {
|
||||
if (!FLAGS_failure_message.empty() && e.what() == FLAGS_failure_message) {
|
||||
LOG_FATAL(
|
||||
"The query should have succeeded or failed with an error "
|
||||
"message that isn't equal to '{}' but it failed with that error "
|
||||
"message",
|
||||
FLAGS_failure_message);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!ProcessException(e.what())) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (!FLAGS_check_failure) continue;
|
||||
if (FLAGS_should_fail) {
|
||||
LOG_FATAL(
|
||||
"The query should have failed but instead it executed "
|
||||
"successfully!");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
set(target_name memgraph__integration__flag_check)
|
||||
set(tester_target_name ${target_name}__tester)
|
||||
set(flag_check_target_name ${target_name}__flag_check)
|
||||
|
||||
add_executable(${tester_target_name} tester.cpp)
|
||||
set_target_properties(${tester_target_name} PROPERTIES OUTPUT_NAME tester)
|
||||
target_link_libraries(${tester_target_name} mg-communication)
|
||||
|
||||
add_executable(${flag_check_target_name} flag_check.cpp)
|
||||
set_target_properties(${flag_check_target_name} PROPERTIES OUTPUT_NAME flag_check)
|
||||
target_link_libraries(${flag_check_target_name} mg-communication)
|
||||
@@ -1,59 +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.
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "communication/bolt/client.hpp"
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "io/network/utils.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
DEFINE_string(address, "127.0.0.1", "Server address");
|
||||
DEFINE_int32(port, 7687, "Server port");
|
||||
DEFINE_string(username, "admin", "Username for the database");
|
||||
DEFINE_string(password, "admin", "Password for the database");
|
||||
DEFINE_bool(use_ssl, false, "Set to true to connect with SSL to the server.");
|
||||
|
||||
/**
|
||||
* Verifies that user 'user' has privileges that are given as positional
|
||||
* arguments.
|
||||
*/
|
||||
int main(int argc, char **argv) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
memgraph::communication::SSLInit sslInit;
|
||||
|
||||
memgraph::io::network::Endpoint endpoint(memgraph::io::network::ResolveHostname(FLAGS_address), FLAGS_port);
|
||||
|
||||
memgraph::communication::ClientContext context(FLAGS_use_ssl);
|
||||
memgraph::communication::bolt::Client client(context);
|
||||
|
||||
client.Connect(endpoint, FLAGS_username, FLAGS_password);
|
||||
|
||||
try {
|
||||
std::string query(argv[1]);
|
||||
auto ret = client.Execute(query, {});
|
||||
uint64_t count_got = ret.records.size();
|
||||
|
||||
if (count_got != std::atoi(argv[2])) {
|
||||
LOG_FATAL("Expected the record to have {} entries but they had {} entries!", argv[2], count_got);
|
||||
}
|
||||
|
||||
} catch (const memgraph::communication::bolt::ClientQueryException &e) {
|
||||
LOG_FATAL(
|
||||
"The query shoudn't have failed but it failed with an "
|
||||
"error message '{}', {}",
|
||||
e.what(), argv[0]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
#!/usr/bin/python3 -u
|
||||
|
||||
# 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 argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
PROJECT_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", "..", ".."))
|
||||
|
||||
|
||||
def wait_for_server(port: int, delay: float = 0.1) -> float:
|
||||
cmd = ["nc", "-z", "-w", "1", "127.0.0.1", str(port)]
|
||||
while subprocess.call(cmd) != 0:
|
||||
time.sleep(0.01)
|
||||
time.sleep(delay)
|
||||
|
||||
|
||||
def execute_tester(
|
||||
binary: str,
|
||||
queries: List[str],
|
||||
should_fail: bool = False,
|
||||
failure_message: str = "",
|
||||
username: str = "",
|
||||
password: str = "",
|
||||
check_failure: bool = True,
|
||||
) -> None:
|
||||
args = [binary, "--username", username, "--password", password]
|
||||
if should_fail:
|
||||
args.append("--should-fail")
|
||||
if failure_message:
|
||||
args.extend(["--failure-message", failure_message])
|
||||
if check_failure:
|
||||
args.append("--check-failure")
|
||||
args.extend(queries)
|
||||
subprocess.run(args).check_returncode()
|
||||
|
||||
|
||||
def execute_flag_check(binary: str, queries: List[str], expected: int, username: str = "", password: str = "") -> None:
|
||||
args = [binary, "--username", username, "--password", password]
|
||||
|
||||
args.extend(queries)
|
||||
args.append(str(expected))
|
||||
|
||||
subprocess.run(args).check_returncode()
|
||||
|
||||
|
||||
def start_memgraph(memgraph_args: List[any]) -> subprocess:
|
||||
memgraph = subprocess.Popen(list(map(str, memgraph_args)))
|
||||
time.sleep(0.1)
|
||||
assert memgraph.poll() is None, "Memgraph process died prematurely!"
|
||||
wait_for_server(7687)
|
||||
|
||||
return memgraph
|
||||
|
||||
|
||||
def execute_with_user(tester_binary: str, queries: List[str]) -> None:
|
||||
return execute_tester(
|
||||
tester_binary, queries, should_fail=False, check_failure=True, username="admin", password="admin"
|
||||
)
|
||||
|
||||
|
||||
def execute_without_user(
|
||||
tester_binary: str,
|
||||
queries: List[str],
|
||||
should_fail: bool = False,
|
||||
failure_message: str = "",
|
||||
check_failure: bool = True,
|
||||
) -> None:
|
||||
return execute_tester(tester_binary, queries, should_fail, failure_message, "", "", check_failure)
|
||||
|
||||
|
||||
def cleanup(memgraph: subprocess):
|
||||
if memgraph.poll() is None:
|
||||
memgraph.terminate()
|
||||
assert memgraph.wait() == 0, "Memgraph process didn't exit cleanly!"
|
||||
|
||||
|
||||
def test_without_any_files(tester_binary: str, memgraph_args: List[str]):
|
||||
memgraph = start_memgraph(memgraph_args)
|
||||
execute_without_user(tester_binary, ["MATCH (n) RETURN n"], False)
|
||||
cleanup(memgraph)
|
||||
|
||||
|
||||
def test_init_file(tester_binary: str, memgraph_args: List[str]):
|
||||
memgraph = start_memgraph(memgraph_args)
|
||||
execute_with_user(tester_binary, ["MATCH (n) RETURN n"])
|
||||
execute_without_user(tester_binary, ["MATCH (n) RETURN n"], True, "Handshake with the server failed!", True)
|
||||
cleanup(memgraph)
|
||||
|
||||
|
||||
def test_init_data_file(flag_checker_binary: str, memgraph_args: List[str]):
|
||||
memgraph = start_memgraph(memgraph_args)
|
||||
execute_flag_check(flag_checker_binary, ["MATCH (n) RETURN n"], 2, "user", "user")
|
||||
cleanup(memgraph)
|
||||
|
||||
|
||||
def test_init_and_init_data_file(flag_checker_binary: str, tester_binary: str, memgraph_args: List[str]):
|
||||
memgraph = start_memgraph(memgraph_args)
|
||||
execute_with_user(tester_binary, ["MATCH (n) RETURN n"])
|
||||
execute_without_user(tester_binary, ["MATCH (n) RETURN n"], True, "Handshake with the server failed!", True)
|
||||
execute_flag_check(flag_checker_binary, ["MATCH (n) RETURN n"], 2, "user", "user")
|
||||
cleanup(memgraph)
|
||||
|
||||
|
||||
def execute_test(memgraph_binary: str, tester_binary: str, flag_checker_binary: str) -> None:
|
||||
storage_directory = tempfile.TemporaryDirectory()
|
||||
memgraph_args = [memgraph_binary, "--data-directory", storage_directory.name]
|
||||
|
||||
# Start the memgraph binary
|
||||
with open(os.path.join(os.getcwd(), "dummy_init_file.cypherl"), "w") as temp_file:
|
||||
temp_file.write("CREATE USER admin IDENTIFIED BY 'admin';\n")
|
||||
temp_file.write("CREATE USER user IDENTIFIED BY 'user';\n")
|
||||
|
||||
with open(os.path.join(os.getcwd(), "dummy_init_data_file.cypherl"), "w") as temp_file:
|
||||
temp_file.write("CREATE (n:RANDOM) RETURN n;\n")
|
||||
temp_file.write("CREATE (n:RANDOM {name:'1'}) RETURN n;\n")
|
||||
|
||||
# Run the test with all combinations of permissions
|
||||
print("\033[1;36m~~ Starting env variable check test ~~\033[0m")
|
||||
test_without_any_files(tester_binary, memgraph_args)
|
||||
memgraph_args_with_init_file = memgraph_args + [
|
||||
"--init-file",
|
||||
os.path.join(os.getcwd(), "dummy_init_file.cypherl"),
|
||||
]
|
||||
test_init_file(tester_binary, memgraph_args_with_init_file)
|
||||
memgraph_args_with_init_data_file = memgraph_args + [
|
||||
"--init-data-file",
|
||||
os.path.join(os.getcwd(), "dummy_init_data_file.cypherl"),
|
||||
]
|
||||
|
||||
test_init_data_file(flag_checker_binary, memgraph_args_with_init_data_file)
|
||||
memgraph_args_with_init_file_and_init_data_file = memgraph_args + [
|
||||
"--init-file",
|
||||
os.path.join(os.getcwd(), "dummy_init_file.cypherl"),
|
||||
"--init-data-file",
|
||||
os.path.join(os.getcwd(), "dummy_init_data_file.cypherl"),
|
||||
]
|
||||
test_init_and_init_data_file(flag_checker_binary, tester_binary, memgraph_args_with_init_file_and_init_data_file)
|
||||
print("\033[1;36m~~ Ended env variable check test ~~\033[0m")
|
||||
|
||||
os.remove(os.path.join(os.getcwd(), "dummy_init_data_file.cypherl"))
|
||||
os.remove(os.path.join(os.getcwd(), "dummy_init_file.cypherl"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
memgraph_binary = os.path.join(PROJECT_DIR, "build", "memgraph")
|
||||
tester_binary = os.path.join(PROJECT_DIR, "build", "tests", "integration", "flag_check", "tester")
|
||||
flag_checker_binary = os.path.join(PROJECT_DIR, "build", "tests", "integration", "flag_check", "flag_check")
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--memgraph", default=memgraph_binary)
|
||||
parser.add_argument("--tester", default=tester_binary)
|
||||
parser.add_argument("--flag_checker", default=flag_checker_binary)
|
||||
args = parser.parse_args()
|
||||
|
||||
execute_test(args.memgraph, args.tester, args.flag_checker)
|
||||
|
||||
sys.exit(0)
|
||||
@@ -1,94 +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.
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "communication/bolt/client.hpp"
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "io/network/utils.hpp"
|
||||
|
||||
DEFINE_string(address, "127.0.0.1", "Server address");
|
||||
DEFINE_int32(port, 7687, "Server port");
|
||||
DEFINE_string(username, "", "Username for the database");
|
||||
DEFINE_string(password, "", "Password for the database");
|
||||
DEFINE_bool(use_ssl, false, "Set to true to connect with SSL to the server.");
|
||||
|
||||
DEFINE_bool(check_failure, false, "Set to true to enable failure checking.");
|
||||
DEFINE_bool(should_fail, false, "Set to true to expect a failure.");
|
||||
DEFINE_string(failure_message, "", "Set to the expected failure message.");
|
||||
|
||||
int ProcessException(const std::string &exception_message) {
|
||||
if (FLAGS_should_fail) {
|
||||
if (!FLAGS_failure_message.empty() && exception_message != FLAGS_failure_message) {
|
||||
LOG_FATAL(
|
||||
"The query should have failed with an error message of '{}'' but "
|
||||
"instead it failed with '{}'",
|
||||
FLAGS_failure_message, exception_message);
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
LOG_FATAL(
|
||||
"The query shoudn't have failed but it failed with an "
|
||||
"error message '{}'",
|
||||
exception_message);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Executes queries passed as positional arguments and verifies whether they
|
||||
* succeeded, failed, failed with a specific error message or executed without a
|
||||
* specific error occurring.
|
||||
*/
|
||||
int main(int argc, char **argv) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
memgraph::communication::SSLInit sslInit;
|
||||
|
||||
memgraph::io::network::Endpoint endpoint(memgraph::io::network::ResolveHostname(FLAGS_address), FLAGS_port);
|
||||
|
||||
memgraph::communication::ClientContext context(FLAGS_use_ssl);
|
||||
memgraph::communication::bolt::Client client(context);
|
||||
|
||||
try {
|
||||
client.Connect(endpoint, FLAGS_username, FLAGS_password);
|
||||
} catch (const memgraph::utils::BasicException &e) {
|
||||
return ProcessException(e.what());
|
||||
}
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string query(argv[i]);
|
||||
try {
|
||||
client.Execute(query, {});
|
||||
} catch (const memgraph::communication::bolt::ClientQueryException &e) {
|
||||
if (!FLAGS_check_failure) {
|
||||
if (!FLAGS_failure_message.empty() && e.what() == FLAGS_failure_message) {
|
||||
LOG_FATAL(
|
||||
"The query should have succeeded or failed with an error "
|
||||
"message that isn't equal to '{}' but it failed with that error "
|
||||
"message",
|
||||
FLAGS_failure_message);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!ProcessException(e.what())) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (!FLAGS_check_failure) continue;
|
||||
if (FLAGS_should_fail) {
|
||||
LOG_FATAL(
|
||||
"The query should have failed but instead it executed "
|
||||
"successfully!");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
# :fire: mgBench: Benchmark for graph databases
|
||||
|
||||
## :clipboard: Benchmark Overview
|
||||
|
||||
mgBench is primarily designed to benchmark graph databases. To test graph database performance, this benchmark executes Cypher queries (write, read, update, aggregate, and analyze) on a given dataset. Queries are general and represent a typical workload that would be used to analyze any graph dataset. [BenchGraph](https://memgraph.com/benchgraph/) platform shows the results of running these queries on supported vendors. It shows the overall performance of each system relative to others.
|
||||
|
||||
Three workload types can be executed:
|
||||
- Isolated - Concurrent execution of a single type of query,
|
||||
- Mixed - Concurrent execution of a single type of query mixed with a certain percentage of queries from a designated query group,
|
||||
- Realistic - Concurrent execution of queries from write, read, update and analyze groups.
|
||||
|
||||
Currently, the benchmark is executed on the social media dataset Pokec, available in different sizes. The full list of queries and their grouping is available as [query list](#query-list).
|
||||
|
||||
This methodology is designed to be read from top to bottom to understand what is being tested and how, but feel free to jump to parts that interest you.
|
||||
|
||||
- [:fire: mgBench: Benchmark for graph databases](#fire-mgbench-benchmark-for-graph-databases)
|
||||
- [:clipboard: Benchmark Overview](#clipboard-benchmark-overview)
|
||||
- [:dart: Design goals](#dart-design-goals)
|
||||
- [Reproducibility and validation](#reproducibility-and-validation)
|
||||
- [Database compatibility](#database-compatibility)
|
||||
- [Workloads](#workloads)
|
||||
- [Fine-tuning](#fine-tuning)
|
||||
- [Limitations](#limitations)
|
||||
- [:wrench: mgBench](#wrench-mgbench)
|
||||
- [Important files](#important-files)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Running the benchmark](#running-the-benchmark)
|
||||
- [Database conditions](#database-conditions)
|
||||
- [Comparing results](#comparing-results)
|
||||
- [:bar\_chart: Results](#bar_chart-results)
|
||||
- [:books: Datasets](#books-datasets)
|
||||
- [Pokec](#pokec)
|
||||
- [Query list](#query-list)
|
||||
- [:computer: Platform](#computer-platform)
|
||||
- [Intel - HP](#intel---hp)
|
||||
- [:nut\_and\_bolt: Supported databases](#nut_and_bolt-supported-databases)
|
||||
- [Database notes](#database-notes)
|
||||
- [:raised\_hands: Contributions](#raised_hands-contributions)
|
||||
- [:mega: History and Future of mgBench](#mega-history-and-future-of-mgbench)
|
||||
- [History of mgBench](#history-of-mgbench)
|
||||
- [Future of mgBench](#future-of-mgbench)
|
||||
|
||||
## :dart: Design goals
|
||||
|
||||
### Reproducibility and validation
|
||||
|
||||
Running this benchmark is automated, and the code used to run benchmarks is publicly available. You can [run mgBench](#running-the-benchmark) with default settings to validate the results at [BenchGraph platform](https://memgraph.com/benchgraph). The results may differ depending on the hardware, database configuration, and other variables involved in your setup. But if the results you get are significantly different, feel free to [open a GitHub issue](https://github.com/memgraph/memgraph/issues).
|
||||
|
||||
In the future, the project will be expanded to include more platforms to see how systems perform on different OS and hardware configurations. If you are interested in what will be added and tested, read the section about [the future of mgBench](#future-of-mgbench)
|
||||
|
||||
|
||||
### Database compatibility
|
||||
|
||||
At the moment, support for graph databases is limited. To run the benchmarks, the graph database must support Cypher query language and the Bolt protocol.
|
||||
|
||||
Using Cypher ensures that executed queries are identical or similar on every supported system. Possible differences are noted in [database notes](#database-notes). A single C++ client queries all database systems, and it is based on the Bolt protocol. Using a single client ensures minimal performance penalties from the client side and ensures fairness across different vendors.
|
||||
|
||||
If your database supports the given requirements, feel free to contribute and add your database to mgBench.
|
||||
If your database does not support the mentioned requirements, follow the project because support for other languages and protocols in graph database space will be added.
|
||||
|
||||
|
||||
### Workloads
|
||||
Running queries as standalone units is simple and relatively easy to measure, but vendors often apply various caching and pre-aggregations that influence the results in these kinds of scenarios. Results from running single queries can hint at the database's general performance, but in real life, a database is queried by multiple clients from multiple sides. That is why the mgBench client supports the consecutive execution of various queries. Concurrently writing, reading, updating and executing aggregational and analytical queries provides a better view of overall system performance than executing and measuring a single query. Queries that the mgBench executes are grouped into 5 groups - write, read, update, aggregate and analytical.
|
||||
|
||||
The [BenchGraph platform](https://memgraph.com/benchgraph) shows results made by mgBench by executing three types of workloads:
|
||||
- ***Isolated workload***
|
||||
- ***Mixed workload***
|
||||
- ***Realistic workload***
|
||||
|
||||
Each of these workloads has a specific purpose:
|
||||
|
||||
***Isolated*** workload is the simplest test. An isolated workload goes through all the queries individually, concurrently executing a single query a predefined number of times. It is similar to executing a single query and measuring time but more complex due to concurrency. How many times a specific query will be executed depends on the approximation of the query’s latency. If a query is slower, it will be executed fewer times, if a query is faster, it will be executed more times. The approximation is based on the duration of execution for several concurrent threads, and it varies between vendors.
|
||||
If a query takes arguments, the argument value is changed for each execution. Arguments are generated non-randomly, so each vendor gets the same sequence of queries with the same arguments. This enables a deterministic workload for both vendors.
|
||||
The good thing about isolated workload is that it yields a better picture of single query performance. There is also a negative side, executing the same queries multiple times can trigger strong results caching on the vendor's side, which can result in false query times.
|
||||
|
||||
|
||||
***Mixed*** workload executes a fixed number of queries that read, update, aggregate, or analyze the data concurrently with a certain percentage of write queries because writing from the database can prevent aggressive caching and thus represent a more realistic performance of a single query. The negative side is that there is an added influence of write performance on the results. Currently, mgBench client does not support per-thread performance measurements, but this will be added in future iterations.
|
||||
|
||||
|
||||
***Realistic*** workload represents real-life use cases because queries write, read, update, and perform analytics in a mixed ratio like they would in real projects. The test executes a fixed number of queries, the distribution of which is defined by defining a percentage of queries performing one of four operations. The queries are selected non-randomly, so the workload is identical between different vendors. As with the rest of the workloads, all queries are executed concurrently.
|
||||
|
||||
### Fine-tuning
|
||||
|
||||
Each database system comes with a wide variety of possible configurations. Changing each of those configuration settings can introduce performance improvements or penalties. The focus of this benchmark is "out-of-the-box" performance without fine-tuning with the goal of having the fairest possible comparison. Fine-tuning can make some systems perform magnitudes faster, but this makes general benchmark systems hard to manage because all systems are configured differently, and fine-tuning requires vendor DB experts.
|
||||
|
||||
Some configurational changes are necessary for test execution and are not considered fine-tuning. For example, configuring the database to avoid Bolt client login is valid since the tests are not performed under any type of authorization. All non-default configurations are mentioned in [database notes](#database-notes)
|
||||
|
||||
### Limitations
|
||||
|
||||
Benchmarking different systems is challenging because the setup, environment, queries, workload, and dataset can benefit specific database vendors. Each vendor may have a particularly strong use-case scenario. This benchmark aims to be neutral and fair to all database vendors. Acknowledging some of the current limitations can help understand the issues you might notice:
|
||||
1. mgBench measures and tracks just a tiny subset of everything that can be tracked and compared during testing. Active benchmarking is strenuous because it requires a lot of time to set up and validate. Passive benchmarking is much faster to iterate on but can have a few bugs.
|
||||
2. Datasets and queries used for testing are simple. Datasets and queries in real-world environments can become quite complex. To avoid Cypher specifics, mgBench uses simple queries of different variates. Future versions will include more complex datasets and queries.
|
||||
3. The scale of the dataset used is miniature for production environments. Production environments can have up to trillions of nodes and edges.
|
||||
Query results are not verified or important. The queries might return different results, but only the performance is measured, not correctness.
|
||||
4. All tests are performed on single-node databases.
|
||||
5. Architecturally different systems can be set up and measured biasedly.
|
||||
|
||||
|
||||
## :wrench: mgBench
|
||||
### Important files
|
||||
|
||||
Listed below are the main scripts used to run the benchmarks:
|
||||
|
||||
- `benchmark.py` - Script that runs the queries and workloads.
|
||||
- `datasets.py` - Script that handles datasets and queries for workloads.
|
||||
- `runners.py` - Script holding the configuration for different DB vendors.
|
||||
- `client.cpp` - Client for querying the database.
|
||||
- `graph_bench.py` - Script that starts all predefined and custom-defined workloads.
|
||||
-` compare_results.py` - Script that visually compares benchmark results.
|
||||
|
||||
Except for these scripts, the project also includes dataset files and index configuration files. Once the first test is executed, those files can be located in the newly generated .cache folder.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
To execute a benchmark, you need to download a binary version of supported databases and install Python on your system. Each database vendor can depend on external dependencies, such as Cmake, JVM, etc., so make sure to check specific vendor prerequisites.
|
||||
|
||||
### Running the benchmark
|
||||
To run benchmarks, use the `graph_bench.py` script, which calls all the other necessary scripts. You can start the benchmarks by executing the following command:
|
||||
|
||||
```
|
||||
graph_bench.py
|
||||
--vendor memgraph /home/memgraph/binary
|
||||
--dataset-group basic
|
||||
--dataset-size small
|
||||
--realistic 100 30 70 0 0
|
||||
--realistic 100 50 50 0 0
|
||||
--realistic 100 70 30 0 0
|
||||
--realistic 100 30 40 10 20
|
||||
--mixed 100 30 0 0 0 70
|
||||
```
|
||||
|
||||
|
||||
Isolated workload are always executed, and this commands calls for the execution of four realistic workloads with different distribution of queries and one mixed workload on a small size dataset.
|
||||
|
||||
The distribution of queries from write, read, update and aggregate groups are defined in percentages and stated as arguments following the `--realistic` or `--mixed` flags.
|
||||
|
||||
In the example of `--realistic 100 30 40 10 20` the distribution is as follows:
|
||||
|
||||
- 100 - The number of queries to be executed.
|
||||
- 30 - The percentage of write queries to be executed.
|
||||
- 40 - The percentage of read queries to be executed.
|
||||
- 10 - The percentage of update queries to be executed.
|
||||
- 20 - The percentage of analytical queries to be executed.
|
||||
|
||||
|
||||
For `--mixed` workload argument, the first five parameters are the same, with an addition of a parameter for defining the percentage of individual queries.
|
||||
|
||||
Feel free to add different configurations if you want. Results from the above benchmark run are visible on [BenchGraph platform](https://memgraph.com/benchgraph)
|
||||
|
||||
### Database conditions
|
||||
In a production environment, database query caches are usually warmed from usage or pre-warm procedure to provide the best possible performance. Each workload in mgBench will be executed under the following conditions:
|
||||
- ***Hot run*** - before executing any benchmark query and taking measurements, a set of defined queries is executed to pre-warm the database.
|
||||
- ***Cold run*** - no warm-up was performed on the database before taking benchmark measurements.
|
||||
|
||||
List of queries used for pre-warm up:
|
||||
```
|
||||
CREATE ();
|
||||
CREATE ()-[:TempEdge]->();
|
||||
MATCH (n) RETURN n LIMIT 1;
|
||||
```
|
||||
|
||||
### Comparing results
|
||||
|
||||
Once the benchmark has been run for a single vendor, all the results are saved in appropriately named `.json` files. A summary file is also created for that vendor and it contains all results combined. These summary files are used to compare results against other vendor results via the `compare_results.py` script:
|
||||
|
||||
```
|
||||
compare_results.py
|
||||
--compare
|
||||
“path_to/neo4j_summary.json”
|
||||
“path_to/memgraph_summary.json”
|
||||
--output neo4j_vs_memgraph.html
|
||||
--different-vendors
|
||||
```
|
||||
|
||||
The output is an HTML file with the visual representation of the performance differences between two compared vendors. The first passed summary JSON file is the reference point.
|
||||
|
||||
## :bar_chart: Results
|
||||
Results visible in the HTML file or at [BenchGraph](https://memgraph.com/benchgraph) are throughput, memory, and latency. Database throughput and memory usage directly impact database usability and cost, while the latency of the query shows the base query execution duration.
|
||||
|
||||
***Throughput*** directly defines how performant the database is and how much query traffic it can handle in a fixed time interval. It is expressed in queries per second. In each concurrent workload, execution is split across multiple clients. Each client executes queries concurrently. The duration of total execution is the sum of all concurrent clients' execution duration in seconds. In mgBench, the total count of executed queries and the total duration defines throughput per second across concurrent execution.
|
||||
|
||||
Here is the code snippet from the client, that calculates ***throughput*** and metadata:
|
||||
```
|
||||
// Create and output summary.
|
||||
Metadata final_metadata;
|
||||
uint64_t final_retries = 0;
|
||||
double final_duration = 0.0;
|
||||
for (int i = 0; i < FLAGS_num_workers; ++i) {
|
||||
final_metadata += worker_metadata[i];
|
||||
final_retries += worker_retries[i];
|
||||
final_duration += worker_duration[i];
|
||||
}
|
||||
final_duration /= FLAGS_num_workers;
|
||||
nlohmann::json summary = nlohmann::json::object();
|
||||
summary["count"] = queries.size();
|
||||
summary["duration"] = final_duration;
|
||||
summary["throughput"] = static_cast<double>(queries.size()) / final_duration;
|
||||
summary["retries"] = final_retries;
|
||||
summary["metadata"] = final_metadata.Export();
|
||||
summary["num_workers"] = FLAGS_num_workers;
|
||||
(*stream) << summary.dump() << std::endl;
|
||||
```
|
||||
|
||||
***Memory*** usage is calculated as ***peak RES*** (resident size) memory for each query or workload execution within mgBench. The result includes starting the database, executing the query/workload, and stopping the database. The peak RES is extracted from process PID as VmHVM (peak resident set size) before the process is stopped. The peak memory usage defines the worst-case scenario for a given query or workload, while on average, RAM footprint is lower. Measuring RES over time is supported by `runners.py`. For each vendor, it is possible to add RES tracking across workload execution, but it is not reported in the results.
|
||||
***Latency*** is calculated as the serial execution of 100 identical queries on a single thread. Each query has standard query statistics and tail latency data. The result includes query execution times: max, min, mean, p99, p95, p90, p75, and p50 in seconds.
|
||||
|
||||
Each workload and all the results are based on concurrent query execution, except ***latency***. As stated in [limitations](#limitations) section, mgBench tracks just a subset of resources, but the chapter on [mgBench future](#future-of-mgbench) explains the expansion plans.
|
||||
|
||||
## :books: Datasets
|
||||
Before workload execution, appropriate dataset indexes are set. Each vendor can have a specific syntax for setting up indexes, but those indexes should be schematically as similar as possible.
|
||||
|
||||
After each workload is executed, the database is cleaned, and a new dataset is imported to provide a clean start for the following workload run. When executing isolated and mixed workloads, the database is also restarted after executing each query to minimize the impact on the following query execution.
|
||||
|
||||
### Pokec
|
||||
|
||||
Currently, the only available dataset to run the benchmarks on is the Slovenian social network, Pokec. It’s available in three different sizes, small, medium, and large.
|
||||
- [small](https://s3.eu-west-1.amazonaws.com/deps.memgraph.io/dataset/pokec/benchmark/pokec_small_import.cypher) - vertices 10,000, edges 121,716
|
||||
- [medium](https://s3.eu-west-1.amazonaws.com/deps.memgraph.io/dataset/pokec/benchmark/pokec_medium_import.cypher) - vertices 100,000, edges 1,768,515
|
||||
- [large](https://s3.eu-west-1.amazonaws.com/deps.memgraph.io/dataset/pokec/benchmark/pokec_large.setup.cypher.gz) - vertices 1,632,803, edges 30,622,564.
|
||||
|
||||
Dataset is imported as a CYPHERL file of Cypher queries. Feel free to check dataset links for complete Cypher queries.
|
||||
Once the script is started, a single index is configured on (:User{id}). Only then are queries executed.
|
||||
Index queries for each supported vendor can be downloaded from “https://s3.eu-west-1.amazonaws.com/deps.memgraph.io/dataset/pokec/benchmark/vendor_name.cypher”, just make sure to use the proper vendor name such as `memgraph.cypher`.
|
||||
|
||||
#### Query list
|
||||
|
||||
| |Name | Group | Query |
|
||||
|-|-----| -- | ------------ |
|
||||
|Q1|aggregate | aggregate | MATCH (n:User) RETURN n.age, COUNT(*)|
|
||||
|Q2|aggregate_count | aggregate | MATCH (n) RETURN count(n), count(n.age)|
|
||||
|Q3|aggregate_with_filter | aggregate | MATCH (n:User) WHERE n.age >= 18 RETURN n.age, COUNT(*)|
|
||||
|Q4|min_max_avg | aggregate | MATCH (n) RETURN min(n.age), max(n.age), avg(n.age)|
|
||||
|Q5|expansion_1 | analytical | MATCH (s:User {id: $id})-->(n:User) RETURN n.id|
|
||||
|Q6|expansion_1_with_filter| analytical | MATCH (s:User {id: $id})-->(n:User) WHERE n.age >= 18 RETURN n.id|
|
||||
|Q7|expansion_2| analytical | MATCH (s:User {id: $id})-->()-->(n:User) RETURN DISTINCT n.id|
|
||||
|Q8|expansion_2_with_filter| analytical | MATCH (s:User {id: $id})-->()-->(n:User) WHERE n.age >= 18 RETURN DISTINCT n.id|
|
||||
|Q9|expansion_3| analytical | MATCH (s:User {id: $id})-->()-->()-->(n:User) RETURN DISTINCT n.id|
|
||||
|Q10|expansion_3_with_filter| analytical | MATCH (s:User {id: $id})-->()-->()-->(n:User) WHERE n.age >= 18 RETURN DISTINCT n.id|
|
||||
|Q11|expansion_4| analytical | MATCH (s:User {id: $id})-->()-->()-->()-->(n:User) RETURN DISTINCT n.id|
|
||||
|Q12|expansion_4_with_filter| analytical | MATCH (s:User {id: $id})-->()-->()-->()-->(n:User) WHERE n.age >= 18 RETURN DISTINCT n.id|
|
||||
|Q13|neighbours_2| analytical | MATCH (s:User {id: $id})-[*1..2]->(n:User) RETURN DISTINCT n.id|
|
||||
|Q14|neighbours_2_with_filter| analytical | MATCH (s:User {id: $id})-[*1..2]->(n:User) WHERE n.age >= 18 RETURN DISTINCT n.id|
|
||||
|Q15|neighbours_2_with_data| analytical | MATCH (s:User {id: $id})-[*1..2]->(n:User) RETURN DISTINCT n.id, n|
|
||||
|Q16|neighbours_2_with_data_and_filter| analytical | MATCH (s:User {id: $id})-[*1..2]->(n:User) WHERE n.age >= 18 RETURN DISTINCT n.id, n|
|
||||
|Q17|pattern_cycle| analytical | MATCH (n:User {id: $id})-[e1]->(m)-[e2]->(n) RETURN e1, m, e2|
|
||||
|Q18|pattern_long| analytical | MATCH (n1:User {id: $id})-[e1]->(n2)-[e2]->(n3)-[e3]->(n4)<-[e4]-(n5) RETURN n5 LIMIT 1|
|
||||
|Q19|pattern_short| analytical | MATCH (n:User {id: $id})-[e]->(m) RETURN m LIMIT 1|
|
||||
|Q20|single_edge_write| write | MATCH (n:User {id: $from}), (m:User {id: $to}) WITH n, m CREATE (n)-[e:Temp]->(m) RETURN e|
|
||||
|Q21|single_vertex_write| write |CREATE (n:UserTemp {id : $id}) RETURN n|
|
||||
|Q22|single_vertex_property_update| update | MATCH (n:User {id: $id})-[e]->(m) RETURN m LIMIT 1|
|
||||
|Q23|single_vertex_read| read | MATCH (n:User {id : $id}) RETURN n|
|
||||
|
||||
## :computer: Platform
|
||||
|
||||
Testing on different hardware platforms and cloudVMs is essential for validating benchmark results. Currently, the tests are run on two different platforms.
|
||||
|
||||
### Intel - HP
|
||||
|
||||
- Server: HP DL360 G6
|
||||
- CPU: 2 x Intel Xeon X5650 6C12T @ 2.67GHz
|
||||
- RAM: 144GB
|
||||
- OS: Debian 4.19
|
||||
|
||||
## :nut_and_bolt: Supported databases
|
||||
|
||||
Due to current [database compatibility](link) requirements, the only supported database systems at the moment are:
|
||||
1. Memgraph v2.4
|
||||
2. Neo4j Community Edition v5.1.
|
||||
|
||||
### Database notes
|
||||
|
||||
Running configurations that differ from default configuration:
|
||||
|
||||
- Memgraph - `storage_snapshot_on_exit=true`, `storage_recover_on_startup=true`
|
||||
- Neo4j - `dbms.security.auth_enabled=false`
|
||||
|
||||
## :raised_hands: Contributions
|
||||
|
||||
As previously stated, mgBench will expand, and we will need help adding more datasets, queries, databases, and support for protocols in mgBench. Feel free to contribute to any of those, and throw us a start :star:!
|
||||
|
||||
## :mega: History and Future of mgBench
|
||||
### History of mgBench
|
||||
|
||||
Infrastructure around mgBench was developed to test and maintain Memgraph performance. When critical code is changed, a performance test is run on Memgraph’s CI/CD infrastructure to ensure performance is not impacted. Due to the usage of mgBench for internal testing, some parts of the code are still tightly connected to Memgraph’s CI/CD infrastructure. The remains of that code do not impact benchmark setup or performance in any way.
|
||||
|
||||
### Future of mgBench
|
||||
We have big plans for mgBench infrastructure that refers to the above mentioned [limitations](#limitations). Even though a basic dataset can give a solid indication of performance, adding bigger and more complex datasets is a priority to enable the execution of complex analytical queries.
|
||||
|
||||
Also high on the list is expanding the list of vendors and providing support for different protocols and languages. The goal is to use mgBench to see how well Memgraph performs on various benchmarks tasks and publicly commit to improving.
|
||||
|
||||
mgBench is currently a passive benchmark since resource usage and saturation across execution are not tracked. Sanity checks were performed, but these values are needed to get the full picture after each test. mgBench also deserves its own repository, and it will be decoupled from Memgraph’s testing infrastructure.
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2022 Memgraph Ltd.
|
||||
# Copyright 2021 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
|
||||
@@ -17,20 +17,66 @@ import copy
|
||||
import fnmatch
|
||||
import inspect
|
||||
import json
|
||||
import math
|
||||
import multiprocessing
|
||||
import random
|
||||
import statistics
|
||||
import sys
|
||||
|
||||
import datasets
|
||||
import helpers
|
||||
import log
|
||||
import helpers
|
||||
import runners
|
||||
|
||||
WITH_FINE_GRAINED_AUTHORIZATION = "with_fine_grained_authorization"
|
||||
WITHOUT_FINE_GRAINED_AUTHORIZATION = "without_fine_grained_authorization"
|
||||
|
||||
|
||||
def get_queries(gen, count):
|
||||
# Make the generator deterministic.
|
||||
random.seed(gen.__name__)
|
||||
# Generate queries.
|
||||
ret = []
|
||||
for i in range(count):
|
||||
ret.append(gen())
|
||||
return ret
|
||||
|
||||
|
||||
def match_patterns(dataset, variant, group, test, is_default_variant, patterns):
|
||||
for pattern in patterns:
|
||||
verdict = [fnmatch.fnmatchcase(dataset, pattern[0])]
|
||||
if pattern[1] != "":
|
||||
verdict.append(fnmatch.fnmatchcase(variant, pattern[1]))
|
||||
else:
|
||||
verdict.append(is_default_variant)
|
||||
verdict.append(fnmatch.fnmatchcase(group, pattern[2]))
|
||||
verdict.append(fnmatch.fnmatchcase(test, pattern[3]))
|
||||
if all(verdict):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def filter_benchmarks(generators, patterns):
|
||||
patterns = copy.deepcopy(patterns)
|
||||
for i in range(len(patterns)):
|
||||
pattern = patterns[i].split("/")
|
||||
if len(pattern) > 4 or len(pattern) == 0:
|
||||
raise Exception("Invalid benchmark description '" + pattern + "'!")
|
||||
pattern.extend(["", "*", "*"][len(pattern) - 1 :])
|
||||
patterns[i] = pattern
|
||||
filtered = []
|
||||
for dataset in sorted(generators.keys()):
|
||||
generator, tests = generators[dataset]
|
||||
for variant in generator.VARIANTS:
|
||||
is_default_variant = variant == generator.DEFAULT_VARIANT
|
||||
current = collections.defaultdict(list)
|
||||
for group in tests:
|
||||
for test_name, test_func in tests[group]:
|
||||
if match_patterns(dataset, variant, group, test_name, is_default_variant, patterns):
|
||||
current[group].append((test_name, test_func))
|
||||
if len(current) > 0:
|
||||
filtered.append((generator(variant), dict(current)))
|
||||
return filtered
|
||||
|
||||
|
||||
# Parse options.
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Memgraph benchmark executor.",
|
||||
@@ -43,28 +89,22 @@ parser.add_argument(
|
||||
help="descriptions of benchmarks that should be run; "
|
||||
"multiple descriptions can be specified to run multiple "
|
||||
"benchmarks; the description is specified as "
|
||||
"dataset/variant/group/query; Unix shell-style wildcards "
|
||||
"can be used in the descriptions; variant, group and query "
|
||||
"dataset/variant/group/test; Unix shell-style wildcards "
|
||||
"can be used in the descriptions; variant, group and test "
|
||||
"are optional and they can be left out; the default "
|
||||
"variant is '' which selects the default dataset variant; "
|
||||
"the default group is '*' which selects all groups; the"
|
||||
"default query is '*' which selects all queries",
|
||||
"the default group is '*' which selects all groups; the "
|
||||
"default test is '*' which selects all tests",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--vendor-binary",
|
||||
help="Vendor binary used for benchmarking, by defuault it is memgraph",
|
||||
"--memgraph-binary",
|
||||
default=helpers.get_binary_path("memgraph"),
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--vendor-name",
|
||||
default="memgraph",
|
||||
help="Input vendor binary name (memgraph, neo4j)",
|
||||
help="Memgraph binary used for benchmarking",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--client-binary",
|
||||
default=helpers.get_binary_path("tests/mgbench/client"),
|
||||
help="Client binary used for benchmarking",
|
||||
help="client binary used for benchmarking",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--num-workers-for-import",
|
||||
@@ -82,7 +122,7 @@ parser.add_argument(
|
||||
"--single-threaded-runtime-sec",
|
||||
type=int,
|
||||
default=10,
|
||||
help="single threaded duration of each query",
|
||||
help="single threaded duration of each test",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-load-query-counts",
|
||||
@@ -105,368 +145,9 @@ parser.add_argument(
|
||||
help="directory path where temporary data should " "be stored",
|
||||
)
|
||||
parser.add_argument("--no-properties-on-edges", action="store_true", help="disable properties on edges")
|
||||
|
||||
parser.add_argument("--bolt-port", default=7687, help="memgraph bolt port")
|
||||
|
||||
parser.add_argument(
|
||||
"--no-authorization",
|
||||
action="store_false",
|
||||
default=True,
|
||||
help="Run each query with authorization",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--warmup-run",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Run warmup before benchmarks",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--mixed-workload",
|
||||
nargs="*",
|
||||
type=int,
|
||||
default=[],
|
||||
help="""Define combination that defines the mixed workload.
|
||||
Mixed workload can be run as a single configuration for all groups of queries,
|
||||
Pass the positional arguments as values of what percentage of
|
||||
write/read/update/analytical queries you want to have in your workload.
|
||||
Example: --mixed-workload 1000 20 70 10 0 will execute 1000 queries, 20% write,
|
||||
70% read, 10% update and 0% analytical.
|
||||
|
||||
Mixed workload can also be run on each query under some defined load.
|
||||
By passing one more positional argument, you are defining what percentage of that query
|
||||
will be in mixed workload, and this is executed for each query. The rest of the queries will be
|
||||
selected from the appropriate groups
|
||||
Running --mixed-workload 1000 30 0 0 0 70, will execute each query 700 times or 70%,
|
||||
with the presence of 300 write queries from write type or 30%""",
|
||||
)
|
||||
|
||||
parser.add_argument("--tail-latency", type=int, default=100, help="Number of queries for the tail latency statistics")
|
||||
|
||||
parser.add_argument(
|
||||
"--performance-tracking",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Flag for runners performance tracking, this logs RES through time and vendor specific performance tracking.",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
class Workload:
|
||||
def __init__(self, config):
|
||||
config_len = len(config)
|
||||
if config_len == 0:
|
||||
self.name = "Isolated"
|
||||
self.config = config
|
||||
elif config_len >= 5:
|
||||
if sum(config[1:]) != 100:
|
||||
raise Exception(
|
||||
"Please make sure that passed arguments % sum to 100% percent!, passed: ",
|
||||
config,
|
||||
)
|
||||
if config_len == 5:
|
||||
self.name = "Realistic"
|
||||
self.config = config
|
||||
else:
|
||||
self.name = "Mixed"
|
||||
self.config = config
|
||||
|
||||
|
||||
def get_queries(gen, count):
|
||||
# Make the generator deterministic.
|
||||
random.seed(gen.__name__)
|
||||
# Generate queries.
|
||||
ret = []
|
||||
for i in range(count):
|
||||
ret.append(gen())
|
||||
return ret
|
||||
|
||||
|
||||
def match_patterns(dataset, variant, group, query, is_default_variant, patterns):
|
||||
for pattern in patterns:
|
||||
verdict = [fnmatch.fnmatchcase(dataset, pattern[0])]
|
||||
if pattern[1] != "":
|
||||
verdict.append(fnmatch.fnmatchcase(variant, pattern[1]))
|
||||
else:
|
||||
verdict.append(is_default_variant)
|
||||
verdict.append(fnmatch.fnmatchcase(group, pattern[2]))
|
||||
verdict.append(fnmatch.fnmatchcase(query, pattern[3]))
|
||||
if all(verdict):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def filter_benchmarks(generators, patterns):
|
||||
patterns = copy.deepcopy(patterns)
|
||||
for i in range(len(patterns)):
|
||||
pattern = patterns[i].split("/")
|
||||
if len(pattern) > 5 or len(pattern) == 0:
|
||||
raise Exception("Invalid benchmark description '" + pattern + "'!")
|
||||
pattern.extend(["", "*", "*"][len(pattern) - 1 :])
|
||||
patterns[i] = pattern
|
||||
filtered = []
|
||||
for dataset in sorted(generators.keys()):
|
||||
generator, queries = generators[dataset]
|
||||
for variant in generator.VARIANTS:
|
||||
is_default_variant = variant == generator.DEFAULT_VARIANT
|
||||
current = collections.defaultdict(list)
|
||||
for group in queries:
|
||||
for query_name, query_func in queries[group]:
|
||||
if match_patterns(
|
||||
dataset,
|
||||
variant,
|
||||
group,
|
||||
query_name,
|
||||
is_default_variant,
|
||||
patterns,
|
||||
):
|
||||
current[group].append((query_name, query_func))
|
||||
if len(current) > 0:
|
||||
filtered.append((generator(variant, args.vendor_name), dict(current)))
|
||||
return filtered
|
||||
|
||||
|
||||
def warmup(client):
|
||||
print("Executing warm-up queries")
|
||||
client.execute(
|
||||
queries=[
|
||||
("CREATE ();", {}),
|
||||
("CREATE ()-[:TempEdge]->();", {}),
|
||||
("MATCH (n) RETURN n LIMIT 1;", {}),
|
||||
],
|
||||
num_workers=1,
|
||||
)
|
||||
|
||||
|
||||
def tail_latency(vendor, client, func):
|
||||
vendor.start_benchmark("tail_latency")
|
||||
if args.warmup_run:
|
||||
warmup(client)
|
||||
latency = []
|
||||
iteration = args.tail_latency
|
||||
query_list = get_queries(func, iteration)
|
||||
for i in range(0, iteration):
|
||||
ret = client.execute(queries=[query_list[i]], num_workers=1)
|
||||
latency.append(ret[0]["duration"])
|
||||
latency.sort()
|
||||
query_stats = {
|
||||
"iterations": iteration,
|
||||
"min": latency[0],
|
||||
"max": latency[iteration - 1],
|
||||
"mean": statistics.mean(latency),
|
||||
"p99": latency[math.floor(iteration * 0.99) - 1],
|
||||
"p95": latency[math.floor(iteration * 0.95) - 1],
|
||||
"p90": latency[math.floor(iteration * 0.90) - 1],
|
||||
"p75": latency[math.floor(iteration * 0.75) - 1],
|
||||
"p50": latency[math.floor(iteration * 0.50) - 1],
|
||||
}
|
||||
print("Query statistics for tail latency: ")
|
||||
print(query_stats)
|
||||
vendor.stop("tail_latency")
|
||||
return query_stats
|
||||
|
||||
|
||||
def mixed_workload(vendor, client, dataset, group, queries, workload):
|
||||
|
||||
num_of_queries = workload.config[0]
|
||||
percentage_distribution = workload.config[1:]
|
||||
if sum(percentage_distribution) != 100:
|
||||
raise Exception(
|
||||
"Please make sure that passed arguments % sum to 100% percent!, passed: ",
|
||||
percentage_distribution,
|
||||
)
|
||||
s = [str(i) for i in workload.config]
|
||||
|
||||
config_distribution = "_".join(s)
|
||||
|
||||
print("Generating mixed workload.")
|
||||
|
||||
percentages_by_type = {
|
||||
"write": percentage_distribution[0],
|
||||
"read": percentage_distribution[1],
|
||||
"update": percentage_distribution[2],
|
||||
"analytical": percentage_distribution[3],
|
||||
}
|
||||
|
||||
queries_by_type = {
|
||||
"write": [],
|
||||
"read": [],
|
||||
"update": [],
|
||||
"analytical": [],
|
||||
}
|
||||
|
||||
for (_, funcname) in queries[group]:
|
||||
for key in queries_by_type.keys():
|
||||
if key in funcname:
|
||||
queries_by_type[key].append(funcname)
|
||||
|
||||
for key, percentage in percentages_by_type.items():
|
||||
if percentage != 0 and len(queries_by_type[key]) == 0:
|
||||
raise Exception(
|
||||
"There is a missing query in group (write, read, update or analytical) for given workload distribution."
|
||||
)
|
||||
|
||||
random.seed(config_distribution)
|
||||
|
||||
# Executing mixed workload for each test
|
||||
if workload.name == "Mixed":
|
||||
for query, funcname in queries[group]:
|
||||
full_workload = []
|
||||
|
||||
log.info(
|
||||
"Running query in mixed workload:",
|
||||
"{}/{}/{}".format(
|
||||
group,
|
||||
query,
|
||||
funcname,
|
||||
),
|
||||
)
|
||||
base_query = getattr(dataset, funcname)
|
||||
|
||||
base_query_type = funcname.rsplit("_", 1)[1]
|
||||
|
||||
if percentages_by_type.get(base_query_type, 0) > 0:
|
||||
continue
|
||||
|
||||
options = ["write", "read", "update", "analytical", "query"]
|
||||
function_type = random.choices(population=options, weights=percentage_distribution, k=num_of_queries)
|
||||
|
||||
for t in function_type:
|
||||
# Get the apropropriate functions with same probabilty
|
||||
if t == "query":
|
||||
full_workload.append(base_query())
|
||||
else:
|
||||
funcname = random.choices(queries_by_type[t], k=1)[0]
|
||||
aditional_query = getattr(dataset, funcname)
|
||||
full_workload.append(aditional_query())
|
||||
|
||||
vendor.start_benchmark(
|
||||
dataset.NAME + dataset.get_variant() + "_" + "mixed" + "_" + query + "_" + config_distribution
|
||||
)
|
||||
if args.warmup_run:
|
||||
warmup(client)
|
||||
ret = client.execute(
|
||||
queries=full_workload,
|
||||
num_workers=args.num_workers_for_benchmark,
|
||||
)[0]
|
||||
usage_workload = vendor.stop(
|
||||
dataset.NAME + dataset.get_variant() + "_" + "mixed" + "_" + query + "_" + config_distribution
|
||||
)
|
||||
|
||||
ret["database"] = usage_workload
|
||||
|
||||
results_key = [
|
||||
dataset.NAME,
|
||||
dataset.get_variant(),
|
||||
group,
|
||||
query + "_" + config_distribution,
|
||||
WITHOUT_FINE_GRAINED_AUTHORIZATION,
|
||||
]
|
||||
results.set_value(*results_key, value=ret)
|
||||
|
||||
else:
|
||||
# Executing mixed workload from groups of queries
|
||||
full_workload = []
|
||||
options = ["write", "read", "update", "analytical"]
|
||||
function_type = random.choices(population=options, weights=percentage_distribution, k=num_of_queries)
|
||||
|
||||
for t in function_type:
|
||||
# Get the apropropriate functions with same probabilty
|
||||
funcname = random.choices(queries_by_type[t], k=1)[0]
|
||||
aditional_query = getattr(dataset, funcname)
|
||||
full_workload.append(aditional_query())
|
||||
|
||||
vendor.start_benchmark(dataset.NAME + dataset.get_variant() + "_" + workload.name + "_" + config_distribution)
|
||||
if args.warmup_run:
|
||||
warmup(client)
|
||||
ret = client.execute(
|
||||
queries=full_workload,
|
||||
num_workers=args.num_workers_for_benchmark,
|
||||
)[0]
|
||||
usage_workload = vendor.stop(
|
||||
dataset.NAME + dataset.get_variant() + "_" + workload.name + "_" + config_distribution
|
||||
)
|
||||
mixed_workload = {
|
||||
"count": ret["count"],
|
||||
"duration": ret["duration"],
|
||||
"retries": ret["retries"],
|
||||
"throughput": ret["throughput"],
|
||||
"num_workers": ret["num_workers"],
|
||||
"database": usage_workload,
|
||||
}
|
||||
results_key = [
|
||||
dataset.NAME,
|
||||
dataset.get_variant(),
|
||||
group,
|
||||
config_distribution,
|
||||
WITHOUT_FINE_GRAINED_AUTHORIZATION,
|
||||
]
|
||||
results.set_value(*results_key, value=mixed_workload)
|
||||
|
||||
print(mixed_workload)
|
||||
|
||||
|
||||
def get_query_cache_count(vendor, client, func, config_key):
|
||||
cached_count = config.get_value(*config_key)
|
||||
|
||||
if cached_count is None:
|
||||
print(
|
||||
"Determining the number of queries necessary for",
|
||||
args.single_threaded_runtime_sec,
|
||||
"seconds of single-threaded runtime...",
|
||||
)
|
||||
# First run to prime the query caches.
|
||||
vendor.start_benchmark("cache")
|
||||
if args.warmup_run:
|
||||
warmup(client)
|
||||
client.execute(queries=get_queries(func, 1), num_workers=1)
|
||||
# Get a sense of the runtime.
|
||||
count = 1
|
||||
while True:
|
||||
ret = client.execute(queries=get_queries(func, count), num_workers=1)
|
||||
duration = ret[0]["duration"]
|
||||
should_execute = int(args.single_threaded_runtime_sec / (duration / count))
|
||||
print(
|
||||
"executed_queries={}, total_duration={}, "
|
||||
"query_duration={}, estimated_count={}".format(count, duration, duration / count, should_execute)
|
||||
)
|
||||
# We don't have to execute the next iteration when
|
||||
# `should_execute` becomes the same order of magnitude as
|
||||
# `count * 10`.
|
||||
if should_execute / (count * 10) < 10:
|
||||
count = should_execute
|
||||
break
|
||||
else:
|
||||
count = count * 10
|
||||
vendor.stop("cache")
|
||||
|
||||
# Lower bound for count
|
||||
if count < 20:
|
||||
count = 20
|
||||
|
||||
config.set_value(
|
||||
*config_key,
|
||||
value={
|
||||
"count": count,
|
||||
"duration": args.single_threaded_runtime_sec,
|
||||
},
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"Using cached query count of",
|
||||
cached_count["count"],
|
||||
"queries for",
|
||||
cached_count["duration"],
|
||||
"seconds of single-threaded runtime.",
|
||||
)
|
||||
count = int(cached_count["count"] * args.single_threaded_runtime_sec / cached_count["duration"])
|
||||
return count
|
||||
|
||||
|
||||
# Testing pre commit.
|
||||
|
||||
# Detect available datasets.
|
||||
generators = {}
|
||||
for key in dir(datasets):
|
||||
@@ -475,13 +156,13 @@ for key in dir(datasets):
|
||||
dataset = getattr(datasets, key)
|
||||
if not inspect.isclass(dataset) or dataset == datasets.Dataset or not issubclass(dataset, datasets.Dataset):
|
||||
continue
|
||||
queries = collections.defaultdict(list)
|
||||
tests = collections.defaultdict(list)
|
||||
for funcname in dir(dataset):
|
||||
if not funcname.startswith("benchmark__"):
|
||||
continue
|
||||
group, query = funcname.split("__")[1:]
|
||||
queries[group].append((query, funcname))
|
||||
generators[dataset.NAME] = (dataset, dict(queries))
|
||||
group, test = funcname.split("__")[1:]
|
||||
tests[group].append((test, funcname))
|
||||
generators[dataset.NAME] = (dataset, dict(tests))
|
||||
if dataset.PROPERTIES_ON_EDGES and args.no_properties_on_edges:
|
||||
raise Exception(
|
||||
'The "{}" dataset requires properties on edges, ' "but you have disabled them!".format(dataset.NAME)
|
||||
@@ -489,19 +170,19 @@ for key in dir(datasets):
|
||||
|
||||
# List datasets if there is no specified dataset.
|
||||
if len(args.benchmarks) == 0:
|
||||
log.init("Available queries")
|
||||
log.init("Available tests")
|
||||
for name in sorted(generators.keys()):
|
||||
print("Dataset:", name)
|
||||
dataset, queries = generators[name]
|
||||
dataset, tests = generators[name]
|
||||
print(
|
||||
" Variants:",
|
||||
", ".join(dataset.VARIANTS),
|
||||
"(default: " + dataset.DEFAULT_VARIANT + ")",
|
||||
)
|
||||
for group in sorted(queries.keys()):
|
||||
for group in sorted(tests.keys()):
|
||||
print(" Group:", group)
|
||||
for query_name, query_func in queries[group]:
|
||||
print(" Query:", query_name)
|
||||
for test_name, test_func in tests[group]:
|
||||
print(" Test:", test_name)
|
||||
sys.exit(0)
|
||||
|
||||
# Create cache, config and results objects.
|
||||
@@ -515,121 +196,133 @@ results = helpers.RecursiveDict()
|
||||
# Filter out the generators.
|
||||
benchmarks = filter_benchmarks(generators, args.benchmarks)
|
||||
# Run all specified benchmarks.
|
||||
for dataset, queries in benchmarks:
|
||||
|
||||
workload = Workload(args.mixed_workload)
|
||||
|
||||
run_config = {
|
||||
"vendor": args.vendor_name,
|
||||
"condition": "hot" if args.warmup_run else "cold",
|
||||
"workload": workload.name,
|
||||
"workload_config": workload.config,
|
||||
}
|
||||
|
||||
results.set_value("__run_configuration__", value=run_config)
|
||||
|
||||
for dataset, tests in benchmarks:
|
||||
log.init("Preparing", dataset.NAME + "/" + dataset.get_variant(), "dataset")
|
||||
dataset.prepare(cache.cache_directory("datasets", dataset.NAME, dataset.get_variant()))
|
||||
|
||||
# TODO: Create some abstract class for vendors, that will hold this data
|
||||
if args.vendor_name == "neo4j":
|
||||
vendor = runners.Neo4j(
|
||||
args.vendor_binary,
|
||||
args.temporary_directory,
|
||||
args.bolt_port,
|
||||
args.performance_tracking,
|
||||
)
|
||||
else:
|
||||
vendor = runners.Memgraph(
|
||||
args.vendor_binary,
|
||||
args.temporary_directory,
|
||||
not args.no_properties_on_edges,
|
||||
args.bolt_port,
|
||||
args.performance_tracking,
|
||||
)
|
||||
|
||||
# Prepare runners and import the dataset.
|
||||
memgraph = runners.Memgraph(
|
||||
args.memgraph_binary,
|
||||
args.temporary_directory,
|
||||
not args.no_properties_on_edges,
|
||||
args.bolt_port,
|
||||
)
|
||||
client = runners.Client(args.client_binary, args.temporary_directory, args.bolt_port)
|
||||
memgraph.start_preparation()
|
||||
ret = client.execute(file_path=dataset.get_file(), num_workers=args.num_workers_for_import)
|
||||
usage = memgraph.stop()
|
||||
|
||||
ret = None
|
||||
usage = None
|
||||
if args.vendor_name == "neo4j":
|
||||
vendor.start_preparation("preparation")
|
||||
print("Executing database cleanup and index setup...")
|
||||
ret = client.execute(file_path=dataset.get_index(), num_workers=args.num_workers_for_import)
|
||||
usage = vendor.stop("preparation")
|
||||
dump_dir = cache.cache_directory("datasets", dataset.NAME, dataset.get_variant())
|
||||
dump_file, exists = dump_dir.get_file("neo4j.dump")
|
||||
if exists:
|
||||
vendor.load_db_from_dump(path=dump_dir.get_path())
|
||||
else:
|
||||
vendor.start_preparation("import")
|
||||
print("Importing dataset...")
|
||||
ret = client.execute(file_path=dataset.get_file(), num_workers=args.num_workers_for_import)
|
||||
usage = vendor.stop("import")
|
||||
|
||||
vendor.dump_db(path=dump_dir.get_path())
|
||||
else:
|
||||
vendor.start_preparation("import")
|
||||
print("Executing database cleanup and index setup...")
|
||||
ret = client.execute(file_path=dataset.get_index(), num_workers=args.num_workers_for_import)
|
||||
print("Importing dataset...")
|
||||
ret = client.execute(file_path=dataset.get_file(), num_workers=args.num_workers_for_import)
|
||||
usage = vendor.stop("import")
|
||||
# Save import results.
|
||||
import_key = [dataset.NAME, dataset.get_variant(), "__import__"]
|
||||
if ret != None and usage != None:
|
||||
# Display import statistics.
|
||||
print()
|
||||
for row in ret:
|
||||
print(
|
||||
"Executed",
|
||||
row["count"],
|
||||
"queries in",
|
||||
row["duration"],
|
||||
"seconds using",
|
||||
row["num_workers"],
|
||||
"workers with a total throughput of",
|
||||
row["throughput"],
|
||||
"queries/second.",
|
||||
)
|
||||
print()
|
||||
# Display import statistics.
|
||||
print()
|
||||
for row in ret:
|
||||
print(
|
||||
"The database used",
|
||||
usage["cpu"],
|
||||
"seconds of CPU time and peaked at",
|
||||
usage["memory"] / 1024 / 1024,
|
||||
"MiB of RAM.",
|
||||
"Executed",
|
||||
row["count"],
|
||||
"queries in",
|
||||
row["duration"],
|
||||
"seconds using",
|
||||
row["num_workers"],
|
||||
"workers with a total throughput of",
|
||||
row["throughput"],
|
||||
"queries/second.",
|
||||
)
|
||||
print()
|
||||
print(
|
||||
"The database used",
|
||||
usage["cpu"],
|
||||
"seconds of CPU time and peaked at",
|
||||
usage["memory"] / 1024 / 1024,
|
||||
"MiB of RAM.",
|
||||
)
|
||||
|
||||
results.set_value(*import_key, value={"client": ret, "database": usage})
|
||||
else:
|
||||
results.set_value(*import_key, value={"client": "dump_load", "database": "dump_load"})
|
||||
# Save import results.
|
||||
import_key = [dataset.NAME, dataset.get_variant(), "__import__"]
|
||||
results.set_value(*import_key, value={"client": ret, "database": usage})
|
||||
|
||||
# TODO: cache import data
|
||||
|
||||
# Run all benchmarks in all available groups.
|
||||
for group in sorted(queries.keys()):
|
||||
|
||||
# Running queries in mixed workload
|
||||
if workload.name == "Mixed" or workload.name == "Realistic":
|
||||
mixed_workload(vendor, client, dataset, group, queries, workload)
|
||||
else:
|
||||
for query, funcname in queries[group]:
|
||||
log.info(
|
||||
"Running query:",
|
||||
"{}/{}/{}/{}".format(group, query, funcname, WITHOUT_FINE_GRAINED_AUTHORIZATION),
|
||||
)
|
||||
for with_fine_grained_authorization in [False, True]:
|
||||
if with_fine_grained_authorization:
|
||||
memgraph.start_preparation()
|
||||
client.execute(file_path=dataset.get_file(), num_workers=args.num_workers_for_import)
|
||||
client.execute(
|
||||
queries=[
|
||||
("CREATE USER user IDENTIFIED BY 'test';", {}),
|
||||
("GRANT ALL PRIVILEGES TO user;", {}),
|
||||
("GRANT CREATE_DELETE ON EDGE_TYPES * TO user;", {}),
|
||||
("GRANT CREATE_DELETE ON LABELS * TO user;", {}),
|
||||
]
|
||||
)
|
||||
client = runners.Client(
|
||||
args.client_binary,
|
||||
args.temporary_directory,
|
||||
args.bolt_port,
|
||||
username="user",
|
||||
password="test",
|
||||
)
|
||||
memgraph.stop()
|
||||
|
||||
test_type = (
|
||||
WITH_FINE_GRAINED_AUTHORIZATION if with_fine_grained_authorization else WITHOUT_FINE_GRAINED_AUTHORIZATION
|
||||
)
|
||||
|
||||
for group in sorted(tests.keys()):
|
||||
for test, funcname in tests[group]:
|
||||
log.info("Running test:", "{}/{}/{}".format(group, test, test_type))
|
||||
func = getattr(dataset, funcname)
|
||||
|
||||
query_statistics = tail_latency(vendor, client, func)
|
||||
|
||||
# Query count for each vendor
|
||||
config_key = [
|
||||
dataset.NAME,
|
||||
dataset.get_variant(),
|
||||
args.vendor_name,
|
||||
group,
|
||||
query,
|
||||
]
|
||||
count = get_query_cache_count(vendor, client, func, config_key)
|
||||
# Get number of queries to execute.
|
||||
# TODO: implement minimum number of queries, `max(10, num_workers)`
|
||||
config_key = [dataset.NAME, dataset.get_variant(), group, test, test_type]
|
||||
cached_count = config.get_value(*config_key)
|
||||
if cached_count is None:
|
||||
print(
|
||||
"Determining the number of queries necessary for",
|
||||
args.single_threaded_runtime_sec,
|
||||
"seconds of single-threaded runtime...",
|
||||
)
|
||||
# First run to prime the query caches.
|
||||
memgraph.start_benchmark()
|
||||
client.execute(queries=get_queries(func, 1), num_workers=1)
|
||||
# Get a sense of the runtime.
|
||||
count = 1
|
||||
while True:
|
||||
ret = client.execute(queries=get_queries(func, count), num_workers=1)
|
||||
duration = ret[0]["duration"]
|
||||
should_execute = int(args.single_threaded_runtime_sec / (duration / count))
|
||||
print(
|
||||
"executed_queries={}, total_duration={}, "
|
||||
"query_duration={}, estimated_count={}".format(
|
||||
count, duration, duration / count, should_execute
|
||||
)
|
||||
)
|
||||
# We don't have to execute the next iteration when
|
||||
# `should_execute` becomes the same order of magnitude as
|
||||
# `count * 10`.
|
||||
if should_execute / (count * 10) < 10:
|
||||
count = should_execute
|
||||
break
|
||||
else:
|
||||
count = count * 10
|
||||
memgraph.stop()
|
||||
config.set_value(
|
||||
*config_key,
|
||||
value={
|
||||
"count": count,
|
||||
"duration": args.single_threaded_runtime_sec,
|
||||
},
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"Using cached query count of",
|
||||
cached_count["count"],
|
||||
"queries for",
|
||||
cached_count["duration"],
|
||||
"seconds of single-threaded runtime.",
|
||||
)
|
||||
count = int(cached_count["count"] * args.single_threaded_runtime_sec / cached_count["duration"])
|
||||
|
||||
# Benchmark run.
|
||||
print("Sample query:", get_queries(func, 1)[0][0])
|
||||
@@ -645,16 +338,13 @@ for dataset, queries in benchmarks:
|
||||
args.num_workers_for_benchmark,
|
||||
"concurrent clients.",
|
||||
)
|
||||
vendor.start_benchmark(dataset.NAME + dataset.get_variant() + "_" + workload.name + "_" + query)
|
||||
if args.warmup_run:
|
||||
warmup(client)
|
||||
memgraph.start_benchmark()
|
||||
ret = client.execute(
|
||||
queries=get_queries(func, count),
|
||||
num_workers=args.num_workers_for_benchmark,
|
||||
)[0]
|
||||
usage = vendor.stop(dataset.NAME + dataset.get_variant() + "_" + workload.name + "_" + query)
|
||||
usage = memgraph.stop()
|
||||
ret["database"] = usage
|
||||
ret["query_statistics"] = query_statistics
|
||||
|
||||
# Output summary.
|
||||
print()
|
||||
@@ -672,108 +362,9 @@ for dataset, queries in benchmarks:
|
||||
log.success("Throughput: {:02f} QPS".format(ret["throughput"]))
|
||||
|
||||
# Save results.
|
||||
results_key = [
|
||||
dataset.NAME,
|
||||
dataset.get_variant(),
|
||||
group,
|
||||
query,
|
||||
WITHOUT_FINE_GRAINED_AUTHORIZATION,
|
||||
]
|
||||
results_key = [dataset.NAME, dataset.get_variant(), group, test, test_type]
|
||||
results.set_value(*results_key, value=ret)
|
||||
|
||||
## If there is need for authorization testing.
|
||||
if args.no_authorization:
|
||||
print("Running query with authorization")
|
||||
vendor.start_benchmark("authorization")
|
||||
client.execute(
|
||||
queries=[
|
||||
("CREATE USER user IDENTIFIED BY 'test';", {}),
|
||||
("GRANT ALL PRIVILEGES TO user;", {}),
|
||||
("GRANT CREATE_DELETE ON EDGE_TYPES * TO user;", {}),
|
||||
("GRANT CREATE_DELETE ON LABELS * TO user;", {}),
|
||||
]
|
||||
)
|
||||
client = runners.Client(
|
||||
args.client_binary,
|
||||
args.temporary_directory,
|
||||
args.bolt_port,
|
||||
username="user",
|
||||
password="test",
|
||||
)
|
||||
vendor.stop("authorization")
|
||||
|
||||
for query, funcname in queries[group]:
|
||||
|
||||
log.info(
|
||||
"Running query:",
|
||||
"{}/{}/{}/{}".format(group, query, funcname, WITH_FINE_GRAINED_AUTHORIZATION),
|
||||
)
|
||||
func = getattr(dataset, funcname)
|
||||
|
||||
query_statistics = tail_latency(vendor, client, func)
|
||||
|
||||
config_key = [
|
||||
dataset.NAME,
|
||||
dataset.get_variant(),
|
||||
args.vendor_name,
|
||||
group,
|
||||
query,
|
||||
]
|
||||
count = get_query_cache_count(vendor, client, func, config_key)
|
||||
|
||||
vendor.start_benchmark("authorization")
|
||||
if args.warmup_run:
|
||||
warmup(client)
|
||||
ret = client.execute(
|
||||
queries=get_queries(func, count),
|
||||
num_workers=args.num_workers_for_benchmark,
|
||||
)[0]
|
||||
usage = vendor.stop("authorization")
|
||||
ret["database"] = usage
|
||||
ret["query_statistics"] = query_statistics
|
||||
|
||||
# Output summary.
|
||||
print()
|
||||
print(
|
||||
"Executed",
|
||||
ret["count"],
|
||||
"queries in",
|
||||
ret["duration"],
|
||||
"seconds.",
|
||||
)
|
||||
print("Queries have been retried", ret["retries"], "times.")
|
||||
print("Database used {:.3f} seconds of CPU time.".format(usage["cpu"]))
|
||||
print("Database peaked at {:.3f} MiB of memory.".format(usage["memory"] / 1024.0 / 1024.0))
|
||||
print("{:<31} {:>20} {:>20} {:>20}".format("Metadata:", "min", "avg", "max"))
|
||||
metadata = ret["metadata"]
|
||||
for key in sorted(metadata.keys()):
|
||||
print(
|
||||
"{name:>30}: {minimum:>20.06f} {average:>20.06f} "
|
||||
"{maximum:>20.06f}".format(name=key, **metadata[key])
|
||||
)
|
||||
log.success("Throughput: {:02f} QPS".format(ret["throughput"]))
|
||||
# Save results.
|
||||
results_key = [
|
||||
dataset.NAME,
|
||||
dataset.get_variant(),
|
||||
group,
|
||||
query,
|
||||
WITH_FINE_GRAINED_AUTHORIZATION,
|
||||
]
|
||||
results.set_value(*results_key, value=ret)
|
||||
|
||||
# Clean up database from any roles and users job
|
||||
vendor.start_benchmark("authorizations")
|
||||
ret = client.execute(
|
||||
queries=[
|
||||
("REVOKE LABELS * FROM user;", {}),
|
||||
("REVOKE EDGE_TYPES * FROM user;", {}),
|
||||
("DROP USER user;", {}),
|
||||
]
|
||||
)
|
||||
vendor.stop("authorization")
|
||||
|
||||
|
||||
# Save configuration.
|
||||
if not args.no_save_query_counts:
|
||||
cache.save_config(config)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2022 Memgraph Ltd.
|
||||
# Copyright 2021 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
|
||||
@@ -15,6 +15,48 @@ import argparse
|
||||
import json
|
||||
|
||||
|
||||
FIELDS = [
|
||||
{
|
||||
"name": "throughput",
|
||||
"positive_diff_better": True,
|
||||
"scaling": 1,
|
||||
"unit": "QPS",
|
||||
"diff_treshold": 0.05, # 5%
|
||||
},
|
||||
{
|
||||
"name": "duration",
|
||||
"positive_diff_better": False,
|
||||
"scaling": 1,
|
||||
"unit": "s",
|
||||
},
|
||||
{
|
||||
"name": "parsing_time",
|
||||
"positive_diff_better": False,
|
||||
"scaling": 1000,
|
||||
"unit": "ms",
|
||||
},
|
||||
{
|
||||
"name": "planning_time",
|
||||
"positive_diff_better": False,
|
||||
"scaling": 1000,
|
||||
"unit": "ms",
|
||||
},
|
||||
{
|
||||
"name": "plan_execution_time",
|
||||
"positive_diff_better": False,
|
||||
"scaling": 1000,
|
||||
"unit": "ms",
|
||||
},
|
||||
{
|
||||
"name": "memory",
|
||||
"positive_diff_better": False,
|
||||
"scaling": 1 / 1024 / 1024,
|
||||
"unit": "MiB",
|
||||
"diff_treshold": 0.02, # 2%
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def load_results(fname):
|
||||
with open(fname) as f:
|
||||
return json.load(f)
|
||||
@@ -35,11 +77,9 @@ def recursive_get(data, *args, value=None):
|
||||
return data
|
||||
|
||||
|
||||
def compare_results(results_from, results_to, fields, ignored, different_vendors):
|
||||
def compare_results(results_from, results_to, fields, ignored):
|
||||
ret = {}
|
||||
for dataset, variants in results_to.items():
|
||||
if dataset == "__run_configuration__":
|
||||
continue
|
||||
for variant, groups in variants.items():
|
||||
for group, scenarios in groups.items():
|
||||
if group == "__import__":
|
||||
@@ -49,11 +89,9 @@ def compare_results(results_from, results_to, fields, ignored, different_vendors
|
||||
continue
|
||||
|
||||
summary_from = recursive_get(results_from, dataset, variant, group, scenario, value={})
|
||||
summary_from = summary_from["without_fine_grained_authorization"]
|
||||
summary_to = summary_to["without_fine_grained_authorization"]
|
||||
if (
|
||||
len(summary_from) > 0
|
||||
and (summary_to["count"] != summary_from["count"] and not different_vendors)
|
||||
and summary_to["count"] != summary_from["count"]
|
||||
or summary_to["num_workers"] != summary_from["num_workers"]
|
||||
):
|
||||
raise Exception("Incompatible results!")
|
||||
@@ -77,19 +115,13 @@ def compare_results(results_from, results_to, fields, ignored, different_vendors
|
||||
recursive_get(summary_from, "database", key, value=None),
|
||||
summary_to["database"][key],
|
||||
)
|
||||
elif summary_to.get("query_statistics") != None and key in summary_to["query_statistics"]:
|
||||
row[key] = compute_diff(
|
||||
recursive_get(summary_from, "query_statistics", key, value=None),
|
||||
summary_to["query_statistics"][key],
|
||||
)
|
||||
elif not different_vendors:
|
||||
else:
|
||||
row[key] = compute_diff(
|
||||
recursive_get(summary_from, "metadata", key, "average", value=None),
|
||||
summary_to["metadata"][key]["average"],
|
||||
)
|
||||
if row.get(key) != None and (
|
||||
"diff" not in row[key]
|
||||
or ("diff_treshold" in field and abs(row[key]["diff"]) >= field["diff_treshold"])
|
||||
if "diff" not in row[key] or (
|
||||
"diff_treshold" in field and abs(row[key]["diff"]) >= field["diff_treshold"]
|
||||
):
|
||||
performance_changed = True
|
||||
if performance_changed:
|
||||
@@ -117,22 +149,19 @@ def generate_remarkup(fields, data):
|
||||
ret += " <tr>\n"
|
||||
ret += " <td>{}</td>\n".format(testcode)
|
||||
for field in fields:
|
||||
result = data[testcode].get(field["name"])
|
||||
if result != None:
|
||||
value = result["value"] * field["scaling"]
|
||||
if "diff" in result:
|
||||
diff = result["diff"]
|
||||
arrow = "arrow-up" if diff >= 0 else "arrow-down"
|
||||
if not (field["positive_diff_better"] ^ (diff >= 0)):
|
||||
color = "green"
|
||||
else:
|
||||
color = "red"
|
||||
sign = "{{icon {} color={}}}".format(arrow, color)
|
||||
ret += ' <td bgcolor="{}">{:.3f}{} ({:+.2%})</td>\n'.format(
|
||||
color, value, field["unit"], diff
|
||||
)
|
||||
result = data[testcode][field["name"]]
|
||||
value = result["value"] * field["scaling"]
|
||||
if "diff" in result:
|
||||
diff = result["diff"]
|
||||
arrow = "arrow-up" if diff >= 0 else "arrow-down"
|
||||
if not (field["positive_diff_better"] ^ (diff >= 0)):
|
||||
color = "green"
|
||||
else:
|
||||
ret += '<td bgcolor="blue">{:.3f}{} //(new)// </td>\n'.format(value, field["unit"])
|
||||
color = "red"
|
||||
sign = "{{icon {} color={}}}".format(arrow, color)
|
||||
ret += ' <td bgcolor="{}">{:.3f}{} ({:+.2%})</td>\n'.format(color, value, field["unit"], diff)
|
||||
else:
|
||||
ret += '<td bgcolor="blue">{:.3f}{} //(new)// </td>\n'.format(value, field["unit"])
|
||||
ret += " </tr>\n"
|
||||
ret += "</table>\n"
|
||||
else:
|
||||
@@ -152,96 +181,8 @@ if __name__ == "__main__":
|
||||
parser.add_argument("--output", default="", help="output file name")
|
||||
# file is read line by line, each representing one test name
|
||||
parser.add_argument("--exclude_tests_file", help="file listing test names to be excluded")
|
||||
|
||||
parser.add_argument(
|
||||
"--different-vendors",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Comparing different vendors, there is no need for metadata, duration, count check.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--difference-threshold", type=float, help="Difference threshold for memory and throughput, 0.02 = 2% "
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
fields = [
|
||||
{
|
||||
"name": "throughput",
|
||||
"positive_diff_better": True,
|
||||
"scaling": 1,
|
||||
"unit": "QPS",
|
||||
"diff_treshold": 0.05, # 5%
|
||||
},
|
||||
{
|
||||
"name": "duration",
|
||||
"positive_diff_better": False,
|
||||
"scaling": 1,
|
||||
"unit": "s",
|
||||
},
|
||||
{
|
||||
"name": "parsing_time",
|
||||
"positive_diff_better": False,
|
||||
"scaling": 1000,
|
||||
"unit": "ms",
|
||||
},
|
||||
{
|
||||
"name": "planning_time",
|
||||
"positive_diff_better": False,
|
||||
"scaling": 1000,
|
||||
"unit": "ms",
|
||||
},
|
||||
{
|
||||
"name": "plan_execution_time",
|
||||
"positive_diff_better": False,
|
||||
"scaling": 1000,
|
||||
"unit": "ms",
|
||||
},
|
||||
{
|
||||
"name": "memory",
|
||||
"positive_diff_better": False,
|
||||
"scaling": 1 / 1024 / 1024,
|
||||
"unit": "MiB",
|
||||
"diff_treshold": 0.02, # 2%
|
||||
},
|
||||
{
|
||||
"name": "max",
|
||||
"positive_diff_better": False,
|
||||
"scaling": 1000,
|
||||
"unit": "ms",
|
||||
},
|
||||
{
|
||||
"name": "p99",
|
||||
"positive_diff_better": False,
|
||||
"scaling": 1000,
|
||||
"unit": "ms",
|
||||
},
|
||||
{
|
||||
"name": "p90",
|
||||
"positive_diff_better": False,
|
||||
"scaling": 1000,
|
||||
"unit": "ms",
|
||||
},
|
||||
{
|
||||
"name": "p75",
|
||||
"positive_diff_better": False,
|
||||
"scaling": 1000,
|
||||
"unit": "ms",
|
||||
},
|
||||
{
|
||||
"name": "p50",
|
||||
"positive_diff_better": False,
|
||||
"scaling": 1000,
|
||||
"unit": "ms",
|
||||
},
|
||||
{
|
||||
"name": "mean",
|
||||
"positive_diff_better": False,
|
||||
"scaling": 1000,
|
||||
"unit": "ms",
|
||||
},
|
||||
]
|
||||
|
||||
if args.compare is None or len(args.compare) == 0:
|
||||
raise Exception("You must specify at least one pair of files!")
|
||||
|
||||
@@ -251,29 +192,13 @@ if __name__ == "__main__":
|
||||
else:
|
||||
ignored = []
|
||||
|
||||
cleaned = []
|
||||
if args.different_vendors:
|
||||
ignore_on_different_vendors = {"duration", "parsing_time", "planning_time", "plan_execution_time"}
|
||||
for field in fields:
|
||||
key = field["name"]
|
||||
if key in ignore_on_different_vendors:
|
||||
continue
|
||||
else:
|
||||
cleaned.append(field)
|
||||
fields = cleaned
|
||||
|
||||
if args.difference_threshold > 0.01:
|
||||
for field in fields:
|
||||
if "diff_treshold" in field.keys():
|
||||
field["diff_treshold"] = args.difference_threshold
|
||||
|
||||
data = {}
|
||||
for file_from, file_to in args.compare:
|
||||
results_from = load_results(file_from)
|
||||
results_to = load_results(file_to)
|
||||
data.update(compare_results(results_from, results_to, fields, ignored, args.different_vendors))
|
||||
data.update(compare_results(results_from, results_to, FIELDS, ignored))
|
||||
|
||||
remarkup = generate_remarkup(fields, data)
|
||||
remarkup = generate_remarkup(FIELDS, data)
|
||||
if args.output:
|
||||
with open(args.output, "w") as f:
|
||||
f.write(remarkup)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2022 Memgraph Ltd.
|
||||
# Copyright 2021 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
|
||||
@@ -27,8 +27,6 @@ class Dataset:
|
||||
FILES = {
|
||||
"default": "/foo/bar",
|
||||
}
|
||||
INDEX = None
|
||||
INDEX_FILES = {"default": ""}
|
||||
# List of query file URLs that should be used to import the dataset.
|
||||
URLS = None
|
||||
# Number of vertices/edges for each variant.
|
||||
@@ -38,7 +36,7 @@ class Dataset:
|
||||
# Indicates whether the dataset has properties on edges.
|
||||
PROPERTIES_ON_EDGES = False
|
||||
|
||||
def __init__(self, variant=None, vendor=None):
|
||||
def __init__(self, variant=None):
|
||||
"""
|
||||
Accepts a `variant` variable that indicates which variant
|
||||
of the dataset should be executed.
|
||||
@@ -51,10 +49,7 @@ class Dataset:
|
||||
raise ValueError("The variant doesn't have a defined URL or " "file path!")
|
||||
if variant not in self.SIZES:
|
||||
raise ValueError("The variant doesn't have a defined dataset " "size!")
|
||||
if vendor not in self.INDEX_FILES:
|
||||
raise ValueError("Vendor does not have INDEX for dataset!")
|
||||
self._variant = variant
|
||||
self._vendor = vendor
|
||||
if self.FILES is not None:
|
||||
self._file = self.FILES.get(variant, None)
|
||||
else:
|
||||
@@ -63,12 +58,6 @@ class Dataset:
|
||||
self._url = self.URLS.get(variant, None)
|
||||
else:
|
||||
self._url = None
|
||||
|
||||
if self.INDEX_FILES is not None:
|
||||
self._index = self.INDEX_FILES.get(vendor, None)
|
||||
else:
|
||||
self._index = None
|
||||
|
||||
self._size = self.SIZES[variant]
|
||||
if "vertices" not in self._size or "edges" not in self._size:
|
||||
raise ValueError("The size defined for this variant doesn't " "have the number of vertices and/or edges!")
|
||||
@@ -78,34 +67,21 @@ class Dataset:
|
||||
def prepare(self, directory):
|
||||
if self._file is not None:
|
||||
print("Using dataset file:", self._file)
|
||||
else:
|
||||
# TODO: add support for JSON datasets
|
||||
cached_input, exists = directory.get_file("dataset.cypher")
|
||||
if not exists:
|
||||
print("Downloading dataset file:", self._url)
|
||||
downloaded_file = helpers.download_file(self._url, directory.get_path())
|
||||
print("Unpacking and caching file:", downloaded_file)
|
||||
helpers.unpack_and_move_file(downloaded_file, cached_input)
|
||||
print("Using cached dataset file:", cached_input)
|
||||
self._file = cached_input
|
||||
|
||||
cached_index, exists = directory.get_file(self._vendor + ".cypher")
|
||||
return
|
||||
# TODO: add support for JSON datasets
|
||||
cached_input, exists = directory.get_file("dataset.cypher")
|
||||
if not exists:
|
||||
print("Downloading index file:", self._index)
|
||||
downloaded_file = helpers.download_file(self._index, directory.get_path())
|
||||
print("Downloading dataset file:", self._url)
|
||||
downloaded_file = helpers.download_file(self._url, directory.get_path())
|
||||
print("Unpacking and caching file:", downloaded_file)
|
||||
helpers.unpack_and_move_file(downloaded_file, cached_index)
|
||||
print("Using cached index file:", cached_index)
|
||||
self._index = cached_index
|
||||
helpers.unpack_and_move_file(downloaded_file, cached_input)
|
||||
print("Using cached dataset file:", cached_input)
|
||||
self._file = cached_input
|
||||
|
||||
def get_variant(self):
|
||||
"""Returns the current variant of the dataset."""
|
||||
return self._variant
|
||||
|
||||
def get_index(self):
|
||||
"""Get index file, defined by vendor"""
|
||||
return self._index
|
||||
|
||||
def get_file(self):
|
||||
"""
|
||||
Returns path to the file that contains dataset creation queries.
|
||||
@@ -127,23 +103,16 @@ class Pokec(Dataset):
|
||||
VARIANTS = ["small", "medium", "large"]
|
||||
DEFAULT_VARIANT = "small"
|
||||
FILES = None
|
||||
|
||||
URLS = {
|
||||
"small": "https://s3.eu-west-1.amazonaws.com/deps.memgraph.io/dataset/pokec/benchmark/pokec_small_import.cypher",
|
||||
"medium": "https://s3.eu-west-1.amazonaws.com/deps.memgraph.io/dataset/pokec/benchmark/pokec_medium_import.cypher",
|
||||
"large": "https://s3.eu-west-1.amazonaws.com/deps.memgraph.io/dataset/pokec/benchmark/pokec_large.setup.cypher.gz",
|
||||
"small": "https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/pokec_small.setup.cypher",
|
||||
"medium": "https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/pokec_medium.setup.cypher",
|
||||
"large": "https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/pokec_large.setup.cypher.gz",
|
||||
}
|
||||
SIZES = {
|
||||
"small": {"vertices": 10000, "edges": 121716},
|
||||
"medium": {"vertices": 100000, "edges": 1768515},
|
||||
"large": {"vertices": 1632803, "edges": 30622564},
|
||||
}
|
||||
INDEX = None
|
||||
INDEX_FILES = {
|
||||
"memgraph": "https://s3.eu-west-1.amazonaws.com/deps.memgraph.io/dataset/pokec/benchmark/memgraph.cypher",
|
||||
"neo4j": "https://s3.eu-west-1.amazonaws.com/deps.memgraph.io/dataset/pokec/benchmark/neo4j.cypher",
|
||||
}
|
||||
|
||||
PROPERTIES_ON_EDGES = False
|
||||
|
||||
# Helpers used to generate the queries
|
||||
@@ -166,10 +135,7 @@ class Pokec(Dataset):
|
||||
return ("MATCH (n:User {id : $id}) RETURN n", {"id": self._get_random_vertex()})
|
||||
|
||||
def benchmark__arango__single_vertex_write(self):
|
||||
return (
|
||||
"CREATE (n:UserTemp {id : $id}) RETURN n",
|
||||
{"id": random.randint(1, self._num_vertices * 10)},
|
||||
)
|
||||
return ("CREATE (n:UserTemp {id : $id}) RETURN n", {"id": random.randint(1, self._num_vertices * 10)})
|
||||
|
||||
def benchmark__arango__single_edge_write(self):
|
||||
vertex_from, vertex_to = self._get_random_from_to()
|
||||
@@ -181,17 +147,11 @@ class Pokec(Dataset):
|
||||
def benchmark__arango__aggregate(self):
|
||||
return ("MATCH (n:User) RETURN n.age, COUNT(*)", {})
|
||||
|
||||
def benchmark__arango__aggregate_with_distinct(self):
|
||||
return ("MATCH (n:User) RETURN COUNT(DISTINCT n.age)", {})
|
||||
|
||||
def benchmark__arango__aggregate_with_filter(self):
|
||||
return ("MATCH (n:User) WHERE n.age >= 18 RETURN n.age, COUNT(*)", {})
|
||||
|
||||
def benchmark__arango__expansion_1(self):
|
||||
return (
|
||||
"MATCH (s:User {id: $id})-->(n:User) " "RETURN n.id",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
return ("MATCH (s:User {id: $id})-->(n:User) " "RETURN n.id", {"id": self._get_random_vertex()})
|
||||
|
||||
def benchmark__arango__expansion_1_with_filter(self):
|
||||
return (
|
||||
@@ -200,10 +160,7 @@ class Pokec(Dataset):
|
||||
)
|
||||
|
||||
def benchmark__arango__expansion_2(self):
|
||||
return (
|
||||
"MATCH (s:User {id: $id})-->()-->(n:User) " "RETURN DISTINCT n.id",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
return ("MATCH (s:User {id: $id})-->()-->(n:User) " "RETURN DISTINCT n.id", {"id": self._get_random_vertex()})
|
||||
|
||||
def benchmark__arango__expansion_2_with_filter(self):
|
||||
return (
|
||||
@@ -236,10 +193,7 @@ class Pokec(Dataset):
|
||||
)
|
||||
|
||||
def benchmark__arango__neighbours_2(self):
|
||||
return (
|
||||
"MATCH (s:User {id: $id})-[*1..2]->(n:User) " "RETURN DISTINCT n.id",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
return ("MATCH (s:User {id: $id})-[*1..2]->(n:User) " "RETURN DISTINCT n.id", {"id": self._get_random_vertex()})
|
||||
|
||||
def benchmark__arango__neighbours_2_with_filter(self):
|
||||
return (
|
||||
@@ -316,10 +270,7 @@ class Pokec(Dataset):
|
||||
return ("MATCH (n) RETURN min(n.age), max(n.age), avg(n.age)", {})
|
||||
|
||||
def benchmark__match__pattern_cycle(self):
|
||||
return (
|
||||
"MATCH (n:User {id: $id})-[e1]->(m)-[e2]->(n) " "RETURN e1, m, e2",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
return ("MATCH (n:User {id: $id})-[e1]->(m)-[e2]->(n) " "RETURN e1, m, e2", {"id": self._get_random_vertex()})
|
||||
|
||||
def benchmark__match__pattern_long(self):
|
||||
return (
|
||||
@@ -328,16 +279,10 @@ class Pokec(Dataset):
|
||||
)
|
||||
|
||||
def benchmark__match__pattern_short(self):
|
||||
return (
|
||||
"MATCH (n:User {id: $id})-[e]->(m) " "RETURN m LIMIT 1",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
return ("MATCH (n:User {id: $id})-[e]->(m) " "RETURN m LIMIT 1", {"id": self._get_random_vertex()})
|
||||
|
||||
def benchmark__match__vertex_on_label_property(self):
|
||||
return (
|
||||
"MATCH (n:User) WITH n WHERE n.id = $id RETURN n",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
return ("MATCH (n:User) WITH n WHERE n.id = $id RETURN n", {"id": self._get_random_vertex()})
|
||||
|
||||
def benchmark__match__vertex_on_label_property_index(self):
|
||||
return ("MATCH (n:User {id: $id}) RETURN n", {"id": self._get_random_vertex()})
|
||||
@@ -346,133 +291,4 @@ class Pokec(Dataset):
|
||||
return ("MATCH (n {id: $id}) RETURN n", {"id": self._get_random_vertex()})
|
||||
|
||||
def benchmark__update__vertex_on_property(self):
|
||||
return (
|
||||
"MATCH (n {id: $id}) SET n.property = -1",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
|
||||
# Basic benchmark queries
|
||||
|
||||
def benchmark__basic__single_vertex_read_read(self):
|
||||
return ("MATCH (n:User {id : $id}) RETURN n", {"id": self._get_random_vertex()})
|
||||
|
||||
def benchmark__basic__single_vertex_write_write(self):
|
||||
return (
|
||||
"CREATE (n:UserTemp {id : $id}) RETURN n",
|
||||
{"id": random.randint(1, self._num_vertices * 10)},
|
||||
)
|
||||
|
||||
def benchmark__basic__single_vertex_property_update_update(self):
|
||||
return (
|
||||
"MATCH (n {id: $id}) SET n.property = -1",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
|
||||
def benchmark__basic__single_edge_write_write(self):
|
||||
vertex_from, vertex_to = self._get_random_from_to()
|
||||
return (
|
||||
"MATCH (n:User {id: $from}), (m:User {id: $to}) WITH n, m " "CREATE (n)-[e:Temp]->(m) RETURN e",
|
||||
{"from": vertex_from, "to": vertex_to},
|
||||
)
|
||||
|
||||
def benchmark__basic__aggregate_aggregate(self):
|
||||
return ("MATCH (n:User) RETURN n.age, COUNT(*)", {})
|
||||
|
||||
def benchmark__basic__aggregate_count_aggregate(self):
|
||||
return ("MATCH (n) RETURN count(n), count(n.age)", {})
|
||||
|
||||
def benchmark__basic__aggregate_with_filter_aggregate(self):
|
||||
return ("MATCH (n:User) WHERE n.age >= 18 RETURN n.age, COUNT(*)", {})
|
||||
|
||||
def benchmark__basic__min_max_avg_aggregate(self):
|
||||
return ("MATCH (n) RETURN min(n.age), max(n.age), avg(n.age)", {})
|
||||
|
||||
def benchmark__basic__expansion_1_analytical(self):
|
||||
return (
|
||||
"MATCH (s:User {id: $id})-->(n:User) " "RETURN n.id",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
|
||||
def benchmark__basic__expansion_1_with_filter_analytical(self):
|
||||
return (
|
||||
"MATCH (s:User {id: $id})-->(n:User) " "WHERE n.age >= 18 " "RETURN n.id",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
|
||||
def benchmark__basic__expansion_2_analytical(self):
|
||||
return (
|
||||
"MATCH (s:User {id: $id})-->()-->(n:User) " "RETURN DISTINCT n.id",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
|
||||
def benchmark__basic__expansion_2_with_filter_analytical(self):
|
||||
return (
|
||||
"MATCH (s:User {id: $id})-->()-->(n:User) " "WHERE n.age >= 18 " "RETURN DISTINCT n.id",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
|
||||
def benchmark__basic__expansion_3_analytical(self):
|
||||
return (
|
||||
"MATCH (s:User {id: $id})-->()-->()-->(n:User) " "RETURN DISTINCT n.id",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
|
||||
def benchmark__basic__expansion_3_with_filter_analytical(self):
|
||||
return (
|
||||
"MATCH (s:User {id: $id})-->()-->()-->(n:User) " "WHERE n.age >= 18 " "RETURN DISTINCT n.id",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
|
||||
def benchmark__basic__expansion_4_analytical(self):
|
||||
return (
|
||||
"MATCH (s:User {id: $id})-->()-->()-->()-->(n:User) " "RETURN DISTINCT n.id",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
|
||||
def benchmark__basic__expansion_4_with_filter_analytical(self):
|
||||
return (
|
||||
"MATCH (s:User {id: $id})-->()-->()-->()-->(n:User) " "WHERE n.age >= 18 " "RETURN DISTINCT n.id",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
|
||||
def benchmark__basic__neighbours_2_analytical(self):
|
||||
return (
|
||||
"MATCH (s:User {id: $id})-[*1..2]->(n:User) " "RETURN DISTINCT n.id",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
|
||||
def benchmark__basic__neighbours_2_with_filter_analytical(self):
|
||||
return (
|
||||
"MATCH (s:User {id: $id})-[*1..2]->(n:User) " "WHERE n.age >= 18 " "RETURN DISTINCT n.id",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
|
||||
def benchmark__basic__neighbours_2_with_data_analytical(self):
|
||||
return (
|
||||
"MATCH (s:User {id: $id})-[*1..2]->(n:User) " "RETURN DISTINCT n.id, n",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
|
||||
def benchmark__basic__neighbours_2_with_data_and_filter_analytical(self):
|
||||
return (
|
||||
"MATCH (s:User {id: $id})-[*1..2]->(n:User) " "WHERE n.age >= 18 " "RETURN DISTINCT n.id, n",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
|
||||
def benchmark__basic__pattern_cycle_analytical(self):
|
||||
return (
|
||||
"MATCH (n:User {id: $id})-[e1]->(m)-[e2]->(n) " "RETURN e1, m, e2",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
|
||||
def benchmark__basic__pattern_long_analytical(self):
|
||||
return (
|
||||
"MATCH (n1:User {id: $id})-[e1]->(n2)-[e2]->" "(n3)-[e3]->(n4)<-[e4]-(n5) " "RETURN n5 LIMIT 1",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
|
||||
def benchmark__basic__pattern_short_analytical(self):
|
||||
return (
|
||||
"MATCH (n:User {id: $id})-[e]->(m) " "RETURN m LIMIT 1",
|
||||
{"id": self._get_random_vertex()},
|
||||
)
|
||||
return ("MATCH (n {id: $id}) SET n.property = -1", {"id": self._get_random_vertex()})
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run graph database benchmarks on supported databases(Memgraph and Neo4j)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--vendor",
|
||||
nargs=2,
|
||||
action="append",
|
||||
metavar=("vendor_name", "vendor_binary"),
|
||||
help="Forward name and paths to vendors binary"
|
||||
"Example: --vendor memgraph /path/to/binary --vendor neo4j /path/to/binary",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dataset-size",
|
||||
default="small",
|
||||
choices=["small", "medium", "large"],
|
||||
help="Pick a dataset size (small, medium, large)",
|
||||
)
|
||||
|
||||
parser.add_argument("--dataset-group", default="basic", help="Select a group of queries")
|
||||
|
||||
parser.add_argument(
|
||||
"--realistic",
|
||||
nargs=5,
|
||||
action="append",
|
||||
metavar=("num_of_queries", "write", "read", "update", "analytical"),
|
||||
help="Forward config for group run",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--mixed",
|
||||
nargs=6,
|
||||
action="append",
|
||||
metavar=(
|
||||
"num_of_queries",
|
||||
"write",
|
||||
"read",
|
||||
"update",
|
||||
"analytical",
|
||||
"query_percentage",
|
||||
),
|
||||
help="Forward config for query",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def run_full_benchmarks(vendor, binary, dataset_size, dataset_group, realistic, mixed):
|
||||
|
||||
configurations = [
|
||||
# Basic full group test cold
|
||||
[
|
||||
"--export-results",
|
||||
vendor + "_" + dataset_size + "_cold_isolated.json",
|
||||
],
|
||||
# Basic full group test hot
|
||||
[
|
||||
"--export-results",
|
||||
vendor + "_" + dataset_size + "_hot_isolated.json",
|
||||
"--warmup-run",
|
||||
],
|
||||
]
|
||||
|
||||
# Configurations for full workload
|
||||
for count, write, read, update, analytical in realistic:
|
||||
cold = [
|
||||
"--export-results",
|
||||
vendor
|
||||
+ "_"
|
||||
+ dataset_size
|
||||
+ "_cold_realistic_{}_{}_{}_{}_{}.json".format(count, write, read, update, analytical),
|
||||
"--mixed-workload",
|
||||
count,
|
||||
write,
|
||||
read,
|
||||
update,
|
||||
analytical,
|
||||
]
|
||||
|
||||
hot = [
|
||||
"--export-results",
|
||||
vendor
|
||||
+ "_"
|
||||
+ dataset_size
|
||||
+ "_hot_realistic_{}_{}_{}_{}_{}.json".format(count, write, read, update, analytical),
|
||||
"--warmup-run",
|
||||
"--mixed-workload",
|
||||
count,
|
||||
write,
|
||||
read,
|
||||
update,
|
||||
analytical,
|
||||
]
|
||||
configurations.append(cold)
|
||||
configurations.append(hot)
|
||||
|
||||
# Configurations for workload per query
|
||||
for count, write, read, update, analytical, query in mixed:
|
||||
cold = [
|
||||
"--export-results",
|
||||
vendor
|
||||
+ "_"
|
||||
+ dataset_size
|
||||
+ "_cold_mixed_{}_{}_{}_{}_{}_{}.json".format(count, write, read, update, analytical, query),
|
||||
"--mixed-workload",
|
||||
count,
|
||||
write,
|
||||
read,
|
||||
update,
|
||||
analytical,
|
||||
query,
|
||||
]
|
||||
hot = [
|
||||
"--export-results",
|
||||
vendor
|
||||
+ "_"
|
||||
+ dataset_size
|
||||
+ "_hot_mixed_{}_{}_{}_{}_{}_{}.json".format(count, write, read, update, analytical, query),
|
||||
"--warmup-run",
|
||||
"--mixed-workload",
|
||||
count,
|
||||
write,
|
||||
read,
|
||||
update,
|
||||
analytical,
|
||||
query,
|
||||
]
|
||||
configurations.append(cold)
|
||||
configurations.append(hot)
|
||||
|
||||
default_args = [
|
||||
"python3",
|
||||
"benchmark.py",
|
||||
"--vendor-binary",
|
||||
binary,
|
||||
"--vendor-name",
|
||||
vendor,
|
||||
"--num-workers-for-benchmark",
|
||||
"12",
|
||||
"--no-authorization",
|
||||
"pokec/" + dataset_size + "/" + dataset_group + "/*",
|
||||
]
|
||||
|
||||
for config in configurations:
|
||||
full_config = default_args + config
|
||||
print(full_config)
|
||||
subprocess.run(args=full_config, check=True)
|
||||
|
||||
|
||||
def collect_all_results(vendor_name, dataset_size, dataset_group):
|
||||
working_directory = Path().absolute()
|
||||
print(working_directory)
|
||||
results = sorted(working_directory.glob(vendor_name + "_" + dataset_size + "_*.json"))
|
||||
summary = {"pokec": {dataset_size: {dataset_group: {}}}}
|
||||
|
||||
for file in results:
|
||||
if "summary" in file.name:
|
||||
continue
|
||||
f = file.open()
|
||||
data = json.loads(f.read())
|
||||
if data["__run_configuration__"]["condition"] == "hot":
|
||||
for key, value in data["pokec"][dataset_size][dataset_group].items():
|
||||
key_condition = key + "_hot"
|
||||
summary["pokec"][dataset_size][dataset_group][key_condition] = value
|
||||
elif data["__run_configuration__"]["condition"] == "cold":
|
||||
for key, value in data["pokec"][dataset_size][dataset_group].items():
|
||||
key_condition = key + "_cold"
|
||||
summary["pokec"][dataset_size][dataset_group][key_condition] = value
|
||||
|
||||
print(summary)
|
||||
|
||||
json_object = json.dumps(summary, indent=4)
|
||||
print(json_object)
|
||||
with open(vendor_name + "_" + dataset_size + "_summary.json", "w") as f:
|
||||
json.dump(summary, f)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parse_arguments()
|
||||
|
||||
realistic = args.realistic
|
||||
mixed = args.mixed
|
||||
|
||||
vendor_names = {"memgraph", "neo4j"}
|
||||
for vendor_name, vendor_binary in args.vendor:
|
||||
path = Path(vendor_binary)
|
||||
if vendor_name.lower() in vendor_names and (path.is_file() or path.is_dir()):
|
||||
run_full_benchmarks(
|
||||
vendor_name,
|
||||
vendor_binary,
|
||||
args.dataset_size,
|
||||
args.dataset_group,
|
||||
realistic,
|
||||
mixed,
|
||||
)
|
||||
collect_all_results(vendor_name, args.dataset_size, args.dataset_group)
|
||||
else:
|
||||
raise Exception(
|
||||
"Check that vendor: {} is supported and you are passing right path: {} to binary.".format(
|
||||
vendor_name, path
|
||||
)
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2022 Memgraph Ltd.
|
||||
# Copyright 2021 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
|
||||
@@ -15,9 +15,7 @@ import os
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def wait_for_server(port, delay=0.1):
|
||||
@@ -52,26 +50,13 @@ def _get_usage(pid):
|
||||
return {"cpu": total_cpu, "memory": peak_rss}
|
||||
|
||||
|
||||
def _get_current_usage(pid):
|
||||
rss = 0
|
||||
with open("/proc/{}/status".format(pid)) as f:
|
||||
for row in f:
|
||||
tmp = row.split()
|
||||
if tmp[0] == "VmRSS:":
|
||||
rss = int(tmp[1])
|
||||
return rss / 1024
|
||||
|
||||
|
||||
class Memgraph:
|
||||
def __init__(self, memgraph_binary, temporary_dir, properties_on_edges, bolt_port, performance_tracking):
|
||||
def __init__(self, memgraph_binary, temporary_dir, properties_on_edges, bolt_port):
|
||||
self._memgraph_binary = memgraph_binary
|
||||
self._directory = tempfile.TemporaryDirectory(dir=temporary_dir)
|
||||
self._properties_on_edges = properties_on_edges
|
||||
self._proc_mg = None
|
||||
self._bolt_port = bolt_port
|
||||
self.performance_tracking = performance_tracking
|
||||
self._stop_event = threading.Event()
|
||||
self._rss = []
|
||||
atexit.register(self._cleanup)
|
||||
|
||||
# Determine Memgraph version
|
||||
@@ -118,249 +103,25 @@ class Memgraph:
|
||||
self._proc_mg = None
|
||||
return ret, usage
|
||||
|
||||
def start_preparation(self, workload):
|
||||
if self.performance_tracking:
|
||||
p = threading.Thread(target=self.res_background_tracking, args=(self._rss, self._stop_event))
|
||||
self._stop_event.clear()
|
||||
self._rss.clear()
|
||||
p.start()
|
||||
self._start(storage_snapshot_on_exit=True)
|
||||
def start_preparation(self):
|
||||
if self._memgraph_version >= (0, 50, 0):
|
||||
self._start(storage_snapshot_on_exit=True)
|
||||
else:
|
||||
self._start(snapshot_on_exit=True)
|
||||
|
||||
def start_benchmark(self, workload):
|
||||
if self.performance_tracking:
|
||||
p = threading.Thread(target=self.res_background_tracking, args=(self._rss, self._stop_event))
|
||||
self._stop_event.clear()
|
||||
self._rss.clear()
|
||||
p.start()
|
||||
self._start(storage_recover_on_startup=True)
|
||||
def start_benchmark(self):
|
||||
# TODO: support custom benchmarking config files!
|
||||
if self._memgraph_version >= (0, 50, 0):
|
||||
self._start(storage_recover_on_startup=True)
|
||||
else:
|
||||
self._start(db_recover_on_startup=True)
|
||||
|
||||
def res_background_tracking(self, res, stop_event):
|
||||
print("Started rss tracking.")
|
||||
while not stop_event.is_set():
|
||||
if self._proc_mg != None:
|
||||
self._rss.append(_get_current_usage(self._proc_mg.pid))
|
||||
time.sleep(0.05)
|
||||
print("Stopped rss tracking. ")
|
||||
|
||||
def dump_rss(self, workload):
|
||||
file_name = workload + "_rss"
|
||||
Path.mkdir(Path().cwd() / "memgraph_memory", exist_ok=True)
|
||||
file = Path(Path().cwd() / "memgraph_memory" / file_name)
|
||||
file.touch()
|
||||
with file.open("r+") as f:
|
||||
for rss in self._rss:
|
||||
f.write(str(rss))
|
||||
f.write("\n")
|
||||
f.close()
|
||||
|
||||
def stop(self, workload):
|
||||
if self.performance_tracking:
|
||||
self._stop_event.set()
|
||||
self.dump_rss(workload)
|
||||
def stop(self):
|
||||
ret, usage = self._cleanup()
|
||||
assert ret == 0, "The database process exited with a non-zero " "status ({})!".format(ret)
|
||||
return usage
|
||||
|
||||
|
||||
class Neo4j:
|
||||
def __init__(self, neo4j_path, temporary_dir, bolt_port, performance_tracking):
|
||||
self._neo4j_path = Path(neo4j_path)
|
||||
self._neo4j_binary = Path(neo4j_path) / "bin" / "neo4j"
|
||||
self._neo4j_config = Path(neo4j_path) / "conf" / "neo4j.conf"
|
||||
self._neo4j_pid = Path(neo4j_path) / "run" / "neo4j.pid"
|
||||
self._neo4j_admin = Path(neo4j_path) / "bin" / "neo4j-admin"
|
||||
self.performance_tracking = performance_tracking
|
||||
self._stop_event = threading.Event()
|
||||
self._rss = []
|
||||
|
||||
if not self._neo4j_binary.is_file():
|
||||
raise Exception("Wrong path to binary!")
|
||||
self._directory = tempfile.TemporaryDirectory(dir=temporary_dir)
|
||||
self._bolt_port = bolt_port
|
||||
atexit.register(self._cleanup)
|
||||
configs = []
|
||||
memory_flag = "server.jvm.additional=-XX:NativeMemoryTracking=detail"
|
||||
auth_flag = "dbms.security.auth_enabled=false"
|
||||
|
||||
if self.performance_tracking:
|
||||
configs.append(memory_flag)
|
||||
else:
|
||||
lines = []
|
||||
with self._neo4j_config.open("r") as file:
|
||||
lines = file.readlines()
|
||||
file.close()
|
||||
|
||||
for i in range(0, len(lines)):
|
||||
if lines[i].strip("\n") == memory_flag:
|
||||
print("Clear up config flag: " + memory_flag)
|
||||
lines[i] = "\n"
|
||||
print(lines[i])
|
||||
|
||||
with self._neo4j_config.open("w") as file:
|
||||
file.writelines(lines)
|
||||
file.close()
|
||||
|
||||
configs.append(auth_flag)
|
||||
print("Check neo4j config flags:")
|
||||
for conf in configs:
|
||||
with self._neo4j_config.open("r+") as file:
|
||||
lines = file.readlines()
|
||||
line_exist = False
|
||||
for line in lines:
|
||||
if conf == line.rstrip():
|
||||
line_exist = True
|
||||
print("Config line exist at line: " + str(lines.index(line)))
|
||||
print("Line content: " + line)
|
||||
file.close()
|
||||
break
|
||||
if not line_exist:
|
||||
print("Setting config line: " + conf)
|
||||
file.write(conf)
|
||||
file.write("\n")
|
||||
file.close()
|
||||
|
||||
def __del__(self):
|
||||
self._cleanup()
|
||||
atexit.unregister(self._cleanup)
|
||||
|
||||
def _start(self, **kwargs):
|
||||
if self._neo4j_pid.exists():
|
||||
raise Exception("The database process is already running!")
|
||||
args = _convert_args_to_flags(self._neo4j_binary, "start", **kwargs)
|
||||
start_proc = subprocess.run(args, check=True)
|
||||
time.sleep(5)
|
||||
if self._neo4j_pid.exists():
|
||||
print("Neo4j started!")
|
||||
else:
|
||||
raise Exception("The database process died prematurely!")
|
||||
print("Run server check:")
|
||||
wait_for_server(self._bolt_port)
|
||||
|
||||
def _cleanup(self):
|
||||
if self._neo4j_pid.exists():
|
||||
pid = self._neo4j_pid.read_text()
|
||||
print("Clean up: " + pid)
|
||||
usage = _get_usage(pid)
|
||||
|
||||
exit_proc = subprocess.run(args=[self._neo4j_binary, "stop"], capture_output=True, check=True)
|
||||
return exit_proc.returncode, usage
|
||||
else:
|
||||
return 0
|
||||
|
||||
def start_preparation(self, workload):
|
||||
if self.performance_tracking:
|
||||
p = threading.Thread(target=self.res_background_tracking, args=(self._rss, self._stop_event))
|
||||
self._stop_event.clear()
|
||||
self._rss.clear()
|
||||
p.start()
|
||||
|
||||
# Start DB
|
||||
self._start()
|
||||
|
||||
if self.performance_tracking:
|
||||
self.get_memory_usage("start_" + workload)
|
||||
|
||||
def start_benchmark(self, workload):
|
||||
if self.performance_tracking:
|
||||
p = threading.Thread(target=self.res_background_tracking, args=(self._rss, self._stop_event))
|
||||
self._stop_event.clear()
|
||||
self._rss.clear()
|
||||
p.start()
|
||||
# Start DB
|
||||
self._start()
|
||||
|
||||
if self.performance_tracking:
|
||||
self.get_memory_usage("start_" + workload)
|
||||
|
||||
def dump_db(self, path):
|
||||
print("Dumping the neo4j database...")
|
||||
if self._neo4j_pid.exists():
|
||||
raise Exception("Cannot dump DB because it is running.")
|
||||
else:
|
||||
subprocess.run(
|
||||
args=[
|
||||
self._neo4j_admin,
|
||||
"database",
|
||||
"dump",
|
||||
"--overwrite-destination=false",
|
||||
"--to-path",
|
||||
path,
|
||||
"neo4j",
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
def load_db_from_dump(self, path):
|
||||
print("Loading the neo4j database from dump...")
|
||||
if self._neo4j_pid.exists():
|
||||
raise Exception("Cannot dump DB because it is running.")
|
||||
else:
|
||||
subprocess.run(
|
||||
args=[
|
||||
self._neo4j_admin,
|
||||
"database",
|
||||
"load",
|
||||
"--from-path=" + path,
|
||||
"--overwrite-destination=true",
|
||||
"neo4j",
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
def res_background_tracking(self, res, stop_event):
|
||||
print("Started rss tracking.")
|
||||
while not stop_event.is_set():
|
||||
if self._neo4j_pid.exists():
|
||||
pid = self._neo4j_pid.read_text()
|
||||
self._rss.append(_get_current_usage(pid))
|
||||
time.sleep(0.05)
|
||||
print("Stopped rss tracking. ")
|
||||
|
||||
def is_stopped(self):
|
||||
pid_file = self._neo4j_path / "run" / "neo4j.pid"
|
||||
if pid_file.exists():
|
||||
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def stop(self, workload):
|
||||
if self.performance_tracking:
|
||||
self._stop_event.set()
|
||||
self.get_memory_usage("stop_" + workload)
|
||||
self.dump_rss(workload)
|
||||
ret, usage = self._cleanup()
|
||||
assert ret == 0, "The database process exited with a non-zero " "status ({})!".format(ret)
|
||||
return usage
|
||||
|
||||
def dump_rss(self, workload):
|
||||
file_name = workload + "_rss"
|
||||
Path.mkdir(Path().cwd() / "neo4j_memory", exist_ok=True)
|
||||
file = Path(Path().cwd() / "neo4j_memory" / file_name)
|
||||
file.touch()
|
||||
with file.open("r+") as f:
|
||||
for rss in self._rss:
|
||||
f.write(str(rss))
|
||||
f.write("\n")
|
||||
f.close()
|
||||
|
||||
def get_memory_usage(self, workload):
|
||||
Path.mkdir(Path().cwd() / "neo4j_memory", exist_ok=True)
|
||||
|
||||
pid = self._neo4j_pid.read_text()
|
||||
memory_usage = subprocess.run(args=["jcmd", pid, "VM.native_memory"], capture_output=True, text=True)
|
||||
file = Path(Path().cwd() / "neo4j_memory" / workload)
|
||||
if file.exists():
|
||||
with file.open("r+") as f:
|
||||
f.write(memory_usage.stdout)
|
||||
f.close()
|
||||
else:
|
||||
file.touch()
|
||||
with file.open("r+") as f:
|
||||
f.write(memory_usage.stdout)
|
||||
f.close()
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(
|
||||
self, client_binary: str, temporary_directory: str, bolt_port: int, username: str = "", password: str = ""
|
||||
@@ -398,7 +159,7 @@ class Client:
|
||||
password=self._password,
|
||||
port=self._bolt_port,
|
||||
)
|
||||
ret = subprocess.run(args, capture_output=True, check=True)
|
||||
ret = subprocess.run(args, stdout=subprocess.PIPE, check=True)
|
||||
data = ret.stdout.decode("utf-8").strip().split("\n")
|
||||
# data = [x for x in data if not x.startswith("[")]
|
||||
return list(map(json.loads, data))
|
||||
|
||||
@@ -113,9 +113,8 @@ TEST_F(CppApiTestFixture, TestList) {
|
||||
auto a = mgp::Value("a");
|
||||
list_2.Append(a);
|
||||
list_2.AppendExtend(a);
|
||||
list_2.AppendExtend(mgp::Value("b"));
|
||||
|
||||
ASSERT_EQ(list_2.Size(), 3);
|
||||
ASSERT_EQ(list_2.Size(), 2);
|
||||
|
||||
std::vector<mgp::Value> values{mgp::Value("a"), mgp::Value("b"), mgp::Value("c")};
|
||||
auto list_3 = mgp::List(values);
|
||||
@@ -124,18 +123,6 @@ TEST_F(CppApiTestFixture, TestList) {
|
||||
|
||||
auto list_4 = mgp::List({mgp::Value("d"), mgp::Value("e"), mgp::Value("f")});
|
||||
ASSERT_EQ(list_4.Size(), 3);
|
||||
|
||||
// Use copy assignment
|
||||
auto list_x = list_1;
|
||||
|
||||
// Use move assignment
|
||||
std::vector<mgp::List> vector_x;
|
||||
vector_x.push_back(mgp::List());
|
||||
|
||||
// Use Value copy constructor
|
||||
auto value_x = mgp::Value(list_1);
|
||||
// Use Value move constructor
|
||||
auto value_y = mgp::Value(mgp::List());
|
||||
}
|
||||
|
||||
TEST_F(CppApiTestFixture, TestMap) {
|
||||
@@ -164,20 +151,6 @@ TEST_F(CppApiTestFixture, TestMap) {
|
||||
auto map_3 = mgp::Map({p_1, p_2});
|
||||
|
||||
ASSERT_EQ(map_3.Size(), 2);
|
||||
|
||||
// Use copy assignment
|
||||
auto map_x = map_1;
|
||||
|
||||
// Use move assignment
|
||||
std::vector<mgp::Map> vector_x;
|
||||
vector_x.push_back(mgp::Map());
|
||||
|
||||
// Use Value copy constructor
|
||||
auto value_x = mgp::Value(map_1);
|
||||
// Use Value move constructor
|
||||
auto value_y = mgp::Value(mgp::Map());
|
||||
|
||||
auto value_z = value_x;
|
||||
}
|
||||
|
||||
TEST_F(CppApiTestFixture, TestNode) {
|
||||
@@ -213,18 +186,6 @@ TEST_F(CppApiTestFixture, TestNode) {
|
||||
}
|
||||
|
||||
ASSERT_EQ(count_in_relationships, 0);
|
||||
|
||||
// Use copy assignment
|
||||
auto node_x = node_1;
|
||||
|
||||
// Use move assignment
|
||||
std::vector<mgp::Node> vector_x;
|
||||
vector_x.push_back(graph.CreateNode());
|
||||
|
||||
// Use Value copy constructor
|
||||
auto value_x = mgp::Value(node_1);
|
||||
// Use Value move constructor
|
||||
auto value_y = mgp::Value(graph.CreateNode());
|
||||
}
|
||||
|
||||
TEST_F(CppApiTestFixture, TestNodeWithNeighbors) {
|
||||
@@ -233,10 +194,8 @@ TEST_F(CppApiTestFixture, TestNodeWithNeighbors) {
|
||||
|
||||
auto node_1 = graph.CreateNode();
|
||||
auto node_2 = graph.CreateNode();
|
||||
auto node_3 = graph.CreateNode();
|
||||
|
||||
auto relationship_1 = graph.CreateRelationship(node_1, node_2, "edge_type");
|
||||
auto relationship_2 = graph.CreateRelationship(node_1, node_3, "edge_type");
|
||||
auto relationship = graph.CreateRelationship(node_1, node_2, "edge_type");
|
||||
|
||||
int count_out_relationships = 0;
|
||||
int count_in_relationships = 0;
|
||||
@@ -245,13 +204,13 @@ TEST_F(CppApiTestFixture, TestNodeWithNeighbors) {
|
||||
count_out_relationships++;
|
||||
}
|
||||
|
||||
for (const auto _ : node.InRelationships()) {
|
||||
for (const auto _ : node.OutRelationships()) {
|
||||
count_in_relationships++;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_EQ(count_out_relationships, 2);
|
||||
ASSERT_EQ(count_in_relationships, 2);
|
||||
ASSERT_EQ(count_out_relationships, 1);
|
||||
ASSERT_EQ(count_in_relationships, 1);
|
||||
}
|
||||
|
||||
TEST_F(CppApiTestFixture, TestRelationship) {
|
||||
@@ -267,18 +226,6 @@ TEST_F(CppApiTestFixture, TestRelationship) {
|
||||
ASSERT_EQ(relationship.Properties().Size(), 0);
|
||||
ASSERT_EQ(relationship.From().Id(), node_1.Id());
|
||||
ASSERT_EQ(relationship.To().Id(), node_2.Id());
|
||||
|
||||
// Use copy assignment
|
||||
auto relationship_x = relationship;
|
||||
|
||||
// Use move assignment
|
||||
std::vector<mgp::Relationship> vector_x;
|
||||
vector_x.push_back(graph.CreateRelationship(node_2, node_1, "relationship_x"));
|
||||
|
||||
// Use Value copy constructor
|
||||
auto value_x = mgp::Value(relationship);
|
||||
// Use Value move constructor
|
||||
auto value_y = mgp::Value(graph.CreateRelationship(node_2, node_1, "edge_type"));
|
||||
}
|
||||
|
||||
TEST_F(CppApiTestFixture, TestPath) {
|
||||
@@ -300,18 +247,6 @@ TEST_F(CppApiTestFixture, TestPath) {
|
||||
ASSERT_EQ(path.Length(), 1);
|
||||
ASSERT_EQ(path.GetNodeAt(0).Id(), node_0.Id());
|
||||
ASSERT_EQ(path.GetRelationshipAt(0).Id(), relationship.Id());
|
||||
|
||||
// Use copy assignment
|
||||
auto path_x = path;
|
||||
|
||||
// Use move assignment
|
||||
std::vector<mgp::Path> vector_x;
|
||||
vector_x.push_back(mgp::Path(node_0));
|
||||
|
||||
// Use Value copy constructor
|
||||
auto value_x = mgp::Value(path);
|
||||
// Use Value move constructor
|
||||
auto value_y = mgp::Value(mgp::Path(node_0));
|
||||
}
|
||||
|
||||
TEST_F(CppApiTestFixture, TestDate) {
|
||||
@@ -327,18 +262,6 @@ TEST_F(CppApiTestFixture, TestDate) {
|
||||
|
||||
ASSERT_EQ(date_1, date_2);
|
||||
ASSERT_NE(date_2, date_3);
|
||||
|
||||
// Use copy assignment
|
||||
auto date_x = date_1;
|
||||
|
||||
// Use move assignment
|
||||
std::vector<mgp::Date> vector_x;
|
||||
vector_x.push_back(mgp::Date("2022-04-09"));
|
||||
|
||||
// Use Value copy constructor
|
||||
auto value_x = mgp::Value(date_1);
|
||||
// Use Value move constructor
|
||||
auto value_y = mgp::Value(mgp::Date("2022-04-09"));
|
||||
}
|
||||
|
||||
TEST_F(CppApiTestFixture, TestLocalTime) {
|
||||
@@ -355,18 +278,6 @@ TEST_F(CppApiTestFixture, TestLocalTime) {
|
||||
|
||||
ASSERT_EQ(lt_1, lt_2);
|
||||
ASSERT_NE(lt_2, lt_3);
|
||||
|
||||
// Use copy assignment
|
||||
auto lt_x = lt_1;
|
||||
|
||||
// Use move assignment
|
||||
std::vector<mgp::LocalTime> vector_x;
|
||||
vector_x.push_back(mgp::LocalTime("09:15:00"));
|
||||
|
||||
// Use Value copy constructor
|
||||
auto value_x = mgp::Value(lt_1);
|
||||
// Use Value move constructor
|
||||
auto value_y = mgp::Value(mgp::LocalTime("09:15:00"));
|
||||
}
|
||||
|
||||
TEST_F(CppApiTestFixture, TestLocalDateTime) {
|
||||
@@ -384,38 +295,14 @@ TEST_F(CppApiTestFixture, TestLocalDateTime) {
|
||||
ASSERT_EQ(ldt_1.Timestamp() >= 0, true);
|
||||
|
||||
ASSERT_EQ(ldt_1, ldt_2);
|
||||
|
||||
// Use copy assignment
|
||||
auto ldt_x = ldt_1;
|
||||
|
||||
// Use move assignment
|
||||
std::vector<mgp::LocalDateTime> vector_x;
|
||||
vector_x.push_back(mgp::LocalDateTime("2021-10-05T14:15:00"));
|
||||
|
||||
// Use Value copy constructor
|
||||
auto value_x = mgp::Value(ldt_1);
|
||||
// Use Value move constructor
|
||||
auto value_y = mgp::Value(mgp::LocalDateTime("2021-10-05T14:15:00"));
|
||||
}
|
||||
|
||||
TEST_F(CppApiTestFixture, TestDuration) {
|
||||
auto duration_1 = mgp::Duration("PT2M2.33S");
|
||||
auto duration_2 = mgp::Duration(1465355);
|
||||
auto duration_3 = mgp::Duration(5, 14, 15, 0, 0, 0);
|
||||
auto duration_2 = mgp::Duration("PT2M2.33S");
|
||||
auto duration_3 = mgp::Duration(1465355);
|
||||
auto duration_4 = mgp::Duration(5, 14, 15, 0, 0, 0);
|
||||
|
||||
ASSERT_EQ(duration_2.Microseconds(), 1465355);
|
||||
ASSERT_NE(duration_1, duration_2);
|
||||
ASSERT_EQ(duration_3.Microseconds(), 1465355);
|
||||
ASSERT_NE(duration_2, duration_3);
|
||||
|
||||
// Use copy assignment
|
||||
auto duration_x = duration_1;
|
||||
|
||||
// Use move assignment
|
||||
std::vector<mgp::Duration> vector_x;
|
||||
vector_x.push_back(mgp::Duration("PT2M2.33S"));
|
||||
|
||||
// Use Value copy constructor
|
||||
auto value_x = mgp::Value(duration_1);
|
||||
// Use Value move constructor
|
||||
auto value_y = mgp::Value(mgp::Duration("PT2M2.33S"));
|
||||
ASSERT_NE(duration_3, duration_4);
|
||||
}
|
||||
|
||||
@@ -853,7 +853,7 @@ TEST_F(InterpreterTest, ProfileQueryWithLiterals) {
|
||||
auto stream = Interpret("PROFILE UNWIND range(1, 1000) AS x CREATE (:Node {id: x});", {});
|
||||
std::vector<std::string> expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"};
|
||||
EXPECT_EQ(stream.GetHeader(), expected_header);
|
||||
std::vector<std::string> expected_rows{"* EmptyResult", "* CreateNode", "* Unwind", "* Once"};
|
||||
std::vector<std::string> expected_rows{"* CreateNode", "* Unwind", "* Once"};
|
||||
ASSERT_EQ(stream.GetResults().size(), expected_rows.size());
|
||||
auto expected_it = expected_rows.begin();
|
||||
for (const auto &row : stream.GetResults()) {
|
||||
|
||||
@@ -643,70 +643,17 @@ TEST_F(PrintToJsonTest, Aggregate) {
|
||||
{
|
||||
"value" : "(PropertyLookup (Identifier \"node\") \"value\")",
|
||||
"op" : "sum",
|
||||
"output_symbol" : "sum",
|
||||
"distinct" : false
|
||||
"output_symbol" : "sum"
|
||||
},
|
||||
{
|
||||
"value" : "(PropertyLookup (Identifier \"node\") \"value\")",
|
||||
"key" : "(PropertyLookup (Identifier \"node\") \"color\")",
|
||||
"op" : "collect",
|
||||
"output_symbol" : "map",
|
||||
"distinct" : false
|
||||
"output_symbol" : "map"
|
||||
},
|
||||
{
|
||||
"op": "count",
|
||||
"output_symbol": "count",
|
||||
"distinct" : false
|
||||
}
|
||||
],
|
||||
"group_by" : [
|
||||
"(PropertyLookup (Identifier \"node\") \"type\")"
|
||||
],
|
||||
"remember" : ["node"],
|
||||
"input" : {
|
||||
"name" : "ScanAll",
|
||||
"output_symbol" : "node",
|
||||
"input" : { "name" : "Once" }
|
||||
}
|
||||
})sep");
|
||||
}
|
||||
|
||||
TEST_F(PrintToJsonTest, AggregateWithDistinct) {
|
||||
memgraph::storage::PropertyId value = dba.NameToProperty("value");
|
||||
memgraph::storage::PropertyId color = dba.NameToProperty("color");
|
||||
memgraph::storage::PropertyId type = dba.NameToProperty("type");
|
||||
auto node_sym = GetSymbol("node");
|
||||
std::shared_ptr<LogicalOperator> last_op = std::make_shared<ScanAll>(nullptr, node_sym);
|
||||
last_op = std::make_shared<plan::Aggregate>(
|
||||
last_op,
|
||||
std::vector<Aggregate::Element>{
|
||||
{PROPERTY_LOOKUP("node", value), nullptr, Aggregation::Op::SUM, GetSymbol("sum"), true},
|
||||
{PROPERTY_LOOKUP("node", value), PROPERTY_LOOKUP("node", color), Aggregation::Op::COLLECT_MAP,
|
||||
GetSymbol("map"), true},
|
||||
{nullptr, nullptr, Aggregation::Op::COUNT, GetSymbol("count"), true}},
|
||||
std::vector<Expression *>{PROPERTY_LOOKUP("node", type)}, std::vector<Symbol>{node_sym});
|
||||
|
||||
Check(last_op.get(), R"sep(
|
||||
{
|
||||
"name" : "Aggregate",
|
||||
"aggregations" : [
|
||||
{
|
||||
"value" : "(PropertyLookup (Identifier \"node\") \"value\")",
|
||||
"op" : "sum",
|
||||
"output_symbol" : "sum",
|
||||
"distinct" : true
|
||||
},
|
||||
{
|
||||
"value" : "(PropertyLookup (Identifier \"node\") \"value\")",
|
||||
"key" : "(PropertyLookup (Identifier \"node\") \"color\")",
|
||||
"op" : "collect",
|
||||
"output_symbol" : "map",
|
||||
"distinct" : true
|
||||
},
|
||||
{
|
||||
"op": "count",
|
||||
"output_symbol": "count",
|
||||
"distinct" : true
|
||||
"output_symbol": "count"
|
||||
}
|
||||
],
|
||||
"group_by" : [
|
||||
|
||||
@@ -549,15 +549,12 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec
|
||||
#define LESS_EQ(expr1, expr2) storage.Create<memgraph::query::LessEqualOperator>((expr1), (expr2))
|
||||
#define GREATER(expr1, expr2) storage.Create<memgraph::query::GreaterOperator>((expr1), (expr2))
|
||||
#define GREATER_EQ(expr1, expr2) storage.Create<memgraph::query::GreaterEqualOperator>((expr1), (expr2))
|
||||
#define SUM(expr, distinct) \
|
||||
storage.Create<memgraph::query::Aggregation>((expr), nullptr, memgraph::query::Aggregation::Op::SUM, (distinct))
|
||||
#define COUNT(expr, distinct) \
|
||||
storage.Create<memgraph::query::Aggregation>((expr), nullptr, memgraph::query::Aggregation::Op::COUNT, (distinct))
|
||||
#define AVG(expr, distinct) \
|
||||
storage.Create<memgraph::query::Aggregation>((expr), nullptr, memgraph::query::Aggregation::Op::AVG, (distinct))
|
||||
#define COLLECT_LIST(expr, distinct) \
|
||||
storage.Create<memgraph::query::Aggregation>((expr), nullptr, memgraph::query::Aggregation::Op::COLLECT_LIST, \
|
||||
(distinct))
|
||||
#define SUM(expr) storage.Create<memgraph::query::Aggregation>((expr), nullptr, memgraph::query::Aggregation::Op::SUM)
|
||||
#define COUNT(expr) \
|
||||
storage.Create<memgraph::query::Aggregation>((expr), nullptr, memgraph::query::Aggregation::Op::COUNT)
|
||||
#define AVG(expr) storage.Create<memgraph::query::Aggregation>((expr), nullptr, memgraph::query::Aggregation::Op::AVG)
|
||||
#define COLLECT_LIST(expr) \
|
||||
storage.Create<memgraph::query::Aggregation>((expr), nullptr, memgraph::query::Aggregation::Op::COLLECT_LIST)
|
||||
#define EQ(expr1, expr2) storage.Create<memgraph::query::EqualOperator>((expr1), (expr2))
|
||||
#define NEQ(expr1, expr2) storage.Create<memgraph::query::NotEqualOperator>((expr1), (expr2))
|
||||
#define AND(expr1, expr2) storage.Create<memgraph::query::AndOperator>((expr1), (expr2))
|
||||
|
||||
@@ -596,7 +596,7 @@ TEST_F(ExpressionEvaluatorTest, LabelsTest) {
|
||||
}
|
||||
|
||||
TEST_F(ExpressionEvaluatorTest, Aggregation) {
|
||||
auto aggr = storage.Create<Aggregation>(storage.Create<PrimitiveLiteral>(42), nullptr, Aggregation::Op::COUNT, false);
|
||||
auto aggr = storage.Create<Aggregation>(storage.Create<PrimitiveLiteral>(42), nullptr, Aggregation::Op::COUNT);
|
||||
auto aggr_sym = symbol_table.CreateSymbol("aggr", true);
|
||||
aggr->MapTo(aggr_sym);
|
||||
frame[aggr_sym] = TypedValue(1);
|
||||
|
||||
@@ -121,14 +121,14 @@ TYPED_TEST(TestPlanner, CreateExpand) {
|
||||
FakeDbAccessor dba;
|
||||
auto relationship = "relationship";
|
||||
auto *query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m")))));
|
||||
CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectEmptyResult());
|
||||
CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestPlanner, CreateMultipleNode) {
|
||||
// Test CREATE (n), (m)
|
||||
AstStorage storage;
|
||||
auto *query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n")), PATTERN(NODE("m")))));
|
||||
CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateNode(), ExpectEmptyResult());
|
||||
CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateNode());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestPlanner, CreateNodeExpandNode) {
|
||||
@@ -138,8 +138,7 @@ TYPED_TEST(TestPlanner, CreateNodeExpandNode) {
|
||||
auto relationship = "rel";
|
||||
auto *query = QUERY(SINGLE_QUERY(
|
||||
CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m")), PATTERN(NODE("l")))));
|
||||
CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectCreateNode(),
|
||||
ExpectEmptyResult());
|
||||
CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectCreateNode());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestPlanner, CreateNamedPattern) {
|
||||
@@ -149,8 +148,7 @@ TYPED_TEST(TestPlanner, CreateNamedPattern) {
|
||||
auto relationship = "rel";
|
||||
auto *query =
|
||||
QUERY(SINGLE_QUERY(CREATE(NAMED_PATTERN("p", NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m")))));
|
||||
CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectConstructNamedPath(),
|
||||
ExpectEmptyResult());
|
||||
CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectConstructNamedPath());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestPlanner, MatchCreateExpand) {
|
||||
@@ -160,7 +158,7 @@ TYPED_TEST(TestPlanner, MatchCreateExpand) {
|
||||
auto relationship = "relationship";
|
||||
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))),
|
||||
CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {relationship}), NODE("m")))));
|
||||
CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectCreateExpand(), ExpectEmptyResult());
|
||||
CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectCreateExpand());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestPlanner, MatchLabeledNodes) {
|
||||
@@ -262,7 +260,7 @@ TYPED_TEST(TestPlanner, MatchDelete) {
|
||||
// Test MATCH (n) DELETE n
|
||||
AstStorage storage;
|
||||
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), DELETE(IDENT("n"))));
|
||||
CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectDelete(), ExpectEmptyResult());
|
||||
CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectDelete());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestPlanner, MatchNodeSet) {
|
||||
@@ -273,8 +271,7 @@ TYPED_TEST(TestPlanner, MatchNodeSet) {
|
||||
auto label = "label";
|
||||
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), SET(PROPERTY_LOOKUP("n", prop), LITERAL(42)),
|
||||
SET("n", IDENT("n")), SET("n", {label})));
|
||||
CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectSetProperty(), ExpectSetProperties(), ExpectSetLabels(),
|
||||
ExpectEmptyResult());
|
||||
CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectSetProperty(), ExpectSetProperties(), ExpectSetLabels());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestPlanner, MatchRemove) {
|
||||
@@ -285,8 +282,7 @@ TYPED_TEST(TestPlanner, MatchRemove) {
|
||||
auto label = "label";
|
||||
auto *query =
|
||||
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), REMOVE(PROPERTY_LOOKUP("n", prop)), REMOVE("n", {label})));
|
||||
CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectRemoveProperty(), ExpectRemoveLabels(),
|
||||
ExpectEmptyResult());
|
||||
CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectRemoveProperty(), ExpectRemoveLabels());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestPlanner, MatchMultiPattern) {
|
||||
@@ -391,8 +387,7 @@ TYPED_TEST(TestPlanner, CreateMultiExpand) {
|
||||
AstStorage storage;
|
||||
auto *query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"), EDGE("r", Direction::OUT, {r}), NODE("m")),
|
||||
PATTERN(NODE("n"), EDGE("p", Direction::OUT, {p}), NODE("l")))));
|
||||
CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectCreateExpand(),
|
||||
ExpectEmptyResult());
|
||||
CheckPlan<TypeParam>(query, storage, ExpectCreateNode(), ExpectCreateExpand(), ExpectCreateExpand());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestPlanner, MatchWithSumWhereReturn) {
|
||||
@@ -401,7 +396,7 @@ TYPED_TEST(TestPlanner, MatchWithSumWhereReturn) {
|
||||
FakeDbAccessor dba;
|
||||
auto prop = dba.Property("prop");
|
||||
AstStorage storage;
|
||||
auto sum = SUM(PROPERTY_LOOKUP("n", prop), false);
|
||||
auto sum = SUM(PROPERTY_LOOKUP("n", prop));
|
||||
auto literal = LITERAL(42);
|
||||
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WITH(ADD(sum, literal), AS("sum")),
|
||||
WHERE(LESS(IDENT("sum"), LITERAL(42))), RETURN("sum", AS("result"))));
|
||||
@@ -415,7 +410,7 @@ TYPED_TEST(TestPlanner, MatchReturnSum) {
|
||||
auto prop1 = dba.Property("prop1");
|
||||
auto prop2 = dba.Property("prop2");
|
||||
AstStorage storage;
|
||||
auto sum = SUM(PROPERTY_LOOKUP("n", prop1), false);
|
||||
auto sum = SUM(PROPERTY_LOOKUP("n", prop1));
|
||||
auto n_prop2 = PROPERTY_LOOKUP("n", prop2);
|
||||
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN(sum, AS("sum"), n_prop2, AS("group"))));
|
||||
auto aggr = ExpectAggregate({sum}, {n_prop2});
|
||||
@@ -431,54 +426,7 @@ TYPED_TEST(TestPlanner, CreateWithSum) {
|
||||
AstStorage storage;
|
||||
auto ident_n = IDENT("n");
|
||||
auto n_prop = PROPERTY_LOOKUP(ident_n, prop);
|
||||
auto sum = SUM(n_prop, false);
|
||||
auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), WITH(sum, AS("sum"))));
|
||||
auto symbol_table = memgraph::query::MakeSymbolTable(query);
|
||||
auto acc = ExpectAccumulate({symbol_table.at(*ident_n)});
|
||||
auto aggr = ExpectAggregate({sum}, {});
|
||||
auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query);
|
||||
// We expect both the accumulation and aggregation because the part before
|
||||
// WITH updates the database.
|
||||
CheckPlan(planner.plan(), symbol_table, ExpectCreateNode(), acc, aggr, ExpectProduce());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestPlanner, MatchWithSumWithDistinctWhereReturn) {
|
||||
// Test MATCH (n) WITH SUM(DISTINCT n.prop) + 42 AS sum WHERE sum < 42
|
||||
// RETURN sum AS result
|
||||
FakeDbAccessor dba;
|
||||
auto prop = dba.Property("prop");
|
||||
AstStorage storage;
|
||||
auto sum = SUM(PROPERTY_LOOKUP("n", prop), true);
|
||||
auto literal = LITERAL(42);
|
||||
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WITH(ADD(sum, literal), AS("sum")),
|
||||
WHERE(LESS(IDENT("sum"), LITERAL(42))), RETURN("sum", AS("result"))));
|
||||
auto aggr = ExpectAggregate({sum}, {literal});
|
||||
CheckPlan<TypeParam>(query, storage, ExpectScanAll(), aggr, ExpectProduce(), ExpectFilter(), ExpectProduce());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestPlanner, MatchReturnSumWithDistinct) {
|
||||
// Test MATCH (n) RETURN SUM(DISTINCT n.prop1) AS sum, n.prop2 AS group
|
||||
FakeDbAccessor dba;
|
||||
auto prop1 = dba.Property("prop1");
|
||||
auto prop2 = dba.Property("prop2");
|
||||
AstStorage storage;
|
||||
auto sum = SUM(PROPERTY_LOOKUP("n", prop1), true);
|
||||
auto n_prop2 = PROPERTY_LOOKUP("n", prop2);
|
||||
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN(sum, AS("sum"), n_prop2, AS("group"))));
|
||||
auto aggr = ExpectAggregate({sum}, {n_prop2});
|
||||
auto symbol_table = memgraph::query::MakeSymbolTable(query);
|
||||
auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query);
|
||||
CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), aggr, ExpectProduce());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestPlanner, CreateWithSumWithDistinct) {
|
||||
// Test CREATE (n) WITH SUM(DISTINCT n.prop) AS sum
|
||||
FakeDbAccessor dba;
|
||||
auto prop = dba.Property("prop");
|
||||
AstStorage storage;
|
||||
auto ident_n = IDENT("n");
|
||||
auto n_prop = PROPERTY_LOOKUP(ident_n, prop);
|
||||
auto sum = SUM(n_prop, true);
|
||||
auto sum = SUM(n_prop);
|
||||
auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), WITH(sum, AS("sum"))));
|
||||
auto symbol_table = memgraph::query::MakeSymbolTable(query);
|
||||
auto acc = ExpectAccumulate({symbol_table.at(*ident_n)});
|
||||
@@ -496,7 +444,7 @@ TYPED_TEST(TestPlanner, MatchWithCreate) {
|
||||
AstStorage storage;
|
||||
auto *query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WITH("n", AS("a")),
|
||||
CREATE(PATTERN(NODE("a"), EDGE("r", Direction::OUT, {r_type}), NODE("b")))));
|
||||
CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectProduce(), ExpectCreateExpand(), ExpectEmptyResult());
|
||||
CheckPlan<TypeParam>(query, storage, ExpectScanAll(), ExpectProduce(), ExpectCreateExpand());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestPlanner, MatchReturnSkipLimit) {
|
||||
@@ -536,24 +484,7 @@ TYPED_TEST(TestPlanner, CreateReturnSumSkipLimit) {
|
||||
AstStorage storage;
|
||||
auto ident_n = IDENT("n");
|
||||
auto n_prop = PROPERTY_LOOKUP(ident_n, prop);
|
||||
auto sum = SUM(n_prop, false);
|
||||
auto query =
|
||||
QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), RETURN(sum, AS("s"), SKIP(LITERAL(2)), LIMIT(LITERAL(1)))));
|
||||
auto symbol_table = memgraph::query::MakeSymbolTable(query);
|
||||
auto acc = ExpectAccumulate({symbol_table.at(*ident_n)});
|
||||
auto aggr = ExpectAggregate({sum}, {});
|
||||
auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query);
|
||||
CheckPlan(planner.plan(), symbol_table, ExpectCreateNode(), acc, aggr, ExpectProduce(), ExpectSkip(), ExpectLimit());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestPlanner, CreateReturnSumWithDistinctSkipLimit) {
|
||||
// Test CREATE (n) RETURN SUM(n.prop) AS s SKIP 2 LIMIT 1
|
||||
FakeDbAccessor dba;
|
||||
auto prop = dba.Property("prop");
|
||||
AstStorage storage;
|
||||
auto ident_n = IDENT("n");
|
||||
auto n_prop = PROPERTY_LOOKUP(ident_n, prop);
|
||||
auto sum = SUM(n_prop, true);
|
||||
auto sum = SUM(n_prop);
|
||||
auto query =
|
||||
QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n"))), RETURN(sum, AS("s"), SKIP(LITERAL(2)), LIMIT(LITERAL(1)))));
|
||||
auto symbol_table = memgraph::query::MakeSymbolTable(query);
|
||||
@@ -602,24 +533,14 @@ TYPED_TEST(TestPlanner, CreateWithOrderByWhere) {
|
||||
});
|
||||
auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query);
|
||||
CheckPlan(planner.plan(), symbol_table, ExpectCreateNode(), ExpectCreateExpand(), acc, ExpectProduce(),
|
||||
ExpectOrderBy(), ExpectFilter(), ExpectEmptyResult());
|
||||
ExpectOrderBy(), ExpectFilter());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestPlanner, ReturnAddSumCountOrderBy) {
|
||||
// Test RETURN SUM(1) + COUNT(2) AS result ORDER BY result
|
||||
AstStorage storage;
|
||||
auto sum = SUM(LITERAL(1), false);
|
||||
auto count = COUNT(LITERAL(2), false);
|
||||
auto *query = QUERY(SINGLE_QUERY(RETURN(ADD(sum, count), AS("result"), ORDER_BY(IDENT("result")))));
|
||||
auto aggr = ExpectAggregate({sum, count}, {});
|
||||
CheckPlan<TypeParam>(query, storage, aggr, ExpectProduce(), ExpectOrderBy());
|
||||
}
|
||||
|
||||
TYPED_TEST(TestPlanner, ReturnAddSumCountWithDistinctOrderBy) {
|
||||
// Test RETURN SUM(1) + COUNT(2) AS result ORDER BY result
|
||||
AstStorage storage;
|
||||
auto sum = SUM(LITERAL(1), true);
|
||||
auto count = COUNT(LITERAL(2), true);
|
||||
auto sum = SUM(LITERAL(1));
|
||||
auto count = COUNT(LITERAL(2));
|
||||
auto *query = QUERY(SINGLE_QUERY(RETURN(ADD(sum, count), AS("result"), ORDER_BY(IDENT("result")))));
|
||||
auto aggr = ExpectAggregate({sum, count}, {});
|
||||
CheckPlan<TypeParam>(query, storage, aggr, ExpectProduce(), ExpectOrderBy());
|
||||
@@ -689,7 +610,7 @@ TYPED_TEST(TestPlanner, CreateWithDistinctSumWhereReturn) {
|
||||
auto prop = dba.Property("prop");
|
||||
AstStorage storage;
|
||||
auto node_n = NODE("n");
|
||||
auto sum = SUM(PROPERTY_LOOKUP("n", prop), false);
|
||||
auto sum = SUM(PROPERTY_LOOKUP("n", prop));
|
||||
auto query = QUERY(SINGLE_QUERY(CREATE(PATTERN(node_n)), WITH_DISTINCT(sum, AS("s")),
|
||||
WHERE(LESS(IDENT("s"), LITERAL(42))), RETURN("s")));
|
||||
auto symbol_table = memgraph::query::MakeSymbolTable(query);
|
||||
@@ -828,7 +749,7 @@ TYPED_TEST(TestPlanner, MatchReturnAsteriskSum) {
|
||||
FakeDbAccessor dba;
|
||||
auto prop = dba.Property("prop");
|
||||
AstStorage storage;
|
||||
auto sum = SUM(PROPERTY_LOOKUP("n", prop), false);
|
||||
auto sum = SUM(PROPERTY_LOOKUP("n", prop));
|
||||
auto ret = RETURN(sum, AS("s"));
|
||||
ret->body_.all_identifiers = true;
|
||||
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), ret));
|
||||
@@ -859,7 +780,7 @@ TYPED_TEST(TestPlanner, UnwindMergeNodeProperty) {
|
||||
auto *query = QUERY(SINGLE_QUERY(UNWIND(LIST(LITERAL(1)), AS("i")), MERGE(PATTERN(node_n))));
|
||||
std::list<BaseOpChecker *> on_match{new ExpectScanAll(), new ExpectFilter()};
|
||||
std::list<BaseOpChecker *> on_create{new ExpectCreateNode()};
|
||||
CheckPlan<TypeParam>(query, storage, ExpectUnwind(), ExpectMerge(on_match, on_create), ExpectEmptyResult());
|
||||
CheckPlan<TypeParam>(query, storage, ExpectUnwind(), ExpectMerge(on_match, on_create));
|
||||
DeleteListContent(&on_match);
|
||||
DeleteListContent(&on_create);
|
||||
}
|
||||
@@ -879,7 +800,7 @@ TYPED_TEST(TestPlanner, UnwindMergeNodePropertyWithIndex) {
|
||||
std::list<BaseOpChecker *> on_create{new ExpectCreateNode()};
|
||||
auto symbol_table = memgraph::query::MakeSymbolTable(query);
|
||||
auto planner = MakePlanner<TypeParam>(&dba, storage, symbol_table, query);
|
||||
CheckPlan(planner.plan(), symbol_table, ExpectUnwind(), ExpectMerge(on_match, on_create), ExpectEmptyResult());
|
||||
CheckPlan(planner.plan(), symbol_table, ExpectUnwind(), ExpectMerge(on_match, on_create));
|
||||
DeleteListContent(&on_match);
|
||||
DeleteListContent(&on_create);
|
||||
}
|
||||
@@ -897,7 +818,7 @@ TYPED_TEST(TestPlanner, MultipleOptionalMatchReturn) {
|
||||
TYPED_TEST(TestPlanner, FunctionAggregationReturn) {
|
||||
// Test RETURN sqrt(SUM(2)) AS result, 42 AS group_by
|
||||
AstStorage storage;
|
||||
auto sum = SUM(LITERAL(2), false);
|
||||
auto sum = SUM(LITERAL(2));
|
||||
auto group_by_literal = LITERAL(42);
|
||||
auto *query = QUERY(SINGLE_QUERY(RETURN(FN("sqrt", sum), AS("result"), group_by_literal, AS("group_by"))));
|
||||
auto aggr = ExpectAggregate({sum}, {group_by_literal});
|
||||
@@ -914,7 +835,7 @@ TYPED_TEST(TestPlanner, FunctionWithoutArguments) {
|
||||
TYPED_TEST(TestPlanner, ListLiteralAggregationReturn) {
|
||||
// Test RETURN [SUM(2)] AS result, 42 AS group_by
|
||||
AstStorage storage;
|
||||
auto sum = SUM(LITERAL(2), false);
|
||||
auto sum = SUM(LITERAL(2));
|
||||
auto group_by_literal = LITERAL(42);
|
||||
auto *query = QUERY(SINGLE_QUERY(RETURN(LIST(sum), AS("result"), group_by_literal, AS("group_by"))));
|
||||
auto aggr = ExpectAggregate({sum}, {group_by_literal});
|
||||
@@ -925,7 +846,7 @@ TYPED_TEST(TestPlanner, MapLiteralAggregationReturn) {
|
||||
// Test RETURN {sum: SUM(2)} AS result, 42 AS group_by
|
||||
AstStorage storage;
|
||||
FakeDbAccessor dba;
|
||||
auto sum = SUM(LITERAL(2), false);
|
||||
auto sum = SUM(LITERAL(2));
|
||||
auto group_by_literal = LITERAL(42);
|
||||
auto *query = QUERY(
|
||||
SINGLE_QUERY(RETURN(MAP({storage.GetPropertyIx("sum"), sum}), AS("result"), group_by_literal, AS("group_by"))));
|
||||
@@ -936,7 +857,7 @@ TYPED_TEST(TestPlanner, MapLiteralAggregationReturn) {
|
||||
TYPED_TEST(TestPlanner, EmptyListIndexAggregation) {
|
||||
// Test RETURN [][SUM(2)] AS result, 42 AS group_by
|
||||
AstStorage storage;
|
||||
auto sum = SUM(LITERAL(2), false);
|
||||
auto sum = SUM(LITERAL(2));
|
||||
auto empty_list = LIST();
|
||||
auto group_by_literal = LITERAL(42);
|
||||
auto *query = QUERY(SINGLE_QUERY(RETURN(storage.Create<memgraph::query::SubscriptOperator>(empty_list, sum),
|
||||
@@ -951,7 +872,7 @@ TYPED_TEST(TestPlanner, EmptyListIndexAggregation) {
|
||||
TYPED_TEST(TestPlanner, ListSliceAggregationReturn) {
|
||||
// Test RETURN [1, 2][0..SUM(2)] AS result, 42 AS group_by
|
||||
AstStorage storage;
|
||||
auto sum = SUM(LITERAL(2), false);
|
||||
auto sum = SUM(LITERAL(2));
|
||||
auto list = LIST(LITERAL(1), LITERAL(2));
|
||||
auto group_by_literal = LITERAL(42);
|
||||
auto *query =
|
||||
@@ -965,7 +886,7 @@ TYPED_TEST(TestPlanner, ListSliceAggregationReturn) {
|
||||
TYPED_TEST(TestPlanner, ListWithAggregationAndGroupBy) {
|
||||
// Test RETURN [sum(2), 42]
|
||||
AstStorage storage;
|
||||
auto sum = SUM(LITERAL(2), false);
|
||||
auto sum = SUM(LITERAL(2));
|
||||
auto group_by_literal = LITERAL(42);
|
||||
auto *query = QUERY(SINGLE_QUERY(RETURN(LIST(sum, group_by_literal), AS("result"))));
|
||||
auto aggr = ExpectAggregate({sum}, {group_by_literal});
|
||||
@@ -975,8 +896,8 @@ TYPED_TEST(TestPlanner, ListWithAggregationAndGroupBy) {
|
||||
TYPED_TEST(TestPlanner, AggregatonWithListWithAggregationAndGroupBy) {
|
||||
// Test RETURN sum(2), [sum(3), 42]
|
||||
AstStorage storage;
|
||||
auto sum2 = SUM(LITERAL(2), false);
|
||||
auto sum3 = SUM(LITERAL(3), false);
|
||||
auto sum2 = SUM(LITERAL(2));
|
||||
auto sum3 = SUM(LITERAL(3));
|
||||
auto group_by_literal = LITERAL(42);
|
||||
auto *query = QUERY(SINGLE_QUERY(RETURN(sum2, AS("sum2"), LIST(sum3, group_by_literal), AS("list"))));
|
||||
auto aggr = ExpectAggregate({sum2, sum3}, {group_by_literal});
|
||||
@@ -987,7 +908,7 @@ TYPED_TEST(TestPlanner, MapWithAggregationAndGroupBy) {
|
||||
// Test RETURN {lit: 42, sum: sum(2)}
|
||||
AstStorage storage;
|
||||
FakeDbAccessor dba;
|
||||
auto sum = SUM(LITERAL(2), false);
|
||||
auto sum = SUM(LITERAL(2));
|
||||
auto group_by_literal = LITERAL(42);
|
||||
auto *query = QUERY(SINGLE_QUERY(RETURN(
|
||||
MAP({storage.GetPropertyIx("sum"), sum}, {storage.GetPropertyIx("lit"), group_by_literal}), AS("result"))));
|
||||
@@ -1200,7 +1121,7 @@ TYPED_TEST(TestPlanner, SecondPropertyIndex) {
|
||||
TYPED_TEST(TestPlanner, ReturnSumGroupByAll) {
|
||||
// Test RETURN sum([1,2,3]), all(x in [1] where x = 1)
|
||||
AstStorage storage;
|
||||
auto sum = SUM(LIST(LITERAL(1), LITERAL(2), LITERAL(3)), false);
|
||||
auto sum = SUM(LIST(LITERAL(1), LITERAL(2), LITERAL(3)));
|
||||
auto *all = ALL("x", LIST(LITERAL(1)), WHERE(EQ(IDENT("x"), LITERAL(1))));
|
||||
auto *query = QUERY(SINGLE_QUERY(RETURN(sum, AS("sum"), all, AS("all"))));
|
||||
auto aggr = ExpectAggregate({sum}, {all});
|
||||
@@ -1642,7 +1563,7 @@ TYPED_TEST(TestPlanner, Foreach) {
|
||||
auto create = ExpectCreateNode();
|
||||
std::list<BaseOpChecker *> updates{&create};
|
||||
std::list<BaseOpChecker *> input;
|
||||
CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates), ExpectEmptyResult());
|
||||
CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates));
|
||||
}
|
||||
{
|
||||
auto *i = NEXPR("i", IDENT("i"));
|
||||
@@ -1650,7 +1571,7 @@ TYPED_TEST(TestPlanner, Foreach) {
|
||||
auto del = ExpectDelete();
|
||||
std::list<BaseOpChecker *> updates{&del};
|
||||
std::list<BaseOpChecker *> input;
|
||||
CheckPlan<TypeParam>(query, storage, ExpectForeach({input}, updates), ExpectEmptyResult());
|
||||
CheckPlan<TypeParam>(query, storage, ExpectForeach({input}, updates));
|
||||
}
|
||||
{
|
||||
auto prop = dba.Property("prop");
|
||||
@@ -1659,7 +1580,7 @@ TYPED_TEST(TestPlanner, Foreach) {
|
||||
auto set_prop = ExpectSetProperty();
|
||||
std::list<BaseOpChecker *> updates{&set_prop};
|
||||
std::list<BaseOpChecker *> input;
|
||||
CheckPlan<TypeParam>(query, storage, ExpectForeach({input}, updates), ExpectEmptyResult());
|
||||
CheckPlan<TypeParam>(query, storage, ExpectForeach({input}, updates));
|
||||
}
|
||||
{
|
||||
auto *i = NEXPR("i", IDENT("i"));
|
||||
@@ -1671,7 +1592,7 @@ TYPED_TEST(TestPlanner, Foreach) {
|
||||
std::list<BaseOpChecker *> nested_updates{{&create, &del}};
|
||||
auto nested_foreach = ExpectForeach(input, nested_updates);
|
||||
std::list<BaseOpChecker *> updates{&nested_foreach};
|
||||
CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates), ExpectEmptyResult());
|
||||
CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates));
|
||||
}
|
||||
{
|
||||
auto *i = NEXPR("i", IDENT("i"));
|
||||
@@ -1683,7 +1604,7 @@ TYPED_TEST(TestPlanner, Foreach) {
|
||||
std::list<BaseOpChecker *> input{&input_op};
|
||||
auto *query =
|
||||
QUERY(SINGLE_QUERY(FOREACH(i, {CREATE(PATTERN(NODE("n")))}), FOREACH(j, {CREATE(PATTERN(NODE("n")))})));
|
||||
CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates), ExpectEmptyResult());
|
||||
CheckPlan<TypeParam>(query, storage, ExpectForeach(input, updates));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -109,7 +109,7 @@ std::shared_ptr<Produce> MakeAggregationProduce(std::shared_ptr<LogicalOperator>
|
||||
AstStorage &storage, const std::vector<Expression *> aggr_inputs,
|
||||
const std::vector<Aggregation::Op> aggr_ops,
|
||||
const std::vector<Expression *> group_by_exprs,
|
||||
const std::vector<Symbol> remember, const bool distinct) {
|
||||
const std::vector<Symbol> remember) {
|
||||
// prepare all the aggregations
|
||||
std::vector<Aggregate::Element> aggregates;
|
||||
std::vector<NamedExpression *> named_expressions;
|
||||
@@ -124,7 +124,7 @@ std::shared_ptr<Produce> MakeAggregationProduce(std::shared_ptr<LogicalOperator>
|
||||
named_expressions.push_back(named_expr);
|
||||
// the key expression is only used in COLLECT_MAP
|
||||
Expression *key_expr_ptr = aggr_op == Aggregation::Op::COLLECT_MAP ? LITERAL("key") : nullptr;
|
||||
aggregates.emplace_back(Aggregate::Element{*aggr_inputs_it++, key_expr_ptr, aggr_op, aggr_sym, distinct});
|
||||
aggregates.emplace_back(Aggregate::Element{*aggr_inputs_it++, key_expr_ptr, aggr_op, aggr_sym});
|
||||
}
|
||||
|
||||
// Produce will also evaluate group_by expressions and return them after the
|
||||
@@ -155,21 +155,16 @@ class QueryPlanAggregateOps : public ::testing::Test {
|
||||
ASSERT_TRUE(dba.InsertVertex().SetProperty(prop, memgraph::storage::PropertyValue(5)).HasValue());
|
||||
ASSERT_TRUE(dba.InsertVertex().SetProperty(prop, memgraph::storage::PropertyValue(7)).HasValue());
|
||||
ASSERT_TRUE(dba.InsertVertex().SetProperty(prop, memgraph::storage::PropertyValue(12)).HasValue());
|
||||
ASSERT_TRUE(dba.InsertVertex().SetProperty(prop, memgraph::storage::PropertyValue(5)).HasValue());
|
||||
ASSERT_TRUE(dba.InsertVertex().SetProperty(prop, memgraph::storage::PropertyValue(5)).HasValue());
|
||||
ASSERT_TRUE(dba.InsertVertex().SetProperty(prop, memgraph::storage::PropertyValue(12)).HasValue());
|
||||
|
||||
// a missing property (null) gets ignored by all aggregations except
|
||||
// COUNT(*)
|
||||
dba.InsertVertex();
|
||||
dba.AdvanceCommand();
|
||||
}
|
||||
|
||||
auto AggregationResults(bool with_group_by, bool distinct,
|
||||
std::vector<Aggregation::Op> ops = {
|
||||
Aggregation::Op::COUNT, Aggregation::Op::COUNT, Aggregation::Op::MIN,
|
||||
Aggregation::Op::MAX, Aggregation::Op::SUM, Aggregation::Op::AVG,
|
||||
Aggregation::Op::COLLECT_LIST, Aggregation::Op::COLLECT_MAP}) {
|
||||
auto AggregationResults(bool with_group_by, std::vector<Aggregation::Op> ops = {
|
||||
Aggregation::Op::COUNT, Aggregation::Op::COUNT, Aggregation::Op::MIN,
|
||||
Aggregation::Op::MAX, Aggregation::Op::SUM, Aggregation::Op::AVG,
|
||||
Aggregation::Op::COLLECT_LIST, Aggregation::Op::COLLECT_MAP}) {
|
||||
// match all nodes and perform aggregations
|
||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||
auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop);
|
||||
@@ -178,8 +173,7 @@ class QueryPlanAggregateOps : public ::testing::Test {
|
||||
std::vector<Expression *> group_bys;
|
||||
if (with_group_by) group_bys.push_back(n_p);
|
||||
aggregation_expressions[0] = nullptr;
|
||||
auto produce =
|
||||
MakeAggregationProduce(n.op_, symbol_table, storage, aggregation_expressions, ops, group_bys, {}, distinct);
|
||||
auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, aggregation_expressions, ops, group_bys, {});
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
return CollectProduce(*produce, &context);
|
||||
}
|
||||
@@ -187,16 +181,16 @@ class QueryPlanAggregateOps : public ::testing::Test {
|
||||
|
||||
TEST_F(QueryPlanAggregateOps, WithData) {
|
||||
AddData();
|
||||
auto results = AggregationResults(false, false);
|
||||
auto results = AggregationResults(false);
|
||||
|
||||
ASSERT_EQ(results.size(), 1);
|
||||
ASSERT_EQ(results[0].size(), 8);
|
||||
// count(*)
|
||||
ASSERT_EQ(results[0][0].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][0].ValueInt(), 7);
|
||||
EXPECT_EQ(results[0][0].ValueInt(), 4);
|
||||
// count
|
||||
ASSERT_EQ(results[0][1].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][1].ValueInt(), 6);
|
||||
EXPECT_EQ(results[0][1].ValueInt(), 3);
|
||||
// min
|
||||
ASSERT_EQ(results[0][2].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][2].ValueInt(), 5);
|
||||
@@ -205,13 +199,13 @@ TEST_F(QueryPlanAggregateOps, WithData) {
|
||||
EXPECT_EQ(results[0][3].ValueInt(), 12);
|
||||
// sum
|
||||
ASSERT_EQ(results[0][4].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][4].ValueInt(), 46);
|
||||
EXPECT_EQ(results[0][4].ValueInt(), 24);
|
||||
// avg
|
||||
ASSERT_EQ(results[0][5].type(), TypedValue::Type::Double);
|
||||
EXPECT_FLOAT_EQ(results[0][5].ValueDouble(), 46 / 6.0);
|
||||
EXPECT_FLOAT_EQ(results[0][5].ValueDouble(), 24 / 3.0);
|
||||
// collect list
|
||||
ASSERT_EQ(results[0][6].type(), TypedValue::Type::List);
|
||||
EXPECT_THAT(ToIntList(results[0][6]), UnorderedElementsAre(5, 7, 12, 5, 5, 12));
|
||||
EXPECT_THAT(ToIntList(results[0][6]), UnorderedElementsAre(5, 7, 12));
|
||||
// collect map
|
||||
ASSERT_EQ(results[0][7].type(), TypedValue::Type::Map);
|
||||
auto map = ToIntMap(results[0][7]);
|
||||
@@ -222,46 +216,46 @@ TEST_F(QueryPlanAggregateOps, WithData) {
|
||||
|
||||
TEST_F(QueryPlanAggregateOps, WithoutDataWithGroupBy) {
|
||||
{
|
||||
auto results = AggregationResults(true, false, {Aggregation::Op::COUNT});
|
||||
auto results = AggregationResults(true, {Aggregation::Op::COUNT});
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][0].ValueInt(), 0);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, false, {Aggregation::Op::SUM});
|
||||
auto results = AggregationResults(true, {Aggregation::Op::SUM});
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][0].ValueInt(), 0);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, false, {Aggregation::Op::AVG});
|
||||
auto results = AggregationResults(true, {Aggregation::Op::AVG});
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Null);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, false, {Aggregation::Op::MIN});
|
||||
auto results = AggregationResults(true, {Aggregation::Op::MIN});
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Null);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, false, {Aggregation::Op::MAX});
|
||||
auto results = AggregationResults(true, {Aggregation::Op::MAX});
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Null);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, false, {Aggregation::Op::COLLECT_LIST});
|
||||
auto results = AggregationResults(true, {Aggregation::Op::COLLECT_LIST});
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::List);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, false, {Aggregation::Op::COLLECT_MAP});
|
||||
auto results = AggregationResults(true, {Aggregation::Op::COLLECT_MAP});
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Map);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(QueryPlanAggregateOps, WithoutDataWithoutGroupBy) {
|
||||
auto results = AggregationResults(false, false);
|
||||
auto results = AggregationResults(false);
|
||||
ASSERT_EQ(results.size(), 1);
|
||||
ASSERT_EQ(results[0].size(), 8);
|
||||
// count(*)
|
||||
@@ -331,8 +325,7 @@ TEST(QueryPlan, AggregateGroupByValues) {
|
||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||
auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop);
|
||||
|
||||
auto produce =
|
||||
MakeAggregationProduce(n.op_, symbol_table, storage, {n_p}, {Aggregation::Op::COUNT}, {n_p}, {n.sym_}, false);
|
||||
auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {n_p}, {Aggregation::Op::COUNT}, {n_p}, {n.sym_});
|
||||
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
auto results = CollectProduce(*produce, &context);
|
||||
@@ -379,7 +372,7 @@ TEST(QueryPlan, AggregateMultipleGroupBy) {
|
||||
auto n_p3 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop3);
|
||||
|
||||
auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {n_p1}, {Aggregation::Op::COUNT},
|
||||
{n_p1, n_p2, n_p3}, {n.sym_}, false);
|
||||
{n_p1, n_p2, n_p3}, {n.sym_});
|
||||
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
auto results = CollectProduce(*produce, &context);
|
||||
@@ -394,7 +387,7 @@ TEST(QueryPlan, AggregateNoInput) {
|
||||
SymbolTable symbol_table;
|
||||
|
||||
auto two = LITERAL(2);
|
||||
auto produce = MakeAggregationProduce(nullptr, symbol_table, storage, {two}, {Aggregation::Op::COUNT}, {}, {}, false);
|
||||
auto produce = MakeAggregationProduce(nullptr, symbol_table, storage, {two}, {Aggregation::Op::COUNT}, {}, {});
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
auto results = CollectProduce(*produce, &context);
|
||||
EXPECT_EQ(1, results.size());
|
||||
@@ -426,7 +419,7 @@ TEST(QueryPlan, AggregateCountEdgeCases) {
|
||||
// returns -1 when there are no results
|
||||
// otherwise returns MATCH (n) RETURN count(n.prop)
|
||||
auto count = [&]() {
|
||||
auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {n_p}, {Aggregation::Op::COUNT}, {}, {}, false);
|
||||
auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {n_p}, {Aggregation::Op::COUNT}, {}, {});
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
auto results = CollectProduce(*produce, &context);
|
||||
if (results.size() == 0) return -1L;
|
||||
@@ -486,7 +479,7 @@ TEST(QueryPlan, AggregateFirstValueTypes) {
|
||||
auto n_id = n_prop_string->expression_;
|
||||
|
||||
auto aggregate = [&](Expression *expression, Aggregation::Op aggr_op) {
|
||||
auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {expression}, {aggr_op}, {}, {}, false);
|
||||
auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {expression}, {aggr_op}, {}, {});
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
CollectProduce(*produce, &context);
|
||||
};
|
||||
@@ -540,7 +533,7 @@ TEST(QueryPlan, AggregateTypes) {
|
||||
auto n_p2 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), p2);
|
||||
|
||||
auto aggregate = [&](Expression *expression, Aggregation::Op aggr_op) {
|
||||
auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {expression}, {aggr_op}, {}, {}, false);
|
||||
auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {expression}, {aggr_op}, {}, {});
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
CollectProduce(*produce, &context);
|
||||
};
|
||||
@@ -616,396 +609,3 @@ TEST(QueryPlan, Unwind) {
|
||||
expected_y_it++;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(QueryPlanAggregateOps, WithDataDistinct) {
|
||||
AddData();
|
||||
auto results = AggregationResults(false, true);
|
||||
|
||||
ASSERT_EQ(results.size(), 1);
|
||||
ASSERT_EQ(results[0].size(), 8);
|
||||
// count(*)
|
||||
ASSERT_EQ(results[0][0].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][0].ValueInt(), 7);
|
||||
// count
|
||||
ASSERT_EQ(results[0][1].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][1].ValueInt(), 3);
|
||||
// min
|
||||
ASSERT_EQ(results[0][2].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][2].ValueInt(), 5);
|
||||
// max
|
||||
ASSERT_EQ(results[0][3].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][3].ValueInt(), 12);
|
||||
// sum
|
||||
ASSERT_EQ(results[0][4].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][4].ValueInt(), 24);
|
||||
// avg
|
||||
ASSERT_EQ(results[0][5].type(), TypedValue::Type::Double);
|
||||
EXPECT_FLOAT_EQ(results[0][5].ValueDouble(), 24 / 3.0);
|
||||
// collect list
|
||||
ASSERT_EQ(results[0][6].type(), TypedValue::Type::List);
|
||||
EXPECT_THAT(ToIntList(results[0][6]), UnorderedElementsAre(5, 7, 12));
|
||||
// collect map
|
||||
ASSERT_EQ(results[0][7].type(), TypedValue::Type::Map);
|
||||
auto map = ToIntMap(results[0][7]);
|
||||
ASSERT_EQ(map.size(), 1);
|
||||
EXPECT_EQ(map.begin()->first, "key");
|
||||
EXPECT_FALSE(std::set<int>({5, 7, 12}).insert(map.begin()->second).second);
|
||||
}
|
||||
|
||||
TEST_F(QueryPlanAggregateOps, WithoutDataWithDistinctAndWithGroupBy) {
|
||||
{
|
||||
auto results = AggregationResults(true, true, {Aggregation::Op::COUNT});
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][0].ValueInt(), 0);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, true, {Aggregation::Op::SUM});
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][0].ValueInt(), 0);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, true, {Aggregation::Op::AVG});
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Null);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, true, {Aggregation::Op::MIN});
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Null);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, true, {Aggregation::Op::MAX});
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Null);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, true, {Aggregation::Op::COLLECT_LIST});
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::List);
|
||||
}
|
||||
{
|
||||
auto results = AggregationResults(true, true, {Aggregation::Op::COLLECT_MAP});
|
||||
EXPECT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0][0].type(), TypedValue::Type::Map);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(QueryPlanAggregateOps, WithoutDataWithDistinctAndWithoutGroupBy) {
|
||||
auto results = AggregationResults(false, true);
|
||||
ASSERT_EQ(results.size(), 1);
|
||||
ASSERT_EQ(results[0].size(), 8);
|
||||
// count(*)
|
||||
ASSERT_EQ(results[0][0].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][0].ValueInt(), 0);
|
||||
// count
|
||||
ASSERT_EQ(results[0][1].type(), TypedValue::Type::Int);
|
||||
EXPECT_EQ(results[0][1].ValueInt(), 0);
|
||||
// min
|
||||
EXPECT_TRUE(results[0][2].IsNull());
|
||||
// max
|
||||
EXPECT_TRUE(results[0][3].IsNull());
|
||||
// sum
|
||||
EXPECT_EQ(results[0][4].ValueInt(), 0);
|
||||
// avg
|
||||
EXPECT_TRUE(results[0][5].IsNull());
|
||||
// collect list
|
||||
ASSERT_EQ(results[0][6].type(), TypedValue::Type::List);
|
||||
EXPECT_EQ(ToIntList(results[0][6]).size(), 0);
|
||||
// collect map
|
||||
ASSERT_EQ(results[0][7].type(), TypedValue::Type::Map);
|
||||
EXPECT_EQ(ToIntMap(results[0][7]).size(), 0);
|
||||
}
|
||||
|
||||
TEST(QueryPlan, AggregateGroupByValuesWithDistinct) {
|
||||
// Tests that distinct groups are aggregated properly for values of all types.
|
||||
// Also test the "remember" part of the Aggregation API as final results are
|
||||
// obtained via a property lookup of a remembered node.
|
||||
memgraph::storage::Storage db;
|
||||
auto storage_dba = db.Access();
|
||||
memgraph::query::DbAccessor dba(&storage_dba);
|
||||
|
||||
// a vector of memgraph::storage::PropertyValue to be set as property values on vertices
|
||||
// most of them should result in a distinct group (commented where not)
|
||||
std::vector<memgraph::storage::PropertyValue> group_by_vals;
|
||||
group_by_vals.emplace_back(4);
|
||||
group_by_vals.emplace_back(7);
|
||||
group_by_vals.emplace_back(7.3);
|
||||
group_by_vals.emplace_back(7.2);
|
||||
group_by_vals.emplace_back("Johhny");
|
||||
group_by_vals.emplace_back("Jane");
|
||||
group_by_vals.emplace_back("1");
|
||||
group_by_vals.emplace_back(true);
|
||||
group_by_vals.emplace_back(false);
|
||||
group_by_vals.emplace_back(std::vector<memgraph::storage::PropertyValue>{memgraph::storage::PropertyValue(1)});
|
||||
group_by_vals.emplace_back(std::vector<memgraph::storage::PropertyValue>{memgraph::storage::PropertyValue(1),
|
||||
memgraph::storage::PropertyValue(2)});
|
||||
group_by_vals.emplace_back(std::vector<memgraph::storage::PropertyValue>{memgraph::storage::PropertyValue(2),
|
||||
memgraph::storage::PropertyValue(1)});
|
||||
group_by_vals.emplace_back(memgraph::storage::PropertyValue());
|
||||
// should NOT result in another group because 7.0 == 7
|
||||
group_by_vals.emplace_back(7.0);
|
||||
// should NOT result in another group
|
||||
group_by_vals.emplace_back(std::vector<memgraph::storage::PropertyValue>{memgraph::storage::PropertyValue(1),
|
||||
memgraph::storage::PropertyValue(2.0)});
|
||||
|
||||
// generate a lot of vertices and set props on them
|
||||
auto prop = dba.NameToProperty("prop");
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
ASSERT_TRUE(dba.InsertVertex().SetProperty(prop, group_by_vals[i % group_by_vals.size()]).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
// match all nodes and perform aggregations
|
||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||
auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop);
|
||||
|
||||
auto produce =
|
||||
MakeAggregationProduce(n.op_, symbol_table, storage, {n_p}, {Aggregation::Op::COUNT}, {n_p}, {n.sym_}, true);
|
||||
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
auto results = CollectProduce(*produce, &context);
|
||||
ASSERT_EQ(results.size(), group_by_vals.size() - 2);
|
||||
std::unordered_set<TypedValue, TypedValue::Hash, TypedValue::BoolEqual> result_group_bys;
|
||||
for (const auto &row : results) {
|
||||
ASSERT_EQ(2, row.size());
|
||||
if (!row[1].IsNull()) {
|
||||
ASSERT_EQ(1, row[0].ValueInt());
|
||||
}
|
||||
result_group_bys.insert(row[1]);
|
||||
}
|
||||
ASSERT_EQ(result_group_bys.size(), group_by_vals.size() - 2);
|
||||
std::vector<TypedValue> group_by_tvals;
|
||||
group_by_tvals.reserve(group_by_vals.size());
|
||||
for (const auto &v : group_by_vals) group_by_tvals.emplace_back(v);
|
||||
EXPECT_TRUE(std::is_permutation(group_by_tvals.begin(), group_by_tvals.end() - 2, result_group_bys.begin(),
|
||||
TypedValue::BoolEqual{}));
|
||||
}
|
||||
|
||||
TEST(QueryPlan, AggregateMultipleGroupByWithDistinct) {
|
||||
// in this test we have 3 different properties that have different values
|
||||
// for different records and assert that we get the correct combination
|
||||
// of values in our groups
|
||||
memgraph::storage::Storage db;
|
||||
auto storage_dba = db.Access();
|
||||
memgraph::query::DbAccessor dba(&storage_dba);
|
||||
|
||||
auto prop1 = dba.NameToProperty("prop1");
|
||||
auto prop2 = dba.NameToProperty("prop2");
|
||||
auto prop3 = dba.NameToProperty("prop3");
|
||||
for (int i = 0; i < 2 * 3 * 5; ++i) {
|
||||
auto v = dba.InsertVertex();
|
||||
ASSERT_TRUE(v.SetProperty(prop1, memgraph::storage::PropertyValue(static_cast<bool>(i % 2))).HasValue());
|
||||
ASSERT_TRUE(v.SetProperty(prop2, memgraph::storage::PropertyValue(i % 3)).HasValue());
|
||||
ASSERT_TRUE(v.SetProperty(prop3, memgraph::storage::PropertyValue("value" + std::to_string(i % 5))).HasValue());
|
||||
}
|
||||
dba.AdvanceCommand();
|
||||
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
// match all nodes and perform aggregations
|
||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||
auto n_p1 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop1);
|
||||
auto n_p2 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop2);
|
||||
|
||||
auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {n_p1}, {Aggregation::Op::COUNT}, {n_p1, n_p2},
|
||||
{n.sym_}, true);
|
||||
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
auto results = CollectProduce(*produce, &context);
|
||||
for (const auto &row : results) {
|
||||
ASSERT_EQ(1, row[0].ValueInt());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(QueryPlan, AggregateNoInputWithDistinct) {
|
||||
memgraph::storage::Storage db;
|
||||
auto storage_dba = db.Access();
|
||||
memgraph::query::DbAccessor dba(&storage_dba);
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
auto two = LITERAL(2);
|
||||
auto produce = MakeAggregationProduce(nullptr, symbol_table, storage, {two}, {Aggregation::Op::COUNT}, {}, {}, true);
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
auto results = CollectProduce(*produce, &context);
|
||||
EXPECT_EQ(1, results.size());
|
||||
EXPECT_EQ(1, results[0].size());
|
||||
EXPECT_EQ(TypedValue::Type::Int, results[0][0].type());
|
||||
EXPECT_EQ(1, results[0][0].ValueInt());
|
||||
}
|
||||
|
||||
TEST(QueryPlan, AggregateCountEdgeCasesWithDistinct) {
|
||||
// tests for detected bugs in the COUNT aggregation behavior
|
||||
// ensure that COUNT returns correctly for
|
||||
// - 0 vertices in database
|
||||
// - 1 vertex in database, property not set
|
||||
// - 1 vertex in database, property set
|
||||
// - 2 vertices in database, property set on one
|
||||
// - 2 vertices in database, property set on both
|
||||
|
||||
memgraph::storage::Storage db;
|
||||
auto storage_dba = db.Access();
|
||||
memgraph::query::DbAccessor dba(&storage_dba);
|
||||
auto prop = dba.NameToProperty("prop");
|
||||
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||
auto n_p = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop);
|
||||
|
||||
// returns -1 when there are no results
|
||||
// otherwise returns MATCH (n) RETURN count(n.prop)
|
||||
auto count = [&]() {
|
||||
auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {n_p}, {Aggregation::Op::COUNT}, {}, {}, true);
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
auto results = CollectProduce(*produce, &context);
|
||||
if (results.size() == 0) return -1L;
|
||||
EXPECT_EQ(1, results.size());
|
||||
EXPECT_EQ(1, results[0].size());
|
||||
EXPECT_EQ(TypedValue::Type::Int, results[0][0].type());
|
||||
return results[0][0].ValueInt();
|
||||
};
|
||||
|
||||
// no vertices yet in database
|
||||
EXPECT_EQ(0, count());
|
||||
|
||||
// one vertex, no property set
|
||||
dba.InsertVertex();
|
||||
dba.AdvanceCommand();
|
||||
EXPECT_EQ(0, count());
|
||||
|
||||
// one vertex, property set
|
||||
for (auto va : dba.Vertices(memgraph::storage::View::OLD))
|
||||
ASSERT_TRUE(va.SetProperty(prop, memgraph::storage::PropertyValue(42)).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
EXPECT_EQ(1, count());
|
||||
|
||||
// two vertices, one with property set
|
||||
dba.InsertVertex();
|
||||
dba.AdvanceCommand();
|
||||
EXPECT_EQ(1, count());
|
||||
|
||||
// two vertices, both with property set
|
||||
for (auto va : dba.Vertices(memgraph::storage::View::OLD))
|
||||
ASSERT_TRUE(va.SetProperty(prop, memgraph::storage::PropertyValue(42)).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
EXPECT_EQ(1, count());
|
||||
}
|
||||
|
||||
TEST(QueryPlan, AggregateFirstValueTypesWithDistinct) {
|
||||
// testing exceptions that get emitted by the first-value
|
||||
// type check
|
||||
|
||||
memgraph::storage::Storage db;
|
||||
auto storage_dba = db.Access();
|
||||
memgraph::query::DbAccessor dba(&storage_dba);
|
||||
|
||||
auto v1 = dba.InsertVertex();
|
||||
auto prop_string = dba.NameToProperty("string");
|
||||
ASSERT_TRUE(v1.SetProperty(prop_string, memgraph::storage::PropertyValue("johhny")).HasValue());
|
||||
auto prop_int = dba.NameToProperty("int");
|
||||
ASSERT_TRUE(v1.SetProperty(prop_int, memgraph::storage::PropertyValue(12)).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||
auto n_prop_string = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop_string);
|
||||
auto n_prop_int = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), prop_int);
|
||||
auto n_id = n_prop_string->expression_;
|
||||
|
||||
auto aggregate = [&](Expression *expression, Aggregation::Op aggr_op) {
|
||||
auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {expression}, {aggr_op}, {}, {}, true);
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
CollectProduce(*produce, &context);
|
||||
};
|
||||
|
||||
// everything except for COUNT and COLLECT fails on a Vertex
|
||||
aggregate(n_id, Aggregation::Op::COUNT);
|
||||
EXPECT_THROW(aggregate(n_id, Aggregation::Op::MIN), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_id, Aggregation::Op::MAX), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_id, Aggregation::Op::AVG), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_id, Aggregation::Op::SUM), QueryRuntimeException);
|
||||
|
||||
// on strings AVG and SUM fail
|
||||
aggregate(n_prop_string, Aggregation::Op::COUNT);
|
||||
aggregate(n_prop_string, Aggregation::Op::MIN);
|
||||
aggregate(n_prop_string, Aggregation::Op::MAX);
|
||||
EXPECT_THROW(aggregate(n_prop_string, Aggregation::Op::AVG), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_prop_string, Aggregation::Op::SUM), QueryRuntimeException);
|
||||
|
||||
// on ints nothing fails
|
||||
aggregate(n_prop_int, Aggregation::Op::COUNT);
|
||||
aggregate(n_prop_int, Aggregation::Op::MIN);
|
||||
aggregate(n_prop_int, Aggregation::Op::MAX);
|
||||
aggregate(n_prop_int, Aggregation::Op::AVG);
|
||||
aggregate(n_prop_int, Aggregation::Op::SUM);
|
||||
aggregate(n_prop_int, Aggregation::Op::COLLECT_LIST);
|
||||
aggregate(n_prop_int, Aggregation::Op::COLLECT_MAP);
|
||||
}
|
||||
|
||||
TEST(QueryPlan, AggregateTypesWithDistinct) {
|
||||
// testing exceptions that can get emitted by an aggregation
|
||||
// does not check all combinations that can result in an exception
|
||||
// (that logic is defined and tested by TypedValue)
|
||||
|
||||
memgraph::storage::Storage db;
|
||||
auto storage_dba = db.Access();
|
||||
memgraph::query::DbAccessor dba(&storage_dba);
|
||||
|
||||
auto p1 = dba.NameToProperty("p1"); // has only string props
|
||||
ASSERT_TRUE(dba.InsertVertex().SetProperty(p1, memgraph::storage::PropertyValue("string")).HasValue());
|
||||
ASSERT_TRUE(dba.InsertVertex().SetProperty(p1, memgraph::storage::PropertyValue("str2")).HasValue());
|
||||
auto p2 = dba.NameToProperty("p2"); // combines int and bool
|
||||
ASSERT_TRUE(dba.InsertVertex().SetProperty(p2, memgraph::storage::PropertyValue(42)).HasValue());
|
||||
ASSERT_TRUE(dba.InsertVertex().SetProperty(p2, memgraph::storage::PropertyValue(true)).HasValue());
|
||||
dba.AdvanceCommand();
|
||||
|
||||
AstStorage storage;
|
||||
SymbolTable symbol_table;
|
||||
|
||||
auto n = MakeScanAll(storage, symbol_table, "n");
|
||||
auto n_p1 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), p1);
|
||||
auto n_p2 = PROPERTY_LOOKUP(IDENT("n")->MapTo(n.sym_), p2);
|
||||
|
||||
auto aggregate = [&](Expression *expression, Aggregation::Op aggr_op) {
|
||||
auto produce = MakeAggregationProduce(n.op_, symbol_table, storage, {expression}, {aggr_op}, {}, {}, true);
|
||||
auto context = MakeContext(storage, symbol_table, &dba);
|
||||
CollectProduce(*produce, &context);
|
||||
};
|
||||
|
||||
// everything except for COUNT and COLLECT fails on a Vertex
|
||||
auto n_id = n_p1->expression_;
|
||||
aggregate(n_id, Aggregation::Op::COUNT);
|
||||
aggregate(n_id, Aggregation::Op::COLLECT_LIST);
|
||||
aggregate(n_id, Aggregation::Op::COLLECT_MAP);
|
||||
EXPECT_THROW(aggregate(n_id, Aggregation::Op::MIN), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_id, Aggregation::Op::MAX), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_id, Aggregation::Op::AVG), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_id, Aggregation::Op::SUM), QueryRuntimeException);
|
||||
|
||||
// on strings AVG and SUM fail
|
||||
aggregate(n_p1, Aggregation::Op::COUNT);
|
||||
aggregate(n_p1, Aggregation::Op::COLLECT_LIST);
|
||||
aggregate(n_p1, Aggregation::Op::COLLECT_MAP);
|
||||
aggregate(n_p1, Aggregation::Op::MIN);
|
||||
aggregate(n_p1, Aggregation::Op::MAX);
|
||||
EXPECT_THROW(aggregate(n_p1, Aggregation::Op::AVG), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_p1, Aggregation::Op::SUM), QueryRuntimeException);
|
||||
|
||||
// combination of int and bool, everything except COUNT and COLLECT fails
|
||||
aggregate(n_p2, Aggregation::Op::COUNT);
|
||||
aggregate(n_p2, Aggregation::Op::COLLECT_LIST);
|
||||
aggregate(n_p2, Aggregation::Op::COLLECT_MAP);
|
||||
EXPECT_THROW(aggregate(n_p2, Aggregation::Op::MIN), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_p2, Aggregation::Op::MAX), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_p2, Aggregation::Op::AVG), QueryRuntimeException);
|
||||
EXPECT_THROW(aggregate(n_p2, Aggregation::Op::SUM), QueryRuntimeException);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,6 @@ class PlanChecker : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
PRE_VISIT(ExpandVariable);
|
||||
PRE_VISIT(Filter);
|
||||
PRE_VISIT(ConstructNamedPath);
|
||||
PRE_VISIT(EmptyResult);
|
||||
PRE_VISIT(Produce);
|
||||
PRE_VISIT(SetProperty);
|
||||
PRE_VISIT(SetProperties);
|
||||
@@ -91,7 +90,7 @@ class PlanChecker : public virtual HierarchicalLogicalOperatorVisitor {
|
||||
}
|
||||
PRE_VISIT(Unwind);
|
||||
PRE_VISIT(Distinct);
|
||||
|
||||
|
||||
bool PreVisit(Foreach &op) override {
|
||||
CheckOp(op);
|
||||
return false;
|
||||
@@ -144,7 +143,6 @@ using ExpectExpand = OpChecker<Expand>;
|
||||
using ExpectFilter = OpChecker<Filter>;
|
||||
using ExpectConstructNamedPath = OpChecker<ConstructNamedPath>;
|
||||
using ExpectProduce = OpChecker<Produce>;
|
||||
using ExpectEmptyResult = OpChecker<EmptyResult>;
|
||||
using ExpectSetProperty = OpChecker<SetProperty>;
|
||||
using ExpectSetProperties = OpChecker<SetProperties>;
|
||||
using ExpectSetLabels = OpChecker<SetLabels>;
|
||||
@@ -218,7 +216,6 @@ class ExpectAggregate : public OpChecker<Aggregate> {
|
||||
EXPECT_EQ(typeid(aggr_elem.value).hash_code(), typeid(aggr->expression1_).hash_code());
|
||||
EXPECT_EQ(typeid(aggr_elem.key).hash_code(), typeid(aggr->expression2_).hash_code());
|
||||
EXPECT_EQ(aggr_elem.op, aggr->op_);
|
||||
EXPECT_EQ(aggr_elem.distinct, aggr->distinct_);
|
||||
EXPECT_EQ(aggr_elem.output_sym, symbol_table.at(*aggr));
|
||||
}
|
||||
EXPECT_EQ(aggr_it, aggregations_.end());
|
||||
|
||||
@@ -253,7 +253,7 @@ TEST_F(TestSymbolGenerator, MatchWithWhere) {
|
||||
TEST_F(TestSymbolGenerator, MatchWithWhereUnbound) {
|
||||
// Test MATCH (old) WITH COUNT(old) AS c WHERE old.prop < 42
|
||||
auto prop = dba.NameToProperty("prop");
|
||||
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), WITH(COUNT(IDENT("old"), false), AS("c")),
|
||||
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), WITH(COUNT(IDENT("old")), AS("c")),
|
||||
WHERE(LESS(PROPERTY_LOOKUP("old", prop), LITERAL(42)))));
|
||||
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError);
|
||||
}
|
||||
@@ -313,7 +313,7 @@ TEST_F(TestSymbolGenerator, MatchReturnSum) {
|
||||
// Test MATCH (n) RETURN SUM(n.prop) + 42 AS result
|
||||
auto prop = dba.NameToProperty("prop");
|
||||
auto node = NODE("n");
|
||||
auto sum = SUM(PROPERTY_LOOKUP("n", prop), false);
|
||||
auto sum = SUM(PROPERTY_LOOKUP("n", prop));
|
||||
auto as_result = AS("result");
|
||||
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node)), RETURN(ADD(sum, LITERAL(42)), as_result)));
|
||||
auto symbol_table = memgraph::query::MakeSymbolTable(query);
|
||||
@@ -330,9 +330,8 @@ TEST_F(TestSymbolGenerator, MatchReturnSum) {
|
||||
TEST_F(TestSymbolGenerator, NestedAggregation) {
|
||||
// Test MATCH (n) RETURN SUM(42 + SUM(n.prop)) AS s
|
||||
auto prop = dba.NameToProperty("prop");
|
||||
auto query =
|
||||
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))),
|
||||
RETURN(SUM(ADD(LITERAL(42), SUM(PROPERTY_LOOKUP("n", prop), false)), false), AS("s"))));
|
||||
auto query = QUERY(
|
||||
SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN(SUM(ADD(LITERAL(42), SUM(PROPERTY_LOOKUP("n", prop)))), AS("s"))));
|
||||
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
|
||||
}
|
||||
|
||||
@@ -340,7 +339,7 @@ TEST_F(TestSymbolGenerator, WrongAggregationContext) {
|
||||
// Test MATCH (n) WITH n.prop AS prop WHERE SUM(prop) < 42
|
||||
auto prop = dba.NameToProperty("prop");
|
||||
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), WITH(PROPERTY_LOOKUP("n", prop), AS("prop")),
|
||||
WHERE(LESS(SUM(IDENT("prop"), false), LITERAL(42)))));
|
||||
WHERE(LESS(SUM(IDENT("prop")), LITERAL(42)))));
|
||||
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
|
||||
}
|
||||
|
||||
@@ -430,15 +429,14 @@ TEST_F(TestSymbolGenerator, LimitUsingIdentifier) {
|
||||
|
||||
TEST_F(TestSymbolGenerator, OrderByAggregation) {
|
||||
// Test MATCH (old) RETURN old AS new ORDER BY COUNT(1)
|
||||
auto query =
|
||||
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), RETURN("old", AS("new"), ORDER_BY(COUNT(LITERAL(1), false)))));
|
||||
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), RETURN("old", AS("new"), ORDER_BY(COUNT(LITERAL(1))))));
|
||||
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), SemanticException);
|
||||
}
|
||||
|
||||
TEST_F(TestSymbolGenerator, OrderByUnboundVariable) {
|
||||
// Test MATCH (old) RETURN COUNT(old) AS new ORDER BY old
|
||||
auto query = QUERY(
|
||||
SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), RETURN(COUNT(IDENT("old"), false), AS("new"), ORDER_BY(IDENT("old")))));
|
||||
auto query =
|
||||
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("old"))), RETURN(COUNT(IDENT("old")), AS("new"), ORDER_BY(IDENT("old")))));
|
||||
EXPECT_THROW(memgraph::query::MakeSymbolTable(query), UnboundVariableError);
|
||||
}
|
||||
|
||||
@@ -448,7 +446,7 @@ TEST_F(TestSymbolGenerator, AggregationOrderBy) {
|
||||
auto ident_old = IDENT("old");
|
||||
auto as_new = AS("new");
|
||||
auto ident_new = IDENT("new");
|
||||
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node)), RETURN(COUNT(ident_old, false), as_new, ORDER_BY(ident_new))));
|
||||
auto query = QUERY(SINGLE_QUERY(MATCH(PATTERN(node)), RETURN(COUNT(ident_old), as_new, ORDER_BY(ident_new))));
|
||||
auto symbol_table = memgraph::query::MakeSymbolTable(query);
|
||||
// Symbols for pattern, `old`, `count(old)` and `new`
|
||||
EXPECT_EQ(symbol_table.max_position(), 4);
|
||||
|
||||
26
tools/check-build-system
Executable file
26
tools/check-build-system
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
tools=(
|
||||
go
|
||||
dotnet
|
||||
npm
|
||||
node
|
||||
java
|
||||
javac
|
||||
mono
|
||||
mcs
|
||||
virtualenv
|
||||
)
|
||||
|
||||
is_ok=true
|
||||
for tool in "${tools[@]}"; do
|
||||
if ! which "$tool" >/dev/null; then
|
||||
is_ok=false
|
||||
echo "$tool not installed!"
|
||||
fi
|
||||
done
|
||||
if [ "$is_ok" = true ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user