Compare commits
86 Commits
v2.0.0
...
MG-for-mrm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f57843088b | ||
|
|
6a2e566492 | ||
|
|
fbf52a4ce2 | ||
|
|
59cc1d5f2b | ||
|
|
6c2763b492 | ||
|
|
80905b83c1 | ||
|
|
ced84e17b6 | ||
|
|
834ce5f51a | ||
|
|
ba0262559b | ||
|
|
cc281355d6 | ||
|
|
1a73c8178c | ||
|
|
8dd1a4cc97 | ||
|
|
397752311f | ||
|
|
4ae1202a68 | ||
|
|
9b7801379c | ||
|
|
339dec2a3b | ||
|
|
214db8da48 | ||
|
|
8a34bf95d2 | ||
|
|
635fe8b42f | ||
|
|
5c576b95af | ||
|
|
8ea6b48879 | ||
|
|
bb1308acc7 | ||
|
|
1e3de8e76a | ||
|
|
0bc73da66d | ||
|
|
382d96496a | ||
|
|
b9dd12c88c | ||
|
|
e1f31d3d02 | ||
|
|
339cd9b84e | ||
|
|
7deac4ac8b | ||
|
|
fec887a67c | ||
|
|
f3a3a4ed87 | ||
|
|
ec65187442 | ||
|
|
079c0495c3 | ||
|
|
90cfed2ada | ||
|
|
8716b8e992 | ||
|
|
a277541354 | ||
|
|
b01564f179 | ||
|
|
d9bb4e2e46 | ||
|
|
05d0aee494 | ||
|
|
aabec99a8e | ||
|
|
d277dd49a3 | ||
|
|
18f9d19b18 | ||
|
|
530eed5c6d | ||
|
|
12f4e0068a | ||
|
|
e8976e0f1c | ||
|
|
6eb52581eb | ||
|
|
8606e69fd6 | ||
|
|
c7b045bffc | ||
|
|
b66cc66503 | ||
|
|
0e4719018a | ||
|
|
0ebd52aac3 | ||
|
|
6c971b856e | ||
|
|
6cb293688d | ||
|
|
16709dff6c | ||
|
|
95dd3481c0 | ||
|
|
47c0c629c7 | ||
|
|
636c551047 | ||
|
|
72384b2b71 | ||
|
|
a9b1ff9bea | ||
|
|
e40bc32624 | ||
|
|
bd21bc82b7 | ||
|
|
9c1680e82c | ||
|
|
1a78c3695d | ||
|
|
10196f3d7d | ||
|
|
2c86fefbb5 | ||
|
|
df689dccd5 | ||
|
|
6033fbe9bd | ||
|
|
519a204424 | ||
|
|
3fc5e57fef | ||
|
|
906933d2b3 | ||
|
|
e9a937ad6d | ||
|
|
6dc2cdfae4 | ||
|
|
69ea410406 | ||
|
|
24a576c8e9 | ||
|
|
d417ffee6e | ||
|
|
f868b51483 | ||
|
|
26dc73c809 | ||
|
|
9119c89eba | ||
|
|
4e7ea34ae9 | ||
|
|
d7d291217b | ||
|
|
42facfacbc | ||
|
|
bd1e848363 | ||
|
|
0ffb58b764 | ||
|
|
9551b6973a | ||
|
|
f7ef5f50a4 | ||
|
|
046cd80054 |
@@ -2,6 +2,7 @@
|
||||
Checks: '*,
|
||||
-abseil-string-find-str-contains,
|
||||
-altera-struct-pack-align,
|
||||
-altera-unroll-loops,
|
||||
-android-*,
|
||||
-cert-err58-cpp,
|
||||
-cppcoreguidelines-avoid-c-arrays,
|
||||
@@ -58,7 +59,8 @@ Checks: '*,
|
||||
-readability-implicit-bool-conversion,
|
||||
-readability-magic-numbers,
|
||||
-readability-named-parameter,
|
||||
-misc-no-recursion'
|
||||
-misc-no-recursion,
|
||||
-concurrency-mt-unsafe'
|
||||
WarningsAsErrors: ''
|
||||
HeaderFilterRegex: 'src/.*'
|
||||
AnalyzeTemporaryDtors: false
|
||||
|
||||
@@ -30,6 +30,14 @@ for file in $modified_files; do
|
||||
if [ $code -ne 0 ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
echo "Running header checker..."
|
||||
$project_folder/tools/header-checker.py $tmpdir/$file $file --amend-year
|
||||
code=$?
|
||||
|
||||
if [ $code -ne 0 ]; then
|
||||
break
|
||||
fi
|
||||
done;
|
||||
|
||||
return $code
|
||||
|
||||
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG] "
|
||||
labels: bug
|
||||
assignees: gitbuda, antonio2368
|
||||
|
||||
---
|
||||
|
||||
|
||||
**Memgraph version**
|
||||
Which version did you use?
|
||||
|
||||
**Environment**
|
||||
Some information about the environment you are using Memgraph on: operating
|
||||
system, how do you connect, with or without docker, which driver etc.
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Run the following query '...'
|
||||
2. Click on '....'
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Logs**
|
||||
If applicable, add logs of Memgraph, CLI output or screenshots to help explain
|
||||
your problem.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
@@ -42,6 +42,16 @@ jobs:
|
||||
--groups aggregation 1000_create unwind_create dense_expand match \
|
||||
--no-strict
|
||||
|
||||
- name: Get branch name (merge)
|
||||
if: github.event_name != 'pull_request'
|
||||
shell: bash
|
||||
run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/} | tr / -)" >> $GITHUB_ENV
|
||||
|
||||
- name: Get branch name (pull request)
|
||||
if: github.event_name == 'pull_request'
|
||||
shell: bash
|
||||
run: echo "BRANCH_NAME=$(echo ${GITHUB_HEAD_REF} | tr / -)" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload macro benchmark results
|
||||
run: |
|
||||
cd tools/bench-graph-client
|
||||
@@ -51,7 +61,8 @@ jobs:
|
||||
./main.py --benchmark-name "macro_benchmark" \
|
||||
--benchmark-results-path "../../tests/macro_benchmark/.harness_summary" \
|
||||
--github-run-id "${{ github.run_id }}" \
|
||||
--github-run-number "${{ github.run_number }}"
|
||||
--github-run-number "${{ github.run_number }}" \
|
||||
--head-branch-name "${{ env.BRANCH_NAME }}"
|
||||
|
||||
- name: Run mgbench
|
||||
run: |
|
||||
@@ -67,4 +78,5 @@ jobs:
|
||||
./main.py --benchmark-name "mgbench" \
|
||||
--benchmark-results-path "../../tests/mgbench/benchmark_result.json" \
|
||||
--github-run-id "${{ github.run_id }}" \
|
||||
--github-run-number "${{ github.run_number }}"
|
||||
--github-run-number "${{ github.run_number }}" \
|
||||
--head-branch-name "${{ env.BRANCH_NAME }}"
|
||||
89
.github/workflows/diff.yaml
vendored
89
.github/workflows/diff.yaml
vendored
@@ -2,11 +2,15 @@ name: Diff
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '**/*.md'
|
||||
- '.clang-format'
|
||||
- 'CODEOWNERS'
|
||||
- "docs/**"
|
||||
- "**/*.md"
|
||||
- ".clang-format"
|
||||
- "CODEOWNERS"
|
||||
|
||||
jobs:
|
||||
community_build:
|
||||
@@ -28,7 +32,7 @@ jobs:
|
||||
- name: Build community binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
@@ -41,38 +45,12 @@ jobs:
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure -j$THREADS
|
||||
|
||||
- name: Run stress test
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration
|
||||
|
||||
- name: Create community DEB package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
cd build
|
||||
|
||||
# create mgconsole
|
||||
# we use the -B to force the build
|
||||
make -j$THREADS -B mgconsole
|
||||
|
||||
# Create community DEB package.
|
||||
mkdir output && cd output
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save community DEB package
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Community DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
|
||||
code_analysis:
|
||||
name: "Code analysis"
|
||||
runs-on: [self-hosted, Linux, X64, Diff]
|
||||
@@ -92,7 +70,7 @@ jobs:
|
||||
- name: Build combined ASAN, UBSAN and coverage binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
@@ -104,7 +82,7 @@ jobs:
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run unit tests. It is restricted to 2 threads intentionally, because higher concurrency makes the timing related tests unstable.
|
||||
cd build
|
||||
@@ -113,7 +91,7 @@ jobs:
|
||||
- name: Compute code coverage
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Compute code coverage.
|
||||
cd tools/github
|
||||
@@ -131,7 +109,7 @@ jobs:
|
||||
|
||||
- name: Run clang-tidy
|
||||
run: |
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Restrict clang-tidy results only to the modified parts
|
||||
git diff -U0 master... -- src ':!*.hpp' | ./tools/github/clang-tidy/clang-tidy-diff.py -p 1 -j $THREADS -path build | tee ./build/clang_tidy_output.txt
|
||||
@@ -158,7 +136,7 @@ jobs:
|
||||
- name: Build debug binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
@@ -171,7 +149,7 @@ jobs:
|
||||
- name: Run leftover CTest tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run leftover CTest tests (all except unit and benchmark tests).
|
||||
cd build
|
||||
@@ -203,7 +181,7 @@ jobs:
|
||||
- name: Run cppcheck and clang-format
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run cppcheck and clang-format.
|
||||
cd tools/github
|
||||
@@ -234,7 +212,7 @@ jobs:
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
@@ -257,6 +235,15 @@ jobs:
|
||||
tests/gql_behave/gql_behave_status.csv
|
||||
tests/gql_behave/gql_behave_status.html
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure -j$THREADS
|
||||
|
||||
- name: Run e2e tests
|
||||
run: |
|
||||
# TODO(gitbuda): Setup mgclient and pymgclient properly.
|
||||
@@ -285,7 +272,7 @@ jobs:
|
||||
- name: Create enterprise DEB package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
cd build
|
||||
|
||||
@@ -332,7 +319,7 @@ jobs:
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
@@ -373,7 +360,7 @@ jobs:
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
@@ -390,6 +377,16 @@ jobs:
|
||||
--groups aggregation 1000_create unwind_create dense_expand match \
|
||||
--no-strict
|
||||
|
||||
- name: Get branch name (merge)
|
||||
if: github.event_name != 'pull_request'
|
||||
shell: bash
|
||||
run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/} | tr / -)" >> $GITHUB_ENV
|
||||
|
||||
- name: Get branch name (pull request)
|
||||
if: github.event_name == 'pull_request'
|
||||
shell: bash
|
||||
run: echo "BRANCH_NAME=$(echo ${GITHUB_HEAD_REF} | tr / -)" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload macro benchmark results
|
||||
run: |
|
||||
cd tools/bench-graph-client
|
||||
@@ -399,7 +396,8 @@ jobs:
|
||||
./main.py --benchmark-name "macro_benchmark" \
|
||||
--benchmark-results-path "../../tests/macro_benchmark/.harness_summary" \
|
||||
--github-run-id "${{ github.run_id }}" \
|
||||
--github-run-number "${{ github.run_number }}"
|
||||
--github-run-number "${{ github.run_number }}" \
|
||||
--head-branch-name "${{ env.BRANCH_NAME }}"
|
||||
|
||||
- name: Run mgbench
|
||||
run: |
|
||||
@@ -415,4 +413,5 @@ jobs:
|
||||
./main.py --benchmark-name "mgbench" \
|
||||
--benchmark-results-path "../../tests/mgbench/benchmark_result.json" \
|
||||
--github-run-id "${{ github.run_id }}" \
|
||||
--github-run-number "${{ github.run_number }}"
|
||||
--github-run-number "${{ github.run_number }}" \
|
||||
--head-branch-name "${{ env.BRANCH_NAME }}"
|
||||
|
||||
6
.github/workflows/full_clang_tidy.yaml
vendored
6
.github/workflows/full_clang_tidy.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Build debug binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
@@ -36,10 +36,10 @@ jobs:
|
||||
|
||||
- name: Run clang-tidy
|
||||
run: |
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# The results are also written to standard output in order to retain them in the logs
|
||||
./tools/github/clang-tidy/run-clang-tidy.py -p build -j $THREADS -clang-tidy-binary=/opt/toolchain-v3/bin/clang-tidy "$PWD/src/*" |
|
||||
./tools/github/clang-tidy/run-clang-tidy.py -p build -j $THREADS -clang-tidy-binary=/opt/toolchain-v4/bin/clang-tidy "$PWD/src/*" |
|
||||
tee ./build/full_clang_tidy_output.txt
|
||||
|
||||
- name: Summarize clang-tidy results
|
||||
|
||||
65
.github/workflows/package_all.yaml
vendored
65
.github/workflows/package_all.yaml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: centos-7_enterprise
|
||||
name: centos-7
|
||||
path: build/output/centos-7/memgraph*.rpm
|
||||
|
||||
centos-8:
|
||||
@@ -36,26 +36,9 @@ jobs:
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: centos-8_enterprise
|
||||
name: centos-8
|
||||
path: build/output/centos-8/memgraph*.rpm
|
||||
|
||||
debian-9:
|
||||
runs-on: [self-hosted, DockerMgBuild]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package debian-9
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: debian-9_enterprise
|
||||
path: build/output/debian-9/memgraph*.deb
|
||||
|
||||
debian-10:
|
||||
runs-on: [self-hosted, DockerMgBuild]
|
||||
timeout-minutes: 60
|
||||
@@ -70,9 +53,26 @@ jobs:
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: debian-10_enterprise
|
||||
name: debian-10
|
||||
path: build/output/debian-10/memgraph*.deb
|
||||
|
||||
debian-11:
|
||||
runs-on: [self-hosted, DockerMgBuild]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package debian-11
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: debian-11
|
||||
path: build/output/debian-11/memgraph*.deb
|
||||
|
||||
docker:
|
||||
runs-on: [self-hosted, DockerMgBuild]
|
||||
timeout-minutes: 60
|
||||
@@ -84,12 +84,12 @@ jobs:
|
||||
- name: "Build package"
|
||||
run: |
|
||||
cd release/package
|
||||
./run.sh package debian-10 --for-docker
|
||||
./run.sh package debian-11 --for-docker
|
||||
./run.sh docker
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: docker_enterprise
|
||||
name: docker
|
||||
path: build/output/docker/memgraph*.tar.gz
|
||||
|
||||
ubuntu-1804:
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ubuntu-1804_enterprise
|
||||
name: ubuntu-1804
|
||||
path: build/output/ubuntu-18.04/memgraph*.deb
|
||||
|
||||
ubuntu-2004:
|
||||
@@ -123,5 +123,22 @@ jobs:
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ubuntu-2004_enterprise
|
||||
name: ubuntu-2004
|
||||
path: build/output/ubuntu-20.04/memgraph*.deb
|
||||
|
||||
debian-11-platform:
|
||||
runs-on: [self-hosted, DockerMgBuild]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package debian-11 --for-platform
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: debian-11-platform
|
||||
path: build/output/debian-11/memgraph*.deb
|
||||
|
||||
63
.github/workflows/release_centos8.yaml
vendored
63
.github/workflows/release_centos8.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Build community binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
@@ -36,47 +36,15 @@ jobs:
|
||||
cmake -DCMAKE_BUILD_TYPE=release -DMG_ENTERPRISE=OFF ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Create community RPM package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
cd build
|
||||
|
||||
# create mgconsole
|
||||
# we use the -B to force the build
|
||||
make -j$THREADS -B mgconsole
|
||||
|
||||
# Create community RPM package.
|
||||
mkdir output && cd output
|
||||
cpack -G RPM --config ../CPackConfig.cmake
|
||||
rpmlint memgraph*.rpm
|
||||
|
||||
- name: Save community RPM package
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Community RPM package"
|
||||
path: build/output/memgraph*.rpm
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Run stress test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration
|
||||
|
||||
- name: Run stress test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration --large-dataset
|
||||
|
||||
coverage_build:
|
||||
name: "Coverage build"
|
||||
runs-on: [self-hosted, Linux, X64, CentOS8]
|
||||
@@ -96,7 +64,7 @@ jobs:
|
||||
- name: Build coverage binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
@@ -109,7 +77,7 @@ jobs:
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
@@ -118,7 +86,7 @@ jobs:
|
||||
- name: Compute code coverage
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Compute code coverage.
|
||||
cd tools/github
|
||||
@@ -153,7 +121,7 @@ jobs:
|
||||
- name: Build debug binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
@@ -166,7 +134,7 @@ jobs:
|
||||
- name: Run leftover CTest tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run leftover CTest tests (all except unit and benchmark tests).
|
||||
cd build
|
||||
@@ -198,7 +166,7 @@ jobs:
|
||||
- name: Run cppcheck and clang-format
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run cppcheck and clang-format.
|
||||
cd tools/github
|
||||
@@ -230,7 +198,7 @@ jobs:
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
@@ -243,7 +211,7 @@ jobs:
|
||||
- name: Create enterprise RPM package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
cd build
|
||||
|
||||
@@ -265,7 +233,7 @@ jobs:
|
||||
- name: Run micro benchmark tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run micro benchmark tests.
|
||||
cd build
|
||||
@@ -301,6 +269,15 @@ jobs:
|
||||
tests/gql_behave/gql_behave_status.csv
|
||||
tests/gql_behave/gql_behave_status.html
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Run e2e tests
|
||||
run: |
|
||||
# TODO(gitbuda): Setup mgclient and pymgclient properly.
|
||||
|
||||
64
.github/workflows/release_debian10.yaml
vendored
64
.github/workflows/release_debian10.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Build community binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
@@ -36,46 +36,15 @@ jobs:
|
||||
cmake -DCMAKE_BUILD_TYPE=release -DMG_ENTERPRISE=OFF ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Create community DEB package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
cd build
|
||||
|
||||
# create mgconsole
|
||||
# we use the -B to force the build
|
||||
make -j$THREADS -B mgconsole
|
||||
|
||||
# Create community DEB package.
|
||||
mkdir output && cd output
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save community DEB package
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Community DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Run stress test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration
|
||||
|
||||
- name: Run stress test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration --large-dataset
|
||||
|
||||
coverage_build:
|
||||
name: "Coverage build"
|
||||
runs-on: [self-hosted, Linux, X64, Debian10]
|
||||
@@ -95,7 +64,7 @@ jobs:
|
||||
- name: Build coverage binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
@@ -108,7 +77,7 @@ jobs:
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
@@ -117,7 +86,7 @@ jobs:
|
||||
- name: Compute code coverage
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Compute code coverage.
|
||||
cd tools/github
|
||||
@@ -152,7 +121,7 @@ jobs:
|
||||
- name: Build debug binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
@@ -165,7 +134,7 @@ jobs:
|
||||
- name: Run leftover CTest tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run leftover CTest tests (all except unit and benchmark tests).
|
||||
cd build
|
||||
@@ -197,7 +166,7 @@ jobs:
|
||||
- name: Run cppcheck and clang-format
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run cppcheck and clang-format.
|
||||
cd tools/github
|
||||
@@ -229,7 +198,7 @@ jobs:
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
@@ -242,7 +211,7 @@ jobs:
|
||||
- name: Create enterprise DEB package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
cd build
|
||||
|
||||
@@ -263,7 +232,7 @@ jobs:
|
||||
- name: Run micro benchmark tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run micro benchmark tests.
|
||||
cd build
|
||||
@@ -299,6 +268,15 @@ jobs:
|
||||
tests/gql_behave/gql_behave_status.csv
|
||||
tests/gql_behave/gql_behave_status.html
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Run e2e tests
|
||||
run: |
|
||||
# TODO(gitbuda): Setup mgclient and pymgclient properly.
|
||||
@@ -355,7 +333,7 @@ jobs:
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
62
.github/workflows/release_ubuntu2004.yaml
vendored
62
.github/workflows/release_ubuntu2004.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Build community binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
@@ -36,46 +36,15 @@ jobs:
|
||||
cmake -DCMAKE_BUILD_TYPE=release -DMG_ENTERPRISE=OFF ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Create community DEB package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
cd build
|
||||
|
||||
# create mgconsole
|
||||
# we use the -B to force the build
|
||||
make -j$THREADS -B mgconsole
|
||||
|
||||
# Create community DEB package.
|
||||
mkdir output && cd output
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save community DEB package
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Community DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Run stress test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration
|
||||
|
||||
- name: Run stress test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration --large-dataset
|
||||
|
||||
coverage_build:
|
||||
name: "Coverage build"
|
||||
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
||||
@@ -95,7 +64,7 @@ jobs:
|
||||
- name: Build coverage binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
@@ -108,7 +77,7 @@ jobs:
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
@@ -117,7 +86,7 @@ jobs:
|
||||
- name: Compute code coverage
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Compute code coverage.
|
||||
cd tools/github
|
||||
@@ -152,7 +121,7 @@ jobs:
|
||||
- name: Build debug binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
@@ -165,7 +134,7 @@ jobs:
|
||||
- name: Run leftover CTest tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run leftover CTest tests (all except unit and benchmark tests).
|
||||
cd build
|
||||
@@ -197,7 +166,7 @@ jobs:
|
||||
- name: Run cppcheck and clang-format
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run cppcheck and clang-format.
|
||||
cd tools/github
|
||||
@@ -229,7 +198,7 @@ jobs:
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
@@ -242,7 +211,7 @@ jobs:
|
||||
- name: Create enterprise DEB package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
cd build
|
||||
|
||||
@@ -263,7 +232,7 @@ jobs:
|
||||
- name: Run micro benchmark tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run micro benchmark tests.
|
||||
cd build
|
||||
@@ -299,6 +268,15 @@ jobs:
|
||||
tests/gql_behave/gql_behave_status.csv
|
||||
tests/gql_behave/gql_behave_status.html
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Run e2e tests
|
||||
run: |
|
||||
# TODO(gitbuda): Setup mgclient and pymgclient properly.
|
||||
|
||||
@@ -35,9 +35,6 @@ else()
|
||||
message(FATAL_ERROR "Couldn't find clang and/or clang++!")
|
||||
endif()
|
||||
|
||||
option(BUILD_FOR_DOCKER "Build Memgraph binary for docker." OFF)
|
||||
message(STATUS "BUILD_FOR_DOCKER: ${BUILD_FOR_DOCKER}")
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
project(memgraph)
|
||||
@@ -69,7 +66,7 @@ if (MG_ENTERPRISE)
|
||||
else()
|
||||
set(get_version_offering "--open-source")
|
||||
endif()
|
||||
set(get_version_script "${CMAKE_SOURCE_DIR}/release/get_version.py")
|
||||
set(get_version_script "${CMAKE_CURRENT_SOURCE_DIR}/release/get_version.py")
|
||||
|
||||
# Get version that should be used in the binary.
|
||||
execute_process(
|
||||
@@ -79,7 +76,7 @@ execute_process(
|
||||
"${MEMGRAPH_OVERRIDE_VERSION}"
|
||||
"${MEMGRAPH_OVERRIDE_VERSION_SUFFIX}"
|
||||
"--memgraph-root-dir"
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
)
|
||||
if(MEMGRAPH_VERSION_RESULT AND NOT MEMGRAPH_VERSION_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "Unable to get Memgraph version.")
|
||||
@@ -96,7 +93,7 @@ execute_process(
|
||||
"${MEMGRAPH_OVERRIDE_VERSION}"
|
||||
"${MEMGRAPH_OVERRIDE_VERSION_SUFFIX}"
|
||||
"--memgraph-root-dir"
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
)
|
||||
if(MEMGRAPH_VERSION_DEB_RESULT AND NOT MEMGRAPH_VERSION_DEB_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "Unable to get Memgraph DEB version.")
|
||||
@@ -113,7 +110,7 @@ execute_process(
|
||||
"${MEMGRAPH_OVERRIDE_VERSION}"
|
||||
"${MEMGRAPH_OVERRIDE_VERSION_SUFFIX}"
|
||||
"--memgraph-root-dir"
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
)
|
||||
if(MEMGRAPH_VERSION_RPM_RESULT AND NOT MEMGRAPH_VERSION_RPM_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "Unable to get Memgraph RPM version.")
|
||||
@@ -325,7 +322,8 @@ if (UBSAN)
|
||||
# To make the program abort on undefined behavior, use UBSAN_OPTIONS=halt_on_error=1.
|
||||
endif()
|
||||
|
||||
set(MG_PYTHON_VERSION "" CACHE STRING "Specify the exact python version used by the query modules")
|
||||
set(MG_PYTHON_VERSION "" CACHE STRING "Specify the exact Python version used by the query modules")
|
||||
set(MG_PYTHON_PATH "" CACHE STRING "Specify the exact Python path used by the query modules")
|
||||
|
||||
# Add subprojects
|
||||
include_directories(src)
|
||||
@@ -334,8 +332,13 @@ add_subdirectory(src)
|
||||
# Release configuration
|
||||
add_subdirectory(release)
|
||||
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
option(MG_ENABLE_TESTING "Set this to OFF to disable building test binaries" ON)
|
||||
message(STATUS "MG_ENABLE_TESTING: ${MG_ENABLE_TESTING}")
|
||||
|
||||
if (MG_ENABLE_TESTING)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
if(TOOLS)
|
||||
add_subdirectory(tools)
|
||||
|
||||
127
CODE_OF_CONDUCT.md
Normal file
127
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing other's private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
[contact@memgraph.com](contact@memgraph.com). All complaints will be reviewed
|
||||
and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the Contributor Covenant, version 2.1,
|
||||
available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder][mozilla coc].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq).
|
||||
Translations are available at
|
||||
[https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations).
|
||||
121
CONTRIBUTING.md
Normal file
121
CONTRIBUTING.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# How to contribute?
|
||||
|
||||
This is a general purpose guide for contributing to Memgraph. We're still
|
||||
working out the kinks to make contributing to this project as easy and
|
||||
transparent as possible, but we're not quite there yet. Hopefully, this document
|
||||
makes the process for contributing clear and answers some questions that you may
|
||||
have.
|
||||
|
||||
- [How to contribute?](#how-to-contribute)
|
||||
- [Open development](#open-development)
|
||||
- [Branch organization](#branch-organization)
|
||||
- [Bugs & changes](#bugs--changes)
|
||||
- [Where to find known issues?](#where-to-find-known-issues)
|
||||
- [Proposing a change](#proposing-a-change)
|
||||
- [Your first pull request](#your-first-pull-request)
|
||||
- [Sending a pull request](#sending-a-pull-request)
|
||||
- [Style guide](#style-guide)
|
||||
- [How to get in touch?](#how-to-get-in-touch)
|
||||
- [Code of Conduct](#code-of-conduct)
|
||||
- [License](#license)
|
||||
- [Attribution](#attribution)
|
||||
|
||||
## Open development
|
||||
|
||||
All work on Memgraph is done via [GitHub](https://github.com/memgraph/memgraph).
|
||||
Both core team members and external contributors send pull requests which go
|
||||
through the same review process.
|
||||
|
||||
## Branch organization
|
||||
|
||||
Most pull requests should target the [`master
|
||||
branch`](https://github.com/memgraph/memgraph/tree/master). We only use separate
|
||||
branches for developing new features and fixing bugs before they are merged with
|
||||
`master`. We do our best to keep `master` in good shape, with all tests passing.
|
||||
|
||||
Code that lands in `master` must be compatible with the latest stable release.
|
||||
It may contain additional features but no breaking changes if it's not
|
||||
absolutely necessary. We should be able to release a new minor version from the
|
||||
tip of `master` at any time.
|
||||
|
||||
## Bugs & changes
|
||||
|
||||
### Where to find known issues?
|
||||
|
||||
We are using [GitHub Issues](https://github.com/memgraph/memgraph/issues) for
|
||||
our public bugs. We keep a close eye on this and try to make it clear when we
|
||||
have an internal fix in progress. Before filing a new task, try to make sure
|
||||
your problem doesn't already exist.
|
||||
|
||||
### Proposing a change
|
||||
|
||||
If you intend to change the public API, or make any non-trivial changes to the
|
||||
implementation, we recommend [filing an
|
||||
issue](https://github.com/memgraph/memgraph/issues/new). This lets us reach an
|
||||
agreement on your proposal before you put significant effort into it.
|
||||
|
||||
If you're only fixing a bug, it's fine to submit a pull request right away but
|
||||
we still recommend to file an issue detailing what you're fixing. This is
|
||||
helpful in case we don't accept that specific fix but want to keep track of the
|
||||
issue.
|
||||
|
||||
### Your first pull request
|
||||
|
||||
Working on your first Pull Request? You can learn how from this free video
|
||||
series:
|
||||
|
||||
**[How to Contribute to an Open Source Project on
|
||||
GitHub](https://app.egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github)**
|
||||
|
||||
If you decide to fix an issue, please be sure to check the comment thread in
|
||||
case somebody is already working on a fix. If nobody is working on it at the
|
||||
moment, please leave a comment stating that you intend to work on it so other
|
||||
people don't accidentally duplicate your effort.
|
||||
|
||||
If somebody claims an issue but doesn't follow up for more than two weeks, it's
|
||||
fine to take it over but you should still leave a comment.
|
||||
|
||||
### Sending a pull request
|
||||
|
||||
The core team is monitoring for pull requests. We will review your pull request
|
||||
and either merge it, request changes to it, or close it with an explanation.
|
||||
**Before submitting a pull request,** please make sure the following is done:
|
||||
|
||||
1. Fork [the repository](https://github.com/memgraph/memgraph) and create your
|
||||
branch from `master`.
|
||||
2. If you've fixed a bug or added code that should be tested, add tests!
|
||||
3. Use the formatter `clang-format` for C/C++ code and `flake8` for Python code.
|
||||
`clang-format` will automatically detect the `.clang-format` file in the root
|
||||
directory while `flake8` can be used with the default configuration.
|
||||
|
||||
### Style guide
|
||||
|
||||
Memgraph uses the [Google Style
|
||||
Guide](https://google.github.io/styleguide/cppguide.html) for C++ in most of its
|
||||
code. You should follow them whenever writing new code.
|
||||
|
||||
## How to get in touch?
|
||||
|
||||
Aside from communicating directly via Pull Requests and Issues, the Memgraph
|
||||
Community [Discord Server](https://discord.gg/memgraph) is the best place for
|
||||
conversing with project maintainers and other community members.
|
||||
|
||||
## [Code of Conduct](https://github.com/memgraph/memgraph/blob/master/CODE_OF_CONDUCT.md)
|
||||
|
||||
Memgraph has adopted the [Contributor
|
||||
Covenant](https://www.contributor-covenant.org/) as its Code of Conduct, and we
|
||||
expect project participants to adhere to it. Please read [the full
|
||||
text](https://github.com/memgraph/memgraph/blob/master/CODE_OF_CONDUCT.md) so
|
||||
that you can understand what actions will and will not be tolerated.
|
||||
|
||||
## License
|
||||
|
||||
By contributing to Memgraph, you agree that your contributions will be licensed
|
||||
under the [Memgraph licensing
|
||||
scheme](https://github.com/memgraph/memgraph/blob/master/LICENSE).
|
||||
|
||||
## Attribution
|
||||
|
||||
This Contributing guide is adapted from the **React.js Contributing guide**
|
||||
available at
|
||||
[https://reactjs.org/docs/how-to-contribute.html](https://reactjs.org/docs/how-to-contribute.html).
|
||||
143
README.md
143
README.md
@@ -1,21 +1,136 @@
|
||||
# Memgraph
|
||||
<p align="center">
|
||||
<img width="400px" src="https://uploads-ssl.webflow.com/5e7ceb09657a69bdab054b3a/5e7ceb09657a6937ab054bba_Black_Original%20_Logo.png">
|
||||
</p>
|
||||
|
||||
Memgraph is an ACID compliant high performance transactional distributed
|
||||
in-memory graph database featuring runtime native query compiling, lock free
|
||||
data structures, multi-version concurrency control and asynchronous IO.
|
||||
---
|
||||
|
||||
## Development Documentation
|
||||
<p align="center">
|
||||
Build modern, graph-based applications on top of your streaming data in minutes.
|
||||
</p>
|
||||
|
||||
Please continue in
|
||||
[Notion](https://www.notion.so/memgraph/memgraph-0428591638604c8385550e214ea9f3e6).
|
||||
<p align="center">
|
||||
<a href="https://github.com/memgraph/memgraph/blob/master/licenses/APL.txt">
|
||||
<img src="https://img.shields.io/badge/license-APL-green" alt="license" title="license"/>
|
||||
</a>
|
||||
<a href="https://github.com/memgraph/memgraph/blob/master/licenses/BSL.txt">
|
||||
<img src="https://img.shields.io/badge/license-BSL-yellowgreen" alt="license" title="license"/>
|
||||
</a>
|
||||
<a href="https://github.com/memgraph/memgraph/blob/master/licenses/MEL.txt" alt="Documentation">
|
||||
<img src="https://img.shields.io/badge/license-MEL-yellow" alt="license" title="license"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## User Documentation
|
||||
<p align="center">
|
||||
<a href="https://github.com/memgraph/memgraph">
|
||||
<img src="https://img.shields.io/github/workflow/status/memgraph/memgraph/Release%20Ubuntu%2020.04/master" alt="build" title="build"/>
|
||||
</a>
|
||||
<a href="https://memgraph.com/docs/" alt="Documentation">
|
||||
<img src="https://img.shields.io/badge/documentation-Memgraph-orange" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
Memgraph user documentation is maintained within
|
||||
[docs](https://github.com/memgraph/docs) repository. The documentation is also
|
||||
available on [GitBook](https://docs.memgraph.com).
|
||||
<p align="center">
|
||||
<a href="https://memgr.ph/join-discord">
|
||||
<img src="https://img.shields.io/badge/Discord-7289DA?style=for-the-badge&logo=discord&logoColor=white" alt="Discord"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## Licences
|
||||
## :clipboard: Description
|
||||
|
||||
* [Community](release/LICENSE_COMMUNITY.md)
|
||||
* [Enterprise](release/LICENSE_ENTERPRISE.md)
|
||||
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
|
||||
interface to query your data with Cypher, a widely-used and declarative query
|
||||
language that is easy to write, understand and optimize for performance. This is
|
||||
achieved by using the property graph data model, which stores data in terms of
|
||||
objects, their attributes, and the relationships that connect them. This is a
|
||||
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 consistently and
|
||||
without surprises. It’s also ACID-compliant and highly available.
|
||||
|
||||
## :floppy_disk: Download & Install
|
||||
|
||||
### Windows
|
||||
|
||||
[](https://memgraph.com/docs/memgraph/install-memgraph-on-windows-docker)
|
||||
[](https://memgraph.com/docs/memgraph/install-memgraph-on-windows-wsl)
|
||||
|
||||
### macOS
|
||||
|
||||
[](https://memgraph.com/docs/memgraph/install-memgraph-on-macos-docker)
|
||||
|
||||
### Linux
|
||||
|
||||
[](https://memgraph.com/docs/memgraph/install-memgraph-on-linux-docker)
|
||||
[](https://memgraph.com/docs/memgraph/install-memgraph-on-debian)
|
||||
[](https://memgraph.com/docs/memgraph/install-memgraph-on-ubuntu)
|
||||
[](https://memgraph.com/docs/memgraph/install-memgraph-from-rpm)
|
||||
|
||||
You can find the binaries and Docker images on the [Download
|
||||
Hub](https://memgraph.com/download) and the installation instructions in the
|
||||
[official documentation](https://memgraph.com/docs/memgraph/installation).
|
||||
|
||||
## :zap: Features
|
||||
|
||||
- Run Python, Rust, and C/C++ code natively, check out the
|
||||
[MAGE](https://github.com/memgraph/mage) graph algorithm library
|
||||
- Native support for machine learning
|
||||
- Streaming support
|
||||
- Replication
|
||||
- Authentication and authorization
|
||||
- ACID compliance
|
||||
|
||||
## :bookmark_tabs: Documentation
|
||||
|
||||
The Memgraph documentation is available at
|
||||
[memgraph.com/docs](https://memgraph.com/docs).
|
||||
|
||||
## :question: Configuration
|
||||
|
||||
Command line options that Memgraph accepts are available in the [reference
|
||||
guide](https://memgraph.com/docs/memgraph/reference-guide/configuration).
|
||||
|
||||
## :trophy: Contributing
|
||||
|
||||
The main purpose of this repository is to continue evolving Memgraph, making it
|
||||
faster and easier to use. Development of Memgraph happens in the open on GitHub,
|
||||
and we are grateful to the community for contributing bug fixes and
|
||||
improvements. Read below to learn how you can take part in improving Memgraph.
|
||||
|
||||
### Code of Conduct
|
||||
|
||||
Memgraph has adopted a Code of Conduct that we expect project participants to
|
||||
adhere to. Please read [the full text](CODE_OF_CONDUCT.md) so that you can
|
||||
understand what actions will and will not be tolerated.
|
||||
|
||||
### Contributing Guide
|
||||
|
||||
Read our [contributing guide](CONTRIBUTING.md) to learn about our development
|
||||
process and how to propose bug fixes and improvements.
|
||||
|
||||
### Internals
|
||||
|
||||
Read our
|
||||
[internal](https://memgraph.notion.site/Memgraph-Internals-12b69132d67a417898972927d6870bd2)
|
||||
docs to learn more about Memgraph's architecture, how to build the project from
|
||||
source and how to start contributing. All information related to the database,
|
||||
can be found in the aforementioned docs.
|
||||
|
||||
### :scroll: License
|
||||
|
||||
Memgraph Community is available under the [BSL
|
||||
license](./licenses/BSL.txt).</br> Memgraph Enterprise is available under the
|
||||
[MEL license](./licenses/MEL.txt).
|
||||
|
||||
<p align="center">
|
||||
<a href="#">
|
||||
<img src="https://img.shields.io/badge/⬆️back_to_top_⬆️-white" alt="Back to top" title="Back to top"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
55
cmake/FindJemalloc.cmake
Normal file
55
cmake/FindJemalloc.cmake
Normal file
@@ -0,0 +1,55 @@
|
||||
# Try to find jemalloc library
|
||||
#
|
||||
# Use this module as:
|
||||
# find_package(Jemalloc)
|
||||
#
|
||||
# or:
|
||||
# find_package(Jemalloc REQUIRED)
|
||||
#
|
||||
# This will define the following variables:
|
||||
#
|
||||
# Jemalloc_FOUND True if the system has the jemalloc library.
|
||||
# Jemalloc_INCLUDE_DIRS Include directories needed to use jemalloc.
|
||||
# Jemalloc_LIBRARIES Libraries needed to link to jemalloc.
|
||||
#
|
||||
# The following cache variables may also be set:
|
||||
#
|
||||
# Jemalloc_INCLUDE_DIR The directory containing jemalloc/jemalloc.h.
|
||||
# Jemalloc_LIBRARY The path to the jemalloc static library.
|
||||
|
||||
find_path(Jemalloc_INCLUDE_DIR NAMES jemalloc/jemalloc.h PATH_SUFFIXES include)
|
||||
|
||||
find_library(Jemalloc_LIBRARY NAMES libjemalloc.a PATH_SUFFIXES lib)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Jemalloc
|
||||
FOUND_VAR Jemalloc_FOUND
|
||||
REQUIRED_VARS
|
||||
Jemalloc_LIBRARY
|
||||
Jemalloc_INCLUDE_DIR
|
||||
)
|
||||
|
||||
if(Jemalloc_FOUND)
|
||||
set(Jemalloc_LIBRARIES ${Jemalloc_LIBRARY})
|
||||
set(Jemalloc_INCLUDE_DIRS ${Jemalloc_INCLUDE_DIR})
|
||||
else()
|
||||
if(Jemalloc_FIND_REQUIRED)
|
||||
message(FATAL_ERROR "Cannot find jemalloc!")
|
||||
else()
|
||||
message(WARNING "jemalloc is not found!")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(Jemalloc_FOUND AND NOT TARGET Jemalloc::Jemalloc)
|
||||
add_library(Jemalloc::Jemalloc UNKNOWN IMPORTED)
|
||||
set_target_properties(Jemalloc::Jemalloc
|
||||
PROPERTIES
|
||||
IMPORTED_LOCATION "${Jemalloc_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${Jemalloc_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(
|
||||
Jemalloc_INCLUDE_DIR
|
||||
Jemalloc_LIBRARY
|
||||
)
|
||||
@@ -1,4 +0,0 @@
|
||||
# Memgraph Build and Run Environments
|
||||
|
||||
Please continue in
|
||||
[Notion](https://www.notion.so/memgraph/Tools-05e0baafb78a49b386e0063b4833d23d).
|
||||
@@ -14,9 +14,14 @@ TOOLCHAIN_BUILD_DEPS=(
|
||||
expat-devel libipt-devel libbabeltrace-devel xz-devel python3-devel # gdb
|
||||
texinfo # gdb
|
||||
libcurl-devel # cmake
|
||||
curl # snappy
|
||||
readline-devel # cmake and llvm
|
||||
libffi-devel libxml2-devel perl-Digest-MD5 # llvm
|
||||
libedit-devel pcre-devel automake bison # swig
|
||||
file
|
||||
openssl-devel
|
||||
gmp-devel
|
||||
gperf
|
||||
)
|
||||
|
||||
TOOLCHAIN_RUN_DEPS=(
|
||||
@@ -26,10 +31,10 @@ TOOLCHAIN_RUN_DEPS=(
|
||||
expat libipt libbabeltrace xz-libs python3 # for gdb
|
||||
readline # for cmake and llvm
|
||||
libffi libxml2 # for llvm
|
||||
openssl-devel
|
||||
)
|
||||
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
git # source code control
|
||||
make pkgconfig # build system
|
||||
curl wget # for downloading libs
|
||||
libuuid-devel java-11-openjdk # required by antlr
|
||||
@@ -49,6 +54,7 @@ MEMGRAPH_BUILD_DEPS=(
|
||||
doxygen graphviz # source documentation generators
|
||||
which mono-complete dotnet-sdk-3.1 golang nodejs zip unzip java-11-openjdk-devel # for driver tests
|
||||
autoconf # for jemalloc code generation
|
||||
libtool # for protobuf code generation
|
||||
)
|
||||
|
||||
list() {
|
||||
@@ -94,8 +100,12 @@ install() {
|
||||
echo "NOTE: export LANG=en_US.utf8"
|
||||
fi
|
||||
yum install -y epel-release
|
||||
yum remove -y ius-release
|
||||
yum install -y \
|
||||
https://repo.ius.io/ius-release-el7.rpm
|
||||
yum update -y
|
||||
yum install -y wget git python3 python3-pip
|
||||
yum install -y wget python3 python3-pip
|
||||
yum install -y git224
|
||||
for pkg in $1; do
|
||||
if [ "$pkg" == libipt ]; then
|
||||
if ! yum list installed libipt >/dev/null 2>/dev/null; then
|
||||
|
||||
@@ -13,9 +13,14 @@ TOOLCHAIN_BUILD_DEPS=(
|
||||
zlib-devel # zlib library used for all builds
|
||||
expat-devel libipt-devel libbabeltrace-devel xz-devel python36-devel texinfo # for gdb
|
||||
libcurl-devel # for cmake
|
||||
curl # snappy
|
||||
readline-devel # for cmake and llvm
|
||||
libffi-devel libxml2-devel # for llvm
|
||||
libedit-devel pcre-devel automake bison # for swig
|
||||
file
|
||||
openssl-devel
|
||||
gmp-devel
|
||||
gperf
|
||||
)
|
||||
|
||||
TOOLCHAIN_RUN_DEPS=(
|
||||
@@ -25,6 +30,7 @@ TOOLCHAIN_RUN_DEPS=(
|
||||
expat libipt libbabeltrace xz-libs python36 # for gdb
|
||||
readline # for cmake and llvm
|
||||
libffi libxml2 # for llvm
|
||||
openssl-devel
|
||||
)
|
||||
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
@@ -48,6 +54,7 @@ MEMGRAPH_BUILD_DEPS=(
|
||||
which mono-complete dotnet-sdk-3.1 nodejs golang zip unzip java-11-openjdk-devel # for driver tests
|
||||
sbcl # for custom Lisp C++ preprocessing
|
||||
autoconf # for jemalloc code generation
|
||||
libtool # for protobuf code generation
|
||||
)
|
||||
|
||||
list() {
|
||||
|
||||
@@ -15,6 +15,12 @@ TOOLCHAIN_BUILD_DEPS=(
|
||||
libcurl4-openssl-dev # for cmake
|
||||
libreadline-dev # for cmake and llvm
|
||||
libffi-dev libxml2-dev # for llvm
|
||||
curl # snappy
|
||||
file # for libunwind
|
||||
libssl-dev # for libevent
|
||||
libgmp-dev # for gdb
|
||||
gperf # for proxygen
|
||||
git # for fbthrift
|
||||
libedit-dev libpcre3-dev automake bison # for swig
|
||||
)
|
||||
|
||||
@@ -26,6 +32,7 @@ TOOLCHAIN_RUN_DEPS=(
|
||||
libcurl4 # for cmake
|
||||
libreadline7 # for cmake and llvm
|
||||
libffi6 libxml2 # for llvm
|
||||
libssl-dev # for libevent
|
||||
)
|
||||
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
@@ -46,6 +53,7 @@ MEMGRAPH_BUILD_DEPS=(
|
||||
mono-runtime mono-mcs zip unzip default-jdk-headless # for driver tests
|
||||
dotnet-sdk-3.1 golang nodejs npm
|
||||
autoconf # for jemalloc code generation
|
||||
libtool # for protobuf code generation
|
||||
)
|
||||
|
||||
list() {
|
||||
|
||||
104
environment/os/debian-11.sh
Executable file
104
environment/os/debian-11.sh
Executable file
@@ -0,0 +1,104 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc g++ build-essential make # generic build tools
|
||||
wget # used for archive download
|
||||
gnupg # used for archive signature verification
|
||||
tar gzip bzip2 xz-utils unzip # used for archive unpacking
|
||||
zlib1g-dev # zlib library used for all builds
|
||||
libexpat1-dev libipt-dev libbabeltrace-dev liblzma-dev python3-dev texinfo # for gdb
|
||||
libcurl4-openssl-dev # for cmake
|
||||
libreadline-dev # for cmake and llvm
|
||||
libffi-dev libxml2-dev # for llvm
|
||||
libedit-dev libpcre3-dev automake bison # for swig
|
||||
curl # snappy
|
||||
file # for libunwind
|
||||
libssl-dev # for libevent
|
||||
libgmp-dev
|
||||
gperf # for proxygen
|
||||
git # for fbthrift
|
||||
)
|
||||
|
||||
TOOLCHAIN_RUN_DEPS=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz-utils # used for archive unpacking
|
||||
zlib1g # zlib library used for all builds
|
||||
libexpat1 libipt2 libbabeltrace1 liblzma5 python3 # for gdb
|
||||
libcurl4 # for cmake
|
||||
file # for CPack
|
||||
libreadline8 # for cmake and llvm
|
||||
libffi7 libxml2 # for llvm
|
||||
libssl-dev # for libevent
|
||||
)
|
||||
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
git # source code control
|
||||
make pkg-config # build system
|
||||
curl wget # for downloading libs
|
||||
uuid-dev default-jre-headless # required by antlr
|
||||
libreadline-dev # for memgraph console
|
||||
libpython3-dev python3-dev # for query modules
|
||||
libssl-dev
|
||||
libseccomp-dev
|
||||
netcat # tests are using nc to wait for memgraph
|
||||
python3 virtualenv python3-virtualenv python3-pip # for qa, macro_benchmark and stress tests
|
||||
python3-yaml # for the configuration generator
|
||||
libcurl4-openssl-dev # mg-requests
|
||||
sbcl # for custom Lisp C++ preprocessing
|
||||
doxygen graphviz # source documentation generators
|
||||
mono-runtime mono-mcs zip unzip default-jdk-headless # for driver tests
|
||||
dotnet-sdk-3.1 golang nodejs npm
|
||||
autoconf # for jemalloc code generation
|
||||
libtool # for protobuf code generation
|
||||
)
|
||||
|
||||
list() {
|
||||
echo "$1"
|
||||
}
|
||||
|
||||
check() {
|
||||
check_all_dpkg "$1"
|
||||
}
|
||||
|
||||
install() {
|
||||
cat >/etc/apt/sources.list <<EOF
|
||||
deb http://deb.debian.org/debian bullseye main
|
||||
deb-src http://deb.debian.org/debian bullseye main
|
||||
|
||||
deb http://deb.debian.org/debian-security/ bullseye-security main
|
||||
deb-src http://deb.debian.org/debian-security/ bullseye-security main
|
||||
|
||||
deb http://deb.debian.org/debian bullseye-updates main
|
||||
deb-src http://deb.debian.org/debian bullseye-updates main
|
||||
EOF
|
||||
cd "$DIR"
|
||||
apt update
|
||||
# If GitHub Actions runner is installed, append LANG to the environment.
|
||||
# Python related tests doesn't work the LANG export.
|
||||
if [ -d "/home/gh/actions-runner" ]; then
|
||||
echo "LANG=en_US.utf8" >> /home/gh/actions-runner/.env
|
||||
else
|
||||
echo "NOTE: export LANG=en_US.utf8"
|
||||
fi
|
||||
apt install -y wget
|
||||
for pkg in $1; do
|
||||
if [ "$pkg" == dotnet-sdk-3.1 ]; then
|
||||
if ! dpkg -s "$pkg" 2>/dev/null >/dev/null; then
|
||||
wget -nv https://packages.microsoft.com/config/debian/10/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
|
||||
dpkg -i packages-microsoft-prod.deb
|
||||
apt-get update
|
||||
apt-get install -y apt-transport-https dotnet-sdk-3.1
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
apt install -y "$pkg"
|
||||
done
|
||||
}
|
||||
|
||||
deps=$2"[*]"
|
||||
"$1" "${!deps}"
|
||||
@@ -1,62 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc g++ build-essential make # generic build tools
|
||||
wget # used for archive download
|
||||
gnupg # used for archive signature verification
|
||||
tar gzip bzip2 xz-utils unzip # used for archive unpacking
|
||||
zlib1g-dev # zlib library used for all builds
|
||||
libexpat1-dev libipt-dev libbabeltrace-dev libbabeltrace-ctf-dev liblzma-dev python3-dev texinfo # for gdb
|
||||
libcurl4-openssl-dev # for cmake
|
||||
libreadline-dev # for cmake and llvm
|
||||
libffi-dev libxml2-dev # for llvm
|
||||
libedit-dev libpcre3-dev automake bison # for swig
|
||||
)
|
||||
|
||||
TOOLCHAIN_RUN_DEPS=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz-utils # used for archive unpacking
|
||||
zlib1g # zlib library used for all builds
|
||||
libexpat1 libipt1 libbabeltrace1 libbabeltrace-ctf1 liblzma5 python3 # for gdb
|
||||
libcurl3 # for cmake
|
||||
libreadline7 # for cmake and llvm
|
||||
libffi6 libxml2 # for llvm
|
||||
)
|
||||
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
git # source code control
|
||||
make pkg-config # build system
|
||||
curl wget # for downloading libs
|
||||
uuid-dev default-jre-headless # required by antlr
|
||||
libreadline-dev # for memgraph console
|
||||
libpython3-dev python3-dev # for query modules
|
||||
libssl-dev
|
||||
libseccomp-dev
|
||||
python3 python-virtualenv python3-pip # for qa, macro_benchmark and stress tests
|
||||
python3-yaml # for the configuration generator
|
||||
libcurl4-openssl-dev # mg-requests
|
||||
sbcl # for custom Lisp C++ preprocessing
|
||||
doxygen graphviz # source documentation generators
|
||||
mono-runtime mono-mcs nodejs zip unzip default-jdk-headless # for driver tests
|
||||
autoconf # for jemalloc code generation
|
||||
)
|
||||
|
||||
list() {
|
||||
echo "$1"
|
||||
}
|
||||
|
||||
check() {
|
||||
check_all_dpkg "$1"
|
||||
}
|
||||
|
||||
install() {
|
||||
install_all_apt "$1"
|
||||
}
|
||||
|
||||
deps=$2"[*]"
|
||||
"$1" "${!deps}"
|
||||
@@ -16,6 +16,12 @@ TOOLCHAIN_BUILD_DEPS=(
|
||||
libcurl4-openssl-dev # cmake
|
||||
libreadline-dev # cmake and llvm
|
||||
libffi-dev libxml2-dev # llvm
|
||||
curl # snappy
|
||||
file
|
||||
git # for thrift
|
||||
libgmp-dev # for gdb
|
||||
gperf # for proxygen
|
||||
libssl-dev
|
||||
libedit-dev libpcre3-dev automake bison # swig
|
||||
)
|
||||
|
||||
@@ -27,6 +33,7 @@ TOOLCHAIN_RUN_DEPS=(
|
||||
libcurl4 # for cmake
|
||||
libreadline7 # for cmake and llvm
|
||||
libffi6 libxml2 # for llvm
|
||||
libssl-dev # for libevent
|
||||
)
|
||||
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
@@ -45,6 +52,7 @@ MEMGRAPH_BUILD_DEPS=(
|
||||
doxygen graphviz # source documentation generators
|
||||
mono-runtime mono-mcs nodejs zip unzip default-jdk-headless # driver tests
|
||||
autoconf # for jemalloc code generation
|
||||
libtool # for protobuf code generation
|
||||
)
|
||||
|
||||
list() {
|
||||
|
||||
@@ -15,6 +15,12 @@ TOOLCHAIN_BUILD_DEPS=(
|
||||
libcurl4-openssl-dev # for cmake
|
||||
libreadline-dev # for cmake and llvm
|
||||
libffi-dev libxml2-dev # for llvm
|
||||
curl # snappy
|
||||
file
|
||||
git # for thrift
|
||||
libgmp-dev # for gdb
|
||||
gperf # for proxygen
|
||||
libssl-dev
|
||||
libedit-dev libpcre3-dev automake bison # for swig
|
||||
)
|
||||
|
||||
@@ -26,6 +32,7 @@ TOOLCHAIN_RUN_DEPS=(
|
||||
libcurl4 # for cmake
|
||||
libreadline8 # for cmake and llvm
|
||||
libffi7 libxml2 # for llvm
|
||||
libssl-dev # for libevent
|
||||
)
|
||||
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
@@ -46,6 +53,7 @@ MEMGRAPH_BUILD_DEPS=(
|
||||
mono-runtime mono-mcs zip unzip default-jdk-headless # for driver tests
|
||||
dotnet-sdk-3.1 golang nodejs npm
|
||||
autoconf # for jemalloc code generation
|
||||
libtool # for protobuf code generation
|
||||
)
|
||||
|
||||
list() {
|
||||
|
||||
2
environment/toolchain/proxygen.diff
Normal file
2
environment/toolchain/proxygen.diff
Normal file
@@ -0,0 +1,2 @@
|
||||
24d23
|
||||
< find_dependency(mvfst)
|
||||
16
environment/toolchain/snappy.diff
Normal file
16
environment/toolchain/snappy.diff
Normal file
@@ -0,0 +1,16 @@
|
||||
55,57c55,57
|
||||
< # Disable RTTI.
|
||||
< string(REGEX REPLACE "/GR" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
|
||||
< set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GR-")
|
||||
---
|
||||
> # # Disable RTTI.
|
||||
> # string(REGEX REPLACE "/GR" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
|
||||
> # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GR-")
|
||||
80,82c80,82
|
||||
< # Disable RTTI.
|
||||
< string(REGEX REPLACE "-frtti" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
|
||||
< set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")
|
||||
---
|
||||
> # # Disable RTTI.
|
||||
> # string(REGEX REPLACE "-frtti" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
|
||||
> # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")
|
||||
1089
environment/toolchain/v4.sh
Executable file
1089
environment/toolchain/v4.sh
Executable file
File diff suppressed because it is too large
Load Diff
75
environment/toolchain/xz_pgp.txt
Normal file
75
environment/toolchain/xz_pgp.txt
Normal file
@@ -0,0 +1,75 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBEzEOZIBEACxg/IuXERlDB48JBWmF4NxNUuuup1IhJAJyFGFSKh3OGAO2Ard
|
||||
sNuRLjANsFXA7m7P5eTFcG+BoHHuAVYmKnI3PPZtHVLnUt4pGItPczQZ2BE1WpcI
|
||||
ayjGTBJeKItX3Npqg9D/odO9WWS1i3FQPVdrLn0YH37/BA66jeMQCRo7g7GLpaNf
|
||||
IrvYGsqTbxCwsmA37rpE7oyU4Yrf74HT091WBsRIoq/MelhbxTDMR8eu/dUGZQVc
|
||||
Kj3lN55RepwWwUUKyqarY0zMt4HkFJ7v7yRL+Cvzy92Ouv4Wf2FlhNtEs5LE4Tax
|
||||
W0PO5AEmUoKjX87SezQK0f652018b4u6Ex52cY7p+n5TII/UyoowH6+tY8UHo9yb
|
||||
fStrqgNE/mY2bhA6+AwCaOUGsFzVVPTbjtxL3HacUP/jlA1h78V8VTvTs5d55iG7
|
||||
jSqR9o05wje8rwNiXXK0xtiJahyNzL97Kn/DgPSqPIi45G+8nxWSPFM5eunBKRl9
|
||||
vAnsvwrdPRsR6YR3uMHTuVhQX9/CY891MHkaZJ6wydWtKt3yQwJLYqwo5d4DwnUX
|
||||
CduUwSKv+6RmtWI5ZmTQYOcBRcZyGKml9X9Q8iSbm6cnpFXmLrNQwCJN+D3SiYGc
|
||||
MtbltZo0ysPMa6Xj5xFaYqWk/BI4iLb2Gs+ByGo/+a0Eq4XYBMOpitNniQARAQAB
|
||||
tCdMYXNzZSBDb2xsaW4gPGxhc3NlLmNvbGxpbkB0dWthYW5pLm9yZz6JAlEEEwEK
|
||||
ADsCGwMCHgECF4AECwkIBwMVCggFFgIDAQAWIQQ2kMJAzlG0Zw0wrRw47nV9aRhG
|
||||
IAUCYEt9dQUJFxeR4wAKCRA47nV9aRhGIBNDEACxD6vJ+enZwe3IgkJh5JtLsC9b
|
||||
MWCQRlPW1EVMsg96Cb5Rtron1eN1pp1TlzENJu1/C7C/VEsr9WwOPg26Men7fNf/
|
||||
O21QM9IBWd/uB0Pu333WqKh92ESS5x9ST9DrG39nVGSPkQQBMuia72VrA+crPnwT
|
||||
/h/u1IN6/sff5VDIU24rUiqW2Npy733dANruj7Ny0scRXVPltnVdhqwPHt6qNjC1
|
||||
t+/cCnwHgW1BR1RYXBPpB42z/m29dL9rPrG0YPGWs2Bc+EATUICfEE6eIvwfciue
|
||||
IJTjKT9Y9DrogJC2AYFhjC7N04OKdCB2hFs4BjexJwr4X0GJO7LhFl03c951AsIE
|
||||
GHwrucRPB5bo2vmvQ8IvZn7CmtdUJzXv9JlyU6p+MIK1pz7TK6GgSOSffQIXZn6e
|
||||
nUPtm9mEwuncOfmW8/ODYPs1gCWYgyiFJx8h7eEu+M4MxHSFBs7MwXf/Ae2fSp+M
|
||||
P/p198qB8fC5oVBnF95qb0Qi0uc1D+Gb+gpBF+ymMb+s/VBOR3QWiym7AzBrJ62g
|
||||
UnbC9jMLGnSRI+7p7raUfMTgXr5/oQoBw7ExJVltSSRrim2YH/t4CV47mO6dR9J3
|
||||
1RtsTFIRNhz+07XPsETcuCV/dgqeC8fOFLt9MY17Sufhb1DcGy4urZBOIhXcpTV7
|
||||
vHVj5IYH5nYOT49NRYkCOAQTAQIAIgUCTMQ5kgIbAwYLCQgHAwIGFQgCCQoLBBYC
|
||||
AwECHgECF4AACgkQOO51fWkYRiAg4A/7BXKwoRaXrMbMPOW7vuVF7c2IKB2Yqzn1
|
||||
vLBCwuEHkqY237lDcXY4/5LR+1gcZ3Duw1n/BRSm0FBdvyX/JTWiWNSDUkKAO/0l
|
||||
T2Tg44YLrDT3bzwu8dbU9xQt6kH+SCOHvv5Oe4k79l5mro6fF3H1M0bN63x/YoFY
|
||||
ojy09D7/JptY82oR4f/VdKnfZLJcCViCb0wp8SD2NkDAudKg+K+7PD8HlTWklQQg
|
||||
TZdRXxVZKIJeU42aJDqnRbAhJd64YHyClhqut9F5LUmiP5qfLfNhkKDhNOwk2Blr
|
||||
BGBJkSd7wPyzcX4Mun/L6YspHjbeVMt9TD7HQlo+OOd2OjAHCx6pqwkXnzeLPEaE
|
||||
cPdQ1SHgrBViAxX3DNPubLP0Knw8XwFu96EuhHZgexE1W7bB4LFsJyXAc5k1PqPD
|
||||
CLsAauxmvI2OfI7opG/8wyxDvNgoPjG8fZNAgY0REqPC0JnTXChH31IxUmhNotH8
|
||||
tD3DDTZOHw05n5MwwUrEE9xiETVDfFQcMLfxZ9KLz+BC2g1t5LYublRgnCMNJzFg
|
||||
sNUMM02CphABzl/LCLnumr0eyQQ/weV4twEhLwSDmqLYHL0EdYW0Y3CnnU9vmYxQ
|
||||
cXKbstS71sEJJYBBmSBbf9GxkOY8BRNtwVwY0kPgxv1WqdVBiAFvfB+pyAsrax9B
|
||||
3UeB7ZSwRD6JAhwEEAEKAAYFAlS25GwACgkQlbYYGy0z6ew92Q//ZA9/6piQtoW4
|
||||
PwP/1DtWGyKU8hwR+9FG669iPk/dAG+yoEJtFMOUpg/FUFmCX8Bc4oEHsCVyLxKt
|
||||
DcCVUIRcYNSFi5hTZaBEbwsOlDT37gtlfIIu34hhHRccKaLnN/N9gNMNw8wGh9xg
|
||||
Q/KtxZwcbk/bZIlDkKTJkFBRAekdEGAFDWb/AZOy+LQxS8ZAh1eWkfV0i8opmK9k
|
||||
gPXtLE0WSsqtYyGs58z+BFE9NH3tEUwK6jSvtuLwQl4UrICNbKthcpb8WwH6UXzb
|
||||
q3QNSYVOpf/cqRdBJA6bvb/ku/xyKVL08lGmxD9v1b137R7mafDAFPTsvH2Mt/0V
|
||||
YuhtWav3r1Bl9QksDxt2DTS8wiWDUBetGqOVdcw7vBrXPEWDNBmxeJXsiJ7zJlR+
|
||||
9wrJOm6RV2+l1IPxu96EaPS+kTNBijKrhxb67bww8BTEWTd0wcdJmgWRkM8SIstp
|
||||
IKqd0L2TFYph2/NtrBhRg+DIEPJPpSTGsUMcCEXCZPQ+cIdlQKsWpk0tZ62DlvEl
|
||||
r7E+wgUSQolRfx5KrpZifiS2zQlhzdXv28CJhsVbLyw5fUAWUKIH/dCo5NKsNLk2
|
||||
Lc5DH9VWnFgxAAtW290FqeK/4ulMq7Vs1dQSwyHM2Ni3QqqeaiOrh8gbSY5CMLFN
|
||||
Y3HYRwuTYPa3AobsozCzBj0Zdf/6AFe5Ag0ETMQ5kgEQAL/FwKdjxgPxtSpgq1SM
|
||||
zgZtTTyLqhgGD3NZfadHWHYRIL38NDV3JeTA79Y2zj2dj7KQPDT+0aqeizTV2E3j
|
||||
P3iCQ53VOT4consBaQAgKexpptnS+T1DobtICFJ0GGzf0HRj6KO2zSOuOitWPWlU
|
||||
wbvX7M0LLI2+hqlx0jTPqbJFZ/Za6KTtbS6xdCPVUpUqYZQpokEZcwQmUp8Q+lGo
|
||||
JD2sNYCZyap63X/aAOgCGr2RXYddOH5e8vGzGW+mwtCv+WQ9Ay35mGqI5MqkbZd1
|
||||
Qbuv2b1647E/QEEucfRHVbJVKGGPpFMUJtcItyyIt5jo+r9CCL4Cs47dF/9/RNwu
|
||||
NvpvHXUyqMBQdWNZRMx4k/NGD/WviPi9m6mIMui6rOQsSOaqYdcUX4Nq2Orr3Oaz
|
||||
2JPQdUfeI23iot1vK8hxvUCQTV3HfJghizN6spVl0yQOKBiE8miJRgrjHilH3hTb
|
||||
xoo42xDkNAq+CQo3QAm1ibDxKCDq0RcWPjcCRAN/Q5MmpcodpdKkzV0yGIS4g7s5
|
||||
frVrgV/kox2r4/Yxsr8K909+4H82AjTKGX/BmsQFCTAqBk6p7I0zxjIqJ/w33TZB
|
||||
Q0Pn4r3WIlUPafzY6a9/LAvN1fHRxf9SpCByJsszD03Qu5f5TB8gthsdnVmTo7jj
|
||||
iordEKMtw2aEMLzdWWTQ/TNVABEBAAGJAjwEGAEKACYCGwwWIQQ2kMJAzlG0Zw0w
|
||||
rRw47nV9aRhGIAUCYEt9YAUJFxeRzgAKCRA47nV9aRhGIMLtD/9HuKM4pngImcuz
|
||||
YwzQmdv4j26YYyh4jVsKEmVWTiRcehEgUIlrWkCu3qzd5NK+RetS7kJ8MPnzEUfj
|
||||
YbpdC6yrF6n1mSrZZ4VJMkV2ev37bIgXM+Wp1mCAGbjNxQnjn9RabT/gjIqmGuRn
|
||||
AP7RsSeOSuO/gO9h2Pteciz23ussTilB+8cTooQEQQZe6Kv/zukvL+ccSehLHsZ7
|
||||
qVfRUAmtt8nFkXXE+s8jfLfhqstaI2/RJu5witaPcXM8Mnz2E95aASAbZy0eQot9
|
||||
0Pvf07n9yuC3tueTvzvlXx3h5U3yT44tIOmzANIQjay1TGdm+RBJ2ZYyhyLawlZ2
|
||||
NVUXXSp4QZZXPA0UWbF+pb7Q9cdKDNFVuvGBljuea0Yd0T2o+ibDq43HziX9ll+l
|
||||
SXk9mqvW1UcDOaxWrSsm1Gc1O9g3wqH5xHAhtY8GPh/7VgAawskPkmnlkMW6pYPy
|
||||
zibbeISJL1gd1jIT63y6aoVrtNoo+wYJm280ROflh4+5QOo6QJ+jm70fkXSG/qJ5
|
||||
a8/qCPTHkJc/rpkL6/TDQAJURi9RhDAC0gb40HtusbN1LZEA+i0cWTmYXap+DB4Y
|
||||
R4pApilpaG87M+VUokR4xpnx7vTb2MPa7Mdenvi9FEGnKXadmT8038vlfzz5GGUT
|
||||
MlVin9BQPTpdA+PpRiJvKJgVDeAFOg==
|
||||
=asTC
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
@@ -1405,38 +1405,76 @@ int mgp_must_abort(struct mgp_graph *graph);
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Kafka message API
|
||||
/// Currently the API below is for kafka only but in the future
|
||||
/// mgp_message and mgp_messages might be generic to support
|
||||
/// other streaming systems.
|
||||
/// @name Stream Source message API
|
||||
/// API for accessing specific data contained in a mgp_message
|
||||
/// used for defining transformation procedures.
|
||||
/// Not all methods are available for all stream sources
|
||||
/// so make sure that your transformation procedure can be used
|
||||
/// for a specific source, i.e. only valid methods are used.
|
||||
///@{
|
||||
|
||||
/// A single Kafka message
|
||||
/// A single Stream source message
|
||||
struct mgp_message;
|
||||
|
||||
/// A list of Kafka messages
|
||||
/// A list of Stream source messages
|
||||
struct mgp_messages;
|
||||
|
||||
/// Stream source type.
|
||||
enum mgp_source_type {
|
||||
KAFKA,
|
||||
PULSAR,
|
||||
};
|
||||
|
||||
/// Get the type of the stream source that produced the message.
|
||||
enum mgp_error mgp_message_source_type(struct mgp_message *message, enum mgp_source_type *result);
|
||||
|
||||
/// Payload is not null terminated and not a string but rather a byte array.
|
||||
/// You need to call mgp_message_payload_size() first, to read the size of
|
||||
/// the payload.
|
||||
/// Supported stream sources:
|
||||
/// - Kafka
|
||||
/// - Pulsar
|
||||
/// Return MGP_ERROR_INVALID_ARGUMENT if the message is from an unsupported stream source.
|
||||
enum mgp_error mgp_message_payload(struct mgp_message *message, const char **result);
|
||||
|
||||
/// Get the payload size
|
||||
/// Supported stream sources:
|
||||
/// - Kafka
|
||||
/// - Pulsar
|
||||
/// Return MGP_ERROR_INVALID_ARGUMENT if the message is from an unsupported stream source.
|
||||
enum mgp_error mgp_message_payload_size(struct mgp_message *message, size_t *result);
|
||||
|
||||
/// Get the name of topic
|
||||
/// Supported stream sources:
|
||||
/// - Kafka
|
||||
/// - Pulsar
|
||||
/// Return MGP_ERROR_INVALID_ARGUMENT if the message is from an unsupported stream source.
|
||||
enum mgp_error mgp_message_topic_name(struct mgp_message *message, const char **result);
|
||||
|
||||
/// Get the key of mgp_message as a byte array
|
||||
/// Supported stream sources:
|
||||
/// - Kafka
|
||||
/// Return MGP_ERROR_INVALID_ARGUMENT if the message is from an unsupported stream source.
|
||||
enum mgp_error mgp_message_key(struct mgp_message *message, const char **result);
|
||||
|
||||
/// Get the key size of mgp_message
|
||||
/// Supported stream sources:
|
||||
/// - Kafka
|
||||
/// Return MGP_ERROR_INVALID_ARGUMENT if the message is from an unsupported stream source.
|
||||
enum mgp_error mgp_message_key_size(struct mgp_message *message, size_t *result);
|
||||
|
||||
/// Get the timestamp of mgp_message as a byte array
|
||||
/// Supported stream sources:
|
||||
/// - Kafka
|
||||
/// Return MGP_ERROR_INVALID_ARGUMENT if the message is from an unsupported stream source.
|
||||
enum mgp_error mgp_message_timestamp(struct mgp_message *message, int64_t *result);
|
||||
|
||||
/// Get the message offset from a message.
|
||||
/// Supported stream sources:
|
||||
/// - Kafka
|
||||
/// Return MGP_ERROR_INVALID_ARGUMENT if the message is from an unsupported stream source.
|
||||
enum mgp_error mgp_message_offset(struct mgp_message *message, int64_t *result);
|
||||
|
||||
/// Get the number of messages contained in the mgp_messages list
|
||||
/// Current implementation always returns without errors.
|
||||
enum mgp_error mgp_messages_size(struct mgp_messages *message, size_t *result);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# 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// License, and you may not use this file except in compliance with 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
|
||||
@@ -1259,6 +1260,9 @@ class InvalidMessageError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
SOURCE_TYPE_KAFKA = _mgp.SOURCE_TYPE_KAFKA
|
||||
SOURCE_TYPE_PULSAR = _mgp.SOURCE_TYPE_PULSAR
|
||||
|
||||
class Message:
|
||||
"""Represents a message from a stream."""
|
||||
__slots__ = ('_message',)
|
||||
@@ -1279,26 +1283,73 @@ class Message:
|
||||
"""Return True if `self` is in valid context and may be used."""
|
||||
return self._message.is_valid()
|
||||
|
||||
def source_type(self) -> str:
|
||||
"""
|
||||
Supported in all stream sources
|
||||
|
||||
Raise InvalidArgumentError if the message is from an unsupported stream source.
|
||||
"""
|
||||
if not self.is_valid():
|
||||
raise InvalidMessageError()
|
||||
return self._message.source_type()
|
||||
|
||||
def payload(self) -> bytes:
|
||||
"""
|
||||
Supported stream sources:
|
||||
- Kafka
|
||||
- Pulsar
|
||||
|
||||
Raise InvalidArgumentError if the message is from an unsupported stream source.
|
||||
"""
|
||||
if not self.is_valid():
|
||||
raise InvalidMessageError()
|
||||
return self._message.payload()
|
||||
|
||||
def topic_name(self) -> str:
|
||||
"""
|
||||
Supported stream sources:
|
||||
- Kafka
|
||||
- Pulsar
|
||||
|
||||
Raise InvalidArgumentError if the message is from an unsupported stream source.
|
||||
"""
|
||||
if not self.is_valid():
|
||||
raise InvalidMessageError()
|
||||
return self._message.topic_name()
|
||||
|
||||
def key(self) -> bytes:
|
||||
"""
|
||||
Supported stream sources:
|
||||
- Kafka
|
||||
|
||||
Raise InvalidArgumentError if the message is from an unsupported stream source.
|
||||
"""
|
||||
if not self.is_valid():
|
||||
raise InvalidMessageError()
|
||||
return self._message.key()
|
||||
|
||||
def timestamp(self) -> int:
|
||||
"""
|
||||
Supported stream sources:
|
||||
- Kafka
|
||||
|
||||
Raise InvalidArgumentError if the message is from an unsupported stream source.
|
||||
"""
|
||||
if not self.is_valid():
|
||||
raise InvalidMessageError()
|
||||
return self._message.timestamp()
|
||||
|
||||
def offset(self) -> int:
|
||||
"""
|
||||
Supported stream sources:
|
||||
- Kafka
|
||||
|
||||
Raise InvalidArgumentError if the message is from an unsupported stream source.
|
||||
"""
|
||||
if not self.is_valid():
|
||||
raise InvalidMessageError()
|
||||
return self._message.offset()
|
||||
|
||||
|
||||
class InvalidMessagesError(Exception):
|
||||
"""Signals using a messages instance outside of the registered transformation."""
|
||||
|
||||
1
libs/.gitignore
vendored
1
libs/.gitignore
vendored
@@ -5,3 +5,4 @@
|
||||
!CMakeLists.txt
|
||||
!__main.cpp
|
||||
!jemalloc.cmake
|
||||
!pulsar.patch
|
||||
|
||||
@@ -8,6 +8,15 @@ if (NPROC EQUAL 0)
|
||||
set(NPROC 1)
|
||||
endif()
|
||||
|
||||
find_package(Boost 1.78 REQUIRED)
|
||||
find_package(BZip2 1.0.6 REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
set(GFLAGS_NOTHREADS OFF)
|
||||
find_package(gflags REQUIRED)
|
||||
find_package(fmt 8.0.1)
|
||||
find_package(Jemalloc REQUIRED)
|
||||
find_package(ZLIB 1.2.11 REQUIRED)
|
||||
|
||||
set(LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
# convenience functions
|
||||
@@ -48,7 +57,7 @@ endfunction(import_library)
|
||||
# INSTALL_COMMAND arguments.
|
||||
function(add_external_project name)
|
||||
set(options NO_C_COMPILER)
|
||||
set(one_value_kwargs SOURCE_DIR)
|
||||
set(one_value_kwargs SOURCE_DIR BUILD_IN_SOURCE)
|
||||
set(multi_value_kwargs CMAKE_ARGS DEPENDS INSTALL_COMMAND BUILD_COMMAND
|
||||
CONFIGURE_COMMAND)
|
||||
cmake_parse_arguments(KW "${options}" "${one_value_kwargs}" "${multi_value_kwargs}" ${ARGN})
|
||||
@@ -56,11 +65,16 @@ function(add_external_project name)
|
||||
if (KW_SOURCE_DIR)
|
||||
set(source_dir ${KW_SOURCE_DIR})
|
||||
endif()
|
||||
set(build_in_source 0)
|
||||
if (KW_BUILD_IN_SOURCE)
|
||||
set(build_in_source ${KW_BUILD_IN_SOURCE})
|
||||
endif()
|
||||
if (NOT KW_NO_C_COMPILER)
|
||||
set(KW_CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} ${KW_CMAKE_ARGS})
|
||||
endif()
|
||||
ExternalProject_Add(${name}-proj DEPENDS ${KW_DEPENDS}
|
||||
PREFIX ${source_dir} SOURCE_DIR ${source_dir}
|
||||
BUILD_IN_SOURCE ${build_in_source}
|
||||
CONFIGURE_COMMAND ${KW_CONFIGURE_COMMAND}
|
||||
CMAKE_ARGS -DCMAKE_BUILD_TYPE=Release
|
||||
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
|
||||
@@ -97,17 +111,12 @@ import_external_library(antlr4 STATIC
|
||||
|
||||
# Setup google benchmark.
|
||||
import_external_library(benchmark STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/benchmark/lib/libbenchmark.a
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/benchmark/${CMAKE_INSTALL_LIBDIR}/libbenchmark.a
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/benchmark/include
|
||||
# Skip testing. The tests don't compile with Clang 8.
|
||||
CMAKE_ARGS -DBENCHMARK_ENABLE_TESTING=OFF)
|
||||
|
||||
# setup fmt format
|
||||
import_external_library(fmt STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/fmt/${CMAKE_INSTALL_LIBDIR}/libfmt.a
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/fmt/include
|
||||
# Skip testing.
|
||||
CMAKE_ARGS -DFMT_TEST=OFF)
|
||||
include(FetchContent)
|
||||
|
||||
# setup rapidcheck (it cannot be external, since it doesn't have install
|
||||
# target)
|
||||
@@ -134,55 +143,19 @@ import_library(gtest_main STATIC ${GTEST_MAIN_LIBRARY} ${GTEST_INCLUDE_DIR} gtes
|
||||
import_library(gmock STATIC ${GMOCK_LIBRARY} ${GTEST_INCLUDE_DIR} gtest-proj)
|
||||
import_library(gmock_main STATIC ${GMOCK_MAIN_LIBRARY} ${GTEST_INCLUDE_DIR} gtest-proj)
|
||||
|
||||
# setup google flags
|
||||
set(GFLAGS_NO_FILENAMES "0")
|
||||
if ("${CMAKE_BUILD_TYPE}" MATCHES "^(R|r)(E|e)(L|l).+")
|
||||
set(GFLAGS_NO_FILENAMES "1")
|
||||
endif()
|
||||
|
||||
# setup google flags
|
||||
import_external_library(gflags STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/gflags/lib/libgflags.a
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/gflags/include
|
||||
# Not needed, since gflags is C++ only.
|
||||
NO_C_COMPILER
|
||||
# Don't register installation in ~/.cmake
|
||||
CMAKE_ARGS -DREGISTER_INSTALL_PREFIX=OFF
|
||||
-DBUILD_gflags_nothreads_LIB=OFF
|
||||
-DGFLAGS_NO_FILENAMES=${GFLAGS_NO_FILENAMES})
|
||||
|
||||
# Setup cppitertools
|
||||
import_header_library(cppitertools ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
# Setup json
|
||||
import_header_library(json ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
# Setup bzip2
|
||||
import_external_library(bzip2 STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/bzip2/libbz2.a
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/bzip2
|
||||
# bzip2's Makefile has -g CFLAG which is redundant
|
||||
CONFIGURE_COMMAND sed -i "s/-Wall -Winline -O2 -g/-Wall -Winline -O2/g" ${CMAKE_CURRENT_SOURCE_DIR}/bzip2/Makefile
|
||||
BUILD_COMMAND make -C ${CMAKE_CURRENT_SOURCE_DIR}/bzip2
|
||||
CC=${CMAKE_C_COMPILER}
|
||||
CXX=${CMAKE_CXX_COMPILER}
|
||||
INSTALL_COMMAND true)
|
||||
|
||||
# Setup zlib
|
||||
import_external_library(zlib STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/zlib/lib/libz.a
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/zlib
|
||||
CMAKE_ARGS -DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=true
|
||||
BUILD_COMMAND $(MAKE) zlibstatic)
|
||||
|
||||
# Setup RocksDB
|
||||
import_external_library(rocksdb STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/rocksdb/lib/librocksdb.a
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/rocksdb/include
|
||||
DEPENDS gflags-proj
|
||||
CMAKE_ARGS -Dgflags_DIR=${CMAKE_CURRENT_SOURCE_DIR}/gflags/lib/cmake/gflags
|
||||
-DUSE_RTTI=ON
|
||||
CMAKE_ARGS -DUSE_RTTI=ON
|
||||
-DWITH_TESTS=OFF
|
||||
-DGFLAGS_NOTHREADS=OFF
|
||||
-DCMAKE_INSTALL_LIBDIR=lib
|
||||
-DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=true
|
||||
BUILD_COMMAND $(MAKE) rocksdb)
|
||||
@@ -217,12 +190,11 @@ add_external_project(mgconsole
|
||||
add_custom_target(mgconsole DEPENDS mgconsole-proj)
|
||||
|
||||
# Setup spdlog
|
||||
import_external_library(spdlog STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/spdlog/${CMAKE_INSTALL_LIBDIR}/libspdlog.a
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/spdlog/include
|
||||
BUILD_COMMAND $(MAKE) spdlog)
|
||||
set(SPDLOG_FMT_EXTERNAL ON)
|
||||
FetchContent_Declare(spdlog
|
||||
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/spdlog)
|
||||
|
||||
include(jemalloc.cmake)
|
||||
FetchContent_MakeAvailable(spdlog)
|
||||
|
||||
# Setup librdkafka.
|
||||
import_external_library(librdkafka STATIC
|
||||
@@ -231,14 +203,45 @@ import_external_library(librdkafka STATIC
|
||||
CMAKE_ARGS -DRDKAFKA_BUILD_STATIC=ON
|
||||
-DRDKAFKA_BUILD_EXAMPLES=OFF
|
||||
-DRDKAFKA_BUILD_TESTS=OFF
|
||||
-DWITH_ZSTD=OFF
|
||||
-DENABLE_LZ4_EXT=OFF
|
||||
-DCMAKE_INSTALL_LIBDIR=lib
|
||||
-DWITH_SSL=ON
|
||||
# If we want SASL, we need to install it on build machines
|
||||
-DWITH_SASL=OFF)
|
||||
target_link_libraries(librdkafka INTERFACE ${OPENSSL_LIBRARIES} zlib)
|
||||
target_link_libraries(librdkafka INTERFACE ${OPENSSL_LIBRARIES} ZLIB::ZLIB)
|
||||
|
||||
import_library(librdkafka++ STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/librdkafka/lib/librdkafka++.a
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/librdkafka/include
|
||||
)
|
||||
target_link_libraries(librdkafka++ INTERFACE librdkafka)
|
||||
|
||||
set(PROTOBUF_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/protobuf/lib)
|
||||
import_external_library(protobuf STATIC
|
||||
${PROTOBUF_ROOT}/lib/libprotobuf.a
|
||||
${PROTOBUF_ROOT}/include
|
||||
BUILD_IN_SOURCE 1
|
||||
CONFIGURE_COMMAND true)
|
||||
|
||||
import_external_library(pulsar STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/pulsar/pulsar-client-cpp/lib/libpulsarwithdeps.a
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/pulsar/install/include
|
||||
BUILD_IN_SOURCE 1
|
||||
CONFIGURE_COMMAND cmake pulsar-client-cpp
|
||||
-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_SOURCE_DIR}/pulsar/install
|
||||
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
|
||||
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
|
||||
-DBUILD_DYNAMIC_LIB=OFF
|
||||
-DBUILD_STATIC_LIB=ON
|
||||
-DBUILD_TESTS=OFF
|
||||
-DLINK_STATIC=ON
|
||||
-DPROTOC_PATH=${PROTOBUF_ROOT}/bin/protoc
|
||||
-DBOOST_ROOT=${BOOST_ROOT}
|
||||
-DCMAKE_PREFIX_PATH=${PROTOBUF_ROOT}
|
||||
-DProtobuf_INCLUDE_DIRS=${PROTOBUF_ROOT}/include
|
||||
-DBUILD_PYTHON_WRAPPER=OFF
|
||||
-DBUILD_PERF_TOOLS=OFF
|
||||
-DUSE_LOG4CXX=OFF
|
||||
BUILD_COMMAND $(MAKE) pulsarStaticWithDeps)
|
||||
add_dependencies(pulsar-proj protobuf)
|
||||
|
||||
43
libs/antlr4.patch
Normal file
43
libs/antlr4.patch
Normal file
@@ -0,0 +1,43 @@
|
||||
diff --git a/runtime/Cpp/runtime/CMakeLists.txt b/runtime/Cpp/runtime/CMakeLists.txt
|
||||
index a8503bb..11362cf 100644
|
||||
--- a/runtime/Cpp/runtime/CMakeLists.txt
|
||||
+++ b/runtime/Cpp/runtime/CMakeLists.txt
|
||||
@@ -5,8 +5,8 @@ set(THIRDPARTY_DIR ${CMAKE_BINARY_DIR}/runtime/thirdparty)
|
||||
set(UTFCPP_DIR ${THIRDPARTY_DIR}/utfcpp)
|
||||
ExternalProject_Add(
|
||||
utfcpp
|
||||
- GIT_REPOSITORY "git://github.com/nemtrif/utfcpp"
|
||||
- GIT_TAG "v3.1.1"
|
||||
+ GIT_REPOSITORY "https://github.com/nemtrif/utfcpp"
|
||||
+ GIT_TAG "v3.2.1"
|
||||
SOURCE_DIR ${UTFCPP_DIR}
|
||||
UPDATE_DISCONNECTED 1
|
||||
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${UTFCPP_DIR}/install -Dgtest_force_shared_crt=ON
|
||||
@@ -118,7 +118,7 @@ set_target_properties(antlr4_static
|
||||
ARCHIVE_OUTPUT_DIRECTORY ${LIB_OUTPUT_DIR}
|
||||
COMPILE_FLAGS "${disabled_compile_warnings} ${extra_static_compile_flags}")
|
||||
|
||||
-install(TARGETS antlr4_shared
|
||||
+install(TARGETS antlr4_shared OPTIONAL
|
||||
DESTINATION lib
|
||||
EXPORT antlr4-targets)
|
||||
install(TARGETS antlr4_static
|
||||
diff --git a/runtime/Cpp/runtime/src/support/Any.h b/runtime/Cpp/runtime/src/support/Any.h
|
||||
index 468db98..65a473b 100644
|
||||
--- a/runtime/Cpp/runtime/src/support/Any.h
|
||||
+++ b/runtime/Cpp/runtime/src/support/Any.h
|
||||
@@ -122,12 +122,12 @@ private:
|
||||
}
|
||||
|
||||
private:
|
||||
- template<int N = 0, typename std::enable_if<N == N && std::is_nothrow_copy_constructible<T>::value, int>::type = 0>
|
||||
+ template<int N = 0, typename std::enable_if<N == N && std::is_copy_constructible<T>::value, int>::type = 0>
|
||||
Base* clone() const {
|
||||
return new Derived<T>(value);
|
||||
}
|
||||
|
||||
- template<int N = 0, typename std::enable_if<N == N && !std::is_nothrow_copy_constructible<T>::value, int>::type = 0>
|
||||
+ template<int N = 0, typename std::enable_if<N == N && !std::is_copy_constructible<T>::value, int>::type = 0>
|
||||
Base* clone() const {
|
||||
return nullptr;
|
||||
}
|
||||
1520
libs/pulsar.patch
Normal file
1520
libs/pulsar.patch
Normal file
File diff suppressed because it is too large
Load Diff
21
libs/rocksdb.patch
Normal file
21
libs/rocksdb.patch
Normal file
@@ -0,0 +1,21 @@
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 6761929..6a369af 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -220,6 +220,7 @@ else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -momit-leaf-frame-pointer")
|
||||
endif()
|
||||
endif()
|
||||
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-copy -Wno-unused-but-set-variable")
|
||||
endif()
|
||||
|
||||
include(CheckCCompilerFlag)
|
||||
@@ -997,7 +998,7 @@ if(NOT WIN32 OR ROCKSDB_INSTALL_ON_WINDOWS)
|
||||
|
||||
if(ROCKSDB_BUILD_SHARED)
|
||||
install(
|
||||
- TARGETS ${ROCKSDB_SHARED_LIB}
|
||||
+ TARGETS ${ROCKSDB_SHARED_LIB} OPTIONAL
|
||||
EXPORT RocksDBTargets
|
||||
COMPONENT runtime
|
||||
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
|
||||
156
libs/setup.sh
156
libs/setup.sh
@@ -14,39 +14,49 @@ clone () {
|
||||
local git_repo=$1
|
||||
local dir_name=$2
|
||||
local checkout_id=$3
|
||||
shift 3
|
||||
local shallow=$4
|
||||
shift 4
|
||||
# Clone if there's no repo.
|
||||
if [[ ! -d "$dir_name" ]]; then
|
||||
echo "Cloning from $git_repo"
|
||||
# If the clone fails, it doesn't make sense to continue with the function
|
||||
# execution but the whole script should continue executing because we might
|
||||
# clone the same repo from a different source.
|
||||
git clone "$git_repo" "$dir_name" || return 1
|
||||
|
||||
if [ "$shallow" = true ]; then
|
||||
git clone --depth 1 --branch "$checkout_id" "$git_repo" "$dir_name" || return 1
|
||||
else
|
||||
git clone "$git_repo" "$dir_name" || return 1
|
||||
fi
|
||||
fi
|
||||
pushd "$dir_name"
|
||||
# Just fetch new commits from remote repository. Don't merge/pull them in, so
|
||||
# that we don't clobber local modifications.
|
||||
git fetch
|
||||
# Check whether we have any local changes which need to be preserved.
|
||||
local local_changes=true
|
||||
if git diff --no-ext-diff --quiet && git diff --no-ext-diff --cached --quiet; then
|
||||
local_changes=false
|
||||
fi
|
||||
# Stash regardless of local_changes, so that a user gets a message on stdout.
|
||||
git stash
|
||||
# Checkout the primary commit (there's no need to pull/merge).
|
||||
# The checkout fail should exit this script immediately because the target
|
||||
# commit is not there and that will most likely create build-time errors.
|
||||
git checkout "$checkout_id" || exit 1
|
||||
# Apply any optional cherry pick fixes.
|
||||
while [[ $# -ne 0 ]]; do
|
||||
local cherry_pick_id=$1
|
||||
shift
|
||||
# The cherry-pick fail should exit this script immediately because the
|
||||
# target commit is not there and that will most likely create build-time
|
||||
# errors.
|
||||
git cherry-pick -n "$cherry_pick_id" || exit 1
|
||||
done
|
||||
|
||||
if [ "$shallow" = false ]; then
|
||||
# Stash regardless of local_changes, so that a user gets a message on stdout.
|
||||
git stash
|
||||
# Just fetch new commits from remote repository. Don't merge/pull them in, so
|
||||
# that we don't clobber local modifications.
|
||||
git fetch
|
||||
# Checkout the primary commit (there's no need to pull/merge).
|
||||
# The checkout fail should exit this script immediately because the target
|
||||
# commit is not there and that will most likely create build-time errors.
|
||||
git checkout "$checkout_id" || exit 1
|
||||
# Apply any optional cherry pick fixes.
|
||||
while [[ $# -ne 0 ]]; do
|
||||
local cherry_pick_id=$1
|
||||
shift
|
||||
# The cherry-pick fail should exit this script immediately because the
|
||||
# target commit is not there and that will most likely create build-time
|
||||
# errors.
|
||||
git cherry-pick -n "$cherry_pick_id" || exit 1
|
||||
done
|
||||
fi
|
||||
|
||||
# Reapply any local changes.
|
||||
if [[ $local_changes == true ]]; then
|
||||
git stash pop
|
||||
@@ -70,12 +80,13 @@ repo_clone_try_double () {
|
||||
secondary_url="$2"
|
||||
folder_name="$3"
|
||||
ref="$4"
|
||||
shallow="${5:-false}"
|
||||
echo "Cloning primary from $primary_url secondary from $secondary_url"
|
||||
if [ -z "$primary_url" ]; then echo "Primary should not be empty." && exit 1; fi
|
||||
if [ -z "$secondary_url" ]; then echo "Secondary should not be empty." && exit 1; fi
|
||||
if [ -z "$folder_name" ]; then echo "Clone folder should not be empty." && exit 1; fi
|
||||
if [ -z "$ref" ]; then echo "Git clone ref should not be empty." && exit 1; fi
|
||||
clone "$primary_url" "$folder_name" "$ref" || clone "$secondary_url" "$folder_name" "$ref" || exit 1
|
||||
clone "$primary_url" "$folder_name" "$ref" "$shallow" || clone "$secondary_url" "$folder_name" "$ref" "$shallow" || exit 1
|
||||
echo ""
|
||||
}
|
||||
|
||||
@@ -110,9 +121,12 @@ declare -A primary_urls=(
|
||||
["mgconsole"]="http://$local_cache_host/git/mgconsole.git"
|
||||
["spdlog"]="http://$local_cache_host/git/spdlog"
|
||||
["jemalloc"]="http://$local_cache_host/git/jemalloc.git"
|
||||
["nlohmann"]="http://$local_cache_host/file/nlohmann/json/b3e5cb7f20dcc5c806e418df34324eca60d17d4e/single_include/nlohmann/json.hpp"
|
||||
["nlohmann"]="http://$local_cache_host/file/nlohmann/json/4f8fba14066156b73f1189a2b8bd568bde5284c5/single_include/nlohmann/json.hpp"
|
||||
["neo4j"]="http://$local_cache_host/file/neo4j-community-3.2.3-unix.tar.gz"
|
||||
["librdkafka"]="http://$local_cache_host/git/librdkafka.git"
|
||||
["protobuf"]="http://$local_cache_host/git/protobuf.git"
|
||||
["boost"]="http://$local_cache_host/file/boost_1_77_0.tar.gz"
|
||||
["pulsar"]="http://$local_cache_host/git/pulsar.git"
|
||||
)
|
||||
|
||||
# The goal of secondary urls is to have links to the "source of truth" of
|
||||
@@ -137,48 +151,38 @@ declare -A secondary_urls=(
|
||||
["mgconsole"]="http://github.com/memgraph/mgconsole.git"
|
||||
["spdlog"]="https://github.com/gabime/spdlog"
|
||||
["jemalloc"]="https://github.com/jemalloc/jemalloc.git"
|
||||
["nlohmann"]="https://raw.githubusercontent.com/nlohmann/json/b3e5cb7f20dcc5c806e418df34324eca60d17d4e/single_include/nlohmann/json.hpp"
|
||||
["nlohmann"]="https://raw.githubusercontent.com/nlohmann/json/4f8fba14066156b73f1189a2b8bd568bde5284c5/single_include/nlohmann/json.hpp"
|
||||
["neo4j"]="https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/neo4j-community-3.2.3-unix.tar.gz"
|
||||
["librdkafka"]="https://github.com/edenhill/librdkafka.git"
|
||||
["protobuf"]="https://github.com/protocolbuffers/protobuf.git"
|
||||
["boost"]="https://boostorg.jfrog.io/artifactory/main/release/1.77.0/source/boost_1_77_0.tar.gz"
|
||||
["pulsar"]="https://github.com/apache/pulsar.git"
|
||||
)
|
||||
|
||||
# antlr
|
||||
file_get_try_double "${primary_urls[antlr4-generator]}" "${secondary_urls[antlr4-generator]}"
|
||||
|
||||
antlr4_tag="5e5b6d35b4183fd330102c40947b95c4b5c6abb5" # v4.9.2
|
||||
repo_clone_try_double "${primary_urls[antlr4-code]}" "${secondary_urls[antlr4-code]}" "antlr4" "$antlr4_tag"
|
||||
# remove shared library from install dependencies
|
||||
sed -i 's/install(TARGETS antlr4_shared/install(TARGETS antlr4_shared OPTIONAL/' antlr4/runtime/Cpp/runtime/CMakeLists.txt
|
||||
# fix issue https://github.com/antlr/antlr4/issues/3194 - should update Antlr commit once the PR related to the issue gets merged
|
||||
sed -i 's/std::is_nothrow_copy_constructible/std::is_copy_constructible/' antlr4/runtime/Cpp/runtime/src/support/Any.h
|
||||
# replace the utf8cpp version which is used because the older one uses gtest that doesn't
|
||||
# compile with the newer compilers because of uninitialized variable
|
||||
# the newer utf8cpp switched to ftest
|
||||
sed -i 's/v3.1.1/v3.2.1/' antlr4/runtime/Cpp/runtime/CMakeLists.txt
|
||||
antlr4_tag="4.9.2" # v4.9.2
|
||||
repo_clone_try_double "${primary_urls[antlr4-code]}" "${secondary_urls[antlr4-code]}" "antlr4" "$antlr4_tag" true
|
||||
pushd antlr4
|
||||
git apply ../antlr4.patch
|
||||
popd
|
||||
|
||||
# cppitertools v2.0 2019-12-23
|
||||
cppitertools_ref="cb3635456bdb531121b82b4d2e3afc7ae1f56d47"
|
||||
repo_clone_try_double "${primary_urls[cppitertools]}" "${secondary_urls[cppitertools]}" "cppitertools" "$cppitertools_ref"
|
||||
|
||||
# fmt
|
||||
fmt_tag="7bdf0628b1276379886c7f6dda2cef2b3b374f0b" # (2020-11-25)
|
||||
repo_clone_try_double "${primary_urls[fmt]}" "${secondary_urls[fmt]}" "fmt" "$fmt_tag"
|
||||
|
||||
# rapidcheck
|
||||
rapidcheck_tag="7bc7d302191a4f3d0bf005692677126136e02f60" # (2020-05-04)
|
||||
repo_clone_try_double "${primary_urls[rapidcheck]}" "${secondary_urls[rapidcheck]}" "rapidcheck" "$rapidcheck_tag"
|
||||
|
||||
# google benchmark
|
||||
benchmark_tag="4f8bfeae470950ef005327973f15b0044eceaceb" # v1.1.0
|
||||
repo_clone_try_double "${primary_urls[gbenchmark]}" "${secondary_urls[gbenchmark]}" "benchmark" "$benchmark_tag"
|
||||
benchmark_tag="v1.6.0"
|
||||
repo_clone_try_double "${primary_urls[gbenchmark]}" "${secondary_urls[gbenchmark]}" "benchmark" "$benchmark_tag" true
|
||||
|
||||
# google test
|
||||
googletest_tag="ec44c6c1675c25b9827aacd08c02433cccde7780" # v1.8.0
|
||||
repo_clone_try_double "${primary_urls[gtest]}" "${secondary_urls[gtest]}" "googletest" "$googletest_tag"
|
||||
|
||||
# google flags
|
||||
gflags_tag="b37ceb03a0e56c9f15ce80409438a555f8a67b7c" # custom version (May 6, 2017)
|
||||
repo_clone_try_double "${primary_urls[gflags]}" "${secondary_urls[gflags]}" "gflags" "$gflags_tag"
|
||||
googletest_tag="release-1.8.0"
|
||||
repo_clone_try_double "${primary_urls[gtest]}" "${secondary_urls[gtest]}" "googletest" "$googletest_tag" true
|
||||
|
||||
# libbcrypt
|
||||
libbcrypt_tag="8aa32ad94ebe06b76853b0767c910c9fbf7ccef4" # custom version (Dec 16, 2016)
|
||||
@@ -198,22 +202,15 @@ cd json
|
||||
file_get_try_double "${primary_urls[nlohmann]}" "${secondary_urls[nlohmann]}"
|
||||
cd ..
|
||||
|
||||
bzip2_tag="0405487e2b1de738e7f1c8afb50d19cf44e8d580" # v1.0.6 (May 26, 2011)
|
||||
repo_clone_try_double "${primary_urls[bzip2]}" "${secondary_urls[bzip2]}" "bzip2" "$bzip2_tag"
|
||||
|
||||
zlib_tag="cacf7f1d4e3d44d871b605da3b647f07d718623f" # v1.2.11.
|
||||
repo_clone_try_double "${primary_urls[zlib]}" "${secondary_urls[zlib]}" "zlib" "$zlib_tag"
|
||||
# remove shared library from install dependencies
|
||||
sed -i 's/install(TARGETS zlib zlibstatic/install(TARGETS zlibstatic/g' zlib/CMakeLists.txt
|
||||
|
||||
rocksdb_tag="f3e33549c151f30ac4eb7c22356c6d0331f37652" # (2020-10-14)
|
||||
repo_clone_try_double "${primary_urls[rocksdb]}" "${secondary_urls[rocksdb]}" "rocksdb" "$rocksdb_tag"
|
||||
# remove shared library from install dependencies
|
||||
sed -i 's/TARGETS ${ROCKSDB_SHARED_LIB}/TARGETS ${ROCKSDB_SHARED_LIB} OPTIONAL/' rocksdb/CMakeLists.txt
|
||||
rocksdb_tag="v6.14.6" # (2020-10-14)
|
||||
repo_clone_try_double "${primary_urls[rocksdb]}" "${secondary_urls[rocksdb]}" "rocksdb" "$rocksdb_tag" true
|
||||
pushd rocksdb
|
||||
git apply ../rocksdb.patch
|
||||
popd
|
||||
|
||||
# mgclient
|
||||
mgclient_tag="v1.3.0" # (2021-09-23)
|
||||
repo_clone_try_double "${primary_urls[mgclient]}" "${secondary_urls[mgclient]}" "mgclient" "$mgclient_tag"
|
||||
repo_clone_try_double "${primary_urls[mgclient]}" "${secondary_urls[mgclient]}" "mgclient" "$mgclient_tag" true
|
||||
sed -i 's/\${CMAKE_INSTALL_LIBDIR}/lib/' mgclient/src/CMakeLists.txt
|
||||
|
||||
# pymgclient
|
||||
@@ -221,31 +218,26 @@ pymgclient_tag="4f85c179e56302d46a1e3e2cf43509db65f062b3" # (2021-01-15)
|
||||
repo_clone_try_double "${primary_urls[pymgclient]}" "${secondary_urls[pymgclient]}" "pymgclient" "$pymgclient_tag"
|
||||
|
||||
# mgconsole
|
||||
mgconsole_tag="01ae99bfce772e540e75c076ba03cf06c0c2ac7d" # (2021-05-26)
|
||||
repo_clone_try_double "${primary_urls[mgconsole]}" "${secondary_urls[mgconsole]}" "mgconsole" "$mgconsole_tag"
|
||||
mgconsole_tag="v1.1.0" # (2021-10-07)
|
||||
repo_clone_try_double "${primary_urls[mgconsole]}" "${secondary_urls[mgconsole]}" "mgconsole" "$mgconsole_tag" true
|
||||
|
||||
spdlog_tag="46d418164dd4cd9822cf8ca62a116a3f71569241" # (2020-12-01)
|
||||
repo_clone_try_double "${primary_urls[spdlog]}" "${secondary_urls[spdlog]}" "spdlog" "$spdlog_tag"
|
||||
|
||||
jemalloc_tag="ea6b3e973b477b8061e0076bb257dbd7f3faa756" # (2021-02-11)
|
||||
repo_clone_try_double "${primary_urls[jemalloc]}" "${secondary_urls[jemalloc]}" "jemalloc" "$jemalloc_tag"
|
||||
pushd jemalloc
|
||||
# ThreadPool select job randomly, and there can be some threads that had been
|
||||
# performed some memory heavy task before and will be inactive for some time,
|
||||
# but until it will became active again, the memory will not be freed since by
|
||||
# default each thread has it's own arena, but there should be not more then
|
||||
# 4*CPU arenas (see opt.nareans description).
|
||||
#
|
||||
# By enabling percpu_arena number of arenas limited to number of CPUs and hence
|
||||
# this problem should go away.
|
||||
#
|
||||
# muzzy_decay_ms -- use MADV_FREE when available on newer Linuxes, to
|
||||
# avoid spurious latencies and additional work associated with
|
||||
# MADV_DONTNEED. See
|
||||
# https://github.com/ClickHouse/ClickHouse/issues/11121 for motivation.
|
||||
./autogen.sh --with-malloc-conf="percpu_arena:percpu,oversize_threshold:0,muzzy_decay_ms:5000,dirty_decay_ms:5000"
|
||||
popd
|
||||
spdlog_tag="v1.9.2" # (2021-08-12)
|
||||
repo_clone_try_double "${primary_urls[spdlog]}" "${secondary_urls[spdlog]}" "spdlog" "$spdlog_tag" true
|
||||
|
||||
# librdkafka
|
||||
librdkafka_tag="v1.7.0" # (2021-05-06)
|
||||
repo_clone_try_double "${primary_urls[librdkafka]}" "${secondary_urls[librdkafka]}" "librdkafka" "$librdkafka_tag"
|
||||
repo_clone_try_double "${primary_urls[librdkafka]}" "${secondary_urls[librdkafka]}" "librdkafka" "$librdkafka_tag" true
|
||||
|
||||
# protobuf
|
||||
protobuf_tag="v3.12.4"
|
||||
repo_clone_try_double "${primary_urls[protobuf]}" "${secondary_urls[protobuf]}" "protobuf" "$protobuf_tag" true
|
||||
pushd protobuf
|
||||
./autogen.sh && ./configure CC=clang CXX=clang++ --prefix=$(pwd)/lib
|
||||
popd
|
||||
|
||||
#pulsar
|
||||
pulsar_tag="v2.8.1"
|
||||
repo_clone_try_double "${primary_urls[pulsar]}" "${secondary_urls[pulsar]}" "pulsar" "$pulsar_tag" true
|
||||
pushd pulsar
|
||||
git apply ../pulsar.patch
|
||||
popd
|
||||
|
||||
@@ -36,7 +36,7 @@ ADDITIONAL USE GRANT: You may use the Licensed Work in accordance with the
|
||||
3. using the Licensed Work to create a work or solution
|
||||
which competes (or might reasonably be expected to
|
||||
compete) with the Licensed Work.
|
||||
CHANGE DATE: 2025-10-05
|
||||
CHANGE DATE: 2025-12-08
|
||||
CHANGE LICENSE: Apache License, Version 2.0
|
||||
|
||||
For information about alternative licensing arrangements, please visit: https://memgraph.com/legal.
|
||||
|
||||
@@ -56,7 +56,10 @@ def procedure(context: mgp.ProcCtx,
|
||||
|
||||
|
||||
@mgp.write_proc
|
||||
def write_procedure(context: mgp.ProcCtx, property_name: str, property_value: mgp.Nullable[mgp.Any]) -> mgp.Record(created_vertex=mgp.Vertex):
|
||||
def write_procedure(context: mgp.ProcCtx,
|
||||
property_name: str,
|
||||
property_value: mgp.Nullable[mgp.Any]
|
||||
) -> mgp.Record(created_vertex=mgp.Vertex):
|
||||
"""
|
||||
This example procedure creates a new vertex with the specified property
|
||||
and connects it to all existing vertex which has the same property with
|
||||
@@ -86,7 +89,6 @@ def write_procedure(context: mgp.ProcCtx, property_name: str, property_value: mg
|
||||
vertex.properties.set(property_name, property_value)
|
||||
# Connect the new vertex to the other vertices
|
||||
for v in vertices_to_connect:
|
||||
print("ALMA")
|
||||
context.graph.create_edge(vertex, v, mgp.EdgeType("HAS_SAME_VALUE"))
|
||||
|
||||
return mgp.Record(created_vertex=vertex)
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# Memgraph Release
|
||||
|
||||
Please continue in
|
||||
[Notion](https://www.notion.so/memgraph/Release-621733518c82456ebc119d2a025b5dd3).
|
||||
@@ -1 +1 @@
|
||||
../LICENSE.md
|
||||
../../LICENSE
|
||||
@@ -1,14 +1,14 @@
|
||||
FROM debian:buster
|
||||
FROM debian:bullseye
|
||||
# NOTE: If you change the base distro update release/package as well.
|
||||
|
||||
ARG deb_release
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
openssl libcurl4 libssl1.1 libseccomp2 python3 libpython3.7 python3-pip \
|
||||
openssl libcurl4 libssl1.1 libseccomp2 python3 libpython3.9 python3-pip \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
RUN pip3 install networkx==2.4 numpy==1.19.2 scipy==1.5.2
|
||||
RUN pip3 install networkx==2.4 numpy==1.21.4 scipy==1.7.3
|
||||
|
||||
COPY ${deb_release} /
|
||||
|
||||
|
||||
@@ -35,8 +35,7 @@ cd ${working_dir}
|
||||
# Extract version and offering from deb name.
|
||||
deb_name=`echo $(basename "$deb_path") | sed 's/.deb$//'`
|
||||
version=`echo ${deb_name} | cut -d '_' -f 2 | rev | cut -d '-' -f 2- | rev | tr '+~' '__'`
|
||||
offering=`echo ${version} | cut -d '-' -f 2`
|
||||
dockerfile_path="${working_dir}/memgraph_${offering}.dockerfile"
|
||||
dockerfile_path="${working_dir}/memgraph.dockerfile"
|
||||
image_name="memgraph:${version}"
|
||||
package_name="memgraph-${version}-docker.tar.gz"
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
|
||||
import time
|
||||
from functools import wraps
|
||||
|
||||
# This script is used to determine the current version of Memgraph. The script
|
||||
# determines the current version using `git` automatically. The user can also
|
||||
@@ -93,16 +94,29 @@ import os
|
||||
# https://fedoraproject.org/wiki/Package_Versioning_Examples
|
||||
|
||||
|
||||
def retry(retry_limit, timeout=100):
|
||||
def inner_func(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
for _ in range(retry_limit):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception:
|
||||
time.sleep(timeout)
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
return inner_func
|
||||
|
||||
|
||||
@retry(3)
|
||||
def get_output(*cmd, multiple=False):
|
||||
ret = subprocess.run(cmd, stdout=subprocess.PIPE, check=True)
|
||||
if multiple:
|
||||
return list(map(lambda x: x.strip(),
|
||||
ret.stdout.decode("utf-8").strip().split("\n")))
|
||||
return list(map(lambda x: x.strip(), ret.stdout.decode("utf-8").strip().split("\n")))
|
||||
return ret.stdout.decode("utf-8").strip()
|
||||
|
||||
|
||||
def format_version(variant, version, offering, distance=None, shorthash=None,
|
||||
suffix=None):
|
||||
def format_version(variant, version, offering, distance=None, shorthash=None, suffix=None):
|
||||
if not distance:
|
||||
# This is a release version.
|
||||
if variant == "deb":
|
||||
@@ -135,8 +149,7 @@ def format_version(variant, version, offering, distance=None, shorthash=None,
|
||||
return ret
|
||||
elif variant == "rpm":
|
||||
# <VERSION>_0.<DISTANCE>.<SHORTHASH>.<OFFERING>[.<SUFFIX>]
|
||||
ret = "{}_0.{}.{}{}".format(
|
||||
version, distance, shorthash, "." + offering if offering else "")
|
||||
ret = "{}_0.{}.{}{}".format(version, distance, shorthash, "." + offering if offering else "")
|
||||
if suffix:
|
||||
ret += "." + suffix
|
||||
return ret
|
||||
@@ -149,27 +162,23 @@ def format_version(variant, version, offering, distance=None, shorthash=None,
|
||||
|
||||
|
||||
# Parse arguments.
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Get the current version of Memgraph.")
|
||||
parser = argparse.ArgumentParser(description="Get the current version of Memgraph.")
|
||||
parser.add_argument("--open-source", action="store_true", help="set the current offering to 'open-source'")
|
||||
parser.add_argument("version", help="manual version override, if supplied the version isn't " "determined using git")
|
||||
parser.add_argument("suffix", help="custom suffix for the current version being built")
|
||||
parser.add_argument(
|
||||
"--open-source", action="store_true",
|
||||
help="set the current offering to 'open-source'")
|
||||
"--variant",
|
||||
choices=("binary", "deb", "rpm"),
|
||||
default="binary",
|
||||
help="which variant of the version string should be generated",
|
||||
)
|
||||
parser.add_argument(
|
||||
"version", help="manual version override, if supplied the version isn't "
|
||||
"determined using git")
|
||||
parser.add_argument(
|
||||
"suffix", help="custom suffix for the current version being built")
|
||||
parser.add_argument(
|
||||
"--variant", choices=("binary", "deb", "rpm"), default="binary",
|
||||
help="which variant of the version string should be generated")
|
||||
parser.add_argument(
|
||||
"--memgraph-root-dir", help="The root directory of the checked out "
|
||||
"Memgraph repository.", default=".")
|
||||
"--memgraph-root-dir", help="The root directory of the checked out " "Memgraph repository.", default="."
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.isdir(args.memgraph_root_dir):
|
||||
raise Exception("The root directory ({}) is not a valid directory".format(
|
||||
args.memgraph_root_dir))
|
||||
raise Exception("The root directory ({}) is not a valid directory".format(args.memgraph_root_dir))
|
||||
|
||||
os.chdir(args.memgraph_root_dir)
|
||||
|
||||
@@ -179,8 +188,7 @@ offering = "open-source" if args.open_source else None
|
||||
if args.version:
|
||||
if not re.match(r"^[0-9]+\.[0-9]+\.[0-9]+$", args.version):
|
||||
raise Exception("Invalid version supplied '{}'!".format(args.version))
|
||||
print(format_version(args.variant, args.version, offering,
|
||||
suffix=args.suffix), end="")
|
||||
print(format_version(args.variant, args.version, offering, suffix=args.suffix), end="")
|
||||
sys.exit(0)
|
||||
|
||||
# Within CI, after the regular checkout, master is sometimes (e.g. in the case
|
||||
@@ -234,33 +242,28 @@ versions.sort(reverse=True)
|
||||
current_version = None
|
||||
for version in versions:
|
||||
version_tuple, branch, master_branch_merge = version
|
||||
current_branch_merge = get_output(
|
||||
"git", "merge-base", current_hash, branch)
|
||||
master_current_merge = get_output(
|
||||
"git", "merge-base", current_hash, "master")
|
||||
current_branch_merge = get_output("git", "merge-base", current_hash, branch)
|
||||
master_current_merge = get_output("git", "merge-base", current_hash, "master")
|
||||
# The first check checks whether this commit is a child of `master` and
|
||||
# the version branch was created before us.
|
||||
# The second check checks whether this commit is a child of the version
|
||||
# branch.
|
||||
if master_branch_merge == current_branch_merge or \
|
||||
master_branch_merge == master_current_merge:
|
||||
if master_branch_merge == current_branch_merge or master_branch_merge == master_current_merge:
|
||||
current_version = version
|
||||
break
|
||||
|
||||
# Determine current version.
|
||||
if current_version is None:
|
||||
raise Exception("You are attempting to determine the version for a very "
|
||||
"old version of Memgraph!")
|
||||
raise Exception("You are attempting to determine the version for a very " "old version of Memgraph!")
|
||||
version, branch, master_branch_merge = current_version
|
||||
distance = int(get_output("git", "rev-list", "--count", "--first-parent",
|
||||
master_branch_merge + ".." + current_hash))
|
||||
distance = int(get_output("git", "rev-list", "--count", "--first-parent", master_branch_merge + ".." + current_hash))
|
||||
version_str = ".".join(map(str, version)) + ".0"
|
||||
if distance == 0:
|
||||
print(format_version(args.variant, version_str, offering,
|
||||
suffix=args.suffix),
|
||||
end="")
|
||||
print(format_version(args.variant, version_str, offering, suffix=args.suffix), end="")
|
||||
else:
|
||||
print(format_version(args.variant, version_str, offering,
|
||||
distance=distance, shorthash=current_hash_short,
|
||||
suffix=args.suffix),
|
||||
end="")
|
||||
print(
|
||||
format_version(
|
||||
args.variant, version_str, offering, distance=distance, shorthash=current_hash_short, suffix=args.suffix
|
||||
),
|
||||
end="",
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM debian:9
|
||||
FROM debian:11
|
||||
|
||||
ARG TOOLCHAIN_VERSION
|
||||
|
||||
@@ -10,8 +10,8 @@ RUN apt update && apt install -y \
|
||||
# Do NOT be smart here and clean the cache because the container is used in the
|
||||
# stateful context.
|
||||
|
||||
RUN wget -q https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/${TOOLCHAIN_VERSION}/${TOOLCHAIN_VERSION}-binaries-debian-9.tar.gz \
|
||||
-O ${TOOLCHAIN_VERSION}-binaries-debian-9.tar.gz \
|
||||
&& tar xzvf ${TOOLCHAIN_VERSION}-binaries-debian-9.tar.gz -C /opt
|
||||
RUN wget -q https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/${TOOLCHAIN_VERSION}/${TOOLCHAIN_VERSION}-binaries-debian-11.tar.gz \
|
||||
-O ${TOOLCHAIN_VERSION}-binaries-debian-11.tar.gz \
|
||||
&& tar xzvf ${TOOLCHAIN_VERSION}-binaries-debian-11.tar.gz -C /opt
|
||||
|
||||
ENTRYPOINT ["sleep", "infinity"]
|
||||
@@ -8,14 +8,14 @@ services:
|
||||
build:
|
||||
context: centos-8
|
||||
container_name: "mgbuild_centos-8"
|
||||
mgbuild_debian-9:
|
||||
build:
|
||||
context: debian-9
|
||||
container_name: "mgbuild_debian-9"
|
||||
mgbuild_debian-10:
|
||||
build:
|
||||
context: debian-10
|
||||
container_name: "mgbuild_debian-10"
|
||||
mgbuild_debian-11:
|
||||
build:
|
||||
context: debian-11
|
||||
container_name: "mgbuild_debian-11"
|
||||
mgbuild_ubuntu-18.04:
|
||||
build:
|
||||
context: ubuntu-18.04
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
SUPPORTED_OS=(centos-7 centos-8 debian-9 debian-10 ubuntu-18.04 ubuntu-20.04)
|
||||
SUPPORTED_OS=(centos-7 centos-8 debian-10 debian-11 ubuntu-18.04 ubuntu-20.04)
|
||||
PROJECT_ROOT="$SCRIPT_DIR/../.."
|
||||
TOOLCHAIN_VERSION="toolchain-v3"
|
||||
TOOLCHAIN_VERSION="toolchain-v4"
|
||||
ACTIVATE_TOOLCHAIN="source /opt/${TOOLCHAIN_VERSION}/activate"
|
||||
HOST_OUTPUT_DIR="$PROJECT_ROOT/build/output"
|
||||
|
||||
print_help () {
|
||||
echo "$0 init|package {os} [--for-docker]|docker|test"
|
||||
echo "$0 init|package {os} [--for-docker|--for-platform]|docker|test"
|
||||
echo ""
|
||||
echo " OSs: ${SUPPORTED_OS[*]}"
|
||||
exit 1
|
||||
@@ -18,24 +18,34 @@ print_help () {
|
||||
|
||||
make_package () {
|
||||
os="$1"
|
||||
|
||||
build_container="mgbuild_$os"
|
||||
echo "Building Memgraph for $os on $build_container..."
|
||||
|
||||
package_command=""
|
||||
if [[ "$os" =~ ^"centos".* ]]; then
|
||||
docker exec "$build_container" bash -c "yum -y update"
|
||||
package_command=" cpack -G RPM --config ../CPackConfig.cmake && rpmlint memgraph*.rpm "
|
||||
fi
|
||||
if [[ "$os" =~ ^"debian".* ]]; then
|
||||
docker exec "$build_container" bash -c "apt update"
|
||||
package_command=" cpack -G DEB --config ../CPackConfig.cmake "
|
||||
fi
|
||||
if [[ "$os" =~ ^"ubuntu".* ]]; then
|
||||
docker exec "$build_container" bash -c "apt update"
|
||||
package_command=" cpack -G DEB --config ../CPackConfig.cmake "
|
||||
fi
|
||||
docker_flag=" -DBUILD_FOR_DOCKER=OFF "
|
||||
if [[ "$#" -gt 2 ]]; then
|
||||
if [[ "$3" == "--for-docker" ]]; then
|
||||
docker_flag=" -DBUILD_FOR_DOCKER=ON "
|
||||
telemetry_id_override_flag=""
|
||||
if [[ "$#" -gt 1 ]]; then
|
||||
if [[ "$2" == "--for-docker" ]]; then
|
||||
telemetry_id_override_flag=" -DMG_TELEMETRY_ID_OVERRIDE=DOCKER "
|
||||
elif [[ "$2" == "--for-platform" ]]; then
|
||||
telemetry_id_override_flag=" -DMG_TELEMETRY_ID_OVERRIDE=DOCKER-PLATFORM"
|
||||
else
|
||||
print_help
|
||||
exit
|
||||
fi
|
||||
fi
|
||||
build_container="mgbuild_$os"
|
||||
echo "Building Memgraph for $os on $build_container..."
|
||||
|
||||
echo "Copying project files..."
|
||||
# If master is not the current branch, fetch it, because the get_version
|
||||
@@ -62,9 +72,9 @@ make_package () {
|
||||
docker exec "$build_container" bash -c "/memgraph/environment/os/$os.sh install MEMGRAPH_BUILD_DEPS"
|
||||
|
||||
echo "Building targeted package..."
|
||||
docker exec "$build_container" bash -c "cd /memgraph && ./init"
|
||||
docker exec "$build_container" bash -c "cd /memgraph && $ACTIVATE_TOOLCHAIN && ./init"
|
||||
docker exec "$build_container" bash -c "cd $container_build_dir && rm -rf ./*"
|
||||
docker exec "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && cmake -DCMAKE_BUILD_TYPE=release $docker_flag .."
|
||||
docker exec "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && cmake -DCMAKE_BUILD_TYPE=release $telemetry_id_override_flag .."
|
||||
# ' is used instead of " because we need to run make within the allowed
|
||||
# container resources.
|
||||
# shellcheck disable=SC2016
|
||||
@@ -90,8 +100,8 @@ case "$1" in
|
||||
;;
|
||||
|
||||
docker)
|
||||
# NOTE: Docker is build on top of Debian 10 package.
|
||||
based_on_os="debian-10"
|
||||
# NOTE: Docker is build on top of Debian 11 package.
|
||||
based_on_os="debian-11"
|
||||
# shellcheck disable=SC2012
|
||||
last_package_name=$(cd "$HOST_OUTPUT_DIR/$based_on_os" && ls -t memgraph* | head -1)
|
||||
docker_build_folder="$PROJECT_ROOT/release/docker"
|
||||
|
||||
@@ -37,7 +37,7 @@ set(mg_single_node_v2_sources
|
||||
)
|
||||
|
||||
set(mg_single_node_v2_libs stdc++fs Threads::Threads
|
||||
telemetry_lib mg-query mg-communication mg-memory mg-utils mg-auth)
|
||||
telemetry_lib mg-query mg-communication mg-memory mg-utils mg-auth mg-license mg-settings)
|
||||
if (MG_ENTERPRISE)
|
||||
# These are enterprise subsystems
|
||||
set(mg_single_node_v2_libs ${mg_single_node_v2_libs} mg-audit)
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
set(audit_src_files log.cpp)
|
||||
|
||||
find_package(fmt REQUIRED)
|
||||
find_package(gflags REQUIRED)
|
||||
|
||||
add_library(mg-audit STATIC ${audit_src_files})
|
||||
target_link_libraries(mg-audit json gflags fmt)
|
||||
target_link_libraries(mg-audit json gflags fmt::fmt)
|
||||
target_link_libraries(mg-audit mg-utils mg-storage-v2)
|
||||
|
||||
@@ -5,9 +5,12 @@ set(auth_src_files
|
||||
module.cpp)
|
||||
|
||||
find_package(Seccomp REQUIRED)
|
||||
find_package(fmt REQUIRED)
|
||||
find_package(gflags REQUIRED)
|
||||
|
||||
|
||||
add_library(mg-auth STATIC ${auth_src_files})
|
||||
target_link_libraries(mg-auth json libbcrypt gflags fmt)
|
||||
target_link_libraries(mg-auth json libbcrypt gflags fmt::fmt)
|
||||
target_link_libraries(mg-auth mg-utils mg-kvstore)
|
||||
|
||||
target_link_libraries(mg-auth ${Seccomp_LIBRARIES})
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "utils/flag_validation.hpp"
|
||||
#include "utils/license.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/message.hpp"
|
||||
#include "utils/settings.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
@@ -102,17 +103,15 @@ std::optional<User> Auth::Authenticate(const std::string &username, const std::s
|
||||
if (FLAGS_auth_module_create_missing_user) {
|
||||
user = AddUser(username, password);
|
||||
if (!user) {
|
||||
spdlog::warn(
|
||||
"Couldn't authenticate user '{}' using the auth module because "
|
||||
"the user already exists as a role!",
|
||||
username);
|
||||
spdlog::warn(utils::MessageWithLink(
|
||||
"Couldn't create the missing user '{}' using the auth module because the user already exists as a role.",
|
||||
username, "https://memgr.ph/auth"));
|
||||
return std::nullopt;
|
||||
}
|
||||
} else {
|
||||
spdlog::warn(
|
||||
"Couldn't authenticate user '{}' using the auth module because the "
|
||||
"user doesn't exist!",
|
||||
username);
|
||||
spdlog::warn(utils::MessageWithLink(
|
||||
"Couldn't authenticate user '{}' using the auth module because the user doesn't exist.", username,
|
||||
"https://memgr.ph/auth"));
|
||||
return std::nullopt;
|
||||
}
|
||||
} else {
|
||||
@@ -126,17 +125,16 @@ std::optional<User> Auth::Authenticate(const std::string &username, const std::s
|
||||
role = AddRole(rolename);
|
||||
if (!role) {
|
||||
spdlog::warn(
|
||||
"Couldn't authenticate user '{}' using the auth module "
|
||||
"because the user's role '{}' already exists as a user!",
|
||||
username, rolename);
|
||||
utils::MessageWithLink("Couldn't authenticate user '{}' using the auth module because the user's "
|
||||
"role '{}' already exists as a user.",
|
||||
username, rolename, "https://memgr.ph/auth"));
|
||||
return std::nullopt;
|
||||
}
|
||||
SaveRole(*role);
|
||||
} else {
|
||||
spdlog::warn(
|
||||
"Couldn't authenticate user '{}' using the auth module because "
|
||||
"the user's role '{}' doesn't exist!",
|
||||
username, rolename);
|
||||
spdlog::warn(utils::MessageWithLink(
|
||||
"Couldn't authenticate user '{}' using the auth module because the user's role '{}' doesn't exist.",
|
||||
username, rolename, "https://memgr.ph/auth"));
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
@@ -150,11 +148,13 @@ std::optional<User> Auth::Authenticate(const std::string &username, const std::s
|
||||
} else {
|
||||
auto user = GetUser(username);
|
||||
if (!user) {
|
||||
spdlog::warn("Couldn't authenticate user '{}' because the user doesn't exist", username);
|
||||
spdlog::warn(utils::MessageWithLink("Couldn't authenticate user '{}' because the user doesn't exist.", username,
|
||||
"https://memgr.ph/auth"));
|
||||
return std::nullopt;
|
||||
}
|
||||
if (!user->CheckPassword(password)) {
|
||||
spdlog::warn("Couldn't authenticate user '{}'", username);
|
||||
spdlog::warn(utils::MessageWithLink("Couldn't authenticate user '{}' because the password is not correct.",
|
||||
username, "https://memgr.ph/auth"));
|
||||
return std::nullopt;
|
||||
}
|
||||
return user;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
find_package(fmt REQUIRED)
|
||||
find_package(gflags REQUIRED)
|
||||
|
||||
set(communication_src_files
|
||||
bolt/v1/value.cpp
|
||||
buffer.cpp
|
||||
@@ -7,7 +10,7 @@ set(communication_src_files
|
||||
init.cpp)
|
||||
|
||||
add_library(mg-communication STATIC ${communication_src_files})
|
||||
target_link_libraries(mg-communication Threads::Threads mg-utils mg-io fmt gflags)
|
||||
target_link_libraries(mg-communication Threads::Threads mg-utils mg-io fmt::fmt gflags)
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
target_link_libraries(mg-communication ${OPENSSL_LIBRARIES})
|
||||
|
||||
@@ -31,6 +31,7 @@ enum class Signature : uint8_t {
|
||||
Begin = 0x11,
|
||||
Commit = 0x12,
|
||||
Rollback = 0x13,
|
||||
Route = 0x66,
|
||||
|
||||
Record = 0x71,
|
||||
Success = 0x70,
|
||||
|
||||
@@ -28,7 +28,7 @@ static constexpr size_t kChunkWholeSize = kChunkHeaderSize + kChunkMaxDataSize;
|
||||
*/
|
||||
static constexpr size_t kHandshakeSize = 20;
|
||||
|
||||
static constexpr uint16_t kSupportedVersions[3] = {0x0100, 0x0400, 0x0401};
|
||||
static constexpr uint16_t kSupportedVersions[] = {0x0100, 0x0400, 0x0401, 0x0403};
|
||||
|
||||
static constexpr int kPullAll = -1;
|
||||
static constexpr int kPullLast = -1;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <string>
|
||||
|
||||
@@ -19,337 +20,69 @@
|
||||
#include "communication/bolt/v1/constants.hpp"
|
||||
#include "communication/bolt/v1/exceptions.hpp"
|
||||
#include "communication/bolt/v1/state.hpp"
|
||||
#include "communication/bolt/v1/states/handlers.hpp"
|
||||
#include "communication/bolt/v1/value.hpp"
|
||||
#include "communication/exceptions.hpp"
|
||||
#include "utils/likely.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/message.hpp"
|
||||
|
||||
namespace communication::bolt {
|
||||
|
||||
// TODO (mferencevic): revise these error messages
|
||||
inline std::pair<std::string, std::string> ExceptionToErrorMessage(const std::exception &e) {
|
||||
if (auto *verbose = dynamic_cast<const VerboseError *>(&e)) {
|
||||
return {verbose->code(), verbose->what()};
|
||||
}
|
||||
if (dynamic_cast<const ClientError *>(&e)) {
|
||||
// Clients expect 4 strings separated by dots. First being database name
|
||||
// (for example: Neo, Memgraph...), second being either ClientError,
|
||||
// TransientError or DatabaseError (or ClientNotification for warnings).
|
||||
// ClientError means wrong query, do not retry. DatabaseError means
|
||||
// something wrong in database, do not retry. TransientError means query
|
||||
// failed, but if retried it may succeed, retry it.
|
||||
//
|
||||
// Third and fourth strings being namespace and specific error name.
|
||||
// It is not really important what we put there since we don't expect
|
||||
// any special handling of specific exceptions on client side, but we
|
||||
// need to make sure that we don't accidentally return some exception
|
||||
// name which clients handle in a special way. For example, if client
|
||||
// receives *.TransientError.Transaction.Terminate it will not rerun
|
||||
// query even though TransientError was returned, because of Neo's
|
||||
// semantics of that error.
|
||||
return {"Memgraph.ClientError.MemgraphError.MemgraphError", e.what()};
|
||||
}
|
||||
if (dynamic_cast<const utils::BasicException *>(&e)) {
|
||||
// Exception not derived from QueryException was thrown which means that
|
||||
// database probably aborted transaction because of some timeout,
|
||||
// deadlock, serialization error or something similar. We return
|
||||
// TransientError since retry of same transaction could succeed.
|
||||
return {"Memgraph.TransientError.MemgraphError.MemgraphError", e.what()};
|
||||
}
|
||||
if (dynamic_cast<const std::bad_alloc *>(&e)) {
|
||||
// std::bad_alloc was thrown, God knows in which state is database ->
|
||||
// terminate.
|
||||
LOG_FATAL("Memgraph is out of memory");
|
||||
}
|
||||
// All exceptions used in memgraph are derived from BasicException. Since
|
||||
// we caught some other exception we don't know what is going on. Return
|
||||
// DatabaseError, log real message and return generic string.
|
||||
spdlog::error("Unknown exception occurred during query execution {}", e.what());
|
||||
return {"Memgraph.DatabaseError.MemgraphError.MemgraphError",
|
||||
"An unknown exception occurred, this is unexpected. Real message "
|
||||
"should be in database logs."};
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
inline State HandleFailure(TSession &session, const std::exception &e) {
|
||||
spdlog::trace("Error message: {}", e.what());
|
||||
if (const auto *p = dynamic_cast<const utils::StacktraceException *>(&e)) {
|
||||
spdlog::trace("Error trace: {}", p->trace());
|
||||
}
|
||||
session.encoder_buffer_.Clear();
|
||||
auto code_message = ExceptionToErrorMessage(e);
|
||||
bool fail_sent = session.encoder_.MessageFailure({{"code", code_message.first}, {"message", code_message.second}});
|
||||
if (!fail_sent) {
|
||||
spdlog::trace("Couldn't send failure message!");
|
||||
return State::Close;
|
||||
}
|
||||
return State::Error;
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
State HandleRun(TSession &session, State state, Marker marker) {
|
||||
const std::map<std::string, Value> kEmptyFields = {{"fields", std::vector<Value>{}}};
|
||||
|
||||
const auto expected_marker = session.version_.major == 1 ? Marker::TinyStruct2 : Marker::TinyStruct3;
|
||||
if (marker != expected_marker) {
|
||||
spdlog::trace("Expected {} marker, but received 0x{:02X}!",
|
||||
session.version_.major == 1 ? "TinyStruct2" : "TinyStruct3", utils::UnderlyingCast(marker));
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
Value query, params, extra;
|
||||
if (!session.decoder_.ReadValue(&query, Value::Type::String)) {
|
||||
spdlog::trace("Couldn't read query string!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (!session.decoder_.ReadValue(¶ms, Value::Type::Map)) {
|
||||
spdlog::trace("Couldn't read parameters!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (session.version_.major == 4) {
|
||||
if (!session.decoder_.ReadValue(&extra, Value::Type::Map)) {
|
||||
spdlog::trace("Couldn't read extra field!");
|
||||
}
|
||||
}
|
||||
|
||||
if (state != State::Idle) {
|
||||
// Client could potentially recover if we move to error state, but there is
|
||||
// no legitimate situation in which well working client would end up in this
|
||||
// situation.
|
||||
spdlog::trace("Unexpected RUN command!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
|
||||
|
||||
spdlog::debug("[Run] '{}'", query.ValueString());
|
||||
|
||||
try {
|
||||
// Interpret can throw.
|
||||
auto [header, qid] = session.Interpret(query.ValueString(), params.ValueMap());
|
||||
// Convert std::string to Value
|
||||
std::vector<Value> vec;
|
||||
std::map<std::string, Value> data;
|
||||
vec.reserve(header.size());
|
||||
for (auto &i : header) vec.emplace_back(std::move(i));
|
||||
data.emplace("fields", std::move(vec));
|
||||
// Send the header.
|
||||
if (!session.encoder_.MessageSuccess(data)) {
|
||||
spdlog::trace("Couldn't send query header!");
|
||||
State RunHandlerV1(Signature signature, TSession &session, State state, Marker marker) {
|
||||
switch (signature) {
|
||||
case Signature::Run:
|
||||
return HandleRunV1<TSession>(session, state, marker);
|
||||
case Signature::Pull:
|
||||
return HandlePullV1<TSession>(session, state, marker);
|
||||
case Signature::Discard:
|
||||
return HandleDiscardV1<TSession>(session, state, marker);
|
||||
case Signature::Reset:
|
||||
return HandleReset<TSession>(session, marker);
|
||||
default:
|
||||
spdlog::trace("Unrecognized signature received (0x{:02X})!", utils::UnderlyingCast(signature));
|
||||
return State::Close;
|
||||
}
|
||||
return State::Result;
|
||||
} catch (const std::exception &e) {
|
||||
return HandleFailure(session, e);
|
||||
}
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
template <bool is_pull, typename TSession>
|
||||
State HandlePullDiscard(TSession &session, State state, Marker marker) {
|
||||
const auto expected_marker = session.version_.major == 1 ? Marker::TinyStruct : Marker::TinyStruct1;
|
||||
if (marker != expected_marker) {
|
||||
spdlog::trace("Expected {} marker, but received 0x{:02X}!",
|
||||
session.version_.major == 1 ? "TinyStruct" : "TinyStruct1", utils::UnderlyingCast(marker));
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (state != State::Result) {
|
||||
if constexpr (is_pull) {
|
||||
spdlog::trace("Unexpected PULL!");
|
||||
} else {
|
||||
spdlog::trace("Unexpected DISCARD!");
|
||||
}
|
||||
// Same as `unexpected RUN` case.
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
try {
|
||||
std::optional<int> n;
|
||||
std::optional<int> qid;
|
||||
|
||||
if (session.version_.major == 4) {
|
||||
Value extra;
|
||||
if (!session.decoder_.ReadValue(&extra, Value::Type::Map)) {
|
||||
spdlog::trace("Couldn't read extra field!");
|
||||
}
|
||||
const auto &extra_map = extra.ValueMap();
|
||||
if (extra_map.count("n")) {
|
||||
if (const auto n_value = extra_map.at("n").ValueInt(); n_value != kPullAll) {
|
||||
n = n_value;
|
||||
}
|
||||
}
|
||||
|
||||
if (extra_map.count("qid")) {
|
||||
if (const auto qid_value = extra_map.at("qid").ValueInt(); qid_value != kPullLast) {
|
||||
qid = qid_value;
|
||||
}
|
||||
template <typename TSession, int bolt_minor = 0>
|
||||
State RunHandlerV4(Signature signature, TSession &session, State state, Marker marker) {
|
||||
switch (signature) {
|
||||
case Signature::Run:
|
||||
return HandleRunV4<TSession>(session, state, marker);
|
||||
case Signature::Pull:
|
||||
return HandlePullV4<TSession>(session, state, marker);
|
||||
case Signature::Discard:
|
||||
return HandleDiscardV4<TSession>(session, state, marker);
|
||||
case Signature::Reset:
|
||||
return HandleReset<TSession>(session, marker);
|
||||
case Signature::Begin:
|
||||
return HandleBegin<TSession>(session, state, marker);
|
||||
case Signature::Commit:
|
||||
return HandleCommit<TSession>(session, state, marker);
|
||||
case Signature::Goodbye:
|
||||
return HandleGoodbye<TSession>();
|
||||
case Signature::Rollback:
|
||||
return HandleRollback<TSession>(session, state, marker);
|
||||
case Signature::Noop: {
|
||||
if constexpr (bolt_minor >= 1) {
|
||||
return HandleNoop<TSession>(state);
|
||||
} else {
|
||||
spdlog::trace("Supported only in bolt v4.1");
|
||||
return State::Close;
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, Value> summary;
|
||||
if constexpr (is_pull) {
|
||||
// Pull can throw.
|
||||
summary = session.Pull(&session.encoder_, n, qid);
|
||||
} else {
|
||||
summary = session.Discard(n, qid);
|
||||
case Signature::Route: {
|
||||
if constexpr (bolt_minor >= 3) {
|
||||
if (signature == Signature::Route) return HandleRoute<TSession>(session);
|
||||
} else {
|
||||
spdlog::trace("Supported only in bolt v4.3");
|
||||
return State::Close;
|
||||
}
|
||||
}
|
||||
|
||||
if (!session.encoder_.MessageSuccess(summary)) {
|
||||
spdlog::trace("Couldn't send query summary!");
|
||||
default:
|
||||
spdlog::trace("Unrecognized signature received (0x{:02X})!", utils::UnderlyingCast(signature));
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (summary.count("has_more") && summary.at("has_more").ValueBool()) {
|
||||
return State::Result;
|
||||
}
|
||||
|
||||
return State::Idle;
|
||||
} catch (const std::exception &e) {
|
||||
return HandleFailure(session, e);
|
||||
}
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
template <typename Session>
|
||||
State HandlePull(Session &session, State state, Marker marker) {
|
||||
return detail::HandlePullDiscard<true>(session, state, marker);
|
||||
}
|
||||
|
||||
template <typename Session>
|
||||
State HandleDiscard(Session &session, State state, Marker marker) {
|
||||
return detail::HandlePullDiscard<false>(session, state, marker);
|
||||
}
|
||||
|
||||
template <typename Session>
|
||||
State HandleReset(Session &session, State, Marker marker) {
|
||||
// IMPORTANT: This implementation of the Bolt RESET command isn't fully
|
||||
// compliant to the protocol definition. In the protocol it is defined
|
||||
// that this command should immediately stop any running commands and
|
||||
// reset the session to a clean state. That means that we should always
|
||||
// make a look-ahead for the RESET command before processing anything.
|
||||
// Our implementation, for now, does everything in a blocking fashion
|
||||
// so we cannot simply "kill" a transaction while it is running. So
|
||||
// now this command only resets the session to a clean state. It
|
||||
// does not IGNORE running and pending commands as it should.
|
||||
if (marker != Marker::TinyStruct) {
|
||||
spdlog::trace("Expected TinyStruct marker, but received 0x{:02X}!", utils::UnderlyingCast(marker));
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
// Clear all pending data and send a success message.
|
||||
session.encoder_buffer_.Clear();
|
||||
|
||||
if (!session.encoder_.MessageSuccess()) {
|
||||
spdlog::trace("Couldn't send success message!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
session.Abort();
|
||||
|
||||
return State::Idle;
|
||||
}
|
||||
|
||||
template <typename Session>
|
||||
State HandleBegin(Session &session, State state, Marker marker) {
|
||||
if (session.version_.major == 1) {
|
||||
spdlog::trace("BEGIN messsage not supported in Bolt v1!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (marker != Marker::TinyStruct1) {
|
||||
spdlog::trace("Expected TinyStruct1 marker, but received 0x{:02x}!", utils::UnderlyingCast(marker));
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
Value extra;
|
||||
if (!session.decoder_.ReadValue(&extra, Value::Type::Map)) {
|
||||
spdlog::trace("Couldn't read extra fields!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (state != State::Idle) {
|
||||
spdlog::trace("Unexpected BEGIN command!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
|
||||
|
||||
if (!session.encoder_.MessageSuccess({})) {
|
||||
spdlog::trace("Couldn't send success message!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
try {
|
||||
session.BeginTransaction();
|
||||
} catch (const std::exception &e) {
|
||||
return HandleFailure(session, e);
|
||||
}
|
||||
|
||||
return State::Idle;
|
||||
}
|
||||
|
||||
template <typename Session>
|
||||
State HandleCommit(Session &session, State state, Marker marker) {
|
||||
if (session.version_.major == 1) {
|
||||
spdlog::trace("COMMIT messsage not supported in Bolt v1!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (marker != Marker::TinyStruct) {
|
||||
spdlog::trace("Expected TinyStruct marker, but received 0x{:02x}!", utils::UnderlyingCast(marker));
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (state != State::Idle) {
|
||||
spdlog::trace("Unexpected COMMIT command!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
|
||||
|
||||
try {
|
||||
if (!session.encoder_.MessageSuccess({})) {
|
||||
spdlog::trace("Couldn't send success message!");
|
||||
return State::Close;
|
||||
}
|
||||
session.CommitTransaction();
|
||||
return State::Idle;
|
||||
} catch (const std::exception &e) {
|
||||
return HandleFailure(session, e);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Session>
|
||||
State HandleRollback(Session &session, State state, Marker marker) {
|
||||
if (session.version_.major == 1) {
|
||||
spdlog::trace("ROLLBACK messsage not supported in Bolt v1!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (marker != Marker::TinyStruct) {
|
||||
spdlog::trace("Expected TinyStruct marker, but received 0x{:02x}!", utils::UnderlyingCast(marker));
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (state != State::Idle) {
|
||||
spdlog::trace("Unexpected ROLLBACK command!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
|
||||
|
||||
try {
|
||||
if (!session.encoder_.MessageSuccess({})) {
|
||||
spdlog::trace("Couldn't send success message!");
|
||||
return State::Close;
|
||||
}
|
||||
session.RollbackTransaction();
|
||||
return State::Idle;
|
||||
} catch (const std::exception &e) {
|
||||
return HandleFailure(session, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,8 +92,8 @@ State HandleRollback(Session &session, State state, Marker marker) {
|
||||
* It executes: RUN, PULL_ALL, DISCARD_ALL & RESET.
|
||||
* @param session the session that should be used for the run
|
||||
*/
|
||||
template <typename Session>
|
||||
State StateExecutingRun(Session &session, State state) {
|
||||
template <typename TSession>
|
||||
State StateExecutingRun(TSession &session, State state) {
|
||||
Marker marker;
|
||||
Signature signature;
|
||||
if (!session.decoder_.ReadMessageHeader(&signature, &marker)) {
|
||||
@@ -368,30 +101,21 @@ State StateExecutingRun(Session &session, State state) {
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (UNLIKELY(signature == Signature::Noop && session.version_.major == 4 && session.version_.minor == 1)) {
|
||||
spdlog::trace("Received NOOP message");
|
||||
return state;
|
||||
}
|
||||
|
||||
if (signature == Signature::Run) {
|
||||
return HandleRun(session, state, marker);
|
||||
} else if (signature == Signature::Pull) {
|
||||
return HandlePull(session, state, marker);
|
||||
} else if (signature == Signature::Discard) {
|
||||
return HandleDiscard(session, state, marker);
|
||||
} else if (signature == Signature::Begin) {
|
||||
return HandleBegin(session, state, marker);
|
||||
} else if (signature == Signature::Commit) {
|
||||
return HandleCommit(session, state, marker);
|
||||
} else if (signature == Signature::Rollback) {
|
||||
return HandleRollback(session, state, marker);
|
||||
} else if (signature == Signature::Reset) {
|
||||
return HandleReset(session, state, marker);
|
||||
} else if (signature == Signature::Goodbye && session.version_.major != 1) {
|
||||
throw SessionClosedException("Closing connection.");
|
||||
} else {
|
||||
spdlog::trace("Unrecognized signature received (0x{:02X})!", utils::UnderlyingCast(signature));
|
||||
return State::Close;
|
||||
switch (session.version_.major) {
|
||||
case 1:
|
||||
return RunHandlerV1(signature, session, state, marker);
|
||||
case 4: {
|
||||
if (session.version_.minor >= 3) {
|
||||
return RunHandlerV4<TSession, 3>(signature, session, state, marker);
|
||||
}
|
||||
if (session.version_.minor >= 1) {
|
||||
return RunHandlerV4<TSession, 1>(signature, session, state, marker);
|
||||
}
|
||||
return RunHandlerV4<TSession>(signature, session, state, marker);
|
||||
}
|
||||
default:
|
||||
spdlog::trace("Unsupported bolt version:{}.{})!", session.version_.major, session.version_.minor);
|
||||
return State::Close;
|
||||
}
|
||||
}
|
||||
} // namespace communication::bolt
|
||||
|
||||
418
src/communication/bolt/v1/states/handlers.hpp
Normal file
418
src/communication/bolt/v1/states/handlers.hpp
Normal file
@@ -0,0 +1,418 @@
|
||||
// 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
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "communication/bolt/v1/codes.hpp"
|
||||
#include "communication/bolt/v1/constants.hpp"
|
||||
#include "communication/bolt/v1/state.hpp"
|
||||
#include "communication/bolt/v1/value.hpp"
|
||||
#include "communication/exceptions.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/message.hpp"
|
||||
|
||||
namespace communication::bolt {
|
||||
// TODO: Revise these error messages
|
||||
inline std::pair<std::string, std::string> ExceptionToErrorMessage(const std::exception &e) {
|
||||
if (const auto *verbose = dynamic_cast<const VerboseError *>(&e)) {
|
||||
return {verbose->code(), verbose->what()};
|
||||
}
|
||||
if (dynamic_cast<const ClientError *>(&e)) {
|
||||
// Clients expect 4 strings separated by dots. First being database name
|
||||
// (for example: Neo, Memgraph...), second being either ClientError,
|
||||
// TransientError or DatabaseError (or ClientNotification for warnings).
|
||||
// ClientError means wrong query, do not retry. DatabaseError means
|
||||
// something wrong in database, do not retry. TransientError means query
|
||||
// failed, but if retried it may succeed, retry it.
|
||||
//
|
||||
// Third and fourth strings being namespace and specific error name.
|
||||
// It is not really important what we put there since we don't expect
|
||||
// any special handling of specific exceptions on client side, but we
|
||||
// need to make sure that we don't accidentally return some exception
|
||||
// name which clients handle in a special way. For example, if client
|
||||
// receives *.TransientError.Transaction.Terminate it will not rerun
|
||||
// query even though TransientError was returned, because of Neo's
|
||||
// semantics of that error.
|
||||
return {"Memgraph.ClientError.MemgraphError.MemgraphError", e.what()};
|
||||
}
|
||||
if (dynamic_cast<const utils::BasicException *>(&e)) {
|
||||
// Exception not derived from QueryException was thrown which means that
|
||||
// database probably aborted transaction because of some timeout,
|
||||
// deadlock, serialization error or something similar. We return
|
||||
// TransientError since retry of same transaction could succeed.
|
||||
return {"Memgraph.TransientError.MemgraphError.MemgraphError", e.what()};
|
||||
}
|
||||
if (dynamic_cast<const std::bad_alloc *>(&e)) {
|
||||
// std::bad_alloc was thrown, God knows in which state is database ->
|
||||
// terminate.
|
||||
LOG_FATAL("Memgraph is out of memory");
|
||||
}
|
||||
// All exceptions used in memgraph are derived from BasicException. Since
|
||||
// we caught some other exception we don't know what is going on. Return
|
||||
// DatabaseError, log real message and return generic string.
|
||||
spdlog::error(utils::MessageWithLink("Unknown exception occurred during query execution {}.", e.what(),
|
||||
"https://memgr.ph/unknown"));
|
||||
return {"Memgraph.DatabaseError.MemgraphError.MemgraphError",
|
||||
"An unknown exception occurred, this is unexpected. Real message "
|
||||
"should be in database logs."};
|
||||
}
|
||||
|
||||
namespace details {
|
||||
|
||||
template <typename TSession>
|
||||
State HandleRun(TSession &session, const State state, const Value &query, const Value ¶ms) {
|
||||
if (state != State::Idle) {
|
||||
// Client could potentially recover if we move to error state, but there is
|
||||
// no legitimate situation in which well working client would end up in this
|
||||
// situation.
|
||||
spdlog::trace("Unexpected RUN command!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
|
||||
|
||||
spdlog::debug("[Run] '{}'", query.ValueString());
|
||||
|
||||
try {
|
||||
// Interpret can throw.
|
||||
const auto [header, qid] = session.Interpret(query.ValueString(), params.ValueMap());
|
||||
// Convert std::string to Value
|
||||
std::vector<Value> vec;
|
||||
std::map<std::string, Value> data;
|
||||
vec.reserve(header.size());
|
||||
for (auto &i : header) vec.emplace_back(std::move(i));
|
||||
data.emplace("fields", std::move(vec));
|
||||
// Send the header.
|
||||
if (!session.encoder_.MessageSuccess(data)) {
|
||||
spdlog::trace("Couldn't send query header!");
|
||||
return State::Close;
|
||||
}
|
||||
return State::Result;
|
||||
} catch (const std::exception &e) {
|
||||
return HandleFailure(session, e);
|
||||
}
|
||||
}
|
||||
|
||||
template <bool is_pull, typename TSession>
|
||||
State HandlePullDiscard(TSession &session, std::optional<int> n, std::optional<int> qid) {
|
||||
try {
|
||||
std::map<std::string, Value> summary;
|
||||
if constexpr (is_pull) {
|
||||
// Pull can throw.
|
||||
summary = session.Pull(&session.encoder_, n, qid);
|
||||
} else {
|
||||
summary = session.Discard(n, qid);
|
||||
}
|
||||
|
||||
if (!session.encoder_.MessageSuccess(summary)) {
|
||||
spdlog::trace("Couldn't send query summary!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (summary.count("has_more") && summary.at("has_more").ValueBool()) {
|
||||
return State::Result;
|
||||
}
|
||||
|
||||
return State::Idle;
|
||||
} catch (const std::exception &e) {
|
||||
return HandleFailure(session, e);
|
||||
}
|
||||
}
|
||||
|
||||
template <bool is_pull, typename TSession>
|
||||
State HandlePullDiscardV1(TSession &session, const State state, const Marker marker) {
|
||||
const auto expected_marker = Marker::TinyStruct;
|
||||
if (marker != expected_marker) {
|
||||
spdlog::trace("Expected {} marker, but received 0x{:02X}!", "TinyStruct", utils::UnderlyingCast(marker));
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (state != State::Result) {
|
||||
if constexpr (is_pull) {
|
||||
spdlog::trace("Unexpected PULL!");
|
||||
} else {
|
||||
spdlog::trace("Unexpected DISCARD!");
|
||||
}
|
||||
// Same as `unexpected RUN` case.
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
return HandlePullDiscard<is_pull, TSession>(session, std::nullopt, std::nullopt);
|
||||
}
|
||||
|
||||
template <bool is_pull, typename TSession>
|
||||
State HandlePullDiscardV4(TSession &session, const State state, const Marker marker) {
|
||||
const auto expected_marker = Marker::TinyStruct1;
|
||||
if (marker != expected_marker) {
|
||||
spdlog::trace("Expected {} marker, but received 0x{:02X}!", "TinyStruct1", utils::UnderlyingCast(marker));
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (state != State::Result) {
|
||||
if constexpr (is_pull) {
|
||||
spdlog::trace("Unexpected PULL!");
|
||||
} else {
|
||||
spdlog::trace("Unexpected DISCARD!");
|
||||
}
|
||||
// Same as `unexpected RUN` case.
|
||||
return State::Close;
|
||||
}
|
||||
std::optional<int> n;
|
||||
std::optional<int> qid;
|
||||
Value extra;
|
||||
if (!session.decoder_.ReadValue(&extra, Value::Type::Map)) {
|
||||
spdlog::trace("Couldn't read extra field!");
|
||||
}
|
||||
const auto &extra_map = extra.ValueMap();
|
||||
if (extra_map.count("n")) {
|
||||
if (const auto n_value = extra_map.at("n").ValueInt(); n_value != kPullAll) {
|
||||
n = n_value;
|
||||
}
|
||||
}
|
||||
|
||||
if (extra_map.count("qid")) {
|
||||
if (const auto qid_value = extra_map.at("qid").ValueInt(); qid_value != kPullLast) {
|
||||
qid = qid_value;
|
||||
}
|
||||
}
|
||||
return HandlePullDiscard<is_pull, TSession>(session, n, qid);
|
||||
}
|
||||
} // namespace details
|
||||
|
||||
template <typename TSession>
|
||||
inline State HandleFailure(TSession &session, const std::exception &e) {
|
||||
spdlog::trace("Error message: {}", e.what());
|
||||
if (const auto *p = dynamic_cast<const utils::StacktraceException *>(&e)) {
|
||||
spdlog::trace("Error trace: {}", p->trace());
|
||||
}
|
||||
session.encoder_buffer_.Clear();
|
||||
auto code_message = ExceptionToErrorMessage(e);
|
||||
bool fail_sent = session.encoder_.MessageFailure({{"code", code_message.first}, {"message", code_message.second}});
|
||||
if (!fail_sent) {
|
||||
spdlog::trace("Couldn't send failure message!");
|
||||
return State::Close;
|
||||
}
|
||||
return State::Error;
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
State HandleRunV1(TSession &session, const State state, const Marker marker) {
|
||||
const auto expected_marker = Marker::TinyStruct2;
|
||||
if (marker != expected_marker) {
|
||||
spdlog::trace("Expected {} marker, but received 0x{:02X}!",
|
||||
session.version_.major == 1 ? "TinyStruct2" : "TinyStruct3", utils::UnderlyingCast(marker));
|
||||
return State::Close;
|
||||
}
|
||||
Value query, params;
|
||||
if (!session.decoder_.ReadValue(&query, Value::Type::String)) {
|
||||
spdlog::trace("Couldn't read query string!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (!session.decoder_.ReadValue(¶ms, Value::Type::Map)) {
|
||||
spdlog::trace("Couldn't read parameters!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
return details::HandleRun(session, state, query, params);
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
State HandleRunV4(TSession &session, const State state, const Marker marker) {
|
||||
const auto expected_marker = Marker::TinyStruct3;
|
||||
if (marker != expected_marker) {
|
||||
spdlog::trace("Expected {} marker, but received 0x{:02X}!", "TinyStruct3", utils::UnderlyingCast(marker));
|
||||
return State::Close;
|
||||
}
|
||||
Value query, params, extra;
|
||||
if (!session.decoder_.ReadValue(&query, Value::Type::String)) {
|
||||
spdlog::trace("Couldn't read query string!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (!session.decoder_.ReadValue(¶ms, Value::Type::Map)) {
|
||||
spdlog::trace("Couldn't read parameters!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
// Even though this part seems unnecessary it is needed to move the buffer
|
||||
if (!session.decoder_.ReadValue(&extra, Value::Type::Map)) {
|
||||
spdlog::trace("Couldn't read extra field!");
|
||||
}
|
||||
|
||||
return details::HandleRun(session, state, query, params);
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
State HandlePullV1(TSession &session, const State state, const Marker marker) {
|
||||
return details::HandlePullDiscardV1<true>(session, state, marker);
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
State HandlePullV4(TSession &session, const State state, const Marker marker) {
|
||||
return details::HandlePullDiscardV4<true>(session, state, marker);
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
State HandleDiscardV1(TSession &session, const State state, const Marker marker) {
|
||||
return details::HandlePullDiscardV1<false>(session, state, marker);
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
State HandleDiscardV4(TSession &session, const State state, const Marker marker) {
|
||||
return details::HandlePullDiscardV4<false>(session, state, marker);
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
State HandleReset(TSession &session, const Marker marker) {
|
||||
// IMPORTANT: This implementation of the Bolt RESET command isn't fully
|
||||
// compliant to the protocol definition. In the protocol it is defined
|
||||
// that this command should immediately stop any running commands and
|
||||
// reset the session to a clean state. That means that we should always
|
||||
// make a look-ahead for the RESET command before processing anything.
|
||||
// Our implementation, for now, does everything in a blocking fashion
|
||||
// so we cannot simply "kill" a transaction while it is running. So
|
||||
// now this command only resets the session to a clean state. It
|
||||
// does not IGNORE running and pending commands as it should.
|
||||
if (marker != Marker::TinyStruct) {
|
||||
spdlog::trace("Expected TinyStruct marker, but received 0x{:02X}!", utils::UnderlyingCast(marker));
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
// Clear all pending data and send a success message.
|
||||
session.encoder_buffer_.Clear();
|
||||
|
||||
if (!session.encoder_.MessageSuccess()) {
|
||||
spdlog::trace("Couldn't send success message!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
session.Abort();
|
||||
|
||||
return State::Idle;
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
State HandleBegin(TSession &session, const State state, const Marker marker) {
|
||||
if (marker != Marker::TinyStruct1) {
|
||||
spdlog::trace("Expected TinyStruct1 marker, but received 0x{:02x}!", utils::UnderlyingCast(marker));
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
Value extra;
|
||||
if (!session.decoder_.ReadValue(&extra, Value::Type::Map)) {
|
||||
spdlog::trace("Couldn't read extra fields!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (state != State::Idle) {
|
||||
spdlog::trace("Unexpected BEGIN command!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
|
||||
|
||||
if (!session.encoder_.MessageSuccess({})) {
|
||||
spdlog::trace("Couldn't send success message!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
try {
|
||||
session.BeginTransaction();
|
||||
} catch (const std::exception &e) {
|
||||
return HandleFailure(session, e);
|
||||
}
|
||||
|
||||
return State::Idle;
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
State HandleCommit(TSession &session, const State state, const Marker marker) {
|
||||
if (marker != Marker::TinyStruct) {
|
||||
spdlog::trace("Expected TinyStruct marker, but received 0x{:02x}!", utils::UnderlyingCast(marker));
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (state != State::Idle) {
|
||||
spdlog::trace("Unexpected COMMIT command!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
|
||||
|
||||
try {
|
||||
if (!session.encoder_.MessageSuccess({})) {
|
||||
spdlog::trace("Couldn't send success message!");
|
||||
return State::Close;
|
||||
}
|
||||
session.CommitTransaction();
|
||||
return State::Idle;
|
||||
} catch (const std::exception &e) {
|
||||
return HandleFailure(session, e);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
State HandleRollback(TSession &session, const State state, const Marker marker) {
|
||||
if (marker != Marker::TinyStruct) {
|
||||
spdlog::trace("Expected TinyStruct marker, but received 0x{:02x}!", utils::UnderlyingCast(marker));
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (state != State::Idle) {
|
||||
spdlog::trace("Unexpected ROLLBACK command!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
|
||||
|
||||
try {
|
||||
if (!session.encoder_.MessageSuccess({})) {
|
||||
spdlog::trace("Couldn't send success message!");
|
||||
return State::Close;
|
||||
}
|
||||
session.RollbackTransaction();
|
||||
return State::Idle;
|
||||
} catch (const std::exception &e) {
|
||||
return HandleFailure(session, e);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
State HandleNoop(const State state) {
|
||||
spdlog::trace("Received NOOP message");
|
||||
return state;
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
State HandleGoodbye() {
|
||||
throw SessionClosedException("Closing connection.");
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
State HandleRoute(TSession &session) {
|
||||
// Route message is not implemented since it is neo4j specific, therefore we
|
||||
// will receive it an inform user that there is no implementation.
|
||||
session.encoder_buffer_.Clear();
|
||||
bool fail_sent =
|
||||
session.encoder_.MessageFailure({{"code", 66}, {"message", "Route message not supported in Memgraph!"}});
|
||||
if (!fail_sent) {
|
||||
spdlog::trace("Couldn't send failure message!");
|
||||
return State::Close;
|
||||
}
|
||||
return State::Error;
|
||||
}
|
||||
} // namespace communication::bolt
|
||||
@@ -12,6 +12,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
|
||||
#include "communication/bolt/v1/codes.hpp"
|
||||
#include "communication/bolt/v1/constants.hpp"
|
||||
@@ -21,6 +24,35 @@
|
||||
|
||||
namespace communication::bolt {
|
||||
|
||||
inline bool CopyProtocolInformationIfSupported(uint16_t version, uint8_t *protocol) {
|
||||
const auto *supported_version = std::find(std::begin(kSupportedVersions), std::end(kSupportedVersions), version);
|
||||
if (supported_version != std::end(kSupportedVersions)) {
|
||||
std::memcpy(protocol, &version, sizeof(version));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool CopyProtocolInformationIfSupportedWithOffset(auto data_position, uint8_t *protocol) {
|
||||
struct bolt_range_version {
|
||||
uint8_t offset;
|
||||
uint8_t minor;
|
||||
uint8_t major;
|
||||
} bolt_range_version;
|
||||
std::memcpy(&bolt_range_version, data_position, sizeof(bolt_range_version));
|
||||
if (bolt_range_version.major == 0 || bolt_range_version.minor == 0) return false;
|
||||
bolt_range_version.offset = std::min(bolt_range_version.offset, bolt_range_version.minor);
|
||||
|
||||
for (uint8_t i{0U}; i <= bolt_range_version.offset; i++) {
|
||||
uint8_t current_minor = bolt_range_version.minor - i;
|
||||
if (CopyProtocolInformationIfSupported(static_cast<uint16_t>((bolt_range_version.major << 8U) + current_minor),
|
||||
protocol)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handshake state run function
|
||||
* This function runs everything to make a Bolt handshake with the client.
|
||||
@@ -29,7 +61,7 @@ namespace communication::bolt {
|
||||
template <typename TSession>
|
||||
State StateHandshakeRun(TSession &session) {
|
||||
auto precmp = std::memcmp(session.input_stream_.data(), kPreamble, sizeof(kPreamble));
|
||||
if (UNLIKELY(precmp != 0)) {
|
||||
if (precmp != 0) [[unlikely]] {
|
||||
spdlog::trace("Received a wrong preamble!");
|
||||
return State::Close;
|
||||
}
|
||||
@@ -37,22 +69,22 @@ State StateHandshakeRun(TSession &session) {
|
||||
DMG_ASSERT(session.input_stream_.size() >= kHandshakeSize, "Wrong size of the handshake data!");
|
||||
|
||||
auto dataPosition = session.input_stream_.data() + sizeof(kPreamble);
|
||||
|
||||
uint8_t protocol[4] = {0x00};
|
||||
for (int i = 0; i < 4 && !protocol[3]; ++i) {
|
||||
dataPosition += 2; // version is defined only by the last 2 bytes
|
||||
|
||||
uint16_t version = 0;
|
||||
for (int i = 0; i < 4 && !protocol[3]; ++i) {
|
||||
// If there is an offset defined (e.g. 0x00 0x03 0x03 0x04) the second byte
|
||||
// That would enable the client to pick between 4.0 and 4.3 versions
|
||||
// as per changes in handshake bolt protocol in v4.3
|
||||
if (CopyProtocolInformationIfSupportedWithOffset(dataPosition + 1, protocol + 2)) break;
|
||||
|
||||
dataPosition += 2; // version is defined only by the last 2 bytes
|
||||
uint16_t version{0};
|
||||
std::memcpy(&version, dataPosition, sizeof(version));
|
||||
if (!version) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (const auto supportedVersion : kSupportedVersions) {
|
||||
if (supportedVersion == version) {
|
||||
std::memcpy(protocol + 2, &version, sizeof(version));
|
||||
break;
|
||||
}
|
||||
if (CopyProtocolInformationIfSupported(version, protocol + 2)) {
|
||||
break;
|
||||
}
|
||||
|
||||
dataPosition += 2;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <optional>
|
||||
|
||||
#include "communication/bolt/v1/codes.hpp"
|
||||
#include "communication/bolt/v1/state.hpp"
|
||||
@@ -22,10 +23,45 @@
|
||||
|
||||
namespace communication::bolt {
|
||||
|
||||
namespace detail {
|
||||
namespace details {
|
||||
template <typename TSession>
|
||||
std::optional<Value> StateInitRunV1(TSession &session, const Marker marker) {
|
||||
if (UNLIKELY(marker != Marker::TinyStruct2)) {
|
||||
std::optional<State> AuthenticateUser(TSession &session, Value &metadata) {
|
||||
// Get authentication data.
|
||||
auto &data = metadata.ValueMap();
|
||||
if (!data.count("scheme")) {
|
||||
spdlog::warn("The client didn't supply authentication information!");
|
||||
return State::Close;
|
||||
}
|
||||
std::string username;
|
||||
std::string password;
|
||||
if (data["scheme"].ValueString() == "basic") {
|
||||
if (!data.count("principal") || !data.count("credentials")) {
|
||||
spdlog::warn("The client didn't supply authentication information!");
|
||||
return State::Close;
|
||||
}
|
||||
username = data["principal"].ValueString();
|
||||
password = data["credentials"].ValueString();
|
||||
} else if (data["scheme"].ValueString() != "none") {
|
||||
spdlog::warn("Unsupported authentication scheme: {}", data["scheme"].ValueString());
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
// Authenticate the user.
|
||||
if (!session.Authenticate(username, password)) {
|
||||
if (!session.encoder_.MessageFailure(
|
||||
{{"code", "Memgraph.ClientError.Security.Unauthenticated"}, {"message", "Authentication failure"}})) {
|
||||
spdlog::trace("Couldn't send failure message to the client!");
|
||||
}
|
||||
// Throw an exception to indicate to the network stack that the session
|
||||
// should be closed and cleaned up.
|
||||
throw SessionClosedException("The client is not authenticated!");
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
std::optional<Value> GetMetadataV1(TSession &session, const Marker marker) {
|
||||
if (marker != Marker::TinyStruct2) [[unlikely]] {
|
||||
spdlog::trace("Expected TinyStruct2 marker, but received 0x{:02X}!", utils::UnderlyingCast(marker));
|
||||
spdlog::trace(
|
||||
"The client sent malformed data, but we are continuing "
|
||||
@@ -53,8 +89,8 @@ std::optional<Value> StateInitRunV1(TSession &session, const Marker marker) {
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
std::optional<Value> StateInitRunV4(TSession &session, const Marker marker) {
|
||||
if (UNLIKELY(marker != Marker::TinyStruct1)) {
|
||||
std::optional<Value> GetMetadataV4(TSession &session, const Marker marker) {
|
||||
if (marker != Marker::TinyStruct1) [[unlikely]] {
|
||||
spdlog::trace("Expected TinyStruct1 marker, but received 0x{:02X}!", utils::UnderlyingCast(marker));
|
||||
spdlog::trace(
|
||||
"The client sent malformed data, but we are continuing "
|
||||
@@ -80,15 +116,79 @@ std::optional<Value> StateInitRunV4(TSession &session, const Marker marker) {
|
||||
|
||||
return metadata;
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
template <typename TSession>
|
||||
State SendSuccessMessage(TSession &session) {
|
||||
// Neo4j's Java driver 4.1.1+ requires connection_id.
|
||||
// The only usage in the mentioned version is for logging purposes.
|
||||
// Because it's not critical for the regular usage of the driver
|
||||
// we send a hardcoded value for now.
|
||||
std::map<std::string, Value> metadata{{"connection_id", "bolt-1"}};
|
||||
if (auto server_name = session.GetServerNameForInit(); server_name) {
|
||||
metadata.insert({"server", *server_name});
|
||||
}
|
||||
bool success_sent = session.encoder_.MessageSuccess(metadata);
|
||||
if (!success_sent) {
|
||||
spdlog::trace("Couldn't send success message to the client!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
return State::Idle;
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
State StateInitRunV1(TSession &session, const Marker marker, const Signature signature) {
|
||||
if (signature != Signature::Init) [[unlikely]] {
|
||||
spdlog::trace("Expected Init signature, but received 0x{:02X}!", utils::UnderlyingCast(signature));
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
auto maybeMetadata = GetMetadataV1(session, marker);
|
||||
|
||||
if (!maybeMetadata) {
|
||||
return State::Close;
|
||||
}
|
||||
if (auto result = AuthenticateUser(session, *maybeMetadata)) {
|
||||
return result.value();
|
||||
}
|
||||
|
||||
return SendSuccessMessage(session);
|
||||
}
|
||||
|
||||
template <typename TSession, int bolt_minor = 0>
|
||||
State StateInitRunV4(TSession &session, Marker marker, Signature signature) {
|
||||
if constexpr (bolt_minor > 0) {
|
||||
if (signature == Signature::Noop) [[unlikely]] {
|
||||
SPDLOG_DEBUG("Received NOOP message");
|
||||
return State::Init;
|
||||
}
|
||||
}
|
||||
|
||||
if (signature != Signature::Init) [[unlikely]] {
|
||||
spdlog::trace("Expected Init signature, but received 0x{:02X}!", utils::UnderlyingCast(signature));
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
auto maybeMetadata = GetMetadataV4(session, marker);
|
||||
|
||||
if (!maybeMetadata) {
|
||||
return State::Close;
|
||||
}
|
||||
if (auto result = AuthenticateUser(session, *maybeMetadata)) {
|
||||
return result.value();
|
||||
}
|
||||
|
||||
return SendSuccessMessage(session);
|
||||
}
|
||||
} // namespace details
|
||||
|
||||
/**
|
||||
* Init state run function.
|
||||
* This function runs everything to initialize a Bolt session with the client.
|
||||
* @param session the session that should be used for the run.
|
||||
*/
|
||||
template <typename Session>
|
||||
State StateInitRun(Session &session) {
|
||||
template <typename TSession>
|
||||
State StateInitRun(TSession &session) {
|
||||
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
|
||||
|
||||
Marker marker;
|
||||
@@ -98,72 +198,18 @@ State StateInitRun(Session &session) {
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (UNLIKELY(signature == Signature::Noop && session.version_.major == 4 && session.version_.minor == 1)) {
|
||||
SPDLOG_DEBUG("Received NOOP message");
|
||||
return State::Init;
|
||||
}
|
||||
|
||||
if (UNLIKELY(signature != Signature::Init)) {
|
||||
spdlog::trace("Expected Init signature, but received 0x{:02X}!", utils::UnderlyingCast(signature));
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
auto maybeMetadata =
|
||||
session.version_.major == 1 ? detail::StateInitRunV1(session, marker) : detail::StateInitRunV4(session, marker);
|
||||
|
||||
if (!maybeMetadata) {
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
// Get authentication data.
|
||||
std::string username;
|
||||
std::string password;
|
||||
auto &data = maybeMetadata->ValueMap();
|
||||
if (!data.count("scheme")) {
|
||||
spdlog::warn("The client didn't supply authentication information!");
|
||||
return State::Close;
|
||||
}
|
||||
if (data["scheme"].ValueString() == "basic") {
|
||||
if (!data.count("principal") || !data.count("credentials")) {
|
||||
spdlog::warn("The client didn't supply authentication information!");
|
||||
return State::Close;
|
||||
switch (session.version_.major) {
|
||||
case 1: {
|
||||
return details::StateInitRunV1<TSession>(session, marker, signature);
|
||||
}
|
||||
username = data["principal"].ValueString();
|
||||
password = data["credentials"].ValueString();
|
||||
} else if (data["scheme"].ValueString() != "none") {
|
||||
spdlog::warn("Unsupported authentication scheme: {}", data["scheme"].ValueString());
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
// Authenticate the user.
|
||||
if (!session.Authenticate(username, password)) {
|
||||
if (!session.encoder_.MessageFailure(
|
||||
{{"code", "Memgraph.ClientError.Security.Unauthenticated"}, {"message", "Authentication failure"}})) {
|
||||
spdlog::trace("Couldn't send failure message to the client!");
|
||||
}
|
||||
// Throw an exception to indicate to the network stack that the session
|
||||
// should be closed and cleaned up.
|
||||
throw SessionClosedException("The client is not authenticated!");
|
||||
}
|
||||
|
||||
// Return success.
|
||||
{
|
||||
bool success_sent = false;
|
||||
// Neo4j's Java driver 4.1.1+ requires connection_id.
|
||||
// The only usage in the mentioned version is for logging purposes.
|
||||
// Because it's not critical for the regular usage of the driver
|
||||
// we send a hardcoded value for now.
|
||||
std::map<std::string, Value> metadata{{"connection_id", "bolt-1"}};
|
||||
if (auto server_name = session.GetServerNameForInit(); server_name) {
|
||||
metadata.insert({"server", *server_name});
|
||||
}
|
||||
success_sent = session.encoder_.MessageSuccess(metadata);
|
||||
if (!success_sent) {
|
||||
spdlog::trace("Couldn't send success message to the client!");
|
||||
return State::Close;
|
||||
case 4: {
|
||||
if (session.version_.minor > 0) {
|
||||
return details::StateInitRunV4<TSession, 1>(session, marker, signature);
|
||||
}
|
||||
return details::StateInitRunV4<TSession>(session, marker, signature);
|
||||
}
|
||||
}
|
||||
|
||||
return State::Idle;
|
||||
spdlog::trace("Unsupported bolt version:{}.{})!", session.version_.major, session.version_.minor);
|
||||
return State::Close;
|
||||
}
|
||||
} // namespace communication::bolt
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "communication/listener.hpp"
|
||||
#include "io/network/socket.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/message.hpp"
|
||||
#include "utils/thread.hpp"
|
||||
|
||||
namespace communication {
|
||||
@@ -88,22 +89,22 @@ class Server final {
|
||||
alive_.store(true);
|
||||
|
||||
if (!socket_.Bind(endpoint_)) {
|
||||
spdlog::error("Cannot bind to socket on {}", endpoint_);
|
||||
spdlog::error(
|
||||
utils::MessageWithLink("Cannot bind to socket on endpoint {}.", endpoint_, "https://memgr.ph/socket"));
|
||||
alive_.store(false);
|
||||
return false;
|
||||
}
|
||||
socket_.SetTimeout(1, 0);
|
||||
if (!socket_.Listen(1024)) {
|
||||
spdlog::error("Cannot listen on socket {}", endpoint_);
|
||||
spdlog::error(utils::MessageWithLink("Cannot listen on socket {}", endpoint_, "https://memgr.ph/socket"));
|
||||
alive_.store(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
listener_.Start();
|
||||
|
||||
std::string service_name(service_name_);
|
||||
thread_ = std::thread([this, service_name]() {
|
||||
utils::ThreadSetName(fmt::format("{} server", service_name));
|
||||
thread_ = std::thread([this]() {
|
||||
utils::ThreadSetName(fmt::format("{} server", service_name_));
|
||||
|
||||
spdlog::info("{} server is fully armed and operational", service_name_);
|
||||
spdlog::info("{} listening on {}", service_name_, socket_.endpoint());
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "io/network/socket.hpp"
|
||||
#include "io/network/stream_buffer.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/message.hpp"
|
||||
#include "utils/on_scope_exit.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
|
||||
@@ -190,10 +191,10 @@ class Session final {
|
||||
throw utils::BasicException(SslGetLastError());
|
||||
} else {
|
||||
// This is a fatal error.
|
||||
spdlog::error(
|
||||
"An unknown error occured while processing SSL message."
|
||||
" Please make sure that you have SSL properly configured on "
|
||||
"the server and the client.");
|
||||
spdlog::error(utils::MessageWithLink(
|
||||
"An unknown error occurred while processing SSL messages. "
|
||||
"Please make sure that you have SSL properly configured on the server and the client.",
|
||||
"https://memgr.ph/ssl"));
|
||||
throw utils::BasicException(SslGetLastError());
|
||||
}
|
||||
} else if (len == 0) {
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
add_subdirectory(kafka)
|
||||
add_subdirectory(pulsar)
|
||||
|
||||
23
src/integrations/constants.hpp
Normal file
23
src/integrations/constants.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
namespace integrations {
|
||||
constexpr int64_t kDefaultCheckBatchLimit{1};
|
||||
constexpr std::chrono::milliseconds kDefaultCheckTimeout{30000};
|
||||
constexpr std::chrono::milliseconds kMinimumInterval{1};
|
||||
constexpr int64_t kMinimumSize{1};
|
||||
const std::string kReducted{"<REDUCTED>"};
|
||||
} // namespace integrations
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include <librdkafka/rdkafkacpp.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include "integrations/constants.hpp"
|
||||
#include "integrations/kafka/exceptions.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
@@ -27,27 +29,19 @@
|
||||
|
||||
namespace integrations::kafka {
|
||||
|
||||
constexpr std::chrono::milliseconds kDefaultBatchInterval{100};
|
||||
constexpr int64_t kDefaultBatchSize = 1000;
|
||||
constexpr int64_t kDefaultCheckBatchLimit = 1;
|
||||
constexpr std::chrono::milliseconds kDefaultCheckTimeout{30000};
|
||||
constexpr std::chrono::milliseconds kMinimumInterval{1};
|
||||
constexpr int64_t kMinimumSize{1};
|
||||
|
||||
namespace {
|
||||
utils::BasicResult<std::string, std::vector<Message>> GetBatch(RdKafka::KafkaConsumer &consumer,
|
||||
const ConsumerInfo &info,
|
||||
std::atomic<bool> &is_running) {
|
||||
std::vector<Message> batch{};
|
||||
|
||||
int64_t batch_size = info.batch_size.value_or(kDefaultBatchSize);
|
||||
batch.reserve(batch_size);
|
||||
batch.reserve(info.batch_size);
|
||||
|
||||
auto remaining_timeout_in_ms = info.batch_interval.value_or(kDefaultBatchInterval).count();
|
||||
auto remaining_timeout_in_ms = info.batch_interval.count();
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
bool run_batch = true;
|
||||
for (int64_t i = 0; remaining_timeout_in_ms > 0 && i < batch_size && is_running.load(); ++i) {
|
||||
for (int64_t i = 0; remaining_timeout_in_ms > 0 && i < info.batch_size && is_running.load(); ++i) {
|
||||
std::unique_ptr<RdKafka::Message> msg(consumer.consume(remaining_timeout_in_ms));
|
||||
switch (msg->err()) {
|
||||
case RdKafka::ERR__TIMED_OUT:
|
||||
@@ -78,7 +72,7 @@ utils::BasicResult<std::string, std::vector<Message>> GetBatch(RdKafka::KafkaCon
|
||||
start = now;
|
||||
}
|
||||
|
||||
return {std::move(batch)};
|
||||
return std::move(batch);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -109,14 +103,22 @@ int64_t Message::Timestamp() const {
|
||||
return rd_kafka_message_timestamp(c_message, nullptr);
|
||||
}
|
||||
|
||||
Consumer::Consumer(const std::string &bootstrap_servers, ConsumerInfo info, ConsumerFunction consumer_function)
|
||||
: info_{std::move(info)}, consumer_function_(std::move(consumer_function)) {
|
||||
int64_t Message::Offset() const {
|
||||
const auto *c_message = message_->c_ptr();
|
||||
return c_message->offset;
|
||||
}
|
||||
|
||||
Consumer::Consumer(ConsumerInfo info, ConsumerFunction consumer_function)
|
||||
: info_{std::move(info)},
|
||||
consumer_function_(std::move(consumer_function)),
|
||||
cb_(info_.consumer_name),
|
||||
offset_cb_(info_.consumer_name) {
|
||||
MG_ASSERT(consumer_function_, "Empty consumer function for Kafka consumer");
|
||||
// NOLINTNEXTLINE (modernize-use-nullptr)
|
||||
if (info.batch_interval.value_or(kMinimumInterval) < kMinimumInterval) {
|
||||
if (info_.batch_interval < kMinimumInterval) {
|
||||
throw ConsumerFailedToInitializeException(info_.consumer_name, "Batch interval has to be positive!");
|
||||
}
|
||||
if (info.batch_size.value_or(kMinimumSize) < kMinimumSize) {
|
||||
if (info_.batch_size < kMinimumSize) {
|
||||
throw ConsumerFailedToInitializeException(info_.consumer_name, "Batch size has to be positive!");
|
||||
}
|
||||
|
||||
@@ -127,10 +129,30 @@ Consumer::Consumer(const std::string &bootstrap_servers, ConsumerInfo info, Cons
|
||||
|
||||
std::string error;
|
||||
|
||||
for (const auto &[key, value] : info_.public_configs) {
|
||||
if (conf->set(key, value, error) != RdKafka::Conf::CONF_OK) {
|
||||
throw SettingCustomConfigFailed(info_.consumer_name, error, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &[key, value] : info_.private_configs) {
|
||||
if (conf->set(key, value, error) != RdKafka::Conf::CONF_OK) {
|
||||
throw SettingCustomConfigFailed(info_.consumer_name, error, key, kReducted);
|
||||
}
|
||||
}
|
||||
|
||||
if (conf->set("event_cb", this, error) != RdKafka::Conf::CONF_OK) {
|
||||
throw ConsumerFailedToInitializeException(info_.consumer_name, error);
|
||||
}
|
||||
|
||||
if (conf->set("rebalance_cb", &cb_, error) != RdKafka::Conf::CONF_OK) {
|
||||
throw ConsumerFailedToInitializeException(info_.consumer_name, error);
|
||||
}
|
||||
|
||||
if (conf->set("offset_commit_cb", &offset_cb_, error) != RdKafka::Conf::CONF_OK) {
|
||||
throw ConsumerFailedToInitializeException(info_.consumer_name, error);
|
||||
}
|
||||
|
||||
if (conf->set("enable.partition.eof", "false", error) != RdKafka::Conf::CONF_OK) {
|
||||
throw ConsumerFailedToInitializeException(info_.consumer_name, error);
|
||||
}
|
||||
@@ -139,7 +161,7 @@ Consumer::Consumer(const std::string &bootstrap_servers, ConsumerInfo info, Cons
|
||||
throw ConsumerFailedToInitializeException(info_.consumer_name, error);
|
||||
}
|
||||
|
||||
if (conf->set("bootstrap.servers", bootstrap_servers, error) != RdKafka::Conf::CONF_OK) {
|
||||
if (conf->set("bootstrap.servers", info_.bootstrap_servers, error) != RdKafka::Conf::CONF_OK) {
|
||||
throw ConsumerFailedToInitializeException(info_.consumer_name, error);
|
||||
}
|
||||
|
||||
@@ -170,7 +192,16 @@ Consumer::Consumer(const std::string &bootstrap_servers, ConsumerInfo info, Cons
|
||||
std::inserter(topic_names_from_metadata, topic_names_from_metadata.begin()),
|
||||
[](const auto topic_metadata) { return topic_metadata->topic(); });
|
||||
|
||||
constexpr size_t max_topic_name_length = 249;
|
||||
constexpr auto is_valid_topic_name = [](const auto c) { return std::isalnum(c) || c == '.' || c == '_' || c == '-'; };
|
||||
|
||||
for (const auto &topic_name : info_.topics) {
|
||||
if (topic_name.size() > max_topic_name_length ||
|
||||
std::any_of(topic_name.begin(), topic_name.end(), [&](const auto c) { return !is_valid_topic_name(c); })) {
|
||||
throw ConsumerFailedToInitializeException(info_.consumer_name,
|
||||
fmt::format("'{}' is an invalid topic name", topic_name));
|
||||
}
|
||||
|
||||
if (!topic_names_from_metadata.contains(topic_name)) {
|
||||
throw TopicNotFoundException(info_.consumer_name, topic_name);
|
||||
}
|
||||
@@ -240,10 +271,18 @@ void Consumer::Check(std::optional<std::chrono::milliseconds> timeout, std::opti
|
||||
utils::OnScopeExit restore_is_running([this] { is_running_.store(false); });
|
||||
|
||||
if (last_assignment_.empty()) {
|
||||
if (const auto err = consumer_->assignment(last_assignment_); err != RdKafka::ERR_NO_ERROR) {
|
||||
spdlog::warn("Saving the commited offset of consumer {} failed: {}", info_.consumer_name, RdKafka::err2str(err));
|
||||
auto throw_consumer_check_failed = [this](const auto err) {
|
||||
throw ConsumerCheckFailedException(info_.consumer_name,
|
||||
fmt::format("Couldn't save commited offsets: '{}'", RdKafka::err2str(err)));
|
||||
};
|
||||
if (const auto err = consumer_->assignment(last_assignment_); err != RdKafka::ERR_NO_ERROR) {
|
||||
spdlog::warn("Saving the assignment of consumer {} failed: {}", info_.consumer_name, RdKafka::err2str(err));
|
||||
throw_consumer_check_failed(err);
|
||||
}
|
||||
if (const auto err = consumer_->position(last_assignment_); err != RdKafka::ERR_NO_ERROR) {
|
||||
spdlog::warn("Saving the position offset assignment of consumer {} failed: {}", info_.consumer_name,
|
||||
RdKafka::err2str(err));
|
||||
throw_consumer_check_failed(err);
|
||||
}
|
||||
} else {
|
||||
if (const auto err = consumer_->assign(last_assignment_); err != RdKafka::ERR_NO_ERROR) {
|
||||
@@ -339,10 +378,23 @@ void Consumer::StartConsuming() {
|
||||
|
||||
try {
|
||||
consumer_function_(batch);
|
||||
if (const auto err = consumer_->commitSync(); err != RdKafka::ERR_NO_ERROR) {
|
||||
std::vector<RdKafka::TopicPartition *> partitions;
|
||||
utils::OnScopeExit clear_partitions([&]() { RdKafka::TopicPartition::destroy(partitions); });
|
||||
|
||||
if (const auto err = consumer_->assignment(partitions); err != RdKafka::ERR_NO_ERROR) {
|
||||
throw ConsumerCheckFailedException(
|
||||
info_.consumer_name, fmt::format("Couldn't get assignment to commit offsets: {}", RdKafka::err2str(err)));
|
||||
}
|
||||
if (const auto err = consumer_->position(partitions); err != RdKafka::ERR_NO_ERROR) {
|
||||
throw ConsumerCheckFailedException(
|
||||
info_.consumer_name, fmt::format("Couldn't get offsets from librdkafka {}", RdKafka::err2str(err)));
|
||||
}
|
||||
spdlog::trace("Got offset positions for {}.", info_.consumer_name);
|
||||
if (const auto err = consumer_->commitAsync(partitions); err != RdKafka::ERR_NO_ERROR) {
|
||||
spdlog::warn("Committing offset of consumer {} failed: {}", info_.consumer_name, RdKafka::err2str(err));
|
||||
break;
|
||||
}
|
||||
spdlog::trace("Requested committing offsets asynchronously for {}.", info_.consumer_name);
|
||||
} catch (const std::exception &e) {
|
||||
spdlog::warn("Error happened in consumer {} while processing a batch: {}!", info_.consumer_name, e.what());
|
||||
break;
|
||||
@@ -358,4 +410,63 @@ void Consumer::StopConsuming() {
|
||||
if (thread_.joinable()) thread_.join();
|
||||
}
|
||||
|
||||
utils::BasicResult<std::string> Consumer::SetConsumerOffsets(int64_t offset) {
|
||||
if (is_running_) {
|
||||
throw ConsumerRunningException(info_.consumer_name);
|
||||
}
|
||||
|
||||
if (offset == -1) {
|
||||
offset = RD_KAFKA_OFFSET_BEGINNING;
|
||||
} else if (offset == -2) {
|
||||
offset = RD_KAFKA_OFFSET_END;
|
||||
}
|
||||
|
||||
cb_.set_offset(offset);
|
||||
if (const auto err = consumer_->subscribe(info_.topics); err != RdKafka::ERR_NO_ERROR) {
|
||||
return fmt::format("Could not set offset of consumer: {}. Error: {}", info_.consumer_name, RdKafka::err2str(err));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Consumer::ConsumerRebalanceCb::ConsumerRebalanceCb(std::string consumer_name)
|
||||
: consumer_name_(std::move(consumer_name)) {}
|
||||
|
||||
void Consumer::ConsumerRebalanceCb::rebalance_cb(RdKafka::KafkaConsumer *consumer, RdKafka::ErrorCode err,
|
||||
std::vector<RdKafka::TopicPartition *> &partitions) {
|
||||
if (err == RdKafka::ERR__REVOKE_PARTITIONS) {
|
||||
consumer->unassign();
|
||||
return;
|
||||
}
|
||||
if (err != RdKafka::ERR__ASSIGN_PARTITIONS) {
|
||||
spdlog::critical("Consumer {} received an unexpected error {}", consumer_name_, RdKafka::err2str(err));
|
||||
return;
|
||||
}
|
||||
if (offset_) {
|
||||
for (auto &partition : partitions) {
|
||||
partition->set_offset(*offset_);
|
||||
}
|
||||
offset_.reset();
|
||||
}
|
||||
auto maybe_error = consumer->assign(partitions);
|
||||
if (maybe_error != RdKafka::ErrorCode::ERR_NO_ERROR) {
|
||||
spdlog::warn("Assigning offset of consumer {} failed: {}", consumer_name_, RdKafka::err2str(maybe_error));
|
||||
}
|
||||
maybe_error = consumer->commitSync(partitions);
|
||||
if (maybe_error != RdKafka::ErrorCode::ERR_NO_ERROR) {
|
||||
spdlog::warn("Commiting offsets of consumer {} failed: {}", consumer_name_, RdKafka::err2str(maybe_error));
|
||||
}
|
||||
}
|
||||
void Consumer::ConsumerRebalanceCb::set_offset(int64_t offset) { offset_ = offset; }
|
||||
|
||||
Consumer::OffsetCommitCb::OffsetCommitCb(std::string consumer_name) : consumer_name_{consumer_name} {};
|
||||
|
||||
void Consumer::OffsetCommitCb::offset_commit_cb(RdKafka::ErrorCode err,
|
||||
std::vector<RdKafka::TopicPartition *> & /*offsets*/) {
|
||||
if (err != RdKafka::ErrorCode::ERR_NO_ERROR) {
|
||||
spdlog::error("Committing offset failed for {} with error \"{}\"", consumer_name_, RdKafka::err2str(err));
|
||||
} else {
|
||||
spdlog::trace("Committing offset succeeded for {}", consumer_name_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace integrations::kafka
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -68,6 +69,9 @@ class Message final {
|
||||
/// can be implemented knowing that.
|
||||
int64_t Timestamp() const;
|
||||
|
||||
/// Returns the offset of the message
|
||||
int64_t Offset() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<RdKafka::Message> message_;
|
||||
};
|
||||
@@ -79,8 +83,11 @@ struct ConsumerInfo {
|
||||
std::string consumer_name;
|
||||
std::vector<std::string> topics;
|
||||
std::string consumer_group;
|
||||
std::optional<std::chrono::milliseconds> batch_interval;
|
||||
std::optional<int64_t> batch_size;
|
||||
std::string bootstrap_servers;
|
||||
std::chrono::milliseconds batch_interval;
|
||||
int64_t batch_size;
|
||||
std::unordered_map<std::string, std::string> public_configs;
|
||||
std::unordered_map<std::string, std::string> private_configs;
|
||||
};
|
||||
|
||||
/// Memgraphs Kafka consumer wrapper.
|
||||
@@ -93,7 +100,7 @@ class Consumer final : public RdKafka::EventCb {
|
||||
///
|
||||
/// @throws ConsumerFailedToInitializeException if the consumer can't connect
|
||||
/// to the Kafka endpoint.
|
||||
Consumer(const std::string &bootstrap_servers, ConsumerInfo info, ConsumerFunction consumer_function);
|
||||
Consumer(ConsumerInfo info, ConsumerFunction consumer_function);
|
||||
~Consumer() override;
|
||||
|
||||
Consumer(const Consumer &other) = delete;
|
||||
@@ -137,6 +144,13 @@ class Consumer final : public RdKafka::EventCb {
|
||||
/// Returns true if the consumer is actively consuming messages.
|
||||
bool IsRunning() const;
|
||||
|
||||
/// Sets the consumer's offset.
|
||||
///
|
||||
/// This function returns the empty string on success or an error message otherwise.
|
||||
///
|
||||
/// @param offset: the offset to set.
|
||||
[[nodiscard]] utils::BasicResult<std::string> SetConsumerOffsets(int64_t offset);
|
||||
|
||||
const ConsumerInfo &Info() const;
|
||||
|
||||
private:
|
||||
@@ -146,6 +160,30 @@ class Consumer final : public RdKafka::EventCb {
|
||||
|
||||
void StopConsuming();
|
||||
|
||||
class ConsumerRebalanceCb : public RdKafka::RebalanceCb {
|
||||
public:
|
||||
ConsumerRebalanceCb(std::string consumer_name);
|
||||
|
||||
void rebalance_cb(RdKafka::KafkaConsumer *consumer, RdKafka::ErrorCode err,
|
||||
std::vector<RdKafka::TopicPartition *> &partitions) override final;
|
||||
|
||||
void set_offset(int64_t offset);
|
||||
|
||||
private:
|
||||
std::optional<int64_t> offset_;
|
||||
std::string consumer_name_;
|
||||
};
|
||||
|
||||
class OffsetCommitCb : public RdKafka::OffsetCommitCb {
|
||||
public:
|
||||
explicit OffsetCommitCb(std::string consumer_name);
|
||||
|
||||
void offset_commit_cb(RdKafka::ErrorCode err, std::vector<RdKafka::TopicPartition *> &offsets) final;
|
||||
|
||||
private:
|
||||
std::string consumer_name_;
|
||||
};
|
||||
|
||||
ConsumerInfo info_;
|
||||
ConsumerFunction consumer_function_;
|
||||
mutable std::atomic<bool> is_running_{false};
|
||||
@@ -153,5 +191,7 @@ class Consumer final : public RdKafka::EventCb {
|
||||
std::optional<int64_t> limit_batches_{std::nullopt};
|
||||
std::unique_ptr<RdKafka::KafkaConsumer, std::function<void(RdKafka::KafkaConsumer *)>> consumer_;
|
||||
std::thread thread_;
|
||||
ConsumerRebalanceCb cb_;
|
||||
OffsetCommitCb offset_cb_;
|
||||
};
|
||||
} // namespace integrations::kafka
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "utils/exceptions.hpp"
|
||||
|
||||
@@ -22,37 +22,46 @@ class KafkaStreamException : public utils::BasicException {
|
||||
|
||||
class ConsumerFailedToInitializeException : public KafkaStreamException {
|
||||
public:
|
||||
ConsumerFailedToInitializeException(const std::string &consumer_name, const std::string &error)
|
||||
ConsumerFailedToInitializeException(std::string_view consumer_name, std::string_view error)
|
||||
: KafkaStreamException("Failed to initialize Kafka consumer {} : {}", consumer_name, error) {}
|
||||
};
|
||||
|
||||
class SettingCustomConfigFailed : public ConsumerFailedToInitializeException {
|
||||
public:
|
||||
SettingCustomConfigFailed(std::string_view consumer_name, std::string_view error, std::string_view key,
|
||||
std::string_view value)
|
||||
: ConsumerFailedToInitializeException(
|
||||
consumer_name,
|
||||
fmt::format(R"(failed to set custom config ("{}": "{}"), because of error {})", key, value, error)) {}
|
||||
};
|
||||
|
||||
class ConsumerRunningException : public KafkaStreamException {
|
||||
public:
|
||||
explicit ConsumerRunningException(const std::string &consumer_name)
|
||||
explicit ConsumerRunningException(std::string_view consumer_name)
|
||||
: KafkaStreamException("Kafka consumer {} is already running", consumer_name) {}
|
||||
};
|
||||
|
||||
class ConsumerStoppedException : public KafkaStreamException {
|
||||
public:
|
||||
explicit ConsumerStoppedException(const std::string &consumer_name)
|
||||
explicit ConsumerStoppedException(std::string_view consumer_name)
|
||||
: KafkaStreamException("Kafka consumer {} is already stopped", consumer_name) {}
|
||||
};
|
||||
|
||||
class ConsumerCheckFailedException : public KafkaStreamException {
|
||||
public:
|
||||
explicit ConsumerCheckFailedException(const std::string &consumer_name, const std::string &error)
|
||||
explicit ConsumerCheckFailedException(std::string_view consumer_name, std::string_view error)
|
||||
: KafkaStreamException("Kafka consumer {} check failed: {}", consumer_name, error) {}
|
||||
};
|
||||
|
||||
class ConsumerStartFailedException : public KafkaStreamException {
|
||||
public:
|
||||
explicit ConsumerStartFailedException(const std::string &consumer_name, const std::string &error)
|
||||
explicit ConsumerStartFailedException(std::string_view consumer_name, std::string_view error)
|
||||
: KafkaStreamException("Starting Kafka consumer {} failed: {}", consumer_name, error) {}
|
||||
};
|
||||
|
||||
class TopicNotFoundException : public KafkaStreamException {
|
||||
public:
|
||||
TopicNotFoundException(const std::string &consumer_name, const std::string &topic_name)
|
||||
TopicNotFoundException(std::string_view consumer_name, std::string_view topic_name)
|
||||
: KafkaStreamException("Kafka consumer {} cannot find topic {}", consumer_name, topic_name) {}
|
||||
};
|
||||
} // namespace integrations::kafka
|
||||
|
||||
8
src/integrations/pulsar/CMakeLists.txt
Normal file
8
src/integrations/pulsar/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
set(integrations_pulsar_src_files
|
||||
consumer.cpp
|
||||
)
|
||||
|
||||
find_package(CURL REQUIRED)
|
||||
|
||||
add_library(mg-integrations-pulsar STATIC ${integrations_pulsar_src_files})
|
||||
target_link_libraries(mg-integrations-pulsar mg-utils pulsar Threads::Threads ${CURL_LIBRARIES})
|
||||
287
src/integrations/pulsar/consumer.cpp
Normal file
287
src/integrations/pulsar/consumer.cpp
Normal file
@@ -0,0 +1,287 @@
|
||||
// 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
|
||||
// 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 "integrations/pulsar/consumer.hpp"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <pulsar/Client.h>
|
||||
#include <pulsar/InitialPosition.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "integrations/constants.hpp"
|
||||
#include "integrations/pulsar/exceptions.hpp"
|
||||
#include "utils/concepts.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/on_scope_exit.hpp"
|
||||
#include "utils/result.hpp"
|
||||
#include "utils/thread.hpp"
|
||||
|
||||
namespace integrations::pulsar {
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
concept PulsarConsumer = utils::SameAsAnyOf<T, pulsar_client::Consumer, pulsar_client::Reader>;
|
||||
|
||||
pulsar_client::Result ConsumeMessage(pulsar_client::Consumer &consumer, pulsar_client::Message &message,
|
||||
int remaining_timeout_in_ms) {
|
||||
return consumer.receive(message, remaining_timeout_in_ms);
|
||||
}
|
||||
|
||||
pulsar_client::Result ConsumeMessage(pulsar_client::Reader &reader, pulsar_client::Message &message,
|
||||
int remaining_timeout_in_ms) {
|
||||
return reader.readNext(message, remaining_timeout_in_ms);
|
||||
}
|
||||
|
||||
template <PulsarConsumer TConsumer>
|
||||
utils::BasicResult<std::string, std::vector<Message>> GetBatch(TConsumer &consumer, const ConsumerInfo &info,
|
||||
std::atomic<bool> &is_running,
|
||||
const pulsar_client::MessageId &last_message_id) {
|
||||
std::vector<Message> batch{};
|
||||
|
||||
batch.reserve(info.batch_size);
|
||||
|
||||
auto remaining_timeout_in_ms = info.batch_interval.count();
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
while (remaining_timeout_in_ms > 0 && batch.size() < info.batch_size && is_running) {
|
||||
pulsar_client::Message message;
|
||||
const auto result = ConsumeMessage(consumer, message, remaining_timeout_in_ms);
|
||||
switch (result) {
|
||||
case pulsar_client::Result::ResultTimeout:
|
||||
return std::move(batch);
|
||||
case pulsar_client::Result::ResultOk:
|
||||
if (message.getMessageId() != last_message_id) {
|
||||
batch.emplace_back(Message{std::move(message)});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
spdlog::warn(fmt::format("Unexpected error while consuming message from consumer {}, error: {}",
|
||||
info.consumer_name, result));
|
||||
return {pulsar_client::strResult(result)};
|
||||
}
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto took = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
|
||||
remaining_timeout_in_ms = remaining_timeout_in_ms - took.count();
|
||||
start = now;
|
||||
}
|
||||
|
||||
return std::move(batch);
|
||||
}
|
||||
|
||||
class SpdlogLogger : public pulsar_client::Logger {
|
||||
bool isEnabled(Level /*level*/) override { return spdlog::should_log(spdlog::level::trace); }
|
||||
|
||||
void log(Level /*level*/, int /*line*/, const std::string &message) override {
|
||||
spdlog::trace("[Pulsar] {}", message);
|
||||
}
|
||||
};
|
||||
|
||||
class SpdlogLoggerFactory : public pulsar_client::LoggerFactory {
|
||||
pulsar_client::Logger *getLogger(const std::string & /*file_name*/) override { return new SpdlogLogger; }
|
||||
};
|
||||
|
||||
pulsar_client::Client CreateClient(const std::string &service_url) {
|
||||
pulsar_client::ClientConfiguration conf;
|
||||
conf.setLogger(new SpdlogLoggerFactory);
|
||||
return {service_url, conf};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Message::Message(pulsar_client::Message &&message) : message_{std::move(message)} {}
|
||||
|
||||
std::span<const char> Message::Payload() const {
|
||||
return {static_cast<const char *>(message_.getData()), message_.getLength()};
|
||||
}
|
||||
|
||||
std::string_view Message::TopicName() const { return message_.getTopicName(); }
|
||||
|
||||
Consumer::Consumer(ConsumerInfo info, ConsumerFunction consumer_function)
|
||||
: info_{std::move(info)},
|
||||
client_{CreateClient(info_.service_url)},
|
||||
consumer_function_{std::move(consumer_function)} {
|
||||
pulsar_client::ConsumerConfiguration config;
|
||||
config.setSubscriptionInitialPosition(pulsar_client::InitialPositionLatest);
|
||||
config.setConsumerType(pulsar_client::ConsumerType::ConsumerExclusive);
|
||||
if (pulsar_client::Result result = client_.subscribe(info_.topics, info_.consumer_name, config, consumer_);
|
||||
result != pulsar_client::ResultOk) {
|
||||
throw ConsumerFailedToInitializeException(info_.consumer_name, pulsar_client::strResult(result));
|
||||
}
|
||||
}
|
||||
Consumer::~Consumer() {
|
||||
StopIfRunning();
|
||||
consumer_.close();
|
||||
client_.close();
|
||||
}
|
||||
|
||||
bool Consumer::IsRunning() const { return is_running_; }
|
||||
|
||||
const ConsumerInfo &Consumer::Info() const { return info_; }
|
||||
|
||||
void Consumer::Start() {
|
||||
if (is_running_) {
|
||||
throw ConsumerRunningException(info_.consumer_name);
|
||||
}
|
||||
|
||||
StartConsuming();
|
||||
}
|
||||
|
||||
void Consumer::Stop() {
|
||||
if (!is_running_) {
|
||||
throw ConsumerStoppedException(info_.consumer_name);
|
||||
}
|
||||
StopConsuming();
|
||||
}
|
||||
|
||||
void Consumer::StopIfRunning() {
|
||||
if (is_running_) {
|
||||
StopConsuming();
|
||||
}
|
||||
|
||||
if (thread_.joinable()) {
|
||||
thread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
void Consumer::Check(std::optional<std::chrono::milliseconds> timeout, std::optional<int64_t> limit_batches,
|
||||
const ConsumerFunction &check_consumer_function) const {
|
||||
// NOLINTNEXTLINE (modernize-use-nullptr)
|
||||
if (timeout.value_or(kMinimumInterval) < kMinimumInterval) {
|
||||
throw ConsumerCheckFailedException(info_.consumer_name, "Timeout has to be positive!");
|
||||
}
|
||||
if (limit_batches.value_or(kMinimumSize) < kMinimumSize) {
|
||||
throw ConsumerCheckFailedException(info_.consumer_name, "Batch limit has to be positive!");
|
||||
}
|
||||
// The implementation of this function is questionable: it is const qualified, though it changes the inner state of
|
||||
// PulsarConsumer. Though it changes the inner state, it saves the current assignment for future Check/Start calls to
|
||||
// restore the current state, so the changes made by this function shouldn't be visible for the users of the class. It
|
||||
// also passes a non const reference of PulsarConsumer to GetBatch function. That means the object is bitwise const
|
||||
// (PulsarConsumer is stored in unique_ptr) and internally mostly synchronized. Mostly, because as Start/Stop requires
|
||||
// exclusive access to consumer, so we don't have to deal with simultaneous calls to those functions. The only concern
|
||||
// in this function is to prevent executing this function on multiple threads simultaneously.
|
||||
if (is_running_.exchange(true)) {
|
||||
throw ConsumerRunningException(info_.consumer_name);
|
||||
}
|
||||
|
||||
utils::OnScopeExit restore_is_running([this] { is_running_.store(false); });
|
||||
|
||||
const auto num_of_batches = limit_batches.value_or(kDefaultCheckBatchLimit);
|
||||
const auto timeout_to_use = timeout.value_or(kDefaultCheckTimeout);
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
|
||||
if (info_.topics.size() != 1) {
|
||||
throw ConsumerCheckFailedException(info_.consumer_name, "Check cannot be used for consumers with multiple topics.");
|
||||
}
|
||||
|
||||
std::vector<std::string> partitions;
|
||||
const auto &topic = info_.topics.front();
|
||||
client_.getPartitionsForTopic(topic, partitions);
|
||||
if (partitions.size() > 1) {
|
||||
throw ConsumerCheckFailedException(info_.consumer_name, "Check cannot be used for topics with multiple partitions");
|
||||
}
|
||||
pulsar_client::Reader reader;
|
||||
client_.createReader(topic, last_message_id_, {}, reader);
|
||||
for (int64_t i = 0; i < num_of_batches;) {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
// NOLINTNEXTLINE (modernize-use-nullptr)
|
||||
if (now - start >= timeout_to_use) {
|
||||
throw ConsumerCheckFailedException(info_.consumer_name, "Timeout reached");
|
||||
}
|
||||
|
||||
auto maybe_batch = GetBatch(reader, info_, is_running_, last_message_id_);
|
||||
|
||||
if (maybe_batch.HasError()) {
|
||||
throw ConsumerCheckFailedException(info_.consumer_name, maybe_batch.GetError());
|
||||
}
|
||||
|
||||
const auto &batch = maybe_batch.GetValue();
|
||||
|
||||
if (batch.empty()) {
|
||||
continue;
|
||||
}
|
||||
++i;
|
||||
|
||||
try {
|
||||
check_consumer_function(batch);
|
||||
} catch (const std::exception &e) {
|
||||
spdlog::warn("Pulsar consumer {} check failed with error {}", info_.consumer_name, e.what());
|
||||
throw ConsumerCheckFailedException(info_.consumer_name, e.what());
|
||||
}
|
||||
}
|
||||
reader.close();
|
||||
}
|
||||
|
||||
void Consumer::StartConsuming() {
|
||||
MG_ASSERT(!is_running_, "Cannot start already running consumer!");
|
||||
if (thread_.joinable()) {
|
||||
thread_.join();
|
||||
}
|
||||
|
||||
is_running_.store(true);
|
||||
|
||||
thread_ = std::thread([this] {
|
||||
constexpr auto kMaxThreadNameSize = utils::GetMaxThreadNameSize();
|
||||
const auto full_thread_name = "Cons#" + info_.consumer_name;
|
||||
|
||||
utils::ThreadSetName(full_thread_name.substr(0, kMaxThreadNameSize));
|
||||
|
||||
while (is_running_) {
|
||||
auto maybe_batch = GetBatch(consumer_, info_, is_running_, last_message_id_);
|
||||
|
||||
if (maybe_batch.HasError()) {
|
||||
spdlog::warn("Error happened in consumer {} while fetching messages: {}!", info_.consumer_name,
|
||||
maybe_batch.GetError());
|
||||
break;
|
||||
}
|
||||
|
||||
const auto &batch = maybe_batch.GetValue();
|
||||
|
||||
if (batch.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
spdlog::info("Pulsar consumer {} is processing a batch", info_.consumer_name);
|
||||
|
||||
try {
|
||||
consumer_function_(batch);
|
||||
|
||||
if (std::any_of(batch.begin(), batch.end(), [&](const auto &message) {
|
||||
if (const auto result = consumer_.acknowledge(message.message_); result != pulsar_client::ResultOk) {
|
||||
spdlog::warn("Acknowledging a message of consumer {} failed: {}", info_.consumer_name, result);
|
||||
return true;
|
||||
}
|
||||
last_message_id_ = message.message_.getMessageId();
|
||||
return false;
|
||||
})) {
|
||||
break;
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
spdlog::warn("Error happened in consumer {} while processing a batch: {}!", info_.consumer_name, e.what());
|
||||
break;
|
||||
}
|
||||
|
||||
spdlog::info("Pulsar consumer {} finished processing", info_.consumer_name);
|
||||
}
|
||||
is_running_.store(false);
|
||||
});
|
||||
}
|
||||
|
||||
void Consumer::StopConsuming() {
|
||||
is_running_.store(false);
|
||||
if (thread_.joinable()) {
|
||||
thread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace integrations::pulsar
|
||||
82
src/integrations/pulsar/consumer.hpp
Normal file
82
src/integrations/pulsar/consumer.hpp
Normal file
@@ -0,0 +1,82 @@
|
||||
// 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
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
#include <atomic>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <thread>
|
||||
|
||||
#include <pulsar/Client.h>
|
||||
|
||||
namespace integrations::pulsar {
|
||||
|
||||
namespace pulsar_client = ::pulsar;
|
||||
|
||||
class Consumer;
|
||||
|
||||
class Message final {
|
||||
public:
|
||||
explicit Message(pulsar_client::Message &&message);
|
||||
|
||||
std::span<const char> Payload() const;
|
||||
std::string_view TopicName() const;
|
||||
|
||||
private:
|
||||
pulsar_client::Message message_;
|
||||
|
||||
friend Consumer;
|
||||
};
|
||||
|
||||
using ConsumerFunction = std::function<void(const std::vector<Message> &)>;
|
||||
|
||||
struct ConsumerInfo {
|
||||
int64_t batch_size;
|
||||
std::chrono::milliseconds batch_interval;
|
||||
std::vector<std::string> topics;
|
||||
std::string consumer_name;
|
||||
std::string service_url;
|
||||
};
|
||||
|
||||
class Consumer final {
|
||||
public:
|
||||
Consumer(ConsumerInfo info, ConsumerFunction consumer_function);
|
||||
~Consumer();
|
||||
|
||||
Consumer(const Consumer &) = delete;
|
||||
Consumer(Consumer &&) noexcept = delete;
|
||||
Consumer &operator=(const Consumer &) = delete;
|
||||
Consumer &operator=(Consumer &&) = delete;
|
||||
|
||||
bool IsRunning() const;
|
||||
void Start();
|
||||
void Stop();
|
||||
void StopIfRunning();
|
||||
|
||||
void Check(std::optional<std::chrono::milliseconds> timeout, std::optional<int64_t> limit_batches,
|
||||
const ConsumerFunction &check_consumer_function) const;
|
||||
|
||||
const ConsumerInfo &Info() const;
|
||||
|
||||
private:
|
||||
void StartConsuming();
|
||||
void StopConsuming();
|
||||
|
||||
ConsumerInfo info_;
|
||||
mutable pulsar_client::Client client_;
|
||||
pulsar_client::Consumer consumer_;
|
||||
ConsumerFunction consumer_function_;
|
||||
|
||||
mutable std::atomic<bool> is_running_{false};
|
||||
pulsar_client::MessageId last_message_id_{pulsar_client::MessageId::earliest()};
|
||||
std::thread thread_;
|
||||
};
|
||||
} // namespace integrations::pulsar
|
||||
58
src/integrations/pulsar/exceptions.hpp
Normal file
58
src/integrations/pulsar/exceptions.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
// 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
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "utils/exceptions.hpp"
|
||||
|
||||
namespace integrations::pulsar {
|
||||
class PulsarStreamException : public utils::BasicException {
|
||||
using utils::BasicException::BasicException;
|
||||
};
|
||||
|
||||
class ConsumerFailedToInitializeException : public PulsarStreamException {
|
||||
public:
|
||||
ConsumerFailedToInitializeException(const std::string &consumer_name, const std::string &error)
|
||||
: PulsarStreamException("Failed to initialize Pulsar consumer {} : {}", consumer_name, error) {}
|
||||
};
|
||||
|
||||
class ConsumerRunningException : public PulsarStreamException {
|
||||
public:
|
||||
explicit ConsumerRunningException(const std::string &consumer_name)
|
||||
: PulsarStreamException("Pulsar consumer {} is already running", consumer_name) {}
|
||||
};
|
||||
|
||||
class ConsumerStoppedException : public PulsarStreamException {
|
||||
public:
|
||||
explicit ConsumerStoppedException(const std::string &consumer_name)
|
||||
: PulsarStreamException("Pulsar consumer {} is already stopped", consumer_name) {}
|
||||
};
|
||||
|
||||
class ConsumerCheckFailedException : public PulsarStreamException {
|
||||
public:
|
||||
explicit ConsumerCheckFailedException(const std::string &consumer_name, const std::string &error)
|
||||
: PulsarStreamException("Pulsar consumer {} check failed: {}", consumer_name, error) {}
|
||||
};
|
||||
|
||||
class ConsumerStartFailedException : public PulsarStreamException {
|
||||
public:
|
||||
explicit ConsumerStartFailedException(const std::string &consumer_name, const std::string &error)
|
||||
: PulsarStreamException("Starting Pulsar consumer {} failed: {}", consumer_name, error) {}
|
||||
};
|
||||
|
||||
class TopicNotFoundException : public PulsarStreamException {
|
||||
public:
|
||||
TopicNotFoundException(const std::string &consumer_name, const std::string &topic_name)
|
||||
: PulsarStreamException("Pulsar consumer {} cannot find topic {}", consumer_name, topic_name) {}
|
||||
};
|
||||
} // namespace integrations::pulsar
|
||||
@@ -4,5 +4,8 @@ set(io_src_files
|
||||
network/socket.cpp
|
||||
network/utils.cpp)
|
||||
|
||||
find_package(fmt REQUIRED)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
add_library(mg-io STATIC ${io_src_files})
|
||||
target_link_libraries(mg-io stdc++fs Threads::Threads fmt mg-utils)
|
||||
target_link_libraries(mg-io stdc++fs Threads::Threads fmt::fmt mg-utils)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "io/network/network_error.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/message.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace io::network {
|
||||
@@ -65,15 +66,17 @@ std::optional<std::pair<std::string, uint16_t>> Endpoint::ParseSocketOrIpAddress
|
||||
try {
|
||||
int_port = utils::ParseInt(parts[1]);
|
||||
} catch (utils::BasicException &e) {
|
||||
spdlog::error("Invalid port number: {}", parts[1]);
|
||||
spdlog::error(utils::MessageWithLink("Invalid port number {}.", parts[1], "https://memgr.ph/ports"));
|
||||
return std::nullopt;
|
||||
}
|
||||
if (int_port < 0) {
|
||||
spdlog::error("Port number must be a positive integer!");
|
||||
spdlog::error(utils::MessageWithLink("Invalid port number {}. The port number must be a positive integer.",
|
||||
int_port, "https://memgr.ph/ports"));
|
||||
return std::nullopt;
|
||||
}
|
||||
if (int_port > std::numeric_limits<uint16_t>::max()) {
|
||||
spdlog::error("Port number exceeded maximum possible size!");
|
||||
spdlog::error(utils::MessageWithLink("Invalid port number. The port number exceedes the maximum possible size.",
|
||||
"https://memgr.ph/ports"));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
find_package(gflags REQUIRED)
|
||||
find_package(BZip2 REQUIRED)
|
||||
find_package(ZLIB REQUIRED)
|
||||
|
||||
# STATIC library used to store key-value pairs
|
||||
add_library(mg-kvstore STATIC kvstore.cpp)
|
||||
target_link_libraries(mg-kvstore stdc++fs mg-utils rocksdb bzip2 zlib gflags)
|
||||
target_link_libraries(mg-kvstore stdc++fs mg-utils rocksdb BZip2::BZip2 ZLIB::ZLIB gflags)
|
||||
|
||||
# STATIC library for dummy key-value storage
|
||||
# add_library(mg-kvstore-dummy STATIC kvstore_dummy.cpp)
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
|
||||
# Don't forget to repeat this list below in `define_add_lcp`.
|
||||
set(lcp_src_files
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/lcp.asd
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/compile-lcp
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/package.lisp
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/names.lisp
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/types.lisp
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/clone.lisp
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/code-gen.lisp
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/slk.lisp
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/lcp.lisp
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/debug.lisp
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/test.lisp
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/util.lisp
|
||||
${CMAKE_SOURCE_DIR}/tools/lcp)
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/lcp.asd
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/compile-lcp
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/package.lisp
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/names.lisp
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/types.lisp
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/clone.lisp
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/code-gen.lisp
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/slk.lisp
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/lcp.lisp
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/debug.lisp
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/test.lisp
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/util.lisp
|
||||
${PROJECT_SOURCE_DIR}/tools/lcp)
|
||||
|
||||
# Make `lcp_src_files` a persistent (cache) variable so that
|
||||
# tests/unit/CMakeLists.txt can see it.
|
||||
@@ -65,21 +65,21 @@ macro(define_add_lcp name main_src_files generated_lcp_files)
|
||||
# Repeat the `lcp_src_files` because this is a macro and the variable is
|
||||
# not visible when invoked in another file.
|
||||
set(lcp_src_files
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/lcp.asd
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/compile-lcp
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/package.lisp
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/names.lisp
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/types.lisp
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/clone.lisp
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/code-gen.lisp
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/slk.lisp
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/lcp.lisp
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/debug.lisp
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/test.lisp
|
||||
${CMAKE_SOURCE_DIR}/src/lisp/util.lisp
|
||||
${CMAKE_SOURCE_DIR}/tools/lcp)
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/lcp.asd
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/compile-lcp
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/package.lisp
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/names.lisp
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/types.lisp
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/clone.lisp
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/code-gen.lisp
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/slk.lisp
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/lcp.lisp
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/debug.lisp
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/test.lisp
|
||||
${PROJECT_SOURCE_DIR}/src/lisp/util.lisp
|
||||
${PROJECT_SOURCE_DIR}/tools/lcp)
|
||||
add_custom_command(OUTPUT ${h_file} ${cpp_file}
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/tools/lcp ${lcp_file} ${slk_serialize}
|
||||
COMMAND ${PROJECT_SOURCE_DIR}/tools/lcp ${lcp_file} ${slk_serialize}
|
||||
VERBATIM
|
||||
DEPENDS ${lcp_src_files} lcp ${lcp_file}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -53,6 +53,7 @@
|
||||
#include "utils/license.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
#include "utils/message.hpp"
|
||||
#include "utils/readable_size.hpp"
|
||||
#include "utils/rw_lock.hpp"
|
||||
#include "utils/settings.hpp"
|
||||
@@ -185,9 +186,21 @@ DEFINE_bool(telemetry_enabled, false,
|
||||
"the database runtime (vertex and edge counts and resource usage) "
|
||||
"to allow for easier improvement of the product.");
|
||||
|
||||
// Streams flags
|
||||
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_uint32(
|
||||
stream_transaction_conflict_retries, 30,
|
||||
"Number of times to retry when a stream transformation fails to commit because of conflicting transactions");
|
||||
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_uint32(
|
||||
stream_transaction_retry_interval, 500,
|
||||
"Retry interval in milliseconds when a stream transformation fails to commit because of conflicting transactions");
|
||||
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(kafka_bootstrap_servers, "",
|
||||
"List of Kafka brokers as a comma separated list of broker host or host:port.");
|
||||
"List of default Kafka brokers as a comma separated list of broker host or host:port.");
|
||||
|
||||
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(pulsar_service_url, "", "Default URL used while connecting to Pulsar brokers.");
|
||||
|
||||
// Audit logging flags.
|
||||
#ifdef MG_ENTERPRISE
|
||||
@@ -206,11 +219,19 @@ DEFINE_double(query_execution_timeout_sec, 600,
|
||||
"Maximum allowed query execution time. Queries exceeding this "
|
||||
"limit will be aborted. Value of 0 means no limit.");
|
||||
|
||||
DEFINE_VALIDATED_uint64(after_commit_trigger_pool_size, 1, "Number of threads to process after commit triggers.", {
|
||||
if (value == 0) {
|
||||
std::cout << "At least one thread is required for processing after commit triggers!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_uint64(
|
||||
memory_limit, 0,
|
||||
"Total memory limit in MiB. Set to 0 to use the default values which are 100\% of the phyisical memory if the swap "
|
||||
"is enabled and 90\% of the physical memory otherwise.");
|
||||
DEFINE_uint64(memory_limit, 0,
|
||||
"Total memory limit in MiB. Set to 0 to use the default values which are 100\% of the phyisical "
|
||||
"memory if the swap "
|
||||
"is enabled and 90\% of the physical memory otherwise.");
|
||||
|
||||
namespace {
|
||||
using namespace std::literals;
|
||||
@@ -998,13 +1019,16 @@ int main(int argc, char **argv) {
|
||||
auto gil = py::EnsureGIL();
|
||||
auto maybe_exc = py::AppendToSysPath(py_support_dir.c_str());
|
||||
if (maybe_exc) {
|
||||
spdlog::error("Unable to load support for embedded Python: {}", *maybe_exc);
|
||||
spdlog::error(utils::MessageWithLink("Unable to load support for embedded Python: {}.", *maybe_exc,
|
||||
"https://memgr.ph/python"));
|
||||
}
|
||||
} else {
|
||||
spdlog::error("Unable to load support for embedded Python: missing directory {}", py_support_dir);
|
||||
spdlog::error(utils::MessageWithLink("Unable to load support for embedded Python: missing directory {}.",
|
||||
py_support_dir, "https://memgr.ph/python"));
|
||||
}
|
||||
} catch (const std::filesystem::filesystem_error &e) {
|
||||
spdlog::error("Unable to load support for embedded Python: {}", e.what());
|
||||
spdlog::error(
|
||||
utils::MessageWithLink("Unable to load support for embedded Python: {}.", e.what(), "https://memgr.ph/python"));
|
||||
}
|
||||
|
||||
// Initialize the communication library.
|
||||
@@ -1021,7 +1045,8 @@ int main(int argc, char **argv) {
|
||||
mem_log_scheduler.Run("Memory warning", std::chrono::seconds(3), [] {
|
||||
auto free_ram = utils::sysinfo::AvailableMemory();
|
||||
if (free_ram && *free_ram / 1024 < FLAGS_memory_warning_threshold)
|
||||
spdlog::warn("Running out of available RAM, only {} MB left", *free_ram / 1024);
|
||||
spdlog::warn(utils::MessageWithLink("Running out of available RAM, only {} MB left.", *free_ram / 1024,
|
||||
"https://memgr.ph/ram"));
|
||||
});
|
||||
} else {
|
||||
// Kernel version for the `MemAvailable` value is from: man procfs
|
||||
@@ -1033,6 +1058,7 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
std::cout << "You are running Memgraph v" << gflags::VersionString() << std::endl;
|
||||
std::cout << "To get started with Memgraph, visit https://memgr.ph/start" << std::endl;
|
||||
|
||||
auto data_directory = std::filesystem::path(FLAGS_data_directory);
|
||||
|
||||
@@ -1115,9 +1141,14 @@ int main(int argc, char **argv) {
|
||||
|
||||
query::InterpreterContext interpreter_context{
|
||||
&db,
|
||||
{.query = {.allow_load_csv = FLAGS_allow_load_csv}, .execution_timeout_sec = FLAGS_query_execution_timeout_sec},
|
||||
FLAGS_data_directory,
|
||||
FLAGS_kafka_bootstrap_servers};
|
||||
{.query = {.allow_load_csv = FLAGS_allow_load_csv},
|
||||
.execution_timeout_sec = FLAGS_query_execution_timeout_sec,
|
||||
.after_commit_trigger_pool_size = FLAGS_after_commit_trigger_pool_size,
|
||||
.default_kafka_bootstrap_servers = FLAGS_kafka_bootstrap_servers,
|
||||
.default_pulsar_service_url = FLAGS_pulsar_service_url,
|
||||
.stream_transaction_conflict_retries = FLAGS_stream_transaction_conflict_retries,
|
||||
.stream_transaction_retry_interval = std::chrono::milliseconds(FLAGS_stream_transaction_retry_interval)},
|
||||
FLAGS_data_directory};
|
||||
#ifdef MG_ENTERPRISE
|
||||
SessionData session_data{&db, &interpreter_context, &auth, &audit_log};
|
||||
#else
|
||||
@@ -1127,9 +1158,6 @@ int main(int argc, char **argv) {
|
||||
query::procedure::gModuleRegistry.SetModulesDirectory(query_modules_directories);
|
||||
query::procedure::gModuleRegistry.UnloadAndLoadModulesFromDirectories();
|
||||
|
||||
// As the Stream transformations are using modules, they have to be restored after the query modules are loaded.
|
||||
interpreter_context.streams.RestoreStreams();
|
||||
|
||||
AuthQueryHandler auth_handler(&auth, FLAGS_auth_user_or_role_name_regex);
|
||||
AuthChecker auth_checker{&auth};
|
||||
interpreter_context.auth = &auth_handler;
|
||||
@@ -1145,6 +1173,9 @@ int main(int argc, char **argv) {
|
||||
interpreter_context.auth_checker);
|
||||
}
|
||||
|
||||
// As the Stream transformations are using modules, they have to be restored after the query modules are loaded.
|
||||
interpreter_context.streams.RestoreStreams();
|
||||
|
||||
ServerContext context;
|
||||
std::string service_name = "Bolt";
|
||||
if (!FLAGS_bolt_key_file.empty() && !FLAGS_bolt_cert_file.empty()) {
|
||||
@@ -1152,7 +1183,7 @@ int main(int argc, char **argv) {
|
||||
service_name = "BoltS";
|
||||
spdlog::info("Using secure Bolt connection (with SSL)");
|
||||
} else {
|
||||
spdlog::warn("Using non-secure Bolt connection (without SSL)");
|
||||
spdlog::warn(utils::MessageWithLink("Using non-secure Bolt connection (without SSL).", "https://memgr.ph/ssl"));
|
||||
}
|
||||
|
||||
ServerT server({FLAGS_bolt_address, static_cast<uint16_t>(FLAGS_bolt_port)}, &session_data, &context,
|
||||
|
||||
@@ -2,9 +2,11 @@ set(memory_src_files
|
||||
new_delete.cpp
|
||||
memory_control.cpp)
|
||||
|
||||
find_package(Jemalloc REQUIRED)
|
||||
|
||||
add_library(mg-memory STATIC ${memory_src_files})
|
||||
target_link_libraries(mg-memory mg-utils fmt)
|
||||
|
||||
if (ENABLE_JEMALLOC)
|
||||
target_link_libraries(mg-memory jemalloc)
|
||||
target_link_libraries(mg-memory Jemalloc::Jemalloc)
|
||||
endif()
|
||||
|
||||
@@ -19,13 +19,12 @@
|
||||
#include <cstdlib>
|
||||
#endif
|
||||
|
||||
#include "utils/likely.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
|
||||
namespace {
|
||||
void *newImpl(const std::size_t size) {
|
||||
auto *ptr = malloc(size);
|
||||
if (LIKELY(ptr != nullptr)) {
|
||||
if (ptr != nullptr) [[likely]] {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@@ -34,7 +33,7 @@ void *newImpl(const std::size_t size) {
|
||||
|
||||
void *newImpl(const std::size_t size, const std::align_val_t align) {
|
||||
auto *ptr = aligned_alloc(static_cast<std::size_t>(align), size);
|
||||
if (LIKELY(ptr != nullptr)) {
|
||||
if (ptr != nullptr) [[likely]] {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@@ -47,14 +46,22 @@ void *newNoExcept(const std::size_t size, const std::align_val_t align) noexcept
|
||||
}
|
||||
|
||||
#if USE_JEMALLOC
|
||||
void deleteImpl(void *ptr) noexcept { dallocx(ptr, 0); }
|
||||
void deleteImpl(void *ptr) noexcept {
|
||||
if (ptr == nullptr) [[unlikely]] {
|
||||
return;
|
||||
}
|
||||
dallocx(ptr, 0);
|
||||
}
|
||||
|
||||
void deleteImpl(void *ptr, const std::align_val_t align) noexcept {
|
||||
if (ptr == nullptr) [[unlikely]] {
|
||||
return;
|
||||
}
|
||||
dallocx(ptr, MALLOCX_ALIGN(align)); // NOLINT(hicpp-signed-bitwise)
|
||||
}
|
||||
|
||||
void deleteSized(void *ptr, const std::size_t size) noexcept {
|
||||
if (UNLIKELY(ptr == nullptr)) {
|
||||
if (ptr == nullptr) [[unlikely]] {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -62,7 +69,7 @@ void deleteSized(void *ptr, const std::size_t size) noexcept {
|
||||
}
|
||||
|
||||
void deleteSized(void *ptr, const std::size_t size, const std::align_val_t align) noexcept {
|
||||
if (UNLIKELY(ptr == nullptr)) {
|
||||
if (ptr == nullptr) [[unlikely]] {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -81,7 +88,7 @@ void deleteSized(void *ptr, const std::size_t /*unused*/, const std::align_val_t
|
||||
|
||||
void TrackMemory(std::size_t size) {
|
||||
#if USE_JEMALLOC
|
||||
if (LIKELY(size != 0)) {
|
||||
if (size != 0) [[likely]] {
|
||||
size = nallocx(size, 0);
|
||||
}
|
||||
#endif
|
||||
@@ -90,7 +97,7 @@ void TrackMemory(std::size_t size) {
|
||||
|
||||
void TrackMemory(std::size_t size, const std::align_val_t align) {
|
||||
#if USE_JEMALLOC
|
||||
if (LIKELY(size != 0)) {
|
||||
if (size != 0) [[likely]] {
|
||||
size = nallocx(size, MALLOCX_ALIGN(align)); // NOLINT(hicpp-signed-bitwise)
|
||||
}
|
||||
#endif
|
||||
@@ -120,7 +127,7 @@ bool TrackMemoryNoExcept(const std::size_t size, const std::align_val_t align) {
|
||||
void UntrackMemory([[maybe_unused]] void *ptr, [[maybe_unused]] std::size_t size = 0) noexcept {
|
||||
try {
|
||||
#if USE_JEMALLOC
|
||||
if (LIKELY(ptr != nullptr)) {
|
||||
if (ptr != nullptr) [[likely]] {
|
||||
utils::total_memory_tracker.Free(sallocx(ptr, 0));
|
||||
}
|
||||
#else
|
||||
@@ -138,7 +145,7 @@ void UntrackMemory([[maybe_unused]] void *ptr, [[maybe_unused]] std::size_t size
|
||||
void UntrackMemory(void *ptr, const std::align_val_t align, [[maybe_unused]] std::size_t size = 0) noexcept {
|
||||
try {
|
||||
#if USE_JEMALLOC
|
||||
if (LIKELY(ptr != nullptr)) {
|
||||
if (ptr != nullptr) [[likely]] {
|
||||
utils::total_memory_tracker.Free(sallocx(ptr, MALLOCX_ALIGN(align))); // NOLINT(hicpp-signed-bitwise)
|
||||
}
|
||||
#else
|
||||
@@ -176,28 +183,28 @@ void *operator new[](const std::size_t size, const std::align_val_t align) {
|
||||
}
|
||||
|
||||
void *operator new(const std::size_t size, const std::nothrow_t & /*unused*/) noexcept {
|
||||
if (LIKELY(TrackMemoryNoExcept(size))) {
|
||||
if (TrackMemoryNoExcept(size)) [[likely]] {
|
||||
return newNoExcept(size);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *operator new[](const std::size_t size, const std::nothrow_t & /*unused*/) noexcept {
|
||||
if (LIKELY(TrackMemoryNoExcept(size))) {
|
||||
if (TrackMemoryNoExcept(size)) [[likely]] {
|
||||
return newNoExcept(size);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *operator new(const std::size_t size, const std::align_val_t align, const std::nothrow_t & /*unused*/) noexcept {
|
||||
if (LIKELY(TrackMemoryNoExcept(size, align))) {
|
||||
if (TrackMemoryNoExcept(size, align)) [[likely]] {
|
||||
return newNoExcept(size, align);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *operator new[](const std::size_t size, const std::align_val_t align, const std::nothrow_t & /*unused*/) noexcept {
|
||||
if (LIKELY(TrackMemoryNoExcept(size, align))) {
|
||||
if (TrackMemoryNoExcept(size, align)) [[likely]] {
|
||||
return newNoExcept(size, align);
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "storage/v2/storage.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/message.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "utils/timer.hpp"
|
||||
#include "version.hpp"
|
||||
@@ -433,7 +434,7 @@ void ProcessNodeRow(storage::Storage *store, const std::vector<Field> &fields, c
|
||||
auto it = node_id_map->find(node_id);
|
||||
if (it != node_id_map->end()) {
|
||||
if (FLAGS_skip_duplicate_nodes) {
|
||||
spdlog::warn("Skipping duplicate node with ID '{}'", node_id);
|
||||
spdlog::warn(utils::MessageWithLink("Skipping duplicate node with ID '{}'.", node_id, "https://memgr.ph/csv"));
|
||||
return;
|
||||
} else {
|
||||
throw LoadException("Node with ID '{}' already exists", node_id);
|
||||
@@ -524,7 +525,8 @@ void ProcessRelationshipsRow(storage::Storage *store, const std::vector<Field> &
|
||||
auto it = node_id_map.find(node_id);
|
||||
if (it == node_id_map.end()) {
|
||||
if (FLAGS_skip_bad_relationships) {
|
||||
spdlog::warn("Skipping bad relationship with START_ID '{}'", node_id);
|
||||
spdlog::warn(
|
||||
utils::MessageWithLink("Skipping bad relationship with START_ID '{}'.", node_id, "https://memgr.ph/csv"));
|
||||
return;
|
||||
} else {
|
||||
throw LoadException("Node with ID '{}' does not exist", node_id);
|
||||
@@ -541,7 +543,7 @@ void ProcessRelationshipsRow(storage::Storage *store, const std::vector<Field> &
|
||||
auto it = node_id_map.find(node_id);
|
||||
if (it == node_id_map.end()) {
|
||||
if (FLAGS_skip_bad_relationships) {
|
||||
spdlog::warn("Skipping bad relationship with END_ID '{}'", node_id);
|
||||
spdlog::warn(utils::MessageWithLink("Skipping bad relationship with END_ID '{}'.", node_id, "https://memgr.ph/csv"));
|
||||
return;
|
||||
} else {
|
||||
throw LoadException("Node with ID '{}' does not exist", node_id);
|
||||
|
||||
@@ -20,6 +20,7 @@ set(mg_query_sources
|
||||
interpret/awesome_memgraph_functions.cpp
|
||||
interpret/eval.cpp
|
||||
interpreter.cpp
|
||||
metadata.cpp
|
||||
plan/operator.cpp
|
||||
plan/preprocess.cpp
|
||||
plan/pretty_print.cpp
|
||||
@@ -29,19 +30,27 @@ set(mg_query_sources
|
||||
plan/rule_based_planner.cpp
|
||||
plan/variable_start_planner.cpp
|
||||
procedure/mg_procedure_impl.cpp
|
||||
procedure/mg_procedure_helpers.cpp
|
||||
procedure/module.cpp
|
||||
procedure/py_module.cpp
|
||||
serialization/property_value.cpp
|
||||
streams.cpp
|
||||
stream/streams.cpp
|
||||
stream/sources.cpp
|
||||
stream/common.cpp
|
||||
trigger.cpp
|
||||
trigger_context.cpp
|
||||
typed_value.cpp)
|
||||
|
||||
find_package(Boost REQUIRED)
|
||||
|
||||
add_library(mg-query STATIC ${mg_query_sources})
|
||||
add_dependencies(mg-query generate_lcp_query)
|
||||
target_include_directories(mg-query PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||
target_link_libraries(mg-query dl cppitertools)
|
||||
target_link_libraries(mg-query mg-integrations-kafka mg-storage-v2 mg-utils mg-kvstore mg-memory)
|
||||
target_link_libraries(mg-query dl cppitertools Boost::headers)
|
||||
target_link_libraries(mg-query mg-integrations-pulsar mg-integrations-kafka mg-storage-v2 mg-license mg-utils mg-kvstore mg-memory)
|
||||
if(NOT "${MG_PYTHON_PATH}" STREQUAL "")
|
||||
set(Python3_ROOT_DIR "${MG_PYTHON_PATH}")
|
||||
endif()
|
||||
if("${MG_PYTHON_VERSION}" STREQUAL "")
|
||||
find_package(Python3 3.5 REQUIRED COMPONENTS Development)
|
||||
else()
|
||||
|
||||
@@ -42,15 +42,28 @@ bool TypedValueCompare(const TypedValue &a, const TypedValue &b) {
|
||||
else
|
||||
return a.ValueDouble() < b.ValueDouble();
|
||||
case TypedValue::Type::String:
|
||||
// NOLINTNEXTLINE(modernize-use-nullptr)
|
||||
return a.ValueString() < b.ValueString();
|
||||
case TypedValue::Type::Date:
|
||||
// NOLINTNEXTLINE(modernize-use-nullptr)
|
||||
return a.ValueDate() < b.ValueDate();
|
||||
case TypedValue::Type::LocalTime:
|
||||
// NOLINTNEXTLINE(modernize-use-nullptr)
|
||||
return a.ValueLocalTime() < b.ValueLocalTime();
|
||||
case TypedValue::Type::LocalDateTime:
|
||||
// NOLINTNEXTLINE(modernize-use-nullptr)
|
||||
return a.ValueLocalDateTime() < b.ValueLocalDateTime();
|
||||
case TypedValue::Type::Duration:
|
||||
// NOLINTNEXTLINE(modernize-use-nullptr)
|
||||
return a.ValueDuration() < b.ValueDuration();
|
||||
case TypedValue::Type::List:
|
||||
case TypedValue::Type::Map:
|
||||
case TypedValue::Type::Vertex:
|
||||
case TypedValue::Type::Edge:
|
||||
case TypedValue::Type::Path:
|
||||
throw QueryRuntimeException("Comparison is not defined for values of type {}.", a.type());
|
||||
default:
|
||||
LOG_FATAL("Unhandled comparison for types");
|
||||
case TypedValue::Type::Null:
|
||||
LOG_FATAL("Invalid type");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,10 +33,6 @@ namespace impl {
|
||||
bool TypedValueCompare(const TypedValue &a, const TypedValue &b);
|
||||
} // namespace impl
|
||||
|
||||
constexpr inline std::string_view kSerializationErrorMessage{
|
||||
"Cannot resolve conflicting transactions. You can retry this transaction when the conflicting transaction is "
|
||||
"finished."};
|
||||
|
||||
/// Custom Comparator type for comparing vectors of TypedValues.
|
||||
///
|
||||
/// Does lexicographical ordering of elements based on the above
|
||||
@@ -95,7 +91,7 @@ storage::PropertyValue PropsSetChecked(T *record, const storage::PropertyId &key
|
||||
if (maybe_old_value.HasError()) {
|
||||
switch (maybe_old_value.GetError()) {
|
||||
case storage::Error::SERIALIZATION_ERROR:
|
||||
throw QueryRuntimeException(kSerializationErrorMessage);
|
||||
throw TransactionSerializationException();
|
||||
case storage::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to set properties on a deleted object.");
|
||||
case storage::Error::PROPERTIES_DISABLED:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -10,6 +10,8 @@
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
namespace query {
|
||||
struct InterpreterConfig {
|
||||
@@ -19,5 +21,12 @@ struct InterpreterConfig {
|
||||
|
||||
// The default execution timeout is 10 minutes.
|
||||
double execution_timeout_sec{600.0};
|
||||
|
||||
size_t after_commit_trigger_pool_size;
|
||||
|
||||
std::string default_kafka_bootstrap_servers;
|
||||
std::string default_pulsar_service_url;
|
||||
uint32_t stream_transaction_conflict_retries;
|
||||
std::chrono::milliseconds stream_transaction_retry_interval;
|
||||
};
|
||||
} // namespace query
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "query/common.hpp"
|
||||
#include "query/frontend/semantic/symbol_table.hpp"
|
||||
#include "query/metadata.hpp"
|
||||
#include "query/parameters.hpp"
|
||||
#include "query/plan/profile.hpp"
|
||||
#include "query/trigger.hpp"
|
||||
@@ -68,6 +69,7 @@ struct ExecutionContext {
|
||||
std::chrono::duration<double> profile_execution_time;
|
||||
plan::ProfilingStats stats;
|
||||
plan::ProfilingStats *stats_root{nullptr};
|
||||
ExecutionStats execution_stats;
|
||||
TriggerContextCollector *trigger_context_collector{nullptr};
|
||||
utils::AsyncTimer timer;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -11,23 +11,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// THIS INCLUDE SHOULD ALWAYS COME BEFORE THE
|
||||
// "cypher_main_visitor.hpp"
|
||||
// "planner.hpp" includes json.hpp which uses libc's
|
||||
// EOF macro while "cypher_main_visitor.hpp" includes
|
||||
// "antlr4-runtime.h" which contains a static variable
|
||||
// of the same name, EOF.
|
||||
// This hides the definition of the macro which causes
|
||||
// the compilation to fail.
|
||||
#include "query/plan/planner.hpp"
|
||||
//////////////////////////////////////////////////////
|
||||
#include "query/config.hpp"
|
||||
#include "query/frontend/ast/cypher_main_visitor.hpp"
|
||||
#include "query/frontend/opencypher/parser.hpp"
|
||||
#include "query/frontend/semantic/required_privileges.hpp"
|
||||
#include "query/frontend/semantic/symbol_generator.hpp"
|
||||
#include "query/frontend/stripped.hpp"
|
||||
#include "query/plan/planner.hpp"
|
||||
#include "utils/flag_validation.hpp"
|
||||
#include "utils/timer.hpp"
|
||||
|
||||
|
||||
@@ -130,6 +130,18 @@ class ExplicitTransactionUsageException : public QueryRuntimeException {
|
||||
using QueryRuntimeException::QueryRuntimeException;
|
||||
};
|
||||
|
||||
/**
|
||||
* An exception for serialization error
|
||||
*/
|
||||
class TransactionSerializationException : public QueryException {
|
||||
public:
|
||||
using QueryException::QueryException;
|
||||
TransactionSerializationException()
|
||||
: QueryException(
|
||||
"Cannot resolve conflicting transactions. You can retry this transaction when the conflicting transaction "
|
||||
"is finished") {}
|
||||
};
|
||||
|
||||
class ReconstructionException : public QueryException {
|
||||
public:
|
||||
ReconstructionException()
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
;; 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 License, and you may not use this file except in compliance with 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
|
||||
@@ -53,9 +54,7 @@ cpp<#
|
||||
size_t size = 0;
|
||||
slk::Load(&size, reader);
|
||||
self->${member}.resize(size);
|
||||
for (size_t i = 0;
|
||||
i < size;
|
||||
++i) {
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
self->${member}[i] = query::LoadAstPointer<query::${type}>(storage, reader);
|
||||
}
|
||||
cpp<#))
|
||||
@@ -74,9 +73,7 @@ cpp<#
|
||||
#>cpp
|
||||
size_t size = 0;
|
||||
slk::Load(&size, reader);
|
||||
for (size_t i = 0;
|
||||
i < size;
|
||||
++i) {
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
query::PropertyIx key;
|
||||
slk::Load(&key, reader, storage);
|
||||
auto *value = query::LoadAstPointer<query::Expression>(storage, reader);
|
||||
@@ -92,6 +89,34 @@ cpp<#
|
||||
}
|
||||
cpp<#)
|
||||
|
||||
(defun slk-save-expression-map (member)
|
||||
#>cpp
|
||||
size_t size = self.${member}.size();
|
||||
slk::Save(size, builder);
|
||||
for (const auto &entry : self.${member}) {
|
||||
query::SaveAstPointer(entry.first, builder);
|
||||
query::SaveAstPointer(entry.second, builder);
|
||||
}
|
||||
cpp<#)
|
||||
|
||||
(defun slk-load-expression-map (member)
|
||||
#>cpp
|
||||
size_t size = 0;
|
||||
slk::Load(&size, reader);
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
auto *key = query::LoadAstPointer<query::Expression>(storage, reader);
|
||||
auto *value = query::LoadAstPointer<query::Expression>(storage, reader);
|
||||
self->${member}.emplace(key, value);
|
||||
}
|
||||
cpp<#)
|
||||
|
||||
(defun clone-expression-map (source dest)
|
||||
#>cpp
|
||||
for (const auto &[key, value] : ${source}) {
|
||||
${dest}[key->Clone(storage)] = value->Clone(storage);
|
||||
}
|
||||
cpp<#)
|
||||
|
||||
(defun slk-load-name-ix (name-type)
|
||||
(lambda (member)
|
||||
#>cpp
|
||||
@@ -1818,9 +1843,7 @@ cpp<#
|
||||
size_t size = 0;
|
||||
slk::Load(&size, reader);
|
||||
self->${member}.resize(size);
|
||||
for (size_t i = 0;
|
||||
i < size;
|
||||
++i) {
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
slk::Load(&self->${member}[i], reader, storage);
|
||||
}
|
||||
cpp<#)
|
||||
@@ -2497,29 +2520,68 @@ cpp<#
|
||||
(:serialize (:slk))
|
||||
(:clone))
|
||||
|
||||
(defun clone-variant-topic-names (source destination)
|
||||
#>cpp
|
||||
if (auto *topic_expression = std::get_if<Expression*>(&${source})) {
|
||||
if (*topic_expression == nullptr) {
|
||||
${destination} = nullptr;
|
||||
} else {
|
||||
${destination} = (*topic_expression)->Clone(storage);
|
||||
}
|
||||
} else {
|
||||
${destination} = std::get<std::vector<std::string>>(${source});
|
||||
}
|
||||
cpp<#)
|
||||
|
||||
(lcp:define-class stream-query (query)
|
||||
((action "Action" :scope :public)
|
||||
(type "Type" :scope :public)
|
||||
(stream_name "std::string" :scope :public)
|
||||
(topic_names "std::vector<std::string>" :scope :public)
|
||||
|
||||
(batch_limit "Expression *" :initval "nullptr" :scope :public
|
||||
:slk-save #'slk-save-ast-pointer
|
||||
:slk-load (slk-load-ast-pointer "Expression"))
|
||||
(timeout "Expression *" :initval "nullptr" :scope :public
|
||||
:slk-save #'slk-save-ast-pointer
|
||||
:slk-load (slk-load-ast-pointer "Expression"))
|
||||
|
||||
(transform_name "std::string" :scope :public)
|
||||
(consumer_group "std::string" :scope :public)
|
||||
(batch_interval "Expression *" :initval "nullptr" :scope :public
|
||||
:slk-save #'slk-save-ast-pointer
|
||||
:slk-load (slk-load-ast-pointer "Expression"))
|
||||
(batch_size "Expression *" :initval "nullptr" :scope :public
|
||||
:slk-save #'slk-save-ast-pointer
|
||||
:slk-load (slk-load-ast-pointer "Expression"))
|
||||
(batch_limit "Expression *" :initval "nullptr" :scope :public
|
||||
|
||||
(topic_names "std::variant<Expression*, std::vector<std::string>>" :initval "nullptr"
|
||||
:clone #'clone-variant-topic-names
|
||||
:scope :public)
|
||||
(consumer_group "std::string" :scope :public)
|
||||
(bootstrap_servers "Expression *" :initval "nullptr" :scope :public
|
||||
:slk-save #'slk-save-ast-pointer
|
||||
:slk-load (slk-load-ast-pointer "Expression"))
|
||||
(timeout "Expression *" :initval "nullptr" :scope :public
|
||||
|
||||
(service_url "Expression *" :initval "nullptr" :scope :public
|
||||
:slk-save #'slk-save-ast-pointer
|
||||
:slk-load (slk-load-ast-pointer "Expression")))
|
||||
:slk-load (slk-load-ast-pointer "Expression"))
|
||||
|
||||
(configs "std::unordered_map<Expression *, Expression *>" :scope :public
|
||||
:slk-save #'slk-save-expression-map
|
||||
:slk-load #'slk-load-expression-map
|
||||
:clone #'clone-expression-map)
|
||||
|
||||
(credentials "std::unordered_map<Expression *, Expression *>" :scope :public
|
||||
:slk-save #'slk-save-expression-map
|
||||
:slk-load #'slk-load-expression-map
|
||||
:clone #'clone-expression-map))
|
||||
|
||||
(:public
|
||||
(lcp:define-enum action
|
||||
(create-stream drop-stream start-stream stop-stream start-all-streams stop-all-streams show-streams check-stream)
|
||||
(:serialize))
|
||||
(lcp:define-enum type
|
||||
(kafka pulsar)
|
||||
(:serialize))
|
||||
#>cpp
|
||||
StreamQuery() = default;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -9,18 +9,6 @@
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// THIS INCLUDE SHOULD ALWAYS COME BEFORE THE
|
||||
// "cypher_main_visitor.hpp"
|
||||
// "module.hpp" includes json.hpp which uses libc's
|
||||
// EOF macro while "cypher_main_visitor.hpp" includes
|
||||
// "antlr4-runtime.h" which contains a static variable
|
||||
// of the same name, EOF.
|
||||
// This hides the definition of the macro which causes
|
||||
// the compilation to fail.
|
||||
#include "query/frontend/ast/ast_visitor.hpp"
|
||||
#include "query/procedure/module.hpp"
|
||||
//////////////////////////////////////////////////////
|
||||
#include "query/frontend/ast/cypher_main_visitor.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -31,14 +19,21 @@
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/preprocessor/cat.hpp>
|
||||
|
||||
#include "query/exceptions.hpp"
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
#include "query/frontend/ast/ast_visitor.hpp"
|
||||
#include "query/frontend/parsing.hpp"
|
||||
#include "query/interpret/awesome_memgraph_functions.hpp"
|
||||
#include "query/procedure/module.hpp"
|
||||
#include "query/stream/common.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/string.hpp"
|
||||
@@ -492,39 +487,281 @@ antlrcpp::Any CypherMainVisitor::visitStreamQuery(MemgraphCypher::StreamQueryCon
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitCreateStream(MemgraphCypher::CreateStreamContext *ctx) {
|
||||
MG_ASSERT(ctx->children.size() == 1, "CreateStreamQuery should have exactly one child!");
|
||||
auto *stream_query = ctx->children[0]->accept(this).as<StreamQuery *>();
|
||||
query_ = stream_query;
|
||||
return stream_query;
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::vector<std::string> TopicNamesFromSymbols(
|
||||
antlr4::tree::ParseTreeVisitor &visitor,
|
||||
const std::vector<MemgraphCypher::SymbolicNameWithDotsAndMinusContext *> &topic_name_symbols) {
|
||||
MG_ASSERT(!topic_name_symbols.empty());
|
||||
std::vector<std::string> topic_names;
|
||||
topic_names.reserve(topic_name_symbols.size());
|
||||
std::transform(topic_name_symbols.begin(), topic_name_symbols.end(), std::back_inserter(topic_names),
|
||||
[&visitor](auto *topic_name) { return JoinSymbolicNamesWithDotsAndMinus(visitor, *topic_name); });
|
||||
return topic_names;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
concept EnumUint8 = std::is_enum_v<T> && std::same_as<uint8_t, std::underlying_type_t<T>>;
|
||||
|
||||
template <bool required, typename... ValueTypes>
|
||||
void MapConfig(auto &memory, const EnumUint8 auto &enum_key, auto &destination) {
|
||||
const auto key = static_cast<uint8_t>(enum_key);
|
||||
if (!memory.contains(key)) {
|
||||
if constexpr (required) {
|
||||
throw SemanticException("Config {} is required.", ToString(enum_key));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::visit(
|
||||
[&]<typename T>(T &&value) {
|
||||
using ValueType = std::decay_t<T>;
|
||||
if constexpr (utils::SameAsAnyOf<ValueType, ValueTypes...>) {
|
||||
destination = std::forward<T>(value);
|
||||
} else {
|
||||
LOG_FATAL("Invalid type mapped");
|
||||
}
|
||||
},
|
||||
std::move(memory[key]));
|
||||
memory.erase(key);
|
||||
}
|
||||
|
||||
enum class CommonStreamConfigKey : uint8_t { TRANSFORM, BATCH_INTERVAL, BATCH_SIZE, END };
|
||||
|
||||
std::string_view ToString(const CommonStreamConfigKey key) {
|
||||
switch (key) {
|
||||
case CommonStreamConfigKey::TRANSFORM:
|
||||
return "TRANSFORM";
|
||||
case CommonStreamConfigKey::BATCH_INTERVAL:
|
||||
return "BATCH_INTERVAL";
|
||||
case CommonStreamConfigKey::BATCH_SIZE:
|
||||
return "BATCH_SIZE";
|
||||
case CommonStreamConfigKey::END:
|
||||
LOG_FATAL("Invalid config key used");
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define GENERATE_STREAM_CONFIG_KEY_ENUM(stream, first_config, ...) \
|
||||
enum class BOOST_PP_CAT(stream, ConfigKey) : uint8_t { \
|
||||
first_config = static_cast<uint8_t>(CommonStreamConfigKey::END), \
|
||||
__VA_ARGS__ \
|
||||
};
|
||||
|
||||
GENERATE_STREAM_CONFIG_KEY_ENUM(Kafka, TOPICS, CONSUMER_GROUP, BOOTSTRAP_SERVERS, CONFIGS, CREDENTIALS);
|
||||
|
||||
std::string_view ToString(const KafkaConfigKey key) {
|
||||
switch (key) {
|
||||
case KafkaConfigKey::TOPICS:
|
||||
return "TOPICS";
|
||||
case KafkaConfigKey::CONSUMER_GROUP:
|
||||
return "CONSUMER_GROUP";
|
||||
case KafkaConfigKey::BOOTSTRAP_SERVERS:
|
||||
return "BOOTSTRAP_SERVERS";
|
||||
case KafkaConfigKey::CONFIGS:
|
||||
return "CONFIGS";
|
||||
case KafkaConfigKey::CREDENTIALS:
|
||||
return "CREDENTIALS";
|
||||
}
|
||||
}
|
||||
|
||||
void MapCommonStreamConfigs(auto &memory, StreamQuery &stream_query) {
|
||||
MapConfig<true, std::string>(memory, CommonStreamConfigKey::TRANSFORM, stream_query.transform_name_);
|
||||
MapConfig<false, Expression *>(memory, CommonStreamConfigKey::BATCH_INTERVAL, stream_query.batch_interval_);
|
||||
MapConfig<false, Expression *>(memory, CommonStreamConfigKey::BATCH_SIZE, stream_query.batch_size_);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitConfigKeyValuePair(MemgraphCypher::ConfigKeyValuePairContext *ctx) {
|
||||
MG_ASSERT(ctx->literal().size() == 2);
|
||||
return std::pair{ctx->literal(0)->accept(this).as<Expression *>(), ctx->literal(1)->accept(this).as<Expression *>()};
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitConfigMap(MemgraphCypher::ConfigMapContext *ctx) {
|
||||
std::unordered_map<Expression *, Expression *> map;
|
||||
for (auto *key_value_pair : ctx->configKeyValuePair()) {
|
||||
// If the queries are cached, then only the stripped query is parsed, so the actual keys cannot be determined
|
||||
// here. That means duplicates cannot be checked.
|
||||
map.insert(key_value_pair->accept(this).as<std::pair<Expression *, Expression *>>());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitKafkaCreateStream(MemgraphCypher::KafkaCreateStreamContext *ctx) {
|
||||
auto *stream_query = storage_->Create<StreamQuery>();
|
||||
stream_query->action_ = StreamQuery::Action::CREATE_STREAM;
|
||||
stream_query->type_ = StreamQuery::Type::KAFKA;
|
||||
stream_query->stream_name_ = ctx->streamName()->symbolicName()->accept(this).as<std::string>();
|
||||
|
||||
auto *topic_names_ctx = ctx->topicNames();
|
||||
MG_ASSERT(topic_names_ctx != nullptr);
|
||||
auto topic_names = topic_names_ctx->symbolicNameWithDotsAndMinus();
|
||||
MG_ASSERT(!topic_names.empty());
|
||||
stream_query->topic_names_.reserve(topic_names.size());
|
||||
std::transform(topic_names.begin(), topic_names.end(), std::back_inserter(stream_query->topic_names_),
|
||||
[this](auto *topic_name) { return JoinSymbolicNamesWithDotsAndMinus(*this, *topic_name); });
|
||||
for (auto *create_config_ctx : ctx->kafkaCreateStreamConfig()) {
|
||||
create_config_ctx->accept(this);
|
||||
}
|
||||
|
||||
stream_query->transform_name_ = JoinSymbolicNames(this, ctx->transformationName->symbolicName());
|
||||
MapConfig<true, std::vector<std::string>, Expression *>(memory_, KafkaConfigKey::TOPICS, stream_query->topic_names_);
|
||||
MapConfig<false, std::string>(memory_, KafkaConfigKey::CONSUMER_GROUP, stream_query->consumer_group_);
|
||||
MapConfig<false, Expression *>(memory_, KafkaConfigKey::BOOTSTRAP_SERVERS, stream_query->bootstrap_servers_);
|
||||
MapConfig<false, std::unordered_map<Expression *, Expression *>>(memory_, KafkaConfigKey::CONFIGS,
|
||||
stream_query->configs_);
|
||||
MapConfig<false, std::unordered_map<Expression *, Expression *>>(memory_, KafkaConfigKey::CREDENTIALS,
|
||||
stream_query->credentials_);
|
||||
|
||||
MapCommonStreamConfigs(memory_, *stream_query);
|
||||
|
||||
return stream_query;
|
||||
}
|
||||
|
||||
namespace {
|
||||
void ThrowIfExists(const auto &map, const EnumUint8 auto &enum_key) {
|
||||
const auto key = static_cast<uint8_t>(enum_key);
|
||||
if (map.contains(key)) {
|
||||
throw SemanticException("{} defined multiple times in the query", ToString(enum_key));
|
||||
}
|
||||
}
|
||||
|
||||
void GetTopicNames(auto &destination, MemgraphCypher::TopicNamesContext *topic_names_ctx,
|
||||
antlr4::tree::ParseTreeVisitor &visitor) {
|
||||
MG_ASSERT(topic_names_ctx != nullptr);
|
||||
if (auto *symbolic_topic_names_ctx = topic_names_ctx->symbolicTopicNames()) {
|
||||
destination = TopicNamesFromSymbols(visitor, symbolic_topic_names_ctx->symbolicNameWithDotsAndMinus());
|
||||
} else {
|
||||
if (!topic_names_ctx->literal()->StringLiteral()) {
|
||||
throw SemanticException("Topic names should be defined as a string literal or as symbolic names");
|
||||
}
|
||||
destination = topic_names_ctx->accept(&visitor).as<Expression *>();
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitKafkaCreateStreamConfig(MemgraphCypher::KafkaCreateStreamConfigContext *ctx) {
|
||||
if (ctx->commonCreateStreamConfig()) {
|
||||
return ctx->commonCreateStreamConfig()->accept(this);
|
||||
}
|
||||
|
||||
if (ctx->TOPICS()) {
|
||||
ThrowIfExists(memory_, KafkaConfigKey::TOPICS);
|
||||
constexpr auto topics_key = static_cast<uint8_t>(KafkaConfigKey::TOPICS);
|
||||
GetTopicNames(memory_[topics_key], ctx->topicNames(), *this);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (ctx->CONSUMER_GROUP()) {
|
||||
stream_query->consumer_group_ = JoinSymbolicNamesWithDotsAndMinus(*this, *ctx->consumerGroup);
|
||||
ThrowIfExists(memory_, KafkaConfigKey::CONSUMER_GROUP);
|
||||
constexpr auto consumer_group_key = static_cast<uint8_t>(KafkaConfigKey::CONSUMER_GROUP);
|
||||
memory_[consumer_group_key] = JoinSymbolicNamesWithDotsAndMinus(*this, *ctx->consumerGroup);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (ctx->CONFIGS()) {
|
||||
ThrowIfExists(memory_, KafkaConfigKey::CONFIGS);
|
||||
constexpr auto configs_key = static_cast<uint8_t>(KafkaConfigKey::CONFIGS);
|
||||
memory_.emplace(configs_key, ctx->configsMap->accept(this).as<std::unordered_map<Expression *, Expression *>>());
|
||||
return {};
|
||||
}
|
||||
|
||||
if (ctx->CREDENTIALS()) {
|
||||
ThrowIfExists(memory_, KafkaConfigKey::CREDENTIALS);
|
||||
constexpr auto credentials_key = static_cast<uint8_t>(KafkaConfigKey::CREDENTIALS);
|
||||
memory_.emplace(credentials_key,
|
||||
ctx->credentialsMap->accept(this).as<std::unordered_map<Expression *, Expression *>>());
|
||||
return {};
|
||||
}
|
||||
|
||||
MG_ASSERT(ctx->BOOTSTRAP_SERVERS());
|
||||
ThrowIfExists(memory_, KafkaConfigKey::BOOTSTRAP_SERVERS);
|
||||
if (!ctx->bootstrapServers->StringLiteral()) {
|
||||
throw SemanticException("Bootstrap servers should be a string!");
|
||||
}
|
||||
|
||||
const auto bootstrap_servers_key = static_cast<uint8_t>(KafkaConfigKey::BOOTSTRAP_SERVERS);
|
||||
memory_[bootstrap_servers_key] = ctx->bootstrapServers->accept(this).as<Expression *>();
|
||||
return {};
|
||||
}
|
||||
|
||||
namespace {
|
||||
GENERATE_STREAM_CONFIG_KEY_ENUM(Pulsar, TOPICS, SERVICE_URL);
|
||||
|
||||
std::string_view ToString(const PulsarConfigKey key) {
|
||||
switch (key) {
|
||||
case PulsarConfigKey::TOPICS:
|
||||
return "TOPICS";
|
||||
case PulsarConfigKey::SERVICE_URL:
|
||||
return "SERVICE_URL";
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitPulsarCreateStream(MemgraphCypher::PulsarCreateStreamContext *ctx) {
|
||||
auto *stream_query = storage_->Create<StreamQuery>();
|
||||
stream_query->action_ = StreamQuery::Action::CREATE_STREAM;
|
||||
stream_query->type_ = StreamQuery::Type::PULSAR;
|
||||
stream_query->stream_name_ = ctx->streamName()->symbolicName()->accept(this).as<std::string>();
|
||||
|
||||
for (auto *create_config_ctx : ctx->pulsarCreateStreamConfig()) {
|
||||
create_config_ctx->accept(this);
|
||||
}
|
||||
|
||||
MapConfig<true, std::vector<std::string>, Expression *>(memory_, PulsarConfigKey::TOPICS, stream_query->topic_names_);
|
||||
MapConfig<false, Expression *>(memory_, PulsarConfigKey::SERVICE_URL, stream_query->service_url_);
|
||||
|
||||
MapCommonStreamConfigs(memory_, *stream_query);
|
||||
|
||||
return stream_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitPulsarCreateStreamConfig(MemgraphCypher::PulsarCreateStreamConfigContext *ctx) {
|
||||
if (ctx->commonCreateStreamConfig()) {
|
||||
return ctx->commonCreateStreamConfig()->accept(this);
|
||||
}
|
||||
|
||||
if (ctx->TOPICS()) {
|
||||
ThrowIfExists(memory_, PulsarConfigKey::TOPICS);
|
||||
const auto topics_key = static_cast<uint8_t>(PulsarConfigKey::TOPICS);
|
||||
GetTopicNames(memory_[topics_key], ctx->topicNames(), *this);
|
||||
return {};
|
||||
}
|
||||
|
||||
MG_ASSERT(ctx->SERVICE_URL());
|
||||
ThrowIfExists(memory_, PulsarConfigKey::SERVICE_URL);
|
||||
if (!ctx->serviceUrl->StringLiteral()) {
|
||||
throw SemanticException("Service URL must be a string!");
|
||||
}
|
||||
const auto service_url_key = static_cast<uint8_t>(PulsarConfigKey::SERVICE_URL);
|
||||
memory_[service_url_key] = ctx->serviceUrl->accept(this).as<Expression *>();
|
||||
return {};
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitCommonCreateStreamConfig(MemgraphCypher::CommonCreateStreamConfigContext *ctx) {
|
||||
if (ctx->TRANSFORM()) {
|
||||
ThrowIfExists(memory_, CommonStreamConfigKey::TRANSFORM);
|
||||
const auto transform_key = static_cast<uint8_t>(CommonStreamConfigKey::TRANSFORM);
|
||||
memory_[transform_key] = JoinSymbolicNames(this, ctx->transformationName->symbolicName());
|
||||
return {};
|
||||
}
|
||||
|
||||
if (ctx->BATCH_INTERVAL()) {
|
||||
ThrowIfExists(memory_, CommonStreamConfigKey::BATCH_INTERVAL);
|
||||
if (!ctx->batchInterval->numberLiteral() || !ctx->batchInterval->numberLiteral()->integerLiteral()) {
|
||||
throw SemanticException("Batch interval should be an integer literal!");
|
||||
throw SemanticException("Batch interval must be an integer literal!");
|
||||
}
|
||||
stream_query->batch_interval_ = ctx->batchInterval->accept(this);
|
||||
const auto batch_interval_key = static_cast<uint8_t>(CommonStreamConfigKey::BATCH_INTERVAL);
|
||||
memory_[batch_interval_key] = ctx->batchInterval->accept(this).as<Expression *>();
|
||||
return {};
|
||||
}
|
||||
|
||||
if (ctx->BATCH_SIZE()) {
|
||||
if (!ctx->batchSize->numberLiteral() || !ctx->batchSize->numberLiteral()->integerLiteral()) {
|
||||
throw SemanticException("Batch size should be an integer literal!");
|
||||
}
|
||||
stream_query->batch_size_ = ctx->batchSize->accept(this);
|
||||
MG_ASSERT(ctx->BATCH_SIZE());
|
||||
ThrowIfExists(memory_, CommonStreamConfigKey::BATCH_SIZE);
|
||||
if (!ctx->batchSize->numberLiteral() || !ctx->batchSize->numberLiteral()->integerLiteral()) {
|
||||
throw SemanticException("Batch size must be an integer literal!");
|
||||
}
|
||||
|
||||
return stream_query;
|
||||
const auto batch_size_key = static_cast<uint8_t>(CommonStreamConfigKey::BATCH_SIZE);
|
||||
memory_[batch_size_key] = ctx->batchSize->accept(this).as<Expression *>();
|
||||
return {};
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitDropStream(MemgraphCypher::DropStreamContext *ctx) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -269,6 +269,41 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
*/
|
||||
antlrcpp::Any visitCreateStream(MemgraphCypher::CreateStreamContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitConfigKeyValuePair(MemgraphCypher::ConfigKeyValuePairContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitConfigMap(MemgraphCypher::ConfigMapContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitKafkaCreateStream(MemgraphCypher::KafkaCreateStreamContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitKafkaCreateStreamConfig(MemgraphCypher::KafkaCreateStreamConfigContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitPulsarCreateStreamConfig(MemgraphCypher::PulsarCreateStreamConfigContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitPulsarCreateStream(MemgraphCypher::PulsarCreateStreamContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
antlrcpp::Any visitCommonCreateStreamConfig(MemgraphCypher::CommonCreateStreamConfigContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return StreamQuery*
|
||||
*/
|
||||
@@ -824,6 +859,9 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
ParsingContext context_;
|
||||
AstStorage *storage_;
|
||||
|
||||
std::unordered_map<uint8_t, std::variant<Expression *, std::string, std::vector<std::string>,
|
||||
std::unordered_map<Expression *, Expression *>>>
|
||||
memory_;
|
||||
// Set of identifiers from queries.
|
||||
std::unordered_set<std::string> users_identifiers;
|
||||
// Identifiers that user didn't name.
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
* 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 License, and you may not use this file except in compliance with 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
|
||||
@@ -28,12 +29,15 @@ memgraphCypherKeyword : cypherKeyword
|
||||
| BATCH_LIMIT
|
||||
| BATCH_SIZE
|
||||
| BEFORE
|
||||
| BOOTSTRAP_SERVERS
|
||||
| CHECK
|
||||
| CLEAR
|
||||
| COMMIT
|
||||
| COMMITTED
|
||||
| CONFIG
|
||||
| CONFIGS
|
||||
| CONSUMER_GROUP
|
||||
| CREDENTIALS
|
||||
| CSV
|
||||
| DATA
|
||||
| DELIMITER
|
||||
@@ -293,14 +297,38 @@ symbolicNameWithMinus : symbolicName ( MINUS symbolicName )* ;
|
||||
|
||||
symbolicNameWithDotsAndMinus: symbolicNameWithMinus ( DOT symbolicNameWithMinus )* ;
|
||||
|
||||
topicNames : symbolicNameWithDotsAndMinus ( COMMA symbolicNameWithDotsAndMinus )* ;
|
||||
symbolicTopicNames : symbolicNameWithDotsAndMinus ( COMMA symbolicNameWithDotsAndMinus )* ;
|
||||
|
||||
createStream : CREATE STREAM streamName
|
||||
TOPICS topicNames
|
||||
TRANSFORM transformationName=procedureName
|
||||
( CONSUMER_GROUP consumerGroup=symbolicNameWithDotsAndMinus ) ?
|
||||
( BATCH_INTERVAL batchInterval=literal ) ?
|
||||
( BATCH_SIZE batchSize=literal ) ? ;
|
||||
topicNames : symbolicTopicNames | literal ;
|
||||
|
||||
commonCreateStreamConfig : TRANSFORM transformationName=procedureName
|
||||
| BATCH_INTERVAL batchInterval=literal
|
||||
| BATCH_SIZE batchSize=literal
|
||||
;
|
||||
|
||||
createStream : kafkaCreateStream | pulsarCreateStream ;
|
||||
|
||||
configKeyValuePair : literal ':' literal ;
|
||||
|
||||
configMap : '{' ( configKeyValuePair ( ',' configKeyValuePair )* )? '}' ;
|
||||
|
||||
kafkaCreateStreamConfig : TOPICS topicNames
|
||||
| CONSUMER_GROUP consumerGroup=symbolicNameWithDotsAndMinus
|
||||
| BOOTSTRAP_SERVERS bootstrapServers=literal
|
||||
| CONFIGS configsMap=configMap
|
||||
| CREDENTIALS credentialsMap=configMap
|
||||
| commonCreateStreamConfig
|
||||
;
|
||||
|
||||
kafkaCreateStream : CREATE KAFKA STREAM streamName ( kafkaCreateStreamConfig ) * ;
|
||||
|
||||
|
||||
pulsarCreateStreamConfig : TOPICS topicNames
|
||||
| SERVICE_URL serviceUrl=literal
|
||||
| commonCreateStreamConfig
|
||||
;
|
||||
|
||||
pulsarCreateStream : CREATE PULSAR STREAM streamName ( pulsarCreateStreamConfig ) * ;
|
||||
|
||||
dropStream : DROP STREAM streamName ;
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
* 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 License, and you may not use this file except in compliance with 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
|
||||
@@ -24,81 +25,87 @@ import CypherLexer ;
|
||||
|
||||
UNDERSCORE : '_' ;
|
||||
|
||||
AFTER : A F T E R ;
|
||||
ALTER : A L T E R ;
|
||||
ASYNC : A S Y N C ;
|
||||
AUTH : A U T H ;
|
||||
BAD : B A D ;
|
||||
BATCH_INTERVAL : B A T C H UNDERSCORE I N T E R V A L ;
|
||||
BATCH_LIMIT : B A T C H UNDERSCORE L I M I T ;
|
||||
BATCH_SIZE : B A T C H UNDERSCORE S I Z E ;
|
||||
BEFORE : B E F O R E ;
|
||||
CHECK : C H E C K ;
|
||||
CLEAR : C L E A R ;
|
||||
COMMIT : C O M M I T ;
|
||||
COMMITTED : C O M M I T T E D ;
|
||||
CONFIG : C O N F I G ;
|
||||
CONSUMER_GROUP : C O N S U M E R UNDERSCORE G R O U P ;
|
||||
CSV : C S V ;
|
||||
DATA : D A T A ;
|
||||
DELIMITER : D E L I M I T E R ;
|
||||
DATABASE : D A T A B A S E ;
|
||||
DENY : D E N Y ;
|
||||
DIRECTORY : D I R E C T O R Y ;
|
||||
DROP : D R O P ;
|
||||
DUMP : D U M P ;
|
||||
DURABILITY : D U R A B I L I T Y ;
|
||||
EXECUTE : E X E C U T E ;
|
||||
FOR : F O R ;
|
||||
FREE : F R E E ;
|
||||
FREE_MEMORY : F R E E UNDERSCORE M E M O R Y ;
|
||||
FROM : F R O M ;
|
||||
GLOBAL : G L O B A L ;
|
||||
GRANT : G R A N T ;
|
||||
GRANTS : G R A N T S ;
|
||||
HEADER : H E A D E R ;
|
||||
IDENTIFIED : I D E N T I F I E D ;
|
||||
IGNORE : I G N O R E ;
|
||||
ISOLATION : I S O L A T I O N ;
|
||||
LEVEL : L E V E L ;
|
||||
LOAD : L O A D ;
|
||||
LOCK : L O C K ;
|
||||
MAIN : M A I N ;
|
||||
MODE : M O D E ;
|
||||
NEXT : N E X T ;
|
||||
NO : N O ;
|
||||
PASSWORD : P A S S W O R D ;
|
||||
PORT : P O R T ;
|
||||
PRIVILEGES : P R I V I L E G E S ;
|
||||
READ : R E A D ;
|
||||
READ_FILE : R E A D UNDERSCORE F I L E ;
|
||||
REGISTER : R E G I S T E R ;
|
||||
REPLICA : R E P L I C A ;
|
||||
REPLICAS : R E P L I C A S ;
|
||||
REPLICATION : R E P L I C A T I O N ;
|
||||
REVOKE : R E V O K E ;
|
||||
ROLE : R O L E ;
|
||||
ROLES : R O L E S ;
|
||||
QUOTE : Q U O T E ;
|
||||
SESSION : S E S S I O N ;
|
||||
SETTING : S E T T I N G ;
|
||||
SETTINGS : S E T T I N G S ;
|
||||
SNAPSHOT : S N A P S H O T ;
|
||||
START : S T A R T ;
|
||||
STATS : S T A T S ;
|
||||
STOP : S T O P ;
|
||||
STREAM : S T R E A M ;
|
||||
STREAMS : S T R E A M S ;
|
||||
SYNC : S Y N C ;
|
||||
TIMEOUT : T I M E O U T ;
|
||||
TO : T O ;
|
||||
TOPICS : T O P I C S;
|
||||
TRANSACTION : T R A N S A C T I O N ;
|
||||
TRANSFORM : T R A N S F O R M ;
|
||||
TRIGGER : T R I G G E R ;
|
||||
TRIGGERS : T R I G G E R S ;
|
||||
UNCOMMITTED : U N C O M M I T T E D ;
|
||||
UNLOCK : U N L O C K ;
|
||||
UPDATE : U P D A T E ;
|
||||
USER : U S E R ;
|
||||
USERS : U S E R S ;
|
||||
AFTER : A F T E R ;
|
||||
ALTER : A L T E R ;
|
||||
ASYNC : A S Y N C ;
|
||||
AUTH : A U T H ;
|
||||
BAD : B A D ;
|
||||
BATCH_INTERVAL : B A T C H UNDERSCORE I N T E R V A L ;
|
||||
BATCH_LIMIT : B A T C H UNDERSCORE L I M I T ;
|
||||
BATCH_SIZE : B A T C H UNDERSCORE S I Z E ;
|
||||
BEFORE : B E F O R E ;
|
||||
BOOTSTRAP_SERVERS : B O O T S T R A P UNDERSCORE S E R V E R S ;
|
||||
CHECK : C H E C K ;
|
||||
CLEAR : C L E A R ;
|
||||
COMMIT : C O M M I T ;
|
||||
COMMITTED : C O M M I T T E D ;
|
||||
CONFIG : C O N F I G ;
|
||||
CONFIGS : C O N F I G S;
|
||||
CONSUMER_GROUP : C O N S U M E R UNDERSCORE G R O U P ;
|
||||
CREDENTIALS : C R E D E N T I A L S ;
|
||||
CSV : C S V ;
|
||||
DATA : D A T A ;
|
||||
DELIMITER : D E L I M I T E R ;
|
||||
DATABASE : D A T A B A S E ;
|
||||
DENY : D E N Y ;
|
||||
DIRECTORY : D I R E C T O R Y ;
|
||||
DROP : D R O P ;
|
||||
DUMP : D U M P ;
|
||||
DURABILITY : D U R A B I L I T Y ;
|
||||
EXECUTE : E X E C U T E ;
|
||||
FOR : F O R ;
|
||||
FREE : F R E E ;
|
||||
FREE_MEMORY : F R E E UNDERSCORE M E M O R Y ;
|
||||
FROM : F R O M ;
|
||||
GLOBAL : G L O B A L ;
|
||||
GRANT : G R A N T ;
|
||||
GRANTS : G R A N T S ;
|
||||
HEADER : H E A D E R ;
|
||||
IDENTIFIED : I D E N T I F I E D ;
|
||||
IGNORE : I G N O R E ;
|
||||
ISOLATION : I S O L A T I O N ;
|
||||
KAFKA : K A F K A ;
|
||||
LEVEL : L E V E L ;
|
||||
LOAD : L O A D ;
|
||||
LOCK : L O C K ;
|
||||
MAIN : M A I N ;
|
||||
MODE : M O D E ;
|
||||
NEXT : N E X T ;
|
||||
NO : N O ;
|
||||
PASSWORD : P A S S W O R D ;
|
||||
PORT : P O R T ;
|
||||
PRIVILEGES : P R I V I L E G E S ;
|
||||
PULSAR : P U L S A R ;
|
||||
READ : R E A D ;
|
||||
READ_FILE : R E A D UNDERSCORE F I L E ;
|
||||
REGISTER : R E G I S T E R ;
|
||||
REPLICA : R E P L I C A ;
|
||||
REPLICAS : R E P L I C A S ;
|
||||
REPLICATION : R E P L I C A T I O N ;
|
||||
REVOKE : R E V O K E ;
|
||||
ROLE : R O L E ;
|
||||
ROLES : R O L E S ;
|
||||
QUOTE : Q U O T E ;
|
||||
SERVICE_URL : S E R V I C E UNDERSCORE U R L ;
|
||||
SESSION : S E S S I O N ;
|
||||
SETTING : S E T T I N G ;
|
||||
SETTINGS : S E T T I N G S ;
|
||||
SNAPSHOT : S N A P S H O T ;
|
||||
START : S T A R T ;
|
||||
STATS : S T A T S ;
|
||||
STOP : S T O P ;
|
||||
STREAM : S T R E A M ;
|
||||
STREAMS : S T R E A M S ;
|
||||
SYNC : S Y N C ;
|
||||
TIMEOUT : T I M E O U T ;
|
||||
TO : T O ;
|
||||
TOPICS : T O P I C S;
|
||||
TRANSACTION : T R A N S A C T I O N ;
|
||||
TRANSFORM : T R A N S F O R M ;
|
||||
TRIGGER : T R I G G E R ;
|
||||
TRIGGERS : T R I G G E R S ;
|
||||
UNCOMMITTED : U N C O M M I T T E D ;
|
||||
UNLOCK : U N L O C K ;
|
||||
UPDATE : U P D A T E ;
|
||||
USER : U S E R ;
|
||||
USERS : U S E R S ;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
;; 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 License, and you may not use this file except in compliance with 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
|
||||
|
||||
@@ -89,61 +89,120 @@ class Trie {
|
||||
|
||||
const int kBitsetSize = 65536;
|
||||
|
||||
const trie::Trie kKeywords = {"union", "all",
|
||||
"optional", "match",
|
||||
"unwind", "as",
|
||||
"merge", "on",
|
||||
"create", "set",
|
||||
"detach", "delete",
|
||||
"remove", "with",
|
||||
"distinct", "return",
|
||||
"order", "by",
|
||||
"skip", "limit",
|
||||
"ascending", "asc",
|
||||
"descending", "desc",
|
||||
"where", "or",
|
||||
"xor", "and",
|
||||
"not", "in",
|
||||
"starts", "ends",
|
||||
"contains", "is",
|
||||
"null", "case",
|
||||
"when", "then",
|
||||
"else", "end",
|
||||
"count", "filter",
|
||||
"extract", "any",
|
||||
"none", "single",
|
||||
"true", "false",
|
||||
"reduce", "coalesce",
|
||||
"user", "password",
|
||||
"alter", "drop",
|
||||
"show", "stats",
|
||||
"unique", "explain",
|
||||
"profile", "storage",
|
||||
"index", "info",
|
||||
"exists", "assert",
|
||||
"constraint", "node",
|
||||
"key", "dump",
|
||||
"database", "call",
|
||||
"yield", "memory",
|
||||
"mb", "kb",
|
||||
"unlimited", "free",
|
||||
"procedure", "query",
|
||||
"free_memory", "read_file",
|
||||
"lock_path", "after",
|
||||
"before", "execute",
|
||||
"transaction", "trigger",
|
||||
"triggers", "update",
|
||||
"comitted", "uncomitted",
|
||||
"global", "isolation",
|
||||
"level", "next",
|
||||
"read", "session",
|
||||
"snapshot", "transaction",
|
||||
"batch_limit", "batch_interval",
|
||||
"batch_size", "consumer_group",
|
||||
"start", "stream",
|
||||
"streams", "transform",
|
||||
"topics", "check",
|
||||
"setting", "settings"};
|
||||
const trie::Trie kKeywords = {"union",
|
||||
"all",
|
||||
"optional",
|
||||
"match",
|
||||
"unwind",
|
||||
"as",
|
||||
"merge",
|
||||
"on",
|
||||
"create",
|
||||
"set",
|
||||
"detach",
|
||||
"delete",
|
||||
"remove",
|
||||
"with",
|
||||
"distinct",
|
||||
"return",
|
||||
"order",
|
||||
"by",
|
||||
"skip",
|
||||
"limit",
|
||||
"ascending",
|
||||
"asc",
|
||||
"descending",
|
||||
"desc",
|
||||
"where",
|
||||
"or",
|
||||
"xor",
|
||||
"and",
|
||||
"not",
|
||||
"in",
|
||||
"starts",
|
||||
"ends",
|
||||
"contains",
|
||||
"is",
|
||||
"null",
|
||||
"case",
|
||||
"when",
|
||||
"then",
|
||||
"else",
|
||||
"end",
|
||||
"count",
|
||||
"filter",
|
||||
"extract",
|
||||
"any",
|
||||
"none",
|
||||
"single",
|
||||
"true",
|
||||
"false",
|
||||
"reduce",
|
||||
"coalesce",
|
||||
"user",
|
||||
"password",
|
||||
"alter",
|
||||
"drop",
|
||||
"show",
|
||||
"stats",
|
||||
"unique",
|
||||
"explain",
|
||||
"profile",
|
||||
"storage",
|
||||
"index",
|
||||
"info",
|
||||
"exists",
|
||||
"assert",
|
||||
"constraint",
|
||||
"node",
|
||||
"key",
|
||||
"dump",
|
||||
"database",
|
||||
"call",
|
||||
"yield",
|
||||
"memory",
|
||||
"mb",
|
||||
"kb",
|
||||
"unlimited",
|
||||
"free",
|
||||
"procedure",
|
||||
"query",
|
||||
"free_memory",
|
||||
"read_file",
|
||||
"lock_path",
|
||||
"after",
|
||||
"before",
|
||||
"execute",
|
||||
"transaction",
|
||||
"trigger",
|
||||
"triggers",
|
||||
"update",
|
||||
"comitted",
|
||||
"uncomitted",
|
||||
"global",
|
||||
"isolation",
|
||||
"level",
|
||||
"next",
|
||||
"read",
|
||||
"session",
|
||||
"snapshot",
|
||||
"transaction",
|
||||
"batch_limit",
|
||||
"batch_interval",
|
||||
"batch_size",
|
||||
"consumer_group",
|
||||
"start",
|
||||
"stream",
|
||||
"streams",
|
||||
"transform",
|
||||
"topics",
|
||||
"check",
|
||||
"setting",
|
||||
"settings",
|
||||
"bootstrap_servers",
|
||||
"kafka",
|
||||
"pulsar",
|
||||
"service_url"};
|
||||
|
||||
// Unicode codepoints that are allowed at the start of the unescaped name.
|
||||
const std::bitset<kBitsetSize> kUnescapedNameAllowedStarts(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -10,9 +10,14 @@
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "query/interpreter.hpp"
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
|
||||
@@ -31,10 +36,11 @@
|
||||
#include "query/frontend/semantic/required_privileges.hpp"
|
||||
#include "query/frontend/semantic/symbol_generator.hpp"
|
||||
#include "query/interpret/eval.hpp"
|
||||
#include "query/metadata.hpp"
|
||||
#include "query/plan/planner.hpp"
|
||||
#include "query/plan/profile.hpp"
|
||||
#include "query/plan/vertex_count_cache.hpp"
|
||||
#include "query/streams.hpp"
|
||||
#include "query/stream/common.hpp"
|
||||
#include "query/trigger.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
@@ -52,6 +58,7 @@
|
||||
#include "utils/settings.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "utils/tsc.hpp"
|
||||
#include "utils/variant_helpers.hpp"
|
||||
|
||||
namespace EventCounter {
|
||||
extern Event ReadQuery;
|
||||
@@ -86,7 +93,8 @@ void UpdateTypeCount(const plan::ReadWriteTypeChecker::RWType type) {
|
||||
|
||||
struct Callback {
|
||||
std::vector<std::string> header;
|
||||
std::function<std::vector<std::vector<TypedValue>>()> fn;
|
||||
using CallbackFunction = std::function<std::vector<std::vector<TypedValue>>()>;
|
||||
CallbackFunction fn;
|
||||
bool should_abort_query{false};
|
||||
};
|
||||
|
||||
@@ -106,6 +114,17 @@ std::optional<TResult> GetOptionalValue(query::Expression *expression, Expressio
|
||||
return {};
|
||||
};
|
||||
|
||||
std::optional<std::string> GetOptionalStringValue(query::Expression *expression, ExpressionEvaluator &evaluator) {
|
||||
if (expression != nullptr) {
|
||||
auto value = expression->Accept(evaluator);
|
||||
MG_ASSERT(value.IsNull() || value.IsString());
|
||||
if (value.IsString()) {
|
||||
return {std::string(value.ValueString().begin(), value.ValueString().end())};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
class ReplQueryHandler final : public query::ReplicationQueryHandler {
|
||||
public:
|
||||
explicit ReplQueryHandler(storage::Storage *db) : db_(db) {}
|
||||
@@ -394,7 +413,8 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
|
||||
}
|
||||
|
||||
Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters ¶meters,
|
||||
InterpreterContext *interpreter_context, DbAccessor *db_accessor) {
|
||||
InterpreterContext *interpreter_context, DbAccessor *db_accessor,
|
||||
std::vector<Notification> *notifications) {
|
||||
Frame frame(0);
|
||||
SymbolTable symbol_table;
|
||||
EvaluationContext evaluation_context;
|
||||
@@ -412,11 +432,19 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
|
||||
if (port.IsInt()) {
|
||||
maybe_port = port.ValueInt();
|
||||
}
|
||||
if (maybe_port == 7687 && repl_query->role_ == ReplicationQuery::ReplicationRole::REPLICA) {
|
||||
notifications->emplace_back(SeverityLevel::WARNING, NotificationCode::REPLICA_PORT_WARNING,
|
||||
"Be careful the replication port must be different from the memgraph port!");
|
||||
}
|
||||
callback.fn = [handler = ReplQueryHandler{interpreter_context->db}, role = repl_query->role_,
|
||||
maybe_port]() mutable {
|
||||
handler.SetReplicationRole(role, maybe_port);
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
notifications->emplace_back(
|
||||
SeverityLevel::INFO, NotificationCode::SET_REPLICA,
|
||||
fmt::format("Replica role set to {}.",
|
||||
repl_query->role_ == ReplicationQuery::ReplicationRole::MAIN ? "MAIN" : "REPLICA"));
|
||||
return callback;
|
||||
}
|
||||
case ReplicationQuery::Action::SHOW_REPLICATION_ROLE: {
|
||||
@@ -450,6 +478,8 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
|
||||
handler.RegisterReplica(name, std::string(socket_address.ValueString()), sync_mode, maybe_timeout);
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
notifications->emplace_back(SeverityLevel::INFO, NotificationCode::REGISTER_REPLICA,
|
||||
fmt::format("Replica {} is registered.", repl_query->replica_name_));
|
||||
return callback;
|
||||
}
|
||||
case ReplicationQuery::Action::DROP_REPLICA: {
|
||||
@@ -458,6 +488,8 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &
|
||||
handler.DropReplica(name);
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
notifications->emplace_back(SeverityLevel::INFO, NotificationCode::DROP_REPLICA,
|
||||
fmt::format("Replica {} is dropped.", repl_query->replica_name_));
|
||||
return callback;
|
||||
}
|
||||
case ReplicationQuery::Action::SHOW_REPLICAS: {
|
||||
@@ -500,9 +532,100 @@ std::optional<std::string> StringPointerToOptional(const std::string *str) {
|
||||
return str == nullptr ? std::nullopt : std::make_optional(*str);
|
||||
}
|
||||
|
||||
stream::CommonStreamInfo GetCommonStreamInfo(StreamQuery *stream_query, ExpressionEvaluator &evaluator) {
|
||||
return {
|
||||
.batch_interval = GetOptionalValue<std::chrono::milliseconds>(stream_query->batch_interval_, evaluator)
|
||||
.value_or(stream::kDefaultBatchInterval),
|
||||
.batch_size = GetOptionalValue<int64_t>(stream_query->batch_size_, evaluator).value_or(stream::kDefaultBatchSize),
|
||||
.transformation_name = stream_query->transform_name_};
|
||||
}
|
||||
|
||||
std::vector<std::string> EvaluateTopicNames(ExpressionEvaluator &evaluator,
|
||||
std::variant<Expression *, std::vector<std::string>> topic_variant) {
|
||||
return std::visit(utils::Overloaded{[&](Expression *expression) {
|
||||
auto topic_names = expression->Accept(evaluator);
|
||||
MG_ASSERT(topic_names.IsString());
|
||||
return utils::Split(topic_names.ValueString(), ",");
|
||||
},
|
||||
[&](std::vector<std::string> topic_names) { return topic_names; }},
|
||||
std::move(topic_variant));
|
||||
}
|
||||
|
||||
Callback::CallbackFunction GetKafkaCreateCallback(StreamQuery *stream_query, ExpressionEvaluator &evaluator,
|
||||
InterpreterContext *interpreter_context,
|
||||
const std::string *username) {
|
||||
constexpr std::string_view kDefaultConsumerGroup = "mg_consumer";
|
||||
std::string consumer_group{stream_query->consumer_group_.empty() ? kDefaultConsumerGroup
|
||||
: stream_query->consumer_group_};
|
||||
|
||||
auto bootstrap = GetOptionalStringValue(stream_query->bootstrap_servers_, evaluator);
|
||||
if (bootstrap && bootstrap->empty()) {
|
||||
throw SemanticException("Bootstrap servers must not be an empty string!");
|
||||
}
|
||||
auto common_stream_info = GetCommonStreamInfo(stream_query, evaluator);
|
||||
|
||||
const auto get_config_map = [&evaluator](std::unordered_map<Expression *, Expression *> map,
|
||||
std::string_view map_name) -> std::unordered_map<std::string, std::string> {
|
||||
std::unordered_map<std::string, std::string> config_map;
|
||||
for (const auto [key_expr, value_expr] : map) {
|
||||
const auto key = key_expr->Accept(evaluator);
|
||||
const auto value = value_expr->Accept(evaluator);
|
||||
if (!key.IsString() || !value.IsString()) {
|
||||
throw SemanticException("{} must contain only string keys and values!", map_name);
|
||||
}
|
||||
config_map.emplace(key.ValueString(), value.ValueString());
|
||||
}
|
||||
return config_map;
|
||||
};
|
||||
|
||||
return [interpreter_context, stream_name = stream_query->stream_name_,
|
||||
topic_names = EvaluateTopicNames(evaluator, stream_query->topic_names_),
|
||||
consumer_group = std::move(consumer_group), common_stream_info = std::move(common_stream_info),
|
||||
bootstrap_servers = std::move(bootstrap), owner = StringPointerToOptional(username),
|
||||
configs = get_config_map(stream_query->configs_, "Configs"),
|
||||
credentials = get_config_map(stream_query->credentials_, "Credentials")]() mutable {
|
||||
std::string bootstrap = bootstrap_servers
|
||||
? std::move(*bootstrap_servers)
|
||||
: std::string{interpreter_context->config.default_kafka_bootstrap_servers};
|
||||
interpreter_context->streams.Create<query::stream::KafkaStream>(stream_name,
|
||||
{.common_info = std::move(common_stream_info),
|
||||
.topics = std::move(topic_names),
|
||||
.consumer_group = std::move(consumer_group),
|
||||
.bootstrap_servers = std::move(bootstrap),
|
||||
.configs = std::move(configs),
|
||||
.credentials = std::move(credentials)},
|
||||
std::move(owner));
|
||||
|
||||
return std::vector<std::vector<TypedValue>>{};
|
||||
};
|
||||
}
|
||||
|
||||
Callback::CallbackFunction GetPulsarCreateCallback(StreamQuery *stream_query, ExpressionEvaluator &evaluator,
|
||||
InterpreterContext *interpreter_context,
|
||||
const std::string *username) {
|
||||
auto service_url = GetOptionalStringValue(stream_query->service_url_, evaluator);
|
||||
if (service_url && service_url->empty()) {
|
||||
throw SemanticException("Service URL must not be an empty string!");
|
||||
}
|
||||
auto common_stream_info = GetCommonStreamInfo(stream_query, evaluator);
|
||||
return [interpreter_context, stream_name = stream_query->stream_name_,
|
||||
topic_names = EvaluateTopicNames(evaluator, stream_query->topic_names_),
|
||||
common_stream_info = std::move(common_stream_info), service_url = std::move(service_url),
|
||||
owner = StringPointerToOptional(username)]() mutable {
|
||||
std::string url =
|
||||
service_url ? std::move(*service_url) : std::string{interpreter_context->config.default_pulsar_service_url};
|
||||
interpreter_context->streams.Create<query::stream::PulsarStream>(
|
||||
stream_name,
|
||||
{.common_info = std::move(common_stream_info), .topics = std::move(topic_names), .service_url = std::move(url)},
|
||||
std::move(owner));
|
||||
|
||||
return std::vector<std::vector<TypedValue>>{};
|
||||
};
|
||||
}
|
||||
|
||||
Callback HandleStreamQuery(StreamQuery *stream_query, const Parameters ¶meters,
|
||||
InterpreterContext *interpreter_context, DbAccessor *db_accessor,
|
||||
const std::string *username) {
|
||||
const std::string *username, std::vector<Notification> *notifications) {
|
||||
Frame frame(0);
|
||||
SymbolTable symbol_table;
|
||||
EvaluationContext evaluation_context;
|
||||
@@ -516,25 +639,16 @@ Callback HandleStreamQuery(StreamQuery *stream_query, const Parameters ¶mete
|
||||
switch (stream_query->action_) {
|
||||
case StreamQuery::Action::CREATE_STREAM: {
|
||||
EventCounter::IncrementCounter(EventCounter::StreamsCreated);
|
||||
constexpr std::string_view kDefaultConsumerGroup = "mg_consumer";
|
||||
std::string consumer_group{stream_query->consumer_group_.empty() ? kDefaultConsumerGroup
|
||||
: stream_query->consumer_group_};
|
||||
|
||||
callback.fn =
|
||||
[interpreter_context, stream_name = stream_query->stream_name_, topic_names = stream_query->topic_names_,
|
||||
consumer_group = std::move(consumer_group),
|
||||
batch_interval = GetOptionalValue<std::chrono::milliseconds>(stream_query->batch_interval_, evaluator),
|
||||
batch_size = GetOptionalValue<int64_t>(stream_query->batch_size_, evaluator),
|
||||
transformation_name = stream_query->transform_name_, owner = StringPointerToOptional(username)]() mutable {
|
||||
interpreter_context->streams.Create(stream_name,
|
||||
query::StreamInfo{.topics = std::move(topic_names),
|
||||
.consumer_group = std::move(consumer_group),
|
||||
.batch_interval = batch_interval,
|
||||
.batch_size = batch_size,
|
||||
.transformation_name = std::move(transformation_name),
|
||||
.owner = std::move(owner)});
|
||||
return std::vector<std::vector<TypedValue>>{};
|
||||
};
|
||||
switch (stream_query->type_) {
|
||||
case StreamQuery::Type::KAFKA:
|
||||
callback.fn = GetKafkaCreateCallback(stream_query, evaluator, interpreter_context, username);
|
||||
break;
|
||||
case StreamQuery::Type::PULSAR:
|
||||
callback.fn = GetPulsarCreateCallback(stream_query, evaluator, interpreter_context, username);
|
||||
break;
|
||||
}
|
||||
notifications->emplace_back(SeverityLevel::INFO, NotificationCode::CREATE_STREAM,
|
||||
fmt::format("Created stream {}.", stream_query->stream_name_));
|
||||
return callback;
|
||||
}
|
||||
case StreamQuery::Action::START_STREAM: {
|
||||
@@ -542,6 +656,8 @@ Callback HandleStreamQuery(StreamQuery *stream_query, const Parameters ¶mete
|
||||
interpreter_context->streams.Start(stream_name);
|
||||
return std::vector<std::vector<TypedValue>>{};
|
||||
};
|
||||
notifications->emplace_back(SeverityLevel::INFO, NotificationCode::START_STREAM,
|
||||
fmt::format("Started stream {}.", stream_query->stream_name_));
|
||||
return callback;
|
||||
}
|
||||
case StreamQuery::Action::START_ALL_STREAMS: {
|
||||
@@ -549,6 +665,7 @@ Callback HandleStreamQuery(StreamQuery *stream_query, const Parameters ¶mete
|
||||
interpreter_context->streams.StartAll();
|
||||
return std::vector<std::vector<TypedValue>>{};
|
||||
};
|
||||
notifications->emplace_back(SeverityLevel::INFO, NotificationCode::START_ALL_STREAMS, "Started all streams.");
|
||||
return callback;
|
||||
}
|
||||
case StreamQuery::Action::STOP_STREAM: {
|
||||
@@ -556,6 +673,8 @@ Callback HandleStreamQuery(StreamQuery *stream_query, const Parameters ¶mete
|
||||
interpreter_context->streams.Stop(stream_name);
|
||||
return std::vector<std::vector<TypedValue>>{};
|
||||
};
|
||||
notifications->emplace_back(SeverityLevel::INFO, NotificationCode::STOP_STREAM,
|
||||
fmt::format("Stopped stream {}.", stream_query->stream_name_));
|
||||
return callback;
|
||||
}
|
||||
case StreamQuery::Action::STOP_ALL_STREAMS: {
|
||||
@@ -563,6 +682,7 @@ Callback HandleStreamQuery(StreamQuery *stream_query, const Parameters ¶mete
|
||||
interpreter_context->streams.StopAll();
|
||||
return std::vector<std::vector<TypedValue>>{};
|
||||
};
|
||||
notifications->emplace_back(SeverityLevel::INFO, NotificationCode::STOP_ALL_STREAMS, "Stopped all streams.");
|
||||
return callback;
|
||||
}
|
||||
case StreamQuery::Action::DROP_STREAM: {
|
||||
@@ -570,51 +690,33 @@ Callback HandleStreamQuery(StreamQuery *stream_query, const Parameters ¶mete
|
||||
interpreter_context->streams.Drop(stream_name);
|
||||
return std::vector<std::vector<TypedValue>>{};
|
||||
};
|
||||
notifications->emplace_back(SeverityLevel::INFO, NotificationCode::DROP_STREAM,
|
||||
fmt::format("Dropped stream {}.", stream_query->stream_name_));
|
||||
return callback;
|
||||
}
|
||||
case StreamQuery::Action::SHOW_STREAMS: {
|
||||
callback.header = {"name", "topics", "consumer_group", "batch_interval", "batch_size", "transformation_name",
|
||||
"owner", "is running"};
|
||||
callback.header = {"name", "type", "batch_interval", "batch_size", "transformation_name", "owner", "is running"};
|
||||
callback.fn = [interpreter_context]() {
|
||||
auto streams_status = interpreter_context->streams.GetStreamInfo();
|
||||
std::vector<std::vector<TypedValue>> results;
|
||||
results.reserve(streams_status.size());
|
||||
auto topics_as_typed_topics = [](const auto &topics) {
|
||||
std::vector<TypedValue> typed_topics;
|
||||
typed_topics.reserve(topics.size());
|
||||
for (const auto &elem : topics) {
|
||||
typed_topics.emplace_back(elem);
|
||||
}
|
||||
return typed_topics;
|
||||
};
|
||||
|
||||
auto stream_info_as_typed_stream_info_emplace_in = [topics_as_typed_topics](auto &typed_status,
|
||||
const auto &stream_info) {
|
||||
typed_status.emplace_back(topics_as_typed_topics(stream_info.topics));
|
||||
typed_status.emplace_back(stream_info.consumer_group);
|
||||
if (stream_info.batch_interval.has_value()) {
|
||||
typed_status.emplace_back(stream_info.batch_interval->count());
|
||||
} else {
|
||||
typed_status.emplace_back();
|
||||
}
|
||||
if (stream_info.batch_size.has_value()) {
|
||||
typed_status.emplace_back(*stream_info.batch_size);
|
||||
} else {
|
||||
typed_status.emplace_back();
|
||||
}
|
||||
auto stream_info_as_typed_stream_info_emplace_in = [](auto &typed_status, const auto &stream_info) {
|
||||
typed_status.emplace_back(stream_info.batch_interval.count());
|
||||
typed_status.emplace_back(stream_info.batch_size);
|
||||
typed_status.emplace_back(stream_info.transformation_name);
|
||||
if (stream_info.owner.has_value()) {
|
||||
typed_status.emplace_back(*stream_info.owner);
|
||||
} else {
|
||||
typed_status.emplace_back();
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto &status : streams_status) {
|
||||
std::vector<TypedValue> typed_status;
|
||||
typed_status.reserve(7);
|
||||
typed_status.emplace_back(status.name);
|
||||
typed_status.emplace_back(StreamSourceTypeToString(status.type));
|
||||
stream_info_as_typed_stream_info_emplace_in(typed_status, status.info);
|
||||
if (status.owner.has_value()) {
|
||||
typed_status.emplace_back(*status.owner);
|
||||
} else {
|
||||
typed_status.emplace_back();
|
||||
}
|
||||
typed_status.emplace_back(status.is_running);
|
||||
results.push_back(std::move(typed_status));
|
||||
}
|
||||
@@ -630,6 +732,8 @@ Callback HandleStreamQuery(StreamQuery *stream_query, const Parameters ¶mete
|
||||
batch_limit = GetOptionalValue<int64_t>(stream_query->batch_limit_, evaluator)]() mutable {
|
||||
return interpreter_context->streams.Check(stream_name, timeout, batch_limit);
|
||||
};
|
||||
notifications->emplace_back(SeverityLevel::INFO, NotificationCode::CHECK_STREAM,
|
||||
fmt::format("Checked stream {}.", stream_query->stream_name_));
|
||||
return callback;
|
||||
}
|
||||
}
|
||||
@@ -860,8 +964,19 @@ std::optional<plan::ProfilingStatsWithTotalTime> PullPlan::Pull(AnyStream *strea
|
||||
if (has_unsent_results_) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
summary->insert_or_assign("plan_execution_time", execution_time_.count());
|
||||
// We are finished with pulling all the data, therefore we can send any
|
||||
// metadata about the results i.e. notifications and statistics
|
||||
const bool is_any_counter_set =
|
||||
std::any_of(ctx_.execution_stats.counters.begin(), ctx_.execution_stats.counters.end(),
|
||||
[](const auto &counter) { return counter > 0; });
|
||||
if (is_any_counter_set) {
|
||||
std::map<std::string, TypedValue> stats;
|
||||
for (size_t i = 0; i < ctx_.execution_stats.counters.size(); ++i) {
|
||||
stats.emplace(ExecutionStatsKeyToString(ExecutionStats::Key(i)), ctx_.execution_stats.counters[i]);
|
||||
}
|
||||
summary->insert_or_assign("stats", std::move(stats));
|
||||
}
|
||||
cursor_->Shutdown();
|
||||
ctx_.profile_execution_time = execution_time_;
|
||||
return GetStatsWithTotalTime(ctx_);
|
||||
@@ -871,11 +986,12 @@ using RWType = plan::ReadWriteTypeChecker::RWType;
|
||||
} // namespace
|
||||
|
||||
InterpreterContext::InterpreterContext(storage::Storage *db, const InterpreterConfig config,
|
||||
const std::filesystem::path &data_directory, std::string kafka_bootstrap_servers)
|
||||
const std::filesystem::path &data_directory)
|
||||
: db(db),
|
||||
trigger_store(data_directory / "triggers"),
|
||||
config(config),
|
||||
streams{this, std::move(kafka_bootstrap_servers), data_directory / "streams"} {}
|
||||
trigger_store(data_directory / "triggers"),
|
||||
after_commit_trigger_pool{config.after_commit_trigger_pool_size},
|
||||
streams{this, data_directory / "streams"} {}
|
||||
|
||||
Interpreter::Interpreter(InterpreterContext *interpreter_context) : interpreter_context_(interpreter_context) {
|
||||
MG_ASSERT(interpreter_context_, "Interpreter context must not be NULL");
|
||||
@@ -945,7 +1061,7 @@ PreparedQuery Interpreter::PrepareTransactionQuery(std::string_view query_upper)
|
||||
|
||||
PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string, TypedValue> *summary,
|
||||
InterpreterContext *interpreter_context, DbAccessor *dba,
|
||||
utils::MemoryResource *execution_memory,
|
||||
utils::MemoryResource *execution_memory, std::vector<Notification> *notifications,
|
||||
TriggerContextCollector *trigger_context_collector = nullptr) {
|
||||
auto *cypher_query = utils::Downcast<CypherQuery>(parsed_query.query);
|
||||
|
||||
@@ -960,6 +1076,15 @@ PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string,
|
||||
spdlog::info("Running query with memory limit of {}", utils::GetReadableSize(*memory_limit));
|
||||
}
|
||||
|
||||
if (const auto &clauses = cypher_query->single_query_->clauses_; std::any_of(
|
||||
clauses.begin(), clauses.end(), [](const auto *clause) { return clause->GetTypeInfo() == LoadCsv::kType; })) {
|
||||
notifications->emplace_back(
|
||||
SeverityLevel::INFO, NotificationCode::LOAD_CSV_TIP,
|
||||
"It's important to note that the parser parses the values as strings. It's up to the user to "
|
||||
"convert the parsed row values to the appropriate type. This can be done using the built-in "
|
||||
"conversion functions such as ToInteger, ToFloat, ToBoolean etc.");
|
||||
}
|
||||
|
||||
auto plan = CypherQueryToPlan(parsed_query.stripped_query.hash(), std::move(parsed_query.ast_storage), cypher_query,
|
||||
parsed_query.parameters,
|
||||
parsed_query.is_cacheable ? &interpreter_context->plan_cache : nullptr, dba);
|
||||
@@ -980,7 +1105,6 @@ PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string,
|
||||
header.push_back(
|
||||
utils::FindOr(parsed_query.stripped_query.named_expressions(), symbol.token_position(), symbol.name()).first);
|
||||
}
|
||||
|
||||
auto pull_plan = std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context,
|
||||
execution_memory, trigger_context_collector, memory_limit);
|
||||
return PreparedQuery{std::move(header), std::move(parsed_query.required_privileges),
|
||||
@@ -1138,14 +1262,13 @@ PreparedQuery PrepareDumpQuery(ParsedQuery parsed_query, std::map<std::string, T
|
||||
}
|
||||
|
||||
PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
|
||||
std::map<std::string, TypedValue> *summary, InterpreterContext *interpreter_context,
|
||||
utils::MemoryResource *execution_memory) {
|
||||
std::vector<Notification> *notifications, InterpreterContext *interpreter_context) {
|
||||
if (in_explicit_transaction) {
|
||||
throw IndexInMulticommandTxException();
|
||||
}
|
||||
|
||||
auto *index_query = utils::Downcast<IndexQuery>(parsed_query.query);
|
||||
std::function<void()> handler;
|
||||
std::function<void(Notification &)> handler;
|
||||
|
||||
// Creating an index influences computed plan costs.
|
||||
auto invalidate_plan_cache = [plan_cache = &interpreter_context->plan_cache] {
|
||||
@@ -1156,26 +1279,45 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans
|
||||
};
|
||||
|
||||
auto label = interpreter_context->db->NameToLabel(index_query->label_.name);
|
||||
|
||||
std::vector<storage::PropertyId> properties;
|
||||
std::vector<std::string> properties_string;
|
||||
properties.reserve(index_query->properties_.size());
|
||||
properties_string.reserve(index_query->properties_.size());
|
||||
for (const auto &prop : index_query->properties_) {
|
||||
properties.push_back(interpreter_context->db->NameToProperty(prop.name));
|
||||
properties_string.push_back(prop.name);
|
||||
}
|
||||
auto properties_stringified = utils::Join(properties_string, ", ");
|
||||
|
||||
if (properties.size() > 1) {
|
||||
throw utils::NotYetImplemented("index on multiple properties");
|
||||
}
|
||||
|
||||
Notification index_notification(SeverityLevel::INFO);
|
||||
switch (index_query->action_) {
|
||||
case IndexQuery::Action::CREATE: {
|
||||
handler = [interpreter_context, label, properties = std::move(properties),
|
||||
invalidate_plan_cache = std::move(invalidate_plan_cache)] {
|
||||
index_notification.code = NotificationCode::CREATE_INDEX;
|
||||
index_notification.title =
|
||||
fmt::format("Created index on label {} on properties {}.", index_query->label_.name, properties_stringified);
|
||||
|
||||
handler = [interpreter_context, label, properties_stringified = std::move(properties_stringified),
|
||||
label_name = index_query->label_.name, properties = std::move(properties),
|
||||
invalidate_plan_cache = std::move(invalidate_plan_cache)](Notification &index_notification) {
|
||||
if (properties.empty()) {
|
||||
interpreter_context->db->CreateIndex(label);
|
||||
if (!interpreter_context->db->CreateIndex(label)) {
|
||||
index_notification.code = NotificationCode::EXISTANT_INDEX;
|
||||
index_notification.title =
|
||||
fmt::format("Index on label {} on properties {} already exists.", label_name, properties_stringified);
|
||||
}
|
||||
EventCounter::IncrementCounter(EventCounter::LabelIndexCreated);
|
||||
} else {
|
||||
MG_ASSERT(properties.size() == 1U);
|
||||
interpreter_context->db->CreateIndex(label, properties[0]);
|
||||
if (!interpreter_context->db->CreateIndex(label, properties[0])) {
|
||||
index_notification.code = NotificationCode::EXISTANT_INDEX;
|
||||
index_notification.title =
|
||||
fmt::format("Index on label {} on properties {} already exists.", label_name, properties_stringified);
|
||||
}
|
||||
EventCounter::IncrementCounter(EventCounter::LabelPropertyIndexCreated);
|
||||
}
|
||||
invalidate_plan_cache();
|
||||
@@ -1183,13 +1325,25 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans
|
||||
break;
|
||||
}
|
||||
case IndexQuery::Action::DROP: {
|
||||
handler = [interpreter_context, label, properties = std::move(properties),
|
||||
invalidate_plan_cache = std::move(invalidate_plan_cache)] {
|
||||
index_notification.code = NotificationCode::DROP_INDEX;
|
||||
index_notification.title = fmt::format("Dropped index on label {} on properties {}.", index_query->label_.name,
|
||||
utils::Join(properties_string, ", "));
|
||||
handler = [interpreter_context, label, properties_stringified = std::move(properties_stringified),
|
||||
label_name = index_query->label_.name, properties = std::move(properties),
|
||||
invalidate_plan_cache = std::move(invalidate_plan_cache)](Notification &index_notification) {
|
||||
if (properties.empty()) {
|
||||
interpreter_context->db->DropIndex(label);
|
||||
if (!interpreter_context->db->DropIndex(label)) {
|
||||
index_notification.code = NotificationCode::NONEXISTANT_INDEX;
|
||||
index_notification.title =
|
||||
fmt::format("Index on label {} on properties {} doesn't exist.", label_name, properties_stringified);
|
||||
}
|
||||
} else {
|
||||
MG_ASSERT(properties.size() == 1U);
|
||||
interpreter_context->db->DropIndex(label, properties[0]);
|
||||
if (!interpreter_context->db->DropIndex(label, properties[0])) {
|
||||
index_notification.code = NotificationCode::NONEXISTANT_INDEX;
|
||||
index_notification.title =
|
||||
fmt::format("Index on label {} on properties {} doesn't exist.", label_name, properties_stringified);
|
||||
}
|
||||
}
|
||||
invalidate_plan_cache();
|
||||
};
|
||||
@@ -1197,13 +1351,16 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans
|
||||
}
|
||||
}
|
||||
|
||||
return PreparedQuery{{},
|
||||
std::move(parsed_query.required_privileges),
|
||||
[handler = std::move(handler)](AnyStream *stream, std::optional<int>) {
|
||||
handler();
|
||||
return QueryHandlerResult::NOTHING;
|
||||
},
|
||||
RWType::W};
|
||||
return PreparedQuery{
|
||||
{},
|
||||
std::move(parsed_query.required_privileges),
|
||||
[handler = std::move(handler), notifications, index_notification = std::move(index_notification)](
|
||||
AnyStream * /*stream*/, std::optional<int> /*unused*/) mutable {
|
||||
handler(index_notification);
|
||||
notifications->push_back(index_notification);
|
||||
return QueryHandlerResult::NOTHING;
|
||||
},
|
||||
RWType::W};
|
||||
}
|
||||
|
||||
PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
|
||||
@@ -1243,13 +1400,15 @@ PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transa
|
||||
}
|
||||
|
||||
PreparedQuery PrepareReplicationQuery(ParsedQuery parsed_query, const bool in_explicit_transaction,
|
||||
InterpreterContext *interpreter_context, DbAccessor *dba) {
|
||||
std::vector<Notification> *notifications, InterpreterContext *interpreter_context,
|
||||
DbAccessor *dba) {
|
||||
if (in_explicit_transaction) {
|
||||
throw ReplicationModificationInMulticommandTxException();
|
||||
}
|
||||
|
||||
auto *replication_query = utils::Downcast<ReplicationQuery>(parsed_query.query);
|
||||
auto callback = HandleReplicationQuery(replication_query, parsed_query.parameters, interpreter_context, dba);
|
||||
auto callback =
|
||||
HandleReplicationQuery(replication_query, parsed_query.parameters, interpreter_context, dba, notifications);
|
||||
|
||||
return PreparedQuery{callback.header, std::move(parsed_query.required_privileges),
|
||||
[callback_fn = std::move(callback.fn), pull_plan = std::shared_ptr<PullPlanVector>{nullptr}](
|
||||
@@ -1398,8 +1557,8 @@ Callback ShowTriggers(InterpreterContext *interpreter_context) {
|
||||
}
|
||||
|
||||
PreparedQuery PrepareTriggerQuery(ParsedQuery parsed_query, const bool in_explicit_transaction,
|
||||
InterpreterContext *interpreter_context, DbAccessor *dba,
|
||||
const std::map<std::string, storage::PropertyValue> &user_parameters,
|
||||
std::vector<Notification> *notifications, InterpreterContext *interpreter_context,
|
||||
DbAccessor *dba, const std::map<std::string, storage::PropertyValue> &user_parameters,
|
||||
const std::string *username) {
|
||||
if (in_explicit_transaction) {
|
||||
throw TriggerModificationInMulticommandTxException();
|
||||
@@ -1408,27 +1567,36 @@ PreparedQuery PrepareTriggerQuery(ParsedQuery parsed_query, const bool in_explic
|
||||
auto *trigger_query = utils::Downcast<TriggerQuery>(parsed_query.query);
|
||||
MG_ASSERT(trigger_query);
|
||||
|
||||
auto callback = [trigger_query, interpreter_context, dba, &user_parameters,
|
||||
owner = StringPointerToOptional(username)]() mutable {
|
||||
std::optional<Notification> trigger_notification;
|
||||
auto callback = std::invoke([trigger_query, interpreter_context, dba, &user_parameters,
|
||||
owner = StringPointerToOptional(username), &trigger_notification]() mutable {
|
||||
switch (trigger_query->action_) {
|
||||
case TriggerQuery::Action::CREATE_TRIGGER:
|
||||
trigger_notification.emplace(SeverityLevel::INFO, NotificationCode::CREATE_TRIGGER,
|
||||
fmt::format("Created trigger {}.", trigger_query->trigger_name_));
|
||||
EventCounter::IncrementCounter(EventCounter::TriggersCreated);
|
||||
return CreateTrigger(trigger_query, user_parameters, interpreter_context, dba, std::move(owner));
|
||||
case TriggerQuery::Action::DROP_TRIGGER:
|
||||
trigger_notification.emplace(SeverityLevel::INFO, NotificationCode::DROP_TRIGGER,
|
||||
fmt::format("Dropped trigger {}.", trigger_query->trigger_name_));
|
||||
return DropTrigger(trigger_query, interpreter_context);
|
||||
case TriggerQuery::Action::SHOW_TRIGGERS:
|
||||
return ShowTriggers(interpreter_context);
|
||||
}
|
||||
}();
|
||||
});
|
||||
|
||||
return PreparedQuery{std::move(callback.header), std::move(parsed_query.required_privileges),
|
||||
[callback_fn = std::move(callback.fn), pull_plan = std::shared_ptr<PullPlanVector>{nullptr}](
|
||||
[callback_fn = std::move(callback.fn), pull_plan = std::shared_ptr<PullPlanVector>{nullptr},
|
||||
trigger_notification = std::move(trigger_notification), notifications](
|
||||
AnyStream *stream, std::optional<int> n) mutable -> std::optional<QueryHandlerResult> {
|
||||
if (UNLIKELY(!pull_plan)) {
|
||||
pull_plan = std::make_shared<PullPlanVector>(callback_fn());
|
||||
}
|
||||
|
||||
if (pull_plan->Pull(stream, n)) {
|
||||
if (trigger_notification) {
|
||||
notifications->push_back(std::move(*trigger_notification));
|
||||
}
|
||||
return QueryHandlerResult::COMMIT;
|
||||
}
|
||||
return std::nullopt;
|
||||
@@ -1439,8 +1607,9 @@ PreparedQuery PrepareTriggerQuery(ParsedQuery parsed_query, const bool in_explic
|
||||
}
|
||||
|
||||
PreparedQuery PrepareStreamQuery(ParsedQuery parsed_query, const bool in_explicit_transaction,
|
||||
InterpreterContext *interpreter_context, DbAccessor *dba,
|
||||
const std::map<std::string, storage::PropertyValue> &user_parameters,
|
||||
std::vector<Notification> *notifications, InterpreterContext *interpreter_context,
|
||||
DbAccessor *dba,
|
||||
const std::map<std::string, storage::PropertyValue> & /*user_parameters*/,
|
||||
const std::string *username) {
|
||||
if (in_explicit_transaction) {
|
||||
throw StreamQueryInMulticommandTxException();
|
||||
@@ -1448,7 +1617,8 @@ PreparedQuery PrepareStreamQuery(ParsedQuery parsed_query, const bool in_explici
|
||||
|
||||
auto *stream_query = utils::Downcast<StreamQuery>(parsed_query.query);
|
||||
MG_ASSERT(stream_query);
|
||||
auto callback = HandleStreamQuery(stream_query, parsed_query.parameters, interpreter_context, dba, username);
|
||||
auto callback =
|
||||
HandleStreamQuery(stream_query, parsed_query.parameters, interpreter_context, dba, username, notifications);
|
||||
|
||||
return PreparedQuery{std::move(callback.header), std::move(parsed_query.required_privileges),
|
||||
[callback_fn = std::move(callback.fn), pull_plan = std::shared_ptr<PullPlanVector>{nullptr}](
|
||||
@@ -1648,24 +1818,31 @@ PreparedQuery PrepareInfoQuery(ParsedQuery parsed_query, bool in_explicit_transa
|
||||
}
|
||||
|
||||
PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
|
||||
std::map<std::string, TypedValue> *summary,
|
||||
InterpreterContext *interpreter_context, utils::MemoryResource *execution_memory) {
|
||||
std::vector<Notification> *notifications,
|
||||
InterpreterContext *interpreter_context) {
|
||||
if (in_explicit_transaction) {
|
||||
throw ConstraintInMulticommandTxException();
|
||||
}
|
||||
|
||||
auto *constraint_query = utils::Downcast<ConstraintQuery>(parsed_query.query);
|
||||
std::function<void()> handler;
|
||||
std::function<void(Notification &)> handler;
|
||||
|
||||
auto label = interpreter_context->db->NameToLabel(constraint_query->constraint_.label.name);
|
||||
std::vector<storage::PropertyId> properties;
|
||||
std::vector<std::string> properties_string;
|
||||
properties.reserve(constraint_query->constraint_.properties.size());
|
||||
properties_string.reserve(constraint_query->constraint_.properties.size());
|
||||
for (const auto &prop : constraint_query->constraint_.properties) {
|
||||
properties.push_back(interpreter_context->db->NameToProperty(prop.name));
|
||||
properties_string.push_back(prop.name);
|
||||
}
|
||||
auto properties_stringified = utils::Join(properties_string, ", ");
|
||||
|
||||
Notification constraint_notification(SeverityLevel::INFO);
|
||||
switch (constraint_query->action_type_) {
|
||||
case ConstraintQuery::ActionType::CREATE: {
|
||||
constraint_notification.code = NotificationCode::CREATE_CONSTRAINT;
|
||||
|
||||
switch (constraint_query->constraint_.type) {
|
||||
case Constraint::Type::NODE_KEY:
|
||||
throw utils::NotYetImplemented("Node key constraints");
|
||||
@@ -1673,7 +1850,11 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_
|
||||
if (properties.empty() || properties.size() > 1) {
|
||||
throw SyntaxException("Exactly one property must be used for existence constraints.");
|
||||
}
|
||||
handler = [interpreter_context, label, properties = std::move(properties)] {
|
||||
constraint_notification.title = fmt::format("Created EXISTS constraint on label {} on properties {}.",
|
||||
constraint_query->constraint_.label.name, properties_stringified);
|
||||
handler = [interpreter_context, label, label_name = constraint_query->constraint_.label.name,
|
||||
properties_stringified = std::move(properties_stringified),
|
||||
properties = std::move(properties)](Notification &constraint_notification) {
|
||||
auto res = interpreter_context->db->CreateExistenceConstraint(label, properties[0]);
|
||||
if (res.HasError()) {
|
||||
auto violation = res.GetError();
|
||||
@@ -1685,6 +1866,11 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_
|
||||
"existing node violates it.",
|
||||
label_name, property_name);
|
||||
}
|
||||
if (res.HasValue() && !res.GetValue()) {
|
||||
constraint_notification.code = NotificationCode::EXISTANT_CONSTRAINT;
|
||||
constraint_notification.title = fmt::format(
|
||||
"Constraint EXISTS on label {} on properties {} already exists.", label_name, properties_stringified);
|
||||
}
|
||||
};
|
||||
break;
|
||||
case Constraint::Type::UNIQUE:
|
||||
@@ -1695,7 +1881,12 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_
|
||||
if (property_set.size() != properties.size()) {
|
||||
throw SyntaxException("The given set of properties contains duplicates.");
|
||||
}
|
||||
handler = [interpreter_context, label, property_set = std::move(property_set)] {
|
||||
constraint_notification.title =
|
||||
fmt::format("Created UNIQUE constraint on label {} on properties {}.",
|
||||
constraint_query->constraint_.label.name, utils::Join(properties_string, ", "));
|
||||
handler = [interpreter_context, label, label_name = constraint_query->constraint_.label.name,
|
||||
properties_stringified = std::move(properties_stringified),
|
||||
property_set = std::move(property_set)](Notification &constraint_notification) {
|
||||
auto res = interpreter_context->db->CreateUniqueConstraint(label, property_set);
|
||||
if (res.HasError()) {
|
||||
auto violation = res.GetError();
|
||||
@@ -1709,29 +1900,33 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_
|
||||
"Unable to create unique constraint :{}({}), because an "
|
||||
"existing node violates it.",
|
||||
label_name, property_names_stream.str());
|
||||
} else {
|
||||
switch (res.GetValue()) {
|
||||
case storage::UniqueConstraints::CreationStatus::EMPTY_PROPERTIES:
|
||||
throw SyntaxException(
|
||||
"At least one property must be used for unique "
|
||||
"constraints.");
|
||||
break;
|
||||
case storage::UniqueConstraints::CreationStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED:
|
||||
throw SyntaxException(
|
||||
"Too many properties specified. Limit of {} properties "
|
||||
"for unique constraints is exceeded.",
|
||||
storage::kUniqueConstraintsMaxProperties);
|
||||
break;
|
||||
case storage::UniqueConstraints::CreationStatus::ALREADY_EXISTS:
|
||||
case storage::UniqueConstraints::CreationStatus::SUCCESS:
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch (res.GetValue()) {
|
||||
case storage::UniqueConstraints::CreationStatus::EMPTY_PROPERTIES:
|
||||
throw SyntaxException(
|
||||
"At least one property must be used for unique "
|
||||
"constraints.");
|
||||
case storage::UniqueConstraints::CreationStatus::PROPERTIES_SIZE_LIMIT_EXCEEDED:
|
||||
throw SyntaxException(
|
||||
"Too many properties specified. Limit of {} properties "
|
||||
"for unique constraints is exceeded.",
|
||||
storage::kUniqueConstraintsMaxProperties);
|
||||
case storage::UniqueConstraints::CreationStatus::ALREADY_EXISTS:
|
||||
constraint_notification.code = NotificationCode::EXISTANT_CONSTRAINT;
|
||||
constraint_notification.title =
|
||||
fmt::format("Constraint UNIQUE on label {} on properties {} already exists.", label_name,
|
||||
properties_stringified);
|
||||
break;
|
||||
case storage::UniqueConstraints::CreationStatus::SUCCESS:
|
||||
break;
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
case ConstraintQuery::ActionType::DROP: {
|
||||
constraint_notification.code = NotificationCode::DROP_CONSTRAINT;
|
||||
|
||||
switch (constraint_query->constraint_.type) {
|
||||
case Constraint::Type::NODE_KEY:
|
||||
throw utils::NotYetImplemented("Node key constraints");
|
||||
@@ -1739,8 +1934,17 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_
|
||||
if (properties.empty() || properties.size() > 1) {
|
||||
throw SyntaxException("Exactly one property must be used for existence constraints.");
|
||||
}
|
||||
handler = [interpreter_context, label, properties = std::move(properties)] {
|
||||
interpreter_context->db->DropExistenceConstraint(label, properties[0]);
|
||||
constraint_notification.title =
|
||||
fmt::format("Dropped EXISTS constraint on label {} on properties {}.",
|
||||
constraint_query->constraint_.label.name, utils::Join(properties_string, ", "));
|
||||
handler = [interpreter_context, label, label_name = constraint_query->constraint_.label.name,
|
||||
properties_stringified = std::move(properties_stringified),
|
||||
properties = std::move(properties)](Notification &constraint_notification) {
|
||||
if (!interpreter_context->db->DropExistenceConstraint(label, properties[0])) {
|
||||
constraint_notification.code = NotificationCode::NONEXISTANT_CONSTRAINT;
|
||||
constraint_notification.title = fmt::format(
|
||||
"Constraint EXISTS on label {} on properties {} doesn't exist.", label_name, properties_stringified);
|
||||
}
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
break;
|
||||
@@ -1752,7 +1956,12 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_
|
||||
if (property_set.size() != properties.size()) {
|
||||
throw SyntaxException("The given set of properties contains duplicates.");
|
||||
}
|
||||
handler = [interpreter_context, label, property_set = std::move(property_set)] {
|
||||
constraint_notification.title =
|
||||
fmt::format("Dropped UNIQUE constraint on label {} on properties {}.",
|
||||
constraint_query->constraint_.label.name, utils::Join(properties_string, ", "));
|
||||
handler = [interpreter_context, label, label_name = constraint_query->constraint_.label.name,
|
||||
properties_stringified = std::move(properties_stringified),
|
||||
property_set = std::move(property_set)](Notification &constraint_notification) {
|
||||
auto res = interpreter_context->db->DropUniqueConstraint(label, property_set);
|
||||
switch (res) {
|
||||
case storage::UniqueConstraints::DeletionStatus::EMPTY_PROPERTIES:
|
||||
@@ -1767,6 +1976,11 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_
|
||||
storage::kUniqueConstraintsMaxProperties);
|
||||
break;
|
||||
case storage::UniqueConstraints::DeletionStatus::NOT_FOUND:
|
||||
constraint_notification.code = NotificationCode::NONEXISTANT_CONSTRAINT;
|
||||
constraint_notification.title =
|
||||
fmt::format("Constraint UNIQUE on label {} on properties {} doesn't exist.", label_name,
|
||||
properties_stringified);
|
||||
break;
|
||||
case storage::UniqueConstraints::DeletionStatus::SUCCESS:
|
||||
break;
|
||||
}
|
||||
@@ -1778,8 +1992,10 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_
|
||||
|
||||
return PreparedQuery{{},
|
||||
std::move(parsed_query.required_privileges),
|
||||
[handler = std::move(handler)](AnyStream *stream, std::optional<int> n) {
|
||||
handler();
|
||||
[handler = std::move(handler), constraint_notification = std::move(constraint_notification),
|
||||
notifications](AnyStream * /*stream*/, std::optional<int> /*n*/) mutable {
|
||||
handler(constraint_notification);
|
||||
notifications->push_back(constraint_notification);
|
||||
return QueryHandlerResult::COMMIT;
|
||||
},
|
||||
RWType::NONE};
|
||||
@@ -1865,6 +2081,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
if (utils::Downcast<CypherQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareCypherQuery(std::move(parsed_query), &query_execution->summary, interpreter_context_,
|
||||
&*execution_db_accessor_, &query_execution->execution_memory,
|
||||
&query_execution->notifications,
|
||||
trigger_context_collector_ ? &*trigger_context_collector_ : nullptr);
|
||||
} else if (utils::Downcast<ExplainQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareExplainQuery(std::move(parsed_query), &query_execution->summary, interpreter_context_,
|
||||
@@ -1877,8 +2094,8 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
prepared_query = PrepareDumpQuery(std::move(parsed_query), &query_execution->summary, &*execution_db_accessor_,
|
||||
&query_execution->execution_memory);
|
||||
} else if (utils::Downcast<IndexQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareIndexQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
|
||||
interpreter_context_, &query_execution->execution_memory_with_exception);
|
||||
prepared_query = PrepareIndexQuery(std::move(parsed_query), in_explicit_transaction_,
|
||||
&query_execution->notifications, interpreter_context_);
|
||||
} else if (utils::Downcast<AuthQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareAuthQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
|
||||
interpreter_context_, &*execution_db_accessor_,
|
||||
@@ -1888,23 +2105,25 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
interpreter_context_, interpreter_context_->db,
|
||||
&query_execution->execution_memory_with_exception);
|
||||
} else if (utils::Downcast<ConstraintQuery>(parsed_query.query)) {
|
||||
prepared_query =
|
||||
PrepareConstraintQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
|
||||
interpreter_context_, &query_execution->execution_memory_with_exception);
|
||||
prepared_query = PrepareConstraintQuery(std::move(parsed_query), in_explicit_transaction_,
|
||||
&query_execution->notifications, interpreter_context_);
|
||||
} else if (utils::Downcast<ReplicationQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareReplicationQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_,
|
||||
&*execution_db_accessor_);
|
||||
prepared_query =
|
||||
PrepareReplicationQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->notifications,
|
||||
interpreter_context_, &*execution_db_accessor_);
|
||||
} else if (utils::Downcast<LockPathQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareLockPathQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_,
|
||||
&*execution_db_accessor_);
|
||||
} else if (utils::Downcast<FreeMemoryQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareFreeMemoryQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_);
|
||||
} else if (utils::Downcast<TriggerQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareTriggerQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_,
|
||||
&*execution_db_accessor_, params, username);
|
||||
prepared_query =
|
||||
PrepareTriggerQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->notifications,
|
||||
interpreter_context_, &*execution_db_accessor_, params, username);
|
||||
} else if (utils::Downcast<StreamQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareStreamQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_,
|
||||
&*execution_db_accessor_, params, username);
|
||||
prepared_query =
|
||||
PrepareStreamQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->notifications,
|
||||
interpreter_context_, &*execution_db_accessor_, params, username);
|
||||
} else if (utils::Downcast<IsolationLevelQuery>(parsed_query.query)) {
|
||||
prepared_query =
|
||||
PrepareIsolationLevelQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_, this);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -23,10 +23,11 @@
|
||||
#include "query/frontend/ast/cypher_main_visitor.hpp"
|
||||
#include "query/frontend/stripped.hpp"
|
||||
#include "query/interpret/frame.hpp"
|
||||
#include "query/metadata.hpp"
|
||||
#include "query/plan/operator.hpp"
|
||||
#include "query/plan/read_write_type_checker.hpp"
|
||||
#include "query/stream.hpp"
|
||||
#include "query/streams.hpp"
|
||||
#include "query/stream/streams.hpp"
|
||||
#include "query/trigger.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
#include "storage/v2/isolation_level.hpp"
|
||||
@@ -164,9 +165,10 @@ struct PreparedQuery {
|
||||
*/
|
||||
struct InterpreterContext {
|
||||
explicit InterpreterContext(storage::Storage *db, InterpreterConfig config,
|
||||
const std::filesystem::path &data_directory, std::string kafka_bootstrap_servers);
|
||||
const std::filesystem::path &data_directory);
|
||||
|
||||
storage::Storage *db;
|
||||
const InterpreterConfig config;
|
||||
|
||||
// ANTLR has singleton instance that is shared between threads. It is
|
||||
// protected by locks inside of ANTLR. Unfortunately, they are not protected
|
||||
@@ -185,11 +187,9 @@ struct InterpreterContext {
|
||||
utils::SkipList<PlanCacheEntry> plan_cache;
|
||||
|
||||
TriggerStore trigger_store;
|
||||
utils::ThreadPool after_commit_trigger_pool{1};
|
||||
utils::ThreadPool after_commit_trigger_pool;
|
||||
|
||||
const InterpreterConfig config;
|
||||
|
||||
query::Streams streams;
|
||||
query::stream::Streams streams;
|
||||
};
|
||||
|
||||
/// Function that is used to tell all active interpreters that they should stop
|
||||
@@ -285,6 +285,7 @@ class Interpreter final {
|
||||
utils::ResourceWithOutOfMemoryException execution_memory_with_exception{&execution_memory};
|
||||
|
||||
std::map<std::string, TypedValue> summary;
|
||||
std::vector<Notification> notifications;
|
||||
|
||||
explicit QueryExecution() = default;
|
||||
QueryExecution(const QueryExecution &) = delete;
|
||||
@@ -377,6 +378,14 @@ std::map<std::string, TypedValue> Interpreter::Pull(TStream *result_stream, std:
|
||||
if (maybe_res) {
|
||||
// Save its summary
|
||||
maybe_summary.emplace(std::move(query_execution->summary));
|
||||
if (!query_execution->notifications.empty()) {
|
||||
std::vector<TypedValue> notifications;
|
||||
notifications.reserve(query_execution->notifications.size());
|
||||
for (const auto ¬ification : query_execution->notifications) {
|
||||
notifications.emplace_back(notification.ConvertToMap());
|
||||
}
|
||||
maybe_summary->insert_or_assign("notifications", std::move(notifications));
|
||||
}
|
||||
if (!in_explicit_transaction_) {
|
||||
switch (*maybe_res) {
|
||||
case QueryHandlerResult::COMMIT:
|
||||
|
||||
117
src/query/metadata.cpp
Normal file
117
src/query/metadata.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
// 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
|
||||
// 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 "query/metadata.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <compare>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace query {
|
||||
|
||||
namespace {
|
||||
using namespace std::literals;
|
||||
|
||||
constexpr std::string_view GetSeverityLevelString(const SeverityLevel level) {
|
||||
switch (level) {
|
||||
case SeverityLevel::INFO:
|
||||
return "INFO"sv;
|
||||
case SeverityLevel::WARNING:
|
||||
return "WARNING"sv;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr std::string_view GetCodeString(const NotificationCode code) {
|
||||
switch (code) {
|
||||
case NotificationCode::CREATE_CONSTRAINT:
|
||||
return "CreateConstraint"sv;
|
||||
case NotificationCode::CREATE_INDEX:
|
||||
return "CreateIndex"sv;
|
||||
case NotificationCode::CREATE_STREAM:
|
||||
return "CreateStream"sv;
|
||||
case NotificationCode::CHECK_STREAM:
|
||||
return "CheckStream"sv;
|
||||
case NotificationCode::CREATE_TRIGGER:
|
||||
return "CreateTrigger"sv;
|
||||
case NotificationCode::DROP_CONSTRAINT:
|
||||
return "DropConstraint"sv;
|
||||
case NotificationCode::DROP_REPLICA:
|
||||
return "DropReplica"sv;
|
||||
case NotificationCode::DROP_INDEX:
|
||||
return "DropIndex"sv;
|
||||
case NotificationCode::DROP_STREAM:
|
||||
return "DropStream"sv;
|
||||
case NotificationCode::DROP_TRIGGER:
|
||||
return "DropTrigger"sv;
|
||||
case NotificationCode::EXISTANT_CONSTRAINT:
|
||||
return "ConstraintAlreadyExists"sv;
|
||||
case NotificationCode::EXISTANT_INDEX:
|
||||
return "IndexAlreadyExists"sv;
|
||||
case NotificationCode::LOAD_CSV_TIP:
|
||||
return "LoadCSVTip"sv;
|
||||
case NotificationCode::NONEXISTANT_INDEX:
|
||||
return "IndexDoesNotExist"sv;
|
||||
case NotificationCode::NONEXISTANT_CONSTRAINT:
|
||||
return "ConstraintDoesNotExist"sv;
|
||||
case NotificationCode::REGISTER_REPLICA:
|
||||
return "RegisterReplica"sv;
|
||||
case NotificationCode::REPLICA_PORT_WARNING:
|
||||
return "ReplicaPortWarning"sv;
|
||||
case NotificationCode::SET_REPLICA:
|
||||
return "SetReplica"sv;
|
||||
case NotificationCode::START_STREAM:
|
||||
return "StartStream"sv;
|
||||
case NotificationCode::START_ALL_STREAMS:
|
||||
return "StartAllStreams"sv;
|
||||
case NotificationCode::STOP_STREAM:
|
||||
return "StopStream"sv;
|
||||
case NotificationCode::STOP_ALL_STREAMS:
|
||||
return "StopAllStreams"sv;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Notification::Notification(SeverityLevel level) : level{level} {};
|
||||
|
||||
Notification::Notification(SeverityLevel level, NotificationCode code, std::string title, std::string description)
|
||||
: level{level}, code{code}, title(std::move(title)), description(std::move(description)){};
|
||||
|
||||
Notification::Notification(SeverityLevel level, NotificationCode code, std::string title)
|
||||
: level{level}, code{code}, title(std::move(title)){};
|
||||
|
||||
std::map<std::string, TypedValue> Notification::ConvertToMap() const {
|
||||
return std::map<std::string, TypedValue>{{"severity", TypedValue(GetSeverityLevelString(level))},
|
||||
{"code", TypedValue(GetCodeString(code))},
|
||||
{"title", TypedValue(title)},
|
||||
{"description", TypedValue(description)}};
|
||||
}
|
||||
|
||||
std::string ExecutionStatsKeyToString(const ExecutionStats::Key key) {
|
||||
switch (key) {
|
||||
case ExecutionStats::Key::CREATED_NODES:
|
||||
return std::string("nodes-created");
|
||||
case ExecutionStats::Key::DELETED_NODES:
|
||||
return std::string("nodes-deleted");
|
||||
case ExecutionStats::Key::CREATED_EDGES:
|
||||
return std::string("relationships-created");
|
||||
case ExecutionStats::Key::DELETED_EDGES:
|
||||
return std::string("relationships-deleted");
|
||||
case ExecutionStats::Key::CREATED_LABELS:
|
||||
return std::string("labels-added");
|
||||
case ExecutionStats::Key::DELETED_LABELS:
|
||||
return std::string("labels-removed");
|
||||
case ExecutionStats::Key::UPDATED_PROPERTIES:
|
||||
return std::string("properties-set");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace query
|
||||
90
src/query/metadata.hpp
Normal file
90
src/query/metadata.hpp
Normal file
@@ -0,0 +1,90 @@
|
||||
// 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
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
#include "query/typed_value.hpp"
|
||||
|
||||
namespace query {
|
||||
|
||||
enum class SeverityLevel : uint8_t { INFO, WARNING };
|
||||
|
||||
enum class NotificationCode : uint8_t {
|
||||
CREATE_CONSTRAINT,
|
||||
CREATE_INDEX,
|
||||
CHECK_STREAM,
|
||||
CREATE_STREAM,
|
||||
CREATE_TRIGGER,
|
||||
DROP_CONSTRAINT,
|
||||
DROP_INDEX,
|
||||
DROP_REPLICA,
|
||||
DROP_STREAM,
|
||||
DROP_TRIGGER,
|
||||
EXISTANT_INDEX,
|
||||
EXISTANT_CONSTRAINT,
|
||||
LOAD_CSV_TIP,
|
||||
NONEXISTANT_INDEX,
|
||||
NONEXISTANT_CONSTRAINT,
|
||||
REPLICA_PORT_WARNING,
|
||||
REGISTER_REPLICA,
|
||||
SET_REPLICA,
|
||||
START_STREAM,
|
||||
START_ALL_STREAMS,
|
||||
STOP_STREAM,
|
||||
STOP_ALL_STREAMS,
|
||||
};
|
||||
|
||||
struct Notification {
|
||||
SeverityLevel level;
|
||||
NotificationCode code;
|
||||
std::string title;
|
||||
std::string description;
|
||||
|
||||
explicit Notification(SeverityLevel level);
|
||||
|
||||
Notification(SeverityLevel level, NotificationCode code, std::string title, std::string description);
|
||||
|
||||
Notification(SeverityLevel level, NotificationCode code, std::string title);
|
||||
|
||||
std::map<std::string, TypedValue> ConvertToMap() const;
|
||||
};
|
||||
|
||||
struct ExecutionStats {
|
||||
public:
|
||||
// All the stats have specific key to be compatible with neo4j
|
||||
enum class Key : uint8_t {
|
||||
CREATED_NODES,
|
||||
DELETED_NODES,
|
||||
CREATED_EDGES,
|
||||
DELETED_EDGES,
|
||||
CREATED_LABELS,
|
||||
DELETED_LABELS,
|
||||
UPDATED_PROPERTIES,
|
||||
};
|
||||
|
||||
int64_t &operator[](Key key) { return counters[static_cast<size_t>(key)]; }
|
||||
|
||||
private:
|
||||
static constexpr auto kExecutionStatsCountersSize = std::underlying_type_t<Key>(Key::UPDATED_PROPERTIES) + 1;
|
||||
|
||||
public:
|
||||
std::array<int64_t, kExecutionStatsCountersSize> counters{0};
|
||||
};
|
||||
|
||||
std::string ExecutionStatsKeyToString(ExecutionStats::Key key);
|
||||
|
||||
} // namespace query
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "query/plan/operator.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <queue>
|
||||
#include <random>
|
||||
@@ -49,6 +50,7 @@
|
||||
#include "utils/pmr/vector.hpp"
|
||||
#include "utils/readable_size.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "utils/temporal.hpp"
|
||||
|
||||
// macro for the default implementation of LogicalOperator::Accept
|
||||
// that accepts the visitor and visits it's input_ operator
|
||||
@@ -170,15 +172,16 @@ CreateNode::CreateNode(const std::shared_ptr<LogicalOperator> &input, const Node
|
||||
|
||||
// Creates a vertex on this GraphDb. Returns a reference to vertex placed on the
|
||||
// frame.
|
||||
VertexAccessor &CreateLocalVertex(const NodeCreationInfo &node_info, Frame *frame, const ExecutionContext &context) {
|
||||
VertexAccessor &CreateLocalVertex(const NodeCreationInfo &node_info, Frame *frame, ExecutionContext &context) {
|
||||
auto &dba = *context.db_accessor;
|
||||
auto new_node = dba.InsertVertex();
|
||||
context.execution_stats[ExecutionStats::Key::CREATED_NODES] += 1;
|
||||
for (auto label : node_info.labels) {
|
||||
auto maybe_error = new_node.AddLabel(label);
|
||||
if (maybe_error.HasError()) {
|
||||
switch (maybe_error.GetError()) {
|
||||
case storage::Error::SERIALIZATION_ERROR:
|
||||
throw QueryRuntimeException(kSerializationErrorMessage);
|
||||
throw TransactionSerializationException();
|
||||
case storage::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to set a label on a deleted node.");
|
||||
case storage::Error::VERTEX_HAS_EDGES:
|
||||
@@ -187,6 +190,7 @@ VertexAccessor &CreateLocalVertex(const NodeCreationInfo &node_info, Frame *fram
|
||||
throw QueryRuntimeException("Unexpected error when setting a label.");
|
||||
}
|
||||
}
|
||||
context.execution_stats[ExecutionStats::Key::CREATED_LABELS] += 1;
|
||||
}
|
||||
// Evaluator should use the latest accessors, as modified in this query, when
|
||||
// setting properties on new nodes.
|
||||
@@ -294,7 +298,7 @@ EdgeAccessor CreateEdge(const EdgeCreationInfo &edge_info, DbAccessor *dba, Vert
|
||||
} else {
|
||||
switch (maybe_edge.GetError()) {
|
||||
case storage::Error::SERIALIZATION_ERROR:
|
||||
throw QueryRuntimeException(kSerializationErrorMessage);
|
||||
throw TransactionSerializationException();
|
||||
case storage::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to create an edge on a deleted node.");
|
||||
case storage::Error::VERTEX_HAS_EDGES:
|
||||
@@ -345,6 +349,7 @@ bool CreateExpand::CreateExpandCursor::Pull(Frame &frame, ExecutionContext &cont
|
||||
}
|
||||
}();
|
||||
|
||||
context.execution_stats[ExecutionStats::Key::CREATED_EDGES] += 1;
|
||||
if (context.trigger_context_collector) {
|
||||
context.trigger_context_collector->RegisterCreatedObject(created_edge);
|
||||
}
|
||||
@@ -1445,7 +1450,7 @@ class ExpandWeightedShortestPathCursor : public query::plan::Cursor {
|
||||
// satisfy the "where" condition. if so, places them in the priority
|
||||
// queue.
|
||||
auto expand_pair = [this, &evaluator, &frame, &create_state](const EdgeAccessor &edge, const VertexAccessor &vertex,
|
||||
double weight, int64_t depth) {
|
||||
const TypedValue &total_weight, int64_t depth) {
|
||||
auto *memory = evaluator.GetMemoryResource();
|
||||
if (self_.filter_lambda_.expression) {
|
||||
frame[self_.filter_lambda_.inner_edge_symbol] = edge;
|
||||
@@ -1457,27 +1462,48 @@ class ExpandWeightedShortestPathCursor : public query::plan::Cursor {
|
||||
frame[self_.weight_lambda_->inner_edge_symbol] = edge;
|
||||
frame[self_.weight_lambda_->inner_node_symbol] = vertex;
|
||||
|
||||
TypedValue typed_weight = self_.weight_lambda_->expression->Accept(evaluator);
|
||||
TypedValue current_weight = self_.weight_lambda_->expression->Accept(evaluator);
|
||||
|
||||
if (!typed_weight.IsNumeric()) {
|
||||
throw QueryRuntimeException("Calculated weight must be numeric, got {}.", typed_weight.type());
|
||||
if (!current_weight.IsNumeric() && !current_weight.IsDuration()) {
|
||||
throw QueryRuntimeException("Calculated weight must be numeric or a Duration, got {}.", current_weight.type());
|
||||
}
|
||||
if ((typed_weight < TypedValue(0, memory)).ValueBool()) {
|
||||
|
||||
const auto is_valid_numeric = [&] {
|
||||
return current_weight.IsNumeric() && (current_weight >= TypedValue(0, memory)).ValueBool();
|
||||
};
|
||||
|
||||
const auto is_valid_duration = [&] {
|
||||
return current_weight.IsDuration() && (current_weight >= TypedValue(utils::Duration(0), memory)).ValueBool();
|
||||
};
|
||||
|
||||
if (!is_valid_numeric() && !is_valid_duration()) {
|
||||
throw QueryRuntimeException("Calculated weight must be non-negative!");
|
||||
}
|
||||
|
||||
auto next_state = create_state(vertex, depth);
|
||||
auto next_weight = TypedValue(weight, memory) + typed_weight;
|
||||
auto found_it = total_cost_.find(next_state);
|
||||
if (found_it != total_cost_.end() && found_it->second.ValueDouble() <= next_weight.ValueDouble()) return;
|
||||
|
||||
pq_.push({next_weight.ValueDouble(), depth + 1, vertex, edge});
|
||||
TypedValue next_weight = std::invoke([&] {
|
||||
if (total_weight.IsNull()) {
|
||||
return current_weight;
|
||||
}
|
||||
|
||||
ValidateWeightTypes(current_weight, total_weight);
|
||||
|
||||
return TypedValue(current_weight, memory) + total_weight;
|
||||
});
|
||||
|
||||
auto found_it = total_cost_.find(next_state);
|
||||
if (found_it != total_cost_.end() && (found_it->second.IsNull() || (found_it->second <= next_weight).ValueBool()))
|
||||
return;
|
||||
|
||||
pq_.push({next_weight, depth + 1, vertex, edge});
|
||||
};
|
||||
|
||||
// Populates the priority queue structure with expansions
|
||||
// from the given vertex. skips expansions that don't satisfy
|
||||
// the "where" condition.
|
||||
auto expand_from_vertex = [this, &expand_pair](const VertexAccessor &vertex, double weight, int64_t depth) {
|
||||
auto expand_from_vertex = [this, &expand_pair](const VertexAccessor &vertex, const TypedValue &weight,
|
||||
int64_t depth) {
|
||||
if (self_.common_.direction != EdgeAtom::Direction::IN) {
|
||||
auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::View::OLD, self_.common_.edge_types));
|
||||
for (const auto &edge : out_edges) {
|
||||
@@ -1522,7 +1548,7 @@ class ExpandWeightedShortestPathCursor : public query::plan::Cursor {
|
||||
total_cost_.clear();
|
||||
yielded_vertices_.clear();
|
||||
|
||||
pq_.push({0.0, 0, vertex, std::nullopt});
|
||||
pq_.push({TypedValue(), 0, vertex, std::nullopt});
|
||||
// We are adding the starting vertex to the set of yielded vertices
|
||||
// because we don't want to yield paths that end with the starting
|
||||
// vertex.
|
||||
@@ -1622,17 +1648,38 @@ class ExpandWeightedShortestPathCursor : public query::plan::Cursor {
|
||||
// Keeps track of vertices for which we yielded a path already.
|
||||
utils::pmr::unordered_set<VertexAccessor> yielded_vertices_;
|
||||
|
||||
static void ValidateWeightTypes(const TypedValue &lhs, const TypedValue &rhs) {
|
||||
if (!((lhs.IsNumeric() && lhs.IsNumeric()) || (rhs.IsDuration() && rhs.IsDuration()))) {
|
||||
throw QueryRuntimeException(utils::MessageWithLink(
|
||||
"All weights should be of the same type, either numeric or a Duration. Please update the weight "
|
||||
"expression or the filter expression.",
|
||||
"https://memgr.ph/wsp"));
|
||||
}
|
||||
}
|
||||
|
||||
// Priority queue comparator. Keep lowest weight on top of the queue.
|
||||
class PriorityQueueComparator {
|
||||
public:
|
||||
bool operator()(const std::tuple<double, int64_t, VertexAccessor, std::optional<EdgeAccessor>> &lhs,
|
||||
const std::tuple<double, int64_t, VertexAccessor, std::optional<EdgeAccessor>> &rhs) {
|
||||
return std::get<0>(lhs) > std::get<0>(rhs);
|
||||
bool operator()(const std::tuple<TypedValue, int64_t, VertexAccessor, std::optional<EdgeAccessor>> &lhs,
|
||||
const std::tuple<TypedValue, int64_t, VertexAccessor, std::optional<EdgeAccessor>> &rhs) {
|
||||
const auto &lhs_weight = std::get<0>(lhs);
|
||||
const auto &rhs_weight = std::get<0>(rhs);
|
||||
// Null defines minimum value for all types
|
||||
if (lhs_weight.IsNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rhs_weight.IsNull()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ValidateWeightTypes(lhs_weight, rhs_weight);
|
||||
return (lhs_weight > rhs_weight).ValueBool();
|
||||
}
|
||||
};
|
||||
|
||||
std::priority_queue<std::tuple<double, int64_t, VertexAccessor, std::optional<EdgeAccessor>>,
|
||||
utils::pmr::vector<std::tuple<double, int64_t, VertexAccessor, std::optional<EdgeAccessor>>>,
|
||||
std::priority_queue<std::tuple<TypedValue, int64_t, VertexAccessor, std::optional<EdgeAccessor>>,
|
||||
utils::pmr::vector<std::tuple<TypedValue, int64_t, VertexAccessor, std::optional<EdgeAccessor>>>,
|
||||
PriorityQueueComparator>
|
||||
pq_;
|
||||
|
||||
@@ -1874,7 +1921,7 @@ bool Delete::DeleteCursor::Pull(Frame &frame, ExecutionContext &context) {
|
||||
if (maybe_value.HasError()) {
|
||||
switch (maybe_value.GetError()) {
|
||||
case storage::Error::SERIALIZATION_ERROR:
|
||||
throw QueryRuntimeException(kSerializationErrorMessage);
|
||||
throw TransactionSerializationException();
|
||||
case storage::Error::DELETED_OBJECT:
|
||||
case storage::Error::VERTEX_HAS_EDGES:
|
||||
case storage::Error::PROPERTIES_DISABLED:
|
||||
@@ -1882,7 +1929,7 @@ bool Delete::DeleteCursor::Pull(Frame &frame, ExecutionContext &context) {
|
||||
throw QueryRuntimeException("Unexpected error when deleting an edge.");
|
||||
}
|
||||
}
|
||||
|
||||
context.execution_stats[ExecutionStats::Key::DELETED_EDGES] += 1;
|
||||
if (context.trigger_context_collector && maybe_value.GetValue()) {
|
||||
context.trigger_context_collector->RegisterDeletedObject(*maybe_value.GetValue());
|
||||
}
|
||||
@@ -1900,7 +1947,7 @@ bool Delete::DeleteCursor::Pull(Frame &frame, ExecutionContext &context) {
|
||||
if (res.HasError()) {
|
||||
switch (res.GetError()) {
|
||||
case storage::Error::SERIALIZATION_ERROR:
|
||||
throw QueryRuntimeException(kSerializationErrorMessage);
|
||||
throw TransactionSerializationException();
|
||||
case storage::Error::DELETED_OBJECT:
|
||||
case storage::Error::VERTEX_HAS_EDGES:
|
||||
case storage::Error::PROPERTIES_DISABLED:
|
||||
@@ -1908,19 +1955,30 @@ bool Delete::DeleteCursor::Pull(Frame &frame, ExecutionContext &context) {
|
||||
throw QueryRuntimeException("Unexpected error when deleting a node.");
|
||||
}
|
||||
}
|
||||
if (context.trigger_context_collector &&
|
||||
context.trigger_context_collector->ShouldRegisterDeletedObject<EdgeAccessor>() && res.GetValue()) {
|
||||
context.trigger_context_collector->RegisterDeletedObject(res.GetValue()->first);
|
||||
for (const auto &deleted_edge : res.GetValue()->second) {
|
||||
context.trigger_context_collector->RegisterDeletedObject(deleted_edge);
|
||||
}
|
||||
|
||||
context.execution_stats[ExecutionStats::Key::DELETED_NODES] += 1;
|
||||
if (*res) {
|
||||
context.execution_stats[ExecutionStats::Key::DELETED_EDGES] += static_cast<int64_t>((*res)->second.size());
|
||||
}
|
||||
std::invoke([&] {
|
||||
if (!context.trigger_context_collector || !*res) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.trigger_context_collector->RegisterDeletedObject((*res)->first);
|
||||
if (!context.trigger_context_collector->ShouldRegisterDeletedObject<query::EdgeAccessor>()) {
|
||||
return;
|
||||
}
|
||||
for (const auto &edge : (*res)->second) {
|
||||
context.trigger_context_collector->RegisterDeletedObject(edge);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
auto res = dba.RemoveVertex(&va);
|
||||
if (res.HasError()) {
|
||||
switch (res.GetError()) {
|
||||
case storage::Error::SERIALIZATION_ERROR:
|
||||
throw QueryRuntimeException(kSerializationErrorMessage);
|
||||
throw TransactionSerializationException();
|
||||
case storage::Error::VERTEX_HAS_EDGES:
|
||||
throw RemoveAttachedVertexException();
|
||||
case storage::Error::DELETED_OBJECT:
|
||||
@@ -1929,7 +1987,7 @@ bool Delete::DeleteCursor::Pull(Frame &frame, ExecutionContext &context) {
|
||||
throw QueryRuntimeException("Unexpected error when deleting a node.");
|
||||
}
|
||||
}
|
||||
|
||||
context.execution_stats[ExecutionStats::Key::DELETED_NODES] += 1;
|
||||
if (context.trigger_context_collector && res.GetValue()) {
|
||||
context.trigger_context_collector->RegisterDeletedObject(*res.GetValue());
|
||||
}
|
||||
@@ -1988,7 +2046,7 @@ bool SetProperty::SetPropertyCursor::Pull(Frame &frame, ExecutionContext &contex
|
||||
switch (lhs.type()) {
|
||||
case TypedValue::Type::Vertex: {
|
||||
auto old_value = PropsSetChecked(&lhs.ValueVertex(), self_.property_, rhs);
|
||||
|
||||
context.execution_stats[ExecutionStats::Key::UPDATED_PROPERTIES] += 1;
|
||||
if (context.trigger_context_collector) {
|
||||
// rhs cannot be moved because it was created with the allocator that is only valid during current pull
|
||||
context.trigger_context_collector->RegisterSetObjectProperty(lhs.ValueVertex(), self_.property_,
|
||||
@@ -1998,7 +2056,7 @@ bool SetProperty::SetPropertyCursor::Pull(Frame &frame, ExecutionContext &contex
|
||||
}
|
||||
case TypedValue::Type::Edge: {
|
||||
auto old_value = PropsSetChecked(&lhs.ValueEdge(), self_.property_, rhs);
|
||||
|
||||
context.execution_stats[ExecutionStats::Key::UPDATED_PROPERTIES] += 1;
|
||||
if (context.trigger_context_collector) {
|
||||
// rhs cannot be moved because it was created with the allocator that is only valid during current pull
|
||||
context.trigger_context_collector->RegisterSetObjectProperty(lhs.ValueEdge(), self_.property_,
|
||||
@@ -2070,7 +2128,7 @@ void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetPr
|
||||
case storage::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to set properties on a deleted graph element.");
|
||||
case storage::Error::SERIALIZATION_ERROR:
|
||||
throw QueryRuntimeException(kSerializationErrorMessage);
|
||||
throw TransactionSerializationException();
|
||||
case storage::Error::PROPERTIES_DISABLED:
|
||||
throw QueryRuntimeException("Can't set property because properties on edges are disabled.");
|
||||
case storage::Error::VERTEX_HAS_EDGES:
|
||||
@@ -2126,7 +2184,7 @@ void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetPr
|
||||
case storage::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to set properties on a deleted graph element.");
|
||||
case storage::Error::SERIALIZATION_ERROR:
|
||||
throw QueryRuntimeException(kSerializationErrorMessage);
|
||||
throw TransactionSerializationException();
|
||||
case storage::Error::PROPERTIES_DISABLED:
|
||||
throw QueryRuntimeException("Can't set property because properties on edges are disabled.");
|
||||
case storage::Error::VERTEX_HAS_EDGES:
|
||||
@@ -2241,7 +2299,7 @@ bool SetLabels::SetLabelsCursor::Pull(Frame &frame, ExecutionContext &context) {
|
||||
if (maybe_value.HasError()) {
|
||||
switch (maybe_value.GetError()) {
|
||||
case storage::Error::SERIALIZATION_ERROR:
|
||||
throw QueryRuntimeException(kSerializationErrorMessage);
|
||||
throw TransactionSerializationException();
|
||||
case storage::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to set a label on a deleted node.");
|
||||
case storage::Error::VERTEX_HAS_EDGES:
|
||||
@@ -2299,7 +2357,7 @@ bool RemoveProperty::RemovePropertyCursor::Pull(Frame &frame, ExecutionContext &
|
||||
case storage::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to remove a property on a deleted graph element.");
|
||||
case storage::Error::SERIALIZATION_ERROR:
|
||||
throw QueryRuntimeException(kSerializationErrorMessage);
|
||||
throw TransactionSerializationException();
|
||||
case storage::Error::PROPERTIES_DISABLED:
|
||||
throw QueryRuntimeException(
|
||||
"Can't remove property because properties on edges are "
|
||||
@@ -2370,7 +2428,7 @@ bool RemoveLabels::RemoveLabelsCursor::Pull(Frame &frame, ExecutionContext &cont
|
||||
if (maybe_value.HasError()) {
|
||||
switch (maybe_value.GetError()) {
|
||||
case storage::Error::SERIALIZATION_ERROR:
|
||||
throw QueryRuntimeException(kSerializationErrorMessage);
|
||||
throw TransactionSerializationException();
|
||||
case storage::Error::DELETED_OBJECT:
|
||||
throw QueryRuntimeException("Trying to remove labels from a deleted node.");
|
||||
case storage::Error::VERTEX_HAS_EDGES:
|
||||
@@ -2380,6 +2438,7 @@ bool RemoveLabels::RemoveLabelsCursor::Pull(Frame &frame, ExecutionContext &cont
|
||||
}
|
||||
}
|
||||
|
||||
context.execution_stats[ExecutionStats::Key::DELETED_LABELS] += 1;
|
||||
if (context.trigger_context_collector && *maybe_value) {
|
||||
context.trigger_context_collector->RegisterRemovedVertexLabel(vertex, label);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
;; 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 License, and you may not use this file except in compliance with 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
|
||||
|
||||
36
src/query/procedure/mg_procedure_helpers.cpp
Normal file
36
src/query/procedure/mg_procedure_helpers.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
// 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
|
||||
// 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 "query/procedure/mg_procedure_helpers.hpp"
|
||||
|
||||
namespace query::procedure {
|
||||
MgpUniquePtr<mgp_value> GetStringValueOrSetError(const char *string, mgp_memory *memory, mgp_result *result) {
|
||||
procedure::MgpUniquePtr<mgp_value> value{nullptr, mgp_value_destroy};
|
||||
const auto success =
|
||||
TryOrSetError([&] { return procedure::CreateMgpObject(value, mgp_value_make_string, string, memory); }, result);
|
||||
if (!success) {
|
||||
value.reset();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
bool InsertResultOrSetError(mgp_result *result, mgp_result_record *record, const char *result_name, mgp_value *value) {
|
||||
if (const auto err = mgp_result_record_insert(record, result_name, value); err != MGP_ERROR_NO_ERROR) {
|
||||
const auto error_msg = fmt::format("Unable to set the result for {}, error = {}", result_name, err);
|
||||
static_cast<void>(mgp_result_set_error_msg(result, error_msg.c_str()));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace query::procedure
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "mg_procedure.h"
|
||||
|
||||
namespace query::procedure {
|
||||
@@ -45,4 +47,23 @@ mgp_error CreateMgpObject(MgpUniquePtr<TObj> &obj, TFunc func, TArgs &&...args)
|
||||
obj.reset(raw_obj);
|
||||
return err;
|
||||
}
|
||||
|
||||
template <typename Fun>
|
||||
[[nodiscard]] bool TryOrSetError(Fun &&func, mgp_result *result) {
|
||||
if (const auto err = func(); err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
static_cast<void>(mgp_result_set_error_msg(result, "Not enough memory!"));
|
||||
return false;
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
const auto error_msg = fmt::format("Unexpected error ({})!", err);
|
||||
static_cast<void>(mgp_result_set_error_msg(result, error_msg.c_str()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] MgpUniquePtr<mgp_value> GetStringValueOrSetError(const char *string, mgp_memory *memory,
|
||||
mgp_result *result);
|
||||
|
||||
[[nodiscard]] bool InsertResultOrSetError(mgp_result *result, mgp_result_record *record, const char *result_name,
|
||||
mgp_value *value);
|
||||
} // namespace query::procedure
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "module.hpp"
|
||||
#include "query/procedure/cypher_types.hpp"
|
||||
#include "query/procedure/mg_procedure_helpers.hpp"
|
||||
#include "query/stream/common.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "storage/v2/view.hpp"
|
||||
#include "utils/algorithm.hpp"
|
||||
@@ -35,6 +36,7 @@
|
||||
#include "utils/memory.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "utils/temporal.hpp"
|
||||
#include "utils/variant_helpers.hpp"
|
||||
|
||||
// This file contains implementation of top level C API functions, but this is
|
||||
// all actually part of query::procedure. So use that namespace for simplicity.
|
||||
@@ -1218,7 +1220,7 @@ mgp_error mgp_local_time_get_hour(mgp_local_time *local_time, int *hour) {
|
||||
return WrapExceptions([local_time] { return local_time->local_time.hour; }, hour);
|
||||
}
|
||||
|
||||
mgp_error mgp_local_time_get_minue(mgp_local_time *local_time, int *minute) {
|
||||
mgp_error mgp_local_time_get_minute(mgp_local_time *local_time, int *minute) {
|
||||
return WrapExceptions([local_time] { return local_time->local_time.minute; }, minute);
|
||||
}
|
||||
|
||||
@@ -1565,9 +1567,8 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam
|
||||
if (!MgpVertexIsMutable(*v)) {
|
||||
throw ImmutableObjectException{"Cannot set a property on an immutable vertex!"};
|
||||
}
|
||||
const auto result =
|
||||
v->impl.SetProperty(v->graph->impl->NameToProperty(property_name), ToPropertyValue(*property_value));
|
||||
|
||||
const auto prop_key = v->graph->impl->NameToProperty(property_name);
|
||||
const auto result = v->impl.SetProperty(prop_key, ToPropertyValue(*property_value));
|
||||
if (result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
case storage::Error::DELETED_OBJECT:
|
||||
@@ -1581,6 +1582,22 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam
|
||||
throw SerializationException{"Cannot serialize setting a property of a vertex."};
|
||||
}
|
||||
}
|
||||
|
||||
auto &ctx = v->graph->ctx;
|
||||
|
||||
ctx->execution_stats[query::ExecutionStats::Key::UPDATED_PROPERTIES] += 1;
|
||||
|
||||
auto *trigger_ctx_collector = ctx->trigger_context_collector;
|
||||
if (!trigger_ctx_collector || !trigger_ctx_collector->ShouldRegisterObjectPropertyChange<query::VertexAccessor>()) {
|
||||
return;
|
||||
}
|
||||
const auto old_value = query::TypedValue(*result);
|
||||
if (property_value->type == mgp_value_type::MGP_VALUE_TYPE_NULL) {
|
||||
trigger_ctx_collector->RegisterRemovedObjectProperty(v->impl, prop_key, old_value);
|
||||
return;
|
||||
}
|
||||
const auto new_value = ToTypedValue(*property_value, property_value->memory);
|
||||
trigger_ctx_collector->RegisterSetObjectProperty(v->impl, prop_key, old_value, new_value);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1589,7 +1606,8 @@ mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) {
|
||||
if (!MgpVertexIsMutable(*v)) {
|
||||
throw ImmutableObjectException{"Cannot add a label to an immutable vertex!"};
|
||||
}
|
||||
const auto result = v->impl.AddLabel(v->graph->impl->NameToLabel(label.name));
|
||||
const auto label_id = v->graph->impl->NameToLabel(label.name);
|
||||
const auto result = v->impl.AddLabel(label_id);
|
||||
|
||||
if (result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
@@ -1604,6 +1622,14 @@ mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) {
|
||||
throw SerializationException{"Cannot serialize adding a label to a vertex."};
|
||||
}
|
||||
}
|
||||
|
||||
auto &ctx = v->graph->ctx;
|
||||
|
||||
ctx->execution_stats[query::ExecutionStats::Key::CREATED_LABELS] += 1;
|
||||
|
||||
if (ctx->trigger_context_collector) {
|
||||
ctx->trigger_context_collector->RegisterSetVertexLabel(v->impl, label_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1612,7 +1638,8 @@ mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) {
|
||||
if (!MgpVertexIsMutable(*v)) {
|
||||
throw ImmutableObjectException{"Cannot remove a label from an immutable vertex!"};
|
||||
}
|
||||
const auto result = v->impl.RemoveLabel(v->graph->impl->NameToLabel(label.name));
|
||||
const auto label_id = v->graph->impl->NameToLabel(label.name);
|
||||
const auto result = v->impl.RemoveLabel(label_id);
|
||||
|
||||
if (result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
@@ -1627,6 +1654,14 @@ mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) {
|
||||
throw SerializationException{"Cannot serialize removing a label from a vertex."};
|
||||
}
|
||||
}
|
||||
|
||||
auto &ctx = v->graph->ctx;
|
||||
|
||||
ctx->execution_stats[query::ExecutionStats::Key::DELETED_LABELS] += 1;
|
||||
|
||||
if (ctx->trigger_context_collector) {
|
||||
ctx->trigger_context_collector->RegisterRemovedVertexLabel(v->impl, label_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1954,8 +1989,8 @@ mgp_error mgp_edge_set_property(struct mgp_edge *e, const char *property_name, m
|
||||
if (!MgpEdgeIsMutable(*e)) {
|
||||
throw ImmutableObjectException{"Cannot set a property on an immutable edge!"};
|
||||
}
|
||||
const auto result =
|
||||
e->impl.SetProperty(e->from.graph->impl->NameToProperty(property_name), ToPropertyValue(*property_value));
|
||||
const auto prop_key = e->from.graph->impl->NameToProperty(property_name);
|
||||
const auto result = e->impl.SetProperty(prop_key, ToPropertyValue(*property_value));
|
||||
|
||||
if (result.HasError()) {
|
||||
switch (result.GetError()) {
|
||||
@@ -1971,6 +2006,22 @@ mgp_error mgp_edge_set_property(struct mgp_edge *e, const char *property_name, m
|
||||
throw SerializationException{"Cannot serialize setting a property of an edge."};
|
||||
}
|
||||
}
|
||||
|
||||
auto &ctx = e->from.graph->ctx;
|
||||
|
||||
ctx->execution_stats[query::ExecutionStats::Key::UPDATED_PROPERTIES] += 1;
|
||||
|
||||
auto *trigger_ctx_collector = e->from.graph->ctx->trigger_context_collector;
|
||||
if (!trigger_ctx_collector || !trigger_ctx_collector->ShouldRegisterObjectPropertyChange<query::EdgeAccessor>()) {
|
||||
return;
|
||||
}
|
||||
const auto old_value = query::TypedValue(*result);
|
||||
if (property_value->type == mgp_value_type::MGP_VALUE_TYPE_NULL) {
|
||||
e->from.graph->ctx->trigger_context_collector->RegisterRemovedObjectProperty(e->impl, prop_key, old_value);
|
||||
return;
|
||||
}
|
||||
const auto new_value = ToTypedValue(*property_value, property_value->memory);
|
||||
e->from.graph->ctx->trigger_context_collector->RegisterSetObjectProperty(e->impl, prop_key, old_value, new_value);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2024,8 +2075,14 @@ mgp_error mgp_graph_create_vertex(struct mgp_graph *graph, mgp_memory *memory, m
|
||||
if (!MgpGraphIsMutable(*graph)) {
|
||||
throw ImmutableObjectException{"Cannot create a vertex in an immutable graph!"};
|
||||
}
|
||||
|
||||
auto vertex = graph->impl->InsertVertex();
|
||||
|
||||
auto &ctx = graph->ctx;
|
||||
ctx->execution_stats[query::ExecutionStats::Key::CREATED_NODES] += 1;
|
||||
|
||||
if (ctx->trigger_context_collector) {
|
||||
ctx->trigger_context_collector->RegisterCreatedObject(vertex);
|
||||
}
|
||||
return NewRawMgpObject<mgp_vertex>(memory, vertex, graph);
|
||||
},
|
||||
result);
|
||||
@@ -2051,6 +2108,18 @@ mgp_error mgp_graph_delete_vertex(struct mgp_graph *graph, mgp_vertex *vertex) {
|
||||
throw SerializationException{"Cannot serialize removing a vertex."};
|
||||
}
|
||||
}
|
||||
|
||||
if (!*result) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &ctx = graph->ctx;
|
||||
|
||||
ctx->execution_stats[query::ExecutionStats::Key::DELETED_NODES] += 1;
|
||||
|
||||
if (ctx->trigger_context_collector) {
|
||||
ctx->trigger_context_collector->RegisterDeletedObject(**result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2073,6 +2142,28 @@ mgp_error mgp_graph_detach_delete_vertex(struct mgp_graph *graph, mgp_vertex *ve
|
||||
throw SerializationException{"Cannot serialize removing a vertex."};
|
||||
}
|
||||
}
|
||||
|
||||
if (!*result) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &ctx = graph->ctx;
|
||||
|
||||
ctx->execution_stats[query::ExecutionStats::Key::DELETED_NODES] += 1;
|
||||
ctx->execution_stats[query::ExecutionStats::Key::DELETED_EDGES] += static_cast<int64_t>((*result)->second.size());
|
||||
|
||||
auto *trigger_ctx_collector = ctx->trigger_context_collector;
|
||||
if (!trigger_ctx_collector) {
|
||||
return;
|
||||
}
|
||||
|
||||
trigger_ctx_collector->RegisterDeletedObject((*result)->first);
|
||||
if (!trigger_ctx_collector->ShouldRegisterDeletedObject<query::EdgeAccessor>()) {
|
||||
return;
|
||||
}
|
||||
for (const auto &edge : (*result)->second) {
|
||||
trigger_ctx_collector->RegisterDeletedObject(edge);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2098,6 +2189,13 @@ mgp_error mgp_graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *
|
||||
throw SerializationException{"Cannot serialize creating an edge."};
|
||||
}
|
||||
}
|
||||
auto &ctx = graph->ctx;
|
||||
|
||||
ctx->execution_stats[query::ExecutionStats::Key::CREATED_EDGES] += 1;
|
||||
|
||||
if (ctx->trigger_context_collector) {
|
||||
ctx->trigger_context_collector->RegisterCreatedObject(*edge);
|
||||
}
|
||||
return NewRawMgpObject<mgp_edge>(memory, edge.GetValue(), from->graph);
|
||||
},
|
||||
result);
|
||||
@@ -2122,6 +2220,16 @@ mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, mgp_edge *edge) {
|
||||
throw SerializationException{"Cannot serialize removing an edge."};
|
||||
}
|
||||
}
|
||||
|
||||
if (!*result) {
|
||||
return;
|
||||
}
|
||||
auto &ctx = graph->ctx;
|
||||
|
||||
ctx->execution_stats[query::ExecutionStats::Key::DELETED_EDGES] += 1;
|
||||
if (ctx->trigger_context_collector) {
|
||||
ctx->trigger_context_collector->RegisterDeletedObject(**result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2437,28 +2545,133 @@ bool IsValidIdentifierName(const char *name) {
|
||||
|
||||
} // namespace query::procedure
|
||||
|
||||
namespace {
|
||||
using StreamSourceType = query::stream::StreamSourceType;
|
||||
|
||||
class InvalidMessageFunction : public std::invalid_argument {
|
||||
public:
|
||||
InvalidMessageFunction(const StreamSourceType type, const std::string_view function_name)
|
||||
: std::invalid_argument{fmt::format("'{}' is not defined for a message from a stream of type '{}'", function_name,
|
||||
StreamSourceTypeToString(type))} {}
|
||||
};
|
||||
|
||||
StreamSourceType MessageToStreamSourceType(const mgp_message::KafkaMessage & /*msg*/) {
|
||||
return StreamSourceType::KAFKA;
|
||||
}
|
||||
|
||||
StreamSourceType MessageToStreamSourceType(const mgp_message::PulsarMessage & /*msg*/) {
|
||||
return StreamSourceType::PULSAR;
|
||||
}
|
||||
|
||||
mgp_source_type StreamSourceTypeToMgpSourceType(const StreamSourceType type) {
|
||||
switch (type) {
|
||||
case StreamSourceType::KAFKA:
|
||||
return mgp_source_type::KAFKA;
|
||||
case StreamSourceType::PULSAR:
|
||||
return mgp_source_type::PULSAR;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
mgp_error mgp_message_source_type(mgp_message *message, mgp_source_type *result) {
|
||||
return WrapExceptions(
|
||||
[message] {
|
||||
return std::visit(utils::Overloaded{[](const auto &message) {
|
||||
return StreamSourceTypeToMgpSourceType(MessageToStreamSourceType(message));
|
||||
}},
|
||||
message->msg);
|
||||
},
|
||||
result);
|
||||
}
|
||||
|
||||
mgp_error mgp_message_payload(mgp_message *message, const char **result) {
|
||||
return WrapExceptions([message] { return message->msg->Payload().data(); }, result);
|
||||
return WrapExceptions(
|
||||
[message] {
|
||||
return std::visit(utils::Overloaded{[](const mgp_message::KafkaMessage &msg) { return msg->Payload().data(); },
|
||||
[](const mgp_message::PulsarMessage &msg) { return msg.Payload().data(); },
|
||||
[](const auto &msg) -> const char * {
|
||||
throw InvalidMessageFunction(MessageToStreamSourceType(msg), "payload");
|
||||
}},
|
||||
message->msg);
|
||||
},
|
||||
result);
|
||||
}
|
||||
|
||||
mgp_error mgp_message_payload_size(mgp_message *message, size_t *result) {
|
||||
return WrapExceptions([message] { return message->msg->Payload().size(); }, result);
|
||||
return WrapExceptions(
|
||||
[message] {
|
||||
return std::visit(utils::Overloaded{[](const mgp_message::KafkaMessage &msg) { return msg->Payload().size(); },
|
||||
[](const mgp_message::PulsarMessage &msg) { return msg.Payload().size(); },
|
||||
[](const auto &msg) -> size_t {
|
||||
throw InvalidMessageFunction(MessageToStreamSourceType(msg),
|
||||
"payload_size");
|
||||
}},
|
||||
message->msg);
|
||||
},
|
||||
result);
|
||||
}
|
||||
|
||||
mgp_error mgp_message_topic_name(mgp_message *message, const char **result) {
|
||||
return WrapExceptions([message] { return message->msg->TopicName().data(); }, result);
|
||||
return WrapExceptions(
|
||||
[message] {
|
||||
return std::visit(
|
||||
utils::Overloaded{[](const mgp_message::KafkaMessage &msg) { return msg->TopicName().data(); },
|
||||
[](const mgp_message::PulsarMessage &msg) { return msg.TopicName().data(); },
|
||||
[](const auto &msg) -> const char * {
|
||||
throw InvalidMessageFunction(MessageToStreamSourceType(msg), "topic_name");
|
||||
}},
|
||||
message->msg);
|
||||
},
|
||||
result);
|
||||
}
|
||||
|
||||
mgp_error mgp_message_key(mgp_message *message, const char **result) {
|
||||
return WrapExceptions([message] { return message->msg->Key().data(); }, result);
|
||||
return WrapExceptions(
|
||||
[message] {
|
||||
return std::visit(utils::Overloaded{[](const mgp_message::KafkaMessage &msg) { return msg->Key().data(); },
|
||||
[](const auto &msg) -> const char * {
|
||||
throw InvalidMessageFunction(MessageToStreamSourceType(msg), "key");
|
||||
}},
|
||||
message->msg);
|
||||
},
|
||||
result);
|
||||
}
|
||||
|
||||
mgp_error mgp_message_key_size(mgp_message *message, size_t *result) {
|
||||
return WrapExceptions([message] { return message->msg->Key().size(); }, result);
|
||||
return WrapExceptions(
|
||||
[message] {
|
||||
return std::visit(utils::Overloaded{[](const mgp_message::KafkaMessage &msg) { return msg->Key().size(); },
|
||||
[](const auto &msg) -> size_t {
|
||||
throw InvalidMessageFunction(MessageToStreamSourceType(msg), "key_size");
|
||||
}},
|
||||
message->msg);
|
||||
},
|
||||
result);
|
||||
}
|
||||
|
||||
mgp_error mgp_message_timestamp(mgp_message *message, int64_t *result) {
|
||||
return WrapExceptions([message] { return message->msg->Timestamp(); }, result);
|
||||
return WrapExceptions(
|
||||
[message] {
|
||||
return std::visit(utils::Overloaded{[](const mgp_message::KafkaMessage &msg) { return msg->Timestamp(); },
|
||||
[](const auto &msg) -> int64_t {
|
||||
throw InvalidMessageFunction(MessageToStreamSourceType(msg), "timestamp");
|
||||
}},
|
||||
message->msg);
|
||||
},
|
||||
result);
|
||||
}
|
||||
|
||||
mgp_error mgp_message_offset(struct mgp_message *message, int64_t *result) {
|
||||
return WrapExceptions(
|
||||
[message] {
|
||||
return std::visit(utils::Overloaded{[](const mgp_message::KafkaMessage &msg) { return msg->Offset(); },
|
||||
[](const auto &msg) -> int64_t {
|
||||
throw InvalidMessageFunction(MessageToStreamSourceType(msg), "offset");
|
||||
}},
|
||||
message->msg);
|
||||
},
|
||||
result);
|
||||
}
|
||||
|
||||
mgp_error mgp_messages_size(mgp_messages *messages, size_t *result) {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <ostream>
|
||||
|
||||
#include "integrations/kafka/consumer.hpp"
|
||||
#include "integrations/pulsar/consumer.hpp"
|
||||
#include "query/context.hpp"
|
||||
#include "query/db_accessor.hpp"
|
||||
#include "query/procedure/cypher_type_ptr.hpp"
|
||||
@@ -676,6 +677,17 @@ struct mgp_proc {
|
||||
results(memory),
|
||||
is_write_procedure(is_write_procedure) {}
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error
|
||||
mgp_proc(const std::string_view name, std::function<void(mgp_list *, mgp_graph *, mgp_result *, mgp_memory *)> cb,
|
||||
utils::MemoryResource *memory, bool is_write_procedure)
|
||||
: name(name, memory),
|
||||
cb(cb),
|
||||
args(memory),
|
||||
opt_args(memory),
|
||||
results(memory),
|
||||
is_write_procedure(is_write_procedure) {}
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error
|
||||
mgp_proc(const mgp_proc &other, utils::MemoryResource *memory)
|
||||
@@ -790,7 +802,12 @@ bool IsValidIdentifierName(const char *name);
|
||||
} // namespace query::procedure
|
||||
|
||||
struct mgp_message {
|
||||
const integrations::kafka::Message *msg;
|
||||
explicit mgp_message(const integrations::kafka::Message &message) : msg{&message} {}
|
||||
explicit mgp_message(const integrations::pulsar::Message &message) : msg{message} {}
|
||||
|
||||
using KafkaMessage = const integrations::kafka::Message *;
|
||||
using PulsarMessage = integrations::pulsar::Message;
|
||||
std::variant<KafkaMessage, PulsarMessage> msg;
|
||||
};
|
||||
|
||||
struct mgp_messages {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Memgraph Ltd.
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -18,12 +18,15 @@ extern "C" {
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "fmt/format.h"
|
||||
#include <fmt/format.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "py/py.hpp"
|
||||
#include "query/procedure/mg_procedure_helpers.hpp"
|
||||
#include "query/procedure/py_module.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/message.hpp"
|
||||
#include "utils/pmr/vector.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
@@ -52,6 +55,8 @@ class BuiltinModule final : public Module {
|
||||
|
||||
void AddTransformation(std::string_view name, mgp_trans trans);
|
||||
|
||||
std::optional<std::filesystem::path> Path() const override { return std::nullopt; }
|
||||
|
||||
private:
|
||||
/// Registered procedures
|
||||
std::map<std::string, mgp_proc, std::less<>> procedures_;
|
||||
@@ -132,6 +137,20 @@ void RegisterMgLoad(ModuleRegistry *module_registry, utils::RWLock *lock, Builti
|
||||
module->AddProcedure("load", std::move(load));
|
||||
}
|
||||
|
||||
namespace {
|
||||
[[nodiscard]] bool IsFileEditable(const std::optional<std::filesystem::path> &path) {
|
||||
return path && access(path->c_str(), W_OK) == 0;
|
||||
}
|
||||
|
||||
std::string GetPathString(const std::optional<std::filesystem::path> &path) {
|
||||
if (!path) {
|
||||
return "builtin";
|
||||
}
|
||||
|
||||
return std::filesystem::canonical(*path).generic_string();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void RegisterMgProcedures(
|
||||
// We expect modules to be sorted by name.
|
||||
const std::map<std::string, std::unique_ptr<Module>, std::less<>> *all_modules, BuiltinModule *module) {
|
||||
@@ -146,57 +165,80 @@ void RegisterMgProcedures(
|
||||
static_assert(
|
||||
std::is_same_v<decltype(module->Procedures()), const std::map<std::string, mgp_proc, std::less<>> *>,
|
||||
"Expected module procedures to be sorted by name");
|
||||
|
||||
const auto path = module->Path();
|
||||
const auto path_string = GetPathString(path);
|
||||
const auto is_editable = IsFileEditable(path);
|
||||
|
||||
for (const auto &[proc_name, proc] : *module->Procedures()) {
|
||||
mgp_result_record *record{nullptr};
|
||||
if (const auto err = mgp_result_new_record(result, &record); err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
static_cast<void>(mgp_result_set_error_msg(result, "Not enough memory!"));
|
||||
return;
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
static_cast<void>(mgp_result_set_error_msg(result, "Unexpected error"));
|
||||
{
|
||||
const auto success = TryOrSetError([&] { return mgp_result_new_record(result, &record); }, result);
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto path_value = GetStringValueOrSetError(path_string.c_str(), memory, result);
|
||||
if (!path_value) {
|
||||
return;
|
||||
}
|
||||
|
||||
MgpUniquePtr<mgp_value> is_editable_value{nullptr, mgp_value_destroy};
|
||||
{
|
||||
const auto success = TryOrSetError(
|
||||
[&] { return CreateMgpObject(is_editable_value, mgp_value_make_bool, is_editable, memory); }, result);
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
utils::pmr::string full_name(module_name, memory->impl);
|
||||
full_name.append(1, '.');
|
||||
full_name.append(proc_name);
|
||||
MgpUniquePtr<mgp_value> name_value{nullptr, mgp_value_destroy};
|
||||
if (const auto err = CreateMgpObject(name_value, mgp_value_make_string, full_name.c_str(), memory);
|
||||
err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
static_cast<void>(mgp_result_set_error_msg(result, "Not enough memory!"));
|
||||
return;
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
static_cast<void>(mgp_result_set_error_msg(result, "Unexpected error"));
|
||||
const auto name_value = GetStringValueOrSetError(full_name.c_str(), memory, result);
|
||||
if (!name_value) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
ss << module_name << ".";
|
||||
PrintProcSignature(proc, &ss);
|
||||
const auto signature = ss.str();
|
||||
MgpUniquePtr<mgp_value> signature_value{nullptr, mgp_value_destroy};
|
||||
if (const auto err = CreateMgpObject(signature_value, mgp_value_make_string, signature.c_str(), memory);
|
||||
err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
static_cast<void>(mgp_result_set_error_msg(result, "Not enough memory!"));
|
||||
return;
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
static_cast<void>(mgp_result_set_error_msg(result, "Unexpected error"));
|
||||
const auto signature_value = GetStringValueOrSetError(signature.c_str(), memory, result);
|
||||
if (!signature_value) {
|
||||
return;
|
||||
}
|
||||
MgpUniquePtr<mgp_value> is_write_value{nullptr, mgp_value_destroy};
|
||||
|
||||
if (const auto err =
|
||||
CreateMgpObject(is_write_value, mgp_value_make_bool, proc.is_write_procedure ? 1 : 0, memory);
|
||||
err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
static_cast<void>(mgp_result_set_error_msg(result, "Not enough memory!"));
|
||||
return;
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
static_cast<void>(mgp_result_set_error_msg(result, "Unexpected error"));
|
||||
MgpUniquePtr<mgp_value> is_write_value{nullptr, mgp_value_destroy};
|
||||
{
|
||||
const auto success = TryOrSetError(
|
||||
[&, &proc = proc] {
|
||||
return CreateMgpObject(is_write_value, mgp_value_make_bool, proc.is_write_procedure ? 1 : 0, memory);
|
||||
},
|
||||
result);
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!InsertResultOrSetError(result, record, "name", name_value.get())) {
|
||||
return;
|
||||
}
|
||||
const auto err1 = mgp_result_record_insert(record, "name", name_value.get());
|
||||
const auto err2 = mgp_result_record_insert(record, "signature", signature_value.get());
|
||||
const auto err3 = mgp_result_record_insert(record, "is_write", is_write_value.get());
|
||||
if (err1 != MGP_ERROR_NO_ERROR || err2 != MGP_ERROR_NO_ERROR || err3 != MGP_ERROR_NO_ERROR) {
|
||||
static_cast<void>(mgp_result_set_error_msg(result, "Unable to set the result!"));
|
||||
|
||||
if (!InsertResultOrSetError(result, record, "signature", signature_value.get())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!InsertResultOrSetError(result, record, "is_write", is_write_value.get())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!InsertResultOrSetError(result, record, "path", path_value.get())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!InsertResultOrSetError(result, record, "is_editable", is_editable_value.get())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -206,6 +248,8 @@ void RegisterMgProcedures(
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "name", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "signature", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "is_write", Call<mgp_type *>(mgp_type_bool)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "path", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "is_editable", Call<mgp_type *>(mgp_type_bool)) == MGP_ERROR_NO_ERROR);
|
||||
module->AddProcedure("procedures", std::move(procedures));
|
||||
}
|
||||
|
||||
@@ -218,32 +262,52 @@ void RegisterMgTransformations(const std::map<std::string, std::unique_ptr<Modul
|
||||
static_assert(
|
||||
std::is_same_v<decltype(module->Transformations()), const std::map<std::string, mgp_trans, std::less<>> *>,
|
||||
"Expected module transformations to be sorted by name");
|
||||
|
||||
const auto path = module->Path();
|
||||
const auto path_string = GetPathString(path);
|
||||
const auto is_editable = IsFileEditable(path);
|
||||
|
||||
for (const auto &[trans_name, proc] : *module->Transformations()) {
|
||||
mgp_result_record *record{nullptr};
|
||||
if (const auto err = mgp_result_new_record(result, &record); err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
static_cast<void>(mgp_result_set_error_msg(result, "Not enough memory!"));
|
||||
return;
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
static_cast<void>(mgp_result_set_error_msg(result, "Unexpected error"));
|
||||
{
|
||||
const auto success = TryOrSetError([&] { return mgp_result_new_record(result, &record); }, result);
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto path_value = GetStringValueOrSetError(path_string.c_str(), memory, result);
|
||||
if (!path_value) {
|
||||
return;
|
||||
}
|
||||
|
||||
MgpUniquePtr<mgp_value> is_editable_value{nullptr, mgp_value_destroy};
|
||||
{
|
||||
const auto success = TryOrSetError(
|
||||
[&] { return CreateMgpObject(is_editable_value, mgp_value_make_bool, is_editable, memory); }, result);
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
utils::pmr::string full_name(module_name, memory->impl);
|
||||
full_name.append(1, '.');
|
||||
full_name.append(trans_name);
|
||||
|
||||
MgpUniquePtr<mgp_value> name_value{nullptr, mgp_value_destroy};
|
||||
if (const auto err = CreateMgpObject(name_value, mgp_value_make_string, full_name.c_str(), memory);
|
||||
err == MGP_ERROR_UNABLE_TO_ALLOCATE) {
|
||||
static_cast<void>(mgp_result_set_error_msg(result, "Not enough memory!"));
|
||||
return;
|
||||
} else if (err != MGP_ERROR_NO_ERROR) {
|
||||
static_cast<void>(mgp_result_set_error_msg(result, "Unexpected error"));
|
||||
const auto name_value = GetStringValueOrSetError(full_name.c_str(), memory, result);
|
||||
if (!name_value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (const auto err = mgp_result_record_insert(record, "name", name_value.get()); err != MGP_ERROR_NO_ERROR) {
|
||||
static_cast<void>(mgp_result_set_error_msg(result, "Unable to set the result!"));
|
||||
if (!InsertResultOrSetError(result, record, "name", name_value.get())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!InsertResultOrSetError(result, record, "path", path_value.get())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!InsertResultOrSetError(result, record, "is_editable", is_editable_value.get())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -251,6 +315,8 @@ void RegisterMgTransformations(const std::map<std::string, std::unique_ptr<Modul
|
||||
};
|
||||
mgp_proc procedures("transformations", transformations_cb, utils::NewDeleteResource(), false);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "name", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "path", Call<mgp_type *>(mgp_type_string)) == MGP_ERROR_NO_ERROR);
|
||||
MG_ASSERT(mgp_proc_add_result(&procedures, "is_editable", Call<mgp_type *>(mgp_type_bool)) == MGP_ERROR_NO_ERROR);
|
||||
module->AddProcedure("transformations", std::move(procedures));
|
||||
}
|
||||
|
||||
@@ -296,6 +362,8 @@ class SharedLibraryModule final : public Module {
|
||||
|
||||
const std::map<std::string, mgp_trans, std::less<>> *Transformations() const override;
|
||||
|
||||
std::optional<std::filesystem::path> Path() const override { return file_path_; }
|
||||
|
||||
private:
|
||||
/// Path as requested for loading the module from a library.
|
||||
std::filesystem::path file_path_;
|
||||
@@ -324,14 +392,16 @@ bool SharedLibraryModule::Load(const std::filesystem::path &file_path) {
|
||||
dlerror(); // Clear any existing error.
|
||||
handle_ = dlopen(file_path.c_str(), RTLD_NOW | RTLD_LOCAL);
|
||||
if (!handle_) {
|
||||
spdlog::error("Unable to load module {}; {}", file_path, dlerror());
|
||||
spdlog::error(
|
||||
utils::MessageWithLink("Unable to load module {}; {}.", file_path, dlerror(), "https://memgr.ph/modules"));
|
||||
return false;
|
||||
}
|
||||
// Get required mgp_init_module
|
||||
init_fn_ = reinterpret_cast<int (*)(mgp_module *, mgp_memory *)>(dlsym(handle_, "mgp_init_module"));
|
||||
char *dl_errored = dlerror();
|
||||
if (!init_fn_ || dl_errored) {
|
||||
spdlog::error("Unable to load module {}; {}", file_path, dl_errored);
|
||||
spdlog::error(
|
||||
utils::MessageWithLink("Unable to load module {}; {}.", file_path, dl_errored, "https://memgr.ph/modules"));
|
||||
dlclose(handle_);
|
||||
handle_ = nullptr;
|
||||
return false;
|
||||
@@ -351,8 +421,8 @@ bool SharedLibraryModule::Load(const std::filesystem::path &file_path) {
|
||||
return with_error(error);
|
||||
}
|
||||
for (auto &trans : module_def->transformations) {
|
||||
const bool was_result_added = MgpTransAddFixedResult(&trans.second);
|
||||
if (!was_result_added) {
|
||||
const auto error_code = MgpTransAddFixedResult(&trans.second);
|
||||
if (error_code != MGP_ERROR_NO_ERROR) {
|
||||
const auto error =
|
||||
fmt::format("Unable to add result to transformation in module {}; add result failed", file_path);
|
||||
return with_error(error);
|
||||
@@ -382,7 +452,8 @@ bool SharedLibraryModule::Close() {
|
||||
spdlog::warn("When closing module {}; mgp_shutdown_module returned {}", file_path_, shutdown_res);
|
||||
}
|
||||
if (dlclose(handle_) != 0) {
|
||||
spdlog::error("Failed to close module {}; {}", file_path_, dlerror());
|
||||
spdlog::error(
|
||||
utils::MessageWithLink("Failed to close module {}; {}.", file_path_, dlerror(), "https://memgr.ph/modules"));
|
||||
return false;
|
||||
}
|
||||
spdlog::info("Closed module {}", file_path_);
|
||||
@@ -420,6 +491,7 @@ class PythonModule final : public Module {
|
||||
|
||||
const std::map<std::string, mgp_proc, std::less<>> *Procedures() const override;
|
||||
const std::map<std::string, mgp_trans, std::less<>> *Transformations() const override;
|
||||
std::optional<std::filesystem::path> Path() const override { return file_path_; }
|
||||
|
||||
private:
|
||||
std::filesystem::path file_path_;
|
||||
@@ -441,7 +513,8 @@ bool PythonModule::Load(const std::filesystem::path &file_path) {
|
||||
auto gil = py::EnsureGIL();
|
||||
auto maybe_exc = py::AppendToSysPath(file_path.parent_path().c_str());
|
||||
if (maybe_exc) {
|
||||
spdlog::error("Unable to load module {}; {}", file_path, *maybe_exc);
|
||||
spdlog::error(
|
||||
utils::MessageWithLink("Unable to load module {}; {}.", file_path, *maybe_exc, "https://memgr.ph/modules"));
|
||||
return false;
|
||||
}
|
||||
bool succ = true;
|
||||
@@ -466,7 +539,8 @@ bool PythonModule::Load(const std::filesystem::path &file_path) {
|
||||
return true;
|
||||
}
|
||||
auto exc_info = py::FetchError().value();
|
||||
spdlog::error("Unable to load module {}; {}", file_path, exc_info);
|
||||
spdlog::error(
|
||||
utils::MessageWithLink("Unable to load module {}; {}.", file_path, exc_info, "https://memgr.ph/modules"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -509,7 +583,7 @@ namespace {
|
||||
std::unique_ptr<Module> LoadModuleFromFile(const std::filesystem::path &path) {
|
||||
const auto &ext = path.extension();
|
||||
if (ext != ".so" && ext != ".py") {
|
||||
spdlog::warn("Unknown query module file {}", path);
|
||||
spdlog::warn(utils::MessageWithLink("Unknown query module file {}.", path, "https://memgr.ph/modules"));
|
||||
return nullptr;
|
||||
}
|
||||
std::unique_ptr<Module> module;
|
||||
@@ -531,7 +605,8 @@ bool ModuleRegistry::RegisterModule(const std::string_view &name, std::unique_pt
|
||||
MG_ASSERT(!name.empty(), "Module name cannot be empty");
|
||||
MG_ASSERT(module, "Tried to register an invalid module");
|
||||
if (modules_.find(name) != modules_.end()) {
|
||||
spdlog::error("Unable to overwrite an already loaded module {}", name);
|
||||
spdlog::error(
|
||||
utils::MessageWithLink("Unable to overwrite an already loaded module {}.", name, "https://memgr.ph/modules"));
|
||||
return false;
|
||||
}
|
||||
modules_.emplace(name, std::move(module));
|
||||
@@ -561,7 +636,8 @@ void ModuleRegistry::SetModulesDirectory(std::vector<std::filesystem::path> modu
|
||||
|
||||
bool ModuleRegistry::LoadModuleIfFound(const std::filesystem::path &modules_dir, const std::string_view name) {
|
||||
if (!utils::DirExists(modules_dir)) {
|
||||
spdlog::error("Module directory {} doesn't exist", modules_dir);
|
||||
spdlog::error(
|
||||
utils::MessageWithLink("Module directory {} doesn't exist.", modules_dir, "https://memgr.ph/modules"));
|
||||
return false;
|
||||
}
|
||||
for (const auto &entry : std::filesystem::directory_iterator(modules_dir)) {
|
||||
@@ -598,7 +674,8 @@ bool ModuleRegistry::LoadOrReloadModuleFromName(const std::string_view name) {
|
||||
void ModuleRegistry::LoadModulesFromDirectory(const std::filesystem::path &modules_dir) {
|
||||
if (modules_dir.empty()) return;
|
||||
if (!utils::DirExists(modules_dir)) {
|
||||
spdlog::error("Module directory {} doesn't exist", modules_dir);
|
||||
spdlog::error(
|
||||
utils::MessageWithLink("Module directory {} doesn't exist.", modules_dir, "https://memgr.ph/modules"));
|
||||
return;
|
||||
}
|
||||
for (const auto &entry : std::filesystem::directory_iterator(modules_dir)) {
|
||||
@@ -635,6 +712,16 @@ void ModuleRegistry::UnloadAllModules() {
|
||||
|
||||
utils::MemoryResource &ModuleRegistry::GetSharedMemoryResource() noexcept { return *shared_; }
|
||||
|
||||
bool ModuleRegistry::RegisterMgProcedure(const std::string_view name, mgp_proc proc) {
|
||||
std::unique_lock<utils::RWLock> guard(lock_);
|
||||
if (auto module = modules_.find("mg"); module != modules_.end()) {
|
||||
auto *builtin_module = dynamic_cast<BuiltinModule *>(module->second.get());
|
||||
builtin_module->AddProcedure(name, std::move(proc));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/// This function returns a pair of either
|
||||
@@ -658,9 +745,9 @@ template <typename T>
|
||||
concept ModuleProperties = utils::SameAsAnyOf<T, mgp_proc, mgp_trans>;
|
||||
|
||||
template <ModuleProperties T>
|
||||
std::optional<std::pair<procedure::ModulePtr, const T *>> MakePairIfPropFound(const ModuleRegistry &module_registry,
|
||||
std::string_view fully_qualified_name,
|
||||
utils::MemoryResource *memory) {
|
||||
std::optional<std::pair<ModulePtr, const T *>> MakePairIfPropFound(const ModuleRegistry &module_registry,
|
||||
std::string_view fully_qualified_name,
|
||||
utils::MemoryResource *memory) {
|
||||
auto prop_fun = [](auto &module) {
|
||||
if constexpr (std::is_same_v<T, mgp_proc>) {
|
||||
return module->Procedures();
|
||||
@@ -681,13 +768,13 @@ std::optional<std::pair<procedure::ModulePtr, const T *>> MakePairIfPropFound(co
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<std::pair<procedure::ModulePtr, const mgp_proc *>> FindProcedure(
|
||||
const ModuleRegistry &module_registry, std::string_view fully_qualified_procedure_name,
|
||||
utils::MemoryResource *memory) {
|
||||
std::optional<std::pair<ModulePtr, const mgp_proc *>> FindProcedure(const ModuleRegistry &module_registry,
|
||||
std::string_view fully_qualified_procedure_name,
|
||||
utils::MemoryResource *memory) {
|
||||
return MakePairIfPropFound<mgp_proc>(module_registry, fully_qualified_procedure_name, memory);
|
||||
}
|
||||
|
||||
std::optional<std::pair<procedure::ModulePtr, const mgp_trans *>> FindTransformation(
|
||||
std::optional<std::pair<ModulePtr, const mgp_trans *>> FindTransformation(
|
||||
const ModuleRegistry &module_registry, std::string_view fully_qualified_transformation_name,
|
||||
utils::MemoryResource *memory) {
|
||||
return MakePairIfPropFound<mgp_trans>(module_registry, fully_qualified_transformation_name, memory);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user