Compare commits
104 Commits
T610-FL-Ad
...
T580-FL-fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11edd65757 | ||
|
|
8c58eddd54 | ||
|
|
13c33149e5 | ||
|
|
af519d517a | ||
|
|
49eb6390ff | ||
|
|
b9ae685c48 | ||
|
|
41622cb765 | ||
|
|
a34a2f4140 | ||
|
|
19832b5838 | ||
|
|
e63fae2d5b | ||
|
|
34dd47ef07 | ||
|
|
d3f275b231 | ||
|
|
aad4bcb7a0 | ||
|
|
034b54cb72 | ||
|
|
8cf51d9f68 | ||
|
|
1cd1da84fd | ||
|
|
128a6cd522 | ||
|
|
d9eeedb9ee | ||
|
|
156e2cd095 | ||
|
|
8b834c702c | ||
|
|
eda5213d95 | ||
|
|
1f2a15e7c8 | ||
|
|
d72e7fa38d | ||
|
|
e5e37bc14a | ||
|
|
3ee068bbf9 | ||
|
|
68e846b182 | ||
|
|
310e305cfb | ||
|
|
9d6a23b6bd | ||
|
|
f2d5ab61c4 | ||
|
|
0f77c85824 | ||
|
|
d6d4153fb7 | ||
|
|
7d6a5e5b9c | ||
|
|
c529d52664 | ||
|
|
45451bae3b | ||
|
|
3e11f38548 | ||
|
|
6e4047a847 | ||
|
|
8febdc12fb | ||
|
|
3f23a10f44 | ||
|
|
11300960de | ||
|
|
1d5f387ddd | ||
|
|
c4c3a254bf | ||
|
|
b4beb0fc86 | ||
|
|
58e6097664 | ||
|
|
ff21c0705c | ||
|
|
2a2b99b02a | ||
|
|
3daab6ce97 | ||
|
|
fbd7274c95 | ||
|
|
6efc84f022 | ||
|
|
287c2e94d1 | ||
|
|
417cf4b30b | ||
|
|
68e7fd3d36 | ||
|
|
5261d82063 | ||
|
|
9eb87bcf3e | ||
|
|
a9491d3e68 | ||
|
|
b42e47b0be | ||
|
|
898c894a48 | ||
|
|
a3c2492672 | ||
|
|
a0b8871b36 | ||
|
|
bb6cf35441 | ||
|
|
5bc301d21d | ||
|
|
1b89e679df | ||
|
|
43e0520bc8 | ||
|
|
2c8e45e889 | ||
|
|
0876a8848d | ||
|
|
fb4641a6be | ||
|
|
201f75e809 | ||
|
|
dc8dad9794 | ||
|
|
aa02745915 | ||
|
|
b2d5a8eeca | ||
|
|
c09b175c76 | ||
|
|
f1fe77adfb | ||
|
|
35f8978560 | ||
|
|
9e8fb2516b | ||
|
|
0a66feccff | ||
|
|
d008a2ad8d | ||
|
|
7478300762 | ||
|
|
0bc298c3ad | ||
|
|
05f120b7d4 | ||
|
|
d73d153978 | ||
|
|
b489ac7cff | ||
|
|
e15576f56c | ||
|
|
a98463b0bd | ||
|
|
705631a35d | ||
|
|
d4f0bb0e38 | ||
|
|
531db2d47c | ||
|
|
116262d9a0 | ||
|
|
bbfef45b37 | ||
|
|
05b00edfd4 | ||
|
|
480df4ed69 | ||
|
|
80e0e439b7 | ||
|
|
351258ace8 | ||
|
|
74d3663821 | ||
|
|
eb0b3141d5 | ||
|
|
ff2f8031a9 | ||
|
|
094d4f282d | ||
|
|
3dd2657320 | ||
|
|
6fe474282a | ||
|
|
7fc0fb6520 | ||
|
|
063e297e1e | ||
|
|
86b1688192 | ||
|
|
f629de7e60 | ||
|
|
b737e53456 | ||
|
|
10ca68bb2a | ||
|
|
bfbd8538d4 |
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -3,7 +3,9 @@
|
||||
- [ ] Update [changelog](https://docs.memgraph.com/memgraph/changelog)
|
||||
- [ ] Write E2E tests
|
||||
- [ ] Compare the [benchmarking results](https://bench-graph.memgraph.com/) between the master branch and the Epic branch
|
||||
- [ ] Provide the full content or a guide for the final git message
|
||||
|
||||
[master < Task] PR
|
||||
- [ ] Check, and update documentation if necessary
|
||||
- [ ] Update [changelog](https://docs.memgraph.com/memgraph/changelog)
|
||||
- [ ] Provide the full content or a guide for the final git message
|
||||
|
||||
2
.github/workflows/daily_benchmark.yaml
vendored
2
.github/workflows/daily_benchmark.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
|
||||
60
.github/workflows/diff.yaml
vendored
60
.github/workflows/diff.yaml
vendored
@@ -14,6 +14,7 @@ on:
|
||||
- "**/*.md"
|
||||
- ".clang-format"
|
||||
- "CODEOWNERS"
|
||||
- "licenses/*"
|
||||
|
||||
jobs:
|
||||
community_build:
|
||||
@@ -26,7 +27,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -64,13 +65,18 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build combined ASAN, UBSAN and coverage binaries
|
||||
# This is also needed if we want do to comparison against other branches
|
||||
# See https://github.community/t/checkout-code-fails-when-it-runs-lerna-run-test-since-master/17920
|
||||
- name: Fetch all history for all tags and branches
|
||||
run: git fetch
|
||||
|
||||
- name: Initialize deps
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
@@ -78,6 +84,32 @@ jobs:
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
- name: Set base branch
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: |
|
||||
echo "BASE_BRANCH=origin/${{ github.base_ref }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set base branch # if we manually dispatch or push to master
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
run: |
|
||||
echo "BASE_BRANCH=origin/master" >> $GITHUB_ENV
|
||||
|
||||
- name: Python code analysis
|
||||
run: |
|
||||
CHANGED_FILES=$(git diff -U0 ${{ env.BASE_BRANCH }}... --name-only)
|
||||
for file in ${CHANGED_FILES}; do
|
||||
echo ${file}
|
||||
if [[ ${file} == *.py ]]; then
|
||||
python3 -m black --check --diff ${file}
|
||||
python3 -m isort --check-only --diff ${file}
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Build combined ASAN, UBSAN and coverage binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
cd build
|
||||
cmake -DTEST_COVERAGE=ON -DASAN=ON -DUBSAN=ON ..
|
||||
make -j$THREADS memgraph__unit
|
||||
@@ -105,7 +137,7 @@ jobs:
|
||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||
|
||||
- name: Save code coverage
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/generated/code_coverage.tar.gz
|
||||
@@ -115,7 +147,7 @@ jobs:
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Restrict clang-tidy results only to the modified parts
|
||||
git diff -U0 master... -- src | ./tools/github/clang-tidy/clang-tidy-diff.py -p 1 -j $THREADS -path build | tee ./build/clang_tidy_output.txt
|
||||
git diff -U0 ${{ env.BASE_BRANCH }}... -- src | ./tools/github/clang-tidy/clang-tidy-diff.py -p 1 -j $THREADS -path build -regex ".+\.cpp" | tee ./build/clang_tidy_output.txt
|
||||
|
||||
# Fail if any warning is reported
|
||||
! cat ./build/clang_tidy_output.txt | ./tools/github/clang-tidy/grep_error_lines.sh > /dev/null
|
||||
@@ -130,7 +162,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -191,7 +223,7 @@ jobs:
|
||||
./cppcheck_and_clang_format diff
|
||||
|
||||
- name: Save cppcheck and clang-format errors
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/cppcheck_and_clang_format.txt
|
||||
@@ -206,7 +238,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -231,7 +263,7 @@ jobs:
|
||||
./continuous_integration
|
||||
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
@@ -288,13 +320,13 @@ jobs:
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save enterprise DEB package
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Enterprise DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
|
||||
- name: Save test data
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: "Test data"
|
||||
@@ -313,7 +345,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -338,7 +370,7 @@ jobs:
|
||||
./run.sh test --binary ../../build/memgraph --run-args "test-all --node-configs resources/node-config.edn" --ignore-run-stdout-logs --ignore-run-stderr-logs
|
||||
|
||||
- name: Save Jepsen report
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: "Jepsen Report"
|
||||
@@ -354,7 +386,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
|
||||
2
.github/workflows/full_clang_tidy.yaml
vendored
2
.github/workflows/full_clang_tidy.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
|
||||
24
.github/workflows/package_all.yaml
vendored
24
.github/workflows/package_all.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
run: |
|
||||
./release/package/run.sh package centos-7
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: centos-7
|
||||
path: build/output/centos-7/memgraph*.rpm
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
run: |
|
||||
./release/package/run.sh package centos-9
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: centos-9
|
||||
path: build/output/centos-9/memgraph*.rpm
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
run: |
|
||||
./release/package/run.sh package debian-10
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: debian-10
|
||||
path: build/output/debian-10/memgraph*.deb
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
run: |
|
||||
./release/package/run.sh package debian-11
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: debian-11
|
||||
path: build/output/debian-11/memgraph*.deb
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
./run.sh package debian-11 --for-docker
|
||||
./run.sh docker
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docker
|
||||
path: build/output/docker/memgraph*.tar.gz
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
run: |
|
||||
./release/package/run.sh package ubuntu-18.04
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ubuntu-1804
|
||||
path: build/output/ubuntu-18.04/memgraph*.deb
|
||||
@@ -121,7 +121,7 @@ jobs:
|
||||
run: |
|
||||
./release/package/run.sh package ubuntu-20.04
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ubuntu-2004
|
||||
path: build/output/ubuntu-20.04/memgraph*.deb
|
||||
@@ -138,7 +138,7 @@ jobs:
|
||||
run: |
|
||||
./release/package/run.sh package ubuntu-22.04
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ubuntu-2204
|
||||
path: build/output/ubuntu-22.04/memgraph*.deb
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
run: |
|
||||
./release/package/run.sh package debian-11 --for-platform
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: debian-11-platform
|
||||
path: build/output/debian-11/memgraph*.deb
|
||||
@@ -172,7 +172,7 @@ jobs:
|
||||
run: |
|
||||
./release/package/run.sh package debian-11-arm
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: debian-11
|
||||
path: build/output/debian-11/memgraph*.deb
|
||||
name: debian-11-arm
|
||||
path: build/output/debian-11-arm/memgraph*.deb
|
||||
|
||||
16
.github/workflows/release_centos8.yaml
vendored
16
.github/workflows/release_centos8.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||
|
||||
- name: Save code coverage
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/generated/code_coverage.tar.gz
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -173,7 +173,7 @@ jobs:
|
||||
./cppcheck_and_clang_format diff
|
||||
|
||||
- name: Save cppcheck and clang-format errors
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/cppcheck_and_clang_format.txt
|
||||
@@ -189,7 +189,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -225,7 +225,7 @@ jobs:
|
||||
rpmlint memgraph*.rpm
|
||||
|
||||
- name: Save enterprise RPM package
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Enterprise RPM package"
|
||||
path: build/output/memgraph*.rpm
|
||||
@@ -262,7 +262,7 @@ jobs:
|
||||
./continuous_integration
|
||||
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
|
||||
20
.github/workflows/release_debian10.yaml
vendored
20
.github/workflows/release_debian10.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||
|
||||
- name: Save code coverage
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/generated/code_coverage.tar.gz
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -173,7 +173,7 @@ jobs:
|
||||
./cppcheck_and_clang_format diff
|
||||
|
||||
- name: Save cppcheck and clang-format errors
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/cppcheck_and_clang_format.txt
|
||||
@@ -189,7 +189,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -224,7 +224,7 @@ jobs:
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save enterprise DEB package
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Enterprise DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
@@ -261,7 +261,7 @@ jobs:
|
||||
./continuous_integration
|
||||
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
@@ -324,7 +324,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -349,7 +349,7 @@ jobs:
|
||||
./run.sh test --binary ../../build/memgraph --run-args "test-all --node-configs resources/node-config.edn" --ignore-run-stdout-logs --ignore-run-stderr-logs
|
||||
|
||||
- name: Save Jepsen report
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: "Jepsen Report"
|
||||
|
||||
30
.github/workflows/release_docker.yaml
vendored
30
.github/workflows/release_docker.yaml
vendored
@@ -4,8 +4,12 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Memgraph binary version to publish on Dockerhub."
|
||||
description: "Memgraph binary version to publish on DockerHub."
|
||||
required: true
|
||||
force_release:
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
docker_publish:
|
||||
@@ -15,17 +19,17 @@ jobs:
|
||||
DOCKER_REPOSITORY_NAME: memgraph
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
@@ -36,6 +40,22 @@ jobs:
|
||||
curl -L https://download.memgraph.com/memgraph/v${{ github.event.inputs.version }}/debian-11/memgraph_${{ github.event.inputs.version }}-1_amd64.deb > memgraph-amd64.deb
|
||||
curl -L https://download.memgraph.com/memgraph/v${{ github.event.inputs.version }}/debian-11-aarch64/memgraph_${{ github.event.inputs.version }}-1_arm64.deb > memgraph-arm64.deb
|
||||
|
||||
- name: Check if specified version is already pushed
|
||||
run: |
|
||||
EXISTS=$(docker manifest inspect $DOCKER_ORGANIZATION_NAME/$DOCKER_REPOSITORY_NAME:${{ github.event.inputs.version }} > /dev/null; echo $?)
|
||||
echo $EXISTS
|
||||
if [[ ${EXISTS} -eq 0 ]]; then
|
||||
echo 'The specified version has been already released to DockerHub.'
|
||||
if [[ ${{ github.event.inputs.force_release }} = true ]]; then
|
||||
echo 'Forcing the release!'
|
||||
else
|
||||
echo 'Stopping the release!'
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo 'All good the specified version has not been release to DockerHub.'
|
||||
fi
|
||||
|
||||
- name: Build & push docker images
|
||||
run: |
|
||||
cd release/docker
|
||||
|
||||
16
.github/workflows/release_ubuntu2004.yaml
vendored
16
.github/workflows/release_ubuntu2004.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||
|
||||
- name: Save code coverage
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/generated/code_coverage.tar.gz
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -173,7 +173,7 @@ jobs:
|
||||
./cppcheck_and_clang_format diff
|
||||
|
||||
- name: Save cppcheck and clang-format errors
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/cppcheck_and_clang_format.txt
|
||||
@@ -189,7 +189,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
@@ -224,7 +224,7 @@ jobs:
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save enterprise DEB package
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "Enterprise DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
@@ -261,7 +261,7 @@ jobs:
|
||||
./continuous_integration
|
||||
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,7 +9,6 @@
|
||||
*.swn
|
||||
*.swo
|
||||
*.swp
|
||||
|
||||
*~
|
||||
.DS_Store
|
||||
.gdb_history
|
||||
@@ -27,7 +26,6 @@ src/query/frontend/opencypher/generated/
|
||||
tags
|
||||
ve/
|
||||
ve3/
|
||||
.cache/
|
||||
perf.data*
|
||||
TAGS
|
||||
*.apollo_measurements
|
||||
|
||||
@@ -6,18 +6,14 @@ repos:
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.3.0
|
||||
rev: 22.8.0
|
||||
hooks:
|
||||
- id: black
|
||||
args: # arguments to configure black
|
||||
- --line-length=120
|
||||
- --include='\.pyi?$'
|
||||
# these folders wont be formatted by black
|
||||
- --exclude="""\.git |
|
||||
\.__pycache__|
|
||||
build|
|
||||
libs|
|
||||
.cache"""
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.10.1
|
||||
hooks:
|
||||
- id: isort
|
||||
name: isort (python)
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: v13.0.0
|
||||
hooks:
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
* @antaljanosbenjamin @kostasrim
|
||||
23
README.md
23
README.md
@@ -22,7 +22,7 @@ Build modern, graph-based applications on top of your streaming data in minutes.
|
||||
|
||||
<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"/>
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/memgraph/memgraph/release_debian10.yaml?branch=master&label=build%20and%20test&logo=github"/>
|
||||
</a>
|
||||
<a href="https://memgraph.com/docs/" alt="Documentation">
|
||||
<img src="https://img.shields.io/badge/documentation-Memgraph-orange" />
|
||||
@@ -37,9 +37,10 @@ Build modern, graph-based applications on top of your streaming data in minutes.
|
||||
|
||||
## :clipboard: Description
|
||||
|
||||
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 is an open source graph database built for real-time streaming and
|
||||
compatible with Neo4j. Whether you're a developer or a data scientist with
|
||||
interconnected data, Memgraph will get you the immediate actionable insights
|
||||
fast.
|
||||
|
||||
Memgraph directly connects to your streaming infrastructure. You can ingest data
|
||||
from sources like Kafka, SQL, or plain CSV files. Memgraph provides a standard
|
||||
@@ -51,8 +52,9 @@ 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.
|
||||
to ensure that you’re getting the [best possible
|
||||
performance](http://memgraph.com/benchgraph) consistently and without surprises.
|
||||
It’s also ACID-compliant and highly available.
|
||||
|
||||
## :video_game: Memgraph Playground
|
||||
|
||||
@@ -141,6 +143,15 @@ Memgraph Community is available under the [BSL
|
||||
license](./licenses/BSL.txt).</br> Memgraph Enterprise is available under the
|
||||
[MEL license](./licenses/MEL.txt).
|
||||
|
||||
## 🙋 Community
|
||||
|
||||
- :purple_heart: [**Discord**](https://discord.gg/memgraph)
|
||||
- :busts_in_silhouette: [**Discourse forum**](https://discourse.memgraph.com/)
|
||||
- :open_file_folder: [**Memgraph GitHub**](https://github.com/memgraph)
|
||||
- :bird: [**Twitter**](https://twitter.com/memgraphdb)
|
||||
- :movie_camera:
|
||||
[**YouTube**](https://www.youtube.com/channel/UCZ3HOJvHGxtQ_JHxOselBYg)
|
||||
|
||||
<p align="center">
|
||||
<a href="#">
|
||||
<img src="https://img.shields.io/badge/⬆️back_to_top_⬆️-white" alt="Back to top" title="Back to top"/>
|
||||
|
||||
@@ -5,12 +5,10 @@ import os
|
||||
import subprocess
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
CONFIG_FILE = os.path.join(SCRIPT_DIR, "flags.yaml")
|
||||
WIDTH = 80
|
||||
@@ -18,10 +16,7 @@ WIDTH = 80
|
||||
|
||||
def wrap_text(s, initial_indent="# "):
|
||||
return "\n#\n".join(
|
||||
map(
|
||||
lambda x: textwrap.fill(x, WIDTH, initial_indent=initial_indent, subsequent_indent="# "),
|
||||
s.split("\n"),
|
||||
)
|
||||
map(lambda x: textwrap.fill(x, WIDTH, initial_indent=initial_indent, subsequent_indent="# "), s.split("\n"))
|
||||
)
|
||||
|
||||
|
||||
@@ -100,15 +95,8 @@ def generate_config_file(sections, flags):
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("memgraph_binary", help="path to Memgraph binary")
|
||||
parser.add_argument(
|
||||
"output_file",
|
||||
help="path where to store the generated Memgraph " "configuration file",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--config-file",
|
||||
default=CONFIG_FILE,
|
||||
help="path to generator configuration file",
|
||||
)
|
||||
parser.add_argument("output_file", help="path where to store the generated Memgraph " "configuration file")
|
||||
parser.add_argument("--config-file", default=CONFIG_FILE, help="path to generator configuration file")
|
||||
|
||||
args = parser.parse_args()
|
||||
flags = extract_flags(args.memgraph_binary)
|
||||
|
||||
204
docs/csv-import-tool/README.md
Normal file
204
docs/csv-import-tool/README.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# CSV Import Tool Documentation
|
||||
|
||||
CSV is a universal and very versatile data format used to store large quantities
|
||||
of data. Each Memgraph database instance has a CSV import tool installed called
|
||||
`mg_import_csv`. The CSV import tool should be used for initial bulk ingestion
|
||||
of data into the database. Upon ingestion, the CSV importer creates a snapshot
|
||||
that will be used by the database to recover its state on its next startup.
|
||||
|
||||
If you are already familiar with the Neo4j bulk import tool, then using the
|
||||
`mg_import_csv` tool should be easy. The CSV import tool is fully compatible
|
||||
with the [Neo4j CSV
|
||||
format](https://neo4j.com/docs/operations-manual/current/tools/import/). If you
|
||||
already have a pipeline set-up for Neo4j, you should only replace `neo4j-admin
|
||||
import` with `mg_import_csv`.
|
||||
|
||||
## CSV File Format
|
||||
|
||||
Each row of a CSV file represents a single entry that should be imported into
|
||||
the database. Both nodes and relationships can be imported into the database
|
||||
using CSV files.
|
||||
|
||||
Each set of CSV files must have a header that describes the data that is stored
|
||||
in the CSV files. Each field in the CSV header is in the format
|
||||
`<name>[:<type>]` which identifies the name that should be used for that column
|
||||
and the type that should be used for that column. The type is optional and
|
||||
defaults to `string` (see the following chapter).
|
||||
|
||||
Each CSV field must be divided using the delimiter and each CSV field can either
|
||||
be quoted or unquoted. When the field is quoted, the first and last character in
|
||||
the field *must* be the quote character. If the field isn't quoted, and a quote
|
||||
character appears in it, it is treated as a regular character. If a quote
|
||||
character appears inside a quoted string then the quote character must be
|
||||
doubled in order to escape it. Line feeds and carriage returns are ignored in
|
||||
the CSV file, also, the file can't contain a NULL character.
|
||||
|
||||
## Properties
|
||||
|
||||
Both nodes and relationships can have properties added to them. When importing
|
||||
properties, the CSV importer uses the name specified in the header of the
|
||||
corresponding CSV column for the name of the property. A property is designated
|
||||
by specifying one of the following types in the header:
|
||||
- `integer`, `int`, `long`, `byte`, `short`: creates an integer property
|
||||
- `float`, `double`: creates a float property
|
||||
- `boolean`, `bool`: creates a boolean property
|
||||
- `string`, `char`: creates a string property
|
||||
|
||||
When importing a boolean value, the CSV field should contain exactly the text
|
||||
`true` to import a `True` boolean value. All other text values are treated as a
|
||||
boolean value `False`.
|
||||
|
||||
If you want to import an array of values, you can do so by appending `[]` to any
|
||||
of the above types. The values of the array are then determined by splitting
|
||||
the raw CSV value using the array delimiter character.
|
||||
|
||||
Assuming that the array delimiter is `;`, the following example:
|
||||
```plaintext
|
||||
first_name,last_name:string,number:integer,aliases:string[]
|
||||
John,Doe,1,Johnny;Jo;J-man
|
||||
Melissa,Doe,2,Mel
|
||||
```
|
||||
|
||||
Will yield these results:
|
||||
```plaintext
|
||||
CREATE ({first_name: "John", last_name: "Doe", number: 1, aliases: ["Johnny", "Jo", "J-man"]});
|
||||
CREATE ({first_name: "Melissa", last_name: "Doe", number: 2, aliases: ["Mel"]});
|
||||
```
|
||||
### Nodes
|
||||
|
||||
When importing nodes, several more types can be specified in the header of the
|
||||
CSV file (along with all property types):
|
||||
- `ID`: id of the node that should be used as the node ID when importing
|
||||
relationships
|
||||
- `LABEL`: designates that the field contains additional labels for the node
|
||||
- `IGNORE`: designates that the field should be ignored
|
||||
|
||||
The `ID` field type sets the internal ID that will be used for the node when
|
||||
creating relationships. It is optional and nodes that don't have an ID value
|
||||
specified will be imported, but can't be connected to any relationships. If you
|
||||
want to save the ID value as a property in the database, just specify a name for
|
||||
the ID (`user_id:ID`). If you just want to use the ID during the import, leave
|
||||
out the name of the field (`:ID`). The `ID` field also supports creating
|
||||
separate ID spaces. The ID space is specified with the ID space name appended
|
||||
to the `ID` type in parentheses (`ID(user)`). That allows you to have the same
|
||||
IDs (by value) for multiple different node files (for example, numbers from 1 to
|
||||
N). The IDs in each ID space will be treated as an independent set of IDs that
|
||||
don't interfere with IDs in another ID space.
|
||||
|
||||
The `LABEL` field type adds additional labels to the node. The value is treated
|
||||
as an array type so that multiple additional labels can be specified for each
|
||||
node. The value is split using the array delimiter (`--array-delimiter` flag).
|
||||
|
||||
### Relationships
|
||||
|
||||
In order to be able to import relationships, you must import the nodes in the
|
||||
same invocation of `mg_import_csv` that is used to import the relationships.
|
||||
|
||||
When importing relationships, several more types can be specified in the header
|
||||
of the CSV file (along with all property types):
|
||||
- `START_ID`: id of the start node that should be connected with the
|
||||
relationship
|
||||
- `END_ID`: id of the end node that should be connected with the relationship
|
||||
- `TYPE`: designates the type of the relationship
|
||||
- `IGNORE`: designates that the field should be ignored
|
||||
|
||||
The `START_ID` field type sets the start node that should be connected with the
|
||||
relationship to the end node. The field *must* be specified and the node ID
|
||||
must be one of the node IDs that were specified in the node CSV files. The name
|
||||
of this field is ignored. If the node ID is in an ID space, you can specify the
|
||||
ID space for the in the same way as for the node ID (`START_ID(user)`).
|
||||
|
||||
The `END_ID` field type sets the end node that should be connected with the
|
||||
relationship to the start node. The field *must* be specified and the node ID
|
||||
must be one of the node IDs that were specified in the node CSV files. The name
|
||||
of this field is ignored. If the node ID is in an ID space, you can specify the
|
||||
ID space for the in the same way as for the node ID (`END_ID(user)`).
|
||||
|
||||
The `TYPE` field type sets the type of the relationship. Each relationship
|
||||
*must* have a relationship type, but it doesn't necessarily need to be specified
|
||||
in the CSV file, it can also be set externally for the whole CSV file. The name
|
||||
of this field is ignored.
|
||||
|
||||
## CSV Importer Flags
|
||||
|
||||
The importer has many command line options that allow you to customize the way
|
||||
the importer loads your data.
|
||||
|
||||
The two main flags that are used to specify the input CSV files are `--nodes`
|
||||
and `--relationships`. Basic description of these flags is provided in the table
|
||||
and more detailed explainion can be found further down bellow.
|
||||
|
||||
|
||||
| Flag | Description |
|
||||
|-----------------------| -------------- |
|
||||
|`--nodes` | Used to specify CSV files that contain the nodes to the importer. |
|
||||
|`--relationships` | Used to specify CSV files that contain the relationships to the importer.|
|
||||
|`--delimiter` | Sets the delimiter that should be used when splitting the CSV fields (default `,`)|
|
||||
|`--quote` | Sets the quote character that should be used to quote a CSV field (default `"`)|
|
||||
|`--array-delimiter` | Sets the delimiter that should be used when splitting array values (default `;`)|
|
||||
|`--id-type` | Specifies which data type should be used to store the supplied <br /> node IDs when storing them as properties (if the field name is supplied). <br /> The supported values are either `STRING` or `INTEGER`. (default `STRING`)|
|
||||
|`--ignore-empty-strings` | Instructs the importer to treat all empty strings as `Null` values <br /> instead of an empty string value (default `false`)|
|
||||
|`--ignore-extra-columns` | Instructs the importer to ignore all columns (instead of raising an error) <br /> that aren't specified after the last specified column in the CSV header. (default `false`) |
|
||||
| `--skip-bad-relationships`| Instructs the importer to ignore all relationships (instead of raising an error) <br /> that refer to nodes that don't exist in the node files. (default `false`) |
|
||||
|`--skip-duplicate-nodes` | Instructs the importer to ignore all duplicate nodes (instead of raising an error). <br /> Duplicate nodes are nodes that have an ID that is the same as another node that was already imported. (default `false`) |
|
||||
| `--trim-strings`| Instructs the importer to trim all of the loaded CSV field values before processing them further. <br /> Trimming the fields removes all leading and trailing whitespace from them. (default `false`) |
|
||||
|
||||
The `--nodes` and `--relationships` flags are used to specify CSV files that
|
||||
contain the nodes and relationships to the importer. Multiple files can be
|
||||
specified in each supplied `--nodes` or `--relationships` flag. Files that are
|
||||
supplied in one `--nodes` or `--relationships` flag are treated by the CSV
|
||||
parser as one big CSV file. Only the first line of the first file is parsed for
|
||||
the CSV header, all other files (and rows) are treated as data. This is useful
|
||||
when you have a very large CSV file and don't want to edit its first line just
|
||||
to add a CSV header. Instead, you can specify the header in a separate file
|
||||
(e.g. `users_header.csv` or `friendships_header.csv`) and have the data intact
|
||||
in the large file (e.g. `users.csv` or `friendships.csv`). Also, you can supply
|
||||
additional labels for each set of node files.
|
||||
|
||||
The format of `--nodes` flag is:
|
||||
`[<label>[:<label>]...=]<file>[,<file>][,<file>]...`. Take note that only the
|
||||
first `<file>` part is mandatory, all other parts of the flag value are
|
||||
optional. Multiple `--nodes` flags can be supplied to describe multiple sets of
|
||||
different node files. For the importer to work, at least one `--nodes` flag
|
||||
*must* be supplied.
|
||||
|
||||
The format of `--relationships` flag is: `[<type>=]<file>[,<file>][,<file>]...`.
|
||||
Take note that only the first `<file>` part is mandatory, all other parts of the
|
||||
flag value are optional. Multiple `--relationships` flags can be supplied to
|
||||
describe multiple sets of different relationship files. The `--relationships`
|
||||
flag isn't mandatory.
|
||||
|
||||
## CSV Parser Logic
|
||||
|
||||
The CSV parser uses the same logic as the standard Python CSV parser. The data
|
||||
is parsed in the same way as the following snippet:
|
||||
|
||||
```python
|
||||
import csv
|
||||
for row in csv.reader(stream, strict=True):
|
||||
# process 'row'
|
||||
```
|
||||
|
||||
Python uses 'excel' as the default dialect when parsing CSV files and the
|
||||
default settings for the CSV parser are:
|
||||
- delimiter: `','`
|
||||
- doublequote: `True`
|
||||
- escapechar: `None`
|
||||
- lineterminator: `'\r\n'`
|
||||
- quotechar: `'"'`
|
||||
- skipinitialspace: `False`
|
||||
|
||||
The above snippet can be expanded to:
|
||||
|
||||
```python
|
||||
import csv
|
||||
for row in csv.reader(stream, delimiter=',', doublequote=True,
|
||||
escapechar=None, lineterminator='\r\n',
|
||||
quotechar='"', skipinitialspace=False,
|
||||
strict=True):
|
||||
# process 'row'
|
||||
```
|
||||
|
||||
For more information about the meaning of the above values, see:
|
||||
https://docs.python.org/3/library/csv.html#csv.Dialect
|
||||
|
||||
@@ -5,6 +5,9 @@ set -Eeuo pipefail
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
check_operating_system "centos-7"
|
||||
check_architecture "x86_64"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc gcc-c++ make # generic build tools
|
||||
wget # used for archive download
|
||||
|
||||
@@ -5,6 +5,9 @@ set -Eeuo pipefail
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
check_operating_system "centos-9"
|
||||
check_architecture "x86_64"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils-common gcc gcc-c++ make # generic build tools
|
||||
wget # used for archive download
|
||||
|
||||
@@ -5,6 +5,9 @@ set -Eeuo pipefail
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
check_operating_system "debian-10"
|
||||
check_architecture "x86_64"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc g++ build-essential make # generic build tools
|
||||
wget # used for archive download
|
||||
|
||||
@@ -5,6 +5,9 @@ set -Eeuo pipefail
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
check_operating_system "debian-11"
|
||||
check_architecture "arm64" "aarch64"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc g++ build-essential make # generic build tools
|
||||
wget # used for archive download
|
||||
|
||||
@@ -5,6 +5,9 @@ set -Eeuo pipefail
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
check_operating_system "debian-11"
|
||||
check_architecture "x86_64"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc g++ build-essential make # generic build tools
|
||||
wget # used for archive download
|
||||
|
||||
98
environment/os/fedora-36.sh
Executable file
98
environment/os/fedora-36.sh
Executable file
@@ -0,0 +1,98 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
check_operating_system "fedora-36"
|
||||
check_architecture "x86_64"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils-common gcc gcc-c++ make # generic build tools
|
||||
wget # used for archive download
|
||||
gnupg2 # used for archive signature verification
|
||||
tar gzip bzip2 xz unzip # used for archive unpacking
|
||||
zlib-devel # zlib library used for all builds
|
||||
expat-devel xz-devel python3-devel texinfo libbabeltrace-devel # for gdb
|
||||
curl libcurl-devel # for cmake
|
||||
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
|
||||
diffutils
|
||||
libipt libipt-devel # intel
|
||||
patch
|
||||
)
|
||||
|
||||
TOOLCHAIN_RUN_DEPS=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz # used for archive unpacking
|
||||
zlib # zlib library used for all builds
|
||||
expat xz-libs python3 # for gdb
|
||||
readline # for cmake and llvm
|
||||
libffi libxml2 # for llvm
|
||||
openssl-devel
|
||||
perl # for openssl
|
||||
)
|
||||
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
git # source code control
|
||||
make pkgconf-pkg-config # build system
|
||||
wget # for downloading libs
|
||||
libuuid-devel java-11-openjdk # required by antlr
|
||||
readline-devel # for memgraph console
|
||||
python3-devel # for query modules
|
||||
openssl-devel
|
||||
libseccomp-devel
|
||||
python3 python3-pip python3-virtualenv python3-virtualenvwrapper python3-pyyaml nmap-ncat # for tests
|
||||
libcurl-devel # mg-requests
|
||||
rpm-build rpmlint # for RPM package building
|
||||
doxygen graphviz # source documentation generators
|
||||
which 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() {
|
||||
echo "$1"
|
||||
}
|
||||
|
||||
check() {
|
||||
local missing=""
|
||||
for pkg in $1; do
|
||||
if ! dnf list installed "$pkg" >/dev/null 2>/dev/null; then
|
||||
missing="$pkg $missing"
|
||||
fi
|
||||
done
|
||||
if [ "$missing" != "" ]; then
|
||||
echo "MISSING PACKAGES: $missing"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
install() {
|
||||
cd "$DIR"
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run as root."
|
||||
exit 1
|
||||
fi
|
||||
# If GitHub Actions runner is installed, append LANG to the environment.
|
||||
# Python related tests don't work without 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
|
||||
dnf update -y
|
||||
for pkg in $1; do
|
||||
dnf install -y "$pkg"
|
||||
done
|
||||
}
|
||||
|
||||
deps=$2"[*]"
|
||||
"$1" "${!deps}"
|
||||
@@ -5,6 +5,8 @@ set -Eeuo pipefail
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
check_operating_system "todo-os-name"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
pkg
|
||||
)
|
||||
|
||||
@@ -5,6 +5,9 @@ set -Eeuo pipefail
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
check_operating_system "ubuntu-18.04"
|
||||
check_architecture "x86_64"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc g++ build-essential make # generic build tools
|
||||
wget # archive download
|
||||
|
||||
@@ -5,6 +5,9 @@ set -Eeuo pipefail
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
check_operating_system "ubuntu-20.04"
|
||||
check_architecture "x86_64"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc g++ build-essential make # generic build tools
|
||||
wget # used for archive download
|
||||
|
||||
@@ -5,6 +5,9 @@ set -Eeuo pipefail
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
check_operating_system "ubuntu-22.04"
|
||||
check_architecture "x86_64"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc g++ build-essential make # generic build tools
|
||||
wget # used for archive download
|
||||
|
||||
1
environment/toolchain/.gitignore
vendored
Normal file
1
environment/toolchain/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.tar.gz
|
||||
@@ -10,6 +10,14 @@ cd "$DIR"
|
||||
source "$DIR/../util.sh"
|
||||
DISTRO="$(operating_system)"
|
||||
|
||||
function log_tool_name () {
|
||||
echo ""
|
||||
echo ""
|
||||
echo "#### $1 ####"
|
||||
echo ""
|
||||
echo ""
|
||||
}
|
||||
|
||||
for_arm=false
|
||||
if [[ "$#" -eq 1 ]]; then
|
||||
if [[ "$1" == "--for-arm" ]]; then
|
||||
@@ -20,9 +28,11 @@ if [[ "$#" -eq 1 ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
os="$1"
|
||||
|
||||
# toolchain version
|
||||
TOOLCHAIN_STDCXX="${TOOLCHAIN_STDCXX:-libstdc++}"
|
||||
if [[ "$TOOLCHAIN_STDCXX" != "libstdc++" && "$TOOLCHAIN_STDCXX" != "libc++" ]]; then
|
||||
echo "Only GCC (libstdc++) or LLVM (libc++) C++ standard library implementations are supported."
|
||||
exit 1
|
||||
fi
|
||||
TOOLCHAIN_VERSION=4
|
||||
|
||||
# package versions used
|
||||
@@ -99,6 +109,8 @@ if [ ! -f llvm-$LLVM_VERSION.src.tar.xz ]; then
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/clang-tools-extra-$LLVM_VERSION.src.tar.xz
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/compiler-rt-$LLVM_VERSION.src.tar.xz
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/libunwind-$LLVM_VERSION.src.tar.xz
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/libcxx-$LLVM_VERSION.src.tar.xz
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/libcxxabi-$LLVM_VERSION.src.tar.xz
|
||||
fi
|
||||
if [ ! -f pahole-gdb-master.zip ]; then
|
||||
wget https://github.com/PhilArmstrong/pahole-gdb/archive/master.zip -O pahole-gdb-master.zip
|
||||
@@ -156,6 +168,8 @@ if [ ! -f llvm-$LLVM_VERSION.src.tar.xz.sig ]; then
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/clang-tools-extra-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/compiler-rt-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/libunwind-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/libcxx-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/libcxxabi-$LLVM_VERSION.src.tar.xz.sig
|
||||
fi
|
||||
# list of valid llvm gnupg keys: https://releases.llvm.org/download.html
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0x474E22316ABF4785A88C6E8EA2C794A986419D8A
|
||||
@@ -165,6 +179,8 @@ $GPG --verify lld-$LLVM_VERSION.src.tar.xz.sig lld-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify clang-tools-extra-$LLVM_VERSION.src.tar.xz.sig clang-tools-extra-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify compiler-rt-$LLVM_VERSION.src.tar.xz.sig compiler-rt-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify libunwind-$LLVM_VERSION.src.tar.xz.sig libunwind-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify libcxx-$LLVM_VERSION.src.tar.xz.sig libcxx-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify libcxxabi-$LLVM_VERSION.src.tar.xz.sig libcxxabi-$LLVM_VERSION.src.tar.xz
|
||||
|
||||
popd
|
||||
|
||||
@@ -172,7 +188,7 @@ popd
|
||||
mkdir -p build
|
||||
pushd build
|
||||
|
||||
# compile gcc
|
||||
log_tool_name "GCC $GCC_VERSION"
|
||||
if [ ! -f $PREFIX/bin/gcc ]; then
|
||||
if [ -d gcc-$GCC_VERSION ]; then
|
||||
rm -rf gcc-$GCC_VERSION
|
||||
@@ -263,7 +279,7 @@ fi
|
||||
export PATH=$PREFIX/bin:$PATH
|
||||
export LD_LIBRARY_PATH=$PREFIX/lib64
|
||||
|
||||
# compile binutils
|
||||
log_tool_name "binutils $BINUTILS_VERSION"
|
||||
if [ ! -f $PREFIX/bin/ld.gold ]; then
|
||||
if [ -d binutils-$BINUTILS_VERSION ]; then
|
||||
rm -rf binutils-$BINUTILS_VERSION
|
||||
@@ -327,7 +343,7 @@ if [ ! -f $PREFIX/bin/ld.gold ]; then
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# compile gdb
|
||||
log_tool_name "GDB $GDB_VERSION"
|
||||
if [ ! -f $PREFIX/bin/gdb ]; then
|
||||
if [ -d gdb-$GDB_VERSION ]; then
|
||||
rm -rf gdb-$GDB_VERSION
|
||||
@@ -398,13 +414,13 @@ if [ ! -f $PREFIX/bin/gdb ]; then
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# install pahole
|
||||
log_tool_name "install pahole"
|
||||
if [ ! -d $PREFIX/share/pahole-gdb ]; then
|
||||
unzip ../archives/pahole-gdb-master.zip
|
||||
mv pahole-gdb-master $PREFIX/share/pahole-gdb
|
||||
fi
|
||||
|
||||
# setup system gdbinit
|
||||
log_tool_name "setup system gdbinit"
|
||||
if [ ! -f $PREFIX/etc/gdb/gdbinit ]; then
|
||||
mkdir -p $PREFIX/etc/gdb
|
||||
cat >$PREFIX/etc/gdb/gdbinit <<EOF
|
||||
@@ -430,7 +446,7 @@ end
|
||||
EOF
|
||||
fi
|
||||
|
||||
# compile cmake
|
||||
log_tool_name "cmake $CMAKE_VERSION"
|
||||
if [ ! -f $PREFIX/bin/cmake ]; then
|
||||
if [ -d cmake-$CMAKE_VERSION ]; then
|
||||
rm -rf cmake-$CMAKE_VERSION
|
||||
@@ -456,7 +472,7 @@ if [ ! -f $PREFIX/bin/cmake ]; then
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# compile cppcheck
|
||||
log_tool_name "cppcheck $CPPCHECK_VERSION"
|
||||
if [ ! -f $PREFIX/bin/cppcheck ]; then
|
||||
if [ -d cppcheck-$CPPCHECK_VERSION ]; then
|
||||
rm -rf cppcheck-$CPPCHECK_VERSION
|
||||
@@ -480,7 +496,7 @@ if [ ! -f $PREFIX/bin/cppcheck ]; then
|
||||
popd
|
||||
fi
|
||||
|
||||
# compile swig
|
||||
log_tool_name "swig $SWIG_VERSION"
|
||||
if [ ! -d swig-$SWIG_VERSION/install ]; then
|
||||
if [ -d swig-$SWIG_VERSION ]; then
|
||||
rm -rf swig-$SWIG_VERSION
|
||||
@@ -496,7 +512,7 @@ if [ ! -d swig-$SWIG_VERSION/install ]; then
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# compile llvm
|
||||
log_tool_name "LLVM $LLVM_VERSION"
|
||||
if [ ! -f $PREFIX/bin/clang ]; then
|
||||
if [ -d llvm-$LLVM_VERSION ]; then
|
||||
rm -rf llvm-$LLVM_VERSION
|
||||
@@ -513,8 +529,19 @@ if [ ! -f $PREFIX/bin/clang ]; then
|
||||
mv compiler-rt-$LLVM_VERSION.src/ llvm-$LLVM_VERSION/projects/compiler-rt
|
||||
tar -xvf ../archives/libunwind-$LLVM_VERSION.src.tar.xz
|
||||
mv libunwind-$LLVM_VERSION.src/include/mach-o llvm-$LLVM_VERSION/tools/lld/include
|
||||
|
||||
# The following is required because of libc++
|
||||
tar -xvf ../archives/libcxx-$LLVM_VERSION.src.tar.xz
|
||||
mv libcxx-$LLVM_VERSION.src llvm-$LLVM_VERSION/projects/libcxx
|
||||
tar -xvf ../archives/libcxxabi-$LLVM_VERSION.src.tar.xz
|
||||
mv libcxxabi-$LLVM_VERSION.src llvm-$LLVM_VERSION/projects/libcxxabi
|
||||
# NOTE: We moved part of the libunwind in one of the previous step.
|
||||
rm -r libunwind-$LLVM_VERSION.src
|
||||
tar -xvf ../archives/libunwind-$LLVM_VERSION.src.tar.xz
|
||||
mv libunwind-$LLVM_VERSION.src llvm-$LLVM_VERSION/projects/libunwind
|
||||
|
||||
pushd llvm-$LLVM_VERSION
|
||||
mkdir build && pushd build
|
||||
mkdir -p build && pushd build
|
||||
# activate swig
|
||||
export PATH=$DIR/build/swig-$SWIG_VERSION/install/bin:$PATH
|
||||
# influenced by: https://buildd.debian.org/status/fetch.php?pkg=llvm-toolchain-7&arch=amd64&ver=1%3A7.0.1%7E%2Brc2-1%7Eexp1&stamp=1541506173&raw=0
|
||||
@@ -820,7 +847,11 @@ source $PREFIX/activate
|
||||
export CC=$PREFIX/bin/clang
|
||||
export CXX=$PREFIX/bin/clang++
|
||||
export CFLAGS="$CFLAGS -fPIC"
|
||||
export CXXFLAGS="$CXXFLAGS -fPIC"
|
||||
if [ "$TOOLCHAIN_STDCXX" = "libstdc++" ]; then
|
||||
export CXXFLAGS="$CXXFLAGS -fPIC"
|
||||
else
|
||||
export CXXFLAGS="$CXXFLAGS -fPIC -stdlib=libc++"
|
||||
fi
|
||||
COMMON_CMAKE_FLAGS="-DCMAKE_INSTALL_PREFIX=$PREFIX
|
||||
-DCMAKE_PREFIX_PATH=$PREFIX
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
@@ -834,7 +865,7 @@ COMMON_CMAKE_FLAGS="-DCMAKE_INSTALL_PREFIX=$PREFIX
|
||||
COMMON_CONFIGURE_FLAGS="--enable-shared=no --prefix=$PREFIX"
|
||||
COMMON_MAKE_INSTALL_FLAGS="-j$CPUS BUILD_SHARED=no PREFIX=$PREFIX install"
|
||||
|
||||
# install bzip2
|
||||
log_tool_name "bzip2 $BZIP2_VERSION"
|
||||
if [ ! -f $PREFIX/include/bzlib.h ]; then
|
||||
if [ -d bzip2-$BZIP2_VERSION ]; then
|
||||
rm -rf bzip2-$BZIP2_VERSION
|
||||
@@ -845,7 +876,7 @@ if [ ! -f $PREFIX/include/bzlib.h ]; then
|
||||
popd
|
||||
fi
|
||||
|
||||
# install fmt
|
||||
log_tool_name "fmt $FMT_VERSION"
|
||||
if [ ! -d $PREFIX/include/fmt ]; then
|
||||
if [ -d fmt-$FMT_VERSION ]; then
|
||||
rm -rf fmt-$FMT_VERSION
|
||||
@@ -858,7 +889,7 @@ if [ ! -d $PREFIX/include/fmt ]; then
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# install lz4
|
||||
log_tool_name "lz4 $LZ4_VERSION"
|
||||
if [ ! -f $PREFIX/include/lz4.h ]; then
|
||||
if [ -d lz4-$LZ4_VERSION ]; then
|
||||
rm -rf lz4-$LZ4_VERSION
|
||||
@@ -869,7 +900,7 @@ if [ ! -f $PREFIX/include/lz4.h ]; then
|
||||
popd
|
||||
fi
|
||||
|
||||
# install xz
|
||||
log_tool_name "xz $XZ_VERSION"
|
||||
if [ ! -f $PREFIX/include/lzma.h ]; then
|
||||
if [ -d xz-$XZ_VERSION ]; then
|
||||
rm -rf xz-$XZ_VERSION
|
||||
@@ -881,7 +912,7 @@ if [ ! -f $PREFIX/include/lzma.h ]; then
|
||||
popd
|
||||
fi
|
||||
|
||||
# install zlib
|
||||
log_tool_name "zlib $ZLIB_VERSION"
|
||||
if [ ! -f $PREFIX/include/zlib.h ]; then
|
||||
if [ -d zlib-$ZLIB_VERSION ]; then
|
||||
rm -rf zlib-$ZLIB_VERSION
|
||||
@@ -895,7 +926,7 @@ if [ ! -f $PREFIX/include/zlib.h ]; then
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# install zstd
|
||||
log_tool_name "zstd $ZSTD_VERSION"
|
||||
if [ ! -f $PREFIX/include/zstd.h ]; then
|
||||
if [ -d zstd-$ZSTD_VERSION ]; then
|
||||
rm -rf zstd-$ZSTD_VERSION
|
||||
@@ -910,7 +941,8 @@ if [ ! -f $PREFIX/include/zstd.h ]; then
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
#install jemalloc
|
||||
# TODO(gitbuda): Freeze jmalloc version.
|
||||
log_tool_name "jmalloc"
|
||||
if [ ! -d $PREFIX/include/jemalloc ]; then
|
||||
if [ -d jemalloc ]; then
|
||||
rm -rf jemalloc
|
||||
@@ -927,7 +959,7 @@ if [ ! -d $PREFIX/include/jemalloc ]; then
|
||||
popd
|
||||
fi
|
||||
|
||||
# install boost
|
||||
log_tool_name "BOOST $BOOST_VERSION"
|
||||
if [ ! -d $PREFIX/include/boost ]; then
|
||||
if [ -d boost_$BOOST_VERSION_UNDERSCORES ]; then
|
||||
rm -rf boost_$BOOST_VERSION_UNDERSCORES
|
||||
@@ -935,15 +967,24 @@ if [ ! -d $PREFIX/include/boost ]; then
|
||||
tar -xzf ../archives/boost_$BOOST_VERSION_UNDERSCORES.tar.gz
|
||||
pushd boost_$BOOST_VERSION_UNDERSCORES
|
||||
./bootstrap.sh --prefix=$PREFIX --with-toolset=clang --with-python=python3 --without-icu
|
||||
./b2 toolset=clang -j$CPUS install variant=release link=static cxxstd=20 --disable-icu \
|
||||
-sZLIB_SOURCE="$PREFIX" -sZLIB_INCLUDE="$PREFIX/include" -sZLIB_LIBPATH="$PREFIX/lib" \
|
||||
-sBZIP2_SOURCE="$PREFIX" -sBZIP2_INCLUDE="$PREFIX/include" -sBZIP2_LIBPATH="$PREFIX/lib" \
|
||||
-sLZMA_SOURCE="$PREFIX" -sLZMA_INCLUDE="$PREFIX/include" -sLZMA_LIBPATH="$PREFIX/lib" \
|
||||
-sZSTD_SOURCE="$PREFIX" -sZSTD_INCLUDE="$PREFIX/include" -sZSTD_LIBPATH="$PREFIX/lib"
|
||||
if [ "$TOOLCHAIN_STDCXX" = "libstdc++" ]; then
|
||||
./b2 toolset=clang -j$CPUS install variant=release link=static cxxstd=20 --disable-icu \
|
||||
-sZLIB_SOURCE="$PREFIX" -sZLIB_INCLUDE="$PREFIX/include" -sZLIB_LIBPATH="$PREFIX/lib" \
|
||||
-sBZIP2_SOURCE="$PREFIX" -sBZIP2_INCLUDE="$PREFIX/include" -sBZIP2_LIBPATH="$PREFIX/lib" \
|
||||
-sLZMA_SOURCE="$PREFIX" -sLZMA_INCLUDE="$PREFIX/include" -sLZMA_LIBPATH="$PREFIX/lib" \
|
||||
-sZSTD_SOURCE="$PREFIX" -sZSTD_INCLUDE="$PREFIX/include" -sZSTD_LIBPATH="$PREFIX/lib"
|
||||
else
|
||||
./b2 toolset=clang -j$CPUS install variant=release link=static cxxstd=20 --disable-icu \
|
||||
cxxflags="-stdlib=libc++" linkflags="-stdlib=libc++" \
|
||||
-sZLIB_SOURCE="$PREFIX" -sZLIB_INCLUDE="$PREFIX/include" -sZLIB_LIBPATH="$PREFIX/lib" \
|
||||
-sBZIP2_SOURCE="$PREFIX" -sBZIP2_INCLUDE="$PREFIX/include" -sBZIP2_LIBPATH="$PREFIX/lib" \
|
||||
-sLZMA_SOURCE="$PREFIX" -sLZMA_INCLUDE="$PREFIX/include" -sLZMA_LIBPATH="$PREFIX/lib" \
|
||||
-sZSTD_SOURCE="$PREFIX" -sZSTD_INCLUDE="$PREFIX/include" -sZSTD_LIBPATH="$PREFIX/lib"
|
||||
fi
|
||||
popd
|
||||
fi
|
||||
|
||||
# install double-conversion
|
||||
log_tool_name "double-conversion $DOUBLE_CONVERSION_VERSION"
|
||||
if [ ! -d $PREFIX/include/double-conversion ]; then
|
||||
if [ -d double-conversion-$DOUBLE_CONVERSION_VERSION ]; then
|
||||
rm -rf double-conversion-$DOUBLE_CONVERSION_VERSION
|
||||
@@ -958,7 +999,8 @@ if [ ! -d $PREFIX/include/double-conversion ]; then
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# install gflags
|
||||
# TODO(gitbuda): Freeze gflags version.
|
||||
log_tool_name "gflags"
|
||||
if [ ! -d $PREFIX/include/gflags ]; then
|
||||
if [ -d gflags ]; then
|
||||
rm -rf gflags
|
||||
@@ -977,7 +1019,7 @@ if [ ! -d $PREFIX/include/gflags ]; then
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# install libunwind
|
||||
log_tool_name "libunwind $LIBUNWIND_VERSION"
|
||||
if [ ! -f $PREFIX/include/libunwind.h ]; then
|
||||
if [ -d libunwind-$LIBUNWIND_VERSION ]; then
|
||||
rm -rf libunwind-$LIBUNWIND_VERSION
|
||||
@@ -990,7 +1032,7 @@ if [ ! -f $PREFIX/include/libunwind.h ]; then
|
||||
popd
|
||||
fi
|
||||
|
||||
# install glog
|
||||
log_tool_name "glog $GLOG_VERSION"
|
||||
if [ ! -d $PREFIX/include/glog ]; then
|
||||
if [ -d glog-$GLOG_VERSION ]; then
|
||||
rm -rf glog-$GLOG_VERSION
|
||||
@@ -1004,7 +1046,7 @@ if [ ! -d $PREFIX/include/glog ]; then
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# install libevent
|
||||
log_tool_name "libevent $LIBEVENT_VERSION"
|
||||
if [ ! -d $PREFIX/include/event2 ]; then
|
||||
if [ -d libevent-$LIBEVENT_VERSION ]; then
|
||||
rm -rf libevent-$LIBEVENT_VERSION
|
||||
@@ -1023,7 +1065,7 @@ if [ ! -d $PREFIX/include/event2 ]; then
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# install snappy
|
||||
log_tool_name "snappy $SNAPPY_VERSION"
|
||||
if [ ! -f $PREFIX/include/snappy.h ]; then
|
||||
if [ -d snappy-$SNAPPY_VERSION ]; then
|
||||
rm -rf snappy-$SNAPPY_VERSION
|
||||
@@ -1041,7 +1083,7 @@ if [ ! -f $PREFIX/include/snappy.h ]; then
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# install libsodium
|
||||
log_tool_name "libsodium $LIBSODIUM_VERSION"
|
||||
if [ ! -f $PREFIX/include/sodium.h ]; then
|
||||
if [ -d libsodium-$LIBSODIUM_VERSION ]; then
|
||||
rm -rf libsodium-$LIBSODIUM_VERSION
|
||||
@@ -1053,7 +1095,7 @@ if [ ! -f $PREFIX/include/sodium.h ]; then
|
||||
popd
|
||||
fi
|
||||
|
||||
# install libaio
|
||||
log_tool_name "libaio $LIBAIO_VERSION"
|
||||
if [ ! -f $PREFIX/include/libaio.h ]; then
|
||||
if [ -d libaio-$LIBAIO_VERSION ]; then
|
||||
rm -rf libaio-$LIBAIO_VERSION
|
||||
@@ -1064,7 +1106,7 @@ if [ ! -f $PREFIX/include/libaio.h ]; then
|
||||
popd
|
||||
fi
|
||||
|
||||
# install folly
|
||||
log_tool_name "folly $FBLIBS_VERSION"
|
||||
if [ ! -d $PREFIX/include/folly ]; then
|
||||
if [ -d folly-$FBLIBS_VERSION ]; then
|
||||
rm -rf folly-$FBLIBS_VERSION
|
||||
@@ -1085,7 +1127,7 @@ if [ ! -d $PREFIX/include/folly ]; then
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# install fizz
|
||||
log_tool_name "fizz $FBLIBS_VERSION"
|
||||
if [ ! -d $PREFIX/include/fizz ]; then
|
||||
if [ -d fizz-$FBLIBS_VERSION ]; then
|
||||
rm -rf fizz-$FBLIBS_VERSION
|
||||
@@ -1104,7 +1146,7 @@ if [ ! -d $PREFIX/include/fizz ]; then
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# install wangle
|
||||
log_tool_name "wangle FBLIBS_VERSION"
|
||||
if [ ! -d $PREFIX/include/wangle ]; then
|
||||
if [ -d wangle-$FBLIBS_VERSION ]; then
|
||||
rm -rf wangle-$FBLIBS_VERSION
|
||||
@@ -1123,7 +1165,7 @@ if [ ! -d $PREFIX/include/wangle ]; then
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# install proxygen
|
||||
log_tool_name "proxygen $FBLIBS_VERSION"
|
||||
if [ ! -d $PREFIX/include/proxygen ]; then
|
||||
if [ -d proxygen-$FBLIBS_VERSION ]; then
|
||||
rm -rf proxygen-$FBLIBS_VERSION
|
||||
@@ -1144,7 +1186,7 @@ if [ ! -d $PREFIX/include/proxygen ]; then
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# install flex
|
||||
log_tool_name "flex $FBLIBS_VERSION"
|
||||
if [ ! -f $PREFIX/include/FlexLexer.h ]; then
|
||||
if [ -d flex-$FLEX_VERSION ]; then
|
||||
rm -rf flex-$FLEX_VERSION
|
||||
@@ -1156,7 +1198,7 @@ if [ ! -f $PREFIX/include/FlexLexer.h ]; then
|
||||
popd
|
||||
fi
|
||||
|
||||
# install fbthrift
|
||||
log_tool_name "fbthrift $FBLIBS_VERSION"
|
||||
if [ ! -d $PREFIX/include/thrift ]; then
|
||||
if [ -d fbthrift-$FBLIBS_VERSION ]; then
|
||||
rm -rf fbthrift-$FBLIBS_VERSION
|
||||
@@ -1166,10 +1208,15 @@ if [ ! -d $PREFIX/include/thrift ]; then
|
||||
# build is used by facebook builder
|
||||
mkdir _build
|
||||
pushd _build
|
||||
if [ "$TOOLCHAIN_STDCXX" = "libstdc++" ]; then
|
||||
CMAKE_CXX_FLAGS="-fsized-deallocation"
|
||||
else
|
||||
CMAKE_CXX_FLAGS="-fsized-deallocation -stdlib=libc++"
|
||||
fi
|
||||
cmake .. $COMMON_CMAKE_FLAGS \
|
||||
-Denable_tests=OFF \
|
||||
-DGFLAGS_NOTHREADS=OFF \
|
||||
-DCMAKE_CXX_FLAGS=-fsized-deallocation
|
||||
-DCMAKE_CXX_FLAGS="$CMAKE_CXX_FLAGS"
|
||||
make -j$CPUS install
|
||||
popd
|
||||
fi
|
||||
@@ -1192,7 +1239,12 @@ if [ ! -f $NAME-binaries-$DISTRO.tar.gz ]; then
|
||||
DISTRO_FULL_NAME="$DISTRO_FULL_NAME-amd64"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$TOOLCHAIN_STDCXX" = "libstdc++" ]; then
|
||||
# Pass because infra scripts assume there is not C++ standard lib in the name.
|
||||
echo "NOTE: Not adding anything to the archive name that GCC C++ standard lib is used."
|
||||
else
|
||||
DISTRO_FULL_NAME="$DISTRO_FULL_NAME-libc++"
|
||||
fi
|
||||
tar --owner=root --group=root -cpvzf $NAME-binaries-$DISTRO_FULL_NAME.tar.gz -C /opt $NAME
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,15 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
operating_system() {
|
||||
function operating_system() {
|
||||
grep -E '^(VERSION_)?ID=' /etc/os-release | \
|
||||
sort | cut -d '=' -f 2- | sed 's/"//g' | paste -s -d '-'
|
||||
}
|
||||
|
||||
architecture() {
|
||||
function check_operating_system() {
|
||||
if [ "$(operating_system)" != "$1" ]; then
|
||||
echo "Not the right operating system!"
|
||||
exit 1
|
||||
else
|
||||
echo "The right operating system."
|
||||
fi
|
||||
}
|
||||
|
||||
function architecture() {
|
||||
uname -m
|
||||
}
|
||||
|
||||
check_all_yum() {
|
||||
check_architecture() {
|
||||
for arch in "$@"; do
|
||||
if [ "$(architecture)" = "$arch" ]; then
|
||||
echo "The right architecture!"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
echo "Not the right architecture!"
|
||||
exit 1
|
||||
}
|
||||
|
||||
function check_all_yum() {
|
||||
local missing=""
|
||||
for pkg in $1; do
|
||||
if ! yum list installed "$pkg" >/dev/null 2>/dev/null; then
|
||||
@@ -22,7 +42,7 @@ check_all_yum() {
|
||||
fi
|
||||
}
|
||||
|
||||
check_all_dpkg() {
|
||||
function check_all_dpkg() {
|
||||
local missing=""
|
||||
for pkg in $1; do
|
||||
if ! dpkg -s "$pkg" >/dev/null 2>/dev/null; then
|
||||
@@ -35,7 +55,7 @@ check_all_dpkg() {
|
||||
fi
|
||||
}
|
||||
|
||||
check_all_dnf() {
|
||||
function check_all_dnf() {
|
||||
local missing=""
|
||||
for pkg in $1; do
|
||||
if ! dnf list installed "$pkg" >/dev/null 2>/dev/null; then
|
||||
@@ -47,7 +67,8 @@ check_all_dnf() {
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
install_all_apt() {
|
||||
|
||||
function install_all_apt() {
|
||||
for pkg in $1; do
|
||||
apt install -y "$pkg"
|
||||
done
|
||||
|
||||
734
include/_mgp.hpp
Normal file
734
include/_mgp.hpp
Normal file
@@ -0,0 +1,734 @@
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
/// @file _mgp.hpp
|
||||
///
|
||||
/// The file contains methods that connect mg procedures and the outside code
|
||||
/// Methods like mapping a graph into memory or assigning new mg results or
|
||||
/// their properties are implemented.
|
||||
#pragma once
|
||||
|
||||
#include "mg_exceptions.hpp"
|
||||
#include "mg_procedure.h"
|
||||
|
||||
namespace mgp {
|
||||
|
||||
namespace {
|
||||
inline void MgExceptionHandle(mgp_error result_code) {
|
||||
switch (result_code) {
|
||||
case mgp_error::MGP_ERROR_UNKNOWN_ERROR:
|
||||
throw mg_exception::UnknownException();
|
||||
case mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE:
|
||||
throw mg_exception::AllocationException();
|
||||
case mgp_error::MGP_ERROR_INSUFFICIENT_BUFFER:
|
||||
throw mg_exception::InsufficientBufferException();
|
||||
case mgp_error::MGP_ERROR_OUT_OF_RANGE:
|
||||
throw mg_exception::OutOfRangeException();
|
||||
case mgp_error::MGP_ERROR_LOGIC_ERROR:
|
||||
throw mg_exception::LogicException();
|
||||
case mgp_error::MGP_ERROR_DELETED_OBJECT:
|
||||
throw mg_exception::DeletedObjectException();
|
||||
case mgp_error::MGP_ERROR_INVALID_ARGUMENT:
|
||||
throw mg_exception::InvalidArgumentException();
|
||||
case mgp_error::MGP_ERROR_KEY_ALREADY_EXISTS:
|
||||
throw mg_exception::KeyAlreadyExistsException();
|
||||
case mgp_error::MGP_ERROR_IMMUTABLE_OBJECT:
|
||||
throw mg_exception::ImmutableObjectException();
|
||||
case mgp_error::MGP_ERROR_VALUE_CONVERSION:
|
||||
throw mg_exception::ValueConversionException();
|
||||
case mgp_error::MGP_ERROR_SERIALIZATION_ERROR:
|
||||
throw mg_exception::SerializationException();
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename TResult, typename TFunc, typename... TArgs>
|
||||
TResult MgInvoke(TFunc func, TArgs... args) {
|
||||
TResult result{};
|
||||
|
||||
auto result_code = func(args..., &result);
|
||||
MgExceptionHandle(result_code);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename TFunc, typename... TArgs>
|
||||
inline void MgInvokeVoid(TFunc func, TArgs... args) {
|
||||
auto result_code = func(args...);
|
||||
MgExceptionHandle(result_code);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// mgp_value
|
||||
|
||||
// Make value
|
||||
|
||||
inline mgp_value *value_make_null(mgp_memory *memory) { return MgInvoke<mgp_value *>(mgp_value_make_null, memory); }
|
||||
|
||||
inline mgp_value *value_make_bool(int val, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_value *>(mgp_value_make_bool, val, memory);
|
||||
}
|
||||
|
||||
inline mgp_value *value_make_int(int64_t val, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_value *>(mgp_value_make_int, val, memory);
|
||||
}
|
||||
|
||||
inline mgp_value *value_make_double(double val, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_value *>(mgp_value_make_double, val, memory);
|
||||
}
|
||||
|
||||
inline mgp_value *value_make_string(const char *val, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_value *>(mgp_value_make_string, val, memory);
|
||||
}
|
||||
|
||||
inline mgp_value *value_make_list(mgp_list *val) { return MgInvoke<mgp_value *>(mgp_value_make_list, val); }
|
||||
|
||||
inline mgp_value *value_make_map(mgp_map *val) { return MgInvoke<mgp_value *>(mgp_value_make_map, val); }
|
||||
|
||||
inline mgp_value *value_make_vertex(mgp_vertex *val) { return MgInvoke<mgp_value *>(mgp_value_make_vertex, val); }
|
||||
|
||||
inline mgp_value *value_make_edge(mgp_edge *val) { return MgInvoke<mgp_value *>(mgp_value_make_edge, val); }
|
||||
|
||||
inline mgp_value *value_make_path(mgp_path *val) { return MgInvoke<mgp_value *>(mgp_value_make_path, val); }
|
||||
|
||||
inline mgp_value *value_make_date(mgp_date *val) { return MgInvoke<mgp_value *>(mgp_value_make_date, val); }
|
||||
|
||||
inline mgp_value *value_make_local_time(mgp_local_time *val) {
|
||||
return MgInvoke<mgp_value *>(mgp_value_make_local_time, val);
|
||||
}
|
||||
|
||||
inline mgp_value *value_make_local_date_time(mgp_local_date_time *val) {
|
||||
return MgInvoke<mgp_value *>(mgp_value_make_local_date_time, val);
|
||||
}
|
||||
|
||||
inline mgp_value *value_make_duration(mgp_duration *val) { return MgInvoke<mgp_value *>(mgp_value_make_duration, val); }
|
||||
|
||||
// Copy value
|
||||
|
||||
// TODO: implement within MGP API
|
||||
// with primitive types ({bool, int, double, string}), create a new identical value
|
||||
// otherwise call mgp_##TYPE_copy and convert tpye
|
||||
inline mgp_value *value_copy(mgp_value *val, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_value *>(mgp_value_copy, val, memory);
|
||||
}
|
||||
|
||||
// Destroy value
|
||||
|
||||
inline void value_destroy(mgp_value *val) { mgp_value_destroy(val); }
|
||||
|
||||
// Get value of type
|
||||
|
||||
inline mgp_value_type value_get_type(mgp_value *val) { return MgInvoke<mgp_value_type>(mgp_value_get_type, val); }
|
||||
|
||||
inline bool value_get_bool(mgp_value *val) { return MgInvoke<int>(mgp_value_get_bool, val); }
|
||||
|
||||
inline int64_t value_get_int(mgp_value *val) { return MgInvoke<int64_t>(mgp_value_get_int, val); }
|
||||
|
||||
inline double value_get_double(mgp_value *val) { return MgInvoke<double>(mgp_value_get_double, val); }
|
||||
|
||||
inline const char *value_get_string(mgp_value *val) { return MgInvoke<const char *>(mgp_value_get_string, val); }
|
||||
|
||||
inline mgp_list *value_get_list(mgp_value *val) { return MgInvoke<mgp_list *>(mgp_value_get_list, val); }
|
||||
|
||||
inline mgp_map *value_get_map(mgp_value *val) { return MgInvoke<mgp_map *>(mgp_value_get_map, val); }
|
||||
|
||||
inline mgp_vertex *value_get_vertex(mgp_value *val) { return MgInvoke<mgp_vertex *>(mgp_value_get_vertex, val); }
|
||||
|
||||
inline mgp_edge *value_get_edge(mgp_value *val) { return MgInvoke<mgp_edge *>(mgp_value_get_edge, val); }
|
||||
|
||||
inline mgp_path *value_get_path(mgp_value *val) { return MgInvoke<mgp_path *>(mgp_value_get_path, val); }
|
||||
|
||||
inline mgp_date *value_get_date(mgp_value *val) { return MgInvoke<mgp_date *>(mgp_value_get_date, val); }
|
||||
|
||||
inline mgp_local_time *value_get_local_time(mgp_value *val) {
|
||||
return MgInvoke<mgp_local_time *>(mgp_value_get_local_time, val);
|
||||
}
|
||||
|
||||
inline mgp_local_date_time *value_get_local_date_time(mgp_value *val) {
|
||||
return MgInvoke<mgp_local_date_time *>(mgp_value_get_local_date_time, val);
|
||||
}
|
||||
|
||||
inline mgp_duration *value_get_duration(mgp_value *val) {
|
||||
return MgInvoke<mgp_duration *>(mgp_value_get_duration, val);
|
||||
}
|
||||
|
||||
// Check type of value
|
||||
|
||||
inline bool value_is_null(mgp_value *val) { return MgInvoke<int>(mgp_value_is_null, val); }
|
||||
|
||||
inline bool value_is_bool(mgp_value *val) { return MgInvoke<int>(mgp_value_is_bool, val); }
|
||||
|
||||
inline bool value_is_int(mgp_value *val) { return MgInvoke<int>(mgp_value_is_int, val); }
|
||||
|
||||
inline bool value_is_double(mgp_value *val) { return MgInvoke<int>(mgp_value_is_double, val); }
|
||||
|
||||
inline bool value_is_string(mgp_value *val) { return MgInvoke<int>(mgp_value_is_string, val); }
|
||||
|
||||
inline bool value_is_list(mgp_value *val) { return MgInvoke<int>(mgp_value_is_list, val); }
|
||||
|
||||
inline bool value_is_map(mgp_value *val) { return MgInvoke<int>(mgp_value_is_map, val); }
|
||||
|
||||
inline bool value_is_vertex(mgp_value *val) { return MgInvoke<int>(mgp_value_is_vertex, val); }
|
||||
|
||||
inline bool value_is_edge(mgp_value *val) { return MgInvoke<int>(mgp_value_is_edge, val); }
|
||||
|
||||
inline bool value_is_path(mgp_value *val) { return MgInvoke<int>(mgp_value_is_path, val); }
|
||||
|
||||
inline bool value_is_date(mgp_value *val) { return MgInvoke<int>(mgp_value_is_date, val); }
|
||||
|
||||
inline bool value_is_local_time(mgp_value *val) { return MgInvoke<int>(mgp_value_is_local_time, val); }
|
||||
|
||||
inline bool value_is_local_date_time(mgp_value *val) { return MgInvoke<int>(mgp_value_is_local_date_time, val); }
|
||||
|
||||
inline bool value_is_duration(mgp_value *val) { return MgInvoke<int>(mgp_value_is_duration, val); }
|
||||
|
||||
// Get type
|
||||
|
||||
inline mgp_type *type_any() { return MgInvoke<mgp_type *>(mgp_type_any); }
|
||||
|
||||
inline mgp_type *type_bool() { return MgInvoke<mgp_type *>(mgp_type_bool); }
|
||||
|
||||
inline mgp_type *type_string() { return MgInvoke<mgp_type *>(mgp_type_string); }
|
||||
|
||||
inline mgp_type *type_int() { return MgInvoke<mgp_type *>(mgp_type_int); }
|
||||
|
||||
inline mgp_type *type_float() { return MgInvoke<mgp_type *>(mgp_type_float); }
|
||||
|
||||
inline mgp_type *type_number() { return MgInvoke<mgp_type *>(mgp_type_number); }
|
||||
|
||||
inline mgp_type *type_list(mgp_type *element_type) { return MgInvoke<mgp_type *>(mgp_type_list, element_type); }
|
||||
|
||||
inline mgp_type *type_map() { return MgInvoke<mgp_type *>(mgp_type_map); }
|
||||
|
||||
inline mgp_type *type_node() { return MgInvoke<mgp_type *>(mgp_type_node); }
|
||||
|
||||
inline mgp_type *type_relationship() { return MgInvoke<mgp_type *>(mgp_type_relationship); }
|
||||
|
||||
inline mgp_type *type_path() { return MgInvoke<mgp_type *>(mgp_type_path); }
|
||||
|
||||
inline mgp_type *type_date() { return MgInvoke<mgp_type *>(mgp_type_date); }
|
||||
|
||||
inline mgp_type *type_local_time() { return MgInvoke<mgp_type *>(mgp_type_local_time); }
|
||||
|
||||
inline mgp_type *type_local_date_time() { return MgInvoke<mgp_type *>(mgp_type_local_date_time); }
|
||||
|
||||
inline mgp_type *type_duration() { return MgInvoke<mgp_type *>(mgp_type_duration); }
|
||||
|
||||
inline mgp_type *type_nullable(mgp_type *type) { return MgInvoke<mgp_type *>(mgp_type_nullable, type); }
|
||||
|
||||
// mgp_graph
|
||||
|
||||
inline bool graph_is_mutable(mgp_graph *graph) { return MgInvoke<int>(mgp_graph_is_mutable, graph); }
|
||||
|
||||
inline mgp_vertex *graph_create_vertex(mgp_graph *graph, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_vertex *>(mgp_graph_create_vertex, graph, memory);
|
||||
}
|
||||
|
||||
inline void graph_delete_vertex(mgp_graph *graph, mgp_vertex *vertex) {
|
||||
MgInvokeVoid(mgp_graph_delete_vertex, graph, vertex);
|
||||
}
|
||||
|
||||
inline void graph_detach_delete_vertex(mgp_graph *graph, mgp_vertex *vertex) {
|
||||
MgInvokeVoid(mgp_graph_detach_delete_vertex, graph, vertex);
|
||||
}
|
||||
|
||||
inline mgp_edge *graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *to, mgp_edge_type type,
|
||||
mgp_memory *memory) {
|
||||
return MgInvoke<mgp_edge *>(mgp_graph_create_edge, graph, from, to, type, memory);
|
||||
}
|
||||
|
||||
inline void graph_delete_edge(mgp_graph *graph, mgp_edge *edge) { MgInvokeVoid(mgp_graph_delete_edge, graph, edge); }
|
||||
|
||||
inline mgp_vertex *graph_get_vertex_by_id(mgp_graph *g, mgp_vertex_id id, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_vertex *>(mgp_graph_get_vertex_by_id, g, id, memory);
|
||||
}
|
||||
|
||||
inline mgp_vertices_iterator *graph_iter_vertices(mgp_graph *g, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_vertices_iterator *>(mgp_graph_iter_vertices, g, memory);
|
||||
}
|
||||
|
||||
// mgp_vertices_iterator
|
||||
|
||||
inline void vertices_iterator_destroy(mgp_vertices_iterator *it) { mgp_vertices_iterator_destroy(it); }
|
||||
|
||||
inline mgp_vertex *vertices_iterator_get(mgp_vertices_iterator *it) {
|
||||
return MgInvoke<mgp_vertex *>(mgp_vertices_iterator_get, it);
|
||||
}
|
||||
|
||||
inline mgp_vertex *vertices_iterator_next(mgp_vertices_iterator *it) {
|
||||
return MgInvoke<mgp_vertex *>(mgp_vertices_iterator_next, it);
|
||||
}
|
||||
|
||||
// mgp_edges_iterator
|
||||
|
||||
inline void edges_iterator_destroy(mgp_edges_iterator *it) { mgp_edges_iterator_destroy(it); }
|
||||
|
||||
inline mgp_edge *edges_iterator_get(mgp_edges_iterator *it) { return MgInvoke<mgp_edge *>(mgp_edges_iterator_get, it); }
|
||||
|
||||
inline mgp_edge *edges_iterator_next(mgp_edges_iterator *it) {
|
||||
return MgInvoke<mgp_edge *>(mgp_edges_iterator_next, it);
|
||||
}
|
||||
|
||||
// mgp_properties_iterator
|
||||
|
||||
inline void properties_iterator_destroy(mgp_properties_iterator *it) { mgp_properties_iterator_destroy(it); }
|
||||
|
||||
inline mgp_property *properties_iterator_get(mgp_properties_iterator *it) {
|
||||
return MgInvoke<mgp_property *>(mgp_properties_iterator_get, it);
|
||||
}
|
||||
|
||||
inline mgp_property *properties_iterator_next(mgp_properties_iterator *it) {
|
||||
return MgInvoke<mgp_property *>(mgp_properties_iterator_next, it);
|
||||
}
|
||||
|
||||
// Container {mgp_list, mgp_map} methods
|
||||
|
||||
// mgp_list
|
||||
|
||||
inline mgp_list *list_make_empty(size_t capacity, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_list *>(mgp_list_make_empty, capacity, memory);
|
||||
}
|
||||
|
||||
inline mgp_list *list_copy(mgp_list *list, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_list *>(mgp_list_copy, list, memory);
|
||||
}
|
||||
|
||||
inline void list_destroy(mgp_list *list) { mgp_list_destroy(list); }
|
||||
|
||||
inline void list_append(mgp_list *list, mgp_value *val) { MgInvokeVoid(mgp_list_append, list, val); }
|
||||
|
||||
inline void list_append_extend(mgp_list *list, mgp_value *val) { MgInvokeVoid(mgp_list_append_extend, list, val); }
|
||||
|
||||
inline size_t list_size(mgp_list *list) { return MgInvoke<size_t>(mgp_list_size, list); }
|
||||
|
||||
inline size_t list_capacity(mgp_list *list) { return MgInvoke<size_t>(mgp_list_capacity, list); }
|
||||
|
||||
inline mgp_value *list_at(mgp_list *list, size_t index) { return MgInvoke<mgp_value *>(mgp_list_at, list, index); }
|
||||
|
||||
// mgp_map
|
||||
|
||||
inline mgp_map *map_make_empty(mgp_memory *memory) { return MgInvoke<mgp_map *>(mgp_map_make_empty, memory); }
|
||||
|
||||
inline mgp_map *map_copy(mgp_map *map, mgp_memory *memory) { return MgInvoke<mgp_map *>(mgp_map_copy, map, memory); }
|
||||
|
||||
inline void map_destroy(mgp_map *map) { mgp_map_destroy(map); }
|
||||
|
||||
inline void map_insert(mgp_map *map, const char *key, mgp_value *value) {
|
||||
MgInvokeVoid(mgp_map_insert, map, key, value);
|
||||
}
|
||||
|
||||
inline size_t map_size(mgp_map *map) { return MgInvoke<size_t>(mgp_map_size, map); }
|
||||
|
||||
inline mgp_value *map_at(mgp_map *map, const char *key) { return MgInvoke<mgp_value *>(mgp_map_at, map, key); }
|
||||
|
||||
inline const char *map_item_key(mgp_map_item *item) { return MgInvoke<const char *>(mgp_map_item_key, item); }
|
||||
|
||||
inline mgp_value *map_item_value(mgp_map_item *item) { return MgInvoke<mgp_value *>(mgp_map_item_value, item); }
|
||||
|
||||
inline mgp_map_items_iterator *map_iter_items(mgp_map *map, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_map_items_iterator *>(mgp_map_iter_items, map, memory);
|
||||
}
|
||||
|
||||
inline void map_items_iterator_destroy(mgp_map_items_iterator *it) { mgp_map_items_iterator_destroy(it); }
|
||||
|
||||
inline mgp_map_item *map_items_iterator_get(mgp_map_items_iterator *it) {
|
||||
return MgInvoke<mgp_map_item *>(mgp_map_items_iterator_get, it);
|
||||
}
|
||||
|
||||
inline mgp_map_item *map_items_iterator_next(mgp_map_items_iterator *it) {
|
||||
return MgInvoke<mgp_map_item *>(mgp_map_items_iterator_next, it);
|
||||
}
|
||||
|
||||
// mgp_vertex
|
||||
|
||||
inline mgp_vertex_id vertex_get_id(mgp_vertex *v) { return MgInvoke<mgp_vertex_id>(mgp_vertex_get_id, v); }
|
||||
|
||||
inline mgp_vertex *vertex_copy(mgp_vertex *v, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_vertex *>(mgp_vertex_copy, v, memory);
|
||||
}
|
||||
|
||||
inline void vertex_destroy(mgp_vertex *v) { mgp_vertex_destroy(v); }
|
||||
|
||||
inline bool vertex_equal(mgp_vertex *v1, mgp_vertex *v2) { return MgInvoke<int>(mgp_vertex_equal, v1, v2); }
|
||||
|
||||
inline size_t vertex_labels_count(mgp_vertex *v) { return MgInvoke<size_t>(mgp_vertex_labels_count, v); }
|
||||
|
||||
inline mgp_label vertex_label_at(mgp_vertex *v, size_t index) {
|
||||
return MgInvoke<mgp_label>(mgp_vertex_label_at, v, index);
|
||||
}
|
||||
|
||||
inline bool vertex_has_label(mgp_vertex *v, mgp_label label) { return MgInvoke<int>(mgp_vertex_has_label, v, label); }
|
||||
|
||||
inline bool vertex_has_label_named(mgp_vertex *v, const char *label_name) {
|
||||
return MgInvoke<int>(mgp_vertex_has_label_named, v, label_name);
|
||||
}
|
||||
|
||||
inline void vertex_add_label(mgp_vertex *vertex, mgp_label label) { MgInvokeVoid(mgp_vertex_add_label, vertex, label); }
|
||||
|
||||
inline mgp_value *vertex_get_property(mgp_vertex *v, const char *property_name, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_value *>(mgp_vertex_get_property, v, property_name, memory);
|
||||
}
|
||||
|
||||
inline void vertex_set_property(mgp_vertex *v, const char *property_name, mgp_value *property_value) {
|
||||
MgInvokeVoid(mgp_vertex_set_property, v, property_name, property_value);
|
||||
}
|
||||
|
||||
inline mgp_properties_iterator *vertex_iter_properties(mgp_vertex *v, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_properties_iterator *>(mgp_vertex_iter_properties, v, memory);
|
||||
}
|
||||
|
||||
inline mgp_edges_iterator *vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_edges_iterator *>(mgp_vertex_iter_in_edges, v, memory);
|
||||
}
|
||||
|
||||
inline mgp_edges_iterator *vertex_iter_out_edges(mgp_vertex *v, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_edges_iterator *>(mgp_vertex_iter_out_edges, v, memory);
|
||||
}
|
||||
|
||||
// mgp_edge
|
||||
|
||||
inline mgp_edge_id edge_get_id(mgp_edge *e) { return MgInvoke<mgp_edge_id>(mgp_edge_get_id, e); }
|
||||
|
||||
inline mgp_edge *edge_copy(mgp_edge *e, mgp_memory *memory) { return MgInvoke<mgp_edge *>(mgp_edge_copy, e, memory); }
|
||||
|
||||
inline void edge_destroy(mgp_edge *e) { mgp_edge_destroy(e); }
|
||||
|
||||
inline bool edge_equal(mgp_edge *e1, mgp_edge *e2) { return MgInvoke<int>(mgp_edge_equal, e1, e2); }
|
||||
|
||||
inline mgp_edge_type edge_get_type(mgp_edge *e) { return MgInvoke<mgp_edge_type>(mgp_edge_get_type, e); }
|
||||
|
||||
inline mgp_vertex *edge_get_from(mgp_edge *e) { return MgInvoke<mgp_vertex *>(mgp_edge_get_from, e); }
|
||||
|
||||
inline mgp_vertex *edge_get_to(mgp_edge *e) { return MgInvoke<mgp_vertex *>(mgp_edge_get_to, e); }
|
||||
|
||||
inline mgp_value *edge_get_property(mgp_edge *e, const char *property_name, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_value *>(mgp_edge_get_property, e, property_name, memory);
|
||||
}
|
||||
|
||||
inline void edge_set_property(mgp_edge *e, const char *property_name, mgp_value *property_value) {
|
||||
MgInvokeVoid(mgp_edge_set_property, e, property_name, property_value);
|
||||
}
|
||||
|
||||
inline mgp_properties_iterator *edge_iter_properties(mgp_edge *e, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_properties_iterator *>(mgp_edge_iter_properties, e, memory);
|
||||
}
|
||||
|
||||
// mgp_path
|
||||
|
||||
inline mgp_path *path_make_with_start(mgp_vertex *vertex, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_path *>(mgp_path_make_with_start, vertex, memory);
|
||||
}
|
||||
|
||||
inline mgp_path *path_copy(mgp_path *path, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_path *>(mgp_path_copy, path, memory);
|
||||
}
|
||||
|
||||
inline void path_destroy(mgp_path *path) { mgp_path_destroy(path); }
|
||||
|
||||
inline void path_expand(mgp_path *path, mgp_edge *edge) { MgInvokeVoid(mgp_path_expand, path, edge); }
|
||||
|
||||
inline size_t path_size(mgp_path *path) { return MgInvoke<size_t>(mgp_path_size, path); }
|
||||
|
||||
inline mgp_vertex *path_vertex_at(mgp_path *path, size_t index) {
|
||||
return MgInvoke<mgp_vertex *>(mgp_path_vertex_at, path, index);
|
||||
}
|
||||
|
||||
inline mgp_edge *path_edge_at(mgp_path *path, size_t index) {
|
||||
return MgInvoke<mgp_edge *>(mgp_path_edge_at, path, index);
|
||||
}
|
||||
|
||||
inline bool path_equal(mgp_path *p1, mgp_path *p2) { return MgInvoke<int>(mgp_path_equal, p1, p2); }
|
||||
|
||||
// Temporal type {mgp_date, mgp_local_time, mgp_local_date_time, mgp_duration} methods
|
||||
|
||||
// mgp_date
|
||||
|
||||
inline mgp_date *date_from_string(const char *string, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_date *>(mgp_date_from_string, string, memory);
|
||||
}
|
||||
|
||||
inline mgp_date *date_from_parameters(mgp_date_parameters *parameters, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_date *>(mgp_date_from_parameters, parameters, memory);
|
||||
}
|
||||
|
||||
inline mgp_date *date_copy(mgp_date *date, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_date *>(mgp_date_copy, date, memory);
|
||||
}
|
||||
|
||||
inline void date_destroy(mgp_date *date) { mgp_date_destroy(date); }
|
||||
|
||||
inline bool date_equal(mgp_date *first, mgp_date *second) { return MgInvoke<int>(mgp_date_equal, first, second); }
|
||||
|
||||
inline int date_get_year(mgp_date *date) { return MgInvoke<int>(mgp_date_get_year, date); }
|
||||
|
||||
inline int date_get_month(mgp_date *date) { return MgInvoke<int>(mgp_date_get_month, date); }
|
||||
|
||||
inline int date_get_day(mgp_date *date) { return MgInvoke<int>(mgp_date_get_day, date); }
|
||||
|
||||
inline int64_t date_timestamp(mgp_date *date) { return MgInvoke<int64_t>(mgp_date_timestamp, date); }
|
||||
|
||||
inline mgp_date *date_now(mgp_memory *memory) { return MgInvoke<mgp_date *>(mgp_date_now, memory); }
|
||||
|
||||
inline mgp_date *date_add_duration(mgp_date *date, mgp_duration *dur, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_date *>(mgp_date_add_duration, date, dur, memory);
|
||||
}
|
||||
|
||||
inline mgp_date *date_sub_duration(mgp_date *date, mgp_duration *dur, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_date *>(mgp_date_sub_duration, date, dur, memory);
|
||||
}
|
||||
|
||||
inline mgp_duration *date_diff(mgp_date *first, mgp_date *second, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_duration *>(mgp_date_diff, first, second, memory);
|
||||
}
|
||||
|
||||
// mgp_local_time
|
||||
|
||||
inline mgp_local_time *local_time_from_string(const char *string, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_time *>(mgp_local_time_from_string, string, memory);
|
||||
}
|
||||
|
||||
inline mgp_local_time *local_time_from_parameters(mgp_local_time_parameters *parameters, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_time *>(mgp_local_time_from_parameters, parameters, memory);
|
||||
}
|
||||
|
||||
inline mgp_local_time *local_time_copy(mgp_local_time *local_time, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_time *>(mgp_local_time_copy, local_time, memory);
|
||||
}
|
||||
|
||||
inline void local_time_destroy(mgp_local_time *local_time) { mgp_local_time_destroy(local_time); }
|
||||
|
||||
inline bool local_time_equal(mgp_local_time *first, mgp_local_time *second) {
|
||||
return MgInvoke<int>(mgp_local_time_equal, first, second);
|
||||
}
|
||||
|
||||
inline int local_time_get_hour(mgp_local_time *local_time) {
|
||||
return MgInvoke<int>(mgp_local_time_get_hour, local_time);
|
||||
}
|
||||
|
||||
inline int local_time_get_minute(mgp_local_time *local_time) {
|
||||
return MgInvoke<int>(mgp_local_time_get_minute, local_time);
|
||||
}
|
||||
|
||||
inline int local_time_get_second(mgp_local_time *local_time) {
|
||||
return MgInvoke<int>(mgp_local_time_get_second, local_time);
|
||||
}
|
||||
|
||||
inline int local_time_get_millisecond(mgp_local_time *local_time) {
|
||||
return MgInvoke<int>(mgp_local_time_get_millisecond, local_time);
|
||||
}
|
||||
|
||||
inline int local_time_get_microsecond(mgp_local_time *local_time) {
|
||||
return MgInvoke<int>(mgp_local_time_get_microsecond, local_time);
|
||||
}
|
||||
|
||||
inline int64_t local_time_timestamp(mgp_local_time *local_time) {
|
||||
return MgInvoke<int64_t>(mgp_local_time_timestamp, local_time);
|
||||
}
|
||||
|
||||
inline mgp_local_time *local_time_now(mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_time *>(mgp_local_time_now, memory);
|
||||
}
|
||||
|
||||
inline mgp_local_time *local_time_add_duration(mgp_local_time *local_time, mgp_duration *dur, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_time *>(mgp_local_time_add_duration, local_time, dur, memory);
|
||||
}
|
||||
|
||||
inline mgp_local_time *local_time_sub_duration(mgp_local_time *local_time, mgp_duration *dur, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_time *>(mgp_local_time_sub_duration, local_time, dur, memory);
|
||||
}
|
||||
|
||||
inline mgp_duration *local_time_diff(mgp_local_time *first, mgp_local_time *second, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_duration *>(mgp_local_time_diff, first, second, memory);
|
||||
}
|
||||
|
||||
// mgp_local_date_time
|
||||
|
||||
inline mgp_local_date_time *local_date_time_from_string(const char *string, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_date_time *>(mgp_local_date_time_from_string, string, memory);
|
||||
}
|
||||
|
||||
inline mgp_local_date_time *local_date_time_from_parameters(mgp_local_date_time_parameters *parameters,
|
||||
mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_date_time *>(mgp_local_date_time_from_parameters, parameters, memory);
|
||||
}
|
||||
|
||||
inline mgp_local_date_time *local_date_time_copy(mgp_local_date_time *local_date_time, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_date_time *>(mgp_local_date_time_copy, local_date_time, memory);
|
||||
}
|
||||
|
||||
inline void local_date_time_destroy(mgp_local_date_time *local_date_time) {
|
||||
mgp_local_date_time_destroy(local_date_time);
|
||||
}
|
||||
|
||||
inline bool local_date_time_equal(mgp_local_date_time *first, mgp_local_date_time *second) {
|
||||
return MgInvoke<int>(mgp_local_date_time_equal, first, second);
|
||||
}
|
||||
|
||||
inline int local_date_time_get_year(mgp_local_date_time *local_date_time) {
|
||||
return MgInvoke<int>(mgp_local_date_time_get_year, local_date_time);
|
||||
}
|
||||
|
||||
inline int local_date_time_get_month(mgp_local_date_time *local_date_time) {
|
||||
return MgInvoke<int>(mgp_local_date_time_get_month, local_date_time);
|
||||
}
|
||||
|
||||
inline int local_date_time_get_day(mgp_local_date_time *local_date_time) {
|
||||
return MgInvoke<int>(mgp_local_date_time_get_day, local_date_time);
|
||||
}
|
||||
|
||||
inline int local_date_time_get_hour(mgp_local_date_time *local_date_time) {
|
||||
return MgInvoke<int>(mgp_local_date_time_get_hour, local_date_time);
|
||||
}
|
||||
|
||||
inline int local_date_time_get_minute(mgp_local_date_time *local_date_time) {
|
||||
return MgInvoke<int>(mgp_local_date_time_get_minute, local_date_time);
|
||||
}
|
||||
|
||||
inline int local_date_time_get_second(mgp_local_date_time *local_date_time) {
|
||||
return MgInvoke<int>(mgp_local_date_time_get_second, local_date_time);
|
||||
}
|
||||
|
||||
inline int local_date_time_get_millisecond(mgp_local_date_time *local_date_time) {
|
||||
return MgInvoke<int>(mgp_local_date_time_get_millisecond, local_date_time);
|
||||
}
|
||||
|
||||
inline int local_date_time_get_microsecond(mgp_local_date_time *local_date_time) {
|
||||
return MgInvoke<int>(mgp_local_date_time_get_microsecond, local_date_time);
|
||||
}
|
||||
|
||||
inline int64_t local_date_time_timestamp(mgp_local_date_time *local_date_time) {
|
||||
return MgInvoke<int64_t>(mgp_local_date_time_timestamp, local_date_time);
|
||||
}
|
||||
|
||||
inline mgp_local_date_time *local_date_time_now(mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_date_time *>(mgp_local_date_time_now, memory);
|
||||
}
|
||||
|
||||
inline mgp_local_date_time *local_date_time_add_duration(mgp_local_date_time *local_date_time, mgp_duration *dur,
|
||||
mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_date_time *>(mgp_local_date_time_add_duration, local_date_time, dur, memory);
|
||||
}
|
||||
|
||||
inline mgp_local_date_time *local_date_time_sub_duration(mgp_local_date_time *local_date_time, mgp_duration *dur,
|
||||
mgp_memory *memory) {
|
||||
return MgInvoke<mgp_local_date_time *>(mgp_local_date_time_sub_duration, local_date_time, dur, memory);
|
||||
}
|
||||
|
||||
inline mgp_duration *local_date_time_diff(mgp_local_date_time *first, mgp_local_date_time *second, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_duration *>(mgp_local_date_time_diff, first, second, memory);
|
||||
}
|
||||
|
||||
// mgp_duration
|
||||
|
||||
inline mgp_duration *duration_from_string(const char *string, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_duration *>(mgp_duration_from_string, string, memory);
|
||||
}
|
||||
|
||||
inline mgp_duration *duration_from_parameters(mgp_duration_parameters *parameters, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_duration *>(mgp_duration_from_parameters, parameters, memory);
|
||||
}
|
||||
|
||||
inline mgp_duration *duration_from_microseconds(int64_t microseconds, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_duration *>(mgp_duration_from_microseconds, microseconds, memory);
|
||||
}
|
||||
|
||||
inline mgp_duration *duration_copy(mgp_duration *duration, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_duration *>(mgp_duration_copy, duration, memory);
|
||||
}
|
||||
|
||||
inline void duration_destroy(mgp_duration *duration) { mgp_duration_destroy(duration); }
|
||||
|
||||
inline int64_t duration_get_microseconds(mgp_duration *duration) {
|
||||
return MgInvoke<int64_t>(mgp_duration_get_microseconds, duration);
|
||||
}
|
||||
|
||||
inline bool duration_equal(mgp_duration *first, mgp_duration *second) {
|
||||
return MgInvoke<int>(mgp_duration_equal, first, second);
|
||||
}
|
||||
|
||||
inline mgp_duration *duration_neg(mgp_duration *duration, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_duration *>(mgp_duration_neg, duration, memory);
|
||||
}
|
||||
|
||||
inline mgp_duration *duration_add(mgp_duration *first, mgp_duration *second, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_duration *>(mgp_duration_add, first, second, memory);
|
||||
}
|
||||
|
||||
inline mgp_duration *duration_sub(mgp_duration *first, mgp_duration *second, mgp_memory *memory) {
|
||||
return MgInvoke<mgp_duration *>(mgp_duration_sub, first, second, memory);
|
||||
}
|
||||
|
||||
// Procedure
|
||||
|
||||
inline mgp_proc *module_add_read_procedure(mgp_module *module, const char *name, mgp_proc_cb cb) {
|
||||
return MgInvoke<mgp_proc *>(mgp_module_add_read_procedure, module, name, cb);
|
||||
}
|
||||
|
||||
inline mgp_proc *module_add_write_procedure(mgp_module *module, const char *name, mgp_proc_cb cb) {
|
||||
return MgInvoke<mgp_proc *>(mgp_module_add_write_procedure, module, name, cb);
|
||||
}
|
||||
|
||||
inline void proc_add_arg(mgp_proc *proc, const char *name, mgp_type *type) {
|
||||
MgInvokeVoid(mgp_proc_add_arg, proc, name, type);
|
||||
}
|
||||
|
||||
inline void proc_add_opt_arg(mgp_proc *proc, const char *name, mgp_type *type, mgp_value *default_value) {
|
||||
MgInvokeVoid(mgp_proc_add_opt_arg, proc, name, type, default_value);
|
||||
}
|
||||
|
||||
inline void proc_add_result(mgp_proc *proc, const char *name, mgp_type *type) {
|
||||
MgInvokeVoid(mgp_proc_add_result, proc, name, type);
|
||||
}
|
||||
|
||||
inline void proc_add_deprecated_result(mgp_proc *proc, const char *name, mgp_type *type) {
|
||||
MgInvokeVoid(mgp_proc_add_deprecated_result, proc, name, type);
|
||||
}
|
||||
|
||||
inline bool must_abort(mgp_graph *graph) { return mgp_must_abort(graph); }
|
||||
|
||||
// mgp_result
|
||||
|
||||
inline void result_set_error_msg(mgp_result *res, const char *error_msg) {
|
||||
MgInvokeVoid(mgp_result_set_error_msg, res, error_msg);
|
||||
}
|
||||
|
||||
inline mgp_result_record *result_new_record(mgp_result *res) {
|
||||
return MgInvoke<mgp_result_record *>(mgp_result_new_record, res);
|
||||
}
|
||||
|
||||
inline void result_record_insert(mgp_result_record *record, const char *field_name, mgp_value *val) {
|
||||
MgInvokeVoid(mgp_result_record_insert, record, field_name, val);
|
||||
}
|
||||
|
||||
// Function
|
||||
|
||||
inline mgp_func *module_add_function(mgp_module *module, const char *name, mgp_func_cb cb) {
|
||||
return MgInvoke<mgp_func *>(mgp_module_add_function, module, name, cb);
|
||||
}
|
||||
|
||||
inline void func_add_arg(mgp_func *func, const char *name, mgp_type *type) {
|
||||
MgInvokeVoid(mgp_func_add_arg, func, name, type);
|
||||
}
|
||||
|
||||
inline void func_add_opt_arg(mgp_func *func, const char *name, mgp_type *type, mgp_value *default_value) {
|
||||
MgInvokeVoid(mgp_func_add_opt_arg, func, name, type, default_value);
|
||||
}
|
||||
|
||||
inline void func_result_set_error_msg(mgp_func_result *res, const char *msg, mgp_memory *memory) {
|
||||
MgInvokeVoid(mgp_func_result_set_error_msg, res, msg, memory);
|
||||
}
|
||||
|
||||
inline void func_result_set_value(mgp_func_result *res, mgp_value *value, mgp_memory *memory) {
|
||||
MgInvokeVoid(mgp_func_result_set_value, res, value, memory);
|
||||
}
|
||||
|
||||
} // namespace mgp
|
||||
57
include/mg_exceptions.hpp
Normal file
57
include/mg_exceptions.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
// 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 <exception>
|
||||
#include <iostream>
|
||||
|
||||
namespace mg_exception {
|
||||
struct NotEnoughMemoryException : public std::exception {
|
||||
const char *what() const throw() { return "Not enough memory!"; }
|
||||
};
|
||||
struct UnknownException : public std::exception {
|
||||
const char *what() const throw() { return "Unknown exception!"; }
|
||||
};
|
||||
struct AllocationException : public std::exception {
|
||||
const char *what() const throw() { return "Could not allocate memory!"; }
|
||||
};
|
||||
struct InsufficientBufferException : public std::exception {
|
||||
const char *what() const throw() { return "Buffer is not sufficient to process procedure!"; }
|
||||
};
|
||||
struct OutOfRangeException : public std::exception {
|
||||
const char *what() const throw() { return "Index out of range!"; }
|
||||
};
|
||||
struct LogicException : public std::exception {
|
||||
const char *what() const throw() { return "Logic exception, check the procedure signature!"; }
|
||||
};
|
||||
struct DeletedObjectException : public std::exception {
|
||||
const char *what() const throw() { return "Object is deleted!"; }
|
||||
};
|
||||
struct InvalidArgumentException : public std::exception {
|
||||
const char *what() const throw() { return "Invalid argument!"; }
|
||||
};
|
||||
struct InvalidIDException : public std::exception {
|
||||
const char *what() const throw() { return "Invalid ID!"; }
|
||||
};
|
||||
struct KeyAlreadyExistsException : public std::exception {
|
||||
const char *what() const throw() { return "Key you are trying to set already exists!"; }
|
||||
};
|
||||
struct ImmutableObjectException : public std::exception {
|
||||
const char *what() const throw() { return "Object you are trying to change is immutable!"; }
|
||||
};
|
||||
struct ValueConversionException : public std::exception {
|
||||
const char *what() const throw() { return "Error in value conversion!"; }
|
||||
};
|
||||
struct SerializationException : public std::exception {
|
||||
const char *what() const throw() { return "Error in serialization!"; }
|
||||
};
|
||||
} // namespace mg_exception
|
||||
@@ -37,12 +37,19 @@ extern "C" {
|
||||
/// All functions return an error code that can be used to figure out whether the API call was successful or not. In
|
||||
/// case of failure, the specific error code can be used to identify the reason of the failure.
|
||||
MGP_ENUM_CLASS MGP_NODISCARD mgp_error{
|
||||
MGP_ERROR_NO_ERROR, MGP_ERROR_UNKNOWN_ERROR,
|
||||
MGP_ERROR_UNABLE_TO_ALLOCATE, MGP_ERROR_INSUFFICIENT_BUFFER,
|
||||
MGP_ERROR_OUT_OF_RANGE, MGP_ERROR_LOGIC_ERROR,
|
||||
MGP_ERROR_DELETED_OBJECT, MGP_ERROR_INVALID_ARGUMENT,
|
||||
MGP_ERROR_KEY_ALREADY_EXISTS, MGP_ERROR_IMMUTABLE_OBJECT,
|
||||
MGP_ERROR_VALUE_CONVERSION, MGP_ERROR_SERIALIZATION_ERROR,
|
||||
MGP_ERROR_NO_ERROR,
|
||||
MGP_ERROR_UNKNOWN_ERROR,
|
||||
MGP_ERROR_UNABLE_TO_ALLOCATE,
|
||||
MGP_ERROR_INSUFFICIENT_BUFFER,
|
||||
MGP_ERROR_OUT_OF_RANGE,
|
||||
MGP_ERROR_LOGIC_ERROR,
|
||||
MGP_ERROR_DELETED_OBJECT,
|
||||
MGP_ERROR_INVALID_ARGUMENT,
|
||||
MGP_ERROR_KEY_ALREADY_EXISTS,
|
||||
MGP_ERROR_IMMUTABLE_OBJECT,
|
||||
MGP_ERROR_VALUE_CONVERSION,
|
||||
MGP_ERROR_SERIALIZATION_ERROR,
|
||||
MGP_ERROR_AUTHORIZATION_ERROR,
|
||||
};
|
||||
///@}
|
||||
|
||||
@@ -164,6 +171,8 @@ enum mgp_value_type {
|
||||
MGP_VALUE_TYPE_DURATION,
|
||||
};
|
||||
|
||||
enum mgp_error mgp_value_copy(struct mgp_value *val, struct mgp_memory *memory, struct mgp_value **result);
|
||||
|
||||
/// Free the memory used by the given mgp_value instance.
|
||||
void mgp_value_destroy(struct mgp_value *val);
|
||||
|
||||
@@ -399,6 +408,8 @@ enum mgp_error mgp_value_get_duration(struct mgp_value *val, struct mgp_duration
|
||||
/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_list.
|
||||
enum mgp_error mgp_list_make_empty(size_t capacity, struct mgp_memory *memory, struct mgp_list **result);
|
||||
|
||||
enum mgp_error mgp_list_copy(struct mgp_list *list, struct mgp_memory *memory, struct mgp_list **result);
|
||||
|
||||
/// Free the memory used by the given mgp_list and contained elements.
|
||||
void mgp_list_destroy(struct mgp_list *list);
|
||||
|
||||
@@ -437,6 +448,8 @@ enum mgp_error mgp_list_at(struct mgp_list *list, size_t index, struct mgp_value
|
||||
/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_map.
|
||||
enum mgp_error mgp_map_make_empty(struct mgp_memory *memory, struct mgp_map **result);
|
||||
|
||||
enum mgp_error mgp_map_copy(struct mgp_map *map, struct mgp_memory *memory, struct mgp_map **result);
|
||||
|
||||
/// Free the memory used by the given mgp_map and contained items.
|
||||
void mgp_map_destroy(struct mgp_map *map);
|
||||
|
||||
@@ -1292,6 +1305,12 @@ struct mgp_proc;
|
||||
/// Describes a Memgraph magic function.
|
||||
struct mgp_func;
|
||||
|
||||
/// All available log levels that can be used in mgp_log function
|
||||
MGP_ENUM_CLASS mgp_log_level{
|
||||
MGP_LOG_LEVEL_TRACE, MGP_LOG_LEVEL_DEBUG, MGP_LOG_LEVEL_INFO,
|
||||
MGP_LOG_LEVEL_WARN, MGP_LOG_LEVEL_ERROR, MGP_LOG_LEVEL_CRITICAL,
|
||||
};
|
||||
|
||||
/// Entry-point for a query module read procedure, invoked through openCypher.
|
||||
///
|
||||
/// Passed in arguments will not live longer than the callback's execution.
|
||||
@@ -1386,6 +1405,9 @@ enum mgp_error mgp_proc_add_result(struct mgp_proc *proc, const char *name, stru
|
||||
/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid result name.
|
||||
/// RETURN mgp_error::MGP_ERROR_LOGIC_ERROR if a result field with the same name was already added.
|
||||
enum mgp_error mgp_proc_add_deprecated_result(struct mgp_proc *proc, const char *name, struct mgp_type *type);
|
||||
|
||||
/// Log a message on a certain level.
|
||||
enum mgp_error mgp_log(enum mgp_log_level log_level, const char *output);
|
||||
///@}
|
||||
|
||||
/// @name Execution
|
||||
@@ -1512,6 +1534,10 @@ enum mgp_error mgp_module_add_transformation(struct mgp_module *module, const ch
|
||||
///
|
||||
///@{
|
||||
|
||||
/// State of the database that is exposed to magic functions. Currently it is unused, but it enables extending the
|
||||
/// functionalities of magic functions in future without breaking the API.
|
||||
struct mgp_func_context;
|
||||
|
||||
/// Add a required argument to a function.
|
||||
///
|
||||
/// The order of the added arguments corresponds to the signature of the openCypher function.
|
||||
|
||||
3490
include/mgp.hpp
Normal file
3490
include/mgp.hpp
Normal file
File diff suppressed because it is too large
Load Diff
776
include/mgp.py
776
include/mgp.py
File diff suppressed because it is too large
Load Diff
20
init
20
init
@@ -5,6 +5,9 @@ cd "$DIR"
|
||||
|
||||
source "$DIR/environment/util.sh"
|
||||
|
||||
DISTRO=$(operating_system)
|
||||
ARCHITECTURE=$(architecture)
|
||||
|
||||
function print_help () {
|
||||
echo "Usage: $0 [OPTION]"
|
||||
echo -e "Check for missing packages and setup the project.\n"
|
||||
@@ -64,8 +67,6 @@ else
|
||||
done
|
||||
fi
|
||||
|
||||
DISTRO=$(operating_system)
|
||||
ARCHITECTURE=$(architecture)
|
||||
if [ "${ARCHITECTURE}" = "arm64" ] || [ "${ARCHITECTURE}" = "aarch64" ]; then
|
||||
OS_SCRIPT=$DIR/environment/os/$DISTRO-arm.sh
|
||||
else
|
||||
@@ -111,6 +112,12 @@ if [[ "$setup_libs" == "true" ]]; then
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# Fix for centos 7 during release
|
||||
if [ "${ARCHITECTURE}" = "centos-7" ]; then
|
||||
python3 -m pip uninstall virtualenv
|
||||
python3 -m pip install virtualenv
|
||||
fi
|
||||
|
||||
# setup gql_behave dependencies
|
||||
setup_virtualenv tests/gql_behave
|
||||
|
||||
@@ -139,3 +146,12 @@ done;
|
||||
# Install precommit hook
|
||||
python3 -m pip install pre-commit
|
||||
python3 -m pre_commit install
|
||||
|
||||
# Install py format tools
|
||||
echo "Install black formatter"
|
||||
python3 -m pip install black==22.8.*
|
||||
echo "Install isort"
|
||||
python3 -m pip install isort==5.10.*
|
||||
|
||||
# Link `include/mgp.py` with `release/mgp/mgp.py`
|
||||
ln -v -f include/mgp.py release/mgp/mgp.py
|
||||
|
||||
1
libs/.gitignore
vendored
1
libs/.gitignore
vendored
@@ -5,3 +5,4 @@
|
||||
!CMakeLists.txt
|
||||
!__main.cpp
|
||||
!pulsar.patch
|
||||
!antlr4.10.1.patch
|
||||
|
||||
@@ -106,6 +106,7 @@ import_external_library(antlr4 STATIC
|
||||
-DWITH_LIBCXX=OFF # because of debian bug
|
||||
-DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=true
|
||||
-DCMAKE_CXX_STANDARD=20
|
||||
-DANTLR_BUILD_CPP_TESTS=OFF
|
||||
BUILD_COMMAND $(MAKE) antlr4_static
|
||||
INSTALL_COMMAND $(MAKE) install)
|
||||
|
||||
|
||||
13
libs/antlr4.10.1.patch
Normal file
13
libs/antlr4.10.1.patch
Normal file
@@ -0,0 +1,13 @@
|
||||
diff --git a/runtime/Cpp/runtime/CMakeLists.txt b/runtime/Cpp/runtime/CMakeLists.txt
|
||||
index baf46cac9..2e7756de8 100644
|
||||
--- a/runtime/Cpp/runtime/CMakeLists.txt
|
||||
+++ b/runtime/Cpp/runtime/CMakeLists.txt
|
||||
@@ -134,7 +134,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
|
||||
EXPORT antlr4-targets
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
@@ -1,43 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -105,7 +105,7 @@ repo_clone_try_double () {
|
||||
# Download from primary_urls might fail because the cache is not installed.
|
||||
declare -A primary_urls=(
|
||||
["antlr4-code"]="http://$local_cache_host/git/antlr4.git"
|
||||
["antlr4-generator"]="http://$local_cache_host/file/antlr-4.9.2-complete.jar"
|
||||
["antlr4-generator"]="http://$local_cache_host/file/antlr-4.10.1-complete.jar"
|
||||
["cppitertools"]="http://$local_cache_host/git/cppitertools.git"
|
||||
["rapidcheck"]="http://$local_cache_host/git/rapidcheck.git"
|
||||
["gbenchmark"]="http://$local_cache_host/git/benchmark.git"
|
||||
@@ -130,7 +130,7 @@ declare -A primary_urls=(
|
||||
# should fail.
|
||||
declare -A secondary_urls=(
|
||||
["antlr4-code"]="https://github.com/antlr/antlr4.git"
|
||||
["antlr4-generator"]="http://www.antlr.org/download/antlr-4.9.2-complete.jar"
|
||||
["antlr4-generator"]="https://www.antlr.org/download/antlr-4.10.1-complete.jar"
|
||||
["cppitertools"]="https://github.com/ryanhaining/cppitertools.git"
|
||||
["rapidcheck"]="https://github.com/emil-e/rapidcheck.git"
|
||||
["gbenchmark"]="https://github.com/google/benchmark.git"
|
||||
@@ -152,10 +152,10 @@ declare -A secondary_urls=(
|
||||
# antlr
|
||||
file_get_try_double "${primary_urls[antlr4-generator]}" "${secondary_urls[antlr4-generator]}"
|
||||
|
||||
antlr4_tag="4.9.2" # v4.9.2
|
||||
antlr4_tag="4.10.1" # v4.10.1
|
||||
repo_clone_try_double "${primary_urls[antlr4-code]}" "${secondary_urls[antlr4-code]}" "antlr4" "$antlr4_tag" true
|
||||
pushd antlr4
|
||||
git apply ../antlr4.patch
|
||||
git apply ../antlr4.10.1.patch
|
||||
popd
|
||||
|
||||
# cppitertools v2.0 2019-12-23
|
||||
@@ -199,7 +199,7 @@ git apply ../rocksdb.patch
|
||||
popd
|
||||
|
||||
# mgclient
|
||||
mgclient_tag="96e95c6845463cbe88948392be58d26da0d5ffd3" # (2022-02-08)
|
||||
mgclient_tag="v1.4.0" # (2022-06-14)
|
||||
repo_clone_try_double "${primary_urls[mgclient]}" "${secondary_urls[mgclient]}" "mgclient" "$mgclient_tag"
|
||||
sed -i 's/\${CMAKE_INSTALL_LIBDIR}/lib/' mgclient/src/CMakeLists.txt
|
||||
|
||||
@@ -208,7 +208,7 @@ pymgclient_tag="4f85c179e56302d46a1e3e2cf43509db65f062b3" # (2021-01-15)
|
||||
repo_clone_try_double "${primary_urls[pymgclient]}" "${secondary_urls[pymgclient]}" "pymgclient" "$pymgclient_tag"
|
||||
|
||||
# mgconsole
|
||||
mgconsole_tag="v1.1.0" # (2021-10-07)
|
||||
mgconsole_tag="v1.3.0" # (2022-11-20)
|
||||
repo_clone_try_double "${primary_urls[mgconsole]}" "${secondary_urls[mgconsole]}" "mgconsole" "$mgconsole_tag" true
|
||||
|
||||
spdlog_tag="v1.9.2" # (2021-08-12)
|
||||
|
||||
@@ -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: 2026-27-04
|
||||
CHANGE DATE: 2027-26-01
|
||||
CHANGE LICENSE: Apache License, Version 2.0
|
||||
|
||||
For information about alternative licensing arrangements, please visit: https://memgraph.com/legal.
|
||||
|
||||
@@ -2,8 +2,8 @@ MEMGRAPH
|
||||
ENTERPRISE LICENCE AGREEMENT
|
||||
|
||||
|
||||
Memgraph Limited is registered in England under registration 10195084 and has its registered office at Suite 4,
|
||||
Ironstone House, Ironstone Way, Brixworth, Northampton, NN6 9UD (“Memgraph”).
|
||||
Memgraph Limited is registered in England under registration 10195084 and has its registered office at 90a High Street,
|
||||
Hertfordshire, Berkhamsted, HP4 2BL United Kingdom ("Memgraph").
|
||||
|
||||
|
||||
Memgraph agrees to license and/or grant you (the “Customer”) access to the Software ( as defined below) and provide
|
||||
|
||||
12
pyproject.toml
Normal file
12
pyproject.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
include = '\.pyi?$'
|
||||
extend-exclude = '''
|
||||
/(
|
||||
| .git
|
||||
| .__pycache__
|
||||
| build
|
||||
| libs
|
||||
| .cache
|
||||
)/
|
||||
'''
|
||||
@@ -8,29 +8,40 @@ disallow_in_source_build()
|
||||
|
||||
# Everything that is installed here, should be under the "query_modules" component.
|
||||
set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME "query_modules")
|
||||
|
||||
add_library(example SHARED example.c)
|
||||
target_include_directories(example PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
target_compile_options(example PRIVATE -Wall)
|
||||
|
||||
# Strip the library in release build.
|
||||
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
|
||||
if (lower_build_type STREQUAL "release")
|
||||
add_custom_command(TARGET example POST_BUILD
|
||||
COMMAND strip -s $<TARGET_FILE:example>
|
||||
COMMENT "Stripping symbols and sections from example module")
|
||||
endif()
|
||||
|
||||
install(PROGRAMS $<TARGET_FILE:example>
|
||||
add_library(example_c SHARED example.c)
|
||||
target_include_directories(example_c PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
target_compile_options(example_c PRIVATE -Wall)
|
||||
# Strip C example in release build.
|
||||
if (lower_build_type STREQUAL "release")
|
||||
add_custom_command(TARGET example_c POST_BUILD
|
||||
COMMAND strip -s $<TARGET_FILE:example_c>
|
||||
COMMENT "Stripping symbols and sections from the C example module")
|
||||
endif()
|
||||
install(PROGRAMS $<TARGET_FILE:example_c>
|
||||
DESTINATION lib/memgraph/query_modules
|
||||
RENAME example.so)
|
||||
RENAME example_c.so)
|
||||
# Also install the source of the example, so user can read it.
|
||||
install(FILES example.c DESTINATION lib/memgraph/query_modules/src)
|
||||
|
||||
# Install the Python example
|
||||
install(FILES example.py DESTINATION lib/memgraph/query_modules RENAME py_example.py)
|
||||
add_library(example_cpp SHARED example.cpp)
|
||||
target_include_directories(example_cpp PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
target_compile_options(example_cpp PRIVATE -Wall)
|
||||
# Strip C++ example in release build.
|
||||
if (lower_build_type STREQUAL "release")
|
||||
add_custom_command(TARGET example_cpp POST_BUILD
|
||||
COMMAND strip -s $<TARGET_FILE:example_cpp>
|
||||
COMMENT "Stripping symbols and sections from the C++ example module")
|
||||
endif()
|
||||
install(PROGRAMS $<TARGET_FILE:example_cpp>
|
||||
DESTINATION lib/memgraph/query_modules
|
||||
RENAME example_cpp.so)
|
||||
# Also install the source of the example, so user can read it.
|
||||
install(FILES example.cpp DESTINATION lib/memgraph/query_modules/src)
|
||||
|
||||
# Install the Python modules
|
||||
# Install the Python example and modules
|
||||
install(FILES example.py DESTINATION lib/memgraph/query_modules RENAME py_example.py)
|
||||
install(FILES graph_analyzer.py DESTINATION lib/memgraph/query_modules)
|
||||
install(FILES mgp_networkx.py DESTINATION lib/memgraph/query_modules)
|
||||
install(FILES nxalg.py DESTINATION lib/memgraph/query_modules)
|
||||
|
||||
103
query_modules/example.cpp
Normal file
103
query_modules/example.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include <mgp.hpp>
|
||||
|
||||
void ProcImpl(std::vector<mgp::Value> arguments, mgp::Graph graph, mgp::RecordFactory record_factory) {
|
||||
auto record = record_factory.NewRecord();
|
||||
record.Insert("out", true);
|
||||
}
|
||||
|
||||
void SampleReadProc(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
|
||||
try {
|
||||
mgp::memory = memory;
|
||||
|
||||
std::vector<mgp::Value> arguments;
|
||||
for (size_t i = 0; i < mgp::list_size(args); i++) {
|
||||
auto arg = mgp::Value(mgp::list_at(args, i));
|
||||
arguments.push_back(arg);
|
||||
}
|
||||
|
||||
ProcImpl(arguments, mgp::Graph(memgraph_graph), mgp::RecordFactory(result));
|
||||
} catch (const std::exception &e) {
|
||||
mgp::result_set_error_msg(result, e.what());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void AddXNodes(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
|
||||
mgp::memory = memory;
|
||||
auto graph = mgp::Graph(memgraph_graph);
|
||||
|
||||
std::vector<mgp::Value> arguments;
|
||||
for (size_t i = 0; i < mgp::list_size(args); i++) {
|
||||
auto arg = mgp::Value(mgp::list_at(args, i));
|
||||
arguments.push_back(arg);
|
||||
}
|
||||
|
||||
for (int i = 0; i < arguments[0].ValueInt(); i++) {
|
||||
graph.CreateNode();
|
||||
}
|
||||
}
|
||||
|
||||
void Multiply(mgp_list *args, mgp_func_context *ctx, mgp_func_result *res, mgp_memory *memory) {
|
||||
mgp::memory = memory;
|
||||
|
||||
std::vector<mgp::Value> arguments;
|
||||
for (size_t i = 0; i < mgp::list_size(args); i++) {
|
||||
auto arg = mgp::Value(mgp::list_at(args, i));
|
||||
arguments.push_back(arg);
|
||||
}
|
||||
|
||||
auto result = mgp::Result(res);
|
||||
|
||||
auto first = arguments[0].ValueInt();
|
||||
auto second = arguments[1].ValueInt();
|
||||
|
||||
result.SetValue(first * second);
|
||||
}
|
||||
|
||||
extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) {
|
||||
try {
|
||||
mgp::memory = memory;
|
||||
|
||||
AddProcedure(SampleReadProc, "return_true", mgp::ProcedureType::Read,
|
||||
{mgp::Parameter("param_1", mgp::Type::Int), mgp::Parameter("param_2", mgp::Type::Double, 2.3)},
|
||||
{mgp::Return("out", mgp::Type::Bool)}, module, memory);
|
||||
} catch (const std::exception &e) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
mgp::memory = memory;
|
||||
|
||||
mgp::AddProcedure(AddXNodes, "add_x_nodes", mgp::ProcedureType::Write, {mgp::Parameter("param_1", mgp::Type::Int)},
|
||||
{}, module, memory);
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
mgp::memory = memory;
|
||||
|
||||
mgp::AddFunction(Multiply, "multiply",
|
||||
{mgp::Parameter("int", mgp::Type::Int), mgp::Parameter("int", mgp::Type::Int, (int64_t)3)}, module,
|
||||
memory);
|
||||
|
||||
} catch (const std::exception &e) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int mgp_shutdown_module() { return 0; }
|
||||
@@ -7,11 +7,13 @@ import copy
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def procedure(
|
||||
context: mgp.ProcCtx,
|
||||
required_arg: mgp.Nullable[mgp.Any],
|
||||
optional_arg: mgp.Nullable[mgp.Any] = None,
|
||||
) -> mgp.Record(args=list, vertex_count=int, avg_degree=mgp.Number, props=mgp.Nullable[mgp.Map]):
|
||||
def procedure(context: mgp.ProcCtx,
|
||||
required_arg: mgp.Nullable[mgp.Any],
|
||||
optional_arg: mgp.Nullable[mgp.Any] = None
|
||||
) -> mgp.Record(args=list,
|
||||
vertex_count=int,
|
||||
avg_degree=mgp.Number,
|
||||
props=mgp.Nullable[mgp.Map]):
|
||||
"""
|
||||
This example procedure returns 4 fields.
|
||||
|
||||
@@ -35,7 +37,7 @@ def procedure(
|
||||
if isinstance(required_arg, (mgp.Edge, mgp.Vertex)):
|
||||
props = dict(required_arg.properties.items())
|
||||
elif isinstance(required_arg, mgp.Path):
|
||||
(start_vertex,) = required_arg.vertices
|
||||
start_vertex, = required_arg.vertices
|
||||
props = dict(start_vertex.properties.items())
|
||||
# Count the vertices and edges in the database; this may take a while.
|
||||
vertex_count = 0
|
||||
@@ -49,13 +51,15 @@ def procedure(
|
||||
# Copy the received arguments to make it equivalent to the C example.
|
||||
args_copy = [copy.deepcopy(required_arg), copy.deepcopy(optional_arg)]
|
||||
# Multiple rows can be produced by returning an iterable of mgp.Record.
|
||||
return mgp.Record(args=args_copy, vertex_count=vertex_count, avg_degree=avg_degree, props=props)
|
||||
return mgp.Record(args=args_copy, vertex_count=vertex_count,
|
||||
avg_degree=avg_degree, props=props)
|
||||
|
||||
|
||||
@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
|
||||
|
||||
@@ -4,17 +4,15 @@ from collections import OrderedDict
|
||||
from itertools import chain, repeat
|
||||
from inspect import cleandoc
|
||||
from typing import List, Tuple
|
||||
|
||||
try:
|
||||
import networkx as nx
|
||||
except ImportError as import_error:
|
||||
sys.stderr.write(
|
||||
(
|
||||
"\n"
|
||||
"NOTE: Please install networkx to be able to use graph_analyzer "
|
||||
"module. Using Python:\n" + sys.version + "\n"
|
||||
)
|
||||
)
|
||||
sys.stderr.write((
|
||||
'\n'
|
||||
'NOTE: Please install networkx to be able to use graph_analyzer '
|
||||
'module. Using Python:\n'
|
||||
+ sys.version +
|
||||
'\n'))
|
||||
raise import_error
|
||||
# Imported last because it also depends on networkx.
|
||||
from mgp_networkx import MemgraphMultiDiGraph # noqa E402
|
||||
@@ -25,14 +23,16 @@ _MAX_LIST_SIZE = 10
|
||||
|
||||
@mgp.read_proc
|
||||
def help() -> mgp.Record(name=str, value=str):
|
||||
"""Shows manual page for graph_analyzer."""
|
||||
'''Shows manual page for graph_analyzer.'''
|
||||
records = []
|
||||
|
||||
def make_records(name, doc):
|
||||
return (mgp.Record(name=n, value=v) for n, v in zip(chain([name], repeat("")), cleandoc(doc).splitlines()))
|
||||
return (mgp.Record(name=n, value=v) for n, v in
|
||||
zip(chain([name], repeat('')), cleandoc(doc).splitlines()))
|
||||
|
||||
for func in (help, analyze, analyze_subgraph):
|
||||
records.extend(make_records("Procedure '{}'".format(func.__name__), func.__doc__))
|
||||
records.extend(make_records("Procedure '{}'".format(func.__name__),
|
||||
func.__doc__))
|
||||
|
||||
for m, v in _get_analysis_mapping().items():
|
||||
records.extend(make_records("Analysis '{}'".format(m), v.__doc__))
|
||||
@@ -41,8 +41,10 @@ def help() -> mgp.Record(name=str, value=str):
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def analyze(context: mgp.ProcCtx, analyses: mgp.Nullable[List[str]] = None) -> mgp.Record(name=str, value=str):
|
||||
"""
|
||||
def analyze(context: mgp.ProcCtx,
|
||||
analyses: mgp.Nullable[List[str]] = None
|
||||
) -> mgp.Record(name=str, value=str):
|
||||
'''
|
||||
Shows graph information.
|
||||
|
||||
In case of multiple results, only the first 10 will be shown.
|
||||
@@ -55,20 +57,19 @@ def analyze(context: mgp.ProcCtx, analyses: mgp.Nullable[List[str]] = None) -> m
|
||||
|
||||
Example call (with parameter):
|
||||
CALL graph_analyzer.analyze(['nodes', 'edges']) YIELD *;
|
||||
"""
|
||||
'''
|
||||
g = MemgraphMultiDiGraph(ctx=context)
|
||||
recs = _analyze_graph(context, g, analyses)
|
||||
return [mgp.Record(name=name, value=value) for name, value in recs]
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def analyze_subgraph(
|
||||
context: mgp.ProcCtx,
|
||||
vertices: mgp.List[mgp.Vertex],
|
||||
edges: mgp.List[mgp.Edge],
|
||||
analyses: mgp.Nullable[List[str]] = None,
|
||||
) -> mgp.Record(name=str, value=str):
|
||||
"""
|
||||
def analyze_subgraph(context: mgp.ProcCtx,
|
||||
vertices: mgp.List[mgp.Vertex],
|
||||
edges: mgp.List[mgp.Edge],
|
||||
analyses: mgp.Nullable[List[str]] = None
|
||||
) -> mgp.Record(name=str, value=str):
|
||||
'''
|
||||
Shows subgraph information.
|
||||
|
||||
In case of multiple results, only the first 10 will be shown.
|
||||
@@ -90,40 +91,36 @@ def analyze_subgraph(
|
||||
CALL graph_analyzer.analyze_subgraph(nodes, edges, ['nodes', 'edges'])
|
||||
YIELD *
|
||||
RETURN name, value;
|
||||
"""
|
||||
'''
|
||||
vertices, edges = map(set, [vertices, edges])
|
||||
g = nx.subgraph_view(
|
||||
MemgraphMultiDiGraph(ctx=context),
|
||||
lambda n: n in vertices,
|
||||
lambda n1, n2, e: e in edges,
|
||||
)
|
||||
lambda n1, n2, e: e in edges)
|
||||
recs = _analyze_graph(context, g, analyses)
|
||||
return [mgp.Record(name=name, value=value) for name, value in recs]
|
||||
|
||||
|
||||
def _get_analysis_mapping():
|
||||
return OrderedDict(
|
||||
[
|
||||
("nodes", _number_of_nodes),
|
||||
("edges", _number_of_edges),
|
||||
("bridges", _bridges),
|
||||
("articulation_points", _articulation_points),
|
||||
("avg_degree", _avg_degree),
|
||||
("sorted_nodes_degree", _sorted_nodes_degree),
|
||||
("self_loops", _self_loops),
|
||||
("is_bipartite", _is_bipartite),
|
||||
("is_planar", _is_planar),
|
||||
("is_biconnected: ", _is_biconnected),
|
||||
("is_weakly_connected", _is_weakly_connected),
|
||||
("number_of_weakly_components", _weakly_components),
|
||||
("is_strongly_connected", _is_strongly_connected),
|
||||
("strongly_components", _strongly_components),
|
||||
("is_dag", _is_dag),
|
||||
("is_eulerian", _is_eulerian),
|
||||
("is_forest", _is_forest),
|
||||
("is_tree", _is_tree),
|
||||
]
|
||||
)
|
||||
return OrderedDict([
|
||||
('nodes', _number_of_nodes),
|
||||
('edges', _number_of_edges),
|
||||
('bridges', _bridges),
|
||||
('articulation_points', _articulation_points),
|
||||
('avg_degree', _avg_degree),
|
||||
('sorted_nodes_degree', _sorted_nodes_degree),
|
||||
('self_loops', _self_loops),
|
||||
('is_bipartite', _is_bipartite),
|
||||
('is_planar', _is_planar),
|
||||
('is_biconnected: ', _is_biconnected),
|
||||
('is_weakly_connected', _is_weakly_connected),
|
||||
('number_of_weakly_components', _weakly_components),
|
||||
('is_strongly_connected', _is_strongly_connected),
|
||||
('strongly_components', _strongly_components),
|
||||
('is_dag', _is_dag),
|
||||
('is_eulerian', _is_eulerian),
|
||||
('is_forest', _is_forest),
|
||||
('is_tree', _is_tree)])
|
||||
|
||||
|
||||
def _get_analysis_func(name: str):
|
||||
@@ -135,15 +132,20 @@ def _get_analysis_funcs():
|
||||
return _get_analysis_mapping().values()
|
||||
|
||||
|
||||
def _analyze_graph(context: mgp.ProcCtx, g: nx.MultiDiGraph, analyses: List[str]) -> List[Tuple[str, str]]:
|
||||
def _analyze_graph(context: mgp.ProcCtx,
|
||||
g: nx.MultiDiGraph,
|
||||
analyses: List[str]
|
||||
) -> List[Tuple[str, str]]:
|
||||
|
||||
functions = _get_analysis_funcs() if analyses is None else [_get_analysis_func(name) for name in analyses]
|
||||
functions = (_get_analysis_funcs() if analyses is None
|
||||
else [_get_analysis_func(name) for name in analyses])
|
||||
|
||||
records = []
|
||||
for index, f in enumerate(functions):
|
||||
context.check_must_abort()
|
||||
if f is None:
|
||||
raise KeyError("Graph analysis is not supported: " + analyses[index])
|
||||
raise KeyError('Graph analysis is not supported: ' +
|
||||
analyses[index])
|
||||
name, value = f(g)
|
||||
if isinstance(value, (list, set, tuple)):
|
||||
value = list(value)[:_MAX_LIST_SIZE]
|
||||
@@ -153,120 +155,126 @@ def _analyze_graph(context: mgp.ProcCtx, g: nx.MultiDiGraph, analyses: List[str]
|
||||
|
||||
|
||||
def _number_of_nodes(g: nx.MultiDiGraph) -> Tuple[str, int]:
|
||||
"""Returns number of nodes."""
|
||||
return "Number of nodes", nx.number_of_nodes(g)
|
||||
'''Returns number of nodes.'''
|
||||
return 'Number of nodes', nx.number_of_nodes(g)
|
||||
|
||||
|
||||
def _number_of_edges(g: nx.MultiDiGraph) -> Tuple[str, int]:
|
||||
"""Returns number of edges."""
|
||||
return "Number of edges", nx.number_of_edges(g)
|
||||
'''Returns number of edges.'''
|
||||
return 'Number of edges', nx.number_of_edges(g)
|
||||
|
||||
|
||||
def _avg_degree(g: nx.MultiDiGraph) -> Tuple[str, float]:
|
||||
"""Returns average degree."""
|
||||
'''Returns average degree.'''
|
||||
_, number_of_nodes = _number_of_nodes(g)
|
||||
_, number_of_edges = _number_of_edges(g)
|
||||
avg_degree = 0 if number_of_nodes == 0 else number_of_edges / number_of_nodes
|
||||
return "Average degree", avg_degree
|
||||
avg_degree = (0 if number_of_nodes == 0
|
||||
else number_of_edges / number_of_nodes)
|
||||
return 'Average degree', avg_degree
|
||||
|
||||
|
||||
def _sorted_nodes_degree(g: nx.MultiDiGraph) -> Tuple[str, List[int]]:
|
||||
"""Returns list of sorted nodes degree. [(node_id, degree), ...]"""
|
||||
'''Returns list of sorted nodes degree. [(node_id, degree), ...]'''
|
||||
nodes_degree = [(n, g.degree(n)) for n in g.nodes()]
|
||||
nodes_degree.sort(key=lambda x: x[1], reverse=True)
|
||||
return "Sorted nodes degree", nodes_degree
|
||||
return 'Sorted nodes degree', nodes_degree
|
||||
|
||||
|
||||
def _self_loops(g: nx.MultiDiGraph) -> Tuple[str, int]:
|
||||
"""Returns number of self loops."""
|
||||
return "Self loops", sum((1 if e[0] == e[1] else 0 for e in g.edges()))
|
||||
'''Returns number of self loops.'''
|
||||
return 'Self loops', sum((1 if e[0] == e[1] else 0 for e in g.edges()))
|
||||
|
||||
|
||||
def _is_bipartite(g: nx.MultiDiGraph) -> Tuple[str, bool]:
|
||||
"""Checks if graph is bipartite."""
|
||||
'''Checks if graph is bipartite.'''
|
||||
_, number_of_nodes = _number_of_nodes(g)
|
||||
ret = False if number_of_nodes == 0 else nx.algorithms.bipartite.basic.is_bipartite(g)
|
||||
return "Is bipartite", ret
|
||||
ret = (False if number_of_nodes == 0
|
||||
else nx.algorithms.bipartite.basic.is_bipartite(g))
|
||||
return 'Is bipartite', ret
|
||||
|
||||
|
||||
def _is_planar(g: nx.MultiDiGraph) -> Tuple[str, bool]:
|
||||
"""Checks if graph is planar."""
|
||||
'''Checks if graph is planar.'''
|
||||
_, number_of_nodes = _number_of_nodes(g)
|
||||
ret = False if number_of_nodes == 0 else nx.algorithms.planarity.check_planarity(g)[0]
|
||||
return "Is planar", ret
|
||||
ret = (False if number_of_nodes == 0
|
||||
else nx.algorithms.planarity.check_planarity(g)[0])
|
||||
return 'Is planar', ret
|
||||
|
||||
|
||||
def _is_biconnected(g: nx.MultiDiGraph) -> Tuple[str, bool]:
|
||||
"""Check if graph is biconnected."""
|
||||
'''Check if graph is biconnected.'''
|
||||
_, number_of_nodes = _number_of_nodes(g)
|
||||
ret = False if number_of_nodes == 0 else nx.is_biconnected(nx.MultiDiGraph.to_undirected(g))
|
||||
return "Is biconnected", ret
|
||||
ret = (False if number_of_nodes == 0
|
||||
else nx.is_biconnected(nx.MultiDiGraph.to_undirected(g)))
|
||||
return 'Is biconnected', ret
|
||||
|
||||
|
||||
def _is_weakly_connected(g: nx.MultiDiGraph) -> Tuple[str, bool]:
|
||||
"""Check if graph is weakly connected."""
|
||||
'''Check if graph is weakly connected.'''
|
||||
_, number_of_nodes = _number_of_nodes(g)
|
||||
ret = False if number_of_nodes == 0 else nx.is_weakly_connected(g)
|
||||
return "Is weakly connected", ret
|
||||
return 'Is weakly connected', ret
|
||||
|
||||
|
||||
def _is_strongly_connected(g: nx.MultiDiGraph) -> Tuple[str, bool]:
|
||||
"""Checks if graph is strongly connected."""
|
||||
'''Checks if graph is strongly connected.'''
|
||||
_, number_of_nodes = _number_of_nodes(g)
|
||||
ret = False if number_of_nodes == 0 else nx.is_strongly_connected(g)
|
||||
return "Is strongly connected", ret
|
||||
return 'Is strongly connected', ret
|
||||
|
||||
|
||||
def _is_dag(g: nx.MultiDiGraph) -> Tuple[str, bool]:
|
||||
"""Check if graph is directed acyclic graph (DAG)"""
|
||||
'''Check if graph is directed acyclic graph (DAG)'''
|
||||
_, number_of_nodes = _number_of_nodes(g)
|
||||
ret = False if number_of_nodes == 0 else nx.algorithms.dag.is_directed_acyclic_graph(g)
|
||||
return "Is DAG", ret
|
||||
ret = (False if number_of_nodes == 0
|
||||
else nx.algorithms.dag.is_directed_acyclic_graph(g))
|
||||
return 'Is DAG', ret
|
||||
|
||||
|
||||
def _is_eulerian(g: nx.MultiDiGraph) -> Tuple[str, bool]:
|
||||
"""Checks if graph is Eulerian."""
|
||||
'''Checks if graph is Eulerian.'''
|
||||
_, number_of_nodes = _number_of_nodes(g)
|
||||
ret = False if number_of_nodes == 0 else nx.algorithms.euler.is_eulerian(g)
|
||||
return "Is eulerian", ret
|
||||
ret = (False if number_of_nodes == 0
|
||||
else nx.algorithms.euler.is_eulerian(g))
|
||||
return 'Is eulerian', ret
|
||||
|
||||
|
||||
def _is_forest(g: nx.MultiDiGraph) -> Tuple[str, bool]:
|
||||
"""Checks if graph is forest, all components must be trees."""
|
||||
'''Checks if graph is forest, all components must be trees.'''
|
||||
_, number_of_nodes = _number_of_nodes(g)
|
||||
ret = False if number_of_nodes == 0 else nx.algorithms.tree.recognition.is_forest(g)
|
||||
return "Is forest", ret
|
||||
ret = (False if number_of_nodes == 0
|
||||
else nx.algorithms.tree.recognition.is_forest(g))
|
||||
return 'Is forest', ret
|
||||
|
||||
|
||||
def _is_tree(g: nx.MultiDiGraph) -> Tuple[str, bool]:
|
||||
"""Checks if graph is tree."""
|
||||
'''Checks if graph is tree.'''
|
||||
_, number_of_nodes = _number_of_nodes(g)
|
||||
ret = False if number_of_nodes == 0 else nx.algorithms.tree.recognition.is_tree(g)
|
||||
return "Is tree", ret
|
||||
ret = (False if number_of_nodes == 0
|
||||
else nx.algorithms.tree.recognition.is_tree(g))
|
||||
return 'Is tree', ret
|
||||
|
||||
|
||||
def _bridges(g: nx.MultiDiGraph) -> Tuple[str, int]:
|
||||
"""Returns number of bridges, multiple edges between same nodes are
|
||||
mapped to one edge."""
|
||||
return "Number of bridges", sum(1 for _ in nx.bridges(nx.Graph(g)))
|
||||
'''Returns number of bridges, multiple edges between same nodes are
|
||||
mapped to one edge.'''
|
||||
return 'Number of bridges', sum(1 for _ in nx.bridges(nx.Graph(g)))
|
||||
|
||||
|
||||
def _articulation_points(g: nx.MultiDiGraph):
|
||||
"""Returns number of articulation points."""
|
||||
'''Returns number of articulation points.'''
|
||||
undirected = nx.MultiDiGraph.to_undirected(g)
|
||||
return (
|
||||
"Number of articulation points",
|
||||
sum(1 for _ in nx.articulation_points(undirected)),
|
||||
)
|
||||
return ('Number of articulation points',
|
||||
sum(1 for _ in nx.articulation_points(undirected)))
|
||||
|
||||
|
||||
def _weakly_components(g: nx.MultiDiGraph):
|
||||
"""Returns number of weakly components."""
|
||||
'''Returns number of weakly components.'''
|
||||
comps = nx.algorithms.components.number_weakly_connected_components(g)
|
||||
return "Number of weakly connected components", comps
|
||||
return 'Number of weakly connected components', comps
|
||||
|
||||
|
||||
def _strongly_components(g: nx.MultiDiGraph):
|
||||
"""Returns number of strongly connected components."""
|
||||
'''Returns number of strongly connected components.'''
|
||||
comps = nx.algorithms.components.number_strongly_connected_components(g)
|
||||
return "Number of strongly connected components", comps
|
||||
return 'Number of strongly connected components', comps
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
import sys
|
||||
import mgp
|
||||
import collections
|
||||
|
||||
try:
|
||||
import networkx as nx
|
||||
except ImportError as import_error:
|
||||
sys.stderr.write(
|
||||
(
|
||||
"\n"
|
||||
"NOTE: Please install networkx to be able to use Memgraph NetworkX "
|
||||
"wrappers. Using Python:\n" + sys.version + "\n"
|
||||
)
|
||||
)
|
||||
sys.stderr.write((
|
||||
'\n'
|
||||
'NOTE: Please install networkx to be able to use Memgraph NetworkX '
|
||||
'wrappers. Using Python:\n'
|
||||
+ sys.version +
|
||||
'\n'))
|
||||
raise import_error
|
||||
|
||||
|
||||
class MemgraphAdjlistOuterDict(collections.abc.Mapping):
|
||||
__slots__ = ("_ctx", "_succ", "_multi")
|
||||
__slots__ = ('_ctx', '_succ', '_multi')
|
||||
|
||||
def __init__(self, ctx, succ=True, multi=True):
|
||||
self._ctx = ctx
|
||||
@@ -26,7 +24,8 @@ class MemgraphAdjlistOuterDict(collections.abc.Mapping):
|
||||
def __getitem__(self, key):
|
||||
if key not in self:
|
||||
raise KeyError
|
||||
return MemgraphAdjlistInnerDict(key, succ=self._succ, multi=self._multi)
|
||||
return MemgraphAdjlistInnerDict(key, succ=self._succ,
|
||||
multi=self._multi)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._ctx.graph.vertices)
|
||||
@@ -41,7 +40,7 @@ class MemgraphAdjlistOuterDict(collections.abc.Mapping):
|
||||
|
||||
|
||||
class MemgraphAdjlistInnerDict(collections.abc.Mapping):
|
||||
__slots__ = ("_node", "_succ", "_multi", "_neighbors")
|
||||
__slots__ = ('_node', '_succ', '_multi', '_neighbors')
|
||||
|
||||
def __init__(self, node, succ=True, multi=True):
|
||||
self._node = node
|
||||
@@ -72,26 +71,31 @@ class MemgraphAdjlistInnerDict(collections.abc.Mapping):
|
||||
def _get_neighbors(self):
|
||||
if not self._neighbors:
|
||||
if self._succ:
|
||||
self._neighbors = set(e.to_vertex for e in self._node.out_edges)
|
||||
self._neighbors = set(
|
||||
e.to_vertex for e in self._node.out_edges)
|
||||
else:
|
||||
self._neighbors = set(e.from_vertex for e in self._node.in_edges)
|
||||
self._neighbors = set(
|
||||
e.from_vertex for e in self._node.in_edges)
|
||||
return self._neighbors
|
||||
|
||||
def _get_edge(self, neighbor):
|
||||
if self._succ:
|
||||
edge = list(filter(lambda e: e.to_vertex == neighbor, self._node.out_edges))
|
||||
edge = list(filter(lambda e: e.to_vertex == neighbor,
|
||||
self._node.out_edges))
|
||||
else:
|
||||
edge = list(filter(lambda e: e.from_vertex == neighbor, self._node.in_edges))
|
||||
edge = list(filter(lambda e: e.from_vertex == neighbor,
|
||||
self._node.in_edges))
|
||||
|
||||
assert len(edge) >= 1
|
||||
if len(edge) > 1:
|
||||
raise RuntimeError("Graph contains multiedges but " "is of non-multigraph type: {}".format(edge))
|
||||
raise RuntimeError('Graph contains multiedges but '
|
||||
'is of non-multigraph type: {}'.format(edge))
|
||||
|
||||
return edge[0]
|
||||
|
||||
|
||||
class MemgraphEdgeKeyDict(collections.abc.Mapping):
|
||||
__slots__ = ("_node", "_neighbor", "_succ", "_edges")
|
||||
__slots__ = ('_node', '_neighbor', '_succ', '_edges')
|
||||
|
||||
def __init__(self, node, neighbor, succ=True):
|
||||
self._node = node
|
||||
@@ -118,14 +122,18 @@ class MemgraphEdgeKeyDict(collections.abc.Mapping):
|
||||
def _get_edges(self):
|
||||
if not self._edges:
|
||||
if self._succ:
|
||||
self._edges = list(filter(lambda e: e.to_vertex == self._neighbor, self._node.out_edges))
|
||||
self._edges = list(filter(
|
||||
lambda e: e.to_vertex == self._neighbor,
|
||||
self._node.out_edges))
|
||||
else:
|
||||
self._edges = list(filter(lambda e: e.from_vertex == self._neighbor, self._node.in_edges))
|
||||
self._edges = list(filter(
|
||||
lambda e: e.from_vertex == self._neighbor,
|
||||
self._node.in_edges))
|
||||
return self._edges
|
||||
|
||||
|
||||
class UnhashableProperties(collections.abc.Mapping):
|
||||
__slots__ = "_properties"
|
||||
__slots__ = ('_properties')
|
||||
|
||||
def __init__(self, properties):
|
||||
self._properties = properties
|
||||
@@ -147,7 +155,7 @@ class UnhashableProperties(collections.abc.Mapping):
|
||||
|
||||
|
||||
class MemgraphNodeDict(collections.abc.Mapping):
|
||||
__slots__ = ("_ctx",)
|
||||
__slots__ = ('_ctx',)
|
||||
|
||||
def __init__(self, ctx):
|
||||
self._ctx = ctx
|
||||
@@ -179,7 +187,8 @@ class MemgraphNodeDict(collections.abc.Mapping):
|
||||
|
||||
|
||||
class MemgraphDiGraphBase:
|
||||
def __init__(self, incoming_graph_data=None, ctx=None, multi=True, **kwargs):
|
||||
def __init__(self, incoming_graph_data=None, ctx=None, multi=True,
|
||||
**kwargs):
|
||||
# NOTE: We assume that our graph will never be given any initial data
|
||||
# because we already pull our data from the Memgraph database. This
|
||||
# assert is triggered by certain NetworkX procedures because they
|
||||
@@ -192,30 +201,23 @@ class MemgraphDiGraphBase:
|
||||
# modify the graph's internal attributes and don't try to populate it
|
||||
# with initial data or modify it.
|
||||
|
||||
self.node_dict_factory = lambda: MemgraphNodeDict(ctx) if ctx else self._error
|
||||
self.node_dict_factory = lambda: MemgraphNodeDict(ctx) \
|
||||
if ctx else self._error
|
||||
self.node_attr_dict_factory = self._error
|
||||
|
||||
self.adjlist_outer_dict_factory = lambda: MemgraphAdjlistOuterDict(ctx, multi=multi) if ctx else self._error
|
||||
self.adjlist_outer_dict_factory = \
|
||||
lambda: MemgraphAdjlistOuterDict(ctx, multi=multi) \
|
||||
if ctx else self._error
|
||||
self.adjlist_inner_dict_factory = self._error
|
||||
self.edge_key_dict_factory = self._error
|
||||
self.edge_attr_dict_factory = self._error
|
||||
|
||||
# NOTE: We forbid any mutating operations because our graph is
|
||||
# immutable and pulls its data from the Memgraph database.
|
||||
for f in [
|
||||
"add_node",
|
||||
"add_nodes_from",
|
||||
"remove_node",
|
||||
"remove_nodes_from",
|
||||
"add_edge",
|
||||
"add_edges_from",
|
||||
"add_weighted_edges_from",
|
||||
"new_edge_key",
|
||||
"remove_edge",
|
||||
"remove_edges_from",
|
||||
"update",
|
||||
"clear",
|
||||
]:
|
||||
for f in ['add_node', 'add_nodes_from', 'remove_node',
|
||||
'remove_nodes_from', 'add_edge', 'add_edges_from',
|
||||
'add_weighted_edges_from', 'new_edge_key', 'remove_edge',
|
||||
'remove_edges_from', 'update', 'clear']:
|
||||
setattr(self, f, lambda *args, **kwargs: self._error())
|
||||
|
||||
super().__init__(None, **kwargs)
|
||||
@@ -229,29 +231,33 @@ class MemgraphDiGraphBase:
|
||||
self._pred = MemgraphAdjlistOuterDict(ctx, succ=False, multi=multi)
|
||||
|
||||
def _error(self):
|
||||
raise RuntimeError("Modification operations are not supported")
|
||||
raise RuntimeError('Modification operations are not supported')
|
||||
|
||||
|
||||
class MemgraphMultiDiGraph(MemgraphDiGraphBase, nx.MultiDiGraph):
|
||||
def __init__(self, incoming_graph_data=None, ctx=None, **kwargs):
|
||||
super().__init__(incoming_graph_data=incoming_graph_data, ctx=ctx, multi=True, **kwargs)
|
||||
super().__init__(incoming_graph_data=incoming_graph_data,
|
||||
ctx=ctx, multi=True, **kwargs)
|
||||
|
||||
|
||||
def MemgraphMultiGraph(incoming_graph_data=None, ctx=None, **kwargs):
|
||||
return MemgraphMultiDiGraph(incoming_graph_data=incoming_graph_data, ctx=ctx, **kwargs).to_undirected(as_view=True)
|
||||
return MemgraphMultiDiGraph(incoming_graph_data=incoming_graph_data,
|
||||
ctx=ctx, **kwargs).to_undirected(as_view=True)
|
||||
|
||||
|
||||
class MemgraphDiGraph(MemgraphDiGraphBase, nx.DiGraph):
|
||||
def __init__(self, incoming_graph_data=None, ctx=None, **kwargs):
|
||||
super().__init__(incoming_graph_data=incoming_graph_data, ctx=ctx, multi=False, **kwargs)
|
||||
super().__init__(incoming_graph_data=incoming_graph_data,
|
||||
ctx=ctx, multi=False, **kwargs)
|
||||
|
||||
|
||||
def MemgraphGraph(incoming_graph_data=None, ctx=None, **kwargs):
|
||||
return MemgraphDiGraph(incoming_graph_data=incoming_graph_data, ctx=ctx, **kwargs).to_undirected(as_view=True)
|
||||
return MemgraphDiGraph(incoming_graph_data=incoming_graph_data,
|
||||
ctx=ctx, **kwargs).to_undirected(as_view=True)
|
||||
|
||||
|
||||
class PropertiesDictionary(collections.abc.Mapping):
|
||||
__slots__ = ("_ctx", "_prop", "_len")
|
||||
__slots__ = ('_ctx', '_prop', '_len')
|
||||
|
||||
def __init__(self, ctx, prop):
|
||||
self._ctx = ctx
|
||||
@@ -264,7 +270,8 @@ class PropertiesDictionary(collections.abc.Mapping):
|
||||
try:
|
||||
return vertex.properties[self._prop]
|
||||
except KeyError:
|
||||
raise KeyError(("{} doesn\t have the required " + "property '{}'").format(vertex, self._prop))
|
||||
raise KeyError(("{} doesn\t have the required " +
|
||||
"property '{}'").format(vertex, self._prop))
|
||||
|
||||
def __iter__(self):
|
||||
for v in self._ctx.graph.vertices:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,20 +1,23 @@
|
||||
import sys
|
||||
import mgp
|
||||
|
||||
try:
|
||||
import networkx as nx
|
||||
except ImportError as import_error:
|
||||
sys.stderr.write(
|
||||
"\n" "NOTE: Please install networkx to be able to use wcc module.\n" "Using Python:\n" + sys.version + "\n"
|
||||
)
|
||||
'\n'
|
||||
'NOTE: Please install networkx to be able to use wcc module.\n'
|
||||
'Using Python:\n'
|
||||
+ sys.version +
|
||||
'\n')
|
||||
raise import_error
|
||||
|
||||
|
||||
@mgp.read_proc
|
||||
def get_components(
|
||||
vertices: mgp.List[mgp.Vertex], edges: mgp.List[mgp.Edge]
|
||||
) -> mgp.Record(n_components=int, components=mgp.List[mgp.List[mgp.Vertex]]):
|
||||
"""
|
||||
def get_components(vertices: mgp.List[mgp.Vertex],
|
||||
edges: mgp.List[mgp.Edge]
|
||||
) -> mgp.Record(n_components=int,
|
||||
components=mgp.List[mgp.List[mgp.Vertex]]):
|
||||
'''
|
||||
This procedure finds weakly connected components of a given subgraph of a
|
||||
directed graph.
|
||||
|
||||
@@ -38,7 +41,7 @@ def get_components(
|
||||
WITH collect(n) AS nodes, collect(e) AS edges
|
||||
CALL wcc.get_components(nodes, edges) YIELD *
|
||||
RETURN n_components, components;
|
||||
"""
|
||||
'''
|
||||
g = nx.DiGraph()
|
||||
g.add_nodes_from(vertices)
|
||||
g.add_edges_from([(edge.from_vertex, edge.to_vertex) for edge in edges])
|
||||
|
||||
@@ -104,9 +104,7 @@ def retry(retry_limit, timeout=100):
|
||||
except Exception:
|
||||
time.sleep(timeout)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return inner_func
|
||||
|
||||
|
||||
@@ -165,15 +163,8 @@ def format_version(variant, version, offering, distance=None, shorthash=None, su
|
||||
|
||||
# Parse arguments.
|
||||
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("--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(
|
||||
"--variant",
|
||||
@@ -182,9 +173,7 @@ parser.add_argument(
|
||||
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()
|
||||
|
||||
@@ -267,27 +256,14 @@ for version in versions:
|
||||
if current_version is None:
|
||||
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="")
|
||||
else:
|
||||
print(
|
||||
format_version(
|
||||
args.variant,
|
||||
version_str,
|
||||
offering,
|
||||
distance=distance,
|
||||
shorthash=current_hash_short,
|
||||
suffix=args.suffix,
|
||||
args.variant, version_str, offering, distance=distance, shorthash=current_hash_short, suffix=args.suffix
|
||||
),
|
||||
end="",
|
||||
)
|
||||
|
||||
4
release/mgp/.gitignore
vendored
Normal file
4
release/mgp/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.venv
|
||||
dist
|
||||
mgp.py
|
||||
poetry.lock
|
||||
201
release/mgp/LICENSE
Normal file
201
release/mgp/LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
11
release/mgp/README.md
Normal file
11
release/mgp/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# mgp
|
||||
|
||||
PyPi package used for type hinting when creating query modules. Repository of already available query modules is called [MAGE](https://github.com/memgraph/mage).
|
||||
|
||||
## 🎬 Get started
|
||||
|
||||
To learn more, head over to the [docs for the query modules Python API](https://memgraph.com/docs/memgraph/reference-guide/query-modules/api/python-api). To get started with query modules, check out the [how-to guide](https://memgraph.com/docs/memgraph/how-to-guides/query-modules) on Memgraph docs.
|
||||
|
||||
## 🔢 Versioning
|
||||
|
||||
- mgp v1.1 is compatible with Memgraph >= 2.4.0
|
||||
267
release/mgp/_mgp.py
Normal file
267
release/mgp/_mgp.py
Normal file
@@ -0,0 +1,267 @@
|
||||
from typing import Any
|
||||
|
||||
|
||||
class MgpIterable:
|
||||
def get() -> Any:
|
||||
pass
|
||||
|
||||
def next() -> Any:
|
||||
pass
|
||||
|
||||
|
||||
class Vertex:
|
||||
def is_valid() -> bool: # type: ignore
|
||||
pass
|
||||
|
||||
def underlying_graph_is_mutable() -> bool: # type: ignore
|
||||
pass
|
||||
|
||||
def iter_properties() -> MgpIterable: # type: ignore
|
||||
pass
|
||||
|
||||
def get_property(self, property_name: str) -> "Property": # type: ignore
|
||||
pass
|
||||
|
||||
def set_property(self, property_name: str, value: Any) -> "Property": # type: ignore
|
||||
pass
|
||||
|
||||
def get_id() -> "VertexId": # type: ignore
|
||||
pass
|
||||
|
||||
def label_at(self, index: int) -> "Label": # type: ignore
|
||||
pass
|
||||
|
||||
def labels_count() -> int: # type: ignore
|
||||
pass
|
||||
|
||||
def add_label(self, label: Any):
|
||||
pass
|
||||
|
||||
def remove_label(self, label: Any):
|
||||
pass
|
||||
|
||||
def iter_in_edges() -> MgpIterable: # type: ignore
|
||||
pass
|
||||
|
||||
def iter_out_edges() -> MgpIterable: # type: ignore
|
||||
pass
|
||||
|
||||
|
||||
class Edge:
|
||||
def is_valid() -> bool: # type: ignore
|
||||
pass
|
||||
|
||||
def underlying_graph_is_mutable() -> bool: # type: ignore
|
||||
pass
|
||||
|
||||
def iter_properties() -> MgpIterable: # type: ignore
|
||||
pass
|
||||
|
||||
def get_property(self, property_name: str) -> "Property": # type: ignore
|
||||
pass
|
||||
|
||||
def set_property(self, property_name: str, valuse: Any) -> "Property": # type: ignore
|
||||
pass
|
||||
|
||||
def get_type_name() -> str: # type: ignore
|
||||
pass
|
||||
|
||||
def get_id() -> "EdgeId": # type: ignore
|
||||
pass
|
||||
|
||||
def from_vertex() -> Vertex: # type: ignore
|
||||
pass
|
||||
|
||||
def to_vertex() -> Vertex: # type: ignore
|
||||
pass
|
||||
|
||||
|
||||
class Path:
|
||||
def is_valid() -> bool: # type: ignore
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def make_with_start(vertex: Vertex) -> "Path": # type: ignore
|
||||
pass
|
||||
|
||||
|
||||
class Graph:
|
||||
def is_valid() -> bool: # type: ignore
|
||||
pass
|
||||
|
||||
|
||||
class CypherType:
|
||||
pass
|
||||
|
||||
|
||||
class Message:
|
||||
def is_valid() -> bool: # type: ignore
|
||||
pass
|
||||
|
||||
def source_type() -> str: # type: ignore
|
||||
pass
|
||||
|
||||
def topic_name() -> str: # type: ignore
|
||||
pass
|
||||
|
||||
def key() -> bytes: # type: ignore
|
||||
pass
|
||||
|
||||
def timestamp() -> int: # type: ignore
|
||||
pass
|
||||
|
||||
def offset() -> int: # type: ignore
|
||||
pass
|
||||
|
||||
def payload() -> bytes: # type: ignore
|
||||
pass
|
||||
|
||||
|
||||
class Messages:
|
||||
def is_valid() -> bool: # type: ignore
|
||||
pass
|
||||
|
||||
def message_at(self, id: int) -> Message: # type: ignore
|
||||
pass
|
||||
|
||||
def total_messages() -> int: # type: ignore
|
||||
pass
|
||||
|
||||
|
||||
class UnknownError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnableToAllocateError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InsufficientBufferError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class OutOfRangeError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class LogicErrorError(Exception):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DeletedObjectError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidArgumentError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class KeyAlreadyExistsError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ImmutableObjectError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ValueConversionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SerializationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AuthorizationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def type_nullable(elem: Any):
|
||||
pass
|
||||
|
||||
|
||||
def type_list(elem: Any):
|
||||
pass
|
||||
|
||||
|
||||
def type_bool():
|
||||
pass
|
||||
|
||||
|
||||
def type_string():
|
||||
pass
|
||||
|
||||
|
||||
def type_int():
|
||||
pass
|
||||
|
||||
|
||||
def type_float():
|
||||
pass
|
||||
|
||||
|
||||
def type_number():
|
||||
pass
|
||||
|
||||
|
||||
def type_map():
|
||||
pass
|
||||
|
||||
|
||||
def type_node():
|
||||
pass
|
||||
|
||||
|
||||
def type_relationship():
|
||||
pass
|
||||
|
||||
|
||||
def type_path():
|
||||
pass
|
||||
|
||||
|
||||
def type_date():
|
||||
pass
|
||||
|
||||
|
||||
def type_local_time():
|
||||
pass
|
||||
|
||||
|
||||
def type_local_date_time():
|
||||
pass
|
||||
|
||||
|
||||
def type_duration():
|
||||
pass
|
||||
|
||||
|
||||
def type_any():
|
||||
pass
|
||||
|
||||
|
||||
class _MODULE:
|
||||
@staticmethod
|
||||
def add_read_procedure(wrapper):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def add_write_procedure(wrapper):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def add_transformation(wrapper):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def add_function(wrapper):
|
||||
pass
|
||||
|
||||
|
||||
class SOURCE_TYPE_KAFKA:
|
||||
pass
|
||||
|
||||
|
||||
class SOURCE_TYPE_PULSAR:
|
||||
pass
|
||||
22
release/mgp/instructions.md
Normal file
22
release/mgp/instructions.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# How to publish new versions
|
||||
## Prerequisites
|
||||
1. Installed poetry
|
||||
```
|
||||
pip install poetry
|
||||
```
|
||||
2. Set up [API tokens](https://pypi.org/help/#apitoken)
|
||||
3. Be a collaborator on [pypi](https://pypi.org/project/mgp/)
|
||||
|
||||
## Making changes
|
||||
1. Make changes to the package
|
||||
2. Bump version in `pyproject.tml`
|
||||
3. `poetry build`
|
||||
4. `poetry publish`
|
||||
|
||||
## Why is this not automatized?
|
||||
|
||||
Because someone always has to manually bump up the version in `pyproject.toml`
|
||||
|
||||
## Why does `_mgp.py` exists?
|
||||
Because we are mocking here all the types that are created by Memgraph
|
||||
in order to fix typing errors in `mgp.py`.
|
||||
23
release/mgp/pyproject.toml
Normal file
23
release/mgp/pyproject.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
[tool.poetry]
|
||||
name = "mgp"
|
||||
version = "1.1.1"
|
||||
description = "Memgraph's module for developing MAGE modules. Used only for type hinting!"
|
||||
authors = [
|
||||
"katarinasupe <katarina.supe@memgraph.io>",
|
||||
"jbajic <jure.bajic@memgraph.io>",
|
||||
"antejavor <ante.javor@memgraph.io>",
|
||||
"antaljanosbenjamin <benjamin.antal@memgraph.io>",
|
||||
"MasterMedo <mislav.vuletic@gmail.com>",
|
||||
]
|
||||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
include = ["mgp.py", "_mgp.py"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.7"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
@@ -72,6 +72,8 @@ make_package () {
|
||||
docker exec "$build_container" bash -c "/memgraph/environment/os/$os.sh install MEMGRAPH_BUILD_DEPS"
|
||||
|
||||
echo "Building targeted package..."
|
||||
# Fix issue with git marking directory as not safe
|
||||
docker exec "$build_container" bash -c "cd /memgraph && git config --global --add safe.directory '*'"
|
||||
docker exec "$build_container" bash -c "cd /memgraph && $ACTIVATE_TOOLCHAIN && ./init"
|
||||
docker exec "$build_container" bash -c "cd $container_build_dir && rm -rf ./*"
|
||||
if [[ "$os" == "debian-11-arm" ]]; then
|
||||
|
||||
@@ -12,12 +12,14 @@ add_subdirectory(memory)
|
||||
add_subdirectory(storage/v2)
|
||||
add_subdirectory(integrations)
|
||||
add_subdirectory(query)
|
||||
add_subdirectory(glue)
|
||||
add_subdirectory(slk)
|
||||
add_subdirectory(rpc)
|
||||
add_subdirectory(license)
|
||||
add_subdirectory(auth)
|
||||
|
||||
if (MG_ENTERPRISE)
|
||||
add_subdirectory(audit)
|
||||
if(MG_ENTERPRISE)
|
||||
add_subdirectory(audit)
|
||||
endif()
|
||||
|
||||
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
|
||||
@@ -31,80 +33,94 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
# Memgraph Single Node v2 Executable
|
||||
# ----------------------------------------------------------------------------
|
||||
set(mg_single_node_v2_sources
|
||||
glue/communication.cpp
|
||||
memgraph.cpp
|
||||
glue/auth.cpp
|
||||
memgraph.cpp
|
||||
)
|
||||
|
||||
set(mg_single_node_v2_libs stdc++fs Threads::Threads
|
||||
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)
|
||||
mg-telemetry mg-query mg-communication mg-memory mg-utils mg-auth mg-license mg-settings mg-glue)
|
||||
|
||||
if(MG_ENTERPRISE)
|
||||
# These are enterprise subsystems
|
||||
set(mg_single_node_v2_libs ${mg_single_node_v2_libs} mg-audit)
|
||||
endif()
|
||||
|
||||
# memgraph main executable
|
||||
add_executable(memgraph ${mg_single_node_v2_sources})
|
||||
target_include_directories(memgraph PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||
target_link_libraries(memgraph ${mg_single_node_v2_libs})
|
||||
|
||||
# NOTE: `include/mg_procedure.syms` describes a pattern match for symbols which
|
||||
# should be dynamically exported, so that `dlopen` can correctly link the
|
||||
# symbols in custom procedure module libraries.
|
||||
target_link_libraries(memgraph "-Wl,--dynamic-list=${CMAKE_SOURCE_DIR}/include/mg_procedure.syms")
|
||||
target_link_libraries(memgraph "-Wl,--dynamic-list=${CMAKE_SOURCE_DIR}/include/mg_procedure.syms" "-lprofiler")
|
||||
set_target_properties(memgraph PROPERTIES
|
||||
# Set the executable output name to include version information.
|
||||
OUTPUT_NAME "memgraph-${MEMGRAPH_VERSION}_${CMAKE_BUILD_TYPE}"
|
||||
# Output the executable in main binary dir.
|
||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
# Set the executable output name to include version information.
|
||||
OUTPUT_NAME "memgraph-${MEMGRAPH_VERSION}_${CMAKE_BUILD_TYPE}"
|
||||
|
||||
# Output the executable in main binary dir.
|
||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
# Create symlink to the built executable.
|
||||
add_custom_command(TARGET memgraph POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink $<TARGET_FILE:memgraph> ${CMAKE_BINARY_DIR}/memgraph
|
||||
BYPRODUCTS ${CMAKE_BINARY_DIR}/memgraph
|
||||
COMMENT "Creating symlink to memgraph executable")
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink $<TARGET_FILE:memgraph> ${CMAKE_BINARY_DIR}/memgraph
|
||||
BYPRODUCTS ${CMAKE_BINARY_DIR}/memgraph
|
||||
COMMENT "Creating symlink to memgraph executable")
|
||||
|
||||
# Emulate the installed python_support, by creating a symlink
|
||||
add_custom_command(TARGET memgraph POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_SOURCE_DIR}/include ${CMAKE_BINARY_DIR}/python_support
|
||||
BYPRODUCTS ${CMAKE_BINARY_DIR}/python_support
|
||||
COMMENT "Creating symlink for python_support")
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_SOURCE_DIR}/include ${CMAKE_BINARY_DIR}/python_support
|
||||
BYPRODUCTS ${CMAKE_BINARY_DIR}/python_support
|
||||
COMMENT "Creating symlink for python_support")
|
||||
|
||||
# Strip the executable in release build.
|
||||
if (lower_build_type STREQUAL "release")
|
||||
add_custom_command(TARGET memgraph POST_BUILD
|
||||
COMMAND strip -s $<TARGET_FILE:memgraph>
|
||||
COMMENT "Stripping symbols and sections from memgraph")
|
||||
if(lower_build_type STREQUAL "release")
|
||||
add_custom_command(TARGET memgraph POST_BUILD
|
||||
COMMAND strip -s $<TARGET_FILE:memgraph>
|
||||
COMMENT "Stripping symbols and sections from memgraph")
|
||||
endif()
|
||||
|
||||
# Generate the configuration file.
|
||||
add_custom_command(TARGET memgraph POST_BUILD
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/config/generate.py
|
||||
${CMAKE_BINARY_DIR}/memgraph
|
||||
${CMAKE_BINARY_DIR}/config/memgraph.conf
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/config/generate.py
|
||||
${CMAKE_SOURCE_DIR}/config/flags.yaml
|
||||
BYPRODUCTS ${CMAKE_BINARY_DIR}/config/memgraph.conf
|
||||
COMMENT "Generating memgraph configuration file")
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/config/generate.py
|
||||
${CMAKE_BINARY_DIR}/memgraph
|
||||
${CMAKE_BINARY_DIR}/config/memgraph.conf
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/config/generate.py
|
||||
${CMAKE_SOURCE_DIR}/config/flags.yaml
|
||||
BYPRODUCTS ${CMAKE_BINARY_DIR}/config/memgraph.conf
|
||||
COMMENT "Generating memgraph configuration file")
|
||||
|
||||
# Everything here is under "memgraph" install component.
|
||||
set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME "memgraph")
|
||||
|
||||
# TODO: Default directory permissions to 755
|
||||
# NOTE: This is added in CMake 3.11, so enable it then
|
||||
#set(CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS
|
||||
# OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ WORLD_READ)
|
||||
# set(CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS
|
||||
# OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ WORLD_READ)
|
||||
|
||||
# Install and rename executable to just 'memgraph' Since we have to rename,
|
||||
# we cannot use the recommended `install(TARGETS ...)`.
|
||||
install(PROGRAMS $<TARGET_FILE:memgraph>
|
||||
DESTINATION lib/memgraph RENAME memgraph)
|
||||
|
||||
# Install Python source for supporting our embedded Python.
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/include/mgp.py
|
||||
DESTINATION lib/memgraph/python_support)
|
||||
# Install the include file for writing custom procedures.
|
||||
|
||||
# Install the includes file for writing custom procedures in C and C++>
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/include/mg_procedure.h
|
||||
DESTINATION include/memgraph)
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/include/_mgp.hpp
|
||||
DESTINATION include/memgraph)
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/include/mg_exceptions.hpp
|
||||
DESTINATION include/memgraph)
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/include/mgp.hpp
|
||||
DESTINATION include/memgraph)
|
||||
|
||||
# Install the config file (must use absolute path).
|
||||
install(FILES ${CMAKE_BINARY_DIR}/config/memgraph.conf
|
||||
DESTINATION /etc/memgraph RENAME memgraph.conf)
|
||||
|
||||
# Install logrotate configuration (must use absolute path).
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/release/logrotate.conf
|
||||
DESTINATION /etc/logrotate.d RENAME memgraph)
|
||||
@@ -120,15 +136,14 @@ install(CODE "file(MAKE_DIRECTORY \$ENV{DESTDIR}/var/log/memgraph
|
||||
# ----------------------------------------------------------------------------
|
||||
# Memgraph CSV Import Tool Executable
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
add_executable(mg_import_csv mg_import_csv.cpp)
|
||||
target_link_libraries(mg_import_csv mg-storage-v2)
|
||||
|
||||
# Strip the executable in release build.
|
||||
if (lower_build_type STREQUAL "release")
|
||||
add_custom_command(TARGET mg_import_csv POST_BUILD
|
||||
COMMAND strip -s mg_import_csv
|
||||
COMMENT "Stripping symbols and sections from mg_import_csv")
|
||||
if(lower_build_type STREQUAL "release")
|
||||
add_custom_command(TARGET mg_import_csv POST_BUILD
|
||||
COMMAND strip -s mg_import_csv
|
||||
COMMENT "Stripping symbols and sections from mg_import_csv")
|
||||
endif()
|
||||
|
||||
install(TARGETS mg_import_csv RUNTIME DESTINATION bin)
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "auth/exceptions.hpp"
|
||||
#include "license/license.hpp"
|
||||
#include "utils/flag_validation.hpp"
|
||||
#include "utils/license.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/message.hpp"
|
||||
#include "utils/settings.hpp"
|
||||
@@ -68,10 +68,9 @@ Auth::Auth(const std::string &storage_directory) : storage_(storage_directory),
|
||||
|
||||
std::optional<User> Auth::Authenticate(const std::string &username, const std::string &password) {
|
||||
if (module_.IsUsed()) {
|
||||
const auto license_check_result = utils::license::global_license_checker.IsValidLicense(utils::global_settings);
|
||||
const auto license_check_result = license::global_license_checker.IsEnterpriseValid(utils::global_settings);
|
||||
if (license_check_result.HasError()) {
|
||||
spdlog::warn(
|
||||
utils::license::LicenseCheckErrorToString(license_check_result.GetError(), "authentication modules"));
|
||||
spdlog::warn(license::LicenseCheckErrorToString(license_check_result.GetError(), "authentication modules"));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -226,7 +225,7 @@ std::vector<auth::User> Auth::AllUsers() const {
|
||||
if (username != utils::ToLowerCase(username)) continue;
|
||||
auto user = GetUser(username);
|
||||
if (user) {
|
||||
ret.push_back(*user);
|
||||
ret.push_back(std::move(*user));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
@@ -306,7 +305,7 @@ std::vector<auth::User> Auth::AllUsersForRole(const std::string &rolename_orig)
|
||||
if (it->second == rolename) {
|
||||
auto user = GetUser(username);
|
||||
if (user) {
|
||||
ret.push_back(*user);
|
||||
ret.push_back(std::move(*user));
|
||||
} else {
|
||||
throw AuthException("Couldn't load user '{}'!", username);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
namespace memgraph::auth {
|
||||
/**
|
||||
* This class serves as the main Authentication/Authorization storage.
|
||||
* It provides functions for managing Users, Roles and Permissions.
|
||||
* It provides functions for managing Users, Roles, Permissions and FineGrainedAccessPermissions.
|
||||
* NOTE: The non-const functions in this class aren't thread safe.
|
||||
* TODO (mferencevic): Disable user/role modification functions when they are
|
||||
* being managed by the auth module.
|
||||
|
||||
@@ -8,14 +8,16 @@
|
||||
|
||||
#include "auth/models.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <regex>
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "auth/crypto.hpp"
|
||||
#include "auth/exceptions.hpp"
|
||||
#include "license/license.hpp"
|
||||
#include "utils/cast.hpp"
|
||||
#include "utils/license.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/settings.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
@@ -30,6 +32,7 @@ DEFINE_string(auth_password_strength_regex, default_password_regex.data(),
|
||||
|
||||
namespace memgraph::auth {
|
||||
namespace {
|
||||
|
||||
// Constant list of all available permissions.
|
||||
const std::vector<Permission> kPermissionsAll = {
|
||||
Permission::MATCH, Permission::CREATE, Permission::MERGE, Permission::DELETE,
|
||||
@@ -84,8 +87,6 @@ std::string PermissionToString(Permission permission) {
|
||||
return "MODULE_WRITE";
|
||||
case Permission::WEBSOCKET:
|
||||
return "WEBSOCKET";
|
||||
case Permission::LABELS:
|
||||
return "LABELS";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +101,55 @@ std::string PermissionLevelToString(PermissionLevel level) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
FineGrainedPermission PermissionToFineGrainedPermission(const uint64_t permission) {
|
||||
if (permission & FineGrainedPermission::CREATE_DELETE) {
|
||||
return FineGrainedPermission::CREATE_DELETE;
|
||||
}
|
||||
|
||||
if (permission & FineGrainedPermission::UPDATE) {
|
||||
return FineGrainedPermission::UPDATE;
|
||||
}
|
||||
|
||||
if (permission & FineGrainedPermission::READ) {
|
||||
return FineGrainedPermission::READ;
|
||||
}
|
||||
|
||||
return FineGrainedPermission::NOTHING;
|
||||
}
|
||||
|
||||
std::string FineGrainedPermissionToString(const FineGrainedPermission level) {
|
||||
switch (level) {
|
||||
case FineGrainedPermission::CREATE_DELETE:
|
||||
return "CREATE_DELETE";
|
||||
case FineGrainedPermission::UPDATE:
|
||||
return "UPDATE";
|
||||
case FineGrainedPermission::READ:
|
||||
return "READ";
|
||||
case FineGrainedPermission::NOTHING:
|
||||
return "NOTHING";
|
||||
}
|
||||
}
|
||||
|
||||
FineGrainedAccessPermissions Merge(const FineGrainedAccessPermissions &first,
|
||||
const FineGrainedAccessPermissions &second) {
|
||||
std::unordered_map<std::string, uint64_t> permissions{first.GetPermissions()};
|
||||
std::optional<uint64_t> global_permission;
|
||||
|
||||
if (second.GetGlobalPermission().has_value()) {
|
||||
global_permission = *second.GetGlobalPermission();
|
||||
} else if (first.GetGlobalPermission().has_value()) {
|
||||
global_permission = *first.GetGlobalPermission();
|
||||
}
|
||||
|
||||
for (const auto &[label_name, permission] : second.GetPermissions()) {
|
||||
permissions[label_name] = permission;
|
||||
}
|
||||
|
||||
return FineGrainedAccessPermissions(permissions, global_permission);
|
||||
}
|
||||
#endif
|
||||
|
||||
Permissions::Permissions(uint64_t grants, uint64_t denies) {
|
||||
// The deny bitmask has higher priority than the grant bitmask.
|
||||
denies_ = denies;
|
||||
@@ -173,7 +223,7 @@ Permissions Permissions::Deserialize(const nlohmann::json &data) {
|
||||
if (!data["grants"].is_number_unsigned() || !data["denies"].is_number_unsigned()) {
|
||||
throw AuthException("Couldn't load permissions data!");
|
||||
}
|
||||
return {data["grants"], data["denies"]};
|
||||
return Permissions{data["grants"], data["denies"]};
|
||||
}
|
||||
|
||||
uint64_t Permissions::grants() const { return grants_; }
|
||||
@@ -185,107 +235,191 @@ bool operator==(const Permissions &first, const Permissions &second) {
|
||||
|
||||
bool operator!=(const Permissions &first, const Permissions &second) { return !(first == second); }
|
||||
|
||||
LabelPermissions::LabelPermissions(const std::unordered_set<std::string> &grants,
|
||||
const std::unordered_set<std::string> &denies)
|
||||
: grants_(grants), denies_(denies) {}
|
||||
#ifdef MG_ENTERPRISE
|
||||
FineGrainedAccessPermissions::FineGrainedAccessPermissions(const std::unordered_map<std::string, uint64_t> &permissions,
|
||||
const std::optional<uint64_t> &global_permission)
|
||||
: permissions_(permissions), global_permission_(global_permission) {}
|
||||
|
||||
PermissionLevel LabelPermissions::Has(const std::string &permission) const {
|
||||
if (denies_.find(permission) != denies_.end()) {
|
||||
return PermissionLevel::DENY;
|
||||
}
|
||||
|
||||
if (grants_.find(permission) != denies_.end()) {
|
||||
PermissionLevel FineGrainedAccessPermissions::Has(const std::string &permission,
|
||||
const FineGrainedPermission fine_grained_permission) const {
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return PermissionLevel::GRANT;
|
||||
}
|
||||
const auto concrete_permission = std::invoke([&]() -> uint64_t {
|
||||
if (permissions_.contains(permission)) {
|
||||
return permissions_.at(permission);
|
||||
}
|
||||
|
||||
return PermissionLevel::NEUTRAL;
|
||||
if (global_permission_.has_value()) {
|
||||
return global_permission_.value();
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
const auto temp_permission = concrete_permission & fine_grained_permission;
|
||||
|
||||
return temp_permission > 0 ? PermissionLevel::GRANT : PermissionLevel::DENY;
|
||||
}
|
||||
|
||||
void LabelPermissions::Grant(const std::string &permission) {
|
||||
auto deniedPermissionIter = denies_.find(permission);
|
||||
|
||||
if (deniedPermissionIter != denies_.end()) {
|
||||
denies_.erase(deniedPermissionIter);
|
||||
}
|
||||
|
||||
if (grants_.find(permission) == grants_.end()) {
|
||||
grants_.insert(permission);
|
||||
void FineGrainedAccessPermissions::Grant(const std::string &permission,
|
||||
const FineGrainedPermission fine_grained_permission) {
|
||||
if (permission == kAsterisk) {
|
||||
global_permission_ = CalculateGrant(fine_grained_permission);
|
||||
} else {
|
||||
permissions_[permission] = CalculateGrant(fine_grained_permission);
|
||||
}
|
||||
}
|
||||
|
||||
void LabelPermissions::Revoke(const std::string &permission) {
|
||||
auto deniedPermissionIter = denies_.find(permission);
|
||||
auto grantedPermissionIter = grants_.find(permission);
|
||||
|
||||
if (deniedPermissionIter != denies_.end()) {
|
||||
denies_.erase(deniedPermissionIter);
|
||||
}
|
||||
|
||||
if (grantedPermissionIter != grants_.end()) {
|
||||
grants_.erase(grantedPermissionIter);
|
||||
void FineGrainedAccessPermissions::Revoke(const std::string &permission) {
|
||||
if (permission == kAsterisk) {
|
||||
permissions_.clear();
|
||||
global_permission_ = std::nullopt;
|
||||
} else {
|
||||
permissions_.erase(permission);
|
||||
}
|
||||
}
|
||||
|
||||
void LabelPermissions::Deny(const std::string &permission) {
|
||||
auto grantedPermissionIter = grants_.find(permission);
|
||||
|
||||
if (grantedPermissionIter != grants_.end()) {
|
||||
grants_.erase(grantedPermissionIter);
|
||||
nlohmann::json FineGrainedAccessPermissions::Serialize() const {
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (denies_.find(permission) == denies_.end()) {
|
||||
denies_.insert(permission);
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> LabelPermissions::GetGrants() const { return grants_; }
|
||||
|
||||
std::unordered_set<std::string> LabelPermissions::GetDenies() const { return denies_; }
|
||||
|
||||
nlohmann::json LabelPermissions::Serialize() const {
|
||||
nlohmann::json data = nlohmann::json::object();
|
||||
data["grants"] = grants_;
|
||||
data["denies"] = denies_;
|
||||
data["permissions"] = permissions_;
|
||||
data["global_permission"] = global_permission_.has_value() ? global_permission_.value() : -1;
|
||||
return data;
|
||||
}
|
||||
|
||||
LabelPermissions LabelPermissions::Deserialize(const nlohmann::json &data) {
|
||||
FineGrainedAccessPermissions FineGrainedAccessPermissions::Deserialize(const nlohmann::json &data) {
|
||||
if (!data.is_object()) {
|
||||
throw AuthException("Couldn't load permissions data!");
|
||||
}
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return FineGrainedAccessPermissions{};
|
||||
}
|
||||
std::optional<uint64_t> global_permission;
|
||||
|
||||
return {LabelPermissions(data["grants"], data["denies"])};
|
||||
if (data["global_permission"].empty() || data["global_permission"] == -1) {
|
||||
global_permission = std::nullopt;
|
||||
} else {
|
||||
global_permission = data["global_permission"];
|
||||
}
|
||||
|
||||
return FineGrainedAccessPermissions(data["permissions"], global_permission);
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> LabelPermissions::grants() const { return grants_; }
|
||||
std::unordered_set<std::string> LabelPermissions::denies() const { return denies_; }
|
||||
const std::unordered_map<std::string, uint64_t> &FineGrainedAccessPermissions::GetPermissions() const {
|
||||
return permissions_;
|
||||
}
|
||||
const std::optional<uint64_t> &FineGrainedAccessPermissions::GetGlobalPermission() const { return global_permission_; };
|
||||
|
||||
bool operator==(const LabelPermissions &first, const LabelPermissions &second) {
|
||||
return first.grants() == second.grants() && first.denies() == second.denies();
|
||||
uint64_t FineGrainedAccessPermissions::CalculateGrant(FineGrainedPermission fine_grained_permission) {
|
||||
uint64_t shift{1};
|
||||
uint64_t result{0};
|
||||
auto uint_fine_grained_permission = static_cast<uint64_t>(fine_grained_permission);
|
||||
while (uint_fine_grained_permission > 0) {
|
||||
result |= uint_fine_grained_permission;
|
||||
uint_fine_grained_permission >>= shift;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool operator!=(const LabelPermissions &first, const LabelPermissions &second) { return !(first == second); }
|
||||
bool operator==(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second) {
|
||||
return first.GetPermissions() == second.GetPermissions() &&
|
||||
first.GetGlobalPermission() == second.GetGlobalPermission();
|
||||
}
|
||||
|
||||
bool operator!=(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second) {
|
||||
return !(first == second);
|
||||
}
|
||||
|
||||
FineGrainedAccessHandler::FineGrainedAccessHandler(FineGrainedAccessPermissions labelPermissions,
|
||||
FineGrainedAccessPermissions edgeTypePermissions)
|
||||
: label_permissions_(std::move(labelPermissions)), edge_type_permissions_(std::move(edgeTypePermissions)) {}
|
||||
|
||||
const FineGrainedAccessPermissions &FineGrainedAccessHandler::label_permissions() const { return label_permissions_; }
|
||||
FineGrainedAccessPermissions &FineGrainedAccessHandler::label_permissions() { return label_permissions_; }
|
||||
|
||||
const FineGrainedAccessPermissions &FineGrainedAccessHandler::edge_type_permissions() const {
|
||||
return edge_type_permissions_;
|
||||
}
|
||||
FineGrainedAccessPermissions &FineGrainedAccessHandler::edge_type_permissions() { return edge_type_permissions_; }
|
||||
|
||||
nlohmann::json FineGrainedAccessHandler::Serialize() const {
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return {};
|
||||
}
|
||||
nlohmann::json data = nlohmann::json::object();
|
||||
data["label_permissions"] = label_permissions_.Serialize();
|
||||
data["edge_type_permissions"] = edge_type_permissions_.Serialize();
|
||||
return data;
|
||||
}
|
||||
|
||||
FineGrainedAccessHandler FineGrainedAccessHandler::Deserialize(const nlohmann::json &data) {
|
||||
if (!data.is_object()) {
|
||||
throw AuthException("Couldn't load role data!");
|
||||
}
|
||||
if (!data["label_permissions"].is_object() || !data["edge_type_permissions"].is_object()) {
|
||||
throw AuthException("Couldn't load label_permissions or edge_type_permissions data!");
|
||||
}
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return FineGrainedAccessHandler{};
|
||||
}
|
||||
auto label_permissions = FineGrainedAccessPermissions::Deserialize(data["label_permissions"]);
|
||||
auto edge_type_permissions = FineGrainedAccessPermissions::Deserialize(data["edge_type_permissions"]);
|
||||
|
||||
return FineGrainedAccessHandler(std::move(label_permissions), std::move(edge_type_permissions));
|
||||
}
|
||||
|
||||
bool operator==(const FineGrainedAccessHandler &first, const FineGrainedAccessHandler &second) {
|
||||
return first.label_permissions_ == second.label_permissions_ &&
|
||||
first.edge_type_permissions_ == second.edge_type_permissions_;
|
||||
}
|
||||
|
||||
bool operator!=(const FineGrainedAccessHandler &first, const FineGrainedAccessHandler &second) {
|
||||
return !(first == second);
|
||||
}
|
||||
#endif
|
||||
|
||||
Role::Role(const std::string &rolename) : rolename_(utils::ToLowerCase(rolename)) {}
|
||||
|
||||
Role::Role(const std::string &rolename, const Permissions &permissions)
|
||||
: rolename_(utils::ToLowerCase(rolename)), permissions_(permissions) {}
|
||||
|
||||
Role::Role(const std::string &rolename, const Permissions &permissions, const LabelPermissions &labelPermissions)
|
||||
: rolename_(utils::ToLowerCase(rolename)), permissions_(permissions), labelPermissions_(labelPermissions) {}
|
||||
#ifdef MG_ENTERPRISE
|
||||
Role::Role(const std::string &rolename, const Permissions &permissions,
|
||||
FineGrainedAccessHandler fine_grained_access_handler)
|
||||
: rolename_(utils::ToLowerCase(rolename)),
|
||||
permissions_(permissions),
|
||||
fine_grained_access_handler_(std::move(fine_grained_access_handler)) {}
|
||||
#endif
|
||||
|
||||
const std::string &Role::rolename() const { return rolename_; }
|
||||
const Permissions &Role::permissions() const { return permissions_; }
|
||||
Permissions &Role::permissions() { return permissions_; }
|
||||
#ifdef MG_ENTERPRISE
|
||||
const FineGrainedAccessHandler &Role::fine_grained_access_handler() const { return fine_grained_access_handler_; }
|
||||
FineGrainedAccessHandler &Role::fine_grained_access_handler() { return fine_grained_access_handler_; }
|
||||
|
||||
LabelPermissions &Role::labelPermissions() { return labelPermissions_; }
|
||||
const FineGrainedAccessPermissions &Role::GetFineGrainedAccessLabelPermissions() const {
|
||||
return fine_grained_access_handler_.label_permissions();
|
||||
}
|
||||
|
||||
const FineGrainedAccessPermissions &Role::GetFineGrainedAccessEdgeTypePermissions() const {
|
||||
return fine_grained_access_handler_.edge_type_permissions();
|
||||
}
|
||||
#endif
|
||||
|
||||
nlohmann::json Role::Serialize() const {
|
||||
nlohmann::json data = nlohmann::json::object();
|
||||
data["rolename"] = rolename_;
|
||||
data["permissions"] = permissions_.Serialize();
|
||||
data["labelPermissions"] = labelPermissions_.Serialize();
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize();
|
||||
} else {
|
||||
data["fine_grained_access_handler"] = {};
|
||||
}
|
||||
#endif
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -297,26 +431,42 @@ Role Role::Deserialize(const nlohmann::json &data) {
|
||||
throw AuthException("Couldn't load role data!");
|
||||
}
|
||||
auto permissions = Permissions::Deserialize(data["permissions"]);
|
||||
auto labelPermissions = LabelPermissions::Deserialize(data["labelPermissions"]);
|
||||
|
||||
return {data["rolename"], permissions, labelPermissions};
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
if (!data["fine_grained_access_handler"].is_object()) {
|
||||
throw AuthException("Couldn't load user data!");
|
||||
}
|
||||
auto fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]);
|
||||
return {data["rolename"], permissions, std::move(fine_grained_access_handler)};
|
||||
}
|
||||
#endif
|
||||
return {data["rolename"], permissions};
|
||||
}
|
||||
|
||||
bool operator==(const Role &first, const Role &second) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return first.rolename_ == second.rolename_ && first.permissions_ == second.permissions_ &&
|
||||
first.fine_grained_access_handler_ == second.fine_grained_access_handler_;
|
||||
}
|
||||
#endif
|
||||
return first.rolename_ == second.rolename_ && first.permissions_ == second.permissions_;
|
||||
}
|
||||
|
||||
User::User(const std::string &username) : username_(utils::ToLowerCase(username)) {}
|
||||
User::User() {}
|
||||
|
||||
User::User(const std::string &username) : username_(utils::ToLowerCase(username)) {}
|
||||
User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions)
|
||||
: username_(utils::ToLowerCase(username)), password_hash_(password_hash), permissions_(permissions) {}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions,
|
||||
const LabelPermissions &labelPermissions)
|
||||
FineGrainedAccessHandler fine_grained_access_handler)
|
||||
: username_(utils::ToLowerCase(username)),
|
||||
password_hash_(password_hash),
|
||||
permissions_(permissions),
|
||||
labelPermissions_(labelPermissions) {}
|
||||
fine_grained_access_handler_(std::move(fine_grained_access_handler)) {}
|
||||
#endif
|
||||
|
||||
bool User::CheckPassword(const std::string &password) {
|
||||
if (password_hash_.empty()) return true;
|
||||
@@ -333,13 +483,13 @@ void User::UpdatePassword(const std::optional<std::string> &password) {
|
||||
}
|
||||
|
||||
if (FLAGS_auth_password_strength_regex != default_password_regex) {
|
||||
if (const auto license_check_result = utils::license::global_license_checker.IsValidLicense(utils::global_settings);
|
||||
if (const auto license_check_result = license::global_license_checker.IsEnterpriseValid(utils::global_settings);
|
||||
license_check_result.HasError()) {
|
||||
throw AuthException(
|
||||
"Custom password regex is a Memgraph Enterprise feature. Please set the config "
|
||||
"(\"--auth-password-strength-regex\") to its default value (\"{}\") or remove the flag.\n{}",
|
||||
default_password_regex,
|
||||
utils::license::LicenseCheckErrorToString(license_check_result.GetError(), "password regex"));
|
||||
license::LicenseCheckErrorToString(license_check_result.GetError(), "password regex"));
|
||||
}
|
||||
}
|
||||
std::regex re(FLAGS_auth_password_strength_regex);
|
||||
@@ -359,19 +509,47 @@ void User::ClearRole() { role_ = std::nullopt; }
|
||||
|
||||
Permissions User::GetPermissions() const {
|
||||
if (role_) {
|
||||
return Permissions(permissions_.grants() | role_->permissions().grants(),
|
||||
permissions_.denies() | role_->permissions().denies());
|
||||
return Permissions{permissions_.grants() | role_->permissions().grants(),
|
||||
permissions_.denies() | role_->permissions().denies()};
|
||||
}
|
||||
return permissions_;
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
FineGrainedAccessPermissions User::GetFineGrainedAccessLabelPermissions() const {
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return FineGrainedAccessPermissions{};
|
||||
}
|
||||
|
||||
if (role_) {
|
||||
return Merge(role()->fine_grained_access_handler().label_permissions(),
|
||||
fine_grained_access_handler_.label_permissions());
|
||||
}
|
||||
|
||||
return fine_grained_access_handler_.label_permissions();
|
||||
}
|
||||
|
||||
FineGrainedAccessPermissions User::GetFineGrainedAccessEdgeTypePermissions() const {
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return FineGrainedAccessPermissions{};
|
||||
}
|
||||
if (role_) {
|
||||
return Merge(role()->fine_grained_access_handler().edge_type_permissions(),
|
||||
fine_grained_access_handler_.edge_type_permissions());
|
||||
}
|
||||
return fine_grained_access_handler_.edge_type_permissions();
|
||||
}
|
||||
#endif
|
||||
|
||||
const std::string &User::username() const { return username_; }
|
||||
|
||||
const Permissions &User::permissions() const { return permissions_; }
|
||||
Permissions &User::permissions() { return permissions_; }
|
||||
#ifdef MG_ENTERPRISE
|
||||
const FineGrainedAccessHandler &User::fine_grained_access_handler() const { return fine_grained_access_handler_; }
|
||||
|
||||
LabelPermissions &User::labelPermissions() { return labelPermissions_; }
|
||||
|
||||
FineGrainedAccessHandler &User::fine_grained_access_handler() { return fine_grained_access_handler_; }
|
||||
#endif
|
||||
const Role *User::role() const {
|
||||
if (role_.has_value()) {
|
||||
return &role_.value();
|
||||
@@ -384,7 +562,13 @@ nlohmann::json User::Serialize() const {
|
||||
data["username"] = username_;
|
||||
data["password_hash"] = password_hash_;
|
||||
data["permissions"] = permissions_.Serialize();
|
||||
data["labelPermissions"] = labelPermissions_.Serialize();
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize();
|
||||
} else {
|
||||
data["fine_grained_access_handler"] = {};
|
||||
}
|
||||
#endif
|
||||
// The role shouldn't be serialized here, it is stored as a foreign key.
|
||||
return data;
|
||||
}
|
||||
@@ -397,12 +581,26 @@ User User::Deserialize(const nlohmann::json &data) {
|
||||
throw AuthException("Couldn't load user data!");
|
||||
}
|
||||
auto permissions = Permissions::Deserialize(data["permissions"]);
|
||||
auto labelPermissions = LabelPermissions::Deserialize(data["labelPermissions"]);
|
||||
|
||||
return {data["username"], data["password_hash"], permissions, labelPermissions};
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
if (!data["fine_grained_access_handler"].is_object()) {
|
||||
throw AuthException("Couldn't load user data!");
|
||||
}
|
||||
auto fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]);
|
||||
return {data["username"], data["password_hash"], permissions, fine_grained_access_handler};
|
||||
}
|
||||
#endif
|
||||
return {data["username"], data["password_hash"], permissions};
|
||||
}
|
||||
|
||||
bool operator==(const User &first, const User &second) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return first.username_ == second.username_ && first.password_hash_ == second.password_hash_ &&
|
||||
first.permissions_ == second.permissions_ && first.role_ == second.role_ &&
|
||||
first.fine_grained_access_handler_ == second.fine_grained_access_handler_;
|
||||
}
|
||||
#endif
|
||||
return first.username_ == second.username_ && first.password_hash_ == second.password_hash_ &&
|
||||
first.permissions_ == second.permissions_ && first.role_ == second.role_;
|
||||
}
|
||||
|
||||
@@ -10,11 +10,12 @@
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <json/json.hpp>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace memgraph::auth {
|
||||
const std::string kAsterisk = "*";
|
||||
// These permissions must have values that are applicable for usage in a
|
||||
// bitmask.
|
||||
// clang-format off
|
||||
@@ -39,27 +40,65 @@ enum class Permission : uint64_t {
|
||||
STREAM = 1U << 17U,
|
||||
MODULE_READ = 1U << 18U,
|
||||
MODULE_WRITE = 1U << 19U,
|
||||
WEBSOCKET = 1U << 20U,
|
||||
LABELS = 1U << 21U
|
||||
WEBSOCKET = 1U << 20U
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
// clang-format off
|
||||
enum class FineGrainedPermission : uint64_t {
|
||||
NOTHING = 0,
|
||||
READ = 1,
|
||||
UPDATE = 1U << 1U,
|
||||
CREATE_DELETE = 1U << 2U
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
constexpr inline uint64_t operator|(FineGrainedPermission lhs, FineGrainedPermission rhs) {
|
||||
return static_cast<uint64_t>(lhs) | static_cast<uint64_t>(rhs);
|
||||
}
|
||||
|
||||
constexpr inline uint64_t operator|(uint64_t lhs, FineGrainedPermission rhs) {
|
||||
return lhs | static_cast<uint64_t>(rhs);
|
||||
}
|
||||
|
||||
constexpr inline uint64_t operator&(uint64_t lhs, FineGrainedPermission rhs) {
|
||||
return (lhs & static_cast<uint64_t>(rhs)) != 0;
|
||||
}
|
||||
|
||||
constexpr uint64_t kLabelPermissionAll = memgraph::auth::FineGrainedPermission::CREATE_DELETE |
|
||||
memgraph::auth::FineGrainedPermission::UPDATE |
|
||||
memgraph::auth::FineGrainedPermission::READ;
|
||||
constexpr uint64_t kLabelPermissionMax = static_cast<uint64_t>(memgraph::auth::FineGrainedPermission::CREATE_DELETE);
|
||||
constexpr uint64_t kLabelPermissionMin = static_cast<uint64_t>(memgraph::auth::FineGrainedPermission::READ);
|
||||
#endif
|
||||
|
||||
// Function that converts a permission to its string representation.
|
||||
std::string PermissionToString(Permission permission);
|
||||
|
||||
// Class that indicates what permission level the user/role has.
|
||||
enum class PermissionLevel {
|
||||
GRANT,
|
||||
NEUTRAL,
|
||||
DENY,
|
||||
};
|
||||
enum class PermissionLevel : uint8_t { GRANT, NEUTRAL, DENY };
|
||||
|
||||
// Function that converts a permission level to its string representation.
|
||||
std::string PermissionLevelToString(PermissionLevel level);
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
// Function that converts a label permission level to its string representation.
|
||||
std::string FineGrainedPermissionToString(FineGrainedPermission level);
|
||||
|
||||
// Constructs a label permission from a permission
|
||||
FineGrainedPermission PermissionToFineGrainedPermission(uint64_t permission);
|
||||
#endif
|
||||
|
||||
class Permissions final {
|
||||
public:
|
||||
Permissions(uint64_t grants = 0, uint64_t denies = 0);
|
||||
explicit Permissions(uint64_t grants = 0, uint64_t denies = 0);
|
||||
|
||||
Permissions(const Permissions &) = default;
|
||||
Permissions &operator=(const Permissions &) = default;
|
||||
Permissions(Permissions &&) noexcept = default;
|
||||
Permissions &operator=(Permissions &&) noexcept = default;
|
||||
~Permissions() = default;
|
||||
|
||||
PermissionLevel Has(Permission permission) const;
|
||||
|
||||
@@ -90,52 +129,96 @@ bool operator==(const Permissions &first, const Permissions &second);
|
||||
|
||||
bool operator!=(const Permissions &first, const Permissions &second);
|
||||
|
||||
class LabelPermissions final {
|
||||
#ifdef MG_ENTERPRISE
|
||||
class FineGrainedAccessPermissions final {
|
||||
public:
|
||||
LabelPermissions(const std::unordered_set<std::string> &grants = {},
|
||||
const std::unordered_set<std::string> &denies = {});
|
||||
explicit FineGrainedAccessPermissions(const std::unordered_map<std::string, uint64_t> &permissions = {},
|
||||
const std::optional<uint64_t> &global_permission = std::nullopt);
|
||||
FineGrainedAccessPermissions(const FineGrainedAccessPermissions &) = default;
|
||||
FineGrainedAccessPermissions &operator=(const FineGrainedAccessPermissions &) = default;
|
||||
FineGrainedAccessPermissions(FineGrainedAccessPermissions &&) = default;
|
||||
FineGrainedAccessPermissions &operator=(FineGrainedAccessPermissions &&) = default;
|
||||
~FineGrainedAccessPermissions() = default;
|
||||
PermissionLevel Has(const std::string &permission, FineGrainedPermission fine_grained_permission) const;
|
||||
|
||||
PermissionLevel Has(const std::string &permission) const;
|
||||
|
||||
void Grant(const std::string &permission);
|
||||
void Grant(const std::string &permission, FineGrainedPermission fine_grained_permission);
|
||||
|
||||
void Revoke(const std::string &permission);
|
||||
|
||||
void Deny(const std::string &permission);
|
||||
|
||||
std::unordered_set<std::string> GetGrants() const;
|
||||
std::unordered_set<std::string> GetDenies() const;
|
||||
|
||||
nlohmann::json Serialize() const;
|
||||
|
||||
/// @throw AuthException if unable to deserialize.
|
||||
static LabelPermissions Deserialize(const nlohmann::json &data);
|
||||
static FineGrainedAccessPermissions Deserialize(const nlohmann::json &data);
|
||||
|
||||
std::unordered_set<std::string> grants() const;
|
||||
std::unordered_set<std::string> denies() const;
|
||||
const std::unordered_map<std::string, uint64_t> &GetPermissions() const;
|
||||
const std::optional<uint64_t> &GetGlobalPermission() const;
|
||||
|
||||
private:
|
||||
std::unordered_set<std::string> grants_{};
|
||||
std::unordered_set<std::string> denies_{};
|
||||
std::unordered_map<std::string, uint64_t> permissions_{};
|
||||
std::optional<uint64_t> global_permission_;
|
||||
|
||||
static uint64_t CalculateGrant(FineGrainedPermission fine_grained_permission);
|
||||
};
|
||||
|
||||
bool operator==(const LabelPermissions &first, const LabelPermissions &second);
|
||||
bool operator==(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second);
|
||||
|
||||
bool operator!=(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second);
|
||||
|
||||
class FineGrainedAccessHandler final {
|
||||
public:
|
||||
explicit FineGrainedAccessHandler(FineGrainedAccessPermissions labelPermissions = FineGrainedAccessPermissions(),
|
||||
FineGrainedAccessPermissions edgeTypePermissions = FineGrainedAccessPermissions());
|
||||
|
||||
FineGrainedAccessHandler(const FineGrainedAccessHandler &) = default;
|
||||
FineGrainedAccessHandler &operator=(const FineGrainedAccessHandler &) = default;
|
||||
FineGrainedAccessHandler(FineGrainedAccessHandler &&) noexcept = default;
|
||||
FineGrainedAccessHandler &operator=(FineGrainedAccessHandler &&) noexcept = default;
|
||||
~FineGrainedAccessHandler() = default;
|
||||
|
||||
const FineGrainedAccessPermissions &label_permissions() const;
|
||||
FineGrainedAccessPermissions &label_permissions();
|
||||
|
||||
const FineGrainedAccessPermissions &edge_type_permissions() const;
|
||||
FineGrainedAccessPermissions &edge_type_permissions();
|
||||
|
||||
nlohmann::json Serialize() const;
|
||||
|
||||
/// @throw AuthException if unable to deserialize.
|
||||
static FineGrainedAccessHandler Deserialize(const nlohmann::json &data);
|
||||
|
||||
friend bool operator==(const FineGrainedAccessHandler &first, const FineGrainedAccessHandler &second);
|
||||
|
||||
private:
|
||||
FineGrainedAccessPermissions label_permissions_;
|
||||
FineGrainedAccessPermissions edge_type_permissions_;
|
||||
};
|
||||
|
||||
bool operator==(const FineGrainedAccessHandler &first, const FineGrainedAccessHandler &second);
|
||||
#endif
|
||||
|
||||
bool operator!=(const LabelPermissions &first, const LabelPermissions &second);
|
||||
class Role final {
|
||||
public:
|
||||
Role(const std::string &rolename);
|
||||
|
||||
explicit Role(const std::string &rolename);
|
||||
Role(const std::string &rolename, const Permissions &permissions);
|
||||
|
||||
Role(const std::string &rolename, const Permissions &permissions, const LabelPermissions &labelPermissions);
|
||||
#ifdef MG_ENTERPRISE
|
||||
Role(const std::string &rolename, const Permissions &permissions,
|
||||
FineGrainedAccessHandler fine_grained_access_handler);
|
||||
#endif
|
||||
Role(const Role &) = default;
|
||||
Role &operator=(const Role &) = default;
|
||||
Role(Role &&) noexcept = default;
|
||||
Role &operator=(Role &&) noexcept = default;
|
||||
~Role() = default;
|
||||
|
||||
const std::string &rolename() const;
|
||||
const Permissions &permissions() const;
|
||||
Permissions &permissions();
|
||||
|
||||
LabelPermissions &labelPermissions();
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
const FineGrainedAccessHandler &fine_grained_access_handler() const;
|
||||
FineGrainedAccessHandler &fine_grained_access_handler();
|
||||
const FineGrainedAccessPermissions &GetFineGrainedAccessLabelPermissions() const;
|
||||
const FineGrainedAccessPermissions &GetFineGrainedAccessEdgeTypePermissions() const;
|
||||
#endif
|
||||
nlohmann::json Serialize() const;
|
||||
|
||||
/// @throw AuthException if unable to deserialize.
|
||||
@@ -146,7 +229,9 @@ class Role final {
|
||||
private:
|
||||
std::string rolename_;
|
||||
Permissions permissions_;
|
||||
LabelPermissions labelPermissions_;
|
||||
#ifdef MG_ENTERPRISE
|
||||
FineGrainedAccessHandler fine_grained_access_handler_;
|
||||
#endif
|
||||
};
|
||||
|
||||
bool operator==(const Role &first, const Role &second);
|
||||
@@ -154,12 +239,19 @@ bool operator==(const Role &first, const Role &second);
|
||||
// TODO (mferencevic): Implement password expiry.
|
||||
class User final {
|
||||
public:
|
||||
User(const std::string &username);
|
||||
User();
|
||||
|
||||
explicit User(const std::string &username);
|
||||
User(const std::string &username, const std::string &password_hash, const Permissions &permissions);
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
User(const std::string &username, const std::string &password_hash, const Permissions &permissions,
|
||||
const LabelPermissions &labelPermissions);
|
||||
FineGrainedAccessHandler fine_grained_access_handler);
|
||||
#endif
|
||||
User(const User &) = default;
|
||||
User &operator=(const User &) = default;
|
||||
User(User &&) noexcept = default;
|
||||
User &operator=(User &&) noexcept = default;
|
||||
~User() = default;
|
||||
|
||||
/// @throw AuthException if unable to verify the password.
|
||||
bool CheckPassword(const std::string &password);
|
||||
@@ -173,6 +265,12 @@ class User final {
|
||||
|
||||
Permissions GetPermissions() const;
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
FineGrainedAccessPermissions GetFineGrainedAccessLabelPermissions() const;
|
||||
FineGrainedAccessPermissions GetFineGrainedAccessEdgeTypePermissions() const;
|
||||
const FineGrainedAccessHandler &fine_grained_access_handler() const;
|
||||
FineGrainedAccessHandler &fine_grained_access_handler();
|
||||
#endif
|
||||
const std::string &username() const;
|
||||
|
||||
const Permissions &permissions() const;
|
||||
@@ -180,8 +278,6 @@ class User final {
|
||||
|
||||
const Role *role() const;
|
||||
|
||||
LabelPermissions &labelPermissions();
|
||||
|
||||
nlohmann::json Serialize() const;
|
||||
|
||||
/// @throw AuthException if unable to deserialize.
|
||||
@@ -193,10 +289,16 @@ class User final {
|
||||
std::string username_;
|
||||
std::string password_hash_;
|
||||
Permissions permissions_;
|
||||
#ifdef MG_ENTERPRISE
|
||||
FineGrainedAccessHandler fine_grained_access_handler_;
|
||||
#endif
|
||||
std::optional<Role> role_;
|
||||
LabelPermissions labelPermissions_;
|
||||
};
|
||||
|
||||
bool operator==(const User &first, const User &second);
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
FineGrainedAccessPermissions Merge(const FineGrainedAccessPermissions &first,
|
||||
const FineGrainedAccessPermissions &second);
|
||||
#endif
|
||||
} // namespace memgraph::auth
|
||||
|
||||
@@ -18,24 +18,19 @@ roles_config = config["roles"]
|
||||
# Initialize LDAP server.
|
||||
tls = None
|
||||
if server_config["encryption"] != "disabled":
|
||||
cert_file = server_config["cert_file"] if server_config["cert_file"] else None
|
||||
cert_file = server_config["cert_file"] if server_config["cert_file"] \
|
||||
else None
|
||||
key_file = server_config["key_file"] if server_config["key_file"] else None
|
||||
ca_file = server_config["ca_file"] if server_config["ca_file"] else None
|
||||
validate = ssl.CERT_REQUIRED if server_config["validate_cert"] else ssl.CERT_NONE
|
||||
tls = ldap3.Tls(
|
||||
local_private_key_file=key_file,
|
||||
local_certificate_file=cert_file,
|
||||
ca_certs_file=ca_file,
|
||||
validate=validate,
|
||||
)
|
||||
validate = ssl.CERT_REQUIRED if server_config["validate_cert"] \
|
||||
else ssl.CERT_NONE
|
||||
tls = ldap3.Tls(local_private_key_file=key_file,
|
||||
local_certificate_file=cert_file,
|
||||
ca_certs_file=ca_file,
|
||||
validate=validate)
|
||||
use_ssl = server_config["encryption"] == "ssl"
|
||||
server = ldap3.Server(
|
||||
server_config["host"],
|
||||
port=server_config["port"],
|
||||
tls=tls,
|
||||
use_ssl=use_ssl,
|
||||
get_info=ldap3.ALL,
|
||||
)
|
||||
server = ldap3.Server(server_config["host"], port=server_config["port"],
|
||||
tls=tls, use_ssl=use_ssl, get_info=ldap3.ALL)
|
||||
|
||||
|
||||
# Main authentication/authorization function.
|
||||
@@ -45,12 +40,14 @@ def authenticate(username, password):
|
||||
return {"authenticated": False, "role": ""}
|
||||
|
||||
# Create the DN of the user
|
||||
dn = users_config["prefix"] + ldap3.utils.dn.escape_rdn(username) + users_config["suffix"]
|
||||
dn = users_config["prefix"] + ldap3.utils.dn.escape_rdn(username) + \
|
||||
users_config["suffix"]
|
||||
|
||||
# Bind to the server
|
||||
conn = ldap3.Connection(server, dn, password)
|
||||
if server_config["encryption"] == "starttls" and not conn.start_tls():
|
||||
print("ERROR: Couldn't issue STARTTLS to the LDAP server!", file=sys.stderr)
|
||||
print("ERROR: Couldn't issue STARTTLS to the LDAP server!",
|
||||
file=sys.stderr)
|
||||
return {"authenticated": False, "role": ""}
|
||||
if not conn.bind():
|
||||
return {"authenticated": False, "role": ""}
|
||||
@@ -59,32 +56,25 @@ def authenticate(username, password):
|
||||
if roles_config["root_dn"] != "":
|
||||
# search for role
|
||||
search_filter = "(&(objectclass={objclass})({attr}={value}))".format(
|
||||
objclass=roles_config["root_objectclass"],
|
||||
attr=roles_config["user_attribute"],
|
||||
value=ldap3.utils.conv.escape_filter_chars(dn),
|
||||
)
|
||||
succ = conn.search(
|
||||
roles_config["root_dn"],
|
||||
search_filter,
|
||||
search_scope=ldap3.LEVEL,
|
||||
attributes=[roles_config["role_attribute"]],
|
||||
)
|
||||
objclass=roles_config["root_objectclass"],
|
||||
attr=roles_config["user_attribute"],
|
||||
value=ldap3.utils.conv.escape_filter_chars(dn))
|
||||
succ = conn.search(roles_config["root_dn"], search_filter,
|
||||
search_scope=ldap3.LEVEL,
|
||||
attributes=[roles_config["role_attribute"]])
|
||||
if not succ or len(conn.entries) == 0:
|
||||
return {"authenticated": True, "role": ""}
|
||||
if len(conn.entries) > 1:
|
||||
roles = list(map(lambda x: x[roles_config["role_attribute"]].value, conn.entries))
|
||||
roles = list(map(lambda x: x[roles_config["role_attribute"]].value,
|
||||
conn.entries))
|
||||
# Because we don't know exactly which role the user should have
|
||||
# we authorize the user with an empty role.
|
||||
print(
|
||||
"WARNING: Found more than one role for " "user '" + username + "':",
|
||||
", ".join(roles) + "!",
|
||||
file=sys.stderr,
|
||||
)
|
||||
print("WARNING: Found more than one role for "
|
||||
"user '" + username + "':", ", ".join(roles) + "!",
|
||||
file=sys.stderr)
|
||||
return {"authenticated": True, "role": ""}
|
||||
return {
|
||||
"authenticated": True,
|
||||
"role": conn.entries[0][roles_config["role_attribute"]].value,
|
||||
}
|
||||
return {"authenticated": True,
|
||||
"role": conn.entries[0][roles_config["role_attribute"]].value}
|
||||
else:
|
||||
return {"authenticated": True, "role": ""}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ set(communication_src_files
|
||||
websocket/listener.cpp
|
||||
websocket/session.cpp
|
||||
bolt/v1/value.cpp
|
||||
bolt/client.cpp
|
||||
buffer.cpp
|
||||
client.cpp
|
||||
context.cpp
|
||||
|
||||
267
src/communication/bolt/client.cpp
Normal file
267
src/communication/bolt/client.cpp
Normal file
@@ -0,0 +1,267 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "communication/bolt/client.hpp"
|
||||
|
||||
#include "communication/bolt/v1/codes.hpp"
|
||||
#include "communication/bolt/v1/value.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
namespace {
|
||||
constexpr uint8_t kBoltV43Version[4] = {0x00, 0x00, 0x03, 0x04};
|
||||
constexpr uint8_t kEmptyBoltVersion[4] = {0x00, 0x00, 0x00, 0x00};
|
||||
} // namespace
|
||||
namespace memgraph::communication::bolt {
|
||||
|
||||
Client::Client(communication::ClientContext &context) : client_{&context} {}
|
||||
|
||||
void Client::Connect(const io::network::Endpoint &endpoint, const std::string &username, const std::string &password,
|
||||
const std::string &client_name) {
|
||||
if (!client_.Connect(endpoint)) {
|
||||
throw ClientFatalException("Couldn't connect to {}!", endpoint);
|
||||
}
|
||||
|
||||
if (!client_.Write(kPreamble, sizeof(kPreamble), true)) {
|
||||
spdlog::error("Couldn't send preamble!");
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
|
||||
if (!client_.Write(kBoltV43Version, sizeof(kBoltV43Version), true)) {
|
||||
spdlog::error("Couldn't send protocol version!");
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (!client_.Write(kEmptyBoltVersion, sizeof(kEmptyBoltVersion), i != 2)) {
|
||||
spdlog::error("Couldn't send protocol version!");
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
}
|
||||
|
||||
if (!client_.Read(sizeof(kBoltV43Version))) {
|
||||
spdlog::error("Couldn't get negotiated protocol version!");
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
|
||||
if (memcmp(kBoltV43Version, client_.GetData(), sizeof(kBoltV43Version)) != 0) {
|
||||
spdlog::error("Server negotiated unsupported protocol version!");
|
||||
throw ClientFatalException("The server negotiated an usupported protocol version!");
|
||||
}
|
||||
client_.ShiftData(sizeof(kBoltV43Version));
|
||||
|
||||
if (!encoder_.MessageInit({{"user_agent", client_name},
|
||||
{"scheme", "basic"},
|
||||
{"principal", username},
|
||||
{"credentials", password},
|
||||
{"routing", {}}})) {
|
||||
spdlog::error("Couldn't send init message!");
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
|
||||
Signature signature{};
|
||||
Value metadata;
|
||||
if (!ReadMessage(signature, metadata)) {
|
||||
spdlog::error("Couldn't read init message response!");
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
if (signature != Signature::Success) {
|
||||
spdlog::error("Handshake failed!");
|
||||
throw ClientFatalException("Handshake with the server failed!");
|
||||
}
|
||||
|
||||
spdlog::debug("Metadata of init message response: {}", metadata);
|
||||
}
|
||||
|
||||
QueryData Client::Execute(const std::string &query, const std::map<std::string, Value> ¶meters) {
|
||||
if (!client_.IsConnected()) {
|
||||
throw ClientFatalException("You must first connect to the server before using the client!");
|
||||
}
|
||||
|
||||
spdlog::debug("Sending run message with statement: '{}'; parameters: {}", query, parameters);
|
||||
|
||||
// It is super critical from performance point of view to send the pull message right after the run message. Otherwise
|
||||
// the performance will degrade multiple magnitudes.
|
||||
encoder_.MessageRun(query, parameters, {});
|
||||
encoder_.MessagePull({{"n", Value(-1)}});
|
||||
|
||||
spdlog::debug("Reading run message response");
|
||||
Signature signature{};
|
||||
Value fields;
|
||||
if (!ReadMessage(signature, fields)) {
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
if (fields.type() != Value::Type::Map) {
|
||||
throw ServerMalformedDataException();
|
||||
}
|
||||
|
||||
if (signature == Signature::Failure) {
|
||||
HandleFailure<ClientQueryException>(fields.ValueMap());
|
||||
}
|
||||
if (signature != Signature::Success) {
|
||||
throw ServerMalformedDataException();
|
||||
}
|
||||
|
||||
spdlog::debug("Reading pull_all message response");
|
||||
Marker marker{};
|
||||
Value metadata;
|
||||
std::vector<std::vector<Value>> records;
|
||||
while (true) {
|
||||
if (!GetMessage()) {
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
if (!decoder_.ReadMessageHeader(&signature, &marker)) {
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
if (signature == Signature::Record) {
|
||||
Value record;
|
||||
if (!decoder_.ReadValue(&record, Value::Type::List)) {
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
records.emplace_back(std::move(record.ValueList()));
|
||||
} else if (signature == Signature::Success) {
|
||||
if (!decoder_.ReadValue(&metadata)) {
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
break;
|
||||
} else if (signature == Signature::Failure) {
|
||||
Value data;
|
||||
if (!decoder_.ReadValue(&data)) {
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
HandleFailure<ClientQueryException>(data.ValueMap());
|
||||
} else {
|
||||
throw ServerMalformedDataException();
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata.type() != Value::Type::Map) {
|
||||
throw ServerMalformedDataException();
|
||||
}
|
||||
|
||||
auto &header = fields.ValueMap();
|
||||
|
||||
QueryData ret{{}, std::move(records), std::move(metadata.ValueMap())};
|
||||
|
||||
if (header.find("fields") == header.end()) {
|
||||
throw ServerMalformedDataException();
|
||||
}
|
||||
if (header["fields"].type() != Value::Type::List) {
|
||||
throw ServerMalformedDataException();
|
||||
}
|
||||
auto &field_vector = header["fields"].ValueList();
|
||||
|
||||
for (auto &field_item : field_vector) {
|
||||
if (field_item.type() != Value::Type::String) {
|
||||
throw ServerMalformedDataException();
|
||||
}
|
||||
ret.fields.emplace_back(std::move(field_item.ValueString()));
|
||||
}
|
||||
|
||||
if (header.contains("qid")) {
|
||||
ret.metadata["qid"] = header["qid"];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Client::Reset() {
|
||||
if (!client_.IsConnected()) {
|
||||
throw ClientFatalException("You must first connect to the server before using the client!");
|
||||
}
|
||||
|
||||
spdlog::debug("Sending reset message");
|
||||
|
||||
encoder_.MessageReset();
|
||||
|
||||
Signature signature{};
|
||||
Value fields;
|
||||
// In Execute the pull message is sent right after the run message without reading the answer for the run message.
|
||||
// That means some of the messages sent might get ignored.
|
||||
while (true) {
|
||||
if (!ReadMessage(signature, fields)) {
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
if (signature == Signature::Success) {
|
||||
break;
|
||||
}
|
||||
if (signature != Signature::Ignored) {
|
||||
throw ServerMalformedDataException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::map<std::string, Value>> Client::Route(const std::map<std::string, Value> &routing,
|
||||
const std::vector<Value> &bookmarks,
|
||||
const std::optional<std::string> &db) {
|
||||
if (!client_.IsConnected()) {
|
||||
throw ClientFatalException("You must first connect to the server before using the client!");
|
||||
}
|
||||
|
||||
spdlog::debug("Sending route message with routing: {}; bookmarks: {}; db: {}", routing, bookmarks,
|
||||
db.has_value() ? *db : Value());
|
||||
|
||||
encoder_.MessageRoute(routing, bookmarks, db);
|
||||
|
||||
spdlog::debug("Reading route message response");
|
||||
Signature signature{};
|
||||
Value fields;
|
||||
if (!ReadMessage(signature, fields)) {
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
if (signature == Signature::Ignored) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (signature == Signature::Failure) {
|
||||
HandleFailure(fields.ValueMap());
|
||||
}
|
||||
if (signature != Signature::Success) {
|
||||
throw ServerMalformedDataException{};
|
||||
}
|
||||
return fields.ValueMap();
|
||||
}
|
||||
|
||||
void Client::Close() { client_.Close(); };
|
||||
|
||||
bool Client::GetMessage() {
|
||||
client_.ClearData();
|
||||
while (true) {
|
||||
if (!client_.Read(kChunkHeaderSize)) return false;
|
||||
|
||||
size_t chunk_size = client_.GetData()[0];
|
||||
chunk_size <<= 8U;
|
||||
chunk_size += client_.GetData()[1];
|
||||
if (chunk_size == 0) return true;
|
||||
|
||||
if (!client_.Read(chunk_size)) return false;
|
||||
if (decoder_buffer_.GetChunk() != ChunkState::Whole) return false;
|
||||
client_.ClearData();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Client::ReadMessage(Signature &signature, Value &ret) {
|
||||
Marker marker{};
|
||||
if (!GetMessage()) return false;
|
||||
if (!decoder_.ReadMessageHeader(&signature, &marker)) return false;
|
||||
return ReadMessageData(marker, ret);
|
||||
}
|
||||
|
||||
bool Client::ReadMessageData(Marker marker, Value &ret) {
|
||||
if (marker == Marker::TinyStruct) {
|
||||
ret = Value();
|
||||
return true;
|
||||
}
|
||||
if (marker == Marker::TinyStruct1) {
|
||||
return decoder_.ReadValue(&ret);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace memgraph::communication::bolt
|
||||
@@ -11,6 +11,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "communication/bolt/v1/codes.hpp"
|
||||
#include "communication/bolt/v1/decoder/chunked_decoder_buffer.hpp"
|
||||
#include "communication/bolt/v1/decoder/decoder.hpp"
|
||||
#include "communication/bolt/v1/encoder/chunked_encoder_buffer.hpp"
|
||||
@@ -19,22 +25,17 @@
|
||||
#include "communication/context.hpp"
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
namespace memgraph::communication::bolt {
|
||||
|
||||
/// This exception is thrown whenever an error occurs during query execution
|
||||
/// that isn't fatal (eg. mistyped query or some transient error occurred).
|
||||
/// It should be handled by everyone who uses the client.
|
||||
class ClientQueryException : public utils::BasicException {
|
||||
class FailureResponseException : public utils::BasicException {
|
||||
public:
|
||||
using utils::BasicException::BasicException;
|
||||
FailureResponseException() : utils::BasicException{"Couldn't execute query!"} {}
|
||||
|
||||
ClientQueryException() : utils::BasicException("Couldn't execute query!") {}
|
||||
explicit FailureResponseException(const std::string &message) : utils::BasicException{message} {}
|
||||
|
||||
template <class... Args>
|
||||
ClientQueryException(const std::string &code, Args &&...args)
|
||||
: utils::BasicException(std::forward<Args>(args)...), code_(code) {}
|
||||
FailureResponseException(const std::string &code, const std::string &message)
|
||||
: utils::BasicException{message}, code_{code} {}
|
||||
|
||||
const std::string &code() const { return code_; }
|
||||
|
||||
@@ -42,6 +43,14 @@ class ClientQueryException : public utils::BasicException {
|
||||
std::string code_;
|
||||
};
|
||||
|
||||
/// This exception is thrown whenever an error occurs during query execution
|
||||
/// that isn't fatal (eg. mistyped query or some transient error occurred).
|
||||
/// It should be handled by everyone who uses the client.
|
||||
class ClientQueryException : public FailureResponseException {
|
||||
public:
|
||||
using FailureResponseException::FailureResponseException;
|
||||
};
|
||||
|
||||
/// This exception is thrown whenever a fatal error occurs during query
|
||||
/// execution and/or connecting to the server.
|
||||
/// It should be handled by everyone who uses the client.
|
||||
@@ -76,12 +85,13 @@ struct QueryData {
|
||||
/// server. It supports both SSL and plaintext connections.
|
||||
class Client final {
|
||||
public:
|
||||
explicit Client(communication::ClientContext *context) : client_(context) {}
|
||||
explicit Client(communication::ClientContext &context);
|
||||
|
||||
Client(const Client &) = delete;
|
||||
Client(Client &&) = delete;
|
||||
Client &operator=(const Client &) = delete;
|
||||
Client &operator=(Client &&) = delete;
|
||||
~Client() = default;
|
||||
|
||||
/// Method used to connect to the server. Before executing queries this method
|
||||
/// should be called to set-up the connection to the server. After the
|
||||
@@ -89,50 +99,7 @@ class Client final {
|
||||
/// established connection.
|
||||
/// @throws ClientFatalException when we couldn't connect to the server
|
||||
void Connect(const io::network::Endpoint &endpoint, const std::string &username, const std::string &password,
|
||||
const std::string &client_name = "memgraph-bolt") {
|
||||
if (!client_.Connect(endpoint)) {
|
||||
throw ClientFatalException("Couldn't connect to {}!", endpoint);
|
||||
}
|
||||
|
||||
if (!client_.Write(kPreamble, sizeof(kPreamble), true)) {
|
||||
SPDLOG_ERROR("Couldn't send preamble!");
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (!client_.Write(kProtocol, sizeof(kProtocol), i != 3)) {
|
||||
SPDLOG_ERROR("Couldn't send protocol version!");
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
}
|
||||
|
||||
if (!client_.Read(sizeof(kProtocol))) {
|
||||
SPDLOG_ERROR("Couldn't get negotiated protocol version!");
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
if (memcmp(kProtocol, client_.GetData(), sizeof(kProtocol)) != 0) {
|
||||
SPDLOG_ERROR("Server negotiated unsupported protocol version!");
|
||||
throw ClientFatalException("The server negotiated an usupported protocol version!");
|
||||
}
|
||||
client_.ShiftData(sizeof(kProtocol));
|
||||
|
||||
if (!encoder_.MessageInit(client_name, {{"scheme", "basic"}, {"principal", username}, {"credentials", password}})) {
|
||||
SPDLOG_ERROR("Couldn't send init message!");
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
|
||||
Signature signature;
|
||||
Value metadata;
|
||||
if (!ReadMessage(&signature, &metadata)) {
|
||||
SPDLOG_ERROR("Couldn't read init message response!");
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
if (signature != Signature::Success) {
|
||||
SPDLOG_ERROR("Handshake failed!");
|
||||
throw ClientFatalException("Handshake with the server failed!");
|
||||
}
|
||||
|
||||
SPDLOG_INFO("Metadata of init message response: {}", metadata);
|
||||
}
|
||||
const std::string &client_name = "memgraph-bolt");
|
||||
|
||||
/// Function used to execute queries against the server. Before you can
|
||||
/// execute queries you must connect the client to the server.
|
||||
@@ -140,168 +107,41 @@ class Client final {
|
||||
/// executing the query (eg. mistyped query,
|
||||
/// etc.)
|
||||
/// @throws ClientFatalException when we couldn't communicate with the server
|
||||
QueryData Execute(const std::string &query, const std::map<std::string, Value> ¶meters) {
|
||||
if (!client_.IsConnected()) {
|
||||
throw ClientFatalException("You must first connect to the server before using the client!");
|
||||
}
|
||||
|
||||
SPDLOG_INFO("Sending run message with statement: '{}'; parameters: {}", query, parameters);
|
||||
|
||||
encoder_.MessageRun(query, parameters);
|
||||
encoder_.MessagePullAll();
|
||||
|
||||
SPDLOG_INFO("Reading run message response");
|
||||
Signature signature;
|
||||
Value fields;
|
||||
if (!ReadMessage(&signature, &fields)) {
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
if (fields.type() != Value::Type::Map) {
|
||||
throw ServerMalformedDataException();
|
||||
}
|
||||
|
||||
if (signature == Signature::Failure) {
|
||||
HandleFailure();
|
||||
auto &tmp = fields.ValueMap();
|
||||
auto it = tmp.find("message");
|
||||
if (it != tmp.end()) {
|
||||
auto it_code = tmp.find("code");
|
||||
if (it_code != tmp.end()) {
|
||||
throw ClientQueryException(it_code->second.ValueString(), it->second.ValueString());
|
||||
} else {
|
||||
throw ClientQueryException("", it->second.ValueString());
|
||||
}
|
||||
}
|
||||
throw ClientQueryException();
|
||||
} else if (signature != Signature::Success) {
|
||||
throw ServerMalformedDataException();
|
||||
}
|
||||
|
||||
SPDLOG_INFO("Reading pull_all message response");
|
||||
Marker marker;
|
||||
Value metadata;
|
||||
std::vector<std::vector<Value>> records;
|
||||
while (true) {
|
||||
if (!GetMessage()) {
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
if (!decoder_.ReadMessageHeader(&signature, &marker)) {
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
if (signature == Signature::Record) {
|
||||
Value record;
|
||||
if (!decoder_.ReadValue(&record, Value::Type::List)) {
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
records.emplace_back(std::move(record.ValueList()));
|
||||
} else if (signature == Signature::Success) {
|
||||
if (!decoder_.ReadValue(&metadata)) {
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
break;
|
||||
} else if (signature == Signature::Failure) {
|
||||
Value data;
|
||||
if (!decoder_.ReadValue(&data)) {
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
HandleFailure();
|
||||
auto &tmp = data.ValueMap();
|
||||
auto it = tmp.find("message");
|
||||
if (it != tmp.end()) {
|
||||
auto it_code = tmp.find("code");
|
||||
if (it_code != tmp.end()) {
|
||||
throw ClientQueryException(it_code->second.ValueString(), it->second.ValueString());
|
||||
} else {
|
||||
throw ClientQueryException("", it->second.ValueString());
|
||||
}
|
||||
}
|
||||
throw ClientQueryException();
|
||||
} else {
|
||||
throw ServerMalformedDataException();
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata.type() != Value::Type::Map) {
|
||||
throw ServerMalformedDataException();
|
||||
}
|
||||
|
||||
QueryData ret{{}, std::move(records), std::move(metadata.ValueMap())};
|
||||
|
||||
auto &header = fields.ValueMap();
|
||||
if (header.find("fields") == header.end()) {
|
||||
throw ServerMalformedDataException();
|
||||
}
|
||||
if (header["fields"].type() != Value::Type::List) {
|
||||
throw ServerMalformedDataException();
|
||||
}
|
||||
auto &field_vector = header["fields"].ValueList();
|
||||
|
||||
for (auto &field_item : field_vector) {
|
||||
if (field_item.type() != Value::Type::String) {
|
||||
throw ServerMalformedDataException();
|
||||
}
|
||||
ret.fields.emplace_back(std::move(field_item.ValueString()));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
QueryData Execute(const std::string &query, const std::map<std::string, Value> ¶meters);
|
||||
|
||||
/// Close the active client connection.
|
||||
void Close() { client_.Close(); };
|
||||
void Close();
|
||||
|
||||
/// Can be used to reset the active client connection. Reset is automatically sent after receiving a failure message
|
||||
/// from the server, which result in throwing an FailureResponseException or any exception derived from it.
|
||||
void Reset();
|
||||
|
||||
/// Can be used to send a route message.
|
||||
std::optional<std::map<std::string, Value>> Route(const std::map<std::string, Value> &routing,
|
||||
const std::vector<Value> &bookmarks,
|
||||
const std::optional<std::string> &db);
|
||||
|
||||
private:
|
||||
bool GetMessage() {
|
||||
client_.ClearData();
|
||||
while (true) {
|
||||
if (!client_.Read(kChunkHeaderSize)) return false;
|
||||
using ClientEncoder = ClientEncoder<ChunkedEncoderBuffer<communication::ClientOutputStream>>;
|
||||
|
||||
size_t chunk_size = client_.GetData()[0];
|
||||
chunk_size <<= 8;
|
||||
chunk_size += client_.GetData()[1];
|
||||
if (chunk_size == 0) return true;
|
||||
|
||||
if (!client_.Read(chunk_size)) return false;
|
||||
if (decoder_buffer_.GetChunk() != ChunkState::Whole) return false;
|
||||
client_.ClearData();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadMessage(Signature *signature, Value *ret) {
|
||||
Marker marker;
|
||||
if (!GetMessage()) return false;
|
||||
if (!decoder_.ReadMessageHeader(signature, &marker)) return false;
|
||||
return ReadMessageData(marker, ret);
|
||||
}
|
||||
|
||||
bool ReadMessageData(Marker marker, Value *ret) {
|
||||
if (marker == Marker::TinyStruct) {
|
||||
*ret = Value();
|
||||
return true;
|
||||
} else if (marker == Marker::TinyStruct1) {
|
||||
return decoder_.ReadValue(ret);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void HandleFailure() {
|
||||
if (!encoder_.MessageAckFailure()) {
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
while (true) {
|
||||
Signature signature;
|
||||
Value data;
|
||||
if (!ReadMessage(&signature, &data)) {
|
||||
throw ServerCommunicationException();
|
||||
}
|
||||
if (signature == Signature::Success) {
|
||||
break;
|
||||
} else if (signature != Signature::Ignored) {
|
||||
throw ServerMalformedDataException();
|
||||
template <typename TException = FailureResponseException>
|
||||
[[noreturn]] void HandleFailure(const std::map<std::string, Value> &response_map) {
|
||||
Reset();
|
||||
auto it = response_map.find("message");
|
||||
if (it != response_map.end()) {
|
||||
auto it_code = response_map.find("code");
|
||||
if (it_code != response_map.end()) {
|
||||
throw TException(it_code->second.ValueString(), it->second.ValueString());
|
||||
}
|
||||
throw TException("", it->second.ValueString());
|
||||
}
|
||||
throw TException();
|
||||
}
|
||||
|
||||
bool GetMessage();
|
||||
bool ReadMessage(Signature &signature, Value &ret);
|
||||
bool ReadMessageData(Marker marker, Value &ret);
|
||||
|
||||
// client
|
||||
communication::Client client_;
|
||||
communication::ClientInputStream input_stream_{client_};
|
||||
@@ -313,6 +153,6 @@ class Client final {
|
||||
|
||||
// encoder objects
|
||||
ChunkedEncoderBuffer<communication::ClientOutputStream> encoder_buffer_{output_stream_};
|
||||
ClientEncoder<ChunkedEncoderBuffer<communication::ClientOutputStream>> encoder_{encoder_buffer_};
|
||||
ClientEncoder encoder_{encoder_buffer_};
|
||||
};
|
||||
} // namespace memgraph::communication::bolt
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
namespace memgraph::communication::bolt {
|
||||
|
||||
inline constexpr uint8_t kPreamble[4] = {0x60, 0x60, 0xB0, 0x17};
|
||||
inline constexpr uint8_t kProtocol[4] = {0x00, 0x00, 0x00, 0x01};
|
||||
|
||||
enum class Signature : uint8_t {
|
||||
Noop = 0x00,
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "communication/bolt/v1/codes.hpp"
|
||||
#include "communication/bolt/v1/encoder/base_encoder.hpp"
|
||||
|
||||
@@ -30,6 +35,7 @@ class ClientEncoder : private BaseEncoder<Buffer> {
|
||||
using BaseEncoder<Buffer>::WriteList;
|
||||
using BaseEncoder<Buffer>::WriteMap;
|
||||
using BaseEncoder<Buffer>::WriteString;
|
||||
using BaseEncoder<Buffer>::WriteNull;
|
||||
using BaseEncoder<Buffer>::buffer_;
|
||||
|
||||
public:
|
||||
@@ -38,10 +44,9 @@ class ClientEncoder : private BaseEncoder<Buffer> {
|
||||
/**
|
||||
* Writes a Init message.
|
||||
*
|
||||
* From the Bolt v1 documentation:
|
||||
* InitMessage (signature=0x01) {
|
||||
* String clientName
|
||||
* Map<String,Value> authToken
|
||||
* From the Bolt v4.3 documentation:
|
||||
* HelloMess (signature=0x01) {
|
||||
* Map<String,Value> extra
|
||||
* }
|
||||
*
|
||||
* @param client_name the name of the connected client
|
||||
@@ -49,11 +54,10 @@ class ClientEncoder : private BaseEncoder<Buffer> {
|
||||
* @returns true if the data was successfully sent to the client
|
||||
* when flushing, false otherwise
|
||||
*/
|
||||
bool MessageInit(const std::string client_name, const std::map<std::string, Value> &auth_token) {
|
||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct2));
|
||||
bool MessageInit(const std::map<std::string, Value> &extra) {
|
||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct1));
|
||||
WriteRAW(utils::UnderlyingCast(Signature::Init));
|
||||
WriteString(client_name);
|
||||
WriteMap(auth_token);
|
||||
WriteMap(extra);
|
||||
// Try to flush all remaining data in the buffer, but tell it that we will
|
||||
// send more data (the end of message chunk).
|
||||
if (!buffer_.Flush(true)) return false;
|
||||
@@ -64,10 +68,11 @@ class ClientEncoder : private BaseEncoder<Buffer> {
|
||||
/**
|
||||
* Writes a Run message.
|
||||
*
|
||||
* From the Bolt v1 documentation:
|
||||
* From the Bolt v4.3 documentation:
|
||||
* RunMessage (signature=0x10) {
|
||||
* String statement
|
||||
* Map<String,Value> parameters
|
||||
* String statement
|
||||
* Map<String,Value> parameters
|
||||
* Map<String,Value> extra
|
||||
* }
|
||||
*
|
||||
* @param statement the statement that should be executed
|
||||
@@ -75,11 +80,13 @@ class ClientEncoder : private BaseEncoder<Buffer> {
|
||||
* @returns true if the data was successfully sent to the client
|
||||
* when flushing, false otherwise
|
||||
*/
|
||||
bool MessageRun(const std::string &statement, const std::map<std::string, Value> ¶meters, bool have_more = true) {
|
||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct2));
|
||||
bool MessageRun(const std::string &statement, const std::map<std::string, Value> ¶meters,
|
||||
const std::map<std::string, Value> &extra, bool have_more = true) {
|
||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct3));
|
||||
WriteRAW(utils::UnderlyingCast(Signature::Run));
|
||||
WriteString(statement);
|
||||
WriteMap(parameters);
|
||||
WriteMap(extra);
|
||||
// Try to flush all remaining data in the buffer, but tell it that we will
|
||||
// send more data (the end of message chunk).
|
||||
if (!buffer_.Flush(true)) return false;
|
||||
@@ -90,18 +97,20 @@ class ClientEncoder : private BaseEncoder<Buffer> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a DiscardAll message.
|
||||
* Writes a Discard message.
|
||||
*
|
||||
* From the Bolt v1 documentation:
|
||||
* From the Bolt v4.3 documentation:
|
||||
* DiscardMessage (signature=0x2F) {
|
||||
* Map<String,Value> extra
|
||||
* }
|
||||
*
|
||||
* @returns true if the data was successfully sent to the client
|
||||
* when flushing, false otherwise
|
||||
*/
|
||||
bool MessageDiscardAll() {
|
||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct));
|
||||
bool MessageDiscard(const std::map<std::string, Value> &extra) {
|
||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct1));
|
||||
WriteRAW(utils::UnderlyingCast(Signature::Discard));
|
||||
WriteMap(extra);
|
||||
// Try to flush all remaining data in the buffer, but tell it that we will
|
||||
// send more data (the end of message chunk).
|
||||
if (!buffer_.Flush(true)) return false;
|
||||
@@ -112,36 +121,18 @@ class ClientEncoder : private BaseEncoder<Buffer> {
|
||||
/**
|
||||
* Writes a PullAll message.
|
||||
*
|
||||
* From the Bolt v1 documentation:
|
||||
* PullAllMessage (signature=0x3F) {
|
||||
* From the Bolt v4.3 documentation:
|
||||
* PullMessage (signature=0x3F) {
|
||||
* Map<String,Value> extra
|
||||
* }
|
||||
*
|
||||
* @returns true if the data was successfully sent to the client
|
||||
* when flushing, false otherwise
|
||||
*/
|
||||
bool MessagePullAll() {
|
||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct));
|
||||
bool MessagePull(const std::map<std::string, Value> &extra) {
|
||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct1));
|
||||
WriteRAW(utils::UnderlyingCast(Signature::Pull));
|
||||
// Try to flush all remaining data in the buffer, but tell it that we will
|
||||
// send more data (the end of message chunk).
|
||||
if (!buffer_.Flush(true)) return false;
|
||||
// Flush an empty chunk to indicate that the message is done.
|
||||
return buffer_.Flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a AckFailure message.
|
||||
*
|
||||
* From the Bolt v1 documentation:
|
||||
* AckFailureMessage (signature=0x0E) {
|
||||
* }
|
||||
*
|
||||
* @returns true if the data was successfully sent to the client
|
||||
* when flushing, false otherwise
|
||||
*/
|
||||
bool MessageAckFailure() {
|
||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct));
|
||||
WriteRAW(utils::UnderlyingCast(Signature::AckFailure));
|
||||
WriteMap(extra);
|
||||
// Try to flush all remaining data in the buffer, but tell it that we will
|
||||
// send more data (the end of message chunk).
|
||||
if (!buffer_.Flush(true)) return false;
|
||||
@@ -152,7 +143,7 @@ class ClientEncoder : private BaseEncoder<Buffer> {
|
||||
/**
|
||||
* Writes a Reset message.
|
||||
*
|
||||
* From the Bolt v1 documentation:
|
||||
* From the Bolt v4.3 documentation:
|
||||
* ResetMessage (signature=0x0F) {
|
||||
* }
|
||||
*
|
||||
@@ -168,5 +159,36 @@ class ClientEncoder : private BaseEncoder<Buffer> {
|
||||
// Flush an empty chunk to indicate that the message is done.
|
||||
return buffer_.Flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a Route message.
|
||||
*
|
||||
* From the Bolt v4.3 documentation:
|
||||
* RouteMessage (signature=0x0F) {
|
||||
* Map<String,Value> routing
|
||||
* List<String> bookmarks
|
||||
* String db
|
||||
* }
|
||||
*
|
||||
* @returns true if the data was successfully sent to the client
|
||||
* when flushing, false otherwise
|
||||
*/
|
||||
bool MessageRoute(const std::map<std::string, Value> &routing, const std::vector<Value> &bookmarks,
|
||||
const std::optional<std::string> &db) {
|
||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct3));
|
||||
WriteRAW(utils::UnderlyingCast(Signature::Route));
|
||||
WriteMap(routing);
|
||||
WriteList(bookmarks);
|
||||
if (db.has_value()) {
|
||||
WriteString(*db);
|
||||
} else {
|
||||
WriteNull();
|
||||
}
|
||||
// Try to flush all remaining data in the buffer, but tell it that we will
|
||||
// send more data (the end of message chunk).
|
||||
if (!buffer_.Flush(true)) return false;
|
||||
// Flush an empty chunk to indicate that the message is done.
|
||||
return buffer_.Flush();
|
||||
}
|
||||
};
|
||||
} // namespace memgraph::communication::bolt
|
||||
|
||||
@@ -117,29 +117,6 @@ class Encoder : private BaseEncoder<Buffer> {
|
||||
return buffer_.Flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an Ignored message.
|
||||
*
|
||||
* From the bolt v1 documentation:
|
||||
* IgnoredMessage (signature=0x7E) {
|
||||
* Map<String,Value> metadata
|
||||
* }
|
||||
*
|
||||
* @param metadata the metadata map object that should be sent
|
||||
* @returns true if the data was successfully sent to the client,
|
||||
* false otherwise
|
||||
*/
|
||||
bool MessageIgnored(const std::map<std::string, Value> &metadata) {
|
||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct1));
|
||||
WriteRAW(utils::UnderlyingCast(Signature::Ignored));
|
||||
WriteMap(metadata);
|
||||
// Try to flush all remaining data in the buffer, but tell it that we will
|
||||
// send more data (the end of message chunk).
|
||||
if (!buffer_.Flush(true)) return false;
|
||||
// Flush an empty chunk to indicate that the message is done.
|
||||
return buffer_.Flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an Ignored message.
|
||||
*
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "communication/bolt/v1/codes.hpp"
|
||||
#include "communication/bolt/v1/state.hpp"
|
||||
#include "communication/bolt/v1/states/handlers.hpp"
|
||||
#include "communication/bolt/v1/value.hpp"
|
||||
#include "utils/cast.hpp"
|
||||
#include "utils/likely.hpp"
|
||||
@@ -30,8 +31,8 @@ namespace memgraph::communication::bolt {
|
||||
*/
|
||||
template <typename TSession>
|
||||
State StateErrorRun(TSession &session, State state) {
|
||||
Marker marker;
|
||||
Signature signature;
|
||||
Marker marker{};
|
||||
Signature signature{};
|
||||
if (!session.decoder_.ReadMessageHeader(&signature, &marker)) {
|
||||
spdlog::trace("Missing header data!");
|
||||
return State::Close;
|
||||
@@ -45,54 +46,49 @@ State StateErrorRun(TSession &session, State state) {
|
||||
// Clear the data buffer if it has any leftover data.
|
||||
session.encoder_buffer_.Clear();
|
||||
|
||||
if ((session.version_.major == 1 && signature == Signature::AckFailure) || signature == Signature::Reset) {
|
||||
if (signature == Signature::AckFailure) {
|
||||
spdlog::trace("AckFailure received");
|
||||
} else {
|
||||
spdlog::trace("Reset received");
|
||||
}
|
||||
if (session.version_.major == 1 && signature == Signature::AckFailure) {
|
||||
spdlog::trace("AckFailure received");
|
||||
|
||||
if (!session.encoder_.MessageSuccess()) {
|
||||
spdlog::trace("Couldn't send success message!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
if (signature == Signature::Reset) {
|
||||
session.Abort();
|
||||
return State::Idle;
|
||||
}
|
||||
|
||||
// We got AckFailure get back to right state.
|
||||
MG_ASSERT(state == State::Error, "Shouldn't happen");
|
||||
return State::Idle;
|
||||
} else {
|
||||
uint8_t value = utils::UnderlyingCast(marker);
|
||||
|
||||
// All bolt client messages have less than 15 parameters so if we receive
|
||||
// anything than a TinyStruct it's an error.
|
||||
if ((value & 0xF0) != utils::UnderlyingCast(Marker::TinyStruct)) {
|
||||
spdlog::trace("Expected TinyStruct marker, but received 0x{:02X}!", value);
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
// We need to clean up all parameters from this command.
|
||||
value &= 0x0F; // The length is stored in the lower nibble.
|
||||
Value dv;
|
||||
for (int i = 0; i < value; ++i) {
|
||||
if (!session.decoder_.ReadValue(&dv)) {
|
||||
spdlog::trace("Couldn't clean up parameter {} / {}!", i, value);
|
||||
return State::Close;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore this message.
|
||||
if (!session.encoder_.MessageIgnored()) {
|
||||
spdlog::trace("Couldn't send ignored message!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
// Cleanup done, command ignored, stay in error state.
|
||||
return state;
|
||||
}
|
||||
if (signature == Signature::Reset) {
|
||||
spdlog::trace("Reset received");
|
||||
return HandleReset(session, marker);
|
||||
}
|
||||
|
||||
uint8_t value = utils::UnderlyingCast(marker);
|
||||
|
||||
// All bolt client messages have less than 15 parameters so if we receive
|
||||
// anything than a TinyStruct it's an error.
|
||||
if ((value & 0xF0U) != utils::UnderlyingCast(Marker::TinyStruct)) {
|
||||
spdlog::trace("Expected TinyStruct marker, but received 0x{:02X}!", value);
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
// We need to clean up all parameters from this command.
|
||||
value &= 0x0FU; // The length is stored in the lower nibble.
|
||||
Value dv;
|
||||
for (int i = 0; i < value; ++i) {
|
||||
if (!session.decoder_.ReadValue(&dv)) {
|
||||
spdlog::trace("Couldn't clean up parameter {} / {}!", i, value);
|
||||
return State::Close;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore this message.
|
||||
if (!session.encoder_.MessageIgnored()) {
|
||||
spdlog::trace("Couldn't send ignored message!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
// Cleanup done, command ignored, stay in error state.
|
||||
return state;
|
||||
}
|
||||
} // namespace memgraph::communication::bolt
|
||||
|
||||
@@ -74,7 +74,7 @@ State RunHandlerV4(Signature signature, TSession &session, State state, Marker m
|
||||
}
|
||||
case Signature::Route: {
|
||||
if constexpr (bolt_minor >= 3) {
|
||||
if (signature == Signature::Route) return HandleRoute<TSession>(session);
|
||||
if (signature == Signature::Route) return HandleRoute<TSession>(session, marker);
|
||||
} else {
|
||||
spdlog::trace("Supported only in bolt v4.3");
|
||||
return State::Close;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "communication/bolt/v1/codes.hpp"
|
||||
#include "communication/bolt/v1/constants.hpp"
|
||||
#include "communication/bolt/v1/exceptions.hpp"
|
||||
#include "communication/bolt/v1/state.hpp"
|
||||
#include "communication/bolt/v1/value.hpp"
|
||||
#include "communication/exceptions.hpp"
|
||||
@@ -72,40 +73,6 @@ inline std::pair<std::string, std::string> ExceptionToErrorMessage(const std::ex
|
||||
|
||||
namespace details {
|
||||
|
||||
template <typename TSession>
|
||||
State HandleRun(TSession &session, const State state, const Value &query, const Value ¶ms) {
|
||||
if (state != State::Idle) {
|
||||
// Client could potentially recover if we move to error state, but there is
|
||||
// no legitimate situation in which well working client would end up in this
|
||||
// situation.
|
||||
spdlog::trace("Unexpected RUN command!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
|
||||
|
||||
spdlog::debug("[Run] '{}'", query.ValueString());
|
||||
|
||||
try {
|
||||
// Interpret can throw.
|
||||
const auto [header, qid] = session.Interpret(query.ValueString(), params.ValueMap());
|
||||
// Convert std::string to Value
|
||||
std::vector<Value> vec;
|
||||
std::map<std::string, Value> data;
|
||||
vec.reserve(header.size());
|
||||
for (auto &i : header) vec.emplace_back(std::move(i));
|
||||
data.emplace("fields", std::move(vec));
|
||||
// Send the header.
|
||||
if (!session.encoder_.MessageSuccess(data)) {
|
||||
spdlog::trace("Couldn't send query header!");
|
||||
return State::Close;
|
||||
}
|
||||
return State::Result;
|
||||
} catch (const std::exception &e) {
|
||||
return HandleFailure(session, e);
|
||||
}
|
||||
}
|
||||
|
||||
template <bool is_pull, typename TSession>
|
||||
State HandlePullDiscard(TSession &session, std::optional<int> n, std::optional<int> qid) {
|
||||
try {
|
||||
@@ -136,7 +103,7 @@ 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));
|
||||
spdlog::trace("Expected TinyStruct marker, but received 0x{:02X}!", utils::UnderlyingCast(marker));
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
@@ -157,7 +124,7 @@ 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));
|
||||
spdlog::trace("Expected TinyStruct1 marker, but received 0x{:02X}!", utils::UnderlyingCast(marker));
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
@@ -216,7 +183,8 @@ State HandleRunV1(TSession &session, const State state, const Marker marker) {
|
||||
session.version_.major == 1 ? "TinyStruct2" : "TinyStruct3", utils::UnderlyingCast(marker));
|
||||
return State::Close;
|
||||
}
|
||||
Value query, params;
|
||||
Value query;
|
||||
Value params;
|
||||
if (!session.decoder_.ReadValue(&query, Value::Type::String)) {
|
||||
spdlog::trace("Couldn't read query string!");
|
||||
return State::Close;
|
||||
@@ -227,17 +195,48 @@ State HandleRunV1(TSession &session, const State state, const Marker marker) {
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
return details::HandleRun(session, state, query, params);
|
||||
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 <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));
|
||||
spdlog::trace("Expected TinyStruct3 marker, but received 0x{:02X}!", utils::UnderlyingCast(marker));
|
||||
return State::Close;
|
||||
}
|
||||
Value query, params, extra;
|
||||
Value query;
|
||||
Value params;
|
||||
Value extra;
|
||||
if (!session.decoder_.ReadValue(&query, Value::Type::String)) {
|
||||
spdlog::trace("Couldn't read query string!");
|
||||
return State::Close;
|
||||
@@ -253,7 +252,40 @@ State HandleRunV4(TSession &session, const State state, const Marker marker) {
|
||||
spdlog::trace("Couldn't read extra field!");
|
||||
}
|
||||
|
||||
return details::HandleRun(session, state, query, params);
|
||||
if (state != State::Idle) {
|
||||
// Client could potentially recover if we move to error state, but there is
|
||||
// no legitimate situation in which well working client would end up in this
|
||||
// situation.
|
||||
spdlog::trace("Unexpected RUN command!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
|
||||
|
||||
spdlog::debug("[Run] '{}'", query.ValueString());
|
||||
|
||||
try {
|
||||
// Interpret can throw.
|
||||
const auto [header, qid] = session.Interpret(query.ValueString(), params.ValueMap());
|
||||
// Convert std::string to Value
|
||||
std::vector<Value> vec;
|
||||
std::map<std::string, Value> data;
|
||||
vec.reserve(header.size());
|
||||
for (auto &i : header) vec.emplace_back(std::move(i));
|
||||
data.emplace("fields", std::move(vec));
|
||||
if (qid.has_value()) {
|
||||
data.emplace("qid", Value{*qid});
|
||||
}
|
||||
|
||||
// Send the header.
|
||||
if (!session.encoder_.MessageSuccess(data)) {
|
||||
spdlog::trace("Couldn't send query header!");
|
||||
return State::Close;
|
||||
}
|
||||
return State::Result;
|
||||
} catch (const std::exception &e) {
|
||||
return HandleFailure(session, e);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename TSession>
|
||||
@@ -292,9 +324,6 @@ State HandleReset(TSession &session, const Marker 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;
|
||||
@@ -403,12 +432,33 @@ State HandleGoodbye() {
|
||||
}
|
||||
|
||||
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.
|
||||
State HandleRoute(TSession &session, const Marker marker) {
|
||||
// Route message is not implemented since it is Neo4j specific, therefore we will receive it and inform user that
|
||||
// there is no implementation. Before that, we have to read out the fields from the buffer to leave it in a clean
|
||||
// state.
|
||||
if (marker != Marker::TinyStruct3) {
|
||||
spdlog::trace("Expected TinyStruct3 marker, but received 0x{:02x}!", utils::UnderlyingCast(marker));
|
||||
return State::Close;
|
||||
}
|
||||
Value routing;
|
||||
if (!session.decoder_.ReadValue(&routing, Value::Type::Map)) {
|
||||
spdlog::trace("Couldn't read routing field!");
|
||||
return State::Close;
|
||||
}
|
||||
|
||||
Value bookmarks;
|
||||
if (!session.decoder_.ReadValue(&bookmarks, Value::Type::List)) {
|
||||
spdlog::trace("Couldn't read bookmarks field!");
|
||||
return State::Close;
|
||||
}
|
||||
Value db;
|
||||
if (!session.decoder_.ReadValue(&db)) {
|
||||
spdlog::trace("Couldn't read db field!");
|
||||
return State::Close;
|
||||
}
|
||||
session.encoder_buffer_.Clear();
|
||||
bool fail_sent =
|
||||
session.encoder_.MessageFailure({{"code", 66}, {"message", "Route message not supported in Memgraph!"}});
|
||||
session.encoder_.MessageFailure({{"code", "66"}, {"message", "Route message is not supported in Memgraph!"}});
|
||||
if (!fail_sent) {
|
||||
spdlog::trace("Couldn't send failure message!");
|
||||
return State::Close;
|
||||
|
||||
4
src/glue/CMakeLists.txt
Normal file
4
src/glue/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
set(mg_glue_sources auth.cpp auth_checker.cpp auth_handler.cpp communication.cpp)
|
||||
|
||||
add_library(mg-glue STATIC ${mg_glue_sources})
|
||||
target_link_libraries(mg-glue mg-query mg-auth)
|
||||
@@ -10,6 +10,7 @@
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "glue/auth.hpp"
|
||||
#include "auth/models.hpp"
|
||||
|
||||
namespace memgraph::glue {
|
||||
|
||||
@@ -57,8 +58,22 @@ auth::Permission PrivilegeToPermission(query::AuthQuery::Privilege privilege) {
|
||||
return auth::Permission::MODULE_WRITE;
|
||||
case query::AuthQuery::Privilege::WEBSOCKET:
|
||||
return auth::Permission::WEBSOCKET;
|
||||
case query::AuthQuery::Privilege::LABELS:
|
||||
return auth::Permission::LABELS;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
auth::FineGrainedPermission FineGrainedPrivilegeToFineGrainedPermission(
|
||||
const query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) {
|
||||
switch (fine_grained_privilege) {
|
||||
case query::AuthQuery::FineGrainedPrivilege::NOTHING:
|
||||
return auth::FineGrainedPermission::NOTHING;
|
||||
case query::AuthQuery::FineGrainedPrivilege::READ:
|
||||
return auth::FineGrainedPermission::READ;
|
||||
case query::AuthQuery::FineGrainedPrivilege::UPDATE:
|
||||
return auth::FineGrainedPermission::UPDATE;
|
||||
case query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE:
|
||||
return auth::FineGrainedPermission::CREATE_DELETE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} // namespace memgraph::glue
|
||||
|
||||
@@ -20,4 +20,12 @@ namespace memgraph::glue {
|
||||
*/
|
||||
auth::Permission PrivilegeToPermission(query::AuthQuery::Privilege privilege);
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
/**
|
||||
* Converts query::AuthQuery::FineGrainedPrivilege to its corresponding
|
||||
* auth::EntityPermission.
|
||||
*/
|
||||
auth::FineGrainedPermission FineGrainedPrivilegeToFineGrainedPermission(
|
||||
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege);
|
||||
#endif
|
||||
} // namespace memgraph::glue
|
||||
|
||||
171
src/glue/auth_checker.cpp
Normal file
171
src/glue/auth_checker.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "glue/auth_checker.hpp"
|
||||
|
||||
#include "auth/auth.hpp"
|
||||
#include "auth/models.hpp"
|
||||
#include "glue/auth.hpp"
|
||||
#include "license/license.hpp"
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
namespace {
|
||||
bool IsUserAuthorizedLabels(const memgraph::auth::User &user, const memgraph::query::DbAccessor *dba,
|
||||
const std::vector<memgraph::storage::LabelId> &labels,
|
||||
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) {
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return true;
|
||||
}
|
||||
return std::all_of(labels.begin(), labels.end(), [dba, &user, fine_grained_privilege](const auto &label) {
|
||||
return user.GetFineGrainedAccessLabelPermissions().Has(
|
||||
dba->LabelToName(label), memgraph::glue::FineGrainedPrivilegeToFineGrainedPermission(
|
||||
fine_grained_privilege)) == memgraph::auth::PermissionLevel::GRANT;
|
||||
});
|
||||
}
|
||||
|
||||
bool IsUserAuthorizedGloballyLabels(const memgraph::auth::User &user,
|
||||
const memgraph::auth::FineGrainedPermission fine_grained_permission) {
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return true;
|
||||
}
|
||||
return user.GetFineGrainedAccessLabelPermissions().Has(memgraph::auth::kAsterisk, fine_grained_permission) ==
|
||||
memgraph::auth::PermissionLevel::GRANT;
|
||||
}
|
||||
|
||||
bool IsUserAuthorizedGloballyEdges(const memgraph::auth::User &user,
|
||||
const memgraph::auth::FineGrainedPermission fine_grained_permission) {
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return true;
|
||||
}
|
||||
return user.GetFineGrainedAccessEdgeTypePermissions().Has(memgraph::auth::kAsterisk, fine_grained_permission) ==
|
||||
memgraph::auth::PermissionLevel::GRANT;
|
||||
}
|
||||
|
||||
bool IsUserAuthorizedEdgeType(const memgraph::auth::User &user, const memgraph::query::DbAccessor *dba,
|
||||
const memgraph::storage::EdgeTypeId &edgeType,
|
||||
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) {
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return true;
|
||||
}
|
||||
return user.GetFineGrainedAccessEdgeTypePermissions().Has(
|
||||
dba->EdgeTypeToName(edgeType), memgraph::glue::FineGrainedPrivilegeToFineGrainedPermission(
|
||||
fine_grained_privilege)) == memgraph::auth::PermissionLevel::GRANT;
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
namespace memgraph::glue {
|
||||
|
||||
AuthChecker::AuthChecker(
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth)
|
||||
: auth_(auth) {}
|
||||
|
||||
bool AuthChecker::IsUserAuthorized(const std::optional<std::string> &username,
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) const {
|
||||
std::optional<memgraph::auth::User> maybe_user;
|
||||
{
|
||||
auto locked_auth = auth_->ReadLock();
|
||||
if (!locked_auth->HasUsers()) {
|
||||
return true;
|
||||
}
|
||||
if (username.has_value()) {
|
||||
maybe_user = locked_auth->GetUser(*username);
|
||||
}
|
||||
}
|
||||
|
||||
return maybe_user.has_value() && IsUserAuthorized(*maybe_user, privileges);
|
||||
}
|
||||
#ifdef MG_ENTERPRISE
|
||||
std::unique_ptr<memgraph::query::FineGrainedAuthChecker> AuthChecker::GetFineGrainedAuthChecker(
|
||||
const std::string &username, const memgraph::query::DbAccessor *dba) const {
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
if (!user) {
|
||||
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
|
||||
}
|
||||
|
||||
return std::make_unique<memgraph::glue::FineGrainedAuthChecker>(std::move(*user), dba);
|
||||
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool AuthChecker::IsUserAuthorized(const memgraph::auth::User &user,
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) {
|
||||
const auto user_permissions = user.GetPermissions();
|
||||
return std::all_of(privileges.begin(), privileges.end(), [&user_permissions](const auto privilege) {
|
||||
return user_permissions.Has(memgraph::glue::PrivilegeToPermission(privilege)) ==
|
||||
memgraph::auth::PermissionLevel::GRANT;
|
||||
});
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
FineGrainedAuthChecker::FineGrainedAuthChecker(auth::User user, const memgraph::query::DbAccessor *dba)
|
||||
: user_{std::move(user)}, dba_(dba){};
|
||||
|
||||
bool FineGrainedAuthChecker::Has(const memgraph::query::VertexAccessor &vertex, const memgraph::storage::View view,
|
||||
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const {
|
||||
auto maybe_labels = vertex.Labels(view);
|
||||
if (maybe_labels.HasError()) {
|
||||
switch (maybe_labels.GetError()) {
|
||||
case memgraph::storage::Error::DELETED_OBJECT:
|
||||
throw memgraph::query::QueryRuntimeException("Trying to get labels from a deleted node.");
|
||||
case memgraph::storage::Error::NONEXISTENT_OBJECT:
|
||||
throw memgraph::query::QueryRuntimeException("Trying to get labels from a node that doesn't exist.");
|
||||
case memgraph::storage::Error::SERIALIZATION_ERROR:
|
||||
case memgraph::storage::Error::VERTEX_HAS_EDGES:
|
||||
case memgraph::storage::Error::PROPERTIES_DISABLED:
|
||||
throw memgraph::query::QueryRuntimeException("Unexpected error when getting labels.");
|
||||
}
|
||||
}
|
||||
|
||||
return IsUserAuthorizedLabels(user_, dba_, *maybe_labels, fine_grained_privilege);
|
||||
}
|
||||
|
||||
bool FineGrainedAuthChecker::Has(const memgraph::query::EdgeAccessor &edge,
|
||||
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const {
|
||||
return IsUserAuthorizedEdgeType(user_, dba_, edge.EdgeType(), fine_grained_privilege);
|
||||
}
|
||||
|
||||
bool FineGrainedAuthChecker::Has(const std::vector<memgraph::storage::LabelId> &labels,
|
||||
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const {
|
||||
return IsUserAuthorizedLabels(user_, dba_, labels, fine_grained_privilege);
|
||||
}
|
||||
|
||||
bool FineGrainedAuthChecker::Has(const memgraph::storage::EdgeTypeId &edge_type,
|
||||
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const {
|
||||
return IsUserAuthorizedEdgeType(user_, dba_, edge_type, fine_grained_privilege);
|
||||
}
|
||||
|
||||
bool FineGrainedAuthChecker::HasGlobalPrivilegeOnVertices(
|
||||
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const {
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return true;
|
||||
}
|
||||
return IsUserAuthorizedGloballyLabels(user_, FineGrainedPrivilegeToFineGrainedPermission(fine_grained_privilege));
|
||||
}
|
||||
|
||||
bool FineGrainedAuthChecker::HasGlobalPrivilegeOnEdges(
|
||||
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const {
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return true;
|
||||
}
|
||||
return IsUserAuthorizedGloballyEdges(user_, FineGrainedPrivilegeToFineGrainedPermission(fine_grained_privilege));
|
||||
};
|
||||
#endif
|
||||
} // namespace memgraph::glue
|
||||
67
src/glue/auth_checker.hpp
Normal file
67
src/glue/auth_checker.hpp
Normal file
@@ -0,0 +1,67 @@
|
||||
// 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 "auth/auth.hpp"
|
||||
#include "glue/auth.hpp"
|
||||
#include "query/auth_checker.hpp"
|
||||
#include "query/db_accessor.hpp"
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
|
||||
namespace memgraph::glue {
|
||||
|
||||
class AuthChecker : public query::AuthChecker {
|
||||
public:
|
||||
explicit AuthChecker(
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth);
|
||||
|
||||
bool IsUserAuthorized(const std::optional<std::string> &username,
|
||||
const std::vector<query::AuthQuery::Privilege> &privileges) const override;
|
||||
#ifdef MG_ENTERPRISE
|
||||
std::unique_ptr<memgraph::query::FineGrainedAuthChecker> GetFineGrainedAuthChecker(
|
||||
const std::string &username, const memgraph::query::DbAccessor *dba) const override;
|
||||
#endif
|
||||
[[nodiscard]] static bool IsUserAuthorized(const memgraph::auth::User &user,
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges);
|
||||
|
||||
private:
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
|
||||
};
|
||||
#ifdef MG_ENTERPRISE
|
||||
class FineGrainedAuthChecker : public query::FineGrainedAuthChecker {
|
||||
public:
|
||||
explicit FineGrainedAuthChecker(auth::User user, const memgraph::query::DbAccessor *dba);
|
||||
|
||||
bool Has(const query::VertexAccessor &vertex, memgraph::storage::View view,
|
||||
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const override;
|
||||
|
||||
bool Has(const query::EdgeAccessor &edge,
|
||||
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const override;
|
||||
|
||||
bool Has(const std::vector<memgraph::storage::LabelId> &labels,
|
||||
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const override;
|
||||
|
||||
bool Has(const memgraph::storage::EdgeTypeId &edge_type,
|
||||
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const override;
|
||||
|
||||
bool HasGlobalPrivilegeOnVertices(
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const override;
|
||||
|
||||
bool HasGlobalPrivilegeOnEdges(
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const override;
|
||||
|
||||
private:
|
||||
auth::User user_;
|
||||
const memgraph::query::DbAccessor *dba_;
|
||||
};
|
||||
#endif
|
||||
} // namespace memgraph::glue
|
||||
664
src/glue/auth_handler.cpp
Normal file
664
src/glue/auth_handler.cpp
Normal file
@@ -0,0 +1,664 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "glue/auth_handler.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "auth/models.hpp"
|
||||
#include "glue/auth.hpp"
|
||||
#include "license/license.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
struct PermissionForPrivilegeResult {
|
||||
std::string permission;
|
||||
memgraph::auth::PermissionLevel permission_level;
|
||||
std::string description;
|
||||
};
|
||||
|
||||
struct FineGrainedPermissionForPrivilegeResult {
|
||||
std::string permission;
|
||||
#ifdef MG_ENTERPRISE
|
||||
memgraph::auth::FineGrainedPermission permission_level;
|
||||
#endif
|
||||
std::string description;
|
||||
};
|
||||
|
||||
PermissionForPrivilegeResult GetPermissionForPrivilegeForUserOrRole(
|
||||
const memgraph::auth::Permissions &permissions, const memgraph::query::AuthQuery::Privilege &privilege,
|
||||
const std::string &user_or_role) {
|
||||
PermissionForPrivilegeResult container;
|
||||
|
||||
const auto permission = memgraph::glue::PrivilegeToPermission(privilege);
|
||||
container.permission = memgraph::auth::PermissionToString(permission);
|
||||
container.permission_level = permissions.Has(permission);
|
||||
|
||||
switch (container.permission_level) {
|
||||
case memgraph::auth::PermissionLevel::GRANT:
|
||||
container.description = "GRANTED TO " + user_or_role;
|
||||
break;
|
||||
case memgraph::auth::PermissionLevel::DENY:
|
||||
container.description = "DENIED TO " + user_or_role;
|
||||
break;
|
||||
case memgraph::auth::PermissionLevel::NEUTRAL:
|
||||
break;
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
std::vector<std::vector<memgraph::query::TypedValue>> ConstructPrivilegesResult(
|
||||
const std::vector<PermissionForPrivilegeResult> &privileges) {
|
||||
std::vector<std::vector<memgraph::query::TypedValue>> grants;
|
||||
|
||||
grants.reserve(privileges.size());
|
||||
for (const auto &permission : privileges) {
|
||||
grants.push_back({memgraph::query::TypedValue(permission.permission),
|
||||
memgraph::query::TypedValue(memgraph::auth::PermissionLevelToString(permission.permission_level)),
|
||||
memgraph::query::TypedValue(permission.description)});
|
||||
}
|
||||
|
||||
return grants;
|
||||
}
|
||||
|
||||
std::vector<std::vector<memgraph::query::TypedValue>> ShowUserPrivileges(
|
||||
const std::optional<memgraph::auth::User> &user) {
|
||||
std::vector<PermissionForPrivilegeResult> privilege_results;
|
||||
|
||||
const auto &permissions = user->GetPermissions();
|
||||
const auto &user_level_permissions = user->permissions();
|
||||
|
||||
for (const auto &privilege : memgraph::query::kPrivilegesAll) {
|
||||
auto user_permission_result = GetPermissionForPrivilegeForUserOrRole(permissions, privilege, "USER");
|
||||
auto user_only_permissions_result =
|
||||
GetPermissionForPrivilegeForUserOrRole(user_level_permissions, privilege, "USER");
|
||||
|
||||
if (user_permission_result.permission_level != memgraph::auth::PermissionLevel::NEUTRAL) {
|
||||
std::vector<std::string> full_description;
|
||||
if (user_only_permissions_result.permission_level != memgraph::auth::PermissionLevel::NEUTRAL) {
|
||||
full_description.emplace_back(user_only_permissions_result.description);
|
||||
}
|
||||
|
||||
if (const auto *role = user->role(); role != nullptr) {
|
||||
auto role_permission_result = GetPermissionForPrivilegeForUserOrRole(role->permissions(), privilege, "ROLE");
|
||||
if (role_permission_result.permission_level != memgraph::auth::PermissionLevel::NEUTRAL) {
|
||||
full_description.emplace_back(role_permission_result.description);
|
||||
}
|
||||
}
|
||||
|
||||
privilege_results.push_back(PermissionForPrivilegeResult{user_permission_result.permission,
|
||||
user_permission_result.permission_level,
|
||||
memgraph::utils::Join(full_description, ", ")});
|
||||
}
|
||||
}
|
||||
|
||||
return ConstructPrivilegesResult(privilege_results);
|
||||
}
|
||||
|
||||
std::vector<std::vector<memgraph::query::TypedValue>> ShowRolePrivileges(
|
||||
const std::optional<memgraph::auth::Role> &role) {
|
||||
std::vector<PermissionForPrivilegeResult> privilege_results;
|
||||
const auto &permissions = role->permissions();
|
||||
for (const auto &privilege : memgraph::query::kPrivilegesAll) {
|
||||
auto role_permission_result = GetPermissionForPrivilegeForUserOrRole(permissions, privilege, "ROLE");
|
||||
if (role_permission_result.permission_level != memgraph::auth::PermissionLevel::NEUTRAL) {
|
||||
privilege_results.push_back(role_permission_result);
|
||||
}
|
||||
}
|
||||
|
||||
return ConstructPrivilegesResult(privilege_results);
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
std::vector<FineGrainedPermissionForPrivilegeResult> GetFineGrainedPermissionForPrivilegeForUserOrRole(
|
||||
const memgraph::auth::FineGrainedAccessPermissions &permissions, const std::string &permission_type,
|
||||
const std::string &user_or_role) {
|
||||
std::vector<FineGrainedPermissionForPrivilegeResult> fine_grained_permissions;
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return fine_grained_permissions;
|
||||
}
|
||||
const auto global_permission = permissions.GetGlobalPermission();
|
||||
if (global_permission.has_value()) {
|
||||
const auto &permission_level = memgraph::auth::PermissionToFineGrainedPermission(global_permission.value());
|
||||
|
||||
std::stringstream permission_representation;
|
||||
permission_representation << "ALL " << permission_type << "S";
|
||||
const auto &permission_level_representation =
|
||||
permission_level == memgraph::auth::FineGrainedPermission::NOTHING ? "DENIED" : "GRANTED";
|
||||
|
||||
const auto permission_description =
|
||||
fmt::format("GLOBAL {0} PERMISSION {1} TO {2}", permission_type, permission_level_representation, user_or_role);
|
||||
|
||||
fine_grained_permissions.push_back(FineGrainedPermissionForPrivilegeResult{
|
||||
permission_representation.str(), permission_level, permission_description});
|
||||
}
|
||||
|
||||
for (const auto &[label, permission] : permissions.GetPermissions()) {
|
||||
auto permission_level = memgraph::auth::PermissionToFineGrainedPermission(permission);
|
||||
|
||||
std::stringstream permission_representation;
|
||||
permission_representation << permission_type << " :" << label;
|
||||
|
||||
const auto &permission_level_representation =
|
||||
permission_level == memgraph::auth::FineGrainedPermission::NOTHING ? "DENIED" : "GRANTED";
|
||||
|
||||
const auto permission_description =
|
||||
fmt::format("{0} PERMISSION {1} TO {2}", permission_type, permission_level_representation, user_or_role);
|
||||
|
||||
fine_grained_permissions.push_back(FineGrainedPermissionForPrivilegeResult{
|
||||
permission_representation.str(), permission_level, permission_description});
|
||||
}
|
||||
|
||||
return fine_grained_permissions;
|
||||
}
|
||||
|
||||
std::vector<std::vector<memgraph::query::TypedValue>> ConstructFineGrainedPrivilegesResult(
|
||||
const std::vector<FineGrainedPermissionForPrivilegeResult> &privileges) {
|
||||
std::vector<std::vector<memgraph::query::TypedValue>> grants;
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return {};
|
||||
}
|
||||
grants.reserve(privileges.size());
|
||||
for (const auto &permission : privileges) {
|
||||
grants.push_back(
|
||||
{memgraph::query::TypedValue(permission.permission),
|
||||
memgraph::query::TypedValue(memgraph::auth::FineGrainedPermissionToString(permission.permission_level)),
|
||||
memgraph::query::TypedValue(permission.description)});
|
||||
}
|
||||
|
||||
return grants;
|
||||
}
|
||||
|
||||
std::vector<std::vector<memgraph::query::TypedValue>> ShowFineGrainedUserPrivileges(
|
||||
const std::optional<memgraph::auth::User> &user) {
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return {};
|
||||
}
|
||||
const auto &label_permissions = user->GetFineGrainedAccessLabelPermissions();
|
||||
const auto &edge_type_permissions = user->GetFineGrainedAccessEdgeTypePermissions();
|
||||
|
||||
auto all_fine_grained_permissions =
|
||||
GetFineGrainedPermissionForPrivilegeForUserOrRole(label_permissions, "LABEL", "USER");
|
||||
auto edge_type_fine_grained_permissions =
|
||||
GetFineGrainedPermissionForPrivilegeForUserOrRole(edge_type_permissions, "EDGE_TYPE", "USER");
|
||||
|
||||
all_fine_grained_permissions.insert(all_fine_grained_permissions.end(), edge_type_fine_grained_permissions.begin(),
|
||||
edge_type_fine_grained_permissions.end());
|
||||
|
||||
return ConstructFineGrainedPrivilegesResult(all_fine_grained_permissions);
|
||||
}
|
||||
|
||||
std::vector<std::vector<memgraph::query::TypedValue>> ShowFineGrainedRolePrivileges(
|
||||
const std::optional<memgraph::auth::Role> &role) {
|
||||
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
return {};
|
||||
}
|
||||
const auto &label_permissions = role->GetFineGrainedAccessLabelPermissions();
|
||||
const auto &edge_type_permissions = role->GetFineGrainedAccessEdgeTypePermissions();
|
||||
|
||||
auto all_fine_grained_permissions =
|
||||
GetFineGrainedPermissionForPrivilegeForUserOrRole(label_permissions, "LABEL", "USER");
|
||||
auto edge_type_fine_grained_permissions =
|
||||
GetFineGrainedPermissionForPrivilegeForUserOrRole(edge_type_permissions, "EDGE_TYPE", "USER");
|
||||
|
||||
all_fine_grained_permissions.insert(all_fine_grained_permissions.end(), edge_type_fine_grained_permissions.begin(),
|
||||
edge_type_fine_grained_permissions.end());
|
||||
|
||||
return ConstructFineGrainedPrivilegesResult(all_fine_grained_permissions);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace memgraph::glue {
|
||||
|
||||
AuthQueryHandler::AuthQueryHandler(
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
|
||||
std::string name_regex_string)
|
||||
: auth_(auth), name_regex_string_(std::move(name_regex_string)), name_regex_(name_regex_string_) {}
|
||||
|
||||
bool AuthQueryHandler::CreateUser(const std::string &username, const std::optional<std::string> &password) {
|
||||
if (name_regex_string_ != kDefaultUserRoleRegex) {
|
||||
if (const auto license_check_result =
|
||||
memgraph::license::global_license_checker.IsEnterpriseValid(memgraph::utils::global_settings);
|
||||
license_check_result.HasError()) {
|
||||
throw memgraph::auth::AuthException(
|
||||
"Custom user/role regex is a Memgraph Enterprise feature. Please set the config "
|
||||
"(\"--auth-user-or-role-name-regex\") to its default value (\"{}\") or remove the flag.\n{}",
|
||||
kDefaultUserRoleRegex,
|
||||
memgraph::license::LicenseCheckErrorToString(license_check_result.GetError(), "user/role regex"));
|
||||
}
|
||||
}
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
try {
|
||||
const auto [first_user, user_added] = std::invoke([&, this] {
|
||||
auto locked_auth = auth_->Lock();
|
||||
const auto first_user = !locked_auth->HasUsers();
|
||||
const auto user_added = locked_auth->AddUser(username, password).has_value();
|
||||
return std::make_pair(first_user, user_added);
|
||||
});
|
||||
|
||||
if (first_user) {
|
||||
spdlog::info("{} is first created user. Granting all privileges.", username);
|
||||
GrantPrivilege(username, memgraph::query::kPrivilegesAll
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
{{{memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE, {memgraph::auth::kAsterisk}}}},
|
||||
{
|
||||
{
|
||||
{
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE, {
|
||||
memgraph::auth::kAsterisk
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
return user_added;
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
bool AuthQueryHandler::DropUser(const std::string &username) {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
if (!user) return false;
|
||||
return locked_auth->RemoveUser(username);
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void AuthQueryHandler::SetPassword(const std::string &username, const std::optional<std::string> &password) {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
if (!user) {
|
||||
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist.", username);
|
||||
}
|
||||
user->UpdatePassword(password);
|
||||
locked_auth->SaveUser(*user);
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
bool AuthQueryHandler::CreateRole(const std::string &rolename) {
|
||||
if (!std::regex_match(rolename, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid role name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
return locked_auth->AddRole(rolename).has_value();
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
bool AuthQueryHandler::DropRole(const std::string &rolename) {
|
||||
if (!std::regex_match(rolename, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid role name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto role = locked_auth->GetRole(rolename);
|
||||
|
||||
if (!role) {
|
||||
return false;
|
||||
};
|
||||
|
||||
return locked_auth->RemoveRole(rolename);
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<memgraph::query::TypedValue> AuthQueryHandler::GetUsernames() {
|
||||
try {
|
||||
auto locked_auth = auth_->ReadLock();
|
||||
std::vector<memgraph::query::TypedValue> usernames;
|
||||
const auto &users = locked_auth->AllUsers();
|
||||
usernames.reserve(users.size());
|
||||
for (const auto &user : users) {
|
||||
usernames.emplace_back(user.username());
|
||||
}
|
||||
return usernames;
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<memgraph::query::TypedValue> AuthQueryHandler::GetRolenames() {
|
||||
try {
|
||||
auto locked_auth = auth_->ReadLock();
|
||||
std::vector<memgraph::query::TypedValue> rolenames;
|
||||
const auto &roles = locked_auth->AllRoles();
|
||||
rolenames.reserve(roles.size());
|
||||
for (const auto &role : roles) {
|
||||
rolenames.emplace_back(role.rolename());
|
||||
}
|
||||
return rolenames;
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> AuthQueryHandler::GetRolenameForUser(const std::string &username) {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->ReadLock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
if (!user) {
|
||||
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
|
||||
}
|
||||
|
||||
if (const auto *role = user->role(); role != nullptr) {
|
||||
return role->rolename();
|
||||
}
|
||||
return std::nullopt;
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<memgraph::query::TypedValue> AuthQueryHandler::GetUsernamesForRole(const std::string &rolename) {
|
||||
if (!std::regex_match(rolename, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid role name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->ReadLock();
|
||||
auto role = locked_auth->GetRole(rolename);
|
||||
if (!role) {
|
||||
throw memgraph::query::QueryRuntimeException("Role '{}' doesn't exist.", rolename);
|
||||
}
|
||||
std::vector<memgraph::query::TypedValue> usernames;
|
||||
const auto &users = locked_auth->AllUsersForRole(rolename);
|
||||
usernames.reserve(users.size());
|
||||
for (const auto &user : users) {
|
||||
usernames.emplace_back(user.username());
|
||||
}
|
||||
return usernames;
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void AuthQueryHandler::SetRole(const std::string &username, const std::string &rolename) {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
if (!std::regex_match(rolename, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid role name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
if (!user) {
|
||||
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
|
||||
}
|
||||
auto role = locked_auth->GetRole(rolename);
|
||||
if (!role) {
|
||||
throw memgraph::query::QueryRuntimeException("Role '{}' doesn't exist .", rolename);
|
||||
}
|
||||
if (const auto *current_role = user->role(); current_role != nullptr) {
|
||||
throw memgraph::query::QueryRuntimeException("User '{}' is already a member of role '{}'.", username,
|
||||
current_role->rolename());
|
||||
}
|
||||
user->SetRole(*role);
|
||||
locked_auth->SaveUser(*user);
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void AuthQueryHandler::ClearRole(const std::string &username) {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
if (!user) {
|
||||
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
|
||||
}
|
||||
user->ClearRole();
|
||||
locked_auth->SaveUser(*user);
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::vector<memgraph::query::TypedValue>> AuthQueryHandler::GetPrivileges(const std::string &user_or_role) {
|
||||
if (!std::regex_match(user_or_role, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user or role name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->ReadLock();
|
||||
std::vector<std::vector<memgraph::query::TypedValue>> grants;
|
||||
#ifdef MG_ENTERPRISE
|
||||
std::vector<std::vector<memgraph::query::TypedValue>> fine_grained_grants;
|
||||
#endif
|
||||
auto user = locked_auth->GetUser(user_or_role);
|
||||
auto role = locked_auth->GetRole(user_or_role);
|
||||
if (!user && !role) {
|
||||
throw memgraph::query::QueryRuntimeException("User or role '{}' doesn't exist.", user_or_role);
|
||||
}
|
||||
|
||||
if (user) {
|
||||
grants = ShowUserPrivileges(user);
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
fine_grained_grants = ShowFineGrainedUserPrivileges(user);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
grants = ShowRolePrivileges(role);
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
fine_grained_grants = ShowFineGrainedRolePrivileges(role);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
grants.insert(grants.end(), fine_grained_grants.begin(), fine_grained_grants.end());
|
||||
}
|
||||
#endif
|
||||
return grants;
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void AuthQueryHandler::GrantPrivilege(
|
||||
const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
|
||||
&label_privileges,
|
||||
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
|
||||
&edge_type_privileges
|
||||
#endif
|
||||
) {
|
||||
EditPermissions(
|
||||
user_or_role, privileges,
|
||||
#ifdef MG_ENTERPRISE
|
||||
label_privileges, edge_type_privileges,
|
||||
#endif
|
||||
[](auto &permissions, const auto &permission) {
|
||||
// TODO (mferencevic): should we first check that the
|
||||
// privilege is granted/denied/revoked before
|
||||
// unconditionally granting/denying/revoking it?
|
||||
permissions.Grant(permission);
|
||||
}
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
[](auto &fine_grained_permissions, const auto &privilege_collection) {
|
||||
for (const auto &[privilege, entities] : privilege_collection) {
|
||||
const auto &permission = memgraph::glue::FineGrainedPrivilegeToFineGrainedPermission(privilege);
|
||||
for (const auto &entity : entities) {
|
||||
fine_grained_permissions.Grant(entity, permission);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
);
|
||||
} // namespace memgraph::glue
|
||||
|
||||
void AuthQueryHandler::DenyPrivilege(const std::string &user_or_role,
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) {
|
||||
EditPermissions(
|
||||
user_or_role, privileges,
|
||||
#ifdef MG_ENTERPRISE
|
||||
{}, {},
|
||||
#endif
|
||||
[](auto &permissions, const auto &permission) {
|
||||
// TODO (mferencevic): should we first check that the
|
||||
// privilege is granted/denied/revoked before
|
||||
// unconditionally granting/denying/revoking it?
|
||||
permissions.Deny(permission);
|
||||
}
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
[](auto &fine_grained_permissions, const auto &privilege_collection) {}
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
void AuthQueryHandler::RevokePrivilege(
|
||||
const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
|
||||
&label_privileges,
|
||||
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
|
||||
&edge_type_privileges
|
||||
#endif
|
||||
) {
|
||||
EditPermissions(
|
||||
user_or_role, privileges,
|
||||
#ifdef MG_ENTERPRISE
|
||||
label_privileges, edge_type_privileges,
|
||||
#endif
|
||||
[](auto &permissions, const auto &permission) {
|
||||
// TODO (mferencevic): should we first check that the
|
||||
// privilege is granted/denied/revoked before
|
||||
// unconditionally granting/denying/revoking it?
|
||||
permissions.Revoke(permission);
|
||||
}
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
[](auto &fine_grained_permissions, const auto &privilege_collection) {
|
||||
for ([[maybe_unused]] const auto &[privilege, entities] : privilege_collection) {
|
||||
for (const auto &entity : entities) {
|
||||
fine_grained_permissions.Revoke(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
);
|
||||
} // namespace memgraph::glue
|
||||
|
||||
template <class TEditPermissionsFun
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
class TEditFineGrainedPermissionsFun
|
||||
#endif
|
||||
>
|
||||
void AuthQueryHandler::EditPermissions(
|
||||
const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
|
||||
&label_privileges,
|
||||
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
|
||||
&edge_type_privileges
|
||||
#endif
|
||||
,
|
||||
const TEditPermissionsFun &edit_permissions_fun
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
const TEditFineGrainedPermissionsFun &edit_fine_grained_permissions_fun
|
||||
#endif
|
||||
) {
|
||||
if (!std::regex_match(user_or_role, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user or role name.");
|
||||
}
|
||||
try {
|
||||
std::vector<memgraph::auth::Permission> permissions;
|
||||
permissions.reserve(privileges.size());
|
||||
for (const auto &privilege : privileges) {
|
||||
permissions.push_back(memgraph::glue::PrivilegeToPermission(privilege));
|
||||
}
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto user = locked_auth->GetUser(user_or_role);
|
||||
auto role = locked_auth->GetRole(user_or_role);
|
||||
if (!user && !role) {
|
||||
throw memgraph::query::QueryRuntimeException("User or role '{}' doesn't exist.", user_or_role);
|
||||
}
|
||||
if (user) {
|
||||
for (const auto &permission : permissions) {
|
||||
edit_permissions_fun(user->permissions(), permission);
|
||||
}
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
for (const auto &label_privilege_collection : label_privileges) {
|
||||
edit_fine_grained_permissions_fun(user->fine_grained_access_handler().label_permissions(),
|
||||
label_privilege_collection);
|
||||
}
|
||||
for (const auto &edge_type_privilege_collection : edge_type_privileges) {
|
||||
edit_fine_grained_permissions_fun(user->fine_grained_access_handler().edge_type_permissions(),
|
||||
edge_type_privilege_collection);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
locked_auth->SaveUser(*user);
|
||||
} else {
|
||||
for (const auto &permission : permissions) {
|
||||
edit_permissions_fun(role->permissions(), permission);
|
||||
}
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
for (const auto &label_privilege : label_privileges) {
|
||||
edit_fine_grained_permissions_fun(role->fine_grained_access_handler().label_permissions(), label_privilege);
|
||||
}
|
||||
for (const auto &edge_type_privilege : edge_type_privileges) {
|
||||
edit_fine_grained_permissions_fun(role->fine_grained_access_handler().edge_type_permissions(),
|
||||
edge_type_privilege);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
locked_auth->SaveRole(*role);
|
||||
}
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace memgraph::glue
|
||||
109
src/glue/auth_handler.hpp
Normal file
109
src/glue/auth_handler.hpp
Normal file
@@ -0,0 +1,109 @@
|
||||
// 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 <regex>
|
||||
|
||||
#include "auth/auth.hpp"
|
||||
#include "glue/auth.hpp"
|
||||
#include "license/license.hpp"
|
||||
#include "query/interpreter.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace memgraph::glue {
|
||||
|
||||
inline constexpr std::string_view kDefaultUserRoleRegex = "[a-zA-Z0-9_.+-@]+";
|
||||
|
||||
class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
|
||||
std::string name_regex_string_;
|
||||
std::regex name_regex_;
|
||||
|
||||
public:
|
||||
AuthQueryHandler(memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
|
||||
std::string name_regex_string);
|
||||
|
||||
bool CreateUser(const std::string &username, const std::optional<std::string> &password) override;
|
||||
|
||||
bool DropUser(const std::string &username) override;
|
||||
|
||||
void SetPassword(const std::string &username, const std::optional<std::string> &password) override;
|
||||
|
||||
bool CreateRole(const std::string &rolename) override;
|
||||
|
||||
bool DropRole(const std::string &rolename) override;
|
||||
|
||||
std::vector<memgraph::query::TypedValue> GetUsernames() override;
|
||||
|
||||
std::vector<memgraph::query::TypedValue> GetRolenames() override;
|
||||
|
||||
std::optional<std::string> GetRolenameForUser(const std::string &username) override;
|
||||
|
||||
std::vector<memgraph::query::TypedValue> GetUsernamesForRole(const std::string &rolename) override;
|
||||
|
||||
void SetRole(const std::string &username, const std::string &rolename) override;
|
||||
|
||||
void ClearRole(const std::string &username) override;
|
||||
|
||||
std::vector<std::vector<memgraph::query::TypedValue>> GetPrivileges(const std::string &user_or_role) override;
|
||||
|
||||
void GrantPrivilege(
|
||||
const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
|
||||
&label_privileges,
|
||||
|
||||
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
|
||||
&edge_type_privileges
|
||||
#endif
|
||||
) override;
|
||||
|
||||
void DenyPrivilege(const std::string &user_or_role,
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) override;
|
||||
|
||||
void RevokePrivilege(
|
||||
const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
|
||||
&label_privileges,
|
||||
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
|
||||
&edge_type_privileges
|
||||
#endif
|
||||
) override;
|
||||
|
||||
private:
|
||||
template <class TEditPermissionsFun
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
class TEditFineGrainedPermissionsFun
|
||||
#endif
|
||||
>
|
||||
void EditPermissions(
|
||||
const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
|
||||
&label_privileges,
|
||||
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
|
||||
&edge_type_privileges
|
||||
#endif
|
||||
,
|
||||
const TEditPermissionsFun &edit_permissions_fun
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
const TEditFineGrainedPermissionsFun &edit_fine_grained_permissions_fun
|
||||
#endif
|
||||
);
|
||||
};
|
||||
} // namespace memgraph::glue
|
||||
@@ -127,6 +127,10 @@ storage::Result<Value> ToBoltValue(const query::TypedValue &value, const storage
|
||||
return Value(value.ValueLocalDateTime());
|
||||
case query::TypedValue::Type::Duration:
|
||||
return Value(value.ValueDuration());
|
||||
case query::TypedValue::Type::Graph:
|
||||
auto maybe_graph = ToBoltGraph(value.ValueGraph(), db, view);
|
||||
if (maybe_graph.HasError()) return maybe_graph.GetError();
|
||||
return Value(std::move(*maybe_graph));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,6 +187,30 @@ storage::Result<communication::bolt::Path> ToBoltPath(const query::Path &path, c
|
||||
return communication::bolt::Path(vertices, edges);
|
||||
}
|
||||
|
||||
storage::Result<std::map<std::string, Value>> ToBoltGraph(const query::Graph &graph, const storage::Storage &db,
|
||||
storage::View view) {
|
||||
std::map<std::string, Value> map;
|
||||
std::vector<Value> vertices;
|
||||
vertices.reserve(graph.vertices().size());
|
||||
for (const auto &v : graph.vertices()) {
|
||||
auto maybe_vertex = ToBoltVertex(v, db, view);
|
||||
if (maybe_vertex.HasError()) return maybe_vertex.GetError();
|
||||
vertices.emplace_back(Value(std::move(*maybe_vertex)));
|
||||
}
|
||||
map.emplace("nodes", Value(vertices));
|
||||
|
||||
std::vector<Value> edges;
|
||||
edges.reserve(graph.edges().size());
|
||||
for (const auto &e : graph.edges()) {
|
||||
auto maybe_edge = ToBoltEdge(e, db, view);
|
||||
if (maybe_edge.HasError()) return maybe_edge.GetError();
|
||||
edges.emplace_back(Value(std::move(*maybe_edge)));
|
||||
}
|
||||
map.emplace("edges", Value(edges));
|
||||
|
||||
return std::move(map);
|
||||
}
|
||||
|
||||
storage::PropertyValue ToPropertyValue(const Value &value) {
|
||||
switch (value.type()) {
|
||||
case Value::Type::Null:
|
||||
|
||||
@@ -51,6 +51,15 @@ storage::Result<communication::bolt::Edge> ToBoltEdge(const storage::EdgeAccesso
|
||||
storage::Result<communication::bolt::Path> ToBoltPath(const query::Path &path, const storage::Storage &db,
|
||||
storage::View view);
|
||||
|
||||
/// @param query::Graph for converting to communication::bolt::Map.
|
||||
/// @param storage::Storage for ToBoltVertex and ToBoltEdge.
|
||||
/// @param storage::View for ToBoltVertex and ToBoltEdge.
|
||||
///
|
||||
/// @throw std::bad_alloc
|
||||
storage::Result<std::map<std::string, communication::bolt::Value>> ToBoltGraph(const query::Graph &graph,
|
||||
const storage::Storage &db,
|
||||
storage::View view);
|
||||
|
||||
/// @param query::TypedValue for converting to communication::bolt::Value.
|
||||
/// @param storage::Storage for ToBoltVertex and ToBoltEdge.
|
||||
/// @param storage::View for ToBoltVertex and ToBoltEdge.
|
||||
|
||||
@@ -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
|
||||
@@ -12,6 +12,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -61,3 +62,42 @@ inline void LoadConfig(const std::string &product_name) {
|
||||
for (int i = 0; i < custom_argc; ++i) free(custom_argv[i]);
|
||||
delete[] custom_argv;
|
||||
}
|
||||
|
||||
std::pair<std::string, std::string> LoadUsernameAndPassword(const std::string &pass_file) {
|
||||
std::ifstream file(pass_file);
|
||||
if (file.fail()) {
|
||||
spdlog::warn("Problem with opening MG_PASSFILE, memgraph server will start without user");
|
||||
return {};
|
||||
}
|
||||
std::vector<std::string> result;
|
||||
|
||||
std::string line;
|
||||
std::getline(file, line);
|
||||
size_t pos = 0;
|
||||
std::string token;
|
||||
static constexpr std::string_view delimiter{":"};
|
||||
while ((pos = line.find(delimiter)) != std::string::npos) {
|
||||
if (line[pos - 1] == '\\') {
|
||||
line.erase(pos - 1, 1);
|
||||
token += line.substr(0, pos);
|
||||
line.erase(0, pos);
|
||||
|
||||
} else {
|
||||
token += line.substr(0, pos);
|
||||
result.push_back(token);
|
||||
line.erase(0, pos + delimiter.length());
|
||||
token = "";
|
||||
}
|
||||
}
|
||||
result.push_back(line);
|
||||
file.close();
|
||||
|
||||
if (result.size() != 2) {
|
||||
spdlog::warn(
|
||||
"Wrong data format. Data should be store in format: username:password, memgraph server will start without "
|
||||
"user");
|
||||
return {};
|
||||
}
|
||||
|
||||
return {result[0], result[1]};
|
||||
}
|
||||
|
||||
6
src/license/CMakeLists.txt
Normal file
6
src/license/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
set(license_src_files
|
||||
license_sender.cpp
|
||||
license.cpp)
|
||||
add_library(mg-license STATIC ${license_src_files})
|
||||
|
||||
target_link_libraries(mg-license mg-settings mg-utils mg-requests spdlog::spdlog)
|
||||
@@ -9,17 +9,19 @@
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "utils/license.hpp"
|
||||
#include "license/license.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <charconv>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "slk/serialization.hpp"
|
||||
#include "utils/base64.hpp"
|
||||
#include "utils/cast.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
@@ -27,7 +29,7 @@
|
||||
#include "utils/spin_lock.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
|
||||
namespace memgraph::utils::license {
|
||||
namespace memgraph::license {
|
||||
|
||||
namespace {
|
||||
inline constexpr std::string_view license_key_prefix = "mglk-";
|
||||
@@ -69,6 +71,17 @@ LicenseCheckResult IsValidLicenseInternal(const License &license, const std::str
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::string LicenseTypeToString(const LicenseType license_type) {
|
||||
switch (license_type) {
|
||||
case LicenseType::ENTERPRISE: {
|
||||
return "enterprise";
|
||||
}
|
||||
case LicenseType::OEM: {
|
||||
return "oem";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterLicenseSettings(LicenseChecker &license_checker, utils::Settings &settings) {
|
||||
settings.RegisterSetting(std::string{kEnterpriseLicenseSettingKey}, "",
|
||||
[&] { license_checker.RevalidateLicense(settings); });
|
||||
@@ -81,7 +94,7 @@ LicenseChecker global_license_checker;
|
||||
|
||||
LicenseChecker::~LicenseChecker() { scheduler_.Stop(); }
|
||||
|
||||
std::pair<std::string, std::string> LicenseChecker::GetLicenseInfo(const utils::Settings &settings) const {
|
||||
std::pair<std::string, std::string> LicenseChecker::ExtractLicenseInfo(const utils::Settings &settings) const {
|
||||
if (license_info_override_) {
|
||||
spdlog::warn("Ignoring license info stored in the settings because a different source was specified.");
|
||||
return *license_info_override_;
|
||||
@@ -96,7 +109,7 @@ std::pair<std::string, std::string> LicenseChecker::GetLicenseInfo(const utils::
|
||||
}
|
||||
|
||||
void LicenseChecker::RevalidateLicense(const utils::Settings &settings) {
|
||||
const auto license_info = GetLicenseInfo(settings);
|
||||
const auto license_info = ExtractLicenseInfo(settings);
|
||||
RevalidateLicense(license_info.first, license_info.second);
|
||||
}
|
||||
|
||||
@@ -117,18 +130,7 @@ void LicenseChecker::RevalidateLicense(const std::string &license_key, const std
|
||||
return;
|
||||
}
|
||||
|
||||
struct PreviousLicenseInfo {
|
||||
PreviousLicenseInfo(std::string license_key, std::string organization_name)
|
||||
: license_key(std::move(license_key)), organization_name(std::move(organization_name)) {}
|
||||
|
||||
std::string license_key;
|
||||
std::string organization_name;
|
||||
bool is_valid{false};
|
||||
};
|
||||
|
||||
static utils::Synchronized<std::optional<PreviousLicenseInfo>, utils::SpinLock> previous_license_info;
|
||||
|
||||
auto locked_previous_license_info_ptr = previous_license_info.Lock();
|
||||
auto locked_previous_license_info_ptr = previous_license_info_.Lock();
|
||||
auto &locked_previous_license_info = *locked_previous_license_info_ptr;
|
||||
const bool same_license_info = locked_previous_license_info &&
|
||||
locked_previous_license_info->license_key == license_key &&
|
||||
@@ -140,7 +142,7 @@ void LicenseChecker::RevalidateLicense(const std::string &license_key, const std
|
||||
|
||||
locked_previous_license_info.emplace(license_key, organization_name);
|
||||
|
||||
const auto maybe_license = GetLicense(locked_previous_license_info->license_key);
|
||||
auto maybe_license = GetLicense(locked_previous_license_info->license_key);
|
||||
if (!maybe_license) {
|
||||
spdlog::warn(LicenseCheckErrorToString(LicenseCheckError::INVALID_LICENSE_KEY_STRING, "Enterprise features"));
|
||||
is_valid_.store(false, std::memory_order_relaxed);
|
||||
@@ -156,22 +158,30 @@ void LicenseChecker::RevalidateLicense(const std::string &license_key, const std
|
||||
spdlog::warn(LicenseCheckErrorToString(license_check_result.GetError(), "Enterprise features"));
|
||||
is_valid_.store(false, std::memory_order_relaxed);
|
||||
locked_previous_license_info->is_valid = false;
|
||||
license_type_ = maybe_license->type;
|
||||
set_memory_limit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!same_license_info) {
|
||||
spdlog::info("All Enterprise features are active.");
|
||||
license_type_ = maybe_license->type;
|
||||
if (license_type_ == LicenseType::ENTERPRISE) {
|
||||
spdlog::info("Enterprise license is active.");
|
||||
} else {
|
||||
spdlog::info("OEM license is active.");
|
||||
}
|
||||
is_valid_.store(true, std::memory_order_relaxed);
|
||||
locked_previous_license_info->is_valid = true;
|
||||
set_memory_limit(maybe_license->memory_limit);
|
||||
locked_previous_license_info->license = std::move(*maybe_license);
|
||||
}
|
||||
}
|
||||
|
||||
void LicenseChecker::EnableTesting() {
|
||||
void LicenseChecker::EnableTesting(const LicenseType license_type) {
|
||||
enterprise_enabled_ = true;
|
||||
is_valid_.store(true, std::memory_order_relaxed);
|
||||
spdlog::info("All Enterprise features are activated for testing.");
|
||||
license_type_ = license_type;
|
||||
spdlog::info("The license type {} is set for testing.", LicenseTypeToString(license_type));
|
||||
}
|
||||
|
||||
void LicenseChecker::CheckEnvLicense() {
|
||||
@@ -216,20 +226,26 @@ std::string LicenseCheckErrorToString(LicenseCheckError error, const std::string
|
||||
"following query:\n"
|
||||
"SET DATABASE SETTING \"enterprise.license\" TO \"your-license-key\"",
|
||||
feature);
|
||||
case LicenseCheckError::NOT_ENTERPRISE_LICENSE:
|
||||
return fmt::format("Your license has an invalid type. To use {} you need to have an enterprise license. \n",
|
||||
feature);
|
||||
}
|
||||
}
|
||||
|
||||
LicenseCheckResult LicenseChecker::IsValidLicense(const utils::Settings &settings) const {
|
||||
LicenseCheckResult LicenseChecker::IsEnterpriseValid(const utils::Settings &settings) const {
|
||||
if (enterprise_enabled_) [[unlikely]] {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto license_info = GetLicenseInfo(settings);
|
||||
const auto license_info = ExtractLicenseInfo(settings);
|
||||
|
||||
const auto maybe_license = GetLicense(license_info.first);
|
||||
if (!maybe_license) {
|
||||
return LicenseCheckError::INVALID_LICENSE_KEY_STRING;
|
||||
}
|
||||
if (maybe_license->type != LicenseType::ENTERPRISE) {
|
||||
return LicenseCheckError::NOT_ENTERPRISE_LICENSE;
|
||||
}
|
||||
|
||||
return IsValidLicenseInternal(*maybe_license, license_info.second);
|
||||
}
|
||||
@@ -239,7 +255,13 @@ void LicenseChecker::StartBackgroundLicenseChecker(const utils::Settings &settin
|
||||
scheduler_.Run("licensechecker", std::chrono::minutes{5}, [&, this] { RevalidateLicense(settings); });
|
||||
}
|
||||
|
||||
bool LicenseChecker::IsValidLicenseFast() const { return is_valid_.load(std::memory_order_relaxed); }
|
||||
utils::Synchronized<std::optional<LicenseInfo>, utils::SpinLock> &LicenseChecker::GetLicenseInfo() {
|
||||
return previous_license_info_;
|
||||
}
|
||||
|
||||
bool LicenseChecker::IsEnterpriseValidFast() const {
|
||||
return license_type_ == LicenseType::ENTERPRISE && is_valid_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
std::string Encode(const License &license) {
|
||||
std::vector<uint8_t> buffer;
|
||||
@@ -252,9 +274,10 @@ std::string Encode(const License &license) {
|
||||
slk::Save(license.organization_name, &builder);
|
||||
slk::Save(license.valid_until, &builder);
|
||||
slk::Save(license.memory_limit, &builder);
|
||||
slk::Save(utils::UnderlyingCast(license.type), &builder);
|
||||
builder.Finalize();
|
||||
|
||||
return std::string{license_key_prefix} + base64_encode(buffer.data(), buffer.size());
|
||||
return std::string{license_key_prefix} + utils::base64_encode(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
std::optional<License> Decode(std::string_view license_key) {
|
||||
@@ -266,7 +289,7 @@ std::optional<License> Decode(std::string_view license_key) {
|
||||
|
||||
const auto decoded = std::invoke([license_key]() -> std::optional<std::string> {
|
||||
try {
|
||||
return base64_decode(license_key);
|
||||
return utils::base64_decode(license_key);
|
||||
} catch (const std::runtime_error & /*exception*/) {
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -284,10 +307,12 @@ std::optional<License> Decode(std::string_view license_key) {
|
||||
slk::Load(&valid_until, &reader);
|
||||
int64_t memory_limit{0};
|
||||
slk::Load(&memory_limit, &reader);
|
||||
return License{.organization_name = organization_name, .valid_until = valid_until, .memory_limit = memory_limit};
|
||||
std::underlying_type_t<LicenseType> license_type{0};
|
||||
slk::Load(&license_type, &reader);
|
||||
return {License{organization_name, valid_until, memory_limit, LicenseType(license_type)}};
|
||||
} catch (const slk::SlkReaderException &e) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace memgraph::utils::license
|
||||
} // namespace memgraph::license
|
||||
@@ -12,26 +12,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "utils/result.hpp"
|
||||
#include "utils/scheduler.hpp"
|
||||
#include "utils/settings.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
|
||||
namespace memgraph::utils::license {
|
||||
namespace memgraph::license {
|
||||
|
||||
enum class LicenseType : uint8_t { ENTERPRISE, OEM };
|
||||
|
||||
std::string LicenseTypeToString(LicenseType license_type);
|
||||
|
||||
struct License {
|
||||
License() = default;
|
||||
|
||||
License(std::string organization_name, int64_t valid_until, int64_t memory_limit, LicenseType license_type)
|
||||
: organization_name{std::move(organization_name)},
|
||||
valid_until{valid_until},
|
||||
memory_limit{memory_limit},
|
||||
type{license_type} {}
|
||||
|
||||
std::string organization_name;
|
||||
int64_t valid_until;
|
||||
int64_t memory_limit;
|
||||
LicenseType type;
|
||||
|
||||
bool operator==(const License &) const = default;
|
||||
};
|
||||
|
||||
struct LicenseInfo {
|
||||
LicenseInfo(std::string license_key, std::string organization_name)
|
||||
: license_key(std::move(license_key)), organization_name{std::move(organization_name)} {}
|
||||
|
||||
std::string license_key;
|
||||
std::string organization_name;
|
||||
bool is_valid{false};
|
||||
License license;
|
||||
};
|
||||
|
||||
inline constexpr std::string_view kEnterpriseLicenseSettingKey = "enterprise.license";
|
||||
inline constexpr std::string_view kOrganizationNameSettingKey = "organization.name";
|
||||
|
||||
enum class LicenseCheckError : uint8_t { INVALID_LICENSE_KEY_STRING, INVALID_ORGANIZATION_NAME, EXPIRED_LICENSE };
|
||||
enum class LicenseCheckError : uint8_t {
|
||||
INVALID_LICENSE_KEY_STRING,
|
||||
INVALID_ORGANIZATION_NAME,
|
||||
EXPIRED_LICENSE,
|
||||
NOT_ENTERPRISE_LICENSE
|
||||
};
|
||||
|
||||
std::string LicenseCheckErrorToString(LicenseCheckError error, std::string_view feature);
|
||||
|
||||
@@ -49,19 +80,25 @@ struct LicenseChecker {
|
||||
|
||||
void CheckEnvLicense();
|
||||
void SetLicenseInfoOverride(std::string license_key, std::string organization_name);
|
||||
void EnableTesting();
|
||||
LicenseCheckResult IsValidLicense(const utils::Settings &settings) const;
|
||||
bool IsValidLicenseFast() const;
|
||||
void EnableTesting(LicenseType license_type = LicenseType::ENTERPRISE);
|
||||
// Checks if license is valid and if enterprise is enabled
|
||||
LicenseCheckResult IsEnterpriseValid(const utils::Settings &settings) const;
|
||||
bool IsEnterpriseValidFast() const;
|
||||
|
||||
void StartBackgroundLicenseChecker(const utils::Settings &settings);
|
||||
|
||||
utils::Synchronized<std::optional<LicenseInfo>, utils::SpinLock> &GetLicenseInfo();
|
||||
|
||||
private:
|
||||
std::pair<std::string, std::string> GetLicenseInfo(const utils::Settings &settings) const;
|
||||
std::pair<std::string, std::string> ExtractLicenseInfo(const utils::Settings &settings) const;
|
||||
void RevalidateLicense(const utils::Settings &settings);
|
||||
void RevalidateLicense(const std::string &license_key, const std::string &organization_name);
|
||||
|
||||
std::optional<std::pair<std::string, std::string>> license_info_override_;
|
||||
utils::Synchronized<std::optional<LicenseInfo>, utils::SpinLock> previous_license_info_{std::nullopt};
|
||||
bool enterprise_enabled_{false};
|
||||
std::atomic<bool> is_valid_{false};
|
||||
LicenseType license_type_;
|
||||
utils::Scheduler scheduler_;
|
||||
|
||||
friend void RegisterLicenseSettings(LicenseChecker &license_checker, utils::Settings &settings);
|
||||
@@ -74,4 +111,4 @@ std::string Encode(const License &license);
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
extern LicenseChecker global_license_checker;
|
||||
} // namespace memgraph::utils::license
|
||||
} // namespace memgraph::license
|
||||
71
src/license/license_sender.cpp
Normal file
71
src/license/license_sender.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "license/license_sender.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <cstdint>
|
||||
|
||||
#include "requests/requests.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
#include "utils/stat.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
#include "utils/system_info.hpp"
|
||||
#include "utils/timestamp.hpp"
|
||||
|
||||
namespace memgraph::license {
|
||||
|
||||
LicenseInfoSender::LicenseInfoSender(std::string url, std::string uuid, std::string machine_id, int64_t memory_limit,
|
||||
utils::Synchronized<std::optional<LicenseInfo>, utils::SpinLock> &license_info,
|
||||
std::chrono::seconds request_frequency)
|
||||
: url_{std::move(url)},
|
||||
uuid_{std::move(uuid)},
|
||||
machine_id_{std::move(machine_id)},
|
||||
memory_limit_{memory_limit},
|
||||
license_info_{license_info} {
|
||||
scheduler_.Run("LicenseCheck", request_frequency, [&] { SendData(); });
|
||||
}
|
||||
|
||||
LicenseInfoSender::~LicenseInfoSender() { scheduler_.Stop(); }
|
||||
|
||||
void LicenseInfoSender::SendData() {
|
||||
nlohmann::json data = nlohmann::json::object();
|
||||
|
||||
license_info_.WithLock([&data, this](const auto &license_info) mutable {
|
||||
if (license_info && !license_info->organization_name.empty()) {
|
||||
const auto memory_info = utils::GetMemoryInfo();
|
||||
const auto memory_usage = utils::GetMemoryUsage();
|
||||
data = {{"run_id", uuid_},
|
||||
{"machine_id", machine_id_},
|
||||
{"type", "license-check"},
|
||||
{"license_type", LicenseTypeToString(license_info->license.type)},
|
||||
{"license_key", license_info->license_key},
|
||||
{"organization", license_info->organization_name},
|
||||
{"valid", license_info->is_valid},
|
||||
{"physical_memory_size", memory_info.memory},
|
||||
{"swap_memory_size", memory_info.swap},
|
||||
{"memory_used", memory_usage},
|
||||
{"runtime_memory_limit", memory_limit_},
|
||||
{"license_memory_limit", license_info->license.memory_limit},
|
||||
{"timestamp", utils::Timestamp::Now().SecWithNsecSinceTheEpoch()}};
|
||||
}
|
||||
});
|
||||
|
||||
if (data.empty()) {
|
||||
return;
|
||||
}
|
||||
if (!requests::RequestPostJson(url_, data,
|
||||
/* timeout_in_seconds = */ 2 * 60)) {
|
||||
spdlog::trace("Cannot send license information, enable {} availability!", url_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace memgraph::license
|
||||
50
src/license/license_sender.hpp
Normal file
50
src/license/license_sender.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
// 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 <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include <json/json.hpp>
|
||||
|
||||
#include "license/license.hpp"
|
||||
#include "utils/scheduler.hpp"
|
||||
#include "utils/timer.hpp"
|
||||
|
||||
namespace memgraph::license {
|
||||
|
||||
class LicenseInfoSender final {
|
||||
public:
|
||||
LicenseInfoSender(std::string url, std::string uuid, std::string machine_id, int64_t memory_limit,
|
||||
utils::Synchronized<std::optional<LicenseInfo>, utils::SpinLock> &license_info,
|
||||
std::chrono::seconds request_frequency = std::chrono::seconds(8 * 60 * 60));
|
||||
|
||||
LicenseInfoSender(const LicenseInfoSender &) = delete;
|
||||
LicenseInfoSender(LicenseInfoSender &&) noexcept = delete;
|
||||
LicenseInfoSender &operator=(const LicenseInfoSender &) = delete;
|
||||
LicenseInfoSender &operator=(LicenseInfoSender &&) noexcept = delete;
|
||||
~LicenseInfoSender();
|
||||
|
||||
private:
|
||||
void SendData();
|
||||
|
||||
const std::string url_;
|
||||
const std::string uuid_;
|
||||
const std::string machine_id_;
|
||||
const int64_t memory_limit_;
|
||||
|
||||
utils::Synchronized<std::optional<LicenseInfo>, utils::SpinLock> &license_info_;
|
||||
utils::Scheduler scheduler_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::license
|
||||
532
src/memgraph.cpp
532
src/memgraph.cpp
@@ -19,12 +19,14 @@
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/format.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include <spdlog/common.h>
|
||||
@@ -32,10 +34,15 @@
|
||||
#include <spdlog/sinks/dist_sink.h>
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
|
||||
#include "auth/models.hpp"
|
||||
#include "communication/bolt/v1/constants.hpp"
|
||||
#include "communication/websocket/auth.hpp"
|
||||
#include "communication/websocket/server.hpp"
|
||||
#include "glue/auth_checker.hpp"
|
||||
#include "glue/auth_handler.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "license/license.hpp"
|
||||
#include "license/license_sender.hpp"
|
||||
#include "py/py.hpp"
|
||||
#include "query/auth_checker.hpp"
|
||||
#include "query/discard_value_stream.hpp"
|
||||
@@ -53,7 +60,6 @@
|
||||
#include "utils/event_counter.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/flag_validation.hpp"
|
||||
#include "utils/license.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
#include "utils/message.hpp"
|
||||
@@ -64,6 +70,7 @@
|
||||
#include "utils/string.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
#include "utils/sysinfo/memory.hpp"
|
||||
#include "utils/system_info.hpp"
|
||||
#include "utils/terminate_handler.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
@@ -92,6 +99,10 @@
|
||||
#include "audit/log.hpp"
|
||||
#endif
|
||||
|
||||
constexpr const char *kMgUser = "MEMGRAPH_USER";
|
||||
constexpr const char *kMgPassword = "MEMGRAPH_PASSWORD";
|
||||
constexpr const char *kMgPassfile = "MEMGRAPH_PASSFILE";
|
||||
|
||||
namespace {
|
||||
std::string GetAllowedEnumValuesString(const auto &mappings) {
|
||||
std::vector<std::string> allowed_values;
|
||||
@@ -128,6 +139,10 @@ std::optional<Enum> StringToEnum(const auto &value, const auto &mappings) {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Short help flag.
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_HIDDEN_bool(h, false, "Print usage and exit.");
|
||||
|
||||
// Bolt server flags.
|
||||
DEFINE_string(bolt_address, "0.0.0.0", "IP address on which the Bolt server should listen.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
@@ -157,6 +172,11 @@ DEFINE_string(bolt_key_file, "", "Key file which should be used for the Bolt ser
|
||||
DEFINE_string(bolt_server_name_for_init, "",
|
||||
"Server name which the database should send to the client in the "
|
||||
"Bolt INIT message.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(init_file, "",
|
||||
"Path to cypherl file that is used for configuring users and database schema before server starts.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(init_data_file, "", "Path to cypherl file that is used for creating data after server starts.");
|
||||
|
||||
// General purpose flags.
|
||||
// NOTE: The `data_directory` flag must be the same here and in
|
||||
@@ -164,8 +184,6 @@ DEFINE_string(bolt_server_name_for_init, "",
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(data_directory, "mg_data", "Path to directory in which to save all permanent data.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_HIDDEN_string(log_link_basename, "", "Basename used for symlink creation to the last log file.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_uint64(memory_warning_threshold, 1024,
|
||||
"Memory warning threshold, in MB. If Memgraph detects there is "
|
||||
"less available RAM it will log a warning. Set to 0 to "
|
||||
@@ -349,7 +367,8 @@ DEFINE_VALIDATED_string(query_modules_directory, "",
|
||||
});
|
||||
|
||||
// Logging flags
|
||||
DEFINE_bool(also_log_to_stderr, false, "Log messages go to stderr in addition to logfiles");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_HIDDEN_bool(also_log_to_stderr, false, "Log messages go to stderr in addition to logfiles");
|
||||
DEFINE_string(log_file, "", "Path to where the log should be stored.");
|
||||
|
||||
namespace {
|
||||
@@ -429,9 +448,9 @@ void AddLoggerSink(spdlog::sink_ptr new_sink) {
|
||||
} // namespace
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(license_key, "", "License key for Memgraph Enterprise.");
|
||||
DEFINE_HIDDEN_string(license_key, "", "License key for Memgraph Enterprise.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(organization_name, "", "Organization name.");
|
||||
DEFINE_HIDDEN_string(organization_name, "", "Organization name.");
|
||||
|
||||
/// Encapsulates Dbms and Interpreter that are passed through the network server
|
||||
/// and worker to the session.
|
||||
@@ -459,403 +478,40 @@ struct SessionData {
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth;
|
||||
|
||||
#endif
|
||||
// NOTE: run_id should be const but that complicates code a lot.
|
||||
std::optional<std::string> run_id;
|
||||
};
|
||||
|
||||
inline constexpr std::string_view default_user_role_regex = "[a-zA-Z0-9_.+-@]+";
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(auth_user_or_role_name_regex, default_user_role_regex.data(),
|
||||
DEFINE_string(auth_user_or_role_name_regex, memgraph::glue::kDefaultUserRoleRegex.data(),
|
||||
"Set to the regular expression that each user or role name must fulfill.");
|
||||
|
||||
class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
|
||||
std::string name_regex_string_;
|
||||
std::regex name_regex_;
|
||||
void InitFromCypherlFile(memgraph::query::InterpreterContext &ctx, std::string cypherl_file_path
|
||||
#ifdef MG_ENTERPRISE
|
||||
,
|
||||
memgraph::audit::Log *audit_log
|
||||
#endif
|
||||
) {
|
||||
memgraph::query::Interpreter interpreter(&ctx);
|
||||
std::ifstream file(cypherl_file_path);
|
||||
if (file.is_open()) {
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
if (!line.empty()) {
|
||||
auto results = interpreter.Prepare(line, {}, {});
|
||||
memgraph::query::DiscardValueResultStream stream;
|
||||
interpreter.Pull(&stream, {}, results.qid);
|
||||
|
||||
public:
|
||||
AuthQueryHandler(memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
|
||||
std::string name_regex_string)
|
||||
: auth_(auth), name_regex_string_(std::move(name_regex_string)), name_regex_(name_regex_string_) {}
|
||||
|
||||
bool CreateUser(const std::string &username, const std::optional<std::string> &password) override {
|
||||
if (name_regex_string_ != default_user_role_regex) {
|
||||
if (const auto license_check_result =
|
||||
memgraph::utils::license::global_license_checker.IsValidLicense(memgraph::utils::global_settings);
|
||||
license_check_result.HasError()) {
|
||||
throw memgraph::auth::AuthException(
|
||||
"Custom user/role regex is a Memgraph Enterprise feature. Please set the config "
|
||||
"(\"--auth-user-or-role-name-regex\") to its default value (\"{}\") or remove the flag.\n{}",
|
||||
default_user_role_regex,
|
||||
memgraph::utils::license::LicenseCheckErrorToString(license_check_result.GetError(), "user/role regex"));
|
||||
}
|
||||
}
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
try {
|
||||
const auto [first_user, user_added] = std::invoke([&, this] {
|
||||
auto locked_auth = auth_->Lock();
|
||||
const auto first_user = !locked_auth->HasUsers();
|
||||
const auto user_added = locked_auth->AddUser(username, password).has_value();
|
||||
return std::make_pair(first_user, user_added);
|
||||
});
|
||||
|
||||
if (first_user) {
|
||||
spdlog::info("{} is first created user. Granting all privileges.", username);
|
||||
GrantPrivilege(username, memgraph::query::kPrivilegesAll, {"*"});
|
||||
}
|
||||
|
||||
return user_added;
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
bool DropUser(const std::string &username) override {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
if (!user) return false;
|
||||
return locked_auth->RemoveUser(username);
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void SetPassword(const std::string &username, const std::optional<std::string> &password) override {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
if (!user) {
|
||||
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist.", username);
|
||||
}
|
||||
user->UpdatePassword(password);
|
||||
locked_auth->SaveUser(*user);
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
bool CreateRole(const std::string &rolename) override {
|
||||
if (!std::regex_match(rolename, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid role name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
return locked_auth->AddRole(rolename).has_value();
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
bool DropRole(const std::string &rolename) override {
|
||||
if (!std::regex_match(rolename, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid role name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto role = locked_auth->GetRole(rolename);
|
||||
if (!role) return false;
|
||||
return locked_auth->RemoveRole(rolename);
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<memgraph::query::TypedValue> GetUsernames() override {
|
||||
try {
|
||||
auto locked_auth = auth_->ReadLock();
|
||||
std::vector<memgraph::query::TypedValue> usernames;
|
||||
const auto &users = locked_auth->AllUsers();
|
||||
usernames.reserve(users.size());
|
||||
for (const auto &user : users) {
|
||||
usernames.emplace_back(user.username());
|
||||
}
|
||||
return usernames;
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<memgraph::query::TypedValue> GetRolenames() override {
|
||||
try {
|
||||
auto locked_auth = auth_->ReadLock();
|
||||
std::vector<memgraph::query::TypedValue> rolenames;
|
||||
const auto &roles = locked_auth->AllRoles();
|
||||
rolenames.reserve(roles.size());
|
||||
for (const auto &role : roles) {
|
||||
rolenames.emplace_back(role.rolename());
|
||||
}
|
||||
return rolenames;
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> GetRolenameForUser(const std::string &username) override {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->ReadLock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
if (!user) {
|
||||
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
|
||||
}
|
||||
|
||||
if (const auto *role = user->role(); role != nullptr) {
|
||||
return role->rolename();
|
||||
}
|
||||
return std::nullopt;
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<memgraph::query::TypedValue> GetUsernamesForRole(const std::string &rolename) override {
|
||||
if (!std::regex_match(rolename, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid role name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->ReadLock();
|
||||
auto role = locked_auth->GetRole(rolename);
|
||||
if (!role) {
|
||||
throw memgraph::query::QueryRuntimeException("Role '{}' doesn't exist.", rolename);
|
||||
}
|
||||
std::vector<memgraph::query::TypedValue> usernames;
|
||||
const auto &users = locked_auth->AllUsersForRole(rolename);
|
||||
usernames.reserve(users.size());
|
||||
for (const auto &user : users) {
|
||||
usernames.emplace_back(user.username());
|
||||
}
|
||||
return usernames;
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void SetRole(const std::string &username, const std::string &rolename) override {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
if (!std::regex_match(rolename, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid role name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
if (!user) {
|
||||
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
|
||||
}
|
||||
auto role = locked_auth->GetRole(rolename);
|
||||
if (!role) {
|
||||
throw memgraph::query::QueryRuntimeException("Role '{}' doesn't exist .", rolename);
|
||||
}
|
||||
if (const auto *current_role = user->role(); current_role != nullptr) {
|
||||
throw memgraph::query::QueryRuntimeException("User '{}' is already a member of role '{}'.", username,
|
||||
current_role->rolename());
|
||||
}
|
||||
user->SetRole(*role);
|
||||
locked_auth->SaveUser(*user);
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void ClearRole(const std::string &username) override {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto user = locked_auth->GetUser(username);
|
||||
if (!user) {
|
||||
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
|
||||
}
|
||||
user->ClearRole();
|
||||
locked_auth->SaveUser(*user);
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::vector<memgraph::query::TypedValue>> GetPrivileges(const std::string &user_or_role) override {
|
||||
if (!std::regex_match(user_or_role, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user or role name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->ReadLock();
|
||||
std::vector<std::vector<memgraph::query::TypedValue>> grants;
|
||||
auto user = locked_auth->GetUser(user_or_role);
|
||||
auto role = locked_auth->GetRole(user_or_role);
|
||||
if (!user && !role) {
|
||||
throw memgraph::query::QueryRuntimeException("User or role '{}' doesn't exist.", user_or_role);
|
||||
}
|
||||
if (user) {
|
||||
const auto &permissions = user->GetPermissions();
|
||||
for (const auto &privilege : memgraph::query::kPrivilegesAll) {
|
||||
auto permission = memgraph::glue::PrivilegeToPermission(privilege);
|
||||
auto effective = permissions.Has(permission);
|
||||
if (permissions.Has(permission) != memgraph::auth::PermissionLevel::NEUTRAL) {
|
||||
std::vector<std::string> description;
|
||||
auto user_level = user->permissions().Has(permission);
|
||||
if (user_level == memgraph::auth::PermissionLevel::GRANT) {
|
||||
description.emplace_back("GRANTED TO USER");
|
||||
} else if (user_level == memgraph::auth::PermissionLevel::DENY) {
|
||||
description.emplace_back("DENIED TO USER");
|
||||
}
|
||||
if (const auto *role = user->role(); role != nullptr) {
|
||||
auto role_level = role->permissions().Has(permission);
|
||||
if (role_level == memgraph::auth::PermissionLevel::GRANT) {
|
||||
description.emplace_back("GRANTED TO ROLE");
|
||||
} else if (role_level == memgraph::auth::PermissionLevel::DENY) {
|
||||
description.emplace_back("DENIED TO ROLE");
|
||||
}
|
||||
}
|
||||
grants.push_back({memgraph::query::TypedValue(memgraph::auth::PermissionToString(permission)),
|
||||
memgraph::query::TypedValue(memgraph::auth::PermissionLevelToString(effective)),
|
||||
memgraph::query::TypedValue(memgraph::utils::Join(description, ", "))});
|
||||
}
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
audit_log->Record("", "", line, {});
|
||||
}
|
||||
} else {
|
||||
const auto &permissions = role->permissions();
|
||||
for (const auto &privilege : memgraph::query::kPrivilegesAll) {
|
||||
auto permission = memgraph::glue::PrivilegeToPermission(privilege);
|
||||
auto effective = permissions.Has(permission);
|
||||
if (effective != memgraph::auth::PermissionLevel::NEUTRAL) {
|
||||
std::string description;
|
||||
if (effective == memgraph::auth::PermissionLevel::GRANT) {
|
||||
description = "GRANTED TO ROLE";
|
||||
} else if (effective == memgraph::auth::PermissionLevel::DENY) {
|
||||
description = "DENIED TO ROLE";
|
||||
}
|
||||
grants.push_back({memgraph::query::TypedValue(memgraph::auth::PermissionToString(permission)),
|
||||
memgraph::query::TypedValue(memgraph::auth::PermissionLevelToString(effective)),
|
||||
memgraph::query::TypedValue(description)});
|
||||
}
|
||||
}
|
||||
}
|
||||
return grants;
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void GrantPrivilege(const std::string &user_or_role,
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
|
||||
const std::vector<std::string> &labels) override {
|
||||
EditPermissions(user_or_role, privileges, labels, [](auto *permissions, const auto &permission) {
|
||||
// TODO (mferencevic): should we first check that the
|
||||
// privilege is granted/denied/revoked before
|
||||
// unconditionally granting/denying/revoking it?
|
||||
permissions->Grant(permission);
|
||||
});
|
||||
}
|
||||
|
||||
void DenyPrivilege(const std::string &user_or_role,
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
|
||||
const std::vector<std::string> &labels) override {
|
||||
EditPermissions(user_or_role, privileges, labels, [](auto *permissions, const auto &permission) {
|
||||
// TODO (mferencevic): should we first check that the
|
||||
// privilege is granted/denied/revoked before
|
||||
// unconditionally granting/denying/revoking it?
|
||||
permissions->Deny(permission);
|
||||
});
|
||||
}
|
||||
|
||||
void RevokePrivilege(const std::string &user_or_role,
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
|
||||
const std::vector<std::string> &labels) override {
|
||||
EditPermissions(user_or_role, privileges, labels, [](auto *permissions, const auto &permission) {
|
||||
// TODO (mferencevic): should we first check that the
|
||||
// privilege is granted/denied/revoked before
|
||||
// unconditionally granting/denying/revoking it?
|
||||
permissions->Revoke(permission);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
template <class TEditFun>
|
||||
void EditPermissions(const std::string &user_or_role,
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
|
||||
const std::vector<std::string> &labels, const TEditFun &edit_fun) {
|
||||
if (!std::regex_match(user_or_role, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user or role name.");
|
||||
}
|
||||
try {
|
||||
std::vector<memgraph::auth::Permission> permissions;
|
||||
permissions.reserve(privileges.size());
|
||||
for (const auto &privilege : privileges) {
|
||||
permissions.push_back(memgraph::glue::PrivilegeToPermission(privilege));
|
||||
}
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto user = locked_auth->GetUser(user_or_role);
|
||||
auto role = locked_auth->GetRole(user_or_role);
|
||||
if (!user && !role) {
|
||||
throw memgraph::query::QueryRuntimeException("User or role '{}' doesn't exist.", user_or_role);
|
||||
}
|
||||
if (user) {
|
||||
for (const auto &permission : permissions) {
|
||||
edit_fun(&user->permissions(), permission);
|
||||
}
|
||||
for (const auto &label : labels) {
|
||||
edit_fun(&user->labelPermissions(), label);
|
||||
}
|
||||
locked_auth->SaveUser(*user);
|
||||
} else {
|
||||
for (const auto &permission : permissions) {
|
||||
edit_fun(&role->permissions(), permission);
|
||||
}
|
||||
for (const auto &label : labels) {
|
||||
edit_fun(&role->labelPermissions(), label);
|
||||
}
|
||||
locked_auth->SaveRole(*role);
|
||||
}
|
||||
} catch (const memgraph::auth::AuthException &e) {
|
||||
throw memgraph::query::QueryRuntimeException(e.what());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class AuthChecker final : public memgraph::query::AuthChecker {
|
||||
public:
|
||||
explicit AuthChecker(
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth)
|
||||
: auth_{auth} {}
|
||||
|
||||
static bool IsUserAuthorized(const memgraph::auth::User &user,
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) {
|
||||
const auto user_permissions = user.GetPermissions();
|
||||
return std::all_of(privileges.begin(), privileges.end(), [&user_permissions](const auto privilege) {
|
||||
return user_permissions.Has(memgraph::glue::PrivilegeToPermission(privilege)) ==
|
||||
memgraph::auth::PermissionLevel::GRANT;
|
||||
});
|
||||
}
|
||||
|
||||
bool IsUserAuthorized(const std::optional<std::string> &username,
|
||||
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) const final {
|
||||
std::optional<memgraph::auth::User> maybe_user;
|
||||
{
|
||||
auto locked_auth = auth_->ReadLock();
|
||||
if (!locked_auth->HasUsers()) {
|
||||
return true;
|
||||
}
|
||||
if (username.has_value()) {
|
||||
maybe_user = locked_auth->GetUser(*username);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
return maybe_user.has_value() && IsUserAuthorized(*maybe_user, privileges);
|
||||
file.close();
|
||||
}
|
||||
|
||||
private:
|
||||
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
|
||||
};
|
||||
}
|
||||
|
||||
class BoltSession final : public memgraph::communication::bolt::Session<memgraph::communication::v2::InputStream,
|
||||
memgraph::communication::v2::OutputStream> {
|
||||
@@ -871,7 +527,8 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
|
||||
#if MG_ENTERPRISE
|
||||
audit_log_(data->audit_log),
|
||||
#endif
|
||||
endpoint_(endpoint) {
|
||||
endpoint_(endpoint),
|
||||
run_id_(data->run_id) {
|
||||
}
|
||||
|
||||
using memgraph::communication::bolt::Session<memgraph::communication::v2::InputStream,
|
||||
@@ -892,14 +549,14 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
|
||||
username = &user_->username();
|
||||
}
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
audit_log_->Record(endpoint_.address().to_string(), user_ ? *username : "", query,
|
||||
memgraph::storage::PropertyValue(params_pv));
|
||||
}
|
||||
#endif
|
||||
try {
|
||||
auto result = interpreter_.Prepare(query, params_pv, username);
|
||||
if (user_ && !AuthChecker::IsUserAuthorized(*user_, result.privileges)) {
|
||||
if (user_ && !memgraph::glue::AuthChecker::IsUserAuthorized(*user_, result.privileges)) {
|
||||
interpreter_.Abort();
|
||||
throw memgraph::communication::bolt::ClientError(
|
||||
"You are not authorized to execute this query! Please contact "
|
||||
@@ -911,6 +568,8 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
|
||||
// Wrap QueryException into ClientError, because we want to allow the
|
||||
// client to fix their query.
|
||||
throw memgraph::communication::bolt::ClientError(e.what());
|
||||
} catch (const memgraph::query::ReplicationException &e) {
|
||||
throw memgraph::communication::bolt::ClientError(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -963,6 +622,14 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
|
||||
}
|
||||
decoded_summary.emplace(kv.first, std::move(*maybe_value));
|
||||
}
|
||||
// Add this memgraph instance run_id, received from telemetry
|
||||
// This is sent with every query, instead of only on bolt init inside
|
||||
// communication/bolt/v1/states/init.hpp because neo4jdriver does not
|
||||
// read the init message.
|
||||
if (auto run_id = run_id_; run_id) {
|
||||
decoded_summary.emplace("run_id", *run_id);
|
||||
}
|
||||
|
||||
return decoded_summary;
|
||||
} catch (const memgraph::query::QueryException &e) {
|
||||
// Wrap QueryException into ClientError, because we want to allow the
|
||||
@@ -1014,6 +681,8 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
|
||||
memgraph::audit::Log *audit_log_;
|
||||
#endif
|
||||
memgraph::communication::v2::ServerEndpoint endpoint_;
|
||||
// NOTE: run_id should be const but that complicates code a lot.
|
||||
std::optional<std::string> run_id_;
|
||||
};
|
||||
|
||||
using ServerT = memgraph::communication::v2::Server<BoltSession, SessionData>;
|
||||
@@ -1059,6 +728,11 @@ int main(int argc, char **argv) {
|
||||
LoadConfig("memgraph");
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
if (FLAGS_h) {
|
||||
gflags::ShowUsageWithFlags(argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
InitializeLogger();
|
||||
|
||||
// Unhandled exception handler init.
|
||||
@@ -1153,15 +827,15 @@ int main(int argc, char **argv) {
|
||||
memgraph::utils::OnScopeExit settings_finalizer([&] { memgraph::utils::global_settings.Finalize(); });
|
||||
|
||||
// register all runtime settings
|
||||
memgraph::utils::license::RegisterLicenseSettings(memgraph::utils::license::global_license_checker,
|
||||
memgraph::utils::global_settings);
|
||||
memgraph::license::RegisterLicenseSettings(memgraph::license::global_license_checker,
|
||||
memgraph::utils::global_settings);
|
||||
|
||||
memgraph::utils::license::global_license_checker.CheckEnvLicense();
|
||||
memgraph::license::global_license_checker.CheckEnvLicense();
|
||||
if (!FLAGS_organization_name.empty() && !FLAGS_license_key.empty()) {
|
||||
memgraph::utils::license::global_license_checker.SetLicenseInfoOverride(FLAGS_license_key, FLAGS_organization_name);
|
||||
memgraph::license::global_license_checker.SetLicenseInfoOverride(FLAGS_license_key, FLAGS_organization_name);
|
||||
}
|
||||
|
||||
memgraph::utils::license::global_license_checker.StartBackgroundLicenseChecker(memgraph::utils::global_settings);
|
||||
memgraph::license::global_license_checker.StartBackgroundLicenseChecker(memgraph::utils::global_settings);
|
||||
|
||||
// All enterprise features should be constructed before the main database
|
||||
// storage. This will cause them to be destructed *after* the main database
|
||||
@@ -1205,7 +879,8 @@ int main(int argc, char **argv) {
|
||||
.snapshot_retention_count = FLAGS_storage_snapshot_retention_count,
|
||||
.wal_file_size_kibibytes = FLAGS_storage_wal_file_size_kib,
|
||||
.wal_file_flush_every_n_tx = FLAGS_storage_wal_file_flush_every_n_tx,
|
||||
.snapshot_on_exit = FLAGS_storage_snapshot_on_exit},
|
||||
.snapshot_on_exit = FLAGS_storage_snapshot_on_exit,
|
||||
.restore_replicas_on_startup = true},
|
||||
.transaction = {.isolation_level = ParseIsolationLevel()}};
|
||||
if (FLAGS_storage_snapshot_interval_sec == 0) {
|
||||
if (FLAGS_storage_wal_enabled) {
|
||||
@@ -1246,19 +921,41 @@ int main(int argc, char **argv) {
|
||||
memgraph::query::procedure::gModuleRegistry.SetModulesDirectory(query_modules_directories, FLAGS_data_directory);
|
||||
memgraph::query::procedure::gModuleRegistry.UnloadAndLoadModulesFromDirectories();
|
||||
|
||||
AuthQueryHandler auth_handler(&auth, FLAGS_auth_user_or_role_name_regex);
|
||||
AuthChecker auth_checker{&auth};
|
||||
memgraph::glue::AuthQueryHandler auth_handler(&auth, FLAGS_auth_user_or_role_name_regex);
|
||||
memgraph::glue::AuthChecker auth_checker{&auth};
|
||||
interpreter_context.auth = &auth_handler;
|
||||
interpreter_context.auth_checker = &auth_checker;
|
||||
|
||||
if (!FLAGS_init_file.empty()) {
|
||||
spdlog::info("Running init file.");
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
InitFromCypherlFile(interpreter_context, FLAGS_init_file, &audit_log);
|
||||
}
|
||||
#else
|
||||
InitFromCypherlFile(interpreter_context, FLAGS_init_file);
|
||||
#endif
|
||||
}
|
||||
|
||||
auto *maybe_username = std::getenv(kMgUser);
|
||||
auto *maybe_password = std::getenv(kMgPassword);
|
||||
auto *maybe_pass_file = std::getenv(kMgPassfile);
|
||||
if (maybe_username && maybe_password) {
|
||||
auth_handler.CreateUser(maybe_username, maybe_password);
|
||||
} else if (maybe_pass_file) {
|
||||
const auto [username, password] = LoadUsernameAndPassword(maybe_pass_file);
|
||||
if (!username.empty() && !password.empty()) {
|
||||
auth_handler.CreateUser(username, password);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Triggers can execute query procedures, so we need to reload the modules first and then
|
||||
// the triggers
|
||||
auto storage_accessor = interpreter_context.db->Access();
|
||||
auto dba = memgraph::query::DbAccessor{&storage_accessor};
|
||||
interpreter_context.trigger_store.RestoreTriggers(&interpreter_context.ast_cache, &dba,
|
||||
&interpreter_context.antlr_lock, interpreter_context.config.query,
|
||||
interpreter_context.auth_checker);
|
||||
interpreter_context.trigger_store.RestoreTriggers(
|
||||
&interpreter_context.ast_cache, &dba, interpreter_context.config.query, interpreter_context.auth_checker);
|
||||
}
|
||||
|
||||
// As the Stream transformations are using modules, they have to be restored after the query modules are loaded.
|
||||
@@ -1280,11 +977,15 @@ int main(int argc, char **argv) {
|
||||
ServerT server(server_endpoint, &session_data, &context, FLAGS_bolt_session_inactivity_timeout, service_name,
|
||||
FLAGS_bolt_num_workers);
|
||||
|
||||
const auto run_id = memgraph::utils::GenerateUUID();
|
||||
const auto machine_id = memgraph::utils::GetMachineId();
|
||||
session_data.run_id = run_id;
|
||||
|
||||
// Setup telemetry
|
||||
static constexpr auto telemetry_server{"https://telemetry.memgraph.com/88b5e7e8-746a-11e8-9f85-538a9e9690cc/"};
|
||||
std::optional<memgraph::telemetry::Telemetry> telemetry;
|
||||
if (FLAGS_telemetry_enabled) {
|
||||
telemetry.emplace("https://telemetry.memgraph.com/88b5e7e8-746a-11e8-9f85-538a9e9690cc/",
|
||||
data_directory / "telemetry", std::chrono::minutes(10));
|
||||
telemetry.emplace(telemetry_server, data_directory / "telemetry", run_id, machine_id, std::chrono::minutes(10));
|
||||
telemetry->AddCollector("storage", [&db]() -> nlohmann::json {
|
||||
auto info = db.GetInfo();
|
||||
return {{"vertices", info.vertex_count}, {"edges", info.edge_count}};
|
||||
@@ -1300,6 +1001,8 @@ int main(int argc, char **argv) {
|
||||
return memgraph::query::plan::CallProcedure::GetAndResetCounters();
|
||||
});
|
||||
}
|
||||
memgraph::license::LicenseInfoSender license_info_sender(telemetry_server, run_id, machine_id, memory_limit,
|
||||
memgraph::license::global_license_checker.GetLicenseInfo());
|
||||
|
||||
memgraph::communication::websocket::SafeAuth websocket_auth{&auth};
|
||||
memgraph::communication::websocket::Server websocket_server{
|
||||
@@ -1323,6 +1026,17 @@ int main(int argc, char **argv) {
|
||||
MG_ASSERT(server.Start(), "Couldn't start the Bolt server!");
|
||||
websocket_server.Start();
|
||||
|
||||
if (!FLAGS_init_data_file.empty()) {
|
||||
spdlog::info("Running init data file.");
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||
InitFromCypherlFile(interpreter_context, FLAGS_init_data_file, &audit_log);
|
||||
}
|
||||
#else
|
||||
InitFromCypherlFile(interpreter_context, FLAGS_init_data_file);
|
||||
#endif
|
||||
}
|
||||
|
||||
server.AwaitShutdown();
|
||||
websocket_server.AwaitShutdown();
|
||||
|
||||
|
||||
@@ -39,7 +39,9 @@ set(mg_query_sources
|
||||
stream/common.cpp
|
||||
trigger.cpp
|
||||
trigger_context.cpp
|
||||
typed_value.cpp)
|
||||
typed_value.cpp
|
||||
graph.cpp
|
||||
db_accessor.cpp)
|
||||
|
||||
find_package(Boost REQUIRED)
|
||||
|
||||
@@ -82,7 +84,7 @@ add_custom_command(
|
||||
OUTPUT ${antlr_opencypher_generated_src} ${antlr_opencypher_generated_include}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${opencypher_generated}
|
||||
COMMAND
|
||||
java -jar ${CMAKE_SOURCE_DIR}/libs/antlr-4.9.2-complete.jar
|
||||
java -jar ${CMAKE_SOURCE_DIR}/libs/antlr-4.10.1-complete.jar
|
||||
-Dlanguage=Cpp -visitor -package antlropencypher
|
||||
-o ${opencypher_generated}
|
||||
${opencypher_lexer_grammar} ${opencypher_parser_grammar}
|
||||
|
||||
@@ -11,19 +11,97 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "query/db_accessor.hpp"
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
#include "storage/v2/id_types.hpp"
|
||||
|
||||
namespace memgraph::query {
|
||||
|
||||
class FineGrainedAuthChecker;
|
||||
|
||||
class AuthChecker {
|
||||
public:
|
||||
virtual bool IsUserAuthorized(const std::optional<std::string> &username,
|
||||
const std::vector<query::AuthQuery::Privilege> &privileges) const = 0;
|
||||
virtual ~AuthChecker() = default;
|
||||
|
||||
[[nodiscard]] virtual bool IsUserAuthorized(const std::optional<std::string> &username,
|
||||
const std::vector<query::AuthQuery::Privilege> &privileges) const = 0;
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
[[nodiscard]] virtual std::unique_ptr<FineGrainedAuthChecker> GetFineGrainedAuthChecker(
|
||||
const std::string &username, const memgraph::query::DbAccessor *db_accessor) const = 0;
|
||||
#endif
|
||||
};
|
||||
#ifdef MG_ENTERPRISE
|
||||
class FineGrainedAuthChecker {
|
||||
public:
|
||||
virtual ~FineGrainedAuthChecker() = default;
|
||||
|
||||
[[nodiscard]] virtual bool Has(const query::VertexAccessor &vertex, memgraph::storage::View view,
|
||||
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const = 0;
|
||||
|
||||
[[nodiscard]] virtual bool Has(const query::EdgeAccessor &edge,
|
||||
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const = 0;
|
||||
|
||||
[[nodiscard]] virtual bool Has(const std::vector<memgraph::storage::LabelId> &labels,
|
||||
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const = 0;
|
||||
|
||||
[[nodiscard]] virtual bool Has(const memgraph::storage::EdgeTypeId &edge_type,
|
||||
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const = 0;
|
||||
|
||||
[[nodiscard]] virtual bool HasGlobalPrivilegeOnVertices(
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const = 0;
|
||||
|
||||
[[nodiscard]] virtual bool HasGlobalPrivilegeOnEdges(
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const = 0;
|
||||
};
|
||||
|
||||
class AllowEverythingAuthChecker final : public query::AuthChecker {
|
||||
bool IsUserAuthorized(const std::optional<std::string> &username,
|
||||
const std::vector<query::AuthQuery::Privilege> &privileges) const override {
|
||||
class AllowEverythingFineGrainedAuthChecker final : public query::FineGrainedAuthChecker {
|
||||
public:
|
||||
bool Has(const VertexAccessor & /*vertex*/, const memgraph::storage::View /*view*/,
|
||||
const query::AuthQuery::FineGrainedPrivilege /*fine_grained_privilege*/) const override {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
} // namespace memgraph::query
|
||||
|
||||
bool Has(const memgraph::query::EdgeAccessor & /*edge*/,
|
||||
const query::AuthQuery::FineGrainedPrivilege /*fine_grained_privilege*/) const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Has(const std::vector<memgraph::storage::LabelId> & /*labels*/,
|
||||
const query::AuthQuery::FineGrainedPrivilege /*fine_grained_privilege*/) const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Has(const memgraph::storage::EdgeTypeId & /*edge_type*/,
|
||||
const query::AuthQuery::FineGrainedPrivilege /*fine_grained_privilege*/) const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HasGlobalPrivilegeOnVertices(
|
||||
const memgraph::query::AuthQuery::FineGrainedPrivilege /*fine_grained_privilege*/) const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HasGlobalPrivilegeOnEdges(
|
||||
const memgraph::query::AuthQuery::FineGrainedPrivilege /*fine_grained_privilege*/) const override {
|
||||
return true;
|
||||
}
|
||||
}; // namespace memgraph::query
|
||||
#endif
|
||||
|
||||
class AllowEverythingAuthChecker final : public query::AuthChecker {
|
||||
public:
|
||||
bool IsUserAuthorized(const std::optional<std::string> & /*username*/,
|
||||
const std::vector<query::AuthQuery::Privilege> & /*privileges*/) const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
std::unique_ptr<FineGrainedAuthChecker> GetFineGrainedAuthChecker(const std::string & /*username*/,
|
||||
const query::DbAccessor * /*dba*/) const override {
|
||||
return std::make_unique<AllowEverythingFineGrainedAuthChecker>();
|
||||
}
|
||||
#endif
|
||||
}; // namespace memgraph::query
|
||||
|
||||
} // namespace memgraph::query
|
||||
|
||||
@@ -61,6 +61,7 @@ bool TypedValueCompare(const TypedValue &a, const TypedValue &b) {
|
||||
case TypedValue::Type::Vertex:
|
||||
case TypedValue::Type::Edge:
|
||||
case TypedValue::Type::Path:
|
||||
case TypedValue::Type::Graph:
|
||||
throw QueryRuntimeException("Comparison is not defined for values of type {}.", a.type());
|
||||
case TypedValue::Type::Null:
|
||||
LOG_FATAL("Invalid type");
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
#include "query/common.hpp"
|
||||
@@ -72,6 +73,9 @@ struct ExecutionContext {
|
||||
ExecutionStats execution_stats;
|
||||
TriggerContextCollector *trigger_context_collector{nullptr};
|
||||
utils::AsyncTimer timer;
|
||||
#ifdef MG_ENTERPRISE
|
||||
std::unique_ptr<FineGrainedAuthChecker> auth_checker{nullptr};
|
||||
#endif
|
||||
};
|
||||
|
||||
static_assert(std::is_move_assignable_v<ExecutionContext>, "ExecutionContext must be move assignable!");
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "query/cypher_query_interpreter.hpp"
|
||||
|
||||
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_HIDDEN_bool(query_cost_planner, true, "Use the cost-estimating query planner.");
|
||||
DEFINE_bool(query_cost_planner, true, "Use the cost-estimating query planner.");
|
||||
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_VALIDATED_int32(query_plan_cache_ttl, 60, "Time to live for cached query plans, in seconds.",
|
||||
FLAG_IN_RANGE(0, std::numeric_limits<int32_t>::max()));
|
||||
@@ -21,8 +21,7 @@ namespace memgraph::query {
|
||||
CachedPlan::CachedPlan(std::unique_ptr<LogicalPlan> plan) : plan_(std::move(plan)) {}
|
||||
|
||||
ParsedQuery ParseQuery(const std::string &query_string, const std::map<std::string, storage::PropertyValue> ¶ms,
|
||||
utils::SkipList<QueryCacheEntry> *cache, utils::SpinLock *antlr_lock,
|
||||
const InterpreterConfig::Query &query_config) {
|
||||
utils::SkipList<QueryCacheEntry> *cache, const InterpreterConfig::Query &query_config) {
|
||||
// Strip the query for caching purposes. The process of stripping a query
|
||||
// "normalizes" it by replacing any literals with new parameters. This
|
||||
// results in just the *structure* of the query being taken into account for
|
||||
@@ -63,20 +62,16 @@ ParsedQuery ParseQuery(const std::string &query_string, const std::map<std::stri
|
||||
};
|
||||
|
||||
if (it == accessor.end()) {
|
||||
{
|
||||
std::unique_lock<utils::SpinLock> guard(*antlr_lock);
|
||||
try {
|
||||
parser = std::make_unique<frontend::opencypher::Parser>(stripped_query.query());
|
||||
} catch (const SyntaxException &e) {
|
||||
// There is a syntax exception in the stripped query. Re-run the parser
|
||||
// on the original query to get an appropriate error messsage.
|
||||
parser = std::make_unique<frontend::opencypher::Parser>(query_string);
|
||||
|
||||
try {
|
||||
parser = std::make_unique<frontend::opencypher::Parser>(stripped_query.query());
|
||||
} catch (const SyntaxException &e) {
|
||||
// There is a syntax exception in the stripped query. Re-run the parser
|
||||
// on the original query to get an appropriate error messsage.
|
||||
parser = std::make_unique<frontend::opencypher::Parser>(query_string);
|
||||
|
||||
// If an exception was not thrown here, the stripper messed something
|
||||
// up.
|
||||
LOG_FATAL("The stripped query can't be parsed, but the original can.");
|
||||
}
|
||||
// If an exception was not thrown here, the stripper messed something
|
||||
// up.
|
||||
LOG_FATAL("The stripped query can't be parsed, but the original can.");
|
||||
}
|
||||
|
||||
// Convert the ANTLR4 parse tree into an AST.
|
||||
|
||||
@@ -111,8 +111,7 @@ struct ParsedQuery {
|
||||
};
|
||||
|
||||
ParsedQuery ParseQuery(const std::string &query_string, const std::map<std::string, storage::PropertyValue> ¶ms,
|
||||
utils::SkipList<QueryCacheEntry> *cache, utils::SpinLock *antlr_lock,
|
||||
const InterpreterConfig::Query &query_config);
|
||||
utils::SkipList<QueryCacheEntry> *cache, const InterpreterConfig::Query &query_config);
|
||||
|
||||
class SingleNodeLogicalPlan final : public LogicalPlan {
|
||||
public:
|
||||
|
||||
148
src/query/db_accessor.cpp
Normal file
148
src/query/db_accessor.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include "query/db_accessor.hpp"
|
||||
|
||||
#include "query/graph.hpp"
|
||||
|
||||
#include <cppitertools/filter.hpp>
|
||||
#include <cppitertools/imap.hpp>
|
||||
#include "utils/pmr/unordered_set.hpp"
|
||||
|
||||
namespace memgraph::query {
|
||||
SubgraphDbAccessor::SubgraphDbAccessor(query::DbAccessor db_accessor, Graph *graph)
|
||||
: db_accessor_(db_accessor), graph_(graph) {}
|
||||
|
||||
storage::PropertyId SubgraphDbAccessor::NameToProperty(const std::string_view name) {
|
||||
return db_accessor_.NameToProperty(name);
|
||||
}
|
||||
|
||||
storage::LabelId SubgraphDbAccessor::NameToLabel(const std::string_view name) { return db_accessor_.NameToLabel(name); }
|
||||
|
||||
storage::EdgeTypeId SubgraphDbAccessor::NameToEdgeType(const std::string_view name) {
|
||||
return db_accessor_.NameToEdgeType(name);
|
||||
}
|
||||
|
||||
const std::string &SubgraphDbAccessor::PropertyToName(storage::PropertyId prop) const {
|
||||
return db_accessor_.PropertyToName(prop);
|
||||
}
|
||||
|
||||
const std::string &SubgraphDbAccessor::LabelToName(storage::LabelId label) const {
|
||||
return db_accessor_.LabelToName(label);
|
||||
}
|
||||
|
||||
const std::string &SubgraphDbAccessor::EdgeTypeToName(storage::EdgeTypeId type) const {
|
||||
return db_accessor_.EdgeTypeToName(type);
|
||||
}
|
||||
|
||||
storage::Result<std::optional<EdgeAccessor>> SubgraphDbAccessor::RemoveEdge(EdgeAccessor *edge) {
|
||||
if (!this->graph_->ContainsEdge(*edge)) {
|
||||
throw std::logic_error{"Projected graph must contain edge!"};
|
||||
}
|
||||
auto result = db_accessor_.RemoveEdge(edge);
|
||||
if (result.HasError() || !*result) {
|
||||
return result;
|
||||
}
|
||||
return this->graph_->RemoveEdge(*edge);
|
||||
}
|
||||
|
||||
storage::Result<EdgeAccessor> SubgraphDbAccessor::InsertEdge(SubgraphVertexAccessor *from, SubgraphVertexAccessor *to,
|
||||
const storage::EdgeTypeId &edge_type) {
|
||||
VertexAccessor *from_impl = &from->impl_;
|
||||
VertexAccessor *to_impl = &to->impl_;
|
||||
if (!this->graph_->ContainsVertex(*from_impl) || !this->graph_->ContainsVertex(*to_impl)) {
|
||||
throw std::logic_error{"Projected graph must contain both vertices to insert edge!"};
|
||||
}
|
||||
auto result = db_accessor_.InsertEdge(from_impl, to_impl, edge_type);
|
||||
if (result.HasError()) {
|
||||
return result;
|
||||
}
|
||||
this->graph_->InsertEdge(*result);
|
||||
return result;
|
||||
}
|
||||
|
||||
storage::Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>>
|
||||
SubgraphDbAccessor::DetachRemoveVertex( // NOLINT(readability-convert-member-functions-to-static)
|
||||
SubgraphVertexAccessor *) { // NOLINT(hicpp-named-parameter)
|
||||
throw std::logic_error{
|
||||
"Vertex holds only partial information about edges. Cannot detach delete safely while using projected graph."};
|
||||
}
|
||||
|
||||
storage::Result<std::optional<VertexAccessor>> SubgraphDbAccessor::RemoveVertex(
|
||||
SubgraphVertexAccessor *subgraphvertex_accessor) {
|
||||
VertexAccessor *vertex_accessor = &subgraphvertex_accessor->impl_;
|
||||
if (!this->graph_->ContainsVertex(*vertex_accessor)) {
|
||||
throw std::logic_error{"Projected graph must contain vertex!"};
|
||||
}
|
||||
auto result = db_accessor_.RemoveVertex(vertex_accessor);
|
||||
if (result.HasError() || !*result) {
|
||||
return result;
|
||||
}
|
||||
return this->graph_->RemoveVertex(*vertex_accessor);
|
||||
}
|
||||
|
||||
SubgraphVertexAccessor SubgraphDbAccessor::InsertVertex() {
|
||||
VertexAccessor vertex = db_accessor_.InsertVertex();
|
||||
this->graph_->InsertVertex(vertex);
|
||||
return SubgraphVertexAccessor(vertex, this->getGraph());
|
||||
}
|
||||
|
||||
VerticesIterable SubgraphDbAccessor::Vertices(storage::View) { // NOLINT(hicpp-named-parameter)
|
||||
return VerticesIterable(&graph_->vertices());
|
||||
}
|
||||
|
||||
std::optional<VertexAccessor> SubgraphDbAccessor::FindVertex(storage::Gid gid, storage::View view) {
|
||||
std::optional<VertexAccessor> maybe_vertex = db_accessor_.FindVertex(gid, view);
|
||||
if (maybe_vertex && this->graph_->ContainsVertex(*maybe_vertex)) {
|
||||
return *maybe_vertex;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
query::Graph *SubgraphDbAccessor::getGraph() { return graph_; }
|
||||
|
||||
VertexAccessor SubgraphVertexAccessor::GetVertexAccessor() const { return impl_; }
|
||||
|
||||
auto SubgraphVertexAccessor::OutEdges(storage::View view) const -> decltype(impl_.OutEdges(view)) {
|
||||
auto maybe_edges = impl_.impl_.OutEdges(view, {});
|
||||
if (maybe_edges.HasError()) return maybe_edges.GetError();
|
||||
auto edges = std::move(*maybe_edges);
|
||||
const auto &graph_edges = graph_->edges();
|
||||
|
||||
std::vector<storage::EdgeAccessor> filteredOutEdges;
|
||||
for (auto &edge : edges) {
|
||||
auto edge_q = EdgeAccessor(edge);
|
||||
if (graph_edges.contains(edge_q)) {
|
||||
filteredOutEdges.push_back(edge);
|
||||
}
|
||||
}
|
||||
|
||||
return iter::imap(VertexAccessor::MakeEdgeAccessor, std::move(filteredOutEdges));
|
||||
}
|
||||
|
||||
auto SubgraphVertexAccessor::InEdges(storage::View view) const -> decltype(impl_.InEdges(view)) {
|
||||
auto maybe_edges = impl_.impl_.InEdges(view, {});
|
||||
if (maybe_edges.HasError()) return maybe_edges.GetError();
|
||||
auto edges = std::move(*maybe_edges);
|
||||
const auto &graph_edges = graph_->edges();
|
||||
|
||||
std::vector<storage::EdgeAccessor> filteredOutEdges;
|
||||
for (auto &edge : edges) {
|
||||
auto edge_q = EdgeAccessor(edge);
|
||||
if (graph_edges.contains(edge_q)) {
|
||||
filteredOutEdges.push_back(edge);
|
||||
}
|
||||
}
|
||||
|
||||
return iter::imap(VertexAccessor::MakeEdgeAccessor, std::move(filteredOutEdges));
|
||||
}
|
||||
|
||||
} // namespace memgraph::query
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// Copyright 2023 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -20,6 +20,8 @@
|
||||
#include "storage/v2/id_types.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "storage/v2/result.hpp"
|
||||
#include "utils/pmr/unordered_set.hpp"
|
||||
#include "utils/variant_helpers.hpp"
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// Our communication layer and query engine don't mix
|
||||
@@ -45,6 +47,7 @@
|
||||
|
||||
namespace memgraph::query {
|
||||
|
||||
class Graph;
|
||||
class VertexAccessor;
|
||||
|
||||
class EdgeAccessor final {
|
||||
@@ -122,6 +125,11 @@ class VertexAccessor final {
|
||||
return impl_.SetProperty(key, value);
|
||||
}
|
||||
|
||||
storage::Result<std::vector<storage::PropertyValue>> SetProperties(
|
||||
std::vector<std::pair<storage::PropertyId, storage::PropertyValue>> &properties) {
|
||||
return impl_.SetProperties(properties);
|
||||
}
|
||||
|
||||
storage::Result<storage::PropertyValue> RemoveProperty(storage::PropertyId key) {
|
||||
return SetProperty(key, storage::PropertyValue());
|
||||
}
|
||||
@@ -185,38 +193,123 @@ inline VertexAccessor EdgeAccessor::From() const { return VertexAccessor(impl_.F
|
||||
|
||||
inline bool EdgeAccessor::IsCycle() const { return To() == From(); }
|
||||
|
||||
class DbAccessor final {
|
||||
storage::Storage::Accessor *accessor_;
|
||||
class SubgraphVertexAccessor final {
|
||||
public:
|
||||
query::VertexAccessor impl_;
|
||||
query::Graph *graph_;
|
||||
|
||||
class VerticesIterable final {
|
||||
storage::VerticesIterable iterable_;
|
||||
explicit SubgraphVertexAccessor(query::VertexAccessor impl, query::Graph *graph_) : impl_(impl), graph_(graph_) {}
|
||||
|
||||
bool operator==(const SubgraphVertexAccessor &v) const noexcept {
|
||||
static_assert(noexcept(impl_ == v.impl_));
|
||||
return impl_ == v.impl_;
|
||||
}
|
||||
|
||||
auto InEdges(storage::View view) const -> decltype(impl_.InEdges(view));
|
||||
|
||||
auto OutEdges(storage::View view) const -> decltype(impl_.OutEdges(view));
|
||||
|
||||
auto Labels(storage::View view) const { return impl_.Labels(view); }
|
||||
|
||||
storage::Result<bool> AddLabel(storage::LabelId label) { return impl_.AddLabel(label); }
|
||||
|
||||
storage::Result<bool> RemoveLabel(storage::LabelId label) { return impl_.RemoveLabel(label); }
|
||||
|
||||
storage::Result<bool> HasLabel(storage::View view, storage::LabelId label) const {
|
||||
return impl_.HasLabel(view, label);
|
||||
}
|
||||
|
||||
auto Properties(storage::View view) const { return impl_.Properties(view); }
|
||||
|
||||
storage::Result<storage::PropertyValue> GetProperty(storage::View view, storage::PropertyId key) const {
|
||||
return impl_.GetProperty(view, key);
|
||||
}
|
||||
|
||||
storage::Gid Gid() const noexcept { return impl_.Gid(); }
|
||||
|
||||
storage::Result<storage::PropertyValue> SetProperty(storage::PropertyId key, const storage::PropertyValue &value) {
|
||||
return impl_.SetProperty(key, value);
|
||||
}
|
||||
VertexAccessor GetVertexAccessor() const;
|
||||
};
|
||||
} // namespace memgraph::query
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<memgraph::query::VertexAccessor> {
|
||||
size_t operator()(const memgraph::query::VertexAccessor &v) const { return std::hash<decltype(v.impl_)>{}(v.impl_); }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<memgraph::query::EdgeAccessor> {
|
||||
size_t operator()(const memgraph::query::EdgeAccessor &e) const { return std::hash<decltype(e.impl_)>{}(e.impl_); }
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
namespace memgraph::query {
|
||||
|
||||
class VerticesIterable final {
|
||||
std::variant<storage::VerticesIterable, std::unordered_set<VertexAccessor, std::hash<VertexAccessor>,
|
||||
std::equal_to<void>, utils::Allocator<VertexAccessor>> *>
|
||||
iterable_;
|
||||
|
||||
public:
|
||||
class Iterator final {
|
||||
std::variant<storage::VerticesIterable::Iterator,
|
||||
std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
|
||||
utils::Allocator<VertexAccessor>>::iterator>
|
||||
it_;
|
||||
|
||||
public:
|
||||
class Iterator final {
|
||||
storage::VerticesIterable::Iterator it_;
|
||||
explicit Iterator(storage::VerticesIterable::Iterator it) : it_(it) {}
|
||||
explicit Iterator(std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
|
||||
utils::Allocator<VertexAccessor>>::iterator it)
|
||||
: it_(it) {}
|
||||
|
||||
public:
|
||||
explicit Iterator(storage::VerticesIterable::Iterator it) : it_(it) {}
|
||||
VertexAccessor operator*() const {
|
||||
return std::visit([](auto it_) { return VertexAccessor(*it_); }, it_);
|
||||
}
|
||||
|
||||
VertexAccessor operator*() const { return VertexAccessor(*it_); }
|
||||
Iterator &operator++() {
|
||||
std::visit([this](auto it_) { this->it_ = ++it_; }, it_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator &operator++() {
|
||||
++it_;
|
||||
return *this;
|
||||
}
|
||||
bool operator==(const Iterator &other) const { return it_ == other.it_; }
|
||||
|
||||
bool operator==(const Iterator &other) const { return it_ == other.it_; }
|
||||
|
||||
bool operator!=(const Iterator &other) const { return !(other == *this); }
|
||||
};
|
||||
|
||||
explicit VerticesIterable(storage::VerticesIterable iterable) : iterable_(std::move(iterable)) {}
|
||||
|
||||
Iterator begin() { return Iterator(iterable_.begin()); }
|
||||
|
||||
Iterator end() { return Iterator(iterable_.end()); }
|
||||
bool operator!=(const Iterator &other) const { return !(other == *this); }
|
||||
};
|
||||
|
||||
explicit VerticesIterable(storage::VerticesIterable iterable) : iterable_(std::move(iterable)) {}
|
||||
explicit VerticesIterable(std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
|
||||
utils::Allocator<VertexAccessor>> *vertices)
|
||||
: iterable_(vertices) {}
|
||||
|
||||
Iterator begin() {
|
||||
return std::visit(memgraph::utils::Overloaded{
|
||||
[](storage::VerticesIterable &iterable_) { return Iterator(iterable_.begin()); },
|
||||
[](std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
|
||||
utils::Allocator<VertexAccessor>> *iterable_) {
|
||||
return Iterator(iterable_->begin());
|
||||
}},
|
||||
iterable_);
|
||||
}
|
||||
|
||||
Iterator end() {
|
||||
return std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[](storage::VerticesIterable &iterable_) { return Iterator(iterable_.end()); },
|
||||
[](std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
|
||||
utils::Allocator<VertexAccessor>> *iterable_) { return Iterator(iterable_->end()); }},
|
||||
iterable_);
|
||||
}
|
||||
};
|
||||
|
||||
class DbAccessor final {
|
||||
storage::Storage::Accessor *accessor_;
|
||||
|
||||
public:
|
||||
explicit DbAccessor(storage::Storage::Accessor *accessor) : accessor_(accessor) {}
|
||||
|
||||
@@ -324,7 +417,7 @@ class DbAccessor final {
|
||||
|
||||
void AdvanceCommand() { accessor_->AdvanceCommand(); }
|
||||
|
||||
utils::BasicResult<storage::ConstraintViolation, void> Commit() { return accessor_->Commit(); }
|
||||
utils::BasicResult<storage::StorageDataManipulationError, void> Commit() { return accessor_->Commit(); }
|
||||
|
||||
void Abort() { accessor_->Abort(); }
|
||||
|
||||
@@ -358,18 +451,44 @@ class DbAccessor final {
|
||||
storage::ConstraintsInfo ListAllConstraints() const { return accessor_->ListAllConstraints(); }
|
||||
};
|
||||
|
||||
class SubgraphDbAccessor final {
|
||||
DbAccessor db_accessor_;
|
||||
Graph *graph_;
|
||||
|
||||
public:
|
||||
explicit SubgraphDbAccessor(DbAccessor db_accessor, Graph *graph);
|
||||
|
||||
static SubgraphDbAccessor *MakeSubgraphDbAccessor(DbAccessor *db_accessor, Graph *graph);
|
||||
|
||||
storage::PropertyId NameToProperty(std::string_view name);
|
||||
|
||||
storage::LabelId NameToLabel(std::string_view name);
|
||||
|
||||
storage::EdgeTypeId NameToEdgeType(std::string_view name);
|
||||
|
||||
const std::string &PropertyToName(storage::PropertyId prop) const;
|
||||
|
||||
const std::string &LabelToName(storage::LabelId label) const;
|
||||
|
||||
const std::string &EdgeTypeToName(storage::EdgeTypeId type) const;
|
||||
|
||||
storage::Result<std::optional<EdgeAccessor>> RemoveEdge(EdgeAccessor *edge);
|
||||
|
||||
storage::Result<EdgeAccessor> InsertEdge(SubgraphVertexAccessor *from, SubgraphVertexAccessor *to,
|
||||
const storage::EdgeTypeId &edge_type);
|
||||
|
||||
storage::Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachRemoveVertex(
|
||||
SubgraphVertexAccessor *vertex_accessor);
|
||||
|
||||
storage::Result<std::optional<VertexAccessor>> RemoveVertex(SubgraphVertexAccessor *vertex_accessor);
|
||||
|
||||
SubgraphVertexAccessor InsertVertex();
|
||||
|
||||
VerticesIterable Vertices(storage::View view);
|
||||
|
||||
std::optional<VertexAccessor> FindVertex(storage::Gid gid, storage::View view);
|
||||
|
||||
Graph *getGraph();
|
||||
};
|
||||
|
||||
} // namespace memgraph::query
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<memgraph::query::VertexAccessor> {
|
||||
size_t operator()(const memgraph::query::VertexAccessor &v) const { return std::hash<decltype(v.impl_)>{}(v.impl_); }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<memgraph::query::EdgeAccessor> {
|
||||
size_t operator()(const memgraph::query::EdgeAccessor &e) const { return std::hash<decltype(e.impl_)>{}(e.impl_); }
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
@@ -188,6 +188,12 @@ class FreeMemoryModificationInMulticommandTxException : public QueryException {
|
||||
: QueryException("Free memory query not allowed in multicommand transactions.") {}
|
||||
};
|
||||
|
||||
class ShowConfigModificationInMulticommandTxException : public QueryException {
|
||||
public:
|
||||
ShowConfigModificationInMulticommandTxException()
|
||||
: QueryException("Show config query not allowed in multicommand transactions.") {}
|
||||
};
|
||||
|
||||
class TriggerModificationInMulticommandTxException : public QueryException {
|
||||
public:
|
||||
TriggerModificationInMulticommandTxException()
|
||||
@@ -224,4 +230,11 @@ class VersionInfoInMulticommandTxException : public QueryException {
|
||||
: QueryException("Version info query not allowed in multicommand transactions.") {}
|
||||
};
|
||||
|
||||
class ReplicationException : public utils::BasicException {
|
||||
public:
|
||||
using utils::BasicException::BasicException;
|
||||
explicit ReplicationException(const std::string &message)
|
||||
: utils::BasicException("Replication Exception: {} Check the status of the replicas using 'SHOW REPLICA' query.",
|
||||
message) {}
|
||||
};
|
||||
} // namespace memgraph::query
|
||||
|
||||
@@ -461,10 +461,11 @@ cpp<#
|
||||
(lcp:define-class aggregation (binary-operator)
|
||||
((op "Op" :scope :public)
|
||||
(symbol-pos :int32_t :initval -1 :scope :public
|
||||
:documentation "Symbol table position of the symbol this Aggregation is mapped to."))
|
||||
:documentation "Symbol table position of the symbol this Aggregation is mapped to.")
|
||||
(distinct :bool :initval "false" :scope :public))
|
||||
(:public
|
||||
(lcp:define-enum op
|
||||
(count min max sum avg collect-list collect-map)
|
||||
(count min max sum avg collect-list collect-map project)
|
||||
(:serialize))
|
||||
#>cpp
|
||||
Aggregation() = default;
|
||||
@@ -475,10 +476,11 @@ cpp<#
|
||||
static const constexpr char *const kSum = "SUM";
|
||||
static const constexpr char *const kAvg = "AVG";
|
||||
static const constexpr char *const kCollect = "COLLECT";
|
||||
static const constexpr char *const kProject = "PROJECT";
|
||||
|
||||
static std::string OpToString(Op op) {
|
||||
const char *op_strings[] = {kCount, kMin, kMax, kSum,
|
||||
kAvg, kCollect, kCollect};
|
||||
kAvg, kCollect, kCollect, kProject};
|
||||
return op_strings[static_cast<int>(op)];
|
||||
}
|
||||
|
||||
@@ -504,8 +506,8 @@ cpp<#
|
||||
|
||||
/// Aggregation's first expression is the value being aggregated. The second
|
||||
/// expression is the key used only in COLLECT_MAP.
|
||||
Aggregation(Expression *expression1, Expression *expression2, Op op)
|
||||
: BinaryOperator(expression1, expression2), op_(op) {
|
||||
Aggregation(Expression *expression1, Expression *expression2, Op op, bool distinct)
|
||||
: BinaryOperator(expression1, expression2), op_(op), distinct_(distinct) {
|
||||
// COUNT without expression denotes COUNT(*) in cypher.
|
||||
DMG_ASSERT(expression1 || op == Aggregation::Op::COUNT,
|
||||
"All aggregations, except COUNT require expression");
|
||||
@@ -1380,7 +1382,7 @@ cpp<#
|
||||
:documentation "Variable where the total weight for weighted shortest path will be stored."))
|
||||
(:public
|
||||
(lcp:define-enum type
|
||||
(single depth-first breadth-first weighted-shortest-path)
|
||||
(single depth-first breadth-first weighted-shortest-path all-shortest-paths)
|
||||
(:serialize))
|
||||
(lcp:define-enum direction
|
||||
(in out both)
|
||||
@@ -1432,6 +1434,7 @@ cpp<#
|
||||
case Type::DEPTH_FIRST:
|
||||
case Type::BREADTH_FIRST:
|
||||
case Type::WEIGHTED_SHORTEST_PATH:
|
||||
case Type::ALL_SHORTEST_PATHS:
|
||||
return true;
|
||||
case Type::SINGLE:
|
||||
return false;
|
||||
@@ -2234,17 +2237,18 @@ cpp<#
|
||||
(:serialize (:slk))
|
||||
(:clone))
|
||||
|
||||
|
||||
(lcp:define-class auth-query (query)
|
||||
((action "Action" :scope :public)
|
||||
(user "std::string" :scope :public)
|
||||
(role "std::string" :scope :public)
|
||||
(user-or-role "std::string" :scope :public)
|
||||
|
||||
(password "Expression *" :initval "nullptr" :scope :public
|
||||
:slk-save #'slk-save-ast-pointer
|
||||
:slk-load (slk-load-ast-pointer "Expression"))
|
||||
(labels "std::vector<std::string>" :scope :public)
|
||||
(privileges "std::vector<Privilege>" :scope :public))
|
||||
(privileges "std::vector<Privilege>" :scope :public)
|
||||
(label-privileges "std::vector<std::unordered_map<FineGrainedPrivilege, std::vector<std::string>>>" :scope :public)
|
||||
(edge-type-privileges "std::vector<std::unordered_map<FineGrainedPrivilege, std::vector<std::string>>>" :scope :public))
|
||||
(:public
|
||||
(lcp:define-enum action
|
||||
(create-role drop-role show-roles create-user set-password drop-user
|
||||
@@ -2255,7 +2259,10 @@ cpp<#
|
||||
(lcp:define-enum privilege
|
||||
(create delete match merge set remove index stats auth constraint
|
||||
dump replication durability read_file free_memory trigger config stream module_read module_write
|
||||
websocket labels)
|
||||
websocket)
|
||||
(:serialize))
|
||||
(lcp:define-enum fine-grained-privilege
|
||||
(nothing read update create_delete)
|
||||
(:serialize))
|
||||
#>cpp
|
||||
AuthQuery() = default;
|
||||
@@ -2266,14 +2273,17 @@ cpp<#
|
||||
#>cpp
|
||||
AuthQuery(Action action, std::string user, std::string role,
|
||||
std::string user_or_role, Expression *password,
|
||||
std::vector<std::string> labels ,std::vector<Privilege> privileges)
|
||||
std::vector<Privilege> privileges,
|
||||
std::vector<std::unordered_map<FineGrainedPrivilege, std::vector<std::string>>> label_privileges,
|
||||
std::vector<std::unordered_map<FineGrainedPrivilege, std::vector<std::string>>> edge_type_privileges)
|
||||
: action_(action),
|
||||
user_(user),
|
||||
role_(role),
|
||||
user_or_role_(user_or_role),
|
||||
password_(password),
|
||||
labels_(labels),
|
||||
privileges_(privileges){}
|
||||
privileges_(privileges),
|
||||
label_privileges_(label_privileges),
|
||||
edge_type_privileges_(edge_type_privileges) {}
|
||||
cpp<#)
|
||||
(:private
|
||||
#>cpp
|
||||
@@ -2298,8 +2308,7 @@ const std::vector<AuthQuery::Privilege> kPrivilegesAll = {
|
||||
AuthQuery::Privilege::FREE_MEMORY, AuthQuery::Privilege::TRIGGER,
|
||||
AuthQuery::Privilege::CONFIG, AuthQuery::Privilege::STREAM,
|
||||
AuthQuery::Privilege::MODULE_READ, AuthQuery::Privilege::MODULE_WRITE,
|
||||
AuthQuery::Privilege::WEBSOCKET,
|
||||
AuthQuery::Privilege::LABELS};
|
||||
AuthQuery::Privilege::WEBSOCKET};
|
||||
cpp<#
|
||||
|
||||
(lcp:define-class info-query (query)
|
||||
@@ -2379,10 +2388,7 @@ cpp<#
|
||||
(port "Expression *" :initval "nullptr" :scope :public
|
||||
:slk-save #'slk-save-ast-pointer
|
||||
:slk-load (slk-load-ast-pointer "Expression"))
|
||||
(sync_mode "SyncMode" :scope :public)
|
||||
(timeout "Expression *" :initval "nullptr" :scope :public
|
||||
:slk-save #'slk-save-ast-pointer
|
||||
:slk-load (slk-load-ast-pointer "Expression")))
|
||||
(sync_mode "SyncMode" :scope :public))
|
||||
|
||||
(:public
|
||||
(lcp:define-enum action
|
||||
@@ -2672,5 +2678,13 @@ cpp<#
|
||||
(:serialize (:slk))
|
||||
(:clone))
|
||||
|
||||
(lcp:define-class show-config-query (query) ()
|
||||
(:public
|
||||
#>cpp
|
||||
DEFVISITABLE(QueryVisitor<void>);
|
||||
cpp<#)
|
||||
(:serialize (:slk))
|
||||
(:clone))
|
||||
|
||||
(lcp:pop-namespace) ;; namespace query
|
||||
(lcp:pop-namespace) ;; namespace memgraph
|
||||
|
||||
@@ -94,6 +94,7 @@ class StreamQuery;
|
||||
class SettingQuery;
|
||||
class VersionQuery;
|
||||
class Foreach;
|
||||
class ShowConfigQuery;
|
||||
|
||||
using TreeCompositeVisitor = utils::CompositeVisitor<
|
||||
SingleQuery, CypherUnion, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator, AdditionOperator,
|
||||
@@ -125,9 +126,9 @@ class ExpressionVisitor
|
||||
None, ParameterLookup, Identifier, PrimitiveLiteral, RegexMatch> {};
|
||||
|
||||
template <class TResult>
|
||||
class QueryVisitor
|
||||
: public utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery, InfoQuery,
|
||||
ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery, FreeMemoryQuery, TriggerQuery,
|
||||
IsolationLevelQuery, CreateSnapshotQuery, StreamQuery, SettingQuery, VersionQuery> {};
|
||||
class QueryVisitor : public utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery,
|
||||
InfoQuery, ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery,
|
||||
FreeMemoryQuery, TriggerQuery, IsolationLevelQuery, CreateSnapshotQuery,
|
||||
StreamQuery, SettingQuery, VersionQuery, ShowConfigQuery> {};
|
||||
|
||||
} // namespace memgraph::query
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user