Compare commits
101 Commits
add-gnuplo
...
v2.6.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f51141148 | ||
|
|
97d45ab1d8 | ||
|
|
6abd356d01 | ||
|
|
99a6c72bba | ||
|
|
173f5430aa | ||
|
|
362dc95e27 | ||
|
|
eead24a562 | ||
|
|
d79dd69607 | ||
|
|
024bf0c578 | ||
|
|
d7443a5558 | ||
|
|
2df357b012 | ||
|
|
b2b5a6e2a0 | ||
|
|
5e2ee6c817 | ||
|
|
862a1afdf1 | ||
|
|
bbce21e78f | ||
|
|
15c8662023 | ||
|
|
beaba0fc16 | ||
|
|
8f70c5f2a5 | ||
|
|
14c651d3ba | ||
|
|
04efc7a4a6 | ||
|
|
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 |
@@ -6,7 +6,6 @@ Checks: '*,
|
|||||||
-altera-unroll-loops,
|
-altera-unroll-loops,
|
||||||
-android-*,
|
-android-*,
|
||||||
-cert-err58-cpp,
|
-cert-err58-cpp,
|
||||||
-cert-str34-c,
|
|
||||||
-cppcoreguidelines-avoid-c-arrays,
|
-cppcoreguidelines-avoid-c-arrays,
|
||||||
-cppcoreguidelines-avoid-goto,
|
-cppcoreguidelines-avoid-goto,
|
||||||
-cppcoreguidelines-avoid-magic-numbers,
|
-cppcoreguidelines-avoid-magic-numbers,
|
||||||
@@ -50,7 +49,6 @@ Checks: '*,
|
|||||||
-misc-non-private-member-variables-in-classes,
|
-misc-non-private-member-variables-in-classes,
|
||||||
-modernize-avoid-c-arrays,
|
-modernize-avoid-c-arrays,
|
||||||
-modernize-concat-nested-namespaces,
|
-modernize-concat-nested-namespaces,
|
||||||
-modernize-loop-convert,
|
|
||||||
-modernize-pass-by-value,
|
-modernize-pass-by-value,
|
||||||
-modernize-use-equals-default,
|
-modernize-use-equals-default,
|
||||||
-modernize-use-nodiscard,
|
-modernize-use-nodiscard,
|
||||||
|
|||||||
7
.github/pull_request_template.md
vendored
7
.github/pull_request_template.md
vendored
@@ -1,11 +1,14 @@
|
|||||||
[master < Epic] PR
|
[master < Epic] PR
|
||||||
- [ ] Check, and update documentation if necessary
|
- [ ] Check, and update documentation if necessary
|
||||||
- [ ] Update [changelog](https://docs.memgraph.com/memgraph/changelog)
|
|
||||||
- [ ] Write E2E tests
|
- [ ] Write E2E tests
|
||||||
- [ ] Compare the [benchmarking results](https://bench-graph.memgraph.com/) between the master branch and the Epic branch
|
- [ ] 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
|
- [ ] Provide the full content or a guide for the final git message
|
||||||
|
|
||||||
[master < Task] PR
|
[master < Task] PR
|
||||||
- [ ] Check, and update documentation if necessary
|
- [ ] 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
|
- [ ] Provide the full content or a guide for the final git message
|
||||||
|
|
||||||
|
|
||||||
|
To keep docs changelog up to date, one more thing to do:
|
||||||
|
- [ ] Write a release note here
|
||||||
|
- [ ] Tag someone from docs team in the comments
|
||||||
|
|||||||
2
.github/workflows/daily_benchmark.yaml
vendored
2
.github/workflows/daily_benchmark.yaml
vendored
@@ -3,7 +3,7 @@ name: Daily Benchmark
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 1 * * *"
|
- cron: "0 22 * * *"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release_benchmarks:
|
release_benchmarks:
|
||||||
|
|||||||
255
.github/workflows/diff.yaml
vendored
255
.github/workflows/diff.yaml
vendored
@@ -14,6 +14,7 @@ on:
|
|||||||
- "**/*.md"
|
- "**/*.md"
|
||||||
- ".clang-format"
|
- ".clang-format"
|
||||||
- "CODEOWNERS"
|
- "CODEOWNERS"
|
||||||
|
- "licenses/*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
community_build:
|
community_build:
|
||||||
@@ -79,6 +80,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
# Activate toolchain.
|
# Activate toolchain.
|
||||||
source /opt/toolchain-v4/activate
|
source /opt/toolchain-v4/activate
|
||||||
|
|
||||||
# Initialize dependencies.
|
# Initialize dependencies.
|
||||||
./init
|
./init
|
||||||
|
|
||||||
@@ -99,7 +101,7 @@ jobs:
|
|||||||
echo ${file}
|
echo ${file}
|
||||||
if [[ ${file} == *.py ]]; then
|
if [[ ${file} == *.py ]]; then
|
||||||
python3 -m black --check --diff ${file}
|
python3 -m black --check --diff ${file}
|
||||||
python3 -m isort --check-only --profile "black" --diff ${file}
|
python3 -m isort --check-only --diff ${file}
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -140,22 +142,12 @@ jobs:
|
|||||||
name: "Code coverage"
|
name: "Code coverage"
|
||||||
path: tools/github/generated/code_coverage.tar.gz
|
path: tools/github/generated/code_coverage.tar.gz
|
||||||
|
|
||||||
- name: Set base branch
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
run: |
|
|
||||||
echo "BASE_BRANCH=origin/${{ github.base_ref }}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Set base branch # if we manually dispatch or push to master
|
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
|
||||||
run: |
|
|
||||||
echo "BASE_BRANCH=origin/master" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Run clang-tidy
|
- name: Run clang-tidy
|
||||||
run: |
|
run: |
|
||||||
source /opt/toolchain-v4/activate
|
source /opt/toolchain-v4/activate
|
||||||
|
|
||||||
# Restrict clang-tidy results only to the modified parts
|
# Restrict clang-tidy results only to the modified parts
|
||||||
git diff -U0 ${{ env.BASE_BRANCH }}... -- src | ./tools/github/clang-tidy/clang-tidy-diff.py -p 1 -j $THREADS -extra-arg="-DMG_CLANG_TIDY_CHECK" -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
|
# Fail if any warning is reported
|
||||||
! cat ./build/clang_tidy_output.txt | ./tools/github/clang-tidy/grep_error_lines.sh > /dev/null
|
! cat ./build/clang_tidy_output.txt | ./tools/github/clang-tidy/grep_error_lines.sh > /dev/null
|
||||||
@@ -189,23 +181,52 @@ jobs:
|
|||||||
cmake ..
|
cmake ..
|
||||||
make -j$THREADS
|
make -j$THREADS
|
||||||
|
|
||||||
- name: Run simulation tests
|
- name: Run leftover CTest tests
|
||||||
run: |
|
run: |
|
||||||
# Activate toolchain.
|
# Activate toolchain.
|
||||||
source /opt/toolchain-v4/activate
|
source /opt/toolchain-v4/activate
|
||||||
|
|
||||||
# Run simulation tests.
|
# Run leftover CTest tests (all except unit and benchmark tests).
|
||||||
cd build
|
cd build
|
||||||
ctest -R memgraph__simulation --output-on-failure -j$THREADS
|
ctest -E "(memgraph__unit|memgraph__benchmark)" --output-on-failure
|
||||||
|
|
||||||
- name: Run single benchmark test
|
- name: Run drivers tests
|
||||||
|
run: |
|
||||||
|
./tests/drivers/run.sh
|
||||||
|
|
||||||
|
- name: Run integration tests
|
||||||
|
run: |
|
||||||
|
cd tests/integration
|
||||||
|
for name in *; do
|
||||||
|
if [ ! -d $name ]; then continue; fi
|
||||||
|
pushd $name >/dev/null
|
||||||
|
echo "Running: $name"
|
||||||
|
if [ -x prepare.sh ]; then
|
||||||
|
./prepare.sh
|
||||||
|
fi
|
||||||
|
if [ -x runner.py ]; then
|
||||||
|
./runner.py
|
||||||
|
elif [ -x runner.sh ]; then
|
||||||
|
./runner.sh
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
popd >/dev/null
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Run cppcheck and clang-format
|
||||||
run: |
|
run: |
|
||||||
# Activate toolchain.
|
# Activate toolchain.
|
||||||
source /opt/toolchain-v4/activate
|
source /opt/toolchain-v4/activate
|
||||||
|
|
||||||
# Run simulation tests.
|
# Run cppcheck and clang-format.
|
||||||
cd tests/mgbench
|
cd tools/github
|
||||||
./benchmark.py accesscontrol/small --num-workers-for-import 1 --test-system-arg "split-file splitfiles/accesscontrol_small.shard_configuration bolt-num-workers 1"
|
./cppcheck_and_clang_format diff
|
||||||
|
|
||||||
|
- name: Save cppcheck and clang-format errors
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: "Code coverage"
|
||||||
|
path: tools/github/cppcheck_and_clang_format.txt
|
||||||
|
|
||||||
release_build:
|
release_build:
|
||||||
name: "Release build"
|
name: "Release build"
|
||||||
@@ -236,6 +257,19 @@ jobs:
|
|||||||
cmake -DCMAKE_BUILD_TYPE=release ..
|
cmake -DCMAKE_BUILD_TYPE=release ..
|
||||||
make -j$THREADS
|
make -j$THREADS
|
||||||
|
|
||||||
|
- name: Run GQL Behave tests
|
||||||
|
run: |
|
||||||
|
cd tests/gql_behave
|
||||||
|
./continuous_integration
|
||||||
|
|
||||||
|
- name: Save quality assurance status
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: "GQL Behave Status"
|
||||||
|
path: |
|
||||||
|
tests/gql_behave/gql_behave_status.csv
|
||||||
|
tests/gql_behave/gql_behave_status.html
|
||||||
|
|
||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
run: |
|
run: |
|
||||||
# Activate toolchain.
|
# Activate toolchain.
|
||||||
@@ -245,24 +279,6 @@ jobs:
|
|||||||
cd build
|
cd build
|
||||||
ctest -R memgraph__unit --output-on-failure -j$THREADS
|
ctest -R memgraph__unit --output-on-failure -j$THREADS
|
||||||
|
|
||||||
- name: Run simulation tests
|
|
||||||
run: |
|
|
||||||
# Activate toolchain.
|
|
||||||
source /opt/toolchain-v4/activate
|
|
||||||
|
|
||||||
# Run simulation tests.
|
|
||||||
cd build
|
|
||||||
ctest -R memgraph__simulation --output-on-failure -j$THREADS
|
|
||||||
|
|
||||||
- name: Run single benchmark test
|
|
||||||
run: |
|
|
||||||
# Activate toolchain.
|
|
||||||
source /opt/toolchain-v4/activate
|
|
||||||
|
|
||||||
# Run simulation tests.
|
|
||||||
cd tests/mgbench
|
|
||||||
./benchmark.py accesscontrol/small --num-workers-for-import 1 --test-system-arg "split-file splitfiles/accesscontrol_small.shard_configuration bolt-num-workers 1"
|
|
||||||
|
|
||||||
- name: Run e2e tests
|
- name: Run e2e tests
|
||||||
run: |
|
run: |
|
||||||
# TODO(gitbuda): Setup mgclient and pymgclient properly.
|
# TODO(gitbuda): Setup mgclient and pymgclient properly.
|
||||||
@@ -270,4 +286,167 @@ jobs:
|
|||||||
./setup.sh
|
./setup.sh
|
||||||
source ve3/bin/activate
|
source ve3/bin/activate
|
||||||
cd e2e
|
cd e2e
|
||||||
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../../libs/mgclient/lib python runner.py --workloads-root-directory ./distributed_queries
|
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../../libs/mgclient/lib python runner.py --workloads-root-directory .
|
||||||
|
|
||||||
|
- name: Run stress test (plain)
|
||||||
|
run: |
|
||||||
|
cd tests/stress
|
||||||
|
./continuous_integration
|
||||||
|
|
||||||
|
- name: Run stress test (SSL)
|
||||||
|
run: |
|
||||||
|
cd tests/stress
|
||||||
|
./continuous_integration --use-ssl
|
||||||
|
|
||||||
|
- name: Run durability test
|
||||||
|
run: |
|
||||||
|
cd tests/stress
|
||||||
|
source ve3/bin/activate
|
||||||
|
python3 durability --num-steps 5
|
||||||
|
|
||||||
|
- name: Create enterprise DEB package
|
||||||
|
run: |
|
||||||
|
# Activate toolchain.
|
||||||
|
source /opt/toolchain-v4/activate
|
||||||
|
|
||||||
|
cd build
|
||||||
|
|
||||||
|
# create mgconsole
|
||||||
|
# we use the -B to force the build
|
||||||
|
make -j$THREADS -B mgconsole
|
||||||
|
|
||||||
|
# Create enterprise DEB package.
|
||||||
|
mkdir output && cd output
|
||||||
|
cpack -G DEB --config ../CPackConfig.cmake
|
||||||
|
|
||||||
|
- name: Save enterprise DEB package
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: "Enterprise DEB package"
|
||||||
|
path: build/output/memgraph*.deb
|
||||||
|
|
||||||
|
- name: Save test data
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: "Test data"
|
||||||
|
path: |
|
||||||
|
# multiple paths could be defined
|
||||||
|
build/logs
|
||||||
|
|
||||||
|
release_jepsen_test:
|
||||||
|
name: "Release Jepsen Test"
|
||||||
|
runs-on: [self-hosted, Linux, X64, Debian10, JepsenControl]
|
||||||
|
#continue-on-error: true
|
||||||
|
env:
|
||||||
|
THREADS: 24
|
||||||
|
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||||
|
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set up repository
|
||||||
|
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 release binaries
|
||||||
|
run: |
|
||||||
|
# Activate toolchain.
|
||||||
|
source /opt/toolchain-v4/activate
|
||||||
|
|
||||||
|
# Initialize dependencies.
|
||||||
|
./init
|
||||||
|
|
||||||
|
# Build only memgraph release binarie.
|
||||||
|
cd build
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=release ..
|
||||||
|
make -j$THREADS memgraph
|
||||||
|
|
||||||
|
- name: Run Jepsen tests
|
||||||
|
run: |
|
||||||
|
cd tests/jepsen
|
||||||
|
./run.sh test --binary ../../build/memgraph --run-args "test-all --node-configs resources/node-config.edn" --ignore-run-stdout-logs --ignore-run-stderr-logs
|
||||||
|
|
||||||
|
- name: Save Jepsen report
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
if: ${{ always() }}
|
||||||
|
with:
|
||||||
|
name: "Jepsen Report"
|
||||||
|
path: tests/jepsen/Jepsen.tar.gz
|
||||||
|
|
||||||
|
release_benchmarks:
|
||||||
|
name: "Release benchmarks"
|
||||||
|
runs-on: [self-hosted, Linux, X64, Diff, Gen7]
|
||||||
|
env:
|
||||||
|
THREADS: 24
|
||||||
|
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||||
|
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set up repository
|
||||||
|
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 release binaries
|
||||||
|
run: |
|
||||||
|
# Activate toolchain.
|
||||||
|
source /opt/toolchain-v4/activate
|
||||||
|
|
||||||
|
# Initialize dependencies.
|
||||||
|
./init
|
||||||
|
|
||||||
|
# Build only memgraph release binaries.
|
||||||
|
cd build
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=release ..
|
||||||
|
make -j$THREADS
|
||||||
|
|
||||||
|
- name: Run macro benchmarks
|
||||||
|
run: |
|
||||||
|
cd tests/macro_benchmark
|
||||||
|
./harness QuerySuite MemgraphRunner \
|
||||||
|
--groups aggregation 1000_create unwind_create dense_expand match \
|
||||||
|
--no-strict
|
||||||
|
|
||||||
|
- name: Get branch name (merge)
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
shell: bash
|
||||||
|
run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/} | tr / -)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Get branch name (pull request)
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
shell: bash
|
||||||
|
run: echo "BRANCH_NAME=$(echo ${GITHUB_HEAD_REF} | tr / -)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Upload macro benchmark results
|
||||||
|
run: |
|
||||||
|
cd tools/bench-graph-client
|
||||||
|
virtualenv -p python3 ve3
|
||||||
|
source ve3/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
./main.py --benchmark-name "macro_benchmark" \
|
||||||
|
--benchmark-results-path "../../tests/macro_benchmark/.harness_summary" \
|
||||||
|
--github-run-id "${{ github.run_id }}" \
|
||||||
|
--github-run-number "${{ github.run_number }}" \
|
||||||
|
--head-branch-name "${{ env.BRANCH_NAME }}"
|
||||||
|
|
||||||
|
- name: Run mgbench
|
||||||
|
run: |
|
||||||
|
cd tests/mgbench
|
||||||
|
./benchmark.py --num-workers-for-benchmark 12 --export-results benchmark_result.json pokec/medium/*/*
|
||||||
|
|
||||||
|
- name: Upload mgbench results
|
||||||
|
run: |
|
||||||
|
cd tools/bench-graph-client
|
||||||
|
virtualenv -p python3 ve3
|
||||||
|
source ve3/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
./main.py --benchmark-name "mgbench" \
|
||||||
|
--benchmark-results-path "../../tests/mgbench/benchmark_result.json" \
|
||||||
|
--github-run-id "${{ github.run_id }}" \
|
||||||
|
--github-run-number "${{ github.run_number }}" \
|
||||||
|
--head-branch-name "${{ env.BRANCH_NAME }}"
|
||||||
|
|||||||
2
.github/workflows/full_clang_tidy.yaml
vendored
2
.github/workflows/full_clang_tidy.yaml
vendored
@@ -39,7 +39,7 @@ jobs:
|
|||||||
source /opt/toolchain-v4/activate
|
source /opt/toolchain-v4/activate
|
||||||
|
|
||||||
# The results are also written to standard output in order to retain them in the logs
|
# The results are also written to standard output in order to retain them in the logs
|
||||||
./tools/github/clang-tidy/run-clang-tidy.py -p build -j $THREADS -extra-arg="-DMG_CLANG_TIDY_CHECK" -clang-tidy-binary=/opt/toolchain-v4/bin/clang-tidy "$PWD/src/*" |
|
./tools/github/clang-tidy/run-clang-tidy.py -p build -j $THREADS -clang-tidy-binary=/opt/toolchain-v4/bin/clang-tidy "$PWD/src/*" |
|
||||||
tee ./build/full_clang_tidy_output.txt
|
tee ./build/full_clang_tidy_output.txt
|
||||||
|
|
||||||
- name: Summarize clang-tidy results
|
- name: Summarize clang-tidy results
|
||||||
|
|||||||
34
.github/workflows/package_all.yaml
vendored
34
.github/workflows/package_all.yaml
vendored
@@ -160,6 +160,23 @@ jobs:
|
|||||||
name: debian-11-platform
|
name: debian-11-platform
|
||||||
path: build/output/debian-11/memgraph*.deb
|
path: build/output/debian-11/memgraph*.deb
|
||||||
|
|
||||||
|
fedora-36:
|
||||||
|
runs-on: [self-hosted, DockerMgBuild, X64]
|
||||||
|
timeout-minutes: 60
|
||||||
|
steps:
|
||||||
|
- name: "Set up repository"
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # Required because of release/get_version.py
|
||||||
|
- name: "Build package"
|
||||||
|
run: |
|
||||||
|
./release/package/run.sh package fedora-36
|
||||||
|
- name: "Upload package"
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: fedora-36
|
||||||
|
path: build/output/fedora-36/memgraph*.rpm
|
||||||
|
|
||||||
debian-11-arm:
|
debian-11-arm:
|
||||||
runs-on: [self-hosted, DockerMgBuild, ARM64, strange]
|
runs-on: [self-hosted, DockerMgBuild, ARM64, strange]
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
@@ -176,3 +193,20 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: debian-11-arm
|
name: debian-11-arm
|
||||||
path: build/output/debian-11-arm/memgraph*.deb
|
path: build/output/debian-11-arm/memgraph*.deb
|
||||||
|
|
||||||
|
ubuntu-2204-arm:
|
||||||
|
runs-on: [self-hosted, DockerMgBuild, ARM64, strange]
|
||||||
|
timeout-minutes: 60
|
||||||
|
steps:
|
||||||
|
- name: "Set up repository"
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # Required because of release/get_version.py
|
||||||
|
- name: "Build package"
|
||||||
|
run: |
|
||||||
|
./release/package/run.sh package ubuntu-22.04-arm
|
||||||
|
- name: "Upload package"
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ubuntu-22.04-arm
|
||||||
|
path: build/output/ubuntu-22.04-arm/memgraph*.deb
|
||||||
|
|||||||
2
.github/workflows/release_centos8.yaml
vendored
2
.github/workflows/release_centos8.yaml
vendored
@@ -3,7 +3,7 @@ name: Release CentOS 8
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 1 * * *"
|
- cron: "0 22 * * *"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
community_build:
|
community_build:
|
||||||
|
|||||||
2
.github/workflows/release_debian10.yaml
vendored
2
.github/workflows/release_debian10.yaml
vendored
@@ -3,7 +3,7 @@ name: Release Debian 10
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 1 * * *"
|
- cron: "0 22 * * *"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
community_build:
|
community_build:
|
||||||
|
|||||||
6
.github/workflows/release_docker.yaml
vendored
6
.github/workflows/release_docker.yaml
vendored
@@ -22,14 +22,14 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|||||||
2
.github/workflows/release_ubuntu2004.yaml
vendored
2
.github/workflows/release_ubuntu2004.yaml
vendored
@@ -3,7 +3,7 @@ name: Release Ubuntu 20.04
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 1 * * *"
|
- cron: "0 22 * * *"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
community_build:
|
community_build:
|
||||||
|
|||||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -23,8 +23,6 @@ cmake-build-*
|
|||||||
cmake/DownloadProject/
|
cmake/DownloadProject/
|
||||||
dist/
|
dist/
|
||||||
src/query/frontend/opencypher/generated/
|
src/query/frontend/opencypher/generated/
|
||||||
src/query/v2/frontend/opencypher/generated/
|
|
||||||
src/parser/opencypher/generated
|
|
||||||
tags
|
tags
|
||||||
ve/
|
ve/
|
||||||
ve3/
|
ve3/
|
||||||
@@ -52,25 +50,15 @@ src/distributed/pull_produce_rpc_messages.hpp
|
|||||||
src/distributed/storage_gc_rpc_messages.hpp
|
src/distributed/storage_gc_rpc_messages.hpp
|
||||||
src/distributed/token_sharing_rpc_messages.hpp
|
src/distributed/token_sharing_rpc_messages.hpp
|
||||||
src/distributed/updates_rpc_messages.hpp
|
src/distributed/updates_rpc_messages.hpp
|
||||||
src/query/v2/frontend/ast/ast.hpp
|
|
||||||
src/query/frontend/ast/ast.hpp
|
src/query/frontend/ast/ast.hpp
|
||||||
src/storage/v3/bindings/ast/ast.hpp
|
|
||||||
src/query/distributed/frontend/ast/ast_serialization.hpp
|
src/query/distributed/frontend/ast/ast_serialization.hpp
|
||||||
src/query/v2/distributed/frontend/ast/ast_serialization.hpp
|
|
||||||
src/durability/distributed/state_delta.hpp
|
src/durability/distributed/state_delta.hpp
|
||||||
src/durability/single_node/state_delta.hpp
|
src/durability/single_node/state_delta.hpp
|
||||||
src/durability/single_node_ha/state_delta.hpp
|
src/durability/single_node_ha/state_delta.hpp
|
||||||
src/query/frontend/semantic/symbol.hpp
|
src/query/frontend/semantic/symbol.hpp
|
||||||
src/query/v2/frontend/semantic/symbol.hpp
|
|
||||||
src/expr/semantic/symbol.hpp
|
|
||||||
src/query/distributed/frontend/semantic/symbol_serialization.hpp
|
src/query/distributed/frontend/semantic/symbol_serialization.hpp
|
||||||
src/query/v2/distributed/frontend/semantic/symbol_serialization.hpp
|
|
||||||
src/query/distributed/plan/ops.hpp
|
src/query/distributed/plan/ops.hpp
|
||||||
src/query/v2/distributed/plan/ops.hpp
|
|
||||||
src/query/plan/operator.hpp
|
src/query/plan/operator.hpp
|
||||||
src/query/v2/plan/operator.hpp
|
|
||||||
src/parser/opencypher/generated
|
|
||||||
src/expr/semantic/symbol.hpp
|
|
||||||
src/raft/log_entry.hpp
|
src/raft/log_entry.hpp
|
||||||
src/raft/raft_rpc_messages.hpp
|
src/raft/raft_rpc_messages.hpp
|
||||||
src/raft/snapshot_metadata.hpp
|
src/raft/snapshot_metadata.hpp
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ repos:
|
|||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 22.10.0
|
rev: 22.8.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
- repo: https://github.com/pycqa/isort
|
- repo: https://github.com/pycqa/isort
|
||||||
@@ -14,7 +14,6 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
name: isort (python)
|
name: isort (python)
|
||||||
args: ["--profile", "black"]
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||||
rev: v13.0.0
|
rev: v13.0.0
|
||||||
hooks:
|
hooks:
|
||||||
|
|||||||
@@ -182,8 +182,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|||||||
# c99-designator is disabled because of required mixture of designated and
|
# c99-designator is disabled because of required mixture of designated and
|
||||||
# non-designated initializers in Python Query Module code (`py_module.cpp`).
|
# non-designated initializers in Python Query Module code (`py_module.cpp`).
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall \
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall \
|
||||||
-Werror=switch -Werror=switch-bool -Werror=implicit-fallthrough \
|
-Werror=switch -Werror=switch-bool -Werror=return-type \
|
||||||
-Werror=return-type \
|
|
||||||
-Werror=return-stack-address \
|
-Werror=return-stack-address \
|
||||||
-Wno-c99-designator \
|
-Wno-c99-designator \
|
||||||
-DBOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT")
|
-DBOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT")
|
||||||
|
|||||||
81
README.md
81
README.md
@@ -4,10 +4,6 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
Build modern, graph-based applications on top of your streaming data in minutes.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/memgraph/memgraph/blob/master/licenses/APL.txt">
|
<a href="https://github.com/memgraph/memgraph/blob/master/licenses/APL.txt">
|
||||||
<img src="https://img.shields.io/badge/license-APL-green" alt="license" title="license"/>
|
<img src="https://img.shields.io/badge/license-APL-green" alt="license" title="license"/>
|
||||||
@@ -22,7 +18,7 @@ Build modern, graph-based applications on top of your streaming data in minutes.
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/memgraph/memgraph">
|
<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>
|
||||||
<a href="https://memgraph.com/docs/" alt="Documentation">
|
<a href="https://memgraph.com/docs/" alt="Documentation">
|
||||||
<img src="https://img.shields.io/badge/documentation-Memgraph-orange" />
|
<img src="https://img.shields.io/badge/documentation-Memgraph-orange" />
|
||||||
@@ -37,9 +33,10 @@ Build modern, graph-based applications on top of your streaming data in minutes.
|
|||||||
|
|
||||||
## :clipboard: Description
|
## :clipboard: Description
|
||||||
|
|
||||||
Memgraph is a streaming graph application platform that helps you wrangle your
|
Memgraph is an open source graph database built for real-time streaming and
|
||||||
streaming data, build sophisticated models that you can query in real-time, and
|
compatible with Neo4j. Whether you're a developer or a data scientist with
|
||||||
develop graph applications.
|
interconnected data, Memgraph will get you the immediate actionable insights
|
||||||
|
fast.
|
||||||
|
|
||||||
Memgraph directly connects to your streaming infrastructure. You can ingest data
|
Memgraph directly connects to your streaming infrastructure. You can ingest data
|
||||||
from sources like Kafka, SQL, or plain CSV files. Memgraph provides a standard
|
from sources like Kafka, SQL, or plain CSV files. Memgraph provides a standard
|
||||||
@@ -51,8 +48,20 @@ natural and effective way to model many real-world problems without relying on
|
|||||||
complex SQL schemas.
|
complex SQL schemas.
|
||||||
|
|
||||||
Memgraph is implemented in C/C++ and leverages an in-memory first architecture
|
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
|
to ensure that you’re getting the [best possible
|
||||||
without surprises. It’s also ACID-compliant and highly available.
|
performance](http://memgraph.com/benchgraph) consistently and without surprises.
|
||||||
|
It’s also ACID-compliant and highly available.
|
||||||
|
|
||||||
|
## :zap: Features
|
||||||
|
|
||||||
|
- Run Python, Rust, and C/C++ code natively, check out the
|
||||||
|
[MAGE](https://github.com/memgraph/mage) graph algorithm library
|
||||||
|
- Native support for machine learning
|
||||||
|
- Streaming support
|
||||||
|
- Replication
|
||||||
|
- Authentication and authorization
|
||||||
|
- ACID compliance
|
||||||
|
|
||||||
|
|
||||||
## :video_game: Memgraph Playground
|
## :video_game: Memgraph Playground
|
||||||
|
|
||||||
@@ -76,28 +85,49 @@ your browser.
|
|||||||
### macOS
|
### macOS
|
||||||
|
|
||||||
[](https://memgraph.com/docs/memgraph/install-memgraph-on-macos-docker)
|
[](https://memgraph.com/docs/memgraph/install-memgraph-on-macos-docker)
|
||||||
|
[](https://memgraph.com/docs/memgraph/install-memgraph-on-ubuntu)
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
[](https://memgraph.com/docs/memgraph/install-memgraph-on-linux-docker)
|
[](https://memgraph.com/docs/memgraph/install-memgraph-on-linux-docker)
|
||||||
[](https://memgraph.com/docs/memgraph/install-memgraph-on-debian)
|
[](https://memgraph.com/docs/memgraph/install-memgraph-on-debian)
|
||||||
[](https://memgraph.com/docs/memgraph/install-memgraph-on-ubuntu)
|
[](https://memgraph.com/docs/memgraph/install-memgraph-on-ubuntu)
|
||||||
[](https://memgraph.com/docs/memgraph/install-memgraph-from-rpm)
|
||||||
OS](https://img.shields.io/badge/cent%20os-002260?style=for-the-badge&logo=centos&logoColor=F0F0F0)](https://memgraph.com/docs/memgraph/install-memgraph-from-rpm)
|
[](https://memgraph.com/docs/memgraph/install-memgraph-from-rpm)
|
||||||
|
[](https://memgraph.com/docs/memgraph/install-memgraph-from-rpm)
|
||||||
|
|
||||||
You can find the binaries and Docker images on the [Download
|
You can find the binaries and Docker images on the [Download
|
||||||
Hub](https://memgraph.com/download) and the installation instructions in the
|
Hub](https://memgraph.com/download) and the installation instructions in the
|
||||||
[official documentation](https://memgraph.com/docs/memgraph/installation).
|
[official documentation](https://memgraph.com/docs/memgraph/installation).
|
||||||
|
|
||||||
## :zap: Features
|
|
||||||
|
|
||||||
- Run Python, Rust, and C/C++ code natively, check out the
|
## :cloud: Memgraph Cloud
|
||||||
[MAGE](https://github.com/memgraph/mage) graph algorithm library
|
|
||||||
- Native support for machine learning
|
Check out [Memgraph Cloud](https://memgraph.com/docs/memgraph-cloud) - a cloud service fully managed on AWS and available in 6 geographic regions around the world. Memgraph Cloud allows you to create projects with Enterprise instances of MemgraphDB from your browser.
|
||||||
- Streaming support
|
|
||||||
- Replication
|
<p align="left">
|
||||||
- Authentication and authorization
|
<a href="https://memgraph.com/docs/memgraph-cloud">
|
||||||
- ACID compliance
|
<img width="450px" alt="Memgraph Cloud" src="https://public-assets.memgraph.com/memgraph-gifs%2Fcloud.gif">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## :link: Connect to Memgraph
|
||||||
|
|
||||||
|
[Connect to the database](https://memgraph.com/docs/memgraph/connect-to-memgraph) using Memgraph Lab, mgconsole, various drivers (Python, C/C++ and others) and WebSocket.
|
||||||
|
|
||||||
|
### :microscope: Memgraph Lab
|
||||||
|
|
||||||
|
Visualize graphs and play with queries to understand your data. [Memgraph Lab](https://memgraph.com/docs/memgraph-lab) is a user interface that helps you explore and manipulate the data stored in Memgraph. Visualize graphs, execute ad hoc queries, and optimize their performance.
|
||||||
|
|
||||||
|
<p align="left">
|
||||||
|
<a href="https://memgraph.com/docs/memgraph-lab">
|
||||||
|
<img width="450px" alt="Memgraph Cloud" src="https://public-assets.memgraph.com/memgraph-gifs%2Flab.gif">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## :file_folder: Import data
|
||||||
|
|
||||||
|
[Import data](https://memgraph.com/docs/memgraph/import-data) into Memgraph using Kafka, RedPanda or Pulsar streams, CSV and JSON files, or Cypher commands.
|
||||||
|
|
||||||
## :bookmark_tabs: Documentation
|
## :bookmark_tabs: Documentation
|
||||||
|
|
||||||
@@ -141,8 +171,17 @@ Memgraph Community is available under the [BSL
|
|||||||
license](./licenses/BSL.txt).</br> Memgraph Enterprise is available under the
|
license](./licenses/BSL.txt).</br> Memgraph Enterprise is available under the
|
||||||
[MEL license](./licenses/MEL.txt).
|
[MEL license](./licenses/MEL.txt).
|
||||||
|
|
||||||
|
## :busts_in_silhouette: Community
|
||||||
|
|
||||||
|
- :purple_heart: [**Discord**](https://discord.gg/memgraph)
|
||||||
|
- :ocean: [**Stack Overflow**](https://stackoverflow.com/questions/tagged/memgraphdb)
|
||||||
|
- :busts_in_silhouette: [**Discourse forum**](https://discourse.memgraph.com/)
|
||||||
|
- :bird: [**Twitter**](https://twitter.com/memgraphdb)
|
||||||
|
- :movie_camera:
|
||||||
|
[**YouTube**](https://www.youtube.com/channel/UCZ3HOJvHGxtQ_JHxOselBYg)
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="#">
|
<a href="#">
|
||||||
<img src="https://img.shields.io/badge/⬆️back_to_top_⬆️-white" alt="Back to top" title="Back to top"/>
|
<img src="https://img.shields.io/badge/⬆️ back_to_top_⬆️-white" alt="Back to top" title="Back to top"/>
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -83,9 +83,13 @@ modifications:
|
|||||||
value: "true"
|
value: "true"
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
# - name: "query_modules_directory"
|
- name: "query_modules_directory"
|
||||||
# value: "/usr/lib/memgraph/query_modules"
|
value: "/usr/lib/memgraph/query_modules"
|
||||||
# override: true
|
override: true
|
||||||
|
|
||||||
|
- name: "auth_module_executable"
|
||||||
|
value: "/usr/lib/memgraph/auth_module/example.py"
|
||||||
|
override: false
|
||||||
|
|
||||||
- name: "memory_limit"
|
- name: "memory_limit"
|
||||||
value: "0"
|
value: "0"
|
||||||
|
|||||||
@@ -5,12 +5,10 @@ import os
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||||
CONFIG_FILE = os.path.join(SCRIPT_DIR, "flags.yaml")
|
CONFIG_FILE = os.path.join(SCRIPT_DIR, "flags.yaml")
|
||||||
WIDTH = 80
|
WIDTH = 80
|
||||||
@@ -18,14 +16,13 @@ WIDTH = 80
|
|||||||
|
|
||||||
def wrap_text(s, initial_indent="# "):
|
def wrap_text(s, initial_indent="# "):
|
||||||
return "\n#\n".join(
|
return "\n#\n".join(
|
||||||
map(lambda x: textwrap.fill(x, WIDTH, initial_indent=initial_indent,
|
map(lambda x: textwrap.fill(x, WIDTH, initial_indent=initial_indent, subsequent_indent="# "), s.split("\n"))
|
||||||
subsequent_indent="# "), s.split("\n")))
|
)
|
||||||
|
|
||||||
|
|
||||||
def extract_flags(binary_path):
|
def extract_flags(binary_path):
|
||||||
ret = {}
|
ret = {}
|
||||||
data = subprocess.run([binary_path, "--help-xml"],
|
data = subprocess.run([binary_path, "--help-xml"], stdout=subprocess.PIPE).stdout.decode("utf-8")
|
||||||
stdout=subprocess.PIPE).stdout.decode("utf-8")
|
|
||||||
root = ET.fromstring(data)
|
root = ET.fromstring(data)
|
||||||
for child in root:
|
for child in root:
|
||||||
if child.tag == "usage" and child.text.lower().count("warning"):
|
if child.tag == "usage" and child.text.lower().count("warning"):
|
||||||
@@ -46,8 +43,7 @@ def apply_config_to_flags(config, flags):
|
|||||||
for modification in config["modifications"]:
|
for modification in config["modifications"]:
|
||||||
name = modification["name"]
|
name = modification["name"]
|
||||||
if name not in flags:
|
if name not in flags:
|
||||||
print("WARNING: Flag '" + name + "' missing from binary!",
|
print("WARNING: Flag '" + name + "' missing from binary!", file=sys.stderr)
|
||||||
file=sys.stderr)
|
|
||||||
continue
|
continue
|
||||||
flags[name]["default"] = modification["value"]
|
flags[name]["default"] = modification["value"]
|
||||||
flags[name]["override"] = modification["override"]
|
flags[name]["override"] = modification["override"]
|
||||||
@@ -75,8 +71,9 @@ def extract_sections(flags):
|
|||||||
else:
|
else:
|
||||||
sections.append((current_section, current_flags))
|
sections.append((current_section, current_flags))
|
||||||
sections.append(("other", other))
|
sections.append(("other", other))
|
||||||
assert set(sum(map(lambda x: x[1], sections), [])) == set(flags.keys()), \
|
assert set(sum(map(lambda x: x[1], sections), [])) == set(
|
||||||
"The section extraction algorithm lost some flags!"
|
flags.keys()
|
||||||
|
), "The section extraction algorithm lost some flags!"
|
||||||
return sections
|
return sections
|
||||||
|
|
||||||
|
|
||||||
@@ -89,8 +86,7 @@ def generate_config_file(sections, flags):
|
|||||||
helpstr = flag["meaning"] + " [" + flag["type"] + "]"
|
helpstr = flag["meaning"] + " [" + flag["type"] + "]"
|
||||||
ret += wrap_text(helpstr) + "\n"
|
ret += wrap_text(helpstr) + "\n"
|
||||||
prefix = "# " if not flag["override"] else ""
|
prefix = "# " if not flag["override"] else ""
|
||||||
ret += prefix + "--" + flag["name"].replace("_", "-") + \
|
ret += prefix + "--" + flag["name"].replace("_", "-") + "=" + flag["default"] + "\n\n"
|
||||||
"=" + flag["default"] + "\n\n"
|
|
||||||
ret += "\n"
|
ret += "\n"
|
||||||
ret += wrap_text(config["footer"])
|
ret += wrap_text(config["footer"])
|
||||||
return ret.strip() + "\n"
|
return ret.strip() + "\n"
|
||||||
@@ -98,13 +94,9 @@ def generate_config_file(sections, flags):
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("memgraph_binary",
|
parser.add_argument("memgraph_binary", help="path to 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("output_file",
|
parser.add_argument("--config-file", default=CONFIG_FILE, help="path to generator configuration 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()
|
args = parser.parse_args()
|
||||||
flags = extract_flags(args.memgraph_binary)
|
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
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
|||||||
source "$DIR/../util.sh"
|
source "$DIR/../util.sh"
|
||||||
|
|
||||||
check_operating_system "debian-11"
|
check_operating_system "debian-11"
|
||||||
check_architecture "arm64"
|
check_architecture "arm64" "aarch64"
|
||||||
|
|
||||||
TOOLCHAIN_BUILD_DEPS=(
|
TOOLCHAIN_BUILD_DEPS=(
|
||||||
coreutils gcc g++ build-essential make # generic build tools
|
coreutils gcc g++ build-essential make # generic build tools
|
||||||
|
|||||||
103
environment/os/fedora-36.sh
Executable file
103
environment/os/fedora-36.sh
Executable file
@@ -0,0 +1,103 @@
|
|||||||
|
#!/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
|
||||||
|
perl # for openssl
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
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=""
|
||||||
|
# On Fedora yum/dnf and python10 use newer glibc which is not compatible
|
||||||
|
# with ours, so we need to momentarely disable env
|
||||||
|
local OLD_LD_LIBRARY_PATH=${LD_LIBRARY_PATH}
|
||||||
|
LD_LIBRARY_PATH=""
|
||||||
|
for pkg in $1; do
|
||||||
|
if ! dnf list installed "$pkg" >/dev/null 2>/dev/null; then
|
||||||
|
missing="$pkg $missing"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ "$missing" != "" ]; then
|
||||||
|
echo "MISSING PACKAGES: $missing"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
LD_LIBRARY_PATH=${OLD_LD_LIBRARY_PATH}
|
||||||
|
}
|
||||||
|
|
||||||
|
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}"
|
||||||
96
environment/os/ubuntu-22.04-arm.sh
Executable file
96
environment/os/ubuntu-22.04-arm.sh
Executable file
@@ -0,0 +1,96 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||||
|
source "$DIR/../util.sh"
|
||||||
|
|
||||||
|
check_operating_system "ubuntu-22.04"
|
||||||
|
check_architecture "arm64" "aarch64"
|
||||||
|
|
||||||
|
TOOLCHAIN_BUILD_DEPS=(
|
||||||
|
coreutils gcc g++ build-essential make # generic build tools
|
||||||
|
wget # used for archive download
|
||||||
|
gnupg # used for archive signature verification
|
||||||
|
tar gzip bzip2 xz-utils unzip # used for archive unpacking
|
||||||
|
zlib1g-dev # zlib library used for all builds
|
||||||
|
libexpat1-dev libbabeltrace-dev liblzma-dev python3-dev texinfo # for gdb
|
||||||
|
libcurl4-openssl-dev # for cmake
|
||||||
|
libreadline-dev # for cmake and llvm
|
||||||
|
libffi-dev libxml2-dev # for llvm
|
||||||
|
curl # snappy
|
||||||
|
file
|
||||||
|
git # for thrift
|
||||||
|
libgmp-dev # for gdb
|
||||||
|
gperf # for proxygen
|
||||||
|
libssl-dev
|
||||||
|
libedit-dev libpcre3-dev automake bison # for swig
|
||||||
|
)
|
||||||
|
|
||||||
|
TOOLCHAIN_RUN_DEPS=(
|
||||||
|
make # generic build tools
|
||||||
|
tar gzip bzip2 xz-utils # used for archive unpacking
|
||||||
|
zlib1g # zlib library used for all builds
|
||||||
|
libexpat1 libbabeltrace1 liblzma5 python3 # for gdb
|
||||||
|
libcurl4 # for cmake
|
||||||
|
libreadline8 # for cmake and llvm
|
||||||
|
libffi7 libxml2 # for llvm
|
||||||
|
libssl-dev # for libevent
|
||||||
|
)
|
||||||
|
|
||||||
|
MEMGRAPH_BUILD_DEPS=(
|
||||||
|
git # source code control
|
||||||
|
make pkg-config # build system
|
||||||
|
curl wget # for downloading libs
|
||||||
|
uuid-dev default-jre-headless # required by antlr
|
||||||
|
libreadline-dev # for memgraph console
|
||||||
|
libpython3-dev python3-dev # for query modules
|
||||||
|
libssl-dev
|
||||||
|
libseccomp-dev
|
||||||
|
netcat # tests are using nc to wait for memgraph
|
||||||
|
python3 python3-virtualenv python3-pip # for qa, macro_benchmark and stress tests
|
||||||
|
python3-yaml # for the configuration generator
|
||||||
|
libcurl4-openssl-dev # mg-requests
|
||||||
|
sbcl # for custom Lisp C++ preprocessing
|
||||||
|
doxygen graphviz # source documentation generators
|
||||||
|
mono-runtime mono-mcs zip unzip default-jdk-headless # for driver tests
|
||||||
|
dotnet-sdk-6.0 golang nodejs npm
|
||||||
|
autoconf # for jemalloc code generation
|
||||||
|
libtool # for protobuf code generation
|
||||||
|
)
|
||||||
|
|
||||||
|
list() {
|
||||||
|
echo "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
check() {
|
||||||
|
check_all_dpkg "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
install() {
|
||||||
|
cd "$DIR"
|
||||||
|
apt update
|
||||||
|
# If GitHub Actions runner is installed, append LANG to the environment.
|
||||||
|
# Python related tests doesn't work the LANG export.
|
||||||
|
if [ -d "/home/gh/actions-runner" ]; then
|
||||||
|
echo "LANG=en_US.utf8" >> /home/gh/actions-runner/.env
|
||||||
|
else
|
||||||
|
echo "NOTE: export LANG=en_US.utf8"
|
||||||
|
fi
|
||||||
|
apt install -y wget
|
||||||
|
for pkg in $1; do
|
||||||
|
if [ "$pkg" == dotnet-sdk-6.0 ]; then
|
||||||
|
if ! dpkg -s dotnet-sdk-6.0 2>/dev/null >/dev/null; then
|
||||||
|
wget -nv https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
|
||||||
|
dpkg -i packages-microsoft-prod.deb
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y apt-transport-https dotnet-sdk-6.0
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
apt install -y "$pkg"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
deps=$2"[*]"
|
||||||
|
"$1" "${!deps}"
|
||||||
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"
|
source "$DIR/../util.sh"
|
||||||
DISTRO="$(operating_system)"
|
DISTRO="$(operating_system)"
|
||||||
|
|
||||||
|
function log_tool_name () {
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
echo "#### $1 ####"
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
for_arm=false
|
for_arm=false
|
||||||
if [[ "$#" -eq 1 ]]; then
|
if [[ "$#" -eq 1 ]]; then
|
||||||
if [[ "$1" == "--for-arm" ]]; then
|
if [[ "$1" == "--for-arm" ]]; then
|
||||||
@@ -20,9 +28,11 @@ if [[ "$#" -eq 1 ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
os="$1"
|
TOOLCHAIN_STDCXX="${TOOLCHAIN_STDCXX:-libstdc++}"
|
||||||
|
if [[ "$TOOLCHAIN_STDCXX" != "libstdc++" && "$TOOLCHAIN_STDCXX" != "libc++" ]]; then
|
||||||
# toolchain version
|
echo "Only GCC (libstdc++) or LLVM (libc++) C++ standard library implementations are supported."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
TOOLCHAIN_VERSION=4
|
TOOLCHAIN_VERSION=4
|
||||||
|
|
||||||
# package versions used
|
# package versions used
|
||||||
@@ -41,11 +51,19 @@ CPPCHECK_VERSION=2.6
|
|||||||
LLVM_VERSION=13.0.0
|
LLVM_VERSION=13.0.0
|
||||||
SWIG_VERSION=4.0.2 # used only for LLVM compilation
|
SWIG_VERSION=4.0.2 # used only for LLVM compilation
|
||||||
|
|
||||||
# Check for the dependencies.
|
# Set the right env script
|
||||||
echo "ALL BUILD PACKAGES: $($DIR/../os/$DISTRO.sh list TOOLCHAIN_BUILD_DEPS)"
|
ENV_SCRIPT="$DIR/../os/$DISTRO.sh"
|
||||||
$DIR/../os/$DISTRO.sh check TOOLCHAIN_BUILD_DEPS
|
if [[ "$for_arm" = true ]]; then
|
||||||
echo "ALL RUN PACKAGES: $($DIR/../os/$DISTRO.sh list TOOLCHAIN_RUN_DEPS)"
|
ENV_SCRIPT="$DIR/../os/$DISTRO-arm.sh"
|
||||||
$DIR/../os/$DISTRO.sh check TOOLCHAIN_RUN_DEPS
|
fi
|
||||||
|
|
||||||
|
# Check for the toolchain build dependencies.
|
||||||
|
echo "ALL BUILD PACKAGES: $(${ENV_SCRIPT} list TOOLCHAIN_BUILD_DEPS)"
|
||||||
|
${ENV_SCRIPT} check TOOLCHAIN_BUILD_DEPS
|
||||||
|
|
||||||
|
# Check for the toolchain run dependencies.
|
||||||
|
echo "ALL RUN PACKAGES: $(${ENV_SCRIPT} list TOOLCHAIN_RUN_DEPS)"
|
||||||
|
${ENV_SCRIPT} check TOOLCHAIN_RUN_DEPS
|
||||||
|
|
||||||
# check installation directory
|
# check installation directory
|
||||||
NAME=toolchain-v$TOOLCHAIN_VERSION
|
NAME=toolchain-v$TOOLCHAIN_VERSION
|
||||||
@@ -99,6 +117,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/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/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/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
|
fi
|
||||||
if [ ! -f pahole-gdb-master.zip ]; then
|
if [ ! -f pahole-gdb-master.zip ]; then
|
||||||
wget https://github.com/PhilArmstrong/pahole-gdb/archive/master.zip -O pahole-gdb-master.zip
|
wget https://github.com/PhilArmstrong/pahole-gdb/archive/master.zip -O pahole-gdb-master.zip
|
||||||
@@ -156,6 +176,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/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/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/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
|
fi
|
||||||
# list of valid llvm gnupg keys: https://releases.llvm.org/download.html
|
# list of valid llvm gnupg keys: https://releases.llvm.org/download.html
|
||||||
$GPG --keyserver $KEYSERVER --recv-keys 0x474E22316ABF4785A88C6E8EA2C794A986419D8A
|
$GPG --keyserver $KEYSERVER --recv-keys 0x474E22316ABF4785A88C6E8EA2C794A986419D8A
|
||||||
@@ -165,6 +187,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 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 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 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
|
popd
|
||||||
|
|
||||||
@@ -172,7 +196,7 @@ popd
|
|||||||
mkdir -p build
|
mkdir -p build
|
||||||
pushd build
|
pushd build
|
||||||
|
|
||||||
# compile gcc
|
log_tool_name "GCC $GCC_VERSION"
|
||||||
if [ ! -f $PREFIX/bin/gcc ]; then
|
if [ ! -f $PREFIX/bin/gcc ]; then
|
||||||
if [ -d gcc-$GCC_VERSION ]; then
|
if [ -d gcc-$GCC_VERSION ]; then
|
||||||
rm -rf gcc-$GCC_VERSION
|
rm -rf gcc-$GCC_VERSION
|
||||||
@@ -263,7 +287,7 @@ fi
|
|||||||
export PATH=$PREFIX/bin:$PATH
|
export PATH=$PREFIX/bin:$PATH
|
||||||
export LD_LIBRARY_PATH=$PREFIX/lib64
|
export LD_LIBRARY_PATH=$PREFIX/lib64
|
||||||
|
|
||||||
# compile binutils
|
log_tool_name "binutils $BINUTILS_VERSION"
|
||||||
if [ ! -f $PREFIX/bin/ld.gold ]; then
|
if [ ! -f $PREFIX/bin/ld.gold ]; then
|
||||||
if [ -d binutils-$BINUTILS_VERSION ]; then
|
if [ -d binutils-$BINUTILS_VERSION ]; then
|
||||||
rm -rf binutils-$BINUTILS_VERSION
|
rm -rf binutils-$BINUTILS_VERSION
|
||||||
@@ -327,7 +351,7 @@ if [ ! -f $PREFIX/bin/ld.gold ]; then
|
|||||||
popd && popd
|
popd && popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# compile gdb
|
log_tool_name "GDB $GDB_VERSION"
|
||||||
if [ ! -f $PREFIX/bin/gdb ]; then
|
if [ ! -f $PREFIX/bin/gdb ]; then
|
||||||
if [ -d gdb-$GDB_VERSION ]; then
|
if [ -d gdb-$GDB_VERSION ]; then
|
||||||
rm -rf gdb-$GDB_VERSION
|
rm -rf gdb-$GDB_VERSION
|
||||||
@@ -363,6 +387,34 @@ if [ ! -f $PREFIX/bin/gdb ]; then
|
|||||||
--without-babeltrace \
|
--without-babeltrace \
|
||||||
--enable-tui \
|
--enable-tui \
|
||||||
--with-python=python3
|
--with-python=python3
|
||||||
|
elif [[ "${DISTRO}" == fedora* ]]; then
|
||||||
|
# Remove readline, gdb does not compile
|
||||||
|
env \
|
||||||
|
CC=gcc \
|
||||||
|
CXX=g++ \
|
||||||
|
CFLAGS="-g -O2 -fstack-protector-strong -Wformat -Werror=format-security" \
|
||||||
|
CXXFLAGS="-g -O2 -fstack-protector-strong -Wformat -Werror=format-security" \
|
||||||
|
CPPFLAGS="-Wdate-time -D_FORTIFY_SOURCE=2 -fPIC" \
|
||||||
|
LDFLAGS="-Wl,-z,relro" \
|
||||||
|
PYTHON="" \
|
||||||
|
../configure \
|
||||||
|
--build=x86_64-linux-gnu \
|
||||||
|
--host=x86_64-linux-gnu \
|
||||||
|
--prefix=$PREFIX \
|
||||||
|
--disable-maintainer-mode \
|
||||||
|
--disable-dependency-tracking \
|
||||||
|
--disable-silent-rules \
|
||||||
|
--disable-gdbtk \
|
||||||
|
--disable-shared \
|
||||||
|
--without-guile \
|
||||||
|
--with-system-gdbinit=$PREFIX/etc/gdb/gdbinit \
|
||||||
|
--with-expat \
|
||||||
|
--with-system-zlib \
|
||||||
|
--with-lzma \
|
||||||
|
--with-babeltrace \
|
||||||
|
--with-intel-pt \
|
||||||
|
--enable-tui \
|
||||||
|
--with-python=python3
|
||||||
else
|
else
|
||||||
# https://buildd.debian.org/status/fetch.php?pkg=gdb&arch=amd64&ver=8.2.1-2&stamp=1550831554&raw=0
|
# https://buildd.debian.org/status/fetch.php?pkg=gdb&arch=amd64&ver=8.2.1-2&stamp=1550831554&raw=0
|
||||||
env \
|
env \
|
||||||
@@ -398,13 +450,13 @@ if [ ! -f $PREFIX/bin/gdb ]; then
|
|||||||
popd && popd
|
popd && popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install pahole
|
log_tool_name "install pahole"
|
||||||
if [ ! -d $PREFIX/share/pahole-gdb ]; then
|
if [ ! -d $PREFIX/share/pahole-gdb ]; then
|
||||||
unzip ../archives/pahole-gdb-master.zip
|
unzip ../archives/pahole-gdb-master.zip
|
||||||
mv pahole-gdb-master $PREFIX/share/pahole-gdb
|
mv pahole-gdb-master $PREFIX/share/pahole-gdb
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# setup system gdbinit
|
log_tool_name "setup system gdbinit"
|
||||||
if [ ! -f $PREFIX/etc/gdb/gdbinit ]; then
|
if [ ! -f $PREFIX/etc/gdb/gdbinit ]; then
|
||||||
mkdir -p $PREFIX/etc/gdb
|
mkdir -p $PREFIX/etc/gdb
|
||||||
cat >$PREFIX/etc/gdb/gdbinit <<EOF
|
cat >$PREFIX/etc/gdb/gdbinit <<EOF
|
||||||
@@ -430,7 +482,7 @@ end
|
|||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# compile cmake
|
log_tool_name "cmake $CMAKE_VERSION"
|
||||||
if [ ! -f $PREFIX/bin/cmake ]; then
|
if [ ! -f $PREFIX/bin/cmake ]; then
|
||||||
if [ -d cmake-$CMAKE_VERSION ]; then
|
if [ -d cmake-$CMAKE_VERSION ]; then
|
||||||
rm -rf cmake-$CMAKE_VERSION
|
rm -rf cmake-$CMAKE_VERSION
|
||||||
@@ -456,7 +508,7 @@ if [ ! -f $PREFIX/bin/cmake ]; then
|
|||||||
popd && popd
|
popd && popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# compile cppcheck
|
log_tool_name "cppcheck $CPPCHECK_VERSION"
|
||||||
if [ ! -f $PREFIX/bin/cppcheck ]; then
|
if [ ! -f $PREFIX/bin/cppcheck ]; then
|
||||||
if [ -d cppcheck-$CPPCHECK_VERSION ]; then
|
if [ -d cppcheck-$CPPCHECK_VERSION ]; then
|
||||||
rm -rf cppcheck-$CPPCHECK_VERSION
|
rm -rf cppcheck-$CPPCHECK_VERSION
|
||||||
@@ -480,7 +532,7 @@ if [ ! -f $PREFIX/bin/cppcheck ]; then
|
|||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# compile swig
|
log_tool_name "swig $SWIG_VERSION"
|
||||||
if [ ! -d swig-$SWIG_VERSION/install ]; then
|
if [ ! -d swig-$SWIG_VERSION/install ]; then
|
||||||
if [ -d swig-$SWIG_VERSION ]; then
|
if [ -d swig-$SWIG_VERSION ]; then
|
||||||
rm -rf swig-$SWIG_VERSION
|
rm -rf swig-$SWIG_VERSION
|
||||||
@@ -496,7 +548,7 @@ if [ ! -d swig-$SWIG_VERSION/install ]; then
|
|||||||
popd && popd
|
popd && popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# compile llvm
|
log_tool_name "LLVM $LLVM_VERSION"
|
||||||
if [ ! -f $PREFIX/bin/clang ]; then
|
if [ ! -f $PREFIX/bin/clang ]; then
|
||||||
if [ -d llvm-$LLVM_VERSION ]; then
|
if [ -d llvm-$LLVM_VERSION ]; then
|
||||||
rm -rf llvm-$LLVM_VERSION
|
rm -rf llvm-$LLVM_VERSION
|
||||||
@@ -513,8 +565,19 @@ if [ ! -f $PREFIX/bin/clang ]; then
|
|||||||
mv compiler-rt-$LLVM_VERSION.src/ llvm-$LLVM_VERSION/projects/compiler-rt
|
mv compiler-rt-$LLVM_VERSION.src/ llvm-$LLVM_VERSION/projects/compiler-rt
|
||||||
tar -xvf ../archives/libunwind-$LLVM_VERSION.src.tar.xz
|
tar -xvf ../archives/libunwind-$LLVM_VERSION.src.tar.xz
|
||||||
mv libunwind-$LLVM_VERSION.src/include/mach-o llvm-$LLVM_VERSION/tools/lld/include
|
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
|
pushd llvm-$LLVM_VERSION
|
||||||
mkdir build && pushd build
|
mkdir -p build && pushd build
|
||||||
# activate swig
|
# activate swig
|
||||||
export PATH=$DIR/build/swig-$SWIG_VERSION/install/bin:$PATH
|
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
|
# 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
|
||||||
@@ -567,7 +630,7 @@ In order to be able to run all of these tools you should install the following
|
|||||||
packages:
|
packages:
|
||||||
|
|
||||||
\`\`\`
|
\`\`\`
|
||||||
$($DIR/../os/$DISTRO.sh list TOOLCHAIN_RUN_DEPS)
|
$($DIR/../os/$ENV_SCRIPT.sh list TOOLCHAIN_RUN_DEPS)
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@@ -624,6 +687,7 @@ export PS1="($NAME) \$PS1"
|
|||||||
export LD_LIBRARY_PATH=$PREFIX/lib:$PREFIX/lib64
|
export LD_LIBRARY_PATH=$PREFIX/lib:$PREFIX/lib64
|
||||||
export CXXFLAGS=-isystem\ $PREFIX/include\ \$CXXFLAGS
|
export CXXFLAGS=-isystem\ $PREFIX/include\ \$CXXFLAGS
|
||||||
export CFLAGS=-isystem\ $PREFIX/include\ \$CFLAGS
|
export CFLAGS=-isystem\ $PREFIX/include\ \$CFLAGS
|
||||||
|
export VENV=$PREFIX
|
||||||
|
|
||||||
# disable root
|
# disable root
|
||||||
function su () {
|
function su () {
|
||||||
@@ -675,7 +739,7 @@ PROXYGEN_SHA256=5360a8ccdfb2f5a6c7b3eed331ec7ab0e2c792d579c6fff499c85c516c11fe14
|
|||||||
SNAPPY_SHA256=75c1fbb3d618dd3a0483bff0e26d0a92b495bbe5059c8b4f1c962b478b6e06e7
|
SNAPPY_SHA256=75c1fbb3d618dd3a0483bff0e26d0a92b495bbe5059c8b4f1c962b478b6e06e7
|
||||||
SNAPPY_VERSION=1.1.9
|
SNAPPY_VERSION=1.1.9
|
||||||
XZ_VERSION=5.2.5 # for LZMA
|
XZ_VERSION=5.2.5 # for LZMA
|
||||||
ZLIB_VERSION=1.2.12
|
ZLIB_VERSION=1.2.13
|
||||||
ZSTD_VERSION=1.5.0
|
ZSTD_VERSION=1.5.0
|
||||||
WANGLE_SHA256=1002e9c32b6f4837f6a760016e3b3e22f3509880ef3eaad191c80dc92655f23f
|
WANGLE_SHA256=1002e9c32b6f4837f6a760016e3b3e22f3509880ef3eaad191c80dc92655f23f
|
||||||
|
|
||||||
@@ -820,7 +884,11 @@ source $PREFIX/activate
|
|||||||
export CC=$PREFIX/bin/clang
|
export CC=$PREFIX/bin/clang
|
||||||
export CXX=$PREFIX/bin/clang++
|
export CXX=$PREFIX/bin/clang++
|
||||||
export CFLAGS="$CFLAGS -fPIC"
|
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
|
COMMON_CMAKE_FLAGS="-DCMAKE_INSTALL_PREFIX=$PREFIX
|
||||||
-DCMAKE_PREFIX_PATH=$PREFIX
|
-DCMAKE_PREFIX_PATH=$PREFIX
|
||||||
-DCMAKE_BUILD_TYPE=Release
|
-DCMAKE_BUILD_TYPE=Release
|
||||||
@@ -834,7 +902,7 @@ COMMON_CMAKE_FLAGS="-DCMAKE_INSTALL_PREFIX=$PREFIX
|
|||||||
COMMON_CONFIGURE_FLAGS="--enable-shared=no --prefix=$PREFIX"
|
COMMON_CONFIGURE_FLAGS="--enable-shared=no --prefix=$PREFIX"
|
||||||
COMMON_MAKE_INSTALL_FLAGS="-j$CPUS BUILD_SHARED=no PREFIX=$PREFIX install"
|
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 [ ! -f $PREFIX/include/bzlib.h ]; then
|
||||||
if [ -d bzip2-$BZIP2_VERSION ]; then
|
if [ -d bzip2-$BZIP2_VERSION ]; then
|
||||||
rm -rf bzip2-$BZIP2_VERSION
|
rm -rf bzip2-$BZIP2_VERSION
|
||||||
@@ -845,7 +913,7 @@ if [ ! -f $PREFIX/include/bzlib.h ]; then
|
|||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install fmt
|
log_tool_name "fmt $FMT_VERSION"
|
||||||
if [ ! -d $PREFIX/include/fmt ]; then
|
if [ ! -d $PREFIX/include/fmt ]; then
|
||||||
if [ -d fmt-$FMT_VERSION ]; then
|
if [ -d fmt-$FMT_VERSION ]; then
|
||||||
rm -rf fmt-$FMT_VERSION
|
rm -rf fmt-$FMT_VERSION
|
||||||
@@ -858,7 +926,7 @@ if [ ! -d $PREFIX/include/fmt ]; then
|
|||||||
popd && popd
|
popd && popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install lz4
|
log_tool_name "lz4 $LZ4_VERSION"
|
||||||
if [ ! -f $PREFIX/include/lz4.h ]; then
|
if [ ! -f $PREFIX/include/lz4.h ]; then
|
||||||
if [ -d lz4-$LZ4_VERSION ]; then
|
if [ -d lz4-$LZ4_VERSION ]; then
|
||||||
rm -rf lz4-$LZ4_VERSION
|
rm -rf lz4-$LZ4_VERSION
|
||||||
@@ -869,7 +937,7 @@ if [ ! -f $PREFIX/include/lz4.h ]; then
|
|||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install xz
|
log_tool_name "xz $XZ_VERSION"
|
||||||
if [ ! -f $PREFIX/include/lzma.h ]; then
|
if [ ! -f $PREFIX/include/lzma.h ]; then
|
||||||
if [ -d xz-$XZ_VERSION ]; then
|
if [ -d xz-$XZ_VERSION ]; then
|
||||||
rm -rf xz-$XZ_VERSION
|
rm -rf xz-$XZ_VERSION
|
||||||
@@ -881,7 +949,7 @@ if [ ! -f $PREFIX/include/lzma.h ]; then
|
|||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install zlib
|
log_tool_name "zlib $ZLIB_VERSION"
|
||||||
if [ ! -f $PREFIX/include/zlib.h ]; then
|
if [ ! -f $PREFIX/include/zlib.h ]; then
|
||||||
if [ -d zlib-$ZLIB_VERSION ]; then
|
if [ -d zlib-$ZLIB_VERSION ]; then
|
||||||
rm -rf zlib-$ZLIB_VERSION
|
rm -rf zlib-$ZLIB_VERSION
|
||||||
@@ -895,7 +963,7 @@ if [ ! -f $PREFIX/include/zlib.h ]; then
|
|||||||
popd && popd
|
popd && popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install zstd
|
log_tool_name "zstd $ZSTD_VERSION"
|
||||||
if [ ! -f $PREFIX/include/zstd.h ]; then
|
if [ ! -f $PREFIX/include/zstd.h ]; then
|
||||||
if [ -d zstd-$ZSTD_VERSION ]; then
|
if [ -d zstd-$ZSTD_VERSION ]; then
|
||||||
rm -rf zstd-$ZSTD_VERSION
|
rm -rf zstd-$ZSTD_VERSION
|
||||||
@@ -910,7 +978,8 @@ if [ ! -f $PREFIX/include/zstd.h ]; then
|
|||||||
popd && popd
|
popd && popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
#install jemalloc
|
# TODO(gitbuda): Freeze jmalloc version.
|
||||||
|
log_tool_name "jmalloc"
|
||||||
if [ ! -d $PREFIX/include/jemalloc ]; then
|
if [ ! -d $PREFIX/include/jemalloc ]; then
|
||||||
if [ -d jemalloc ]; then
|
if [ -d jemalloc ]; then
|
||||||
rm -rf jemalloc
|
rm -rf jemalloc
|
||||||
@@ -927,7 +996,7 @@ if [ ! -d $PREFIX/include/jemalloc ]; then
|
|||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install boost
|
log_tool_name "BOOST $BOOST_VERSION"
|
||||||
if [ ! -d $PREFIX/include/boost ]; then
|
if [ ! -d $PREFIX/include/boost ]; then
|
||||||
if [ -d boost_$BOOST_VERSION_UNDERSCORES ]; then
|
if [ -d boost_$BOOST_VERSION_UNDERSCORES ]; then
|
||||||
rm -rf boost_$BOOST_VERSION_UNDERSCORES
|
rm -rf boost_$BOOST_VERSION_UNDERSCORES
|
||||||
@@ -935,15 +1004,24 @@ if [ ! -d $PREFIX/include/boost ]; then
|
|||||||
tar -xzf ../archives/boost_$BOOST_VERSION_UNDERSCORES.tar.gz
|
tar -xzf ../archives/boost_$BOOST_VERSION_UNDERSCORES.tar.gz
|
||||||
pushd boost_$BOOST_VERSION_UNDERSCORES
|
pushd boost_$BOOST_VERSION_UNDERSCORES
|
||||||
./bootstrap.sh --prefix=$PREFIX --with-toolset=clang --with-python=python3 --without-icu
|
./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 \
|
if [ "$TOOLCHAIN_STDCXX" = "libstdc++" ]; then
|
||||||
-sZLIB_SOURCE="$PREFIX" -sZLIB_INCLUDE="$PREFIX/include" -sZLIB_LIBPATH="$PREFIX/lib" \
|
./b2 toolset=clang -j$CPUS install variant=release link=static cxxstd=20 --disable-icu \
|
||||||
-sBZIP2_SOURCE="$PREFIX" -sBZIP2_INCLUDE="$PREFIX/include" -sBZIP2_LIBPATH="$PREFIX/lib" \
|
-sZLIB_SOURCE="$PREFIX" -sZLIB_INCLUDE="$PREFIX/include" -sZLIB_LIBPATH="$PREFIX/lib" \
|
||||||
-sLZMA_SOURCE="$PREFIX" -sLZMA_INCLUDE="$PREFIX/include" -sLZMA_LIBPATH="$PREFIX/lib" \
|
-sBZIP2_SOURCE="$PREFIX" -sBZIP2_INCLUDE="$PREFIX/include" -sBZIP2_LIBPATH="$PREFIX/lib" \
|
||||||
-sZSTD_SOURCE="$PREFIX" -sZSTD_INCLUDE="$PREFIX/include" -sZSTD_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
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install double-conversion
|
log_tool_name "double-conversion $DOUBLE_CONVERSION_VERSION"
|
||||||
if [ ! -d $PREFIX/include/double-conversion ]; then
|
if [ ! -d $PREFIX/include/double-conversion ]; then
|
||||||
if [ -d double-conversion-$DOUBLE_CONVERSION_VERSION ]; then
|
if [ -d double-conversion-$DOUBLE_CONVERSION_VERSION ]; then
|
||||||
rm -rf double-conversion-$DOUBLE_CONVERSION_VERSION
|
rm -rf double-conversion-$DOUBLE_CONVERSION_VERSION
|
||||||
@@ -958,7 +1036,8 @@ if [ ! -d $PREFIX/include/double-conversion ]; then
|
|||||||
popd && popd
|
popd && popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install gflags
|
# TODO(gitbuda): Freeze gflags version.
|
||||||
|
log_tool_name "gflags"
|
||||||
if [ ! -d $PREFIX/include/gflags ]; then
|
if [ ! -d $PREFIX/include/gflags ]; then
|
||||||
if [ -d gflags ]; then
|
if [ -d gflags ]; then
|
||||||
rm -rf gflags
|
rm -rf gflags
|
||||||
@@ -977,7 +1056,7 @@ if [ ! -d $PREFIX/include/gflags ]; then
|
|||||||
popd && popd
|
popd && popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install libunwind
|
log_tool_name "libunwind $LIBUNWIND_VERSION"
|
||||||
if [ ! -f $PREFIX/include/libunwind.h ]; then
|
if [ ! -f $PREFIX/include/libunwind.h ]; then
|
||||||
if [ -d libunwind-$LIBUNWIND_VERSION ]; then
|
if [ -d libunwind-$LIBUNWIND_VERSION ]; then
|
||||||
rm -rf libunwind-$LIBUNWIND_VERSION
|
rm -rf libunwind-$LIBUNWIND_VERSION
|
||||||
@@ -990,7 +1069,7 @@ if [ ! -f $PREFIX/include/libunwind.h ]; then
|
|||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install glog
|
log_tool_name "glog $GLOG_VERSION"
|
||||||
if [ ! -d $PREFIX/include/glog ]; then
|
if [ ! -d $PREFIX/include/glog ]; then
|
||||||
if [ -d glog-$GLOG_VERSION ]; then
|
if [ -d glog-$GLOG_VERSION ]; then
|
||||||
rm -rf glog-$GLOG_VERSION
|
rm -rf glog-$GLOG_VERSION
|
||||||
@@ -1004,7 +1083,7 @@ if [ ! -d $PREFIX/include/glog ]; then
|
|||||||
popd && popd
|
popd && popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install libevent
|
log_tool_name "libevent $LIBEVENT_VERSION"
|
||||||
if [ ! -d $PREFIX/include/event2 ]; then
|
if [ ! -d $PREFIX/include/event2 ]; then
|
||||||
if [ -d libevent-$LIBEVENT_VERSION ]; then
|
if [ -d libevent-$LIBEVENT_VERSION ]; then
|
||||||
rm -rf libevent-$LIBEVENT_VERSION
|
rm -rf libevent-$LIBEVENT_VERSION
|
||||||
@@ -1023,7 +1102,7 @@ if [ ! -d $PREFIX/include/event2 ]; then
|
|||||||
popd && popd
|
popd && popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install snappy
|
log_tool_name "snappy $SNAPPY_VERSION"
|
||||||
if [ ! -f $PREFIX/include/snappy.h ]; then
|
if [ ! -f $PREFIX/include/snappy.h ]; then
|
||||||
if [ -d snappy-$SNAPPY_VERSION ]; then
|
if [ -d snappy-$SNAPPY_VERSION ]; then
|
||||||
rm -rf snappy-$SNAPPY_VERSION
|
rm -rf snappy-$SNAPPY_VERSION
|
||||||
@@ -1041,7 +1120,7 @@ if [ ! -f $PREFIX/include/snappy.h ]; then
|
|||||||
popd && popd
|
popd && popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install libsodium
|
log_tool_name "libsodium $LIBSODIUM_VERSION"
|
||||||
if [ ! -f $PREFIX/include/sodium.h ]; then
|
if [ ! -f $PREFIX/include/sodium.h ]; then
|
||||||
if [ -d libsodium-$LIBSODIUM_VERSION ]; then
|
if [ -d libsodium-$LIBSODIUM_VERSION ]; then
|
||||||
rm -rf libsodium-$LIBSODIUM_VERSION
|
rm -rf libsodium-$LIBSODIUM_VERSION
|
||||||
@@ -1053,7 +1132,7 @@ if [ ! -f $PREFIX/include/sodium.h ]; then
|
|||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install libaio
|
log_tool_name "libaio $LIBAIO_VERSION"
|
||||||
if [ ! -f $PREFIX/include/libaio.h ]; then
|
if [ ! -f $PREFIX/include/libaio.h ]; then
|
||||||
if [ -d libaio-$LIBAIO_VERSION ]; then
|
if [ -d libaio-$LIBAIO_VERSION ]; then
|
||||||
rm -rf libaio-$LIBAIO_VERSION
|
rm -rf libaio-$LIBAIO_VERSION
|
||||||
@@ -1064,7 +1143,7 @@ if [ ! -f $PREFIX/include/libaio.h ]; then
|
|||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install folly
|
log_tool_name "folly $FBLIBS_VERSION"
|
||||||
if [ ! -d $PREFIX/include/folly ]; then
|
if [ ! -d $PREFIX/include/folly ]; then
|
||||||
if [ -d folly-$FBLIBS_VERSION ]; then
|
if [ -d folly-$FBLIBS_VERSION ]; then
|
||||||
rm -rf folly-$FBLIBS_VERSION
|
rm -rf folly-$FBLIBS_VERSION
|
||||||
@@ -1085,7 +1164,7 @@ if [ ! -d $PREFIX/include/folly ]; then
|
|||||||
popd && popd
|
popd && popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install fizz
|
log_tool_name "fizz $FBLIBS_VERSION"
|
||||||
if [ ! -d $PREFIX/include/fizz ]; then
|
if [ ! -d $PREFIX/include/fizz ]; then
|
||||||
if [ -d fizz-$FBLIBS_VERSION ]; then
|
if [ -d fizz-$FBLIBS_VERSION ]; then
|
||||||
rm -rf fizz-$FBLIBS_VERSION
|
rm -rf fizz-$FBLIBS_VERSION
|
||||||
@@ -1104,7 +1183,7 @@ if [ ! -d $PREFIX/include/fizz ]; then
|
|||||||
popd && popd
|
popd && popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install wangle
|
log_tool_name "wangle FBLIBS_VERSION"
|
||||||
if [ ! -d $PREFIX/include/wangle ]; then
|
if [ ! -d $PREFIX/include/wangle ]; then
|
||||||
if [ -d wangle-$FBLIBS_VERSION ]; then
|
if [ -d wangle-$FBLIBS_VERSION ]; then
|
||||||
rm -rf wangle-$FBLIBS_VERSION
|
rm -rf wangle-$FBLIBS_VERSION
|
||||||
@@ -1123,7 +1202,7 @@ if [ ! -d $PREFIX/include/wangle ]; then
|
|||||||
popd && popd
|
popd && popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install proxygen
|
log_tool_name "proxygen $FBLIBS_VERSION"
|
||||||
if [ ! -d $PREFIX/include/proxygen ]; then
|
if [ ! -d $PREFIX/include/proxygen ]; then
|
||||||
if [ -d proxygen-$FBLIBS_VERSION ]; then
|
if [ -d proxygen-$FBLIBS_VERSION ]; then
|
||||||
rm -rf proxygen-$FBLIBS_VERSION
|
rm -rf proxygen-$FBLIBS_VERSION
|
||||||
@@ -1144,7 +1223,7 @@ if [ ! -d $PREFIX/include/proxygen ]; then
|
|||||||
popd && popd
|
popd && popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install flex
|
log_tool_name "flex $FBLIBS_VERSION"
|
||||||
if [ ! -f $PREFIX/include/FlexLexer.h ]; then
|
if [ ! -f $PREFIX/include/FlexLexer.h ]; then
|
||||||
if [ -d flex-$FLEX_VERSION ]; then
|
if [ -d flex-$FLEX_VERSION ]; then
|
||||||
rm -rf flex-$FLEX_VERSION
|
rm -rf flex-$FLEX_VERSION
|
||||||
@@ -1156,7 +1235,7 @@ if [ ! -f $PREFIX/include/FlexLexer.h ]; then
|
|||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install fbthrift
|
log_tool_name "fbthrift $FBLIBS_VERSION"
|
||||||
if [ ! -d $PREFIX/include/thrift ]; then
|
if [ ! -d $PREFIX/include/thrift ]; then
|
||||||
if [ -d fbthrift-$FBLIBS_VERSION ]; then
|
if [ -d fbthrift-$FBLIBS_VERSION ]; then
|
||||||
rm -rf fbthrift-$FBLIBS_VERSION
|
rm -rf fbthrift-$FBLIBS_VERSION
|
||||||
@@ -1166,10 +1245,15 @@ if [ ! -d $PREFIX/include/thrift ]; then
|
|||||||
# build is used by facebook builder
|
# build is used by facebook builder
|
||||||
mkdir _build
|
mkdir _build
|
||||||
pushd _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 \
|
cmake .. $COMMON_CMAKE_FLAGS \
|
||||||
-Denable_tests=OFF \
|
-Denable_tests=OFF \
|
||||||
-DGFLAGS_NOTHREADS=OFF \
|
-DGFLAGS_NOTHREADS=OFF \
|
||||||
-DCMAKE_CXX_FLAGS=-fsized-deallocation
|
-DCMAKE_CXX_FLAGS="$CMAKE_CXX_FLAGS"
|
||||||
make -j$CPUS install
|
make -j$CPUS install
|
||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
@@ -1179,7 +1263,7 @@ popd
|
|||||||
# create toolchain archive
|
# create toolchain archive
|
||||||
if [ ! -f $NAME-binaries-$DISTRO.tar.gz ]; then
|
if [ ! -f $NAME-binaries-$DISTRO.tar.gz ]; then
|
||||||
DISTRO_FULL_NAME=${DISTRO}
|
DISTRO_FULL_NAME=${DISTRO}
|
||||||
if [[ "${DISTRO}" == centos* ]]; then
|
if [[ "${DISTRO}" == centos* ]] || [[ "${DISTRO}" == fedora* ]]; then
|
||||||
if [[ "$for_arm" = "true" ]]; then
|
if [[ "$for_arm" = "true" ]]; then
|
||||||
DISTRO_FULL_NAME="$DISTRO_FULL_NAME-aarch64"
|
DISTRO_FULL_NAME="$DISTRO_FULL_NAME-aarch64"
|
||||||
else
|
else
|
||||||
@@ -1192,7 +1276,12 @@ if [ ! -f $NAME-binaries-$DISTRO.tar.gz ]; then
|
|||||||
DISTRO_FULL_NAME="$DISTRO_FULL_NAME-amd64"
|
DISTRO_FULL_NAME="$DISTRO_FULL_NAME-amd64"
|
||||||
fi
|
fi
|
||||||
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
|
tar --owner=root --group=root -cpvzf $NAME-binaries-$DISTRO_FULL_NAME.tar.gz -C /opt $NAME
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
operating_system() {
|
function operating_system() {
|
||||||
grep -E '^(VERSION_)?ID=' /etc/os-release | \
|
grep -E '^(VERSION_)?ID=' /etc/os-release | \
|
||||||
sort | cut -d '=' -f 2- | sed 's/"//g' | paste -s -d '-'
|
sort | cut -d '=' -f 2- | sed 's/"//g' | paste -s -d '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
check_operating_system() {
|
function check_operating_system() {
|
||||||
if [ "$(operating_system)" != "$1" ]; then
|
if [ "$(operating_system)" != "$1" ]; then
|
||||||
echo "Not the right operating system!"
|
echo "Not the right operating system!"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -14,20 +14,25 @@ check_operating_system() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
architecture() {
|
function architecture() {
|
||||||
uname -m
|
uname -m
|
||||||
}
|
}
|
||||||
|
|
||||||
check_architecture() {
|
check_architecture() {
|
||||||
if [ "$(architecture)" != "$1" ]; then
|
local ARCH=$(architecture)
|
||||||
echo "Not the right architecture!"
|
for arch in "$@"; do
|
||||||
exit 1
|
if [ "${ARCH}" = "$arch" ]; then
|
||||||
else
|
echo "The right architecture!"
|
||||||
echo "The right architecture."
|
return 0
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
|
echo "Not the right architecture!"
|
||||||
|
echo "Expected: $@"
|
||||||
|
echo "Actual: ${ARCH}"
|
||||||
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
check_all_yum() {
|
function check_all_yum() {
|
||||||
local missing=""
|
local missing=""
|
||||||
for pkg in $1; do
|
for pkg in $1; do
|
||||||
if ! yum list installed "$pkg" >/dev/null 2>/dev/null; then
|
if ! yum list installed "$pkg" >/dev/null 2>/dev/null; then
|
||||||
@@ -40,7 +45,7 @@ check_all_yum() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
check_all_dpkg() {
|
function check_all_dpkg() {
|
||||||
local missing=""
|
local missing=""
|
||||||
for pkg in $1; do
|
for pkg in $1; do
|
||||||
if ! dpkg -s "$pkg" >/dev/null 2>/dev/null; then
|
if ! dpkg -s "$pkg" >/dev/null 2>/dev/null; then
|
||||||
@@ -53,7 +58,7 @@ check_all_dpkg() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
check_all_dnf() {
|
function check_all_dnf() {
|
||||||
local missing=""
|
local missing=""
|
||||||
for pkg in $1; do
|
for pkg in $1; do
|
||||||
if ! dnf list installed "$pkg" >/dev/null 2>/dev/null; then
|
if ! dnf list installed "$pkg" >/dev/null 2>/dev/null; then
|
||||||
@@ -65,7 +70,8 @@ check_all_dnf() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
install_all_apt() {
|
|
||||||
|
function install_all_apt() {
|
||||||
for pkg in $1; do
|
for pkg in $1; do
|
||||||
apt install -y "$pkg"
|
apt install -y "$pkg"
|
||||||
done
|
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
|
||||||
343
include/_mgp_mock.py
Normal file
343
include/_mgp_mock.py
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
import typing
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
NX_LABEL_ATTR = "labels"
|
||||||
|
NX_TYPE_ATTR = "type"
|
||||||
|
|
||||||
|
SOURCE_TYPE_KAFKA = "SOURCE_TYPE_KAFKA"
|
||||||
|
SOURCE_TYPE_PULSAR = "SOURCE_TYPE_PULSAR"
|
||||||
|
|
||||||
|
"""
|
||||||
|
This module provides helpers for the mock Python API, much like _mgp.py does for mgp.py.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidArgumentError(Exception):
|
||||||
|
"""
|
||||||
|
Signals that some of the arguments have invalid values.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ImmutableObjectError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LogicErrorError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DeletedObjectError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class EdgeConstants(Enum):
|
||||||
|
I_START = 0
|
||||||
|
I_END = 1
|
||||||
|
I_KEY = 2
|
||||||
|
|
||||||
|
|
||||||
|
class Graph:
|
||||||
|
"""Wrapper around a NetworkX MultiDiGraph instance."""
|
||||||
|
|
||||||
|
__slots__ = ("nx", "_highest_vertex_id", "_highest_edge_id", "_valid")
|
||||||
|
|
||||||
|
def __init__(self, graph: nx.MultiDiGraph) -> None:
|
||||||
|
if not isinstance(graph, nx.MultiDiGraph):
|
||||||
|
raise TypeError(f"Expected 'networkx.classes.multidigraph.MultiDiGraph', got '{type(graph)}'")
|
||||||
|
|
||||||
|
self.nx = graph
|
||||||
|
self._highest_vertex_id = None
|
||||||
|
self._highest_edge_id = None
|
||||||
|
self._valid = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def vertex_ids(self):
|
||||||
|
return self.nx.nodes
|
||||||
|
|
||||||
|
def vertex_is_isolate(self, vertex_id: int) -> bool:
|
||||||
|
return nx.is_isolate(self.nx, vertex_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def vertices(self):
|
||||||
|
return (Vertex(node_id, self) for node_id in self.nx.nodes)
|
||||||
|
|
||||||
|
def has_node(self, node_id):
|
||||||
|
return self.nx.has_node(node_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def edges(self):
|
||||||
|
return self.nx.edges
|
||||||
|
|
||||||
|
def is_valid(self) -> bool:
|
||||||
|
return self._valid
|
||||||
|
|
||||||
|
def get_vertex_by_id(self, vertex_id: int) -> "Vertex":
|
||||||
|
return Vertex(vertex_id, self)
|
||||||
|
|
||||||
|
def invalidate(self):
|
||||||
|
self._valid = False
|
||||||
|
|
||||||
|
def is_immutable(self) -> bool:
|
||||||
|
return nx.is_frozen(self.nx)
|
||||||
|
|
||||||
|
def make_immutable(self):
|
||||||
|
self.nx = nx.freeze(self.nx)
|
||||||
|
|
||||||
|
def _new_vertex_id(self):
|
||||||
|
if self._highest_vertex_id is None:
|
||||||
|
self._highest_vertex_id = max(vertex_id for vertex_id in self.nx.nodes)
|
||||||
|
|
||||||
|
return self._highest_vertex_id + 1
|
||||||
|
|
||||||
|
def _new_edge_id(self):
|
||||||
|
if self._highest_edge_id is None:
|
||||||
|
self._highest_edge_id = max(edge[EdgeConstants.I_KEY.value] for edge in self.nx.edges(keys=True))
|
||||||
|
|
||||||
|
return self._highest_edge_id + 1
|
||||||
|
|
||||||
|
def create_vertex(self) -> "Vertex":
|
||||||
|
vertex_id = self._new_vertex_id()
|
||||||
|
|
||||||
|
self.nx.add_node(vertex_id)
|
||||||
|
self._highest_vertex_id = vertex_id
|
||||||
|
|
||||||
|
return Vertex(vertex_id, self)
|
||||||
|
|
||||||
|
def create_edge(self, from_vertex: "Vertex", to_vertex: "Vertex", edge_type: str) -> "Edge":
|
||||||
|
if from_vertex.is_deleted() or to_vertex.is_deleted():
|
||||||
|
raise DeletedObjectError("Accessing deleted object.")
|
||||||
|
|
||||||
|
edge_id = self._new_edge_id()
|
||||||
|
|
||||||
|
from_id = from_vertex.id
|
||||||
|
to_id = to_vertex.id
|
||||||
|
|
||||||
|
self.nx.add_edge(from_id, to_id, key=edge_id, type=edge_type)
|
||||||
|
self._highest_edge_id = edge_id
|
||||||
|
|
||||||
|
return Edge((from_id, to_id, edge_id), self)
|
||||||
|
|
||||||
|
def delete_vertex(self, vertex_id: int):
|
||||||
|
self.nx.remove_node(vertex_id)
|
||||||
|
|
||||||
|
def delete_edge(self, from_vertex_id: int, to_vertex_id: int, edge_id: int):
|
||||||
|
self.nx.remove_edge(from_vertex_id, to_vertex_id, edge_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def highest_vertex_id(self) -> int:
|
||||||
|
if self._highest_vertex_id is None:
|
||||||
|
self._highest_vertex_id = max(vertex_id for vertex_id in self.nx.nodes) + 1
|
||||||
|
|
||||||
|
return self._highest_vertex_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def highest_edge_id(self) -> int:
|
||||||
|
if self._highest_edge_id is None:
|
||||||
|
self._highest_edge_id = max(edge[EdgeConstants.I_KEY.value] for edge in self.nx.edges(keys=True))
|
||||||
|
|
||||||
|
return self._highest_edge_id + 1
|
||||||
|
|
||||||
|
|
||||||
|
class Vertex:
|
||||||
|
"""Represents a graph vertex."""
|
||||||
|
|
||||||
|
__slots__ = ("_id", "_graph")
|
||||||
|
|
||||||
|
def __init__(self, id: int, graph: Graph) -> None:
|
||||||
|
if not isinstance(id, int):
|
||||||
|
raise TypeError(f"Expected 'int', got '{type(id)}'")
|
||||||
|
|
||||||
|
if not isinstance(graph, Graph):
|
||||||
|
raise TypeError(f"Expected '_mgp_mock.Graph', got '{type(graph)}'")
|
||||||
|
|
||||||
|
if not graph.nx.has_node(id):
|
||||||
|
raise IndexError(f"Unable to find vertex with ID {id}.")
|
||||||
|
|
||||||
|
self._id = id
|
||||||
|
self._graph = graph
|
||||||
|
|
||||||
|
def is_valid(self) -> bool:
|
||||||
|
return self._graph.is_valid()
|
||||||
|
|
||||||
|
def is_deleted(self) -> bool:
|
||||||
|
return not self._graph.nx.has_node(self._id) and self._id <= self._graph.highest_vertex_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def underlying_graph(self) -> Graph:
|
||||||
|
return self._graph
|
||||||
|
|
||||||
|
def underlying_graph_is_mutable(self) -> bool:
|
||||||
|
return not nx.is_frozen(self._graph.nx)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def labels(self) -> typing.List[int]:
|
||||||
|
return self._graph.nx.nodes[self._id][NX_LABEL_ATTR].split(":")
|
||||||
|
|
||||||
|
def add_label(self, label: str) -> None:
|
||||||
|
if nx.is_frozen(self._graph.nx):
|
||||||
|
raise ImmutableObjectError("Cannot modify immutable object.")
|
||||||
|
|
||||||
|
self._graph.nx.nodes[self._id][NX_LABEL_ATTR] += f":{label}"
|
||||||
|
|
||||||
|
def remove_label(self, label: str) -> None:
|
||||||
|
if nx.is_frozen(self._graph.nx):
|
||||||
|
raise ImmutableObjectError("Cannot modify immutable object.")
|
||||||
|
|
||||||
|
labels = self._graph.nx.nodes[self._id][NX_LABEL_ATTR]
|
||||||
|
if labels.startswith(f"{label}:"):
|
||||||
|
labels = "\n" + labels # pseudo-string starter
|
||||||
|
self._graph.nx.nodes[self._id][NX_LABEL_ATTR] = labels.replace(f"\n{label}:", "")
|
||||||
|
elif labels.endswith(f":{label}"):
|
||||||
|
labels += "\n" # pseudo-string terminator
|
||||||
|
self._graph.nx.nodes[self._id][NX_LABEL_ATTR] = labels.replace(f":{label}\n", "")
|
||||||
|
else:
|
||||||
|
self._graph.nx.nodes[self._id][NX_LABEL_ATTR] = labels.replace(f":{label}:", ":")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self) -> int:
|
||||||
|
return self._id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def properties(self):
|
||||||
|
return (
|
||||||
|
(key, value)
|
||||||
|
for key, value in self._graph.nx.nodes[self._id].items()
|
||||||
|
if key not in (NX_LABEL_ATTR, NX_TYPE_ATTR)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_property(self, property_name: str):
|
||||||
|
return self._graph.nx.nodes[self._id][property_name]
|
||||||
|
|
||||||
|
def set_property(self, property_name: str, value: object):
|
||||||
|
self._graph.nx.nodes[self._id][property_name] = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def in_edges(self) -> typing.Iterable["Edge"]:
|
||||||
|
return [Edge(edge, self._graph) for edge in self._graph.nx.in_edges(self._id, keys=True)]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def out_edges(self) -> typing.Iterable["Edge"]:
|
||||||
|
return [Edge(edge, self._graph) for edge in self._graph.nx.out_edges(self._id, keys=True)]
|
||||||
|
|
||||||
|
|
||||||
|
class Edge:
|
||||||
|
"""Represents a graph edge."""
|
||||||
|
|
||||||
|
__slots__ = ("_edge", "_graph")
|
||||||
|
|
||||||
|
def __init__(self, edge: typing.Tuple[int, int, int], graph: Graph) -> None:
|
||||||
|
if not isinstance(edge, typing.Tuple):
|
||||||
|
raise TypeError(f"Expected 'Tuple', got '{type(edge)}'")
|
||||||
|
|
||||||
|
if not isinstance(graph, Graph):
|
||||||
|
raise TypeError(f"Expected '_mgp_mock.Graph', got '{type(graph)}'")
|
||||||
|
|
||||||
|
if not graph.nx.has_edge(*edge):
|
||||||
|
raise IndexError(f"Unable to find edge with ID {edge[EdgeConstants.I_KEY.value]}.")
|
||||||
|
|
||||||
|
self._edge = edge
|
||||||
|
self._graph = graph
|
||||||
|
|
||||||
|
def is_valid(self) -> bool:
|
||||||
|
return self._graph.is_valid()
|
||||||
|
|
||||||
|
def is_deleted(self) -> bool:
|
||||||
|
return (
|
||||||
|
not self._graph.nx.has_edge(*self._edge)
|
||||||
|
and self._edge[EdgeConstants.I_KEY.value] <= self._graph.highest_edge_id
|
||||||
|
)
|
||||||
|
|
||||||
|
def underlying_graph_is_mutable(self) -> bool:
|
||||||
|
return not nx.is_frozen(self._graph.nx)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self) -> int:
|
||||||
|
return self._edge[EdgeConstants.I_KEY.value]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def edge(self) -> typing.Tuple[int, int, int]:
|
||||||
|
return self._edge
|
||||||
|
|
||||||
|
@property
|
||||||
|
def start_id(self) -> int:
|
||||||
|
return self._edge[EdgeConstants.I_START.value]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def end_id(self) -> int:
|
||||||
|
return self._edge[EdgeConstants.I_END.value]
|
||||||
|
|
||||||
|
def get_type_name(self):
|
||||||
|
return self._graph.nx.get_edge_data(*self._edge)[NX_TYPE_ATTR]
|
||||||
|
|
||||||
|
def from_vertex(self) -> Vertex:
|
||||||
|
return Vertex(self.start_id, self._graph)
|
||||||
|
|
||||||
|
def to_vertex(self) -> Vertex:
|
||||||
|
return Vertex(self.end_id, self._graph)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def properties(self):
|
||||||
|
return (
|
||||||
|
(key, value)
|
||||||
|
for key, value in self._graph.nx.edges[self._edge].items()
|
||||||
|
if key not in (NX_LABEL_ATTR, NX_TYPE_ATTR)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_property(self, property_name: str):
|
||||||
|
return self._graph.nx.edges[self._edge][property_name]
|
||||||
|
|
||||||
|
def set_property(self, property_name: str, value: object):
|
||||||
|
self._graph.nx.edges[self._edge][property_name] = value
|
||||||
|
|
||||||
|
|
||||||
|
class Path:
|
||||||
|
"""Represents a path comprised of `Vertex` and `Edge` instances."""
|
||||||
|
|
||||||
|
__slots__ = ("_vertices", "_edges", "_graph")
|
||||||
|
__create_key = object()
|
||||||
|
|
||||||
|
def __init__(self, create_key, vertex_id: int, graph: Graph) -> None:
|
||||||
|
assert create_key == Path.__create_key, "Path objects must be created using Path.make_with_start"
|
||||||
|
|
||||||
|
self._vertices = [vertex_id]
|
||||||
|
self._edges = []
|
||||||
|
self._graph = graph
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def make_with_start(cls, vertex: Vertex) -> "Path":
|
||||||
|
if not isinstance(vertex, Vertex):
|
||||||
|
raise TypeError(f"Expected 'Vertex', got '{type(vertex)}'")
|
||||||
|
|
||||||
|
if not isinstance(vertex.underlying_graph, Graph):
|
||||||
|
raise TypeError(f"Expected '_mgp_mock.Graph', got '{type(vertex.underlying_graph)}'")
|
||||||
|
|
||||||
|
if not vertex.underlying_graph.nx.has_node(vertex._id):
|
||||||
|
raise IndexError(f"Unable to find vertex with ID {vertex._id}.")
|
||||||
|
|
||||||
|
return Path(cls.__create_key, vertex._id, vertex.underlying_graph)
|
||||||
|
|
||||||
|
def is_valid(self) -> bool:
|
||||||
|
return self._graph.is_valid()
|
||||||
|
|
||||||
|
def underlying_graph_is_mutable(self) -> bool:
|
||||||
|
return not nx.is_frozen(self._graph.nx)
|
||||||
|
|
||||||
|
def expand(self, edge: Edge):
|
||||||
|
if edge.start_id != self._vertices[-1]:
|
||||||
|
raise LogicErrorError("Logic error.")
|
||||||
|
|
||||||
|
self._vertices.append(edge.end_id)
|
||||||
|
self._edges.append((edge.start_id, edge.end_id, edge.id))
|
||||||
|
|
||||||
|
def vertex_at(self, index: int) -> Vertex:
|
||||||
|
return Vertex(self._vertices[index], self._graph)
|
||||||
|
|
||||||
|
def edge_at(self, index: int) -> Edge:
|
||||||
|
return Edge(self._edges[index], self._graph)
|
||||||
|
|
||||||
|
def size(self) -> int:
|
||||||
|
return len(self._edges)
|
||||||
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
|
/// 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.
|
/// 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_ENUM_CLASS MGP_NODISCARD mgp_error{
|
||||||
MGP_ERROR_NO_ERROR, MGP_ERROR_UNKNOWN_ERROR,
|
MGP_ERROR_NO_ERROR,
|
||||||
MGP_ERROR_UNABLE_TO_ALLOCATE, MGP_ERROR_INSUFFICIENT_BUFFER,
|
MGP_ERROR_UNKNOWN_ERROR,
|
||||||
MGP_ERROR_OUT_OF_RANGE, MGP_ERROR_LOGIC_ERROR,
|
MGP_ERROR_UNABLE_TO_ALLOCATE,
|
||||||
MGP_ERROR_DELETED_OBJECT, MGP_ERROR_INVALID_ARGUMENT,
|
MGP_ERROR_INSUFFICIENT_BUFFER,
|
||||||
MGP_ERROR_KEY_ALREADY_EXISTS, MGP_ERROR_IMMUTABLE_OBJECT,
|
MGP_ERROR_OUT_OF_RANGE,
|
||||||
MGP_ERROR_VALUE_CONVERSION, MGP_ERROR_SERIALIZATION_ERROR,
|
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,
|
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.
|
/// Free the memory used by the given mgp_value instance.
|
||||||
void mgp_value_destroy(struct mgp_value *val);
|
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.
|
/// 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_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.
|
/// Free the memory used by the given mgp_list and contained elements.
|
||||||
void mgp_list_destroy(struct mgp_list *list);
|
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.
|
/// 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_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.
|
/// Free the memory used by the given mgp_map and contained items.
|
||||||
void mgp_map_destroy(struct mgp_map *map);
|
void mgp_map_destroy(struct mgp_map *map);
|
||||||
|
|
||||||
@@ -1292,6 +1305,12 @@ struct mgp_proc;
|
|||||||
/// Describes a Memgraph magic function.
|
/// Describes a Memgraph magic function.
|
||||||
struct mgp_func;
|
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.
|
/// Entry-point for a query module read procedure, invoked through openCypher.
|
||||||
///
|
///
|
||||||
/// Passed in arguments will not live longer than the callback's execution.
|
/// 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_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.
|
/// 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);
|
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
|
/// @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.
|
/// Add a required argument to a function.
|
||||||
///
|
///
|
||||||
/// The order of the added arguments corresponds to the signature of the openCypher 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
446
include/mgp.py
446
include/mgp.py
@@ -134,6 +134,15 @@ class SerializationError(_mgp.SerializationError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AuthorizationError(_mgp.AuthorizationError):
|
||||||
|
"""
|
||||||
|
Signals that the user doesn't have sufficient permissions to perform
|
||||||
|
procedure call.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Label:
|
class Label:
|
||||||
"""Label of a `Vertex`."""
|
"""Label of a `Vertex`."""
|
||||||
|
|
||||||
@@ -146,7 +155,7 @@ class Label:
|
|||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""
|
"""
|
||||||
Get the name of the label.
|
Get the name of the label.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A string that represents the name of the label.
|
A string that represents the name of the label.
|
||||||
|
|
||||||
@@ -195,20 +204,20 @@ class Properties:
|
|||||||
def get(self, property_name: str, default=None) -> object:
|
def get(self, property_name: str, default=None) -> object:
|
||||||
"""
|
"""
|
||||||
Get the value of a property with the given name or return default value.
|
Get the value of a property with the given name or return default value.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
property_name: String that represents property name.
|
property_name: String that represents property name.
|
||||||
default: Default value return if there is no property.
|
default: Default value return if there is no property.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Any object value that property under `property_name` has or default value otherwise.
|
Any object value that property under `property_name` has or default value otherwise.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If `edge` or `vertex` is out of context.
|
InvalidContextError: If `edge` or `vertex` is out of context.
|
||||||
UnableToAllocateError: If unable to allocate a `mgp.Value`.
|
UnableToAllocateError: If unable to allocate a `mgp.Value`.
|
||||||
DeletedObjectError: If the `object` has been deleted.
|
DeletedObjectError: If the `object` has been deleted.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```
|
```
|
||||||
vertex.properties.get(property_name)
|
vertex.properties.get(property_name)
|
||||||
edge.properties.get(property_name)
|
edge.properties.get(property_name)
|
||||||
@@ -227,23 +236,23 @@ class Properties:
|
|||||||
Set the value of the property. When the value is `None`, then the
|
Set the value of the property. When the value is `None`, then the
|
||||||
property is removed.
|
property is removed.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
property_name: String that represents property name.
|
property_name: String that represents property name.
|
||||||
value: Object that represents value to be set.
|
value: Object that represents value to be set.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
UnableToAllocateError: If unable to allocate memory for storing the property.
|
UnableToAllocateError: If unable to allocate memory for storing the property.
|
||||||
ImmutableObjectError: If the object is immutable.
|
ImmutableObjectError: If the object is immutable.
|
||||||
DeletedObjectError: If the object has been deleted.
|
DeletedObjectError: If the object has been deleted.
|
||||||
SerializationError: If the object has been modified by another transaction.
|
SerializationError: If the object has been modified by another transaction.
|
||||||
ValueConversionError: If `value` is vertex, edge or path.
|
ValueConversionError: If `value` is vertex, edge or path.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```
|
```
|
||||||
vertex.properties.set(property_name, value)
|
vertex.properties.set(property_name, value)
|
||||||
edge.properties.set(property_name, value)
|
edge.properties.set(property_name, value)
|
||||||
```
|
```
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self[property_name] = value
|
self[property_name] = value
|
||||||
|
|
||||||
@@ -252,15 +261,15 @@ class Properties:
|
|||||||
Iterate over the properties. Doesn’t return a dynamic view of the properties but copies the
|
Iterate over the properties. Doesn’t return a dynamic view of the properties but copies the
|
||||||
current properties.
|
current properties.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Iterable `Property` of names and values.
|
Iterable `Property` of names and values.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If edge or vertex is out of context.
|
InvalidContextError: If edge or vertex is out of context.
|
||||||
UnableToAllocateError: If unable to allocate an iterator.
|
UnableToAllocateError: If unable to allocate an iterator.
|
||||||
DeletedObjectError: If the object has been deleted.
|
DeletedObjectError: If the object has been deleted.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```
|
```
|
||||||
items = vertex.properties.items()
|
items = vertex.properties.items()
|
||||||
for it in items:
|
for it in items:
|
||||||
@@ -290,15 +299,15 @@ class Properties:
|
|||||||
Iterate over property names. Doesn’t return a dynamic view of the property names but copies the
|
Iterate over property names. Doesn’t return a dynamic view of the property names but copies the
|
||||||
name of the current properties.
|
name of the current properties.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Iterable list of strings that represent names/keys of properties.
|
Iterable list of strings that represent names/keys of properties.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If edge or vertex is out of context.
|
InvalidContextError: If edge or vertex is out of context.
|
||||||
UnableToAllocateError: If unable to allocate an iterator.
|
UnableToAllocateError: If unable to allocate an iterator.
|
||||||
DeletedObjectError: If the object has been deleted.
|
DeletedObjectError: If the object has been deleted.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```
|
```
|
||||||
graph.vertex.properties.keys()
|
graph.vertex.properties.keys()
|
||||||
graph.edge.properties.keys()
|
graph.edge.properties.keys()
|
||||||
@@ -314,20 +323,20 @@ class Properties:
|
|||||||
Iterate over property values. Doesn’t return a dynamic view of the property values but copies the
|
Iterate over property values. Doesn’t return a dynamic view of the property values but copies the
|
||||||
value of the current properties.
|
value of the current properties.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Iterable list of property values.
|
Iterable list of property values.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If edge or vertex is out of context.
|
InvalidContextError: If edge or vertex is out of context.
|
||||||
UnableToAllocateError: If unable to allocate an iterator.
|
UnableToAllocateError: If unable to allocate an iterator.
|
||||||
DeletedObjectError: If the object has been deleted.
|
DeletedObjectError: If the object has been deleted.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```
|
```
|
||||||
vertex.properties.values()
|
vertex.properties.values()
|
||||||
edge.properties.values()
|
edge.properties.values()
|
||||||
```
|
```
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not self._vertex_or_edge.is_valid():
|
if not self._vertex_or_edge.is_valid():
|
||||||
raise InvalidContextError()
|
raise InvalidContextError()
|
||||||
@@ -338,15 +347,15 @@ class Properties:
|
|||||||
"""
|
"""
|
||||||
Get the number of properties.
|
Get the number of properties.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A number of properties on vertex or edge.
|
A number of properties on vertex or edge.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If edge or vertex is out of context.
|
InvalidContextError: If edge or vertex is out of context.
|
||||||
UnableToAllocateError: If unable to allocate an iterator.
|
UnableToAllocateError: If unable to allocate an iterator.
|
||||||
DeletedObjectError: If the object has been deleted.
|
DeletedObjectError: If the object has been deleted.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```
|
```
|
||||||
len(vertex.properties)
|
len(vertex.properties)
|
||||||
len(edge.properties)
|
len(edge.properties)
|
||||||
@@ -363,15 +372,15 @@ class Properties:
|
|||||||
"""
|
"""
|
||||||
Iterate over property names.
|
Iterate over property names.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Iterable list of strings that represent names of properties.
|
Iterable list of strings that represent names of properties.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If edge or vertex is out of context.
|
InvalidContextError: If edge or vertex is out of context.
|
||||||
UnableToAllocateError: If unable to allocate an iterator.
|
UnableToAllocateError: If unable to allocate an iterator.
|
||||||
DeletedObjectError: If the object has been deleted.
|
DeletedObjectError: If the object has been deleted.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```
|
```
|
||||||
iter(vertex.properties)
|
iter(vertex.properties)
|
||||||
iter(edge.properties)
|
iter(edge.properties)
|
||||||
@@ -386,24 +395,24 @@ class Properties:
|
|||||||
def __getitem__(self, property_name: str) -> object:
|
def __getitem__(self, property_name: str) -> object:
|
||||||
"""
|
"""
|
||||||
Get the value of a property with the given name or raise KeyError.
|
Get the value of a property with the given name or raise KeyError.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
property_name: String that represents property name.
|
property_name: String that represents property name.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Any value that property under property_name have.
|
Any value that property under property_name have.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If edge or vertex is out of context.
|
InvalidContextError: If edge or vertex is out of context.
|
||||||
UnableToAllocateError: If unable to allocate a mgp.Value.
|
UnableToAllocateError: If unable to allocate a mgp.Value.
|
||||||
DeletedObjectError: If the object has been deleted.
|
DeletedObjectError: If the object has been deleted.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```
|
```
|
||||||
vertex.properties[property_name]
|
vertex.properties[property_name]
|
||||||
edge.properties[property_name]
|
edge.properties[property_name]
|
||||||
```
|
```
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not self._vertex_or_edge.is_valid():
|
if not self._vertex_or_edge.is_valid():
|
||||||
raise InvalidContextError()
|
raise InvalidContextError()
|
||||||
@@ -417,18 +426,18 @@ class Properties:
|
|||||||
Set the value of the property. When the value is `None`, then the
|
Set the value of the property. When the value is `None`, then the
|
||||||
property is removed.
|
property is removed.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
property_name: String that represents property name.
|
property_name: String that represents property name.
|
||||||
value: Object that represents value to be set.
|
value: Object that represents value to be set.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
UnableToAllocateError: If unable to allocate memory for storing the property.
|
UnableToAllocateError: If unable to allocate memory for storing the property.
|
||||||
ImmutableObjectError: If the object is immutable.
|
ImmutableObjectError: If the object is immutable.
|
||||||
DeletedObjectError: If the object has been deleted.
|
DeletedObjectError: If the object has been deleted.
|
||||||
SerializationError: If the object has been modified by another transaction.
|
SerializationError: If the object has been modified by another transaction.
|
||||||
ValueConversionError: If `value` is vertex, edge or path.
|
ValueConversionError: If `value` is vertex, edge or path.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```
|
```
|
||||||
vertex.properties[property_name] = value
|
vertex.properties[property_name] = value
|
||||||
edge.properties[property_name] = value
|
edge.properties[property_name] = value
|
||||||
@@ -443,18 +452,18 @@ class Properties:
|
|||||||
"""
|
"""
|
||||||
Check if there is a property with the given name.
|
Check if there is a property with the given name.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
property_name: String that represents property name
|
property_name: String that represents property name
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Bool value that depends if there is with a given name.
|
Bool value that depends if there is with a given name.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If edge or vertex is out of context.
|
InvalidContextError: If edge or vertex is out of context.
|
||||||
UnableToAllocateError: If unable to allocate a mgp.Value.
|
UnableToAllocateError: If unable to allocate a mgp.Value.
|
||||||
DeletedObjectError: If the object has been deleted.
|
DeletedObjectError: If the object has been deleted.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```
|
```
|
||||||
if property_name in vertex.properties:
|
if property_name in vertex.properties:
|
||||||
```
|
```
|
||||||
@@ -483,7 +492,7 @@ class EdgeType:
|
|||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""
|
"""
|
||||||
Get the name of EdgeType.
|
Get the name of EdgeType.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A string that represents the name of EdgeType.
|
A string that represents the name of EdgeType.
|
||||||
|
|
||||||
@@ -512,7 +521,7 @@ class Edge:
|
|||||||
Access to an Edge is only valid during a single execution of a procedure in
|
Access to an Edge is only valid during a single execution of a procedure in
|
||||||
a query. You should not globally store an instance of an Edge. Using an
|
a query. You should not globally store an instance of an Edge. Using an
|
||||||
invalid Edge instance will raise InvalidContextError.
|
invalid Edge instance will raise InvalidContextError.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ("_edge",)
|
__slots__ = ("_edge",)
|
||||||
@@ -532,10 +541,10 @@ class Edge:
|
|||||||
def is_valid(self) -> bool:
|
def is_valid(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if `edge` is in a valid context and may be used.
|
Check if `edge` is in a valid context and may be used.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A `bool` value depends on if the `edge` is in a valid context.
|
A `bool` value depends on if the `edge` is in a valid context.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```edge.is_valid()```
|
```edge.is_valid()```
|
||||||
|
|
||||||
@@ -543,15 +552,15 @@ class Edge:
|
|||||||
return self._edge.is_valid()
|
return self._edge.is_valid()
|
||||||
|
|
||||||
def underlying_graph_is_mutable(self) -> bool:
|
def underlying_graph_is_mutable(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if the `graph` can be modified.
|
Check if the `graph` can be modified.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A `bool` value depends on if the `graph` is mutable.
|
A `bool` value depends on if the `graph` is mutable.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```edge.underlying_graph_is_mutable()```
|
```edge.underlying_graph_is_mutable()```
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
raise InvalidContextError()
|
raise InvalidContextError()
|
||||||
@@ -564,10 +573,10 @@ class Edge:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
`EdgeId` represents ID of the edge.
|
`EdgeId` represents ID of the edge.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If edge is out of context.
|
InvalidContextError: If edge is out of context.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```edge.id```
|
```edge.id```
|
||||||
"""
|
"""
|
||||||
@@ -581,12 +590,12 @@ class Edge:
|
|||||||
Get the type of edge.
|
Get the type of edge.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
`EdgeType` describing the type of edge.
|
`EdgeType` describing the type of edge.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If edge is out of context.
|
InvalidContextError: If edge is out of context.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```edge.type```
|
```edge.type```
|
||||||
"""
|
"""
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
@@ -598,10 +607,10 @@ class Edge:
|
|||||||
"""
|
"""
|
||||||
Get the source vertex.
|
Get the source vertex.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
`Vertex` from where the edge is directed.
|
`Vertex` from where the edge is directed.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If edge is out of context.
|
InvalidContextError: If edge is out of context.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@@ -615,14 +624,14 @@ class Edge:
|
|||||||
def to_vertex(self) -> "Vertex":
|
def to_vertex(self) -> "Vertex":
|
||||||
"""
|
"""
|
||||||
Get the destination vertex.
|
Get the destination vertex.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
`Vertex` to where the edge is directed.
|
`Vertex` to where the edge is directed.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If edge is out of context.
|
InvalidContextError: If edge is out of context.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```edge.to_vertex```
|
```edge.to_vertex```
|
||||||
"""
|
"""
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
@@ -635,7 +644,7 @@ class Edge:
|
|||||||
Get the properties of the edge.
|
Get the properties of the edge.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
All `Properties` of edge.
|
All `Properties` of edge.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If edge is out of context.
|
InvalidContextError: If edge is out of context.
|
||||||
@@ -692,9 +701,9 @@ class Vertex:
|
|||||||
Checks if `Vertex` is in valid context and may be used.
|
Checks if `Vertex` is in valid context and may be used.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A `bool` value depends on if the `Vertex` is in a valid context.
|
A `bool` value depends on if the `Vertex` is in a valid context.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```vertex.is_valid()```
|
```vertex.is_valid()```
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -702,14 +711,14 @@ class Vertex:
|
|||||||
|
|
||||||
def underlying_graph_is_mutable(self) -> bool:
|
def underlying_graph_is_mutable(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if the `graph` is mutable.
|
Check if the `graph` is mutable.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A `bool` value depends on if the `graph` is mutable.
|
A `bool` value depends on if the `graph` is mutable.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```vertex.underlying_graph_is_mutable()```
|
```vertex.underlying_graph_is_mutable()```
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
raise InvalidContextError()
|
raise InvalidContextError()
|
||||||
@@ -722,10 +731,10 @@ class Vertex:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
`VertexId` represents ID of the vertex.
|
`VertexId` represents ID of the vertex.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If vertex is out of context.
|
InvalidContextError: If vertex is out of context.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```vertex.id```
|
```vertex.id```
|
||||||
"""
|
"""
|
||||||
@@ -738,15 +747,15 @@ class Vertex:
|
|||||||
"""
|
"""
|
||||||
Get the labels of the vertex.
|
Get the labels of the vertex.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A tuple of `Label` representing vertex Labels
|
A tuple of `Label` representing vertex Labels
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If vertex is out of context.
|
InvalidContextError: If vertex is out of context.
|
||||||
OutOfRangeError: If some of the labels are removed while collecting the labels.
|
OutOfRangeError: If some of the labels are removed while collecting the labels.
|
||||||
DeletedObjectError: If `Vertex` has been deleted.
|
DeletedObjectError: If `Vertex` has been deleted.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```vertex.labels```
|
```vertex.labels```
|
||||||
"""
|
"""
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
@@ -757,17 +766,17 @@ class Vertex:
|
|||||||
"""
|
"""
|
||||||
Add the label to the vertex.
|
Add the label to the vertex.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
label: String label to be added.
|
label: String label to be added.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If `Vertex` is out of context.
|
InvalidContextError: If `Vertex` is out of context.
|
||||||
UnableToAllocateError: If unable to allocate memory for storing the label.
|
UnableToAllocateError: If unable to allocate memory for storing the label.
|
||||||
ImmutableObjectError: If `Vertex` is immutable.
|
ImmutableObjectError: If `Vertex` is immutable.
|
||||||
DeletedObjectError: If `Vertex` has been deleted.
|
DeletedObjectError: If `Vertex` has been deleted.
|
||||||
SerializationError: If `Vertex` has been modified by another transaction.
|
SerializationError: If `Vertex` has been modified by another transaction.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```vertex.add_label(label)```
|
```vertex.add_label(label)```
|
||||||
"""
|
"""
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
@@ -778,15 +787,15 @@ class Vertex:
|
|||||||
"""
|
"""
|
||||||
Remove the label from the vertex.
|
Remove the label from the vertex.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
label: String label to be deleted
|
label: String label to be deleted
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If `Vertex` is out of context.
|
InvalidContextError: If `Vertex` is out of context.
|
||||||
ImmutableObjectError: If `Vertex` is immutable.
|
ImmutableObjectError: If `Vertex` is immutable.
|
||||||
DeletedObjectError: If `Vertex` has been deleted.
|
DeletedObjectError: If `Vertex` has been deleted.
|
||||||
SerializationError: If `Vertex` has been modified by another transaction.
|
SerializationError: If `Vertex` has been modified by another transaction.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```vertex.remove_label(label)```
|
```vertex.remove_label(label)```
|
||||||
"""
|
"""
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
@@ -798,13 +807,13 @@ class Vertex:
|
|||||||
"""
|
"""
|
||||||
Get the properties of the vertex.
|
Get the properties of the vertex.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
`Properties` on a current vertex.
|
`Properties` on a current vertex.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If `Vertex` is out of context.
|
InvalidContextError: If `Vertex` is out of context.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```vertex.properties```
|
```vertex.properties```
|
||||||
"""
|
"""
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
@@ -820,13 +829,13 @@ class Vertex:
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Iterable list of `Edge` objects that are directed in towards the current vertex.
|
Iterable list of `Edge` objects that are directed in towards the current vertex.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If `Vertex` is out of context.
|
InvalidContextError: If `Vertex` is out of context.
|
||||||
UnableToAllocateError: If unable to allocate an iterator.
|
UnableToAllocateError: If unable to allocate an iterator.
|
||||||
DeletedObjectError: If `Vertex` has been deleted.
|
DeletedObjectError: If `Vertex` has been deleted.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```for edge in vertex.in_edges:```
|
```for edge in vertex.in_edges:```
|
||||||
"""
|
"""
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
@@ -850,12 +859,12 @@ class Vertex:
|
|||||||
Returns:
|
Returns:
|
||||||
Iterable list of `Edge` objects that are directed out of the current vertex.
|
Iterable list of `Edge` objects that are directed out of the current vertex.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If `Vertex` is out of context.
|
InvalidContextError: If `Vertex` is out of context.
|
||||||
UnableToAllocateError: If unable to allocate an iterator.
|
UnableToAllocateError: If unable to allocate an iterator.
|
||||||
DeletedObjectError: If `Vertex` has been deleted.
|
DeletedObjectError: If `Vertex` has been deleted.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```for edge in vertex.out_edges:```
|
```for edge in vertex.out_edges:```
|
||||||
"""
|
"""
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
@@ -888,7 +897,7 @@ class Path:
|
|||||||
def __init__(self, starting_vertex_or_path: typing.Union[_mgp.Path, Vertex]):
|
def __init__(self, starting_vertex_or_path: typing.Union[_mgp.Path, Vertex]):
|
||||||
"""Initialize with a starting Vertex.
|
"""Initialize with a starting Vertex.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If passed in Vertex is invalid.
|
InvalidContextError: If passed in Vertex is invalid.
|
||||||
UnableToAllocateError: If cannot allocate a path.
|
UnableToAllocateError: If cannot allocate a path.
|
||||||
"""
|
"""
|
||||||
@@ -932,10 +941,10 @@ class Path:
|
|||||||
def is_valid(self) -> bool:
|
def is_valid(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if `Path` is in valid context and may be used.
|
Check if `Path` is in valid context and may be used.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A `bool` value depends on if the `Path` is in a valid context.
|
A `bool` value depends on if the `Path` is in a valid context.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```path.is_valid()```
|
```path.is_valid()```
|
||||||
"""
|
"""
|
||||||
@@ -948,15 +957,15 @@ class Path:
|
|||||||
The last vertex on the path will become the other endpoint of the given
|
The last vertex on the path will become the other endpoint of the given
|
||||||
edge, as continued from the current last vertex.
|
edge, as continued from the current last vertex.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
edge: `Edge` that is added to the path
|
edge: `Edge` that is added to the path
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If using an invalid `Path` instance or if passed in `Edge` is invalid.
|
InvalidContextError: If using an invalid `Path` instance or if passed in `Edge` is invalid.
|
||||||
LogicErrorError: If the current last vertex in the path is not part of the given edge.
|
LogicErrorError: If the current last vertex in the path is not part of the given edge.
|
||||||
UnableToAllocateError: If unable to allocate memory for path extension.
|
UnableToAllocateError: If unable to allocate memory for path extension.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```path.expand(edge)```
|
```path.expand(edge)```
|
||||||
"""
|
"""
|
||||||
if not isinstance(edge, Edge):
|
if not isinstance(edge, Edge):
|
||||||
@@ -973,14 +982,14 @@ class Path:
|
|||||||
"""
|
"""
|
||||||
Vertices are ordered from the start to the end of the path.
|
Vertices are ordered from the start to the end of the path.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A tuple of `Vertex` objects order from start to end of the path.
|
A tuple of `Vertex` objects order from start to end of the path.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If using an invalid Path instance.
|
InvalidContextError: If using an invalid Path instance.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```path.vertices```
|
```path.vertices```
|
||||||
"""
|
"""
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
raise InvalidContextError()
|
raise InvalidContextError()
|
||||||
@@ -994,11 +1003,11 @@ class Path:
|
|||||||
"""
|
"""
|
||||||
Edges are ordered from the start to the end of the path.
|
Edges are ordered from the start to the end of the path.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A tuple of `Edge` objects order from start to end of the path
|
A tuple of `Edge` objects order from start to end of the path
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If using an invalid `Path` instance.
|
InvalidContextError: If using an invalid `Path` instance.
|
||||||
Examples:
|
Examples:
|
||||||
```path.edges```
|
```path.edges```
|
||||||
"""
|
"""
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
@@ -1039,10 +1048,10 @@ class Vertices:
|
|||||||
def is_valid(self) -> bool:
|
def is_valid(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if `Vertices` is in valid context and may be used.
|
Check if `Vertices` is in valid context and may be used.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A `bool` value depends on if the `Vertices` is in valid context.
|
A `bool` value depends on if the `Vertices` is in valid context.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```vertices.is_valid()```
|
```vertices.is_valid()```
|
||||||
"""
|
"""
|
||||||
@@ -1052,14 +1061,14 @@ class Vertices:
|
|||||||
"""
|
"""
|
||||||
Iterate over vertices.
|
Iterate over vertices.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Iterable list of `Vertex` objects.
|
Iterable list of `Vertex` objects.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If context is invalid.
|
InvalidContextError: If context is invalid.
|
||||||
UnableToAllocateError: If unable to allocate an iterator or a vertex.
|
UnableToAllocateError: If unable to allocate an iterator or a vertex.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```
|
```
|
||||||
for vertex in graph.vertices:
|
for vertex in graph.vertices:
|
||||||
```
|
```
|
||||||
@@ -1080,18 +1089,18 @@ class Vertices:
|
|||||||
|
|
||||||
def __contains__(self, vertex):
|
def __contains__(self, vertex):
|
||||||
"""
|
"""
|
||||||
Check if Vertices contain the given vertex.
|
Check if Vertices contain the given vertex.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
vertex: `Vertex` to be checked if it is a part of graph `Vertices`.
|
vertex: `Vertex` to be checked if it is a part of graph `Vertices`.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Bool value depends if there is `Vertex` in graph `Vertices`.
|
Bool value depends if there is `Vertex` in graph `Vertices`.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
UnableToAllocateError: If unable to allocate the vertex.
|
UnableToAllocateError: If unable to allocate the vertex.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```if vertex in graph.vertices:```
|
```if vertex in graph.vertices:```
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
@@ -1104,14 +1113,14 @@ class Vertices:
|
|||||||
"""
|
"""
|
||||||
Get the number of vertices.
|
Get the number of vertices.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A number of vertices in the graph.
|
A number of vertices in the graph.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
InvalidContextError: If context is invalid.
|
InvalidContextError: If context is invalid.
|
||||||
UnableToAllocateError: If unable to allocate an iterator or a vertex.
|
UnableToAllocateError: If unable to allocate an iterator or a vertex.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```len(graph.vertices)```
|
```len(graph.vertices)```
|
||||||
"""
|
"""
|
||||||
if not self._len:
|
if not self._len:
|
||||||
@@ -1140,9 +1149,9 @@ class Graph:
|
|||||||
Check if `graph` is in a valid context and may be used.
|
Check if `graph` is in a valid context and may be used.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A `bool` value depends on if the `graph` is in a valid context.
|
A `bool` value depends on if the `graph` is in a valid context.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```graph.is_valid()```
|
```graph.is_valid()```
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -1169,7 +1178,7 @@ class Graph:
|
|||||||
Examples:
|
Examples:
|
||||||
```graph.get_vertex_by_id(vertex_id)```
|
```graph.get_vertex_by_id(vertex_id)```
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
raise InvalidContextError()
|
raise InvalidContextError()
|
||||||
@@ -1207,11 +1216,11 @@ class Graph:
|
|||||||
def is_mutable(self) -> bool:
|
def is_mutable(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if the graph is mutable. Thus it can be used to modify vertices and edges.
|
Check if the graph is mutable. Thus it can be used to modify vertices and edges.
|
||||||
|
|
||||||
Returns:
|
|
||||||
A `bool` value that depends if the graph is mutable or not.
|
|
||||||
|
|
||||||
Examples:
|
Returns:
|
||||||
|
A `bool` value that depends if the graph is mutable or not.
|
||||||
|
|
||||||
|
Examples:
|
||||||
```graph.is_mutable()```
|
```graph.is_mutable()```
|
||||||
"""
|
"""
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
@@ -1222,14 +1231,14 @@ class Graph:
|
|||||||
"""
|
"""
|
||||||
Create an empty vertex.
|
Create an empty vertex.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Created `Vertex`.
|
Created `Vertex`.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ImmutableObjectError: If `graph` is immutable.
|
ImmutableObjectError: If `graph` is immutable.
|
||||||
UnableToAllocateError: If unable to allocate a vertex.
|
UnableToAllocateError: If unable to allocate a vertex.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
Creating an empty vertex.
|
Creating an empty vertex.
|
||||||
```vertex = graph.create_vertex()```
|
```vertex = graph.create_vertex()```
|
||||||
|
|
||||||
@@ -1249,7 +1258,7 @@ class Graph:
|
|||||||
LogicErrorError: If `vertex` has edges.
|
LogicErrorError: If `vertex` has edges.
|
||||||
SerializationError: If `vertex` has been modified by
|
SerializationError: If `vertex` has been modified by
|
||||||
another transaction.
|
another transaction.
|
||||||
Examples:
|
Examples:
|
||||||
```graph.delete_vertex(vertex)```
|
```graph.delete_vertex(vertex)```
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -1260,36 +1269,39 @@ class Graph:
|
|||||||
def detach_delete_vertex(self, vertex: Vertex) -> None:
|
def detach_delete_vertex(self, vertex: Vertex) -> None:
|
||||||
"""
|
"""
|
||||||
Delete a vertex and all of its edges.
|
Delete a vertex and all of its edges.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
vertex: `Vertex` to be deleted with all of its edges
|
vertex: `Vertex` to be deleted with all of its edges
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ImmutableObjectError: If `graph` is immutable.
|
ImmutableObjectError: If `graph` is immutable.
|
||||||
SerializationError: If `vertex` has been modified by another transaction.
|
SerializationError: If `vertex` has been modified by another transaction.
|
||||||
Examples:
|
Examples:
|
||||||
```graph.detach_delete_vertex(vertex)```
|
```graph.detach_delete_vertex(vertex)```
|
||||||
"""
|
"""
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
raise InvalidContextError()
|
raise InvalidContextError()
|
||||||
self._graph.detach_delete_vertex(vertex._vertex)
|
self._graph.detach_delete_vertex(vertex._vertex)
|
||||||
|
|
||||||
def create_edge(self, from_vertex: Vertex, to_vertex: Vertex, edge_type: EdgeType) -> None:
|
def create_edge(self, from_vertex: Vertex, to_vertex: Vertex, edge_type: EdgeType) -> Edge:
|
||||||
"""
|
"""
|
||||||
Create an edge.
|
Create an edge.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
from_vertex: `Vertex` from where edge is directed.
|
from_vertex: `Vertex` from where edge is directed.
|
||||||
to_vertex: `Vertex' to where edge is directed.
|
to_vertex: `Vertex' to where edge is directed.
|
||||||
edge_type: `EdgeType` defines the type of edge.
|
edge_type: `EdgeType` defines the type of edge.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Created `Edge`.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ImmutableObjectError: If `graph` is immutable.
|
ImmutableObjectError: If `graph` is immutable.
|
||||||
UnableToAllocateError: If unable to allocate an edge.
|
UnableToAllocateError: If unable to allocate an edge.
|
||||||
DeletedObjectError: If `from_vertex` or `to_vertex` has been deleted.
|
DeletedObjectError: If `from_vertex` or `to_vertex` has been deleted.
|
||||||
SerializationError: If `from_vertex` or `to_vertex` has been modified by another transaction.
|
SerializationError: If `from_vertex` or `to_vertex` has been modified by another transaction.
|
||||||
Examples:
|
Examples:
|
||||||
```graph.create_edge(from_vertex, vertex, edge_type)```
|
```edge = graph.create_edge(from_vertex, vertex, edge_type)```
|
||||||
"""
|
"""
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
raise InvalidContextError()
|
raise InvalidContextError()
|
||||||
@@ -1301,8 +1313,8 @@ class Graph:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
edge: `Edge` to be deleted
|
edge: `Edge` to be deleted
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ImmutableObjectError if `graph` is immutable.
|
ImmutableObjectError if `graph` is immutable.
|
||||||
Raise SerializationError if `edge`, its source or destination vertex has been modified by another transaction.
|
Raise SerializationError if `edge`, its source or destination vertex has been modified by another transaction.
|
||||||
"""
|
"""
|
||||||
@@ -1337,15 +1349,15 @@ class ProcCtx:
|
|||||||
@property
|
@property
|
||||||
def graph(self) -> Graph:
|
def graph(self) -> Graph:
|
||||||
"""
|
"""
|
||||||
Access to `Graph` object.
|
Access to `Graph` object.
|
||||||
|
|
||||||
Returns:
|
|
||||||
Graph object.
|
|
||||||
|
|
||||||
Raises:
|
Returns:
|
||||||
|
Graph object.
|
||||||
|
|
||||||
|
Raises:
|
||||||
InvalidContextError: If context is invalid.
|
InvalidContextError: If context is invalid.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```context.graph```
|
```context.graph```
|
||||||
"""
|
"""
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
@@ -1969,6 +1981,8 @@ def _wrap_exceptions():
|
|||||||
raise ValueConversionError(e)
|
raise ValueConversionError(e)
|
||||||
except _mgp.SerializationError as e:
|
except _mgp.SerializationError as e:
|
||||||
raise SerializationError(e)
|
raise SerializationError(e)
|
||||||
|
except _mgp.AuthorizationError as e:
|
||||||
|
raise AuthorizationError(e)
|
||||||
|
|
||||||
return wrapped_func
|
return wrapped_func
|
||||||
|
|
||||||
@@ -2004,4 +2018,82 @@ def _wrap_exceptions():
|
|||||||
setattr(module, name, wrap_function(obj))
|
setattr(module, name, wrap_function(obj))
|
||||||
|
|
||||||
|
|
||||||
|
class Logger:
|
||||||
|
"""Represents a Logger through which it is possible
|
||||||
|
to send logs via API to the graph database.
|
||||||
|
|
||||||
|
The best way to use this Logger is to have one per query module."""
|
||||||
|
|
||||||
|
__slots__ = ("_logger",)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._logger = _mgp._LOGGER
|
||||||
|
|
||||||
|
def info(self, out: str) -> None:
|
||||||
|
"""
|
||||||
|
Log message on INFO level..
|
||||||
|
Args:
|
||||||
|
out: String message to be logged.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
```logger.info("Hello from query module.")```
|
||||||
|
"""
|
||||||
|
self._logger.info(out)
|
||||||
|
|
||||||
|
def warning(self, out: str) -> None:
|
||||||
|
"""
|
||||||
|
Log message on WARNING level..
|
||||||
|
Args:
|
||||||
|
out: String message to be logged.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
```logger.warning("Hello from query module.")```
|
||||||
|
"""
|
||||||
|
self._logger.warning(out)
|
||||||
|
|
||||||
|
def critical(self, out: str) -> None:
|
||||||
|
"""
|
||||||
|
Log message on CRITICAL level..
|
||||||
|
Args:
|
||||||
|
out: String message to be logged.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
```logger.critical("Hello from query module.")```
|
||||||
|
"""
|
||||||
|
self._logger.critical(out)
|
||||||
|
|
||||||
|
def error(self, out: str) -> None:
|
||||||
|
"""
|
||||||
|
Log message on ERROR level..
|
||||||
|
Args:
|
||||||
|
out: String message to be logged.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
```logger.error("Hello from query module.")```
|
||||||
|
"""
|
||||||
|
self._logger.error(out)
|
||||||
|
|
||||||
|
def trace(self, out: str) -> None:
|
||||||
|
"""
|
||||||
|
Log message on TRACE level..
|
||||||
|
Args:
|
||||||
|
out: String message to be logged.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
```logger.trace("Hello from query module.")```
|
||||||
|
"""
|
||||||
|
self._logger.trace(out)
|
||||||
|
|
||||||
|
def debug(self, out: str) -> None:
|
||||||
|
"""
|
||||||
|
Log message on DEBUG level..
|
||||||
|
Args:
|
||||||
|
out: String message to be logged.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
```logger.debug("Hello from query module.")```
|
||||||
|
"""
|
||||||
|
self._logger.debug(out)
|
||||||
|
|
||||||
|
|
||||||
_wrap_exceptions()
|
_wrap_exceptions()
|
||||||
|
|||||||
1655
include/mgp_mock.py
Normal file
1655
include/mgp_mock.py
Normal file
File diff suppressed because it is too large
Load Diff
14
init
14
init
@@ -5,6 +5,9 @@ cd "$DIR"
|
|||||||
|
|
||||||
source "$DIR/environment/util.sh"
|
source "$DIR/environment/util.sh"
|
||||||
|
|
||||||
|
DISTRO=$(operating_system)
|
||||||
|
ARCHITECTURE=$(architecture)
|
||||||
|
|
||||||
function print_help () {
|
function print_help () {
|
||||||
echo "Usage: $0 [OPTION]"
|
echo "Usage: $0 [OPTION]"
|
||||||
echo -e "Check for missing packages and setup the project.\n"
|
echo -e "Check for missing packages and setup the project.\n"
|
||||||
@@ -64,8 +67,6 @@ else
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
DISTRO=$(operating_system)
|
|
||||||
ARCHITECTURE=$(architecture)
|
|
||||||
if [ "${ARCHITECTURE}" = "arm64" ] || [ "${ARCHITECTURE}" = "aarch64" ]; then
|
if [ "${ARCHITECTURE}" = "arm64" ] || [ "${ARCHITECTURE}" = "aarch64" ]; then
|
||||||
OS_SCRIPT=$DIR/environment/os/$DISTRO-arm.sh
|
OS_SCRIPT=$DIR/environment/os/$DISTRO-arm.sh
|
||||||
else
|
else
|
||||||
@@ -111,6 +112,12 @@ if [[ "$setup_libs" == "true" ]]; then
|
|||||||
cd ..
|
cd ..
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Fix for centos 7 during release
|
||||||
|
if [ "${DISTRO}" = "centos-7" ] || [ "${DISTRO}" = "debian-11" ]; then
|
||||||
|
python3 -m pip uninstall -y virtualenv
|
||||||
|
python3 -m pip install virtualenv
|
||||||
|
fi
|
||||||
|
|
||||||
# setup gql_behave dependencies
|
# setup gql_behave dependencies
|
||||||
setup_virtualenv tests/gql_behave
|
setup_virtualenv tests/gql_behave
|
||||||
|
|
||||||
@@ -142,10 +149,9 @@ python3 -m pre_commit install
|
|||||||
|
|
||||||
# Install py format tools
|
# Install py format tools
|
||||||
echo "Install black formatter"
|
echo "Install black formatter"
|
||||||
python3 -m pip install black==22.10.*
|
python3 -m pip install black==22.8.*
|
||||||
echo "Install isort"
|
echo "Install isort"
|
||||||
python3 -m pip install isort==5.10.*
|
python3 -m pip install isort==5.10.*
|
||||||
|
|
||||||
|
|
||||||
# Link `include/mgp.py` with `release/mgp/mgp.py`
|
# Link `include/mgp.py` with `release/mgp/mgp.py`
|
||||||
ln -v -f include/mgp.py release/mgp/mgp.py
|
ln -v -f include/mgp.py release/mgp/mgp.py
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ declare -A primary_urls=(
|
|||||||
["pymgclient"]="http://$local_cache_host/git/pymgclient.git"
|
["pymgclient"]="http://$local_cache_host/git/pymgclient.git"
|
||||||
["mgconsole"]="http://$local_cache_host/git/mgconsole.git"
|
["mgconsole"]="http://$local_cache_host/git/mgconsole.git"
|
||||||
["spdlog"]="http://$local_cache_host/git/spdlog"
|
["spdlog"]="http://$local_cache_host/git/spdlog"
|
||||||
["nlohmann"]="http://$local_cache_host/file/nlohmann/json/9d69186291aca4f0137b69c1dee313b391ff564c/single_include/nlohmann/json.hpp"
|
["nlohmann"]="http://$local_cache_host/file/nlohmann/json/4f8fba14066156b73f1189a2b8bd568bde5284c5/single_include/nlohmann/json.hpp"
|
||||||
["neo4j"]="http://$local_cache_host/file/neo4j-community-3.2.3-unix.tar.gz"
|
["neo4j"]="http://$local_cache_host/file/neo4j-community-3.2.3-unix.tar.gz"
|
||||||
["librdkafka"]="http://$local_cache_host/git/librdkafka.git"
|
["librdkafka"]="http://$local_cache_host/git/librdkafka.git"
|
||||||
["protobuf"]="http://$local_cache_host/git/protobuf.git"
|
["protobuf"]="http://$local_cache_host/git/protobuf.git"
|
||||||
@@ -141,7 +141,7 @@ declare -A secondary_urls=(
|
|||||||
["pymgclient"]="https://github.com/memgraph/pymgclient.git"
|
["pymgclient"]="https://github.com/memgraph/pymgclient.git"
|
||||||
["mgconsole"]="http://github.com/memgraph/mgconsole.git"
|
["mgconsole"]="http://github.com/memgraph/mgconsole.git"
|
||||||
["spdlog"]="https://github.com/gabime/spdlog"
|
["spdlog"]="https://github.com/gabime/spdlog"
|
||||||
["nlohmann"]="https://raw.githubusercontent.com/nlohmann/json/9d69186291aca4f0137b69c1dee313b391ff564c/single_include/nlohmann/json.hpp"
|
["nlohmann"]="https://raw.githubusercontent.com/nlohmann/json/4f8fba14066156b73f1189a2b8bd568bde5284c5/single_include/nlohmann/json.hpp"
|
||||||
["neo4j"]="https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/neo4j-community-3.2.3-unix.tar.gz"
|
["neo4j"]="https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/neo4j-community-3.2.3-unix.tar.gz"
|
||||||
["librdkafka"]="https://github.com/edenhill/librdkafka.git"
|
["librdkafka"]="https://github.com/edenhill/librdkafka.git"
|
||||||
["protobuf"]="https://github.com/protocolbuffers/protobuf.git"
|
["protobuf"]="https://github.com/protocolbuffers/protobuf.git"
|
||||||
@@ -171,7 +171,7 @@ benchmark_tag="v1.6.0"
|
|||||||
repo_clone_try_double "${primary_urls[gbenchmark]}" "${secondary_urls[gbenchmark]}" "benchmark" "$benchmark_tag" true
|
repo_clone_try_double "${primary_urls[gbenchmark]}" "${secondary_urls[gbenchmark]}" "benchmark" "$benchmark_tag" true
|
||||||
|
|
||||||
# google test
|
# google test
|
||||||
googletest_tag="release-1.12.1"
|
googletest_tag="release-1.8.0"
|
||||||
repo_clone_try_double "${primary_urls[gtest]}" "${secondary_urls[gtest]}" "googletest" "$googletest_tag" true
|
repo_clone_try_double "${primary_urls[gtest]}" "${secondary_urls[gtest]}" "googletest" "$googletest_tag" true
|
||||||
|
|
||||||
# libbcrypt
|
# libbcrypt
|
||||||
@@ -208,7 +208,7 @@ pymgclient_tag="4f85c179e56302d46a1e3e2cf43509db65f062b3" # (2021-01-15)
|
|||||||
repo_clone_try_double "${primary_urls[pymgclient]}" "${secondary_urls[pymgclient]}" "pymgclient" "$pymgclient_tag"
|
repo_clone_try_double "${primary_urls[pymgclient]}" "${secondary_urls[pymgclient]}" "pymgclient" "$pymgclient_tag"
|
||||||
|
|
||||||
# mgconsole
|
# 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
|
repo_clone_try_double "${primary_urls[mgconsole]}" "${secondary_urls[mgconsole]}" "mgconsole" "$mgconsole_tag" true
|
||||||
|
|
||||||
spdlog_tag="v1.9.2" # (2021-08-12)
|
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
|
3. using the Licensed Work to create a work or solution
|
||||||
which competes (or might reasonably be expected to
|
which competes (or might reasonably be expected to
|
||||||
compete) with the Licensed Work.
|
compete) with the Licensed Work.
|
||||||
CHANGE DATE: 2026-27-04
|
CHANGE DATE: 2027-08-03
|
||||||
CHANGE LICENSE: Apache License, Version 2.0
|
CHANGE LICENSE: Apache License, Version 2.0
|
||||||
|
|
||||||
For information about alternative licensing arrangements, please visit: https://memgraph.com/legal.
|
For information about alternative licensing arrangements, please visit: https://memgraph.com/legal.
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ MEMGRAPH
|
|||||||
ENTERPRISE LICENCE AGREEMENT
|
ENTERPRISE LICENCE AGREEMENT
|
||||||
|
|
||||||
|
|
||||||
Memgraph Limited is registered in England under registration 10195084 and has its registered office at Suite 4,
|
Memgraph Limited is registered in England under registration 10195084 and has its registered office at 90a High Street,
|
||||||
Ironstone House, Ironstone Way, Brixworth, Northampton, NN6 9UD (“Memgraph”).
|
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
|
Memgraph agrees to license and/or grant you (the “Customer”) access to the Software ( as defined below) and provide
|
||||||
|
|||||||
@@ -8,29 +8,40 @@ disallow_in_source_build()
|
|||||||
|
|
||||||
# Everything that is installed here, should be under the "query_modules" component.
|
# Everything that is installed here, should be under the "query_modules" component.
|
||||||
set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME "query_modules")
|
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)
|
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
|
DESTINATION lib/memgraph/query_modules
|
||||||
RENAME example.so)
|
RENAME example_c.so)
|
||||||
# Also install the source of the example, so user can read it.
|
# Also install the source of the example, so user can read it.
|
||||||
install(FILES example.c DESTINATION lib/memgraph/query_modules/src)
|
install(FILES example.c DESTINATION lib/memgraph/query_modules/src)
|
||||||
|
|
||||||
# Install the Python example
|
add_library(example_cpp SHARED example.cpp)
|
||||||
install(FILES example.py DESTINATION lib/memgraph/query_modules RENAME py_example.py)
|
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 graph_analyzer.py DESTINATION lib/memgraph/query_modules)
|
||||||
install(FILES mgp_networkx.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)
|
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; }
|
||||||
@@ -67,7 +67,7 @@ It aims to deliver developers the speed, simplicity and scale required to build
|
|||||||
the next generation of applications driver by real-time connected data.")
|
the next generation of applications driver by real-time connected data.")
|
||||||
# Add `openssl` package to dependencies list. Used to generate SSL certificates.
|
# Add `openssl` package to dependencies list. Used to generate SSL certificates.
|
||||||
# We also depend on `python3` because we embed it in Memgraph.
|
# We also depend on `python3` because we embed it in Memgraph.
|
||||||
set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.0.0, curl >= 7.29.0, python3 >= 3.5.0, libstdc >= 6, logrotate")
|
set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.0.0, curl >= 7.29.0, python3 >= 3.5.0, libstdc++ >= 6, logrotate")
|
||||||
|
|
||||||
# All variables must be set before including.
|
# All variables must be set before including.
|
||||||
include(CPack)
|
include(CPack)
|
||||||
|
|||||||
1
release/mgp/.gitignore
vendored
1
release/mgp/.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
.venv
|
.venv
|
||||||
dist
|
dist
|
||||||
mgp.py
|
mgp.py
|
||||||
|
poetry.lock
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
# mgp
|
# mgp
|
||||||
|
|
||||||
PyPi package used for type hinting when creating MAGE modules. The get started
|
PyPi package used for type hinting when creating query modules. Repository of already available query modules is called [MAGE](https://github.com/memgraph/mage).
|
||||||
using MAGE repository checkout the repository here: 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
|
||||||
|
|||||||
@@ -173,6 +173,10 @@ class SerializationError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AuthorizationError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def type_nullable(elem: Any):
|
def type_nullable(elem: Any):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -253,3 +257,11 @@ class _MODULE:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def add_function(wrapper):
|
def add_function(wrapper):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SOURCE_TYPE_KAFKA:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SOURCE_TYPE_PULSAR:
|
||||||
|
pass
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "mgp"
|
name = "mgp"
|
||||||
version = "1.0.0"
|
version = "1.1.1"
|
||||||
description = "Memgraph's module for developing MAGE modules. Used only for type hinting!"
|
description = "Memgraph's module for developing MAGE modules. Used only for type hinting!"
|
||||||
authors = [
|
authors = [
|
||||||
"MasterMedo <mislav.vuletic@gmail.com>",
|
|
||||||
"jbajic <jure.bajic@memgraph.io>",
|
|
||||||
"katarinasupe <katarina.supe@memgraph.io>",
|
"katarinasupe <katarina.supe@memgraph.io>",
|
||||||
|
"jbajic <jure.bajic@memgraph.io>",
|
||||||
"antejavor <ante.javor@memgraph.io>",
|
"antejavor <ante.javor@memgraph.io>",
|
||||||
"antaljanosbenjamin <benjamin.antal@memgraph.io>",
|
"antaljanosbenjamin <benjamin.antal@memgraph.io>",
|
||||||
|
"MasterMedo <mislav.vuletic@gmail.com>",
|
||||||
]
|
]
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
11
release/package/arm-builders.yml
Normal file
11
release/package/arm-builders.yml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
debian-11-arm:
|
||||||
|
build:
|
||||||
|
context: debian-11-arm
|
||||||
|
container_name: "mgbuild_debian-11-arm"
|
||||||
|
ubuntu-2204-arm:
|
||||||
|
build:
|
||||||
|
context: ubuntu-22.04-arm
|
||||||
|
container_name: "mgbuild_ubuntu-22.04-arm"
|
||||||
@@ -28,3 +28,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ubuntu-22.04
|
context: ubuntu-22.04
|
||||||
container_name: "mgbuild_ubuntu-22.04"
|
container_name: "mgbuild_ubuntu-22.04"
|
||||||
|
mgbuild_fedora-36:
|
||||||
|
build:
|
||||||
|
context: fedora-36
|
||||||
|
container_name: "mgbuild_fedora-36"
|
||||||
|
|||||||
15
release/package/fedora-36/Dockerfile
Normal file
15
release/package/fedora-36/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
FROM fedora:36
|
||||||
|
|
||||||
|
ARG TOOLCHAIN_VERSION
|
||||||
|
|
||||||
|
# Stops tzdata interactive configuration.
|
||||||
|
RUN yum -y update \
|
||||||
|
&& yum install -y wget git
|
||||||
|
# Do NOT be smart here and clean the cache because the container is used in the
|
||||||
|
# stateful context.
|
||||||
|
|
||||||
|
RUN wget -q https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/${TOOLCHAIN_VERSION}/${TOOLCHAIN_VERSION}-binaries-fedora-36-x86_64.tar.gz \
|
||||||
|
-O ${TOOLCHAIN_VERSION}-binaries-fedora-36-x86_64.tar.gz \
|
||||||
|
&& tar xzvf ${TOOLCHAIN_VERSION}-binaries-fedora-36-x86_64.tar.gz -C /opt
|
||||||
|
|
||||||
|
ENTRYPOINT ["sleep", "infinity"]
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
SUPPORTED_OS=(centos-7 centos-9 debian-10 debian-11 ubuntu-18.04 ubuntu-20.04 ubuntu-22.04 debian-11-arm)
|
SUPPORTED_OS=(centos-7 centos-9 debian-10 debian-11 ubuntu-18.04 ubuntu-20.04 ubuntu-22.04 debian-11-arm fedora-36 ubuntu-22.04-arm)
|
||||||
PROJECT_ROOT="$SCRIPT_DIR/../.."
|
PROJECT_ROOT="$SCRIPT_DIR/../.."
|
||||||
TOOLCHAIN_VERSION="toolchain-v4"
|
TOOLCHAIN_VERSION="toolchain-v4"
|
||||||
ACTIVATE_TOOLCHAIN="source /opt/${TOOLCHAIN_VERSION}/activate"
|
ACTIVATE_TOOLCHAIN="source /opt/${TOOLCHAIN_VERSION}/activate"
|
||||||
@@ -23,9 +23,9 @@ make_package () {
|
|||||||
echo "Building Memgraph for $os on $build_container..."
|
echo "Building Memgraph for $os on $build_container..."
|
||||||
|
|
||||||
package_command=""
|
package_command=""
|
||||||
if [[ "$os" =~ ^"centos".* ]]; then
|
if [[ "$os" =~ ^"centos".* ]] || [[ "$os" =~ ^"fedora".* ]]; then
|
||||||
docker exec "$build_container" bash -c "yum -y update"
|
docker exec "$build_container" bash -c "yum -y update"
|
||||||
package_command=" cpack -G RPM --config ../CPackConfig.cmake && rpmlint memgraph*.rpm "
|
package_command=" cpack -G RPM --config ../CPackConfig.cmake && rpmlint --file='../../release/rpm/rpmlintrc' memgraph*.rpm "
|
||||||
fi
|
fi
|
||||||
if [[ "$os" =~ ^"debian".* ]]; then
|
if [[ "$os" =~ ^"debian".* ]]; then
|
||||||
docker exec "$build_container" bash -c "apt update"
|
docker exec "$build_container" bash -c "apt update"
|
||||||
@@ -72,9 +72,11 @@ make_package () {
|
|||||||
docker exec "$build_container" bash -c "/memgraph/environment/os/$os.sh install MEMGRAPH_BUILD_DEPS"
|
docker exec "$build_container" bash -c "/memgraph/environment/os/$os.sh install MEMGRAPH_BUILD_DEPS"
|
||||||
|
|
||||||
echo "Building targeted package..."
|
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 /memgraph && $ACTIVATE_TOOLCHAIN && ./init"
|
||||||
docker exec "$build_container" bash -c "cd $container_build_dir && rm -rf ./*"
|
docker exec "$build_container" bash -c "cd $container_build_dir && rm -rf ./*"
|
||||||
if [[ "$os" == "debian-11-arm" ]]; then
|
if [[ "$os" =~ "-arm" ]]; then
|
||||||
docker exec "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && cmake -DCMAKE_BUILD_TYPE=release -DMG_ARCH="ARM64" $telemetry_id_override_flag .."
|
docker exec "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && cmake -DCMAKE_BUILD_TYPE=release -DMG_ARCH="ARM64" $telemetry_id_override_flag .."
|
||||||
else
|
else
|
||||||
docker exec "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && cmake -DCMAKE_BUILD_TYPE=release $telemetry_id_override_flag .."
|
docker exec "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && cmake -DCMAKE_BUILD_TYPE=release $telemetry_id_override_flag .."
|
||||||
|
|||||||
17
release/package/ubuntu-22.04-arm/Dockerfile
Normal file
17
release/package/ubuntu-22.04-arm/Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
|
ARG TOOLCHAIN_VERSION
|
||||||
|
|
||||||
|
# Stops tzdata interactive configuration.
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
RUN apt update && apt install -y \
|
||||||
|
ca-certificates wget git
|
||||||
|
# Do NOT be smart here and clean the cache because the container is used in the
|
||||||
|
# stateful context.
|
||||||
|
|
||||||
|
RUN wget -q https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/${TOOLCHAIN_VERSION}/${TOOLCHAIN_VERSION}-binaries-ubuntu-22.04-arm64.tar.gz \
|
||||||
|
-O ${TOOLCHAIN_VERSION}-binaries-ubuntu-22.04-arm64.tar.gz \
|
||||||
|
&& tar xzvf ${TOOLCHAIN_VERSION}-binaries-ubuntu-22.04-arm64.tar.gz -C /opt
|
||||||
|
|
||||||
|
ENTRYPOINT ["sleep", "infinity"]
|
||||||
@@ -30,7 +30,7 @@ BuildRequires: systemd
|
|||||||
# This is needed to prevent Python compilation errors when building the RPM
|
# This is needed to prevent Python compilation errors when building the RPM
|
||||||
# package
|
# package
|
||||||
# https://github.com/scylladb/scylla/issues/2235
|
# https://github.com/scylladb/scylla/issues/2235
|
||||||
%if 0%{?rhel} < 8
|
%if 0%{?rhel} && 0%{?rhel} < 8
|
||||||
%global __os_install_post \
|
%global __os_install_post \
|
||||||
/usr/lib/rpm/redhat/brp-compress \
|
/usr/lib/rpm/redhat/brp-compress \
|
||||||
%{!?__debug_package:\
|
%{!?__debug_package:\
|
||||||
@@ -40,7 +40,9 @@ BuildRequires: systemd
|
|||||||
/usr/lib/rpm/redhat/brp-strip-static-archive %{__strip} \
|
/usr/lib/rpm/redhat/brp-strip-static-archive %{__strip} \
|
||||||
%{!?__jar_repack:/usr/lib/rpm/redhat/brp-java-repack-jars} \
|
%{!?__jar_repack:/usr/lib/rpm/redhat/brp-java-repack-jars} \
|
||||||
%{nil}
|
%{nil}
|
||||||
%else
|
%endif
|
||||||
|
|
||||||
|
%if 0%{?fedora} && 0%{?fedora} < 35
|
||||||
%global __os_install_post \
|
%global __os_install_post \
|
||||||
/usr/lib/rpm/brp-compress \
|
/usr/lib/rpm/brp-compress \
|
||||||
%{!?__debug_package:\
|
%{!?__debug_package:\
|
||||||
|
|||||||
4
release/rpm/rpmlintrc
Normal file
4
release/rpm/rpmlintrc
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# from https://github.com/google/earthenterprise/blob/master/earth_enterprise/rpmlintrc
|
||||||
|
|
||||||
|
# We are not packaging log dir
|
||||||
|
addFilter("E: logrotate-log-dir-not-packaged")
|
||||||
@@ -5,26 +5,21 @@ add_subdirectory(lisp)
|
|||||||
add_subdirectory(utils)
|
add_subdirectory(utils)
|
||||||
add_subdirectory(requests)
|
add_subdirectory(requests)
|
||||||
add_subdirectory(io)
|
add_subdirectory(io)
|
||||||
add_subdirectory(io/simulator)
|
|
||||||
add_subdirectory(kvstore)
|
add_subdirectory(kvstore)
|
||||||
add_subdirectory(telemetry)
|
add_subdirectory(telemetry)
|
||||||
add_subdirectory(communication)
|
add_subdirectory(communication)
|
||||||
add_subdirectory(memory)
|
add_subdirectory(memory)
|
||||||
add_subdirectory(storage/v2)
|
add_subdirectory(storage/v2)
|
||||||
add_subdirectory(storage/v3)
|
|
||||||
add_subdirectory(integrations)
|
add_subdirectory(integrations)
|
||||||
add_subdirectory(query)
|
add_subdirectory(query)
|
||||||
add_subdirectory(query/v2)
|
add_subdirectory(glue)
|
||||||
add_subdirectory(slk)
|
add_subdirectory(slk)
|
||||||
add_subdirectory(rpc)
|
add_subdirectory(rpc)
|
||||||
|
add_subdirectory(license)
|
||||||
add_subdirectory(auth)
|
add_subdirectory(auth)
|
||||||
add_subdirectory(parser)
|
|
||||||
add_subdirectory(expr)
|
|
||||||
add_subdirectory(coordinator)
|
|
||||||
add_subdirectory(functions)
|
|
||||||
|
|
||||||
if (MG_ENTERPRISE)
|
if(MG_ENTERPRISE)
|
||||||
add_subdirectory(audit)
|
add_subdirectory(audit)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
|
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
|
||||||
@@ -38,80 +33,98 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
|||||||
# Memgraph Single Node v2 Executable
|
# Memgraph Single Node v2 Executable
|
||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
set(mg_single_node_v2_sources
|
set(mg_single_node_v2_sources
|
||||||
glue/v2/communication.cpp
|
|
||||||
memgraph.cpp
|
memgraph.cpp
|
||||||
glue/v2/auth.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set(mg_single_node_v2_libs stdc++fs Threads::Threads
|
set(mg_single_node_v2_libs stdc++fs Threads::Threads
|
||||||
telemetry_lib mg-query-v2 mg-communication mg-memory mg-utils mg-auth mg-license mg-settings mg-io mg-coordinator)
|
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
|
if(MG_ENTERPRISE)
|
||||||
set(mg_single_node_v2_libs ${mg_single_node_v2_libs} mg-audit)
|
# These are enterprise subsystems
|
||||||
|
set(mg_single_node_v2_libs ${mg_single_node_v2_libs} mg-audit)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# memgraph main executable
|
# memgraph main executable
|
||||||
add_executable(memgraph ${mg_single_node_v2_sources})
|
add_executable(memgraph ${mg_single_node_v2_sources})
|
||||||
target_include_directories(memgraph PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
target_include_directories(memgraph PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||||
target_link_libraries(memgraph ${mg_single_node_v2_libs})
|
target_link_libraries(memgraph ${mg_single_node_v2_libs})
|
||||||
|
|
||||||
# NOTE: `include/mg_procedure.syms` describes a pattern match for symbols which
|
# NOTE: `include/mg_procedure.syms` describes a pattern match for symbols which
|
||||||
# should be dynamically exported, so that `dlopen` can correctly link the
|
# should be dynamically exported, so that `dlopen` can correctly link the
|
||||||
# symbols in custom procedure module libraries.
|
# 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")
|
||||||
set_target_properties(memgraph PROPERTIES
|
set_target_properties(memgraph PROPERTIES
|
||||||
# Set the executable output name to include version information.
|
|
||||||
OUTPUT_NAME "memgraph-${MEMGRAPH_VERSION}_${CMAKE_BUILD_TYPE}"
|
# Set the executable output name to include version information.
|
||||||
# Output the executable in main binary dir.
|
OUTPUT_NAME "memgraph-${MEMGRAPH_VERSION}_${CMAKE_BUILD_TYPE}"
|
||||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
|
||||||
|
# Output the executable in main binary dir.
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
# Create symlink to the built executable.
|
# Create symlink to the built executable.
|
||||||
add_custom_command(TARGET memgraph POST_BUILD
|
add_custom_command(TARGET memgraph POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND} -E create_symlink $<TARGET_FILE:memgraph> ${CMAKE_BINARY_DIR}/memgraph
|
COMMAND ${CMAKE_COMMAND} -E create_symlink $<TARGET_FILE:memgraph> ${CMAKE_BINARY_DIR}/memgraph
|
||||||
BYPRODUCTS ${CMAKE_BINARY_DIR}/memgraph
|
BYPRODUCTS ${CMAKE_BINARY_DIR}/memgraph
|
||||||
COMMENT "Creating symlink to memgraph executable")
|
COMMENT "Creating symlink to memgraph executable")
|
||||||
|
|
||||||
# Emulate the installed python_support, by creating a symlink
|
# Emulate the installed python_support, by creating a symlink
|
||||||
add_custom_command(TARGET memgraph POST_BUILD
|
add_custom_command(TARGET memgraph POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_SOURCE_DIR}/include ${CMAKE_BINARY_DIR}/python_support
|
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_SOURCE_DIR}/include ${CMAKE_BINARY_DIR}/python_support
|
||||||
BYPRODUCTS ${CMAKE_BINARY_DIR}/python_support
|
BYPRODUCTS ${CMAKE_BINARY_DIR}/python_support
|
||||||
COMMENT "Creating symlink for python_support")
|
COMMENT "Creating symlink for python_support")
|
||||||
|
|
||||||
# Strip the executable in release build.
|
# Strip the executable in release build.
|
||||||
if (lower_build_type STREQUAL "release")
|
if(lower_build_type STREQUAL "release")
|
||||||
add_custom_command(TARGET memgraph POST_BUILD
|
add_custom_command(TARGET memgraph POST_BUILD
|
||||||
COMMAND strip -s $<TARGET_FILE:memgraph>
|
COMMAND strip -s $<TARGET_FILE:memgraph>
|
||||||
COMMENT "Stripping symbols and sections from memgraph")
|
COMMENT "Stripping symbols and sections from memgraph")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Generate the configuration file.
|
# Generate the configuration file.
|
||||||
add_custom_command(TARGET memgraph POST_BUILD
|
add_custom_command(TARGET memgraph POST_BUILD
|
||||||
COMMAND ${CMAKE_SOURCE_DIR}/config/generate.py
|
COMMAND ${CMAKE_SOURCE_DIR}/config/generate.py
|
||||||
${CMAKE_BINARY_DIR}/memgraph
|
${CMAKE_BINARY_DIR}/memgraph
|
||||||
${CMAKE_BINARY_DIR}/config/memgraph.conf
|
${CMAKE_BINARY_DIR}/config/memgraph.conf
|
||||||
DEPENDS ${CMAKE_SOURCE_DIR}/config/generate.py
|
DEPENDS ${CMAKE_SOURCE_DIR}/config/generate.py
|
||||||
${CMAKE_SOURCE_DIR}/config/flags.yaml
|
${CMAKE_SOURCE_DIR}/config/flags.yaml
|
||||||
BYPRODUCTS ${CMAKE_BINARY_DIR}/config/memgraph.conf
|
BYPRODUCTS ${CMAKE_BINARY_DIR}/config/memgraph.conf
|
||||||
COMMENT "Generating memgraph configuration file")
|
COMMENT "Generating memgraph configuration file")
|
||||||
|
|
||||||
# Everything here is under "memgraph" install component.
|
# Everything here is under "memgraph" install component.
|
||||||
set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME "memgraph")
|
set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME "memgraph")
|
||||||
|
|
||||||
# TODO: Default directory permissions to 755
|
# TODO: Default directory permissions to 755
|
||||||
# NOTE: This is added in CMake 3.11, so enable it then
|
# NOTE: This is added in CMake 3.11, so enable it then
|
||||||
#set(CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS
|
# set(CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS
|
||||||
# OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ WORLD_READ)
|
# OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ WORLD_READ)
|
||||||
|
|
||||||
# Install and rename executable to just 'memgraph' Since we have to rename,
|
# Install and rename executable to just 'memgraph' Since we have to rename,
|
||||||
# we cannot use the recommended `install(TARGETS ...)`.
|
# we cannot use the recommended `install(TARGETS ...)`.
|
||||||
install(PROGRAMS $<TARGET_FILE:memgraph>
|
install(PROGRAMS $<TARGET_FILE:memgraph>
|
||||||
DESTINATION lib/memgraph RENAME memgraph)
|
DESTINATION lib/memgraph RENAME memgraph)
|
||||||
|
|
||||||
# Install Python source for supporting our embedded Python.
|
# Install Python source for supporting our embedded Python.
|
||||||
install(FILES ${CMAKE_SOURCE_DIR}/include/mgp.py
|
install(FILES ${CMAKE_SOURCE_DIR}/include/mgp.py
|
||||||
DESTINATION lib/memgraph/python_support)
|
DESTINATION lib/memgraph/python_support)
|
||||||
# Install the include file for writing custom procedures.
|
install(FILES ${CMAKE_SOURCE_DIR}/include/mgp_mock.py
|
||||||
|
DESTINATION lib/memgraph/python_support)
|
||||||
|
install(FILES ${CMAKE_SOURCE_DIR}/include/_mgp_mock.py
|
||||||
|
DESTINATION lib/memgraph/python_support)
|
||||||
|
|
||||||
|
# Install the includes file for writing custom procedures in C and C++>
|
||||||
install(FILES ${CMAKE_SOURCE_DIR}/include/mg_procedure.h
|
install(FILES ${CMAKE_SOURCE_DIR}/include/mg_procedure.h
|
||||||
DESTINATION include/memgraph)
|
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 the config file (must use absolute path).
|
||||||
install(FILES ${CMAKE_BINARY_DIR}/config/memgraph.conf
|
install(FILES ${CMAKE_BINARY_DIR}/config/memgraph.conf
|
||||||
DESTINATION /etc/memgraph RENAME memgraph.conf)
|
DESTINATION /etc/memgraph RENAME memgraph.conf)
|
||||||
|
|
||||||
# Install logrotate configuration (must use absolute path).
|
# Install logrotate configuration (must use absolute path).
|
||||||
install(FILES ${CMAKE_SOURCE_DIR}/release/logrotate.conf
|
install(FILES ${CMAKE_SOURCE_DIR}/release/logrotate.conf
|
||||||
DESTINATION /etc/logrotate.d RENAME memgraph)
|
DESTINATION /etc/logrotate.d RENAME memgraph)
|
||||||
@@ -127,3 +140,18 @@ install(CODE "file(MAKE_DIRECTORY \$ENV{DESTDIR}/var/log/memgraph
|
|||||||
# ----------------------------------------------------------------------------
|
# ----------------------------------------------------------------------------
|
||||||
# Memgraph CSV Import Tool Executable
|
# 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")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
install(TARGETS mg_import_csv RUNTIME DESTINATION bin)
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# Memgraph CSV Import Tool Executable
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
#include "auth/exceptions.hpp"
|
#include "auth/exceptions.hpp"
|
||||||
|
#include "license/license.hpp"
|
||||||
#include "utils/flag_validation.hpp"
|
#include "utils/flag_validation.hpp"
|
||||||
#include "utils/license.hpp"
|
|
||||||
#include "utils/logging.hpp"
|
#include "utils/logging.hpp"
|
||||||
#include "utils/message.hpp"
|
#include "utils/message.hpp"
|
||||||
#include "utils/settings.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) {
|
std::optional<User> Auth::Authenticate(const std::string &username, const std::string &password) {
|
||||||
if (module_.IsUsed()) {
|
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()) {
|
if (license_check_result.HasError()) {
|
||||||
spdlog::warn(
|
spdlog::warn(license::LicenseCheckErrorToString(license_check_result.GetError(), "authentication modules"));
|
||||||
utils::license::LicenseCheckErrorToString(license_check_result.GetError(), "authentication modules"));
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,7 +225,7 @@ std::vector<auth::User> Auth::AllUsers() const {
|
|||||||
if (username != utils::ToLowerCase(username)) continue;
|
if (username != utils::ToLowerCase(username)) continue;
|
||||||
auto user = GetUser(username);
|
auto user = GetUser(username);
|
||||||
if (user) {
|
if (user) {
|
||||||
ret.push_back(*user);
|
ret.push_back(std::move(*user));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
@@ -306,7 +305,7 @@ std::vector<auth::User> Auth::AllUsersForRole(const std::string &rolename_orig)
|
|||||||
if (it->second == rolename) {
|
if (it->second == rolename) {
|
||||||
auto user = GetUser(username);
|
auto user = GetUser(username);
|
||||||
if (user) {
|
if (user) {
|
||||||
ret.push_back(*user);
|
ret.push_back(std::move(*user));
|
||||||
} else {
|
} else {
|
||||||
throw AuthException("Couldn't load user '{}'!", username);
|
throw AuthException("Couldn't load user '{}'!", username);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
namespace memgraph::auth {
|
namespace memgraph::auth {
|
||||||
/**
|
/**
|
||||||
* This class serves as the main Authentication/Authorization storage.
|
* 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.
|
* NOTE: The non-const functions in this class aren't thread safe.
|
||||||
* TODO (mferencevic): Disable user/role modification functions when they are
|
* TODO (mferencevic): Disable user/role modification functions when they are
|
||||||
* being managed by the auth module.
|
* being managed by the auth module.
|
||||||
|
|||||||
@@ -8,14 +8,16 @@
|
|||||||
|
|
||||||
#include "auth/models.hpp"
|
#include "auth/models.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
|
||||||
#include <gflags/gflags.h>
|
#include <gflags/gflags.h>
|
||||||
|
|
||||||
#include "auth/crypto.hpp"
|
#include "auth/crypto.hpp"
|
||||||
#include "auth/exceptions.hpp"
|
#include "auth/exceptions.hpp"
|
||||||
|
#include "license/license.hpp"
|
||||||
#include "utils/cast.hpp"
|
#include "utils/cast.hpp"
|
||||||
#include "utils/license.hpp"
|
#include "utils/logging.hpp"
|
||||||
#include "utils/settings.hpp"
|
#include "utils/settings.hpp"
|
||||||
#include "utils/string.hpp"
|
#include "utils/string.hpp"
|
||||||
|
|
||||||
@@ -30,6 +32,7 @@ DEFINE_string(auth_password_strength_regex, default_password_regex.data(),
|
|||||||
|
|
||||||
namespace memgraph::auth {
|
namespace memgraph::auth {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Constant list of all available permissions.
|
// Constant list of all available permissions.
|
||||||
const std::vector<Permission> kPermissionsAll = {
|
const std::vector<Permission> kPermissionsAll = {
|
||||||
Permission::MATCH, Permission::CREATE, Permission::MERGE, Permission::DELETE,
|
Permission::MATCH, Permission::CREATE, Permission::MERGE, Permission::DELETE,
|
||||||
@@ -37,7 +40,7 @@ const std::vector<Permission> kPermissionsAll = {
|
|||||||
Permission::CONSTRAINT, Permission::DUMP, Permission::AUTH, Permission::REPLICATION,
|
Permission::CONSTRAINT, Permission::DUMP, Permission::AUTH, Permission::REPLICATION,
|
||||||
Permission::DURABILITY, Permission::READ_FILE, Permission::FREE_MEMORY, Permission::TRIGGER,
|
Permission::DURABILITY, Permission::READ_FILE, Permission::FREE_MEMORY, Permission::TRIGGER,
|
||||||
Permission::CONFIG, Permission::STREAM, Permission::MODULE_READ, Permission::MODULE_WRITE,
|
Permission::CONFIG, Permission::STREAM, Permission::MODULE_READ, Permission::MODULE_WRITE,
|
||||||
Permission::WEBSOCKET, Permission::SCHEMA};
|
Permission::WEBSOCKET};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
std::string PermissionToString(Permission permission) {
|
std::string PermissionToString(Permission permission) {
|
||||||
@@ -84,8 +87,6 @@ std::string PermissionToString(Permission permission) {
|
|||||||
return "MODULE_WRITE";
|
return "MODULE_WRITE";
|
||||||
case Permission::WEBSOCKET:
|
case Permission::WEBSOCKET:
|
||||||
return "WEBSOCKET";
|
return "WEBSOCKET";
|
||||||
case Permission::SCHEMA:
|
|
||||||
return "SCHEMA";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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) {
|
Permissions::Permissions(uint64_t grants, uint64_t denies) {
|
||||||
// The deny bitmask has higher priority than the grant bitmask.
|
// The deny bitmask has higher priority than the grant bitmask.
|
||||||
denies_ = denies;
|
denies_ = denies;
|
||||||
@@ -173,7 +223,7 @@ Permissions Permissions::Deserialize(const nlohmann::json &data) {
|
|||||||
if (!data["grants"].is_number_unsigned() || !data["denies"].is_number_unsigned()) {
|
if (!data["grants"].is_number_unsigned() || !data["denies"].is_number_unsigned()) {
|
||||||
throw AuthException("Couldn't load permissions data!");
|
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_; }
|
uint64_t Permissions::grants() const { return grants_; }
|
||||||
@@ -185,19 +235,191 @@ bool operator==(const Permissions &first, const Permissions &second) {
|
|||||||
|
|
||||||
bool operator!=(const Permissions &first, const Permissions &second) { return !(first == second); }
|
bool operator!=(const Permissions &first, const Permissions &second) { return !(first == second); }
|
||||||
|
|
||||||
Role::Role(const std::string &rolename) : rolename_(utils::ToLowerCase(rolename)) {}
|
#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 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 FineGrainedAccessPermissions::Revoke(const std::string &permission) {
|
||||||
|
if (permission == kAsterisk) {
|
||||||
|
permissions_.clear();
|
||||||
|
global_permission_ = std::nullopt;
|
||||||
|
} else {
|
||||||
|
permissions_.erase(permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json FineGrainedAccessPermissions::Serialize() const {
|
||||||
|
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
nlohmann::json data = nlohmann::json::object();
|
||||||
|
data["permissions"] = permissions_;
|
||||||
|
data["global_permission"] = global_permission_.has_value() ? global_permission_.value() : -1;
|
||||||
|
return 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;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::unordered_map<std::string, uint64_t> &FineGrainedAccessPermissions::GetPermissions() const {
|
||||||
|
return permissions_;
|
||||||
|
}
|
||||||
|
const std::optional<uint64_t> &FineGrainedAccessPermissions::GetGlobalPermission() const { return global_permission_; };
|
||||||
|
|
||||||
|
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 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)
|
Role::Role(const std::string &rolename, const Permissions &permissions)
|
||||||
: rolename_(utils::ToLowerCase(rolename)), permissions_(permissions) {}
|
: rolename_(utils::ToLowerCase(rolename)), permissions_(permissions) {}
|
||||||
|
#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 std::string &Role::rolename() const { return rolename_; }
|
||||||
const Permissions &Role::permissions() const { return permissions_; }
|
const Permissions &Role::permissions() const { return permissions_; }
|
||||||
Permissions &Role::permissions() { 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_; }
|
||||||
|
|
||||||
|
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 Role::Serialize() const {
|
||||||
nlohmann::json data = nlohmann::json::object();
|
nlohmann::json data = nlohmann::json::object();
|
||||||
data["rolename"] = rolename_;
|
data["rolename"] = rolename_;
|
||||||
data["permissions"] = permissions_.Serialize();
|
data["permissions"] = permissions_.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;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,18 +431,43 @@ Role Role::Deserialize(const nlohmann::json &data) {
|
|||||||
throw AuthException("Couldn't load role data!");
|
throw AuthException("Couldn't load role data!");
|
||||||
}
|
}
|
||||||
auto permissions = Permissions::Deserialize(data["permissions"]);
|
auto permissions = Permissions::Deserialize(data["permissions"]);
|
||||||
|
#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};
|
return {data["rolename"], permissions};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(const Role &first, const Role &second) {
|
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_;
|
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)
|
User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions)
|
||||||
: username_(utils::ToLowerCase(username)), password_hash_(password_hash), 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,
|
||||||
|
FineGrainedAccessHandler fine_grained_access_handler)
|
||||||
|
: username_(utils::ToLowerCase(username)),
|
||||||
|
password_hash_(password_hash),
|
||||||
|
permissions_(permissions),
|
||||||
|
fine_grained_access_handler_(std::move(fine_grained_access_handler)) {}
|
||||||
|
#endif
|
||||||
|
|
||||||
bool User::CheckPassword(const std::string &password) {
|
bool User::CheckPassword(const std::string &password) {
|
||||||
if (password_hash_.empty()) return true;
|
if (password_hash_.empty()) return true;
|
||||||
return VerifyPassword(password, password_hash_);
|
return VerifyPassword(password, password_hash_);
|
||||||
@@ -236,13 +483,13 @@ void User::UpdatePassword(const std::optional<std::string> &password) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (FLAGS_auth_password_strength_regex != default_password_regex) {
|
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()) {
|
license_check_result.HasError()) {
|
||||||
throw AuthException(
|
throw AuthException(
|
||||||
"Custom password regex is a Memgraph Enterprise feature. Please set the config "
|
"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{}",
|
"(\"--auth-password-strength-regex\") to its default value (\"{}\") or remove the flag.\n{}",
|
||||||
default_password_regex,
|
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);
|
std::regex re(FLAGS_auth_password_strength_regex);
|
||||||
@@ -262,17 +509,47 @@ void User::ClearRole() { role_ = std::nullopt; }
|
|||||||
|
|
||||||
Permissions User::GetPermissions() const {
|
Permissions User::GetPermissions() const {
|
||||||
if (role_) {
|
if (role_) {
|
||||||
return Permissions(permissions_.grants() | role_->permissions().grants(),
|
return Permissions{permissions_.grants() | role_->permissions().grants(),
|
||||||
permissions_.denies() | role_->permissions().denies());
|
permissions_.denies() | role_->permissions().denies()};
|
||||||
}
|
}
|
||||||
return permissions_;
|
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 std::string &User::username() const { return username_; }
|
||||||
|
|
||||||
const Permissions &User::permissions() const { return permissions_; }
|
const Permissions &User::permissions() const { return permissions_; }
|
||||||
Permissions &User::permissions() { return permissions_; }
|
Permissions &User::permissions() { return permissions_; }
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
const FineGrainedAccessHandler &User::fine_grained_access_handler() const { return fine_grained_access_handler_; }
|
||||||
|
|
||||||
|
FineGrainedAccessHandler &User::fine_grained_access_handler() { return fine_grained_access_handler_; }
|
||||||
|
#endif
|
||||||
const Role *User::role() const {
|
const Role *User::role() const {
|
||||||
if (role_.has_value()) {
|
if (role_.has_value()) {
|
||||||
return &role_.value();
|
return &role_.value();
|
||||||
@@ -285,6 +562,13 @@ nlohmann::json User::Serialize() const {
|
|||||||
data["username"] = username_;
|
data["username"] = username_;
|
||||||
data["password_hash"] = password_hash_;
|
data["password_hash"] = password_hash_;
|
||||||
data["permissions"] = permissions_.Serialize();
|
data["permissions"] = permissions_.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.
|
// The role shouldn't be serialized here, it is stored as a foreign key.
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -297,11 +581,28 @@ User User::Deserialize(const nlohmann::json &data) {
|
|||||||
throw AuthException("Couldn't load user data!");
|
throw AuthException("Couldn't load user data!");
|
||||||
}
|
}
|
||||||
auto permissions = Permissions::Deserialize(data["permissions"]);
|
auto permissions = Permissions::Deserialize(data["permissions"]);
|
||||||
|
#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};
|
return {data["username"], data["password_hash"], permissions};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(const User &first, const User &second) {
|
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_ &&
|
return first.username_ == second.username_ && first.password_hash_ == second.password_hash_ &&
|
||||||
first.permissions_ == second.permissions_ && first.role_ == second.role_;
|
first.permissions_ == second.permissions_ && first.role_ == second.role_;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace memgraph::auth
|
} // namespace memgraph::auth
|
||||||
|
|||||||
@@ -10,10 +10,12 @@
|
|||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
#include <json/json.hpp>
|
#include <json/json.hpp>
|
||||||
|
|
||||||
namespace memgraph::auth {
|
namespace memgraph::auth {
|
||||||
|
const std::string kAsterisk = "*";
|
||||||
// These permissions must have values that are applicable for usage in a
|
// These permissions must have values that are applicable for usage in a
|
||||||
// bitmask.
|
// bitmask.
|
||||||
// clang-format off
|
// clang-format off
|
||||||
@@ -38,27 +40,65 @@ enum class Permission : uint64_t {
|
|||||||
STREAM = 1U << 17U,
|
STREAM = 1U << 17U,
|
||||||
MODULE_READ = 1U << 18U,
|
MODULE_READ = 1U << 18U,
|
||||||
MODULE_WRITE = 1U << 19U,
|
MODULE_WRITE = 1U << 19U,
|
||||||
WEBSOCKET = 1U << 20U,
|
WEBSOCKET = 1U << 20U
|
||||||
SCHEMA = 1U << 21U
|
|
||||||
};
|
};
|
||||||
// clang-format on
|
// 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.
|
// Function that converts a permission to its string representation.
|
||||||
std::string PermissionToString(Permission permission);
|
std::string PermissionToString(Permission permission);
|
||||||
|
|
||||||
// Class that indicates what permission level the user/role has.
|
// Class that indicates what permission level the user/role has.
|
||||||
enum class PermissionLevel {
|
enum class PermissionLevel : uint8_t { GRANT, NEUTRAL, DENY };
|
||||||
GRANT,
|
|
||||||
NEUTRAL,
|
|
||||||
DENY,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function that converts a permission level to its string representation.
|
// Function that converts a permission level to its string representation.
|
||||||
std::string PermissionLevelToString(PermissionLevel level);
|
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 {
|
class Permissions final {
|
||||||
public:
|
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;
|
PermissionLevel Has(Permission permission) const;
|
||||||
|
|
||||||
@@ -89,16 +129,96 @@ bool operator==(const Permissions &first, const Permissions &second);
|
|||||||
|
|
||||||
bool operator!=(const Permissions &first, const Permissions &second);
|
bool operator!=(const Permissions &first, const Permissions &second);
|
||||||
|
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
class FineGrainedAccessPermissions final {
|
||||||
|
public:
|
||||||
|
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;
|
||||||
|
|
||||||
|
void Grant(const std::string &permission, FineGrainedPermission fine_grained_permission);
|
||||||
|
|
||||||
|
void Revoke(const std::string &permission);
|
||||||
|
|
||||||
|
nlohmann::json Serialize() const;
|
||||||
|
|
||||||
|
/// @throw AuthException if unable to deserialize.
|
||||||
|
static FineGrainedAccessPermissions Deserialize(const nlohmann::json &data);
|
||||||
|
|
||||||
|
const std::unordered_map<std::string, uint64_t> &GetPermissions() const;
|
||||||
|
const std::optional<uint64_t> &GetGlobalPermission() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
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 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
|
||||||
|
|
||||||
class Role final {
|
class Role final {
|
||||||
public:
|
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);
|
||||||
|
#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 std::string &rolename() const;
|
||||||
const Permissions &permissions() const;
|
const Permissions &permissions() const;
|
||||||
Permissions &permissions();
|
Permissions &permissions();
|
||||||
|
#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;
|
nlohmann::json Serialize() const;
|
||||||
|
|
||||||
/// @throw AuthException if unable to deserialize.
|
/// @throw AuthException if unable to deserialize.
|
||||||
@@ -109,6 +229,9 @@ class Role final {
|
|||||||
private:
|
private:
|
||||||
std::string rolename_;
|
std::string rolename_;
|
||||||
Permissions permissions_;
|
Permissions permissions_;
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
FineGrainedAccessHandler fine_grained_access_handler_;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
bool operator==(const Role &first, const Role &second);
|
bool operator==(const Role &first, const Role &second);
|
||||||
@@ -116,9 +239,19 @@ bool operator==(const Role &first, const Role &second);
|
|||||||
// TODO (mferencevic): Implement password expiry.
|
// TODO (mferencevic): Implement password expiry.
|
||||||
class User final {
|
class User final {
|
||||||
public:
|
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);
|
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,
|
||||||
|
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.
|
/// @throw AuthException if unable to verify the password.
|
||||||
bool CheckPassword(const std::string &password);
|
bool CheckPassword(const std::string &password);
|
||||||
@@ -132,6 +265,12 @@ class User final {
|
|||||||
|
|
||||||
Permissions GetPermissions() const;
|
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 std::string &username() const;
|
||||||
|
|
||||||
const Permissions &permissions() const;
|
const Permissions &permissions() const;
|
||||||
@@ -150,8 +289,16 @@ class User final {
|
|||||||
std::string username_;
|
std::string username_;
|
||||||
std::string password_hash_;
|
std::string password_hash_;
|
||||||
Permissions permissions_;
|
Permissions permissions_;
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
FineGrainedAccessHandler fine_grained_access_handler_;
|
||||||
|
#endif
|
||||||
std::optional<Role> role_;
|
std::optional<Role> role_;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool operator==(const User &first, const User &second);
|
bool operator==(const User &first, const User &second);
|
||||||
|
|
||||||
|
#ifdef MG_ENTERPRISE
|
||||||
|
FineGrainedAccessPermissions Merge(const FineGrainedAccessPermissions &first,
|
||||||
|
const FineGrainedAccessPermissions &second);
|
||||||
|
#endif
|
||||||
} // namespace memgraph::auth
|
} // namespace memgraph::auth
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
namespace memgraph::common {
|
|
||||||
|
|
||||||
enum class ErrorCode : uint8_t {
|
|
||||||
SERIALIZATION_ERROR,
|
|
||||||
NONEXISTENT_OBJECT,
|
|
||||||
DELETED_OBJECT,
|
|
||||||
VERTEX_HAS_EDGES,
|
|
||||||
PROPERTIES_DISABLED,
|
|
||||||
VERTEX_ALREADY_INSERTED,
|
|
||||||
// Schema Violations
|
|
||||||
SCHEMA_NO_SCHEMA_DEFINED_FOR_LABEL,
|
|
||||||
SCHEMA_VERTEX_PROPERTY_WRONG_TYPE,
|
|
||||||
SCHEMA_VERTEX_UPDATE_PRIMARY_KEY,
|
|
||||||
SCHEMA_VERTEX_UPDATE_PRIMARY_LABEL,
|
|
||||||
SCHEMA_VERTEX_SECONDARY_LABEL_IS_PRIMARY,
|
|
||||||
SCHEMA_VERTEX_PRIMARY_PROPERTIES_UNDEFINED,
|
|
||||||
|
|
||||||
OBJECT_NOT_FOUND,
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr std::string_view ErrorCodeToString(const ErrorCode code) {
|
|
||||||
switch (code) {
|
|
||||||
case ErrorCode::SERIALIZATION_ERROR:
|
|
||||||
return "SERIALIZATION_ERROR";
|
|
||||||
case ErrorCode::NONEXISTENT_OBJECT:
|
|
||||||
return "NONEXISTENT_OBJECT";
|
|
||||||
case ErrorCode::DELETED_OBJECT:
|
|
||||||
return "DELETED_OBJECT";
|
|
||||||
case ErrorCode::VERTEX_HAS_EDGES:
|
|
||||||
return "VERTEX_HAS_EDGES";
|
|
||||||
case ErrorCode::PROPERTIES_DISABLED:
|
|
||||||
return "PROPERTIES_DISABLED";
|
|
||||||
case ErrorCode::VERTEX_ALREADY_INSERTED:
|
|
||||||
return "VERTEX_ALREADY_INSERTED";
|
|
||||||
case ErrorCode::SCHEMA_NO_SCHEMA_DEFINED_FOR_LABEL:
|
|
||||||
return "SCHEMA_NO_SCHEMA_DEFINED_FOR_LABEL";
|
|
||||||
case ErrorCode::SCHEMA_VERTEX_PROPERTY_WRONG_TYPE:
|
|
||||||
return "SCHEMA_VERTEX_PROPERTY_WRONG_TYPE";
|
|
||||||
case ErrorCode::SCHEMA_VERTEX_UPDATE_PRIMARY_KEY:
|
|
||||||
return "SCHEMA_VERTEX_UPDATE_PRIMARY_KEY";
|
|
||||||
case ErrorCode::SCHEMA_VERTEX_UPDATE_PRIMARY_LABEL:
|
|
||||||
return "SCHEMA_VERTEX_UPDATE_PRIMARY_LABEL";
|
|
||||||
case ErrorCode::SCHEMA_VERTEX_SECONDARY_LABEL_IS_PRIMARY:
|
|
||||||
return "SCHEMA_VERTEX_SECONDARY_LABEL_IS_PRIMARY";
|
|
||||||
case ErrorCode::SCHEMA_VERTEX_PRIMARY_PROPERTIES_UNDEFINED:
|
|
||||||
return "SCHEMA_VERTEX_PRIMARY_PROPERTIES_UNDEFINED";
|
|
||||||
case ErrorCode::OBJECT_NOT_FOUND:
|
|
||||||
return "OBJECT_NOT_FOUND";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace memgraph::common
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace memgraph::common {
|
|
||||||
enum class SchemaType : uint8_t { BOOL, INT, STRING, DATE, LOCALTIME, LOCALDATETIME, DURATION };
|
|
||||||
|
|
||||||
} // namespace memgraph::common
|
|
||||||
@@ -7,6 +7,7 @@ set(communication_src_files
|
|||||||
websocket/listener.cpp
|
websocket/listener.cpp
|
||||||
websocket/session.cpp
|
websocket/session.cpp
|
||||||
bolt/v1/value.cpp
|
bolt/v1/value.cpp
|
||||||
|
bolt/client.cpp
|
||||||
buffer.cpp
|
buffer.cpp
|
||||||
client.cpp
|
client.cpp
|
||||||
context.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
|
#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/chunked_decoder_buffer.hpp"
|
||||||
#include "communication/bolt/v1/decoder/decoder.hpp"
|
#include "communication/bolt/v1/decoder/decoder.hpp"
|
||||||
#include "communication/bolt/v1/encoder/chunked_encoder_buffer.hpp"
|
#include "communication/bolt/v1/encoder/chunked_encoder_buffer.hpp"
|
||||||
@@ -19,22 +25,17 @@
|
|||||||
#include "communication/context.hpp"
|
#include "communication/context.hpp"
|
||||||
#include "io/network/endpoint.hpp"
|
#include "io/network/endpoint.hpp"
|
||||||
#include "utils/exceptions.hpp"
|
#include "utils/exceptions.hpp"
|
||||||
#include "utils/logging.hpp"
|
|
||||||
|
|
||||||
namespace memgraph::communication::bolt {
|
namespace memgraph::communication::bolt {
|
||||||
|
|
||||||
/// This exception is thrown whenever an error occurs during query execution
|
class FailureResponseException : public utils::BasicException {
|
||||||
/// 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 {
|
|
||||||
public:
|
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>
|
FailureResponseException(const std::string &code, const std::string &message)
|
||||||
ClientQueryException(const std::string &code, Args &&...args)
|
: utils::BasicException{message}, code_{code} {}
|
||||||
: utils::BasicException(std::forward<Args>(args)...), code_(code) {}
|
|
||||||
|
|
||||||
const std::string &code() const { return code_; }
|
const std::string &code() const { return code_; }
|
||||||
|
|
||||||
@@ -42,6 +43,14 @@ class ClientQueryException : public utils::BasicException {
|
|||||||
std::string code_;
|
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
|
/// This exception is thrown whenever a fatal error occurs during query
|
||||||
/// execution and/or connecting to the server.
|
/// execution and/or connecting to the server.
|
||||||
/// It should be handled by everyone who uses the client.
|
/// It should be handled by everyone who uses the client.
|
||||||
@@ -76,12 +85,13 @@ struct QueryData {
|
|||||||
/// server. It supports both SSL and plaintext connections.
|
/// server. It supports both SSL and plaintext connections.
|
||||||
class Client final {
|
class Client final {
|
||||||
public:
|
public:
|
||||||
explicit Client(communication::ClientContext *context) : client_(context) {}
|
explicit Client(communication::ClientContext &context);
|
||||||
|
|
||||||
Client(const Client &) = delete;
|
Client(const Client &) = delete;
|
||||||
Client(Client &&) = delete;
|
Client(Client &&) = delete;
|
||||||
Client &operator=(const Client &) = delete;
|
Client &operator=(const Client &) = delete;
|
||||||
Client &operator=(Client &&) = delete;
|
Client &operator=(Client &&) = delete;
|
||||||
|
~Client() = default;
|
||||||
|
|
||||||
/// Method used to connect to the server. Before executing queries this method
|
/// 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
|
/// should be called to set-up the connection to the server. After the
|
||||||
@@ -89,50 +99,7 @@ class Client final {
|
|||||||
/// established connection.
|
/// established connection.
|
||||||
/// @throws ClientFatalException when we couldn't connect to the server
|
/// @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,
|
void Connect(const io::network::Endpoint &endpoint, const std::string &username, const std::string &password,
|
||||||
const std::string &client_name = "memgraph-bolt") {
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Function used to execute queries against the server. Before you can
|
/// Function used to execute queries against the server. Before you can
|
||||||
/// execute queries you must connect the client to the server.
|
/// execute queries you must connect the client to the server.
|
||||||
@@ -140,168 +107,41 @@ class Client final {
|
|||||||
/// executing the query (eg. mistyped query,
|
/// executing the query (eg. mistyped query,
|
||||||
/// etc.)
|
/// etc.)
|
||||||
/// @throws ClientFatalException when we couldn't communicate with the server
|
/// @throws ClientFatalException when we couldn't communicate with the server
|
||||||
QueryData Execute(const std::string &query, const std::map<std::string, Value> ¶meters) {
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Close the active client connection.
|
/// 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:
|
private:
|
||||||
bool GetMessage() {
|
using ClientEncoder = ClientEncoder<ChunkedEncoderBuffer<communication::ClientOutputStream>>;
|
||||||
client_.ClearData();
|
|
||||||
while (true) {
|
|
||||||
if (!client_.Read(kChunkHeaderSize)) return false;
|
|
||||||
|
|
||||||
size_t chunk_size = client_.GetData()[0];
|
template <typename TException = FailureResponseException>
|
||||||
chunk_size <<= 8;
|
[[noreturn]] void HandleFailure(const std::map<std::string, Value> &response_map) {
|
||||||
chunk_size += client_.GetData()[1];
|
Reset();
|
||||||
if (chunk_size == 0) return true;
|
auto it = response_map.find("message");
|
||||||
|
if (it != response_map.end()) {
|
||||||
if (!client_.Read(chunk_size)) return false;
|
auto it_code = response_map.find("code");
|
||||||
if (decoder_buffer_.GetChunk() != ChunkState::Whole) return false;
|
if (it_code != response_map.end()) {
|
||||||
client_.ClearData();
|
throw TException(it_code->second.ValueString(), it->second.ValueString());
|
||||||
}
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
throw TException("", it->second.ValueString());
|
||||||
}
|
}
|
||||||
|
throw TException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GetMessage();
|
||||||
|
bool ReadMessage(Signature &signature, Value &ret);
|
||||||
|
bool ReadMessageData(Marker marker, Value &ret);
|
||||||
|
|
||||||
// client
|
// client
|
||||||
communication::Client client_;
|
communication::Client client_;
|
||||||
communication::ClientInputStream input_stream_{client_};
|
communication::ClientInputStream input_stream_{client_};
|
||||||
@@ -313,6 +153,6 @@ class Client final {
|
|||||||
|
|
||||||
// encoder objects
|
// encoder objects
|
||||||
ChunkedEncoderBuffer<communication::ClientOutputStream> encoder_buffer_{output_stream_};
|
ChunkedEncoderBuffer<communication::ClientOutputStream> encoder_buffer_{output_stream_};
|
||||||
ClientEncoder<ChunkedEncoderBuffer<communication::ClientOutputStream>> encoder_{encoder_buffer_};
|
ClientEncoder encoder_{encoder_buffer_};
|
||||||
};
|
};
|
||||||
} // namespace memgraph::communication::bolt
|
} // namespace memgraph::communication::bolt
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
namespace memgraph::communication::bolt {
|
namespace memgraph::communication::bolt {
|
||||||
|
|
||||||
inline constexpr uint8_t kPreamble[4] = {0x60, 0x60, 0xB0, 0x17};
|
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 {
|
enum class Signature : uint8_t {
|
||||||
Noop = 0x00,
|
Noop = 0x00,
|
||||||
|
|||||||
@@ -11,6 +11,11 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "communication/bolt/v1/codes.hpp"
|
#include "communication/bolt/v1/codes.hpp"
|
||||||
#include "communication/bolt/v1/encoder/base_encoder.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>::WriteList;
|
||||||
using BaseEncoder<Buffer>::WriteMap;
|
using BaseEncoder<Buffer>::WriteMap;
|
||||||
using BaseEncoder<Buffer>::WriteString;
|
using BaseEncoder<Buffer>::WriteString;
|
||||||
|
using BaseEncoder<Buffer>::WriteNull;
|
||||||
using BaseEncoder<Buffer>::buffer_;
|
using BaseEncoder<Buffer>::buffer_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -38,10 +44,9 @@ class ClientEncoder : private BaseEncoder<Buffer> {
|
|||||||
/**
|
/**
|
||||||
* Writes a Init message.
|
* Writes a Init message.
|
||||||
*
|
*
|
||||||
* From the Bolt v1 documentation:
|
* From the Bolt v4.3 documentation:
|
||||||
* InitMessage (signature=0x01) {
|
* HelloMess (signature=0x01) {
|
||||||
* String clientName
|
* Map<String,Value> extra
|
||||||
* Map<String,Value> authToken
|
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* @param client_name the name of the connected client
|
* @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
|
* @returns true if the data was successfully sent to the client
|
||||||
* when flushing, false otherwise
|
* when flushing, false otherwise
|
||||||
*/
|
*/
|
||||||
bool MessageInit(const std::string client_name, const std::map<std::string, Value> &auth_token) {
|
bool MessageInit(const std::map<std::string, Value> &extra) {
|
||||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct2));
|
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct1));
|
||||||
WriteRAW(utils::UnderlyingCast(Signature::Init));
|
WriteRAW(utils::UnderlyingCast(Signature::Init));
|
||||||
WriteString(client_name);
|
WriteMap(extra);
|
||||||
WriteMap(auth_token);
|
|
||||||
// Try to flush all remaining data in the buffer, but tell it that we will
|
// Try to flush all remaining data in the buffer, but tell it that we will
|
||||||
// send more data (the end of message chunk).
|
// send more data (the end of message chunk).
|
||||||
if (!buffer_.Flush(true)) return false;
|
if (!buffer_.Flush(true)) return false;
|
||||||
@@ -64,10 +68,11 @@ class ClientEncoder : private BaseEncoder<Buffer> {
|
|||||||
/**
|
/**
|
||||||
* Writes a Run message.
|
* Writes a Run message.
|
||||||
*
|
*
|
||||||
* From the Bolt v1 documentation:
|
* From the Bolt v4.3 documentation:
|
||||||
* RunMessage (signature=0x10) {
|
* RunMessage (signature=0x10) {
|
||||||
* String statement
|
* String statement
|
||||||
* Map<String,Value> parameters
|
* Map<String,Value> parameters
|
||||||
|
* Map<String,Value> extra
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* @param statement the statement that should be executed
|
* @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
|
* @returns true if the data was successfully sent to the client
|
||||||
* when flushing, false otherwise
|
* when flushing, false otherwise
|
||||||
*/
|
*/
|
||||||
bool MessageRun(const std::string &statement, const std::map<std::string, Value> ¶meters, bool have_more = true) {
|
bool MessageRun(const std::string &statement, const std::map<std::string, Value> ¶meters,
|
||||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct2));
|
const std::map<std::string, Value> &extra, bool have_more = true) {
|
||||||
|
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct3));
|
||||||
WriteRAW(utils::UnderlyingCast(Signature::Run));
|
WriteRAW(utils::UnderlyingCast(Signature::Run));
|
||||||
WriteString(statement);
|
WriteString(statement);
|
||||||
WriteMap(parameters);
|
WriteMap(parameters);
|
||||||
|
WriteMap(extra);
|
||||||
// Try to flush all remaining data in the buffer, but tell it that we will
|
// Try to flush all remaining data in the buffer, but tell it that we will
|
||||||
// send more data (the end of message chunk).
|
// send more data (the end of message chunk).
|
||||||
if (!buffer_.Flush(true)) return false;
|
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) {
|
* DiscardMessage (signature=0x2F) {
|
||||||
|
* Map<String,Value> extra
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* @returns true if the data was successfully sent to the client
|
* @returns true if the data was successfully sent to the client
|
||||||
* when flushing, false otherwise
|
* when flushing, false otherwise
|
||||||
*/
|
*/
|
||||||
bool MessageDiscardAll() {
|
bool MessageDiscard(const std::map<std::string, Value> &extra) {
|
||||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct));
|
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct1));
|
||||||
WriteRAW(utils::UnderlyingCast(Signature::Discard));
|
WriteRAW(utils::UnderlyingCast(Signature::Discard));
|
||||||
|
WriteMap(extra);
|
||||||
// Try to flush all remaining data in the buffer, but tell it that we will
|
// Try to flush all remaining data in the buffer, but tell it that we will
|
||||||
// send more data (the end of message chunk).
|
// send more data (the end of message chunk).
|
||||||
if (!buffer_.Flush(true)) return false;
|
if (!buffer_.Flush(true)) return false;
|
||||||
@@ -112,36 +121,18 @@ class ClientEncoder : private BaseEncoder<Buffer> {
|
|||||||
/**
|
/**
|
||||||
* Writes a PullAll message.
|
* Writes a PullAll message.
|
||||||
*
|
*
|
||||||
* From the Bolt v1 documentation:
|
* From the Bolt v4.3 documentation:
|
||||||
* PullAllMessage (signature=0x3F) {
|
* PullMessage (signature=0x3F) {
|
||||||
|
* Map<String,Value> extra
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* @returns true if the data was successfully sent to the client
|
* @returns true if the data was successfully sent to the client
|
||||||
* when flushing, false otherwise
|
* when flushing, false otherwise
|
||||||
*/
|
*/
|
||||||
bool MessagePullAll() {
|
bool MessagePull(const std::map<std::string, Value> &extra) {
|
||||||
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct));
|
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct1));
|
||||||
WriteRAW(utils::UnderlyingCast(Signature::Pull));
|
WriteRAW(utils::UnderlyingCast(Signature::Pull));
|
||||||
// Try to flush all remaining data in the buffer, but tell it that we will
|
WriteMap(extra);
|
||||||
// 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));
|
|
||||||
// Try to flush all remaining data in the buffer, but tell it that we will
|
// Try to flush all remaining data in the buffer, but tell it that we will
|
||||||
// send more data (the end of message chunk).
|
// send more data (the end of message chunk).
|
||||||
if (!buffer_.Flush(true)) return false;
|
if (!buffer_.Flush(true)) return false;
|
||||||
@@ -152,7 +143,7 @@ class ClientEncoder : private BaseEncoder<Buffer> {
|
|||||||
/**
|
/**
|
||||||
* Writes a Reset message.
|
* Writes a Reset message.
|
||||||
*
|
*
|
||||||
* From the Bolt v1 documentation:
|
* From the Bolt v4.3 documentation:
|
||||||
* ResetMessage (signature=0x0F) {
|
* ResetMessage (signature=0x0F) {
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
@@ -168,5 +159,36 @@ class ClientEncoder : private BaseEncoder<Buffer> {
|
|||||||
// Flush an empty chunk to indicate that the message is done.
|
// Flush an empty chunk to indicate that the message is done.
|
||||||
return buffer_.Flush();
|
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
|
} // namespace memgraph::communication::bolt
|
||||||
|
|||||||
@@ -117,29 +117,6 @@ class Encoder : private BaseEncoder<Buffer> {
|
|||||||
return buffer_.Flush();
|
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.
|
* Sends an Ignored message.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
#include "communication/bolt/v1/codes.hpp"
|
#include "communication/bolt/v1/codes.hpp"
|
||||||
#include "communication/bolt/v1/state.hpp"
|
#include "communication/bolt/v1/state.hpp"
|
||||||
|
#include "communication/bolt/v1/states/handlers.hpp"
|
||||||
#include "communication/bolt/v1/value.hpp"
|
#include "communication/bolt/v1/value.hpp"
|
||||||
#include "utils/cast.hpp"
|
#include "utils/cast.hpp"
|
||||||
#include "utils/likely.hpp"
|
#include "utils/likely.hpp"
|
||||||
@@ -30,8 +31,8 @@ namespace memgraph::communication::bolt {
|
|||||||
*/
|
*/
|
||||||
template <typename TSession>
|
template <typename TSession>
|
||||||
State StateErrorRun(TSession &session, State state) {
|
State StateErrorRun(TSession &session, State state) {
|
||||||
Marker marker;
|
Marker marker{};
|
||||||
Signature signature;
|
Signature signature{};
|
||||||
if (!session.decoder_.ReadMessageHeader(&signature, &marker)) {
|
if (!session.decoder_.ReadMessageHeader(&signature, &marker)) {
|
||||||
spdlog::trace("Missing header data!");
|
spdlog::trace("Missing header data!");
|
||||||
return State::Close;
|
return State::Close;
|
||||||
@@ -45,54 +46,49 @@ State StateErrorRun(TSession &session, State state) {
|
|||||||
// Clear the data buffer if it has any leftover data.
|
// Clear the data buffer if it has any leftover data.
|
||||||
session.encoder_buffer_.Clear();
|
session.encoder_buffer_.Clear();
|
||||||
|
|
||||||
if ((session.version_.major == 1 && signature == Signature::AckFailure) || signature == Signature::Reset) {
|
if (session.version_.major == 1 && signature == Signature::AckFailure) {
|
||||||
if (signature == Signature::AckFailure) {
|
spdlog::trace("AckFailure received");
|
||||||
spdlog::trace("AckFailure received");
|
|
||||||
} else {
|
|
||||||
spdlog::trace("Reset received");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!session.encoder_.MessageSuccess()) {
|
if (!session.encoder_.MessageSuccess()) {
|
||||||
spdlog::trace("Couldn't send success message!");
|
spdlog::trace("Couldn't send success message!");
|
||||||
return State::Close;
|
return State::Close;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (signature == Signature::Reset) {
|
|
||||||
session.Abort();
|
|
||||||
return State::Idle;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We got AckFailure get back to right state.
|
// We got AckFailure get back to right state.
|
||||||
MG_ASSERT(state == State::Error, "Shouldn't happen");
|
MG_ASSERT(state == State::Error, "Shouldn't happen");
|
||||||
return State::Idle;
|
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
|
} // namespace memgraph::communication::bolt
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ State RunHandlerV4(Signature signature, TSession &session, State state, Marker m
|
|||||||
}
|
}
|
||||||
case Signature::Route: {
|
case Signature::Route: {
|
||||||
if constexpr (bolt_minor >= 3) {
|
if constexpr (bolt_minor >= 3) {
|
||||||
return HandleRoute<TSession>(session);
|
if (signature == Signature::Route) return HandleRoute<TSession>(session, marker);
|
||||||
} else {
|
} else {
|
||||||
spdlog::trace("Supported only in bolt v4.3");
|
spdlog::trace("Supported only in bolt v4.3");
|
||||||
return State::Close;
|
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
|
// 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
|
// 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/codes.hpp"
|
||||||
#include "communication/bolt/v1/constants.hpp"
|
#include "communication/bolt/v1/constants.hpp"
|
||||||
|
#include "communication/bolt/v1/exceptions.hpp"
|
||||||
#include "communication/bolt/v1/state.hpp"
|
#include "communication/bolt/v1/state.hpp"
|
||||||
#include "communication/bolt/v1/value.hpp"
|
#include "communication/bolt/v1/value.hpp"
|
||||||
#include "communication/exceptions.hpp"
|
#include "communication/exceptions.hpp"
|
||||||
@@ -72,40 +73,6 @@ inline std::pair<std::string, std::string> ExceptionToErrorMessage(const std::ex
|
|||||||
|
|
||||||
namespace details {
|
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>
|
template <bool is_pull, typename TSession>
|
||||||
State HandlePullDiscard(TSession &session, std::optional<int> n, std::optional<int> qid) {
|
State HandlePullDiscard(TSession &session, std::optional<int> n, std::optional<int> qid) {
|
||||||
try {
|
try {
|
||||||
@@ -136,7 +103,7 @@ template <bool is_pull, typename TSession>
|
|||||||
State HandlePullDiscardV1(TSession &session, const State state, const Marker marker) {
|
State HandlePullDiscardV1(TSession &session, const State state, const Marker marker) {
|
||||||
const auto expected_marker = Marker::TinyStruct;
|
const auto expected_marker = Marker::TinyStruct;
|
||||||
if (marker != expected_marker) {
|
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;
|
return State::Close;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +124,7 @@ template <bool is_pull, typename TSession>
|
|||||||
State HandlePullDiscardV4(TSession &session, const State state, const Marker marker) {
|
State HandlePullDiscardV4(TSession &session, const State state, const Marker marker) {
|
||||||
const auto expected_marker = Marker::TinyStruct1;
|
const auto expected_marker = Marker::TinyStruct1;
|
||||||
if (marker != expected_marker) {
|
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;
|
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));
|
session.version_.major == 1 ? "TinyStruct2" : "TinyStruct3", utils::UnderlyingCast(marker));
|
||||||
return State::Close;
|
return State::Close;
|
||||||
}
|
}
|
||||||
Value query, params;
|
Value query;
|
||||||
|
Value params;
|
||||||
if (!session.decoder_.ReadValue(&query, Value::Type::String)) {
|
if (!session.decoder_.ReadValue(&query, Value::Type::String)) {
|
||||||
spdlog::trace("Couldn't read query string!");
|
spdlog::trace("Couldn't read query string!");
|
||||||
return State::Close;
|
return State::Close;
|
||||||
@@ -227,17 +195,48 @@ State HandleRunV1(TSession &session, const State state, const Marker marker) {
|
|||||||
return State::Close;
|
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>
|
template <typename TSession>
|
||||||
State HandleRunV4(TSession &session, const State state, const Marker marker) {
|
State HandleRunV4(TSession &session, const State state, const Marker marker) {
|
||||||
const auto expected_marker = Marker::TinyStruct3;
|
const auto expected_marker = Marker::TinyStruct3;
|
||||||
if (marker != expected_marker) {
|
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;
|
return State::Close;
|
||||||
}
|
}
|
||||||
Value query, params, extra;
|
Value query;
|
||||||
|
Value params;
|
||||||
|
Value extra;
|
||||||
if (!session.decoder_.ReadValue(&query, Value::Type::String)) {
|
if (!session.decoder_.ReadValue(&query, Value::Type::String)) {
|
||||||
spdlog::trace("Couldn't read query string!");
|
spdlog::trace("Couldn't read query string!");
|
||||||
return State::Close;
|
return State::Close;
|
||||||
@@ -253,7 +252,40 @@ State HandleRunV4(TSession &session, const State state, const Marker marker) {
|
|||||||
spdlog::trace("Couldn't read extra field!");
|
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>
|
template <typename TSession>
|
||||||
@@ -292,9 +324,6 @@ State HandleReset(TSession &session, const Marker marker) {
|
|||||||
return State::Close;
|
return State::Close;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear all pending data and send a success message.
|
|
||||||
session.encoder_buffer_.Clear();
|
|
||||||
|
|
||||||
if (!session.encoder_.MessageSuccess()) {
|
if (!session.encoder_.MessageSuccess()) {
|
||||||
spdlog::trace("Couldn't send success message!");
|
spdlog::trace("Couldn't send success message!");
|
||||||
return State::Close;
|
return State::Close;
|
||||||
@@ -403,12 +432,33 @@ State HandleGoodbye() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename TSession>
|
template <typename TSession>
|
||||||
State HandleRoute(TSession &session) {
|
State HandleRoute(TSession &session, const Marker marker) {
|
||||||
// Route message is not implemented since it is neo4j specific, therefore we
|
// Route message is not implemented since it is Neo4j specific, therefore we will receive it and inform user that
|
||||||
// will receive it an inform user that there is no implementation.
|
// 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();
|
session.encoder_buffer_.Clear();
|
||||||
bool fail_sent =
|
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) {
|
if (!fail_sent) {
|
||||||
spdlog::trace("Couldn't send failure message!");
|
spdlog::trace("Couldn't send failure message!");
|
||||||
return State::Close;
|
return State::Close;
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
#include "communication/context.hpp"
|
#include "communication/context.hpp"
|
||||||
#include "communication/v2/pool.hpp"
|
#include "communication/v2/pool.hpp"
|
||||||
#include "communication/v2/session.hpp"
|
#include "communication/v2/session.hpp"
|
||||||
#include "utils/message.hpp"
|
|
||||||
#include "utils/spin_lock.hpp"
|
#include "utils/spin_lock.hpp"
|
||||||
#include "utils/synchronized.hpp"
|
#include "utils/synchronized.hpp"
|
||||||
|
|
||||||
@@ -59,10 +58,10 @@ class Listener final : public std::enable_shared_from_this<Listener<TSession, TS
|
|||||||
bool IsRunning() const noexcept { return alive_.load(std::memory_order_relaxed); }
|
bool IsRunning() const noexcept { return alive_.load(std::memory_order_relaxed); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Listener(boost::asio::io_context &io_context, TSessionData &data, ServerContext *server_context,
|
Listener(boost::asio::io_context &io_context, TSessionData *data, ServerContext *server_context,
|
||||||
tcp::endpoint &endpoint, const std::string_view service_name, const uint64_t inactivity_timeout_sec)
|
tcp::endpoint &endpoint, const std::string_view service_name, const uint64_t inactivity_timeout_sec)
|
||||||
: io_context_(io_context),
|
: io_context_(io_context),
|
||||||
data_(&data),
|
data_(data),
|
||||||
server_context_(server_context),
|
server_context_(server_context),
|
||||||
acceptor_(io_context_),
|
acceptor_(io_context_),
|
||||||
endpoint_{endpoint},
|
endpoint_{endpoint},
|
||||||
@@ -111,7 +110,7 @@ class Listener final : public std::enable_shared_from_this<Listener<TSession, TS
|
|||||||
return OnError(ec, "accept");
|
return OnError(ec, "accept");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto session = SessionHandler::Create(std::move(socket), *data_, *server_context_, endpoint_, inactivity_timeout_,
|
auto session = SessionHandler::Create(std::move(socket), data_, *server_context_, endpoint_, inactivity_timeout_,
|
||||||
service_name_);
|
service_name_);
|
||||||
session->Start();
|
session->Start();
|
||||||
DoAccept();
|
DoAccept();
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class Server final {
|
|||||||
* Constructs and binds server to endpoint, operates on session data and
|
* Constructs and binds server to endpoint, operates on session data and
|
||||||
* invokes workers_count workers
|
* invokes workers_count workers
|
||||||
*/
|
*/
|
||||||
Server(ServerEndpoint &endpoint, TSessionData &session_data, ServerContext *server_context,
|
Server(ServerEndpoint &endpoint, TSessionData *session_data, ServerContext *server_context,
|
||||||
const int inactivity_timeout_sec, const std::string_view service_name,
|
const int inactivity_timeout_sec, const std::string_view service_name,
|
||||||
size_t workers_count = std::thread::hardware_concurrency())
|
size_t workers_count = std::thread::hardware_concurrency())
|
||||||
: endpoint_{endpoint},
|
: endpoint_{endpoint},
|
||||||
|
|||||||
@@ -41,7 +41,6 @@
|
|||||||
#include <boost/beast/websocket/rfc6455.hpp>
|
#include <boost/beast/websocket/rfc6455.hpp>
|
||||||
#include <boost/system/detail/error_code.hpp>
|
#include <boost/system/detail/error_code.hpp>
|
||||||
|
|
||||||
#include "communication/buffer.hpp"
|
|
||||||
#include "communication/context.hpp"
|
#include "communication/context.hpp"
|
||||||
#include "communication/exceptions.hpp"
|
#include "communication/exceptions.hpp"
|
||||||
#include "utils/logging.hpp"
|
#include "utils/logging.hpp"
|
||||||
@@ -140,7 +139,7 @@ class WebsocketSession : public std::enable_shared_from_this<WebsocketSession<TS
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
// Take ownership of the socket
|
// Take ownership of the socket
|
||||||
explicit WebsocketSession(tcp::socket &&socket, TSessionData &data, tcp::endpoint endpoint,
|
explicit WebsocketSession(tcp::socket &&socket, TSessionData *data, tcp::endpoint endpoint,
|
||||||
std::string_view service_name)
|
std::string_view service_name)
|
||||||
: ws_(std::move(socket)),
|
: ws_(std::move(socket)),
|
||||||
strand_{boost::asio::make_strand(ws_.get_executor())},
|
strand_{boost::asio::make_strand(ws_.get_executor())},
|
||||||
@@ -312,13 +311,13 @@ class Session final : public std::enable_shared_from_this<Session<TSession, TSes
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit Session(tcp::socket &&socket, TSessionData &data, ServerContext &server_context, tcp::endpoint endpoint,
|
explicit Session(tcp::socket &&socket, TSessionData *data, ServerContext &server_context, tcp::endpoint endpoint,
|
||||||
const std::chrono::seconds inactivity_timeout_sec, std::string_view service_name)
|
const std::chrono::seconds inactivity_timeout_sec, std::string_view service_name)
|
||||||
: socket_(CreateSocket(std::move(socket), server_context)),
|
: socket_(CreateSocket(std::move(socket), server_context)),
|
||||||
strand_{boost::asio::make_strand(GetExecutor())},
|
strand_{boost::asio::make_strand(GetExecutor())},
|
||||||
output_stream_([this](const uint8_t *data, size_t len, bool have_more) { return Write(data, len, have_more); }),
|
output_stream_([this](const uint8_t *data, size_t len, bool have_more) { return Write(data, len, have_more); }),
|
||||||
session_(data, endpoint, input_buffer_.read_end(), &output_stream_),
|
session_(data, endpoint, input_buffer_.read_end(), &output_stream_),
|
||||||
data_{&data},
|
data_{data},
|
||||||
endpoint_{endpoint},
|
endpoint_{endpoint},
|
||||||
remote_endpoint_{GetRemoteEndpoint()},
|
remote_endpoint_{GetRemoteEndpoint()},
|
||||||
service_name_{service_name},
|
service_name_{service_name},
|
||||||
@@ -374,7 +373,7 @@ class Session final : public std::enable_shared_from_this<Session<TSession, TSes
|
|||||||
spdlog::info("Switching {} to websocket connection", remote_endpoint_);
|
spdlog::info("Switching {} to websocket connection", remote_endpoint_);
|
||||||
if (std::holds_alternative<TCPSocket>(socket_)) {
|
if (std::holds_alternative<TCPSocket>(socket_)) {
|
||||||
auto sock = std::get<TCPSocket>(std::move(socket_));
|
auto sock = std::get<TCPSocket>(std::move(socket_));
|
||||||
WebsocketSession<TSession, TSessionData>::Create(std::move(sock), *data_, endpoint_, service_name_)
|
WebsocketSession<TSession, TSessionData>::Create(std::move(sock), data_, endpoint_, service_name_)
|
||||||
->DoAccept(parser.release());
|
->DoAccept(parser.release());
|
||||||
execution_active_ = false;
|
execution_active_ = false;
|
||||||
return;
|
return;
|
||||||
@@ -466,7 +465,7 @@ class Session final : public std::enable_shared_from_this<Session<TSession, TSes
|
|||||||
if (timeout_timer_.expiry() <= boost::asio::steady_timer::clock_type::now()) {
|
if (timeout_timer_.expiry() <= boost::asio::steady_timer::clock_type::now()) {
|
||||||
// The deadline has passed. Stop the session. The other actors will
|
// The deadline has passed. Stop the session. The other actors will
|
||||||
// terminate as soon as possible.
|
// terminate as soon as possible.
|
||||||
spdlog::info("Shutting down session after {} of inactivity", timeout_seconds_.count());
|
spdlog::info("Shutting down session after {} of inactivity", timeout_seconds_);
|
||||||
DoShutdown();
|
DoShutdown();
|
||||||
} else {
|
} else {
|
||||||
// Put the actor back to sleep.
|
// Put the actor back to sleep.
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
set(coordinator_src_files
|
|
||||||
coordinator.cpp
|
|
||||||
shard_map.cpp)
|
|
||||||
|
|
||||||
find_package(fmt REQUIRED)
|
|
||||||
find_package(Threads REQUIRED)
|
|
||||||
|
|
||||||
add_library(mg-coordinator STATIC ${coordinator_src_files})
|
|
||||||
target_link_libraries(mg-coordinator stdc++fs Threads::Threads fmt::fmt mg-utils mg-storage-v3)
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
#include <coordinator/coordinator.hpp>
|
|
||||||
|
|
||||||
namespace memgraph::coordinator {
|
|
||||||
|
|
||||||
CoordinatorWriteResponses Coordinator::ApplyWrite(HeartbeatRequest &&heartbeat_request) {
|
|
||||||
spdlog::info("Coordinator handling HeartbeatRequest");
|
|
||||||
|
|
||||||
// add this storage engine to any under-replicated shards that it is not already a part of
|
|
||||||
|
|
||||||
auto initializing_rsms_for_shard_manager =
|
|
||||||
shard_map_.AssignShards(heartbeat_request.from_storage_manager, heartbeat_request.initialized_rsms);
|
|
||||||
|
|
||||||
return HeartbeatResponse{
|
|
||||||
.shards_to_initialize = initializing_rsms_for_shard_manager,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
CoordinatorWriteResponses Coordinator::ApplyWrite(HlcRequest &&hlc_request) {
|
|
||||||
HlcResponse res{};
|
|
||||||
|
|
||||||
auto hlc_shard_map = shard_map_.GetHlc();
|
|
||||||
|
|
||||||
MG_ASSERT(!(hlc_request.last_shard_map_version.logical_id > hlc_shard_map.logical_id));
|
|
||||||
|
|
||||||
res.new_hlc = Hlc{
|
|
||||||
.logical_id = ++highest_allocated_timestamp_,
|
|
||||||
// TODO(tyler) probably pass some more context to the Coordinator here
|
|
||||||
// so that we can use our wall clock and enforce monotonicity.
|
|
||||||
// .coordinator_wall_clock = io_.Now(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Allways return fresher shard_map for now.
|
|
||||||
res.fresher_shard_map = std::make_optional(shard_map_);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
CoordinatorWriteResponses Coordinator::ApplyWrite(AllocateEdgeIdBatchRequest &&ahr) {
|
|
||||||
AllocateEdgeIdBatchResponse res{};
|
|
||||||
|
|
||||||
uint64_t low = highest_allocated_edge_id_;
|
|
||||||
|
|
||||||
highest_allocated_edge_id_ += ahr.batch_size;
|
|
||||||
|
|
||||||
uint64_t high = highest_allocated_edge_id_;
|
|
||||||
|
|
||||||
res.low = low;
|
|
||||||
res.high = high;
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This splits the shard immediately beneath the provided
|
|
||||||
/// split key, keeping the assigned peers identical for now,
|
|
||||||
/// but letting them be gradually migrated over time.
|
|
||||||
CoordinatorWriteResponses Coordinator::ApplyWrite(SplitShardRequest &&split_shard_request) {
|
|
||||||
SplitShardResponse res{};
|
|
||||||
|
|
||||||
if (split_shard_request.previous_shard_map_version != shard_map_.shard_map_version) {
|
|
||||||
res.success = false;
|
|
||||||
} else {
|
|
||||||
res.success = shard_map_.SplitShard(split_shard_request.previous_shard_map_version, split_shard_request.label_id,
|
|
||||||
split_shard_request.split_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This adds the provided storage engine to the standby storage engine pool,
|
|
||||||
/// which can be used to rebalance storage over time.
|
|
||||||
CoordinatorWriteResponses Coordinator::ApplyWrite(
|
|
||||||
RegisterStorageEngineRequest && /* register_storage_engine_request */) {
|
|
||||||
RegisterStorageEngineResponse res{};
|
|
||||||
// TODO
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This begins the process of draining the provided storage engine from all raft
|
|
||||||
/// clusters that it might be participating in.
|
|
||||||
CoordinatorWriteResponses Coordinator::ApplyWrite(
|
|
||||||
DeregisterStorageEngineRequest && /* register_storage_engine_request */) {
|
|
||||||
DeregisterStorageEngineResponse res{};
|
|
||||||
// TODO
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
CoordinatorWriteResponses Coordinator::ApplyWrite(InitializeLabelRequest &&initialize_label_request) {
|
|
||||||
InitializeLabelResponse res{};
|
|
||||||
|
|
||||||
std::optional<LabelId> new_label_id = shard_map_.InitializeNewLabel(
|
|
||||||
initialize_label_request.label_name, initialize_label_request.schema, initialize_label_request.replication_factor,
|
|
||||||
initialize_label_request.last_shard_map_version);
|
|
||||||
|
|
||||||
if (new_label_id) {
|
|
||||||
res.new_label_id = new_label_id.value();
|
|
||||||
res.fresher_shard_map = std::nullopt;
|
|
||||||
res.success = true;
|
|
||||||
} else {
|
|
||||||
res.fresher_shard_map = shard_map_;
|
|
||||||
res.success = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
CoordinatorWriteResponses Coordinator::ApplyWrite(AllocatePropertyIdsRequest &&allocate_property_ids_request) {
|
|
||||||
AllocatePropertyIdsResponse res{};
|
|
||||||
|
|
||||||
auto property_ids = shard_map_.AllocatePropertyIds(allocate_property_ids_request.property_names);
|
|
||||||
|
|
||||||
res.property_ids = property_ids;
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace memgraph::coordinator
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <optional>
|
|
||||||
#include <set>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <variant>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <boost/uuid/uuid.hpp>
|
|
||||||
|
|
||||||
#include <coordinator/hybrid_logical_clock.hpp>
|
|
||||||
#include <coordinator/shard_map.hpp>
|
|
||||||
#include <io/simulator/simulator.hpp>
|
|
||||||
#include <io/time.hpp>
|
|
||||||
#include <io/transport.hpp>
|
|
||||||
#include <storage/v3/id_types.hpp>
|
|
||||||
#include <storage/v3/schemas.hpp>
|
|
||||||
|
|
||||||
namespace memgraph::coordinator {
|
|
||||||
|
|
||||||
using memgraph::io::Address;
|
|
||||||
using memgraph::storage::v3::LabelId;
|
|
||||||
using memgraph::storage::v3::PropertyId;
|
|
||||||
using memgraph::storage::v3::SchemaProperty;
|
|
||||||
using SimT = memgraph::io::simulator::SimulatorTransport;
|
|
||||||
using PrimaryKey = std::vector<PropertyValue>;
|
|
||||||
|
|
||||||
struct HlcRequest {
|
|
||||||
Hlc last_shard_map_version;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct HlcResponse {
|
|
||||||
Hlc new_hlc;
|
|
||||||
std::optional<ShardMap> fresher_shard_map;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GetShardMapRequest {
|
|
||||||
// No state
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GetShardMapResponse {
|
|
||||||
ShardMap shard_map;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AllocateHlcBatchRequest {
|
|
||||||
Hlc low;
|
|
||||||
Hlc high;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AllocateHlcBatchResponse {
|
|
||||||
bool success;
|
|
||||||
Hlc low;
|
|
||||||
Hlc high;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AllocateEdgeIdBatchRequest {
|
|
||||||
size_t batch_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AllocateEdgeIdBatchResponse {
|
|
||||||
uint64_t low;
|
|
||||||
uint64_t high;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AllocatePropertyIdsRequest {
|
|
||||||
std::vector<std::string> property_names;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AllocatePropertyIdsResponse {
|
|
||||||
std::map<std::string, PropertyId> property_ids;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SplitShardRequest {
|
|
||||||
Hlc previous_shard_map_version;
|
|
||||||
LabelId label_id;
|
|
||||||
PrimaryKey split_key;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SplitShardResponse {
|
|
||||||
bool success;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RegisterStorageEngineRequest {
|
|
||||||
Address address;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RegisterStorageEngineResponse {
|
|
||||||
bool success;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DeregisterStorageEngineRequest {
|
|
||||||
Address address;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DeregisterStorageEngineResponse {
|
|
||||||
bool success;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct InitializeLabelRequest {
|
|
||||||
std::string label_name;
|
|
||||||
std::vector<SchemaProperty> schema;
|
|
||||||
size_t replication_factor;
|
|
||||||
Hlc last_shard_map_version;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct InitializeLabelResponse {
|
|
||||||
bool success;
|
|
||||||
LabelId new_label_id;
|
|
||||||
std::optional<ShardMap> fresher_shard_map;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct HeartbeatRequest {
|
|
||||||
Address from_storage_manager;
|
|
||||||
std::set<boost::uuids::uuid> initialized_rsms;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct HeartbeatResponse {
|
|
||||||
std::vector<ShardToInitialize> shards_to_initialize;
|
|
||||||
};
|
|
||||||
|
|
||||||
using CoordinatorWriteRequests =
|
|
||||||
std::variant<HlcRequest, AllocateEdgeIdBatchRequest, SplitShardRequest, RegisterStorageEngineRequest,
|
|
||||||
DeregisterStorageEngineRequest, InitializeLabelRequest, AllocatePropertyIdsRequest, HeartbeatRequest>;
|
|
||||||
using CoordinatorWriteResponses = std::variant<HlcResponse, AllocateEdgeIdBatchResponse, SplitShardResponse,
|
|
||||||
RegisterStorageEngineResponse, DeregisterStorageEngineResponse,
|
|
||||||
InitializeLabelResponse, AllocatePropertyIdsResponse, HeartbeatResponse>;
|
|
||||||
|
|
||||||
using CoordinatorReadRequests = std::variant<GetShardMapRequest>;
|
|
||||||
using CoordinatorReadResponses = std::variant<GetShardMapResponse>;
|
|
||||||
|
|
||||||
class Coordinator {
|
|
||||||
public:
|
|
||||||
explicit Coordinator(ShardMap sm) : shard_map_{std::move(sm)} {}
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static
|
|
||||||
CoordinatorReadResponses Read(CoordinatorReadRequests requests) {
|
|
||||||
return std::visit([&](auto &&request) { return HandleRead(std::forward<decltype(request)>(request)); },
|
|
||||||
std::move(requests)); // NOLINT(hicpp-move-const-arg,performance-move-const-arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(readability-convert-member-functions-to-static
|
|
||||||
CoordinatorWriteResponses Apply(CoordinatorWriteRequests requests) {
|
|
||||||
return std::visit([&](auto &&request) mutable { return ApplyWrite(std::forward<decltype(request)>(request)); },
|
|
||||||
std::move(requests));
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
ShardMap shard_map_;
|
|
||||||
uint64_t highest_allocated_timestamp_{0};
|
|
||||||
|
|
||||||
/// Query engines need to periodically request batches of unique edge IDs.
|
|
||||||
uint64_t highest_allocated_edge_id_{0};
|
|
||||||
|
|
||||||
CoordinatorReadResponses HandleRead(GetShardMapRequest && /* get_shard_map_request */) {
|
|
||||||
GetShardMapResponse res;
|
|
||||||
res.shard_map = shard_map_;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
CoordinatorWriteResponses ApplyWrite(HeartbeatRequest &&heartbeat_request);
|
|
||||||
|
|
||||||
CoordinatorWriteResponses ApplyWrite(HlcRequest &&hlc_request);
|
|
||||||
|
|
||||||
CoordinatorWriteResponses ApplyWrite(AllocateEdgeIdBatchRequest &&ahr);
|
|
||||||
|
|
||||||
/// This splits the shard immediately beneath the provided
|
|
||||||
/// split key, keeping the assigned peers identical for now,
|
|
||||||
/// but letting them be gradually migrated over time.
|
|
||||||
CoordinatorWriteResponses ApplyWrite(SplitShardRequest &&split_shard_request);
|
|
||||||
|
|
||||||
/// This adds the provided storage engine to the standby storage engine pool,
|
|
||||||
/// which can be used to rebalance storage over time.
|
|
||||||
static CoordinatorWriteResponses ApplyWrite(RegisterStorageEngineRequest && /* register_storage_engine_request */);
|
|
||||||
|
|
||||||
/// This begins the process of draining the provided storage engine from all raft
|
|
||||||
/// clusters that it might be participating in.
|
|
||||||
static CoordinatorWriteResponses ApplyWrite(DeregisterStorageEngineRequest && /* register_storage_engine_request */);
|
|
||||||
|
|
||||||
CoordinatorWriteResponses ApplyWrite(InitializeLabelRequest &&initialize_label_request);
|
|
||||||
|
|
||||||
CoordinatorWriteResponses ApplyWrite(AllocatePropertyIdsRequest &&allocate_property_ids_request);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace memgraph::coordinator
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "coordinator/coordinator.hpp"
|
|
||||||
#include "io/rsm/rsm_client.hpp"
|
|
||||||
|
|
||||||
namespace memgraph::coordinator {
|
|
||||||
|
|
||||||
using memgraph::io::rsm::RsmClient;
|
|
||||||
|
|
||||||
template <typename IoImpl>
|
|
||||||
using CoordinatorClient = RsmClient<IoImpl, CoordinatorWriteRequests, CoordinatorWriteResponses,
|
|
||||||
CoordinatorReadRequests, CoordinatorReadResponses>;
|
|
||||||
} // namespace memgraph::coordinator
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "coordinator/coordinator.hpp"
|
|
||||||
#include "io/rsm/raft.hpp"
|
|
||||||
|
|
||||||
namespace memgraph::coordinator {
|
|
||||||
|
|
||||||
template <typename IoImpl>
|
|
||||||
using CoordinatorRsm = memgraph::io::rsm::Raft<IoImpl, Coordinator, CoordinatorWriteRequests, CoordinatorWriteResponses,
|
|
||||||
CoordinatorReadRequests, CoordinatorReadResponses>;
|
|
||||||
|
|
||||||
} // namespace memgraph::coordinator
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <deque>
|
|
||||||
#include <memory>
|
|
||||||
#include <queue>
|
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
#include "coordinator/coordinator.hpp"
|
|
||||||
#include "coordinator/coordinator_rsm.hpp"
|
|
||||||
#include "coordinator/shard_map.hpp"
|
|
||||||
#include "io/address.hpp"
|
|
||||||
#include "io/future.hpp"
|
|
||||||
#include "io/messages.hpp"
|
|
||||||
#include "io/rsm/raft.hpp"
|
|
||||||
#include "io/time.hpp"
|
|
||||||
#include "io/transport.hpp"
|
|
||||||
#include "query/v2/requests.hpp"
|
|
||||||
|
|
||||||
namespace memgraph::coordinator::coordinator_worker {
|
|
||||||
|
|
||||||
/// Obligations:
|
|
||||||
/// * ShutDown
|
|
||||||
/// * Cron
|
|
||||||
/// * RouteMessage
|
|
||||||
|
|
||||||
using coordinator::Coordinator;
|
|
||||||
using coordinator::CoordinatorRsm;
|
|
||||||
using io::Address;
|
|
||||||
using io::RequestId;
|
|
||||||
using io::Time;
|
|
||||||
using io::messages::CoordinatorMessages;
|
|
||||||
using msgs::ReadRequests;
|
|
||||||
using msgs::ReadResponses;
|
|
||||||
using msgs::WriteRequests;
|
|
||||||
using msgs::WriteResponses;
|
|
||||||
|
|
||||||
struct ShutDown {};
|
|
||||||
|
|
||||||
struct Cron {};
|
|
||||||
|
|
||||||
struct RouteMessage {
|
|
||||||
CoordinatorMessages message;
|
|
||||||
RequestId request_id;
|
|
||||||
Address to;
|
|
||||||
Address from;
|
|
||||||
};
|
|
||||||
|
|
||||||
using Message = std::variant<RouteMessage, Cron, ShutDown>;
|
|
||||||
|
|
||||||
struct QueueInner {
|
|
||||||
std::mutex mu{};
|
|
||||||
std::condition_variable cv;
|
|
||||||
// TODO(tyler) handle simulator communication std::shared_ptr<std::atomic<int>> blocked;
|
|
||||||
|
|
||||||
// TODO(tyler) investigate using a priority queue that prioritizes messages in a way that
|
|
||||||
// improves overall QoS. For example, maybe we want to schedule raft Append messages
|
|
||||||
// ahead of Read messages or generally writes before reads for lowering the load on the
|
|
||||||
// overall system faster etc... When we do this, we need to make sure to avoid
|
|
||||||
// starvation by sometimes randomizing priorities, rather than following a strict
|
|
||||||
// prioritization.
|
|
||||||
std::deque<Message> queue;
|
|
||||||
|
|
||||||
uint64_t submitted = 0;
|
|
||||||
uint64_t calls_to_pop = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// There are two reasons to implement our own Queue instead of using
|
|
||||||
/// one off-the-shelf:
|
|
||||||
/// 1. we will need to know in the simulator when all threads are waiting
|
|
||||||
/// 2. we will want to implement our own priority queue within this for QoS
|
|
||||||
class Queue {
|
|
||||||
std::shared_ptr<QueueInner> inner_ = std::make_shared<QueueInner>();
|
|
||||||
|
|
||||||
public:
|
|
||||||
void Push(Message &&message) {
|
|
||||||
{
|
|
||||||
MG_ASSERT(inner_.use_count() > 0);
|
|
||||||
std::unique_lock<std::mutex> lock(inner_->mu);
|
|
||||||
|
|
||||||
inner_->submitted++;
|
|
||||||
|
|
||||||
inner_->queue.emplace_back(std::move(message));
|
|
||||||
} // lock dropped before notifying condition variable
|
|
||||||
|
|
||||||
inner_->cv.notify_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
Message Pop() {
|
|
||||||
MG_ASSERT(inner_.use_count() > 0);
|
|
||||||
std::unique_lock<std::mutex> lock(inner_->mu);
|
|
||||||
|
|
||||||
inner_->calls_to_pop++;
|
|
||||||
inner_->cv.notify_all();
|
|
||||||
|
|
||||||
while (inner_->queue.empty()) {
|
|
||||||
inner_->cv.wait(lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
Message message = std::move(inner_->queue.front());
|
|
||||||
inner_->queue.pop_front();
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BlockOnQuiescence() const {
|
|
||||||
MG_ASSERT(inner_.use_count() > 0);
|
|
||||||
std::unique_lock<std::mutex> lock(inner_->mu);
|
|
||||||
|
|
||||||
while (inner_->calls_to_pop <= inner_->submitted) {
|
|
||||||
inner_->cv.wait(lock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A CoordinatorWorker owns Raft<CoordinatorRsm> instances. receives messages from the MachineManager.
|
|
||||||
template <typename IoImpl>
|
|
||||||
class CoordinatorWorker {
|
|
||||||
io::Io<IoImpl> io_;
|
|
||||||
Queue queue_;
|
|
||||||
CoordinatorRsm<IoImpl> coordinator_;
|
|
||||||
|
|
||||||
bool Process(ShutDown && /*shut_down*/) { return false; }
|
|
||||||
|
|
||||||
bool Process(Cron && /* cron */) {
|
|
||||||
coordinator_.Cron();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Process(RouteMessage &&route_message) {
|
|
||||||
coordinator_.Handle(std::move(route_message.message), route_message.request_id, route_message.from);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
CoordinatorWorker(io::Io<IoImpl> io, Queue queue, Coordinator coordinator)
|
|
||||||
: io_(std::move(io)), queue_(std::move(queue)), coordinator_{std::move(io_), {}, std::move(coordinator)} {}
|
|
||||||
|
|
||||||
CoordinatorWorker(CoordinatorWorker &&) noexcept = default;
|
|
||||||
CoordinatorWorker &operator=(CoordinatorWorker &&) noexcept = default;
|
|
||||||
CoordinatorWorker(const CoordinatorWorker &) = delete;
|
|
||||||
CoordinatorWorker &operator=(const CoordinatorWorker &) = delete;
|
|
||||||
~CoordinatorWorker() = default;
|
|
||||||
|
|
||||||
void Run() {
|
|
||||||
bool should_continue = true;
|
|
||||||
while (should_continue) {
|
|
||||||
Message message = queue_.Pop();
|
|
||||||
|
|
||||||
should_continue = std::visit([this](auto &&msg) { return this->Process(std::forward<decltype(msg)>(msg)); },
|
|
||||||
std::move(message));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace memgraph::coordinator::coordinator_worker
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <compare>
|
|
||||||
#include <ctime>
|
|
||||||
#include <iomanip>
|
|
||||||
|
|
||||||
#include "io/time.hpp"
|
|
||||||
|
|
||||||
namespace memgraph::coordinator {
|
|
||||||
|
|
||||||
using Time = memgraph::io::Time;
|
|
||||||
|
|
||||||
/// Hybrid-logical clock
|
|
||||||
struct Hlc {
|
|
||||||
uint64_t logical_id = 0;
|
|
||||||
Time coordinator_wall_clock = Time::min();
|
|
||||||
|
|
||||||
auto operator<=>(const Hlc &other) const { return logical_id <=> other.logical_id; }
|
|
||||||
|
|
||||||
bool operator==(const Hlc &other) const = default;
|
|
||||||
bool operator<(const Hlc &other) const = default;
|
|
||||||
bool operator==(const uint64_t other) const { return logical_id == other; }
|
|
||||||
bool operator<(const uint64_t other) const { return logical_id < other; }
|
|
||||||
bool operator>=(const uint64_t other) const { return logical_id >= other; }
|
|
||||||
|
|
||||||
friend std::ostream &operator<<(std::ostream &in, const Hlc &hlc) {
|
|
||||||
auto wall_clock = std::chrono::system_clock::to_time_t(hlc.coordinator_wall_clock);
|
|
||||||
in << "Hlc { logical_id: " << hlc.logical_id;
|
|
||||||
in << ", coordinator_wall_clock: " << std::put_time(std::localtime(&wall_clock), "%F %T");
|
|
||||||
in << " }";
|
|
||||||
|
|
||||||
return in;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace memgraph::coordinator
|
|
||||||
@@ -1,576 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
#include <optional>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "common/types.hpp"
|
|
||||||
#include "coordinator/shard_map.hpp"
|
|
||||||
#include "spdlog/spdlog.h"
|
|
||||||
#include "storage/v3/schemas.hpp"
|
|
||||||
#include "storage/v3/temporal.hpp"
|
|
||||||
#include "utils/cast.hpp"
|
|
||||||
#include "utils/exceptions.hpp"
|
|
||||||
#include "utils/string.hpp"
|
|
||||||
|
|
||||||
namespace memgraph::coordinator {
|
|
||||||
|
|
||||||
using memgraph::common::SchemaType;
|
|
||||||
using memgraph::storage::v3::TemporalData;
|
|
||||||
using memgraph::storage::v3::TemporalType;
|
|
||||||
|
|
||||||
PrimaryKey SchemaToMinKey(const std::vector<SchemaProperty> &schema) {
|
|
||||||
PrimaryKey ret{};
|
|
||||||
|
|
||||||
const int64_t min_int = std::numeric_limits<int64_t>::min();
|
|
||||||
|
|
||||||
const TemporalData date{TemporalType::Date, min_int};
|
|
||||||
const TemporalData local_time{TemporalType::LocalTime, min_int};
|
|
||||||
const TemporalData local_date_time{TemporalType::LocalDateTime, min_int};
|
|
||||||
const TemporalData duration{TemporalType::Duration, min_int};
|
|
||||||
|
|
||||||
for (const auto &schema_property : schema) {
|
|
||||||
switch (schema_property.type) {
|
|
||||||
case SchemaType::BOOL:
|
|
||||||
ret.emplace_back(PropertyValue(false));
|
|
||||||
break;
|
|
||||||
case SchemaType::INT:
|
|
||||||
ret.emplace_back(PropertyValue(min_int));
|
|
||||||
break;
|
|
||||||
case SchemaType::STRING:
|
|
||||||
ret.emplace_back(PropertyValue(""));
|
|
||||||
break;
|
|
||||||
case SchemaType::DATE:
|
|
||||||
ret.emplace_back(PropertyValue(date));
|
|
||||||
break;
|
|
||||||
case SchemaType::LOCALTIME:
|
|
||||||
ret.emplace_back(PropertyValue(local_time));
|
|
||||||
break;
|
|
||||||
case SchemaType::LOCALDATETIME:
|
|
||||||
ret.emplace_back(PropertyValue(local_date_time));
|
|
||||||
break;
|
|
||||||
case SchemaType::DURATION:
|
|
||||||
ret.emplace_back(PropertyValue(duration));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShardMap ShardMap::Parse(std::istream &input_stream) {
|
|
||||||
ShardMap shard_map;
|
|
||||||
const auto read_size = [&input_stream] {
|
|
||||||
size_t size{0};
|
|
||||||
input_stream >> size;
|
|
||||||
return size;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Reads a string until the next whitespace
|
|
||||||
const auto read_word = [&input_stream] {
|
|
||||||
std::string word;
|
|
||||||
input_stream >> word;
|
|
||||||
return word;
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto read_names = [&read_size, &read_word] {
|
|
||||||
const auto number_of_names = read_size();
|
|
||||||
spdlog::trace("Reading {} names", number_of_names);
|
|
||||||
std::vector<std::string> names;
|
|
||||||
names.reserve(number_of_names);
|
|
||||||
|
|
||||||
for (auto name_index = 0; name_index < number_of_names; ++name_index) {
|
|
||||||
names.push_back(read_word());
|
|
||||||
spdlog::trace("Read '{}'", names.back());
|
|
||||||
}
|
|
||||||
return names;
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto read_line = [&input_stream] {
|
|
||||||
std::string line;
|
|
||||||
std::getline(input_stream, line);
|
|
||||||
return line;
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto parse_type = [](const std::string &type) {
|
|
||||||
static const auto type_map = std::unordered_map<std::string, common::SchemaType>{
|
|
||||||
{"string", common::SchemaType::STRING}, {"int", common::SchemaType::INT}, {"bool", common::SchemaType::BOOL}};
|
|
||||||
const auto lower_case_type = utils::ToLowerCase(type);
|
|
||||||
auto it = type_map.find(lower_case_type);
|
|
||||||
MG_ASSERT(it != type_map.end(), "Invalid type in split files: {}", type);
|
|
||||||
return it->second;
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto parse_property_value = [](std::string text, const common::SchemaType type) {
|
|
||||||
if (type == common::SchemaType::STRING) {
|
|
||||||
return storage::v3::PropertyValue{std::move(text)};
|
|
||||||
}
|
|
||||||
if (type == common::SchemaType::INT) {
|
|
||||||
size_t processed{0};
|
|
||||||
int64_t value = std::stoll(text, &processed);
|
|
||||||
MG_ASSERT(processed == text.size() || text[processed] == ' ', "Invalid integer format: '{}'", text);
|
|
||||||
return storage::v3::PropertyValue{value};
|
|
||||||
}
|
|
||||||
LOG_FATAL("Not supported type: {}", utils::UnderlyingCast(type));
|
|
||||||
};
|
|
||||||
|
|
||||||
spdlog::debug("Reading properties");
|
|
||||||
const auto properties = read_names();
|
|
||||||
MG_ASSERT(shard_map.AllocatePropertyIds(properties).size() == properties.size(),
|
|
||||||
"Unexpected number of properties created!");
|
|
||||||
|
|
||||||
spdlog::debug("Reading edge types");
|
|
||||||
const auto edge_types = read_names();
|
|
||||||
MG_ASSERT(shard_map.AllocateEdgeTypeIds(edge_types).size() == edge_types.size(),
|
|
||||||
"Unexpected number of properties created!");
|
|
||||||
|
|
||||||
spdlog::debug("Reading primary labels");
|
|
||||||
const auto number_of_primary_labels = read_size();
|
|
||||||
spdlog::debug("Reading {} primary labels", number_of_primary_labels);
|
|
||||||
|
|
||||||
for (auto label_index = 0; label_index < number_of_primary_labels; ++label_index) {
|
|
||||||
const auto primary_label = read_word();
|
|
||||||
spdlog::debug("Reading primary label named '{}'", primary_label);
|
|
||||||
const auto number_of_primary_properties = read_size();
|
|
||||||
spdlog::debug("Reading {} primary properties", number_of_primary_properties);
|
|
||||||
std::vector<std::string> pp_names;
|
|
||||||
std::vector<common::SchemaType> pp_types;
|
|
||||||
pp_names.reserve(number_of_primary_properties);
|
|
||||||
pp_types.reserve(number_of_primary_properties);
|
|
||||||
for (auto property_index = 0; property_index < number_of_primary_properties; ++property_index) {
|
|
||||||
pp_names.push_back(read_word());
|
|
||||||
spdlog::debug("Reading primary property named '{}'", pp_names.back());
|
|
||||||
pp_types.push_back(parse_type(read_word()));
|
|
||||||
}
|
|
||||||
auto pp_mapping = shard_map.AllocatePropertyIds(pp_names);
|
|
||||||
std::vector<SchemaProperty> schema;
|
|
||||||
schema.reserve(number_of_primary_properties);
|
|
||||||
|
|
||||||
for (auto property_index = 0; property_index < number_of_primary_properties; ++property_index) {
|
|
||||||
schema.push_back(storage::v3::SchemaProperty{pp_mapping.at(pp_names[property_index]), pp_types[property_index]});
|
|
||||||
}
|
|
||||||
const auto hlc = shard_map.GetHlc();
|
|
||||||
MG_ASSERT(shard_map.InitializeNewLabel(primary_label, schema, 1, hlc).has_value(),
|
|
||||||
"Cannot initialize new label: {}", primary_label);
|
|
||||||
|
|
||||||
const auto number_of_split_points = read_size();
|
|
||||||
spdlog::debug("Reading {} split points", number_of_split_points);
|
|
||||||
|
|
||||||
[[maybe_unused]] const auto remainder_from_last_line = read_line();
|
|
||||||
for (auto split_point_index = 0; split_point_index < number_of_split_points; ++split_point_index) {
|
|
||||||
const auto line = read_line();
|
|
||||||
spdlog::debug("Read split point '{}'", line);
|
|
||||||
MG_ASSERT(line.front() == '[', "Invalid split file format!");
|
|
||||||
MG_ASSERT(line.back() == ']', "Invalid split file format!");
|
|
||||||
std::string_view line_view{line};
|
|
||||||
line_view.remove_prefix(1);
|
|
||||||
line_view.remove_suffix(1);
|
|
||||||
static constexpr std::string_view kDelimiter{","};
|
|
||||||
auto pk_values_as_text = utils::Split(line_view, kDelimiter);
|
|
||||||
std::vector<PropertyValue> pk;
|
|
||||||
pk.reserve(number_of_primary_properties);
|
|
||||||
MG_ASSERT(pk_values_as_text.size() == number_of_primary_properties,
|
|
||||||
"Split point contains invalid number of values '{}'", line);
|
|
||||||
|
|
||||||
for (auto property_index = 0; property_index < number_of_primary_properties; ++property_index) {
|
|
||||||
pk.push_back(parse_property_value(std::move(pk_values_as_text[property_index]), schema[property_index].type));
|
|
||||||
}
|
|
||||||
shard_map.SplitShard(shard_map.GetHlc(), shard_map.labels.at(primary_label), pk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return shard_map;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream &operator<<(std::ostream &in, const ShardMap &shard_map) {
|
|
||||||
using utils::print_helpers::operator<<;
|
|
||||||
|
|
||||||
in << "ShardMap { shard_map_version: " << shard_map.shard_map_version;
|
|
||||||
in << ", max_property_id: " << shard_map.max_property_id;
|
|
||||||
in << ", max_edge_type_id: " << shard_map.max_edge_type_id;
|
|
||||||
in << ", properties: " << shard_map.properties;
|
|
||||||
in << ", edge_types: " << shard_map.edge_types;
|
|
||||||
in << ", max_label_id: " << shard_map.max_label_id;
|
|
||||||
in << ", labels: " << shard_map.labels;
|
|
||||||
in << ", label_spaces: " << shard_map.label_spaces;
|
|
||||||
in << ", schemas: " << shard_map.schemas;
|
|
||||||
in << "}";
|
|
||||||
return in;
|
|
||||||
}
|
|
||||||
|
|
||||||
Shards ShardMap::GetShardsForLabel(const LabelName &label) const {
|
|
||||||
const auto id = labels.at(label);
|
|
||||||
const auto &shards = label_spaces.at(id).shards;
|
|
||||||
return shards;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Shards> ShardMap::GetAllShards() const {
|
|
||||||
std::vector<Shards> all_shards;
|
|
||||||
all_shards.reserve(label_spaces.size());
|
|
||||||
std::transform(label_spaces.begin(), label_spaces.end(), std::back_inserter(all_shards),
|
|
||||||
[](const auto &label_space) { return label_space.second.shards; });
|
|
||||||
return all_shards;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(gabor) later we will want to update the wallclock time with
|
|
||||||
// the given Io<impl>'s time as well
|
|
||||||
Hlc ShardMap::IncrementShardMapVersion() noexcept {
|
|
||||||
++shard_map_version.logical_id;
|
|
||||||
return shard_map_version;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(antaljanosbenjamin) use a single map for all name id
|
|
||||||
// mapping and a single counter to maintain the next id
|
|
||||||
std::unordered_map<uint64_t, std::string> ShardMap::IdToNames() {
|
|
||||||
std::unordered_map<uint64_t, std::string> id_to_names;
|
|
||||||
|
|
||||||
const auto map_type_ids = [&id_to_names](const auto &name_to_id_type) {
|
|
||||||
for (const auto &[name, id] : name_to_id_type) {
|
|
||||||
id_to_names.emplace(id.AsUint(), name);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
map_type_ids(edge_types);
|
|
||||||
map_type_ids(labels);
|
|
||||||
map_type_ids(properties);
|
|
||||||
|
|
||||||
return id_to_names;
|
|
||||||
}
|
|
||||||
|
|
||||||
Hlc ShardMap::GetHlc() const noexcept { return shard_map_version; }
|
|
||||||
|
|
||||||
boost::uuids::uuid NewShardUuid(uint64_t shard_id) {
|
|
||||||
return boost::uuids::uuid{0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
static_cast<unsigned char>(shard_id >> 56U),
|
|
||||||
static_cast<unsigned char>(shard_id >> 48U),
|
|
||||||
static_cast<unsigned char>(shard_id >> 40U),
|
|
||||||
static_cast<unsigned char>(shard_id >> 32U),
|
|
||||||
static_cast<unsigned char>(shard_id >> 24U),
|
|
||||||
static_cast<unsigned char>(shard_id >> 16U),
|
|
||||||
static_cast<unsigned char>(shard_id >> 8U),
|
|
||||||
static_cast<unsigned char>(shard_id)};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<ShardToInitialize> ShardMap::AssignShards(Address storage_manager,
|
|
||||||
std::set<boost::uuids::uuid> initialized) {
|
|
||||||
std::vector<ShardToInitialize> ret{};
|
|
||||||
|
|
||||||
bool mutated = false;
|
|
||||||
|
|
||||||
for (auto &[label_id, label_space] : label_spaces) {
|
|
||||||
for (auto it = label_space.shards.begin(); it != label_space.shards.end(); it++) {
|
|
||||||
auto &[low_key, shard] = *it;
|
|
||||||
std::optional<PrimaryKey> high_key;
|
|
||||||
if (const auto next_it = std::next(it); next_it != label_space.shards.end()) {
|
|
||||||
high_key = next_it->first;
|
|
||||||
}
|
|
||||||
// TODO(tyler) avoid these triple-nested loops by having the heartbeat include better info
|
|
||||||
bool machine_contains_shard = false;
|
|
||||||
|
|
||||||
for (auto &aas : shard.peers) {
|
|
||||||
if (initialized.contains(aas.address.unique_id)) {
|
|
||||||
machine_contains_shard = true;
|
|
||||||
if (aas.status != Status::CONSENSUS_PARTICIPANT) {
|
|
||||||
mutated = true;
|
|
||||||
spdlog::info("marking shard as full consensus participant: {}", aas.address.unique_id);
|
|
||||||
aas.status = Status::CONSENSUS_PARTICIPANT;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const bool same_machine = aas.address.last_known_ip == storage_manager.last_known_ip &&
|
|
||||||
aas.address.last_known_port == storage_manager.last_known_port;
|
|
||||||
if (same_machine) {
|
|
||||||
machine_contains_shard = true;
|
|
||||||
spdlog::info("reminding shard manager that they should begin participating in shard");
|
|
||||||
|
|
||||||
ret.push_back(ShardToInitialize{
|
|
||||||
.uuid = aas.address.unique_id,
|
|
||||||
.label_id = label_id,
|
|
||||||
.min_key = low_key,
|
|
||||||
.max_key = high_key,
|
|
||||||
.schema = schemas[label_id],
|
|
||||||
.config = Config{},
|
|
||||||
.id_to_names = IdToNames(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!machine_contains_shard && shard.peers.size() < label_space.replication_factor) {
|
|
||||||
// increment version for each new uuid for deterministic creation
|
|
||||||
IncrementShardMapVersion();
|
|
||||||
|
|
||||||
Address address = storage_manager;
|
|
||||||
|
|
||||||
// TODO(tyler) use deterministic UUID so that coordinators don't diverge here
|
|
||||||
address.unique_id = NewShardUuid(shard_map_version.logical_id);
|
|
||||||
|
|
||||||
spdlog::info("assigning shard manager to shard");
|
|
||||||
|
|
||||||
ret.push_back(ShardToInitialize{
|
|
||||||
.uuid = address.unique_id,
|
|
||||||
.label_id = label_id,
|
|
||||||
.min_key = low_key,
|
|
||||||
.max_key = high_key,
|
|
||||||
.schema = schemas[label_id],
|
|
||||||
.config = Config{},
|
|
||||||
.id_to_names = IdToNames(),
|
|
||||||
});
|
|
||||||
|
|
||||||
AddressAndStatus aas = {
|
|
||||||
.address = address,
|
|
||||||
.status = Status::INITIALIZING,
|
|
||||||
};
|
|
||||||
|
|
||||||
shard.peers.emplace_back(aas);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mutated) {
|
|
||||||
IncrementShardMapVersion();
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShardMap::SplitShard(Hlc previous_shard_map_version, LabelId label_id, const PrimaryKey &key) {
|
|
||||||
if (previous_shard_map_version != shard_map_version) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto &label_space = label_spaces.at(label_id);
|
|
||||||
auto &shards_in_map = label_space.shards;
|
|
||||||
|
|
||||||
MG_ASSERT(!shards_in_map.empty());
|
|
||||||
MG_ASSERT(!shards_in_map.contains(key));
|
|
||||||
MG_ASSERT(label_spaces.contains(label_id));
|
|
||||||
|
|
||||||
// Finding the ShardMetadata that the new PrimaryKey should map to.
|
|
||||||
auto prev = std::prev(shards_in_map.upper_bound(key));
|
|
||||||
ShardMetadata duplicated_shard = prev->second;
|
|
||||||
|
|
||||||
// Apply the split
|
|
||||||
shards_in_map[key] = duplicated_shard;
|
|
||||||
|
|
||||||
IncrementShardMapVersion();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<LabelId> ShardMap::InitializeNewLabel(std::string label_name, std::vector<SchemaProperty> schema,
|
|
||||||
size_t replication_factor, Hlc last_shard_map_version) {
|
|
||||||
if (shard_map_version != last_shard_map_version || labels.contains(label_name)) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const LabelId label_id = LabelId::FromUint(++max_label_id);
|
|
||||||
|
|
||||||
labels.emplace(std::move(label_name), label_id);
|
|
||||||
|
|
||||||
PrimaryKey initial_key = SchemaToMinKey(schema);
|
|
||||||
ShardMetadata empty_shard = {};
|
|
||||||
|
|
||||||
Shards shards = {
|
|
||||||
{initial_key, empty_shard},
|
|
||||||
};
|
|
||||||
|
|
||||||
LabelSpace label_space{
|
|
||||||
.schema = schema,
|
|
||||||
.shards = shards,
|
|
||||||
.replication_factor = replication_factor,
|
|
||||||
};
|
|
||||||
schemas[label_id] = std::move(schema);
|
|
||||||
|
|
||||||
label_spaces.emplace(label_id, label_space);
|
|
||||||
|
|
||||||
IncrementShardMapVersion();
|
|
||||||
|
|
||||||
return label_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShardMap::AddServer(Address server_address) {
|
|
||||||
// Find a random place for the server to plug in
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<LabelId> ShardMap::GetLabelId(const std::string &label) const {
|
|
||||||
if (const auto it = labels.find(label); it != labels.end()) {
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string &ShardMap::GetLabelName(const LabelId label) const {
|
|
||||||
if (const auto it =
|
|
||||||
std::ranges::find_if(labels, [label](const auto &name_id_pair) { return name_id_pair.second == label; });
|
|
||||||
it != labels.end()) {
|
|
||||||
return it->first;
|
|
||||||
}
|
|
||||||
throw utils::BasicException("GetLabelName fails on the given label id!");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<PropertyId> ShardMap::GetPropertyId(const std::string &property_name) const {
|
|
||||||
if (const auto it = properties.find(property_name); it != properties.end()) {
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string &ShardMap::GetPropertyName(const PropertyId property) const {
|
|
||||||
if (const auto it = std::ranges::find_if(
|
|
||||||
properties, [property](const auto &name_id_pair) { return name_id_pair.second == property; });
|
|
||||||
it != properties.end()) {
|
|
||||||
return it->first;
|
|
||||||
}
|
|
||||||
throw utils::BasicException("PropertyId not found!");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<EdgeTypeId> ShardMap::GetEdgeTypeId(const std::string &edge_type) const {
|
|
||||||
if (const auto it = edge_types.find(edge_type); it != edge_types.end()) {
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string &ShardMap::GetEdgeTypeName(const EdgeTypeId property) const {
|
|
||||||
if (const auto it = std::ranges::find_if(
|
|
||||||
edge_types, [property](const auto &name_id_pair) { return name_id_pair.second == property; });
|
|
||||||
it != edge_types.end()) {
|
|
||||||
return it->first;
|
|
||||||
}
|
|
||||||
throw utils::BasicException("EdgeTypeId not found!");
|
|
||||||
}
|
|
||||||
|
|
||||||
Shards ShardMap::GetShardsForRange(const LabelName &label_name, const PrimaryKey &start_key,
|
|
||||||
const PrimaryKey &end_key) const {
|
|
||||||
MG_ASSERT(start_key <= end_key);
|
|
||||||
MG_ASSERT(labels.contains(label_name));
|
|
||||||
|
|
||||||
LabelId label_id = labels.at(label_name);
|
|
||||||
|
|
||||||
const auto &label_space = label_spaces.at(label_id);
|
|
||||||
|
|
||||||
const auto &shards_for_label = label_space.shards;
|
|
||||||
|
|
||||||
MG_ASSERT(shards_for_label.begin()->first <= start_key,
|
|
||||||
"the ShardMap must always contain a minimal key that is less than or equal to any requested key");
|
|
||||||
|
|
||||||
auto it = std::prev(shards_for_label.upper_bound(start_key));
|
|
||||||
const auto end_it = shards_for_label.upper_bound(end_key);
|
|
||||||
|
|
||||||
Shards shards{};
|
|
||||||
|
|
||||||
std::copy(it, end_it, std::inserter(shards, shards.end()));
|
|
||||||
|
|
||||||
return shards;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShardMetadata ShardMap::GetShardForKey(const LabelName &label_name, const PrimaryKey &key) const {
|
|
||||||
MG_ASSERT(labels.contains(label_name));
|
|
||||||
|
|
||||||
LabelId label_id = labels.at(label_name);
|
|
||||||
|
|
||||||
const auto &label_space = label_spaces.at(label_id);
|
|
||||||
|
|
||||||
MG_ASSERT(label_space.shards.begin()->first <= key,
|
|
||||||
"the ShardMap must always contain a minimal key that is less than or equal to any requested key");
|
|
||||||
|
|
||||||
return std::prev(label_space.shards.upper_bound(key))->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShardMetadata ShardMap::GetShardForKey(const LabelId &label_id, const PrimaryKey &key) const {
|
|
||||||
MG_ASSERT(label_spaces.contains(label_id));
|
|
||||||
|
|
||||||
const auto &label_space = label_spaces.at(label_id);
|
|
||||||
|
|
||||||
MG_ASSERT(label_space.shards.begin()->first <= key,
|
|
||||||
"the ShardMap must always contain a minimal key that is less than or equal to any requested key");
|
|
||||||
|
|
||||||
return std::prev(label_space.shards.upper_bound(key))->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
PropertyMap ShardMap::AllocatePropertyIds(const std::vector<PropertyName> &new_properties) {
|
|
||||||
PropertyMap ret{};
|
|
||||||
|
|
||||||
bool mutated = false;
|
|
||||||
|
|
||||||
for (const auto &property_name : new_properties) {
|
|
||||||
if (properties.contains(property_name)) {
|
|
||||||
auto property_id = properties.at(property_name);
|
|
||||||
ret.emplace(property_name, property_id);
|
|
||||||
} else {
|
|
||||||
mutated = true;
|
|
||||||
|
|
||||||
const PropertyId property_id = PropertyId::FromUint(++max_property_id);
|
|
||||||
ret.emplace(property_name, property_id);
|
|
||||||
properties.emplace(property_name, property_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mutated) {
|
|
||||||
IncrementShardMapVersion();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
EdgeTypeIdMap ShardMap::AllocateEdgeTypeIds(const std::vector<EdgeTypeName> &new_edge_types) {
|
|
||||||
EdgeTypeIdMap ret;
|
|
||||||
|
|
||||||
bool mutated = false;
|
|
||||||
|
|
||||||
for (const auto &edge_type_name : new_edge_types) {
|
|
||||||
if (edge_types.contains(edge_type_name)) {
|
|
||||||
auto edge_type_id = edge_types.at(edge_type_name);
|
|
||||||
ret.emplace(edge_type_name, edge_type_id);
|
|
||||||
} else {
|
|
||||||
mutated = true;
|
|
||||||
|
|
||||||
const EdgeTypeId edge_type_id = EdgeTypeId::FromUint(++max_edge_type_id);
|
|
||||||
ret.emplace(edge_type_name, edge_type_id);
|
|
||||||
edge_types.emplace(edge_type_name, edge_type_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mutated) {
|
|
||||||
IncrementShardMapVersion();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShardMap::ClusterInitialized() const {
|
|
||||||
for (const auto &[label_id, label_space] : label_spaces) {
|
|
||||||
for (const auto &[low_key, shard] : label_space.shards) {
|
|
||||||
if (shard.peers.size() < label_space.replication_factor) {
|
|
||||||
spdlog::info("label_space below desired replication factor");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &aas : shard.peers) {
|
|
||||||
if (aas.status != Status::CONSENSUS_PARTICIPANT) {
|
|
||||||
spdlog::info("shard member not yet a CONSENSUS_PARTICIPANT");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace memgraph::coordinator
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <limits>
|
|
||||||
#include <map>
|
|
||||||
#include <set>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <boost/uuid/uuid.hpp>
|
|
||||||
#include <boost/uuid/uuid_generators.hpp>
|
|
||||||
|
|
||||||
#include "common/types.hpp"
|
|
||||||
#include "coordinator/hybrid_logical_clock.hpp"
|
|
||||||
#include "io/address.hpp"
|
|
||||||
#include "storage/v3/config.hpp"
|
|
||||||
#include "storage/v3/id_types.hpp"
|
|
||||||
#include "storage/v3/name_id_mapper.hpp"
|
|
||||||
#include "storage/v3/property_value.hpp"
|
|
||||||
#include "storage/v3/schemas.hpp"
|
|
||||||
#include "storage/v3/temporal.hpp"
|
|
||||||
#include "utils/exceptions.hpp"
|
|
||||||
#include "utils/print_helpers.hpp"
|
|
||||||
|
|
||||||
namespace memgraph::coordinator {
|
|
||||||
|
|
||||||
constexpr int64_t kNotExistingId{0};
|
|
||||||
|
|
||||||
using memgraph::io::Address;
|
|
||||||
using memgraph::storage::v3::Config;
|
|
||||||
using memgraph::storage::v3::EdgeTypeId;
|
|
||||||
using memgraph::storage::v3::LabelId;
|
|
||||||
using memgraph::storage::v3::PropertyId;
|
|
||||||
using memgraph::storage::v3::PropertyValue;
|
|
||||||
using memgraph::storage::v3::SchemaProperty;
|
|
||||||
|
|
||||||
enum class Status : uint8_t {
|
|
||||||
CONSENSUS_PARTICIPANT,
|
|
||||||
INITIALIZING,
|
|
||||||
// TODO(tyler) this will possibly have more states,
|
|
||||||
// depending on the reconfiguration protocol that we
|
|
||||||
// implement.
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AddressAndStatus {
|
|
||||||
memgraph::io::Address address;
|
|
||||||
Status status;
|
|
||||||
|
|
||||||
friend bool operator<(const AddressAndStatus &lhs, const AddressAndStatus &rhs) { return lhs.address < rhs.address; }
|
|
||||||
|
|
||||||
friend std::ostream &operator<<(std::ostream &in, const AddressAndStatus &address_and_status) {
|
|
||||||
in << "AddressAndStatus { address: ";
|
|
||||||
in << address_and_status.address;
|
|
||||||
if (address_and_status.status == Status::CONSENSUS_PARTICIPANT) {
|
|
||||||
in << ", status: CONSENSUS_PARTICIPANT }";
|
|
||||||
} else {
|
|
||||||
in << ", status: INITIALIZING }";
|
|
||||||
}
|
|
||||||
|
|
||||||
return in;
|
|
||||||
}
|
|
||||||
|
|
||||||
friend bool operator==(const AddressAndStatus &lhs, const AddressAndStatus &rhs) {
|
|
||||||
return lhs.address == rhs.address;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using PrimaryKey = std::vector<PropertyValue>;
|
|
||||||
|
|
||||||
struct ShardMetadata {
|
|
||||||
std::vector<AddressAndStatus> peers;
|
|
||||||
uint64_t version;
|
|
||||||
|
|
||||||
friend std::ostream &operator<<(std::ostream &in, const ShardMetadata &shard) {
|
|
||||||
using utils::print_helpers::operator<<;
|
|
||||||
|
|
||||||
in << "ShardMetadata { peers: ";
|
|
||||||
in << shard.peers;
|
|
||||||
in << " version: ";
|
|
||||||
in << shard.version;
|
|
||||||
in << " }";
|
|
||||||
|
|
||||||
return in;
|
|
||||||
}
|
|
||||||
|
|
||||||
friend bool operator==(const ShardMetadata &lhs, const ShardMetadata &rhs) = default;
|
|
||||||
|
|
||||||
friend bool operator<(const ShardMetadata &lhs, const ShardMetadata &rhs) {
|
|
||||||
if (lhs.peers != rhs.peers) {
|
|
||||||
return lhs.peers < rhs.peers;
|
|
||||||
}
|
|
||||||
|
|
||||||
return lhs.version < rhs.version;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using Shards = std::map<PrimaryKey, ShardMetadata>;
|
|
||||||
using LabelName = std::string;
|
|
||||||
using PropertyName = std::string;
|
|
||||||
using EdgeTypeName = std::string;
|
|
||||||
using PropertyMap = std::map<PropertyName, PropertyId>;
|
|
||||||
using EdgeTypeIdMap = std::map<EdgeTypeName, EdgeTypeId>;
|
|
||||||
|
|
||||||
struct ShardToInitialize {
|
|
||||||
boost::uuids::uuid uuid;
|
|
||||||
LabelId label_id;
|
|
||||||
PrimaryKey min_key;
|
|
||||||
std::optional<PrimaryKey> max_key;
|
|
||||||
std::vector<SchemaProperty> schema;
|
|
||||||
Config config;
|
|
||||||
std::unordered_map<uint64_t, std::string> id_to_names;
|
|
||||||
};
|
|
||||||
|
|
||||||
PrimaryKey SchemaToMinKey(const std::vector<SchemaProperty> &schema);
|
|
||||||
|
|
||||||
struct LabelSpace {
|
|
||||||
std::vector<SchemaProperty> schema;
|
|
||||||
// Maps between the smallest primary key stored in the shard and the shard
|
|
||||||
std::map<PrimaryKey, ShardMetadata> shards;
|
|
||||||
size_t replication_factor;
|
|
||||||
|
|
||||||
friend std::ostream &operator<<(std::ostream &in, const LabelSpace &label_space) {
|
|
||||||
using utils::print_helpers::operator<<;
|
|
||||||
|
|
||||||
in << "LabelSpace { schema: ";
|
|
||||||
in << label_space.schema;
|
|
||||||
in << ", shards: ";
|
|
||||||
in << label_space.shards;
|
|
||||||
in << ", replication_factor: " << label_space.replication_factor << "}";
|
|
||||||
|
|
||||||
return in;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ShardMap {
|
|
||||||
Hlc shard_map_version;
|
|
||||||
uint64_t max_property_id{kNotExistingId};
|
|
||||||
uint64_t max_edge_type_id{kNotExistingId};
|
|
||||||
std::map<PropertyName, PropertyId> properties;
|
|
||||||
std::map<EdgeTypeName, EdgeTypeId> edge_types;
|
|
||||||
uint64_t max_label_id{kNotExistingId};
|
|
||||||
std::map<LabelId, LabelSpace> label_spaces;
|
|
||||||
std::map<LabelId, std::vector<SchemaProperty>> schemas;
|
|
||||||
std::map<LabelName, LabelId> labels;
|
|
||||||
|
|
||||||
[[nodiscard]] static ShardMap Parse(std::istream &input_stream);
|
|
||||||
friend std::ostream &operator<<(std::ostream &in, const ShardMap &shard_map);
|
|
||||||
|
|
||||||
Shards GetShardsForLabel(const LabelName &label) const;
|
|
||||||
|
|
||||||
std::vector<Shards> GetAllShards() const;
|
|
||||||
|
|
||||||
// TODO(gabor) later we will want to update the wallclock time with
|
|
||||||
// the given Io<impl>'s time as well
|
|
||||||
Hlc IncrementShardMapVersion() noexcept;
|
|
||||||
Hlc GetHlc() const noexcept;
|
|
||||||
|
|
||||||
std::unordered_map<uint64_t, std::string> IdToNames();
|
|
||||||
|
|
||||||
// Returns the shard UUIDs that have been assigned but not yet acknowledged for this storage manager
|
|
||||||
std::vector<ShardToInitialize> AssignShards(Address storage_manager, std::set<boost::uuids::uuid> initialized);
|
|
||||||
|
|
||||||
bool SplitShard(Hlc previous_shard_map_version, LabelId label_id, const PrimaryKey &key);
|
|
||||||
|
|
||||||
std::optional<LabelId> InitializeNewLabel(std::string label_name, std::vector<SchemaProperty> schema,
|
|
||||||
size_t replication_factor, Hlc last_shard_map_version);
|
|
||||||
|
|
||||||
void AddServer(Address server_address);
|
|
||||||
|
|
||||||
std::optional<LabelId> GetLabelId(const std::string &label) const;
|
|
||||||
// TODO(antaljanosbenjamin): Remove this and instead use NameIdMapper
|
|
||||||
const std::string &GetLabelName(LabelId label) const;
|
|
||||||
std::optional<PropertyId> GetPropertyId(const std::string &property_name) const;
|
|
||||||
const std::string &GetPropertyName(PropertyId property) const;
|
|
||||||
std::optional<EdgeTypeId> GetEdgeTypeId(const std::string &edge_type) const;
|
|
||||||
const std::string &GetEdgeTypeName(EdgeTypeId property) const;
|
|
||||||
|
|
||||||
Shards GetShardsForRange(const LabelName &label_name, const PrimaryKey &start_key, const PrimaryKey &end_key) const;
|
|
||||||
|
|
||||||
ShardMetadata GetShardForKey(const LabelName &label_name, const PrimaryKey &key) const;
|
|
||||||
|
|
||||||
ShardMetadata GetShardForKey(const LabelId &label_id, const PrimaryKey &key) const;
|
|
||||||
|
|
||||||
PropertyMap AllocatePropertyIds(const std::vector<PropertyName> &new_properties);
|
|
||||||
|
|
||||||
EdgeTypeIdMap AllocateEdgeTypeIds(const std::vector<EdgeTypeName> &new_edge_types);
|
|
||||||
|
|
||||||
/// Returns true if all shards have the desired number of replicas and they are in
|
|
||||||
/// the CONSENSUS_PARTICIPANT state. Note that this does not necessarily mean that
|
|
||||||
/// there is also an active leader for each shard.
|
|
||||||
bool ClusterInitialized() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace memgraph::coordinator
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
define_add_lcp(add_lcp_expr lcp_expr_cpp_files generated_lcp_expr_files)
|
|
||||||
|
|
||||||
add_lcp_expr(semantic/symbol.lcp)
|
|
||||||
|
|
||||||
add_custom_target(generate_lcp_expr DEPENDS ${generated_lcp_expr_files})
|
|
||||||
|
|
||||||
set(mg_expr_sources
|
|
||||||
${lcp_expr_cpp_files}
|
|
||||||
parsing.cpp)
|
|
||||||
|
|
||||||
find_package(Boost REQUIRED)
|
|
||||||
|
|
||||||
add_library(mg-expr STATIC ${mg_expr_sources})
|
|
||||||
add_dependencies(mg-expr generate_lcp_expr)
|
|
||||||
target_include_directories(mg-expr PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
|
||||||
target_include_directories(mg-expr PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
|
||||||
target_include_directories(mg-expr PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/ast)
|
|
||||||
target_include_directories(mg-expr PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/interpret)
|
|
||||||
target_include_directories(mg-expr PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/semantic)
|
|
||||||
target_link_libraries(mg-expr cppitertools Boost::headers mg-utils mg-parser mg-functions)
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#ifndef MG_AST_INCLUDE_PATH
|
|
||||||
#ifdef MG_CLANG_TIDY_CHECK
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
|
||||||
#include "query/v2/bindings/bindings.hpp"
|
|
||||||
#else
|
|
||||||
#error Missing AST include path
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef MG_INJECTED_NAMESPACE_NAME
|
|
||||||
#ifdef MG_CLANG_TIDY_CHECK
|
|
||||||
#else
|
|
||||||
#error Missing AST namespace
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include MG_AST_INCLUDE_PATH
|
|
||||||
|
|
||||||
namespace memgraph::expr {
|
|
||||||
using namespace MG_INJECTED_NAMESPACE_NAME; // NOLINT(google-build-using-namespace)
|
|
||||||
} // namespace memgraph::expr
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "utils/visitor.hpp"
|
|
||||||
|
|
||||||
namespace MG_INJECTED_NAMESPACE_NAME {
|
|
||||||
|
|
||||||
// Forward declares for Tree visitors.
|
|
||||||
class CypherQuery;
|
|
||||||
class SingleQuery;
|
|
||||||
class CypherUnion;
|
|
||||||
class NamedExpression;
|
|
||||||
class Identifier;
|
|
||||||
class PropertyLookup;
|
|
||||||
class LabelsTest;
|
|
||||||
class Aggregation;
|
|
||||||
class Function;
|
|
||||||
class Reduce;
|
|
||||||
class Coalesce;
|
|
||||||
class Extract;
|
|
||||||
class All;
|
|
||||||
class Single;
|
|
||||||
class Any;
|
|
||||||
class None;
|
|
||||||
class ParameterLookup;
|
|
||||||
class CallProcedure;
|
|
||||||
class Create;
|
|
||||||
class Match;
|
|
||||||
class Return;
|
|
||||||
class With;
|
|
||||||
class Pattern;
|
|
||||||
class NodeAtom;
|
|
||||||
class EdgeAtom;
|
|
||||||
class PrimitiveLiteral;
|
|
||||||
class ListLiteral;
|
|
||||||
class MapLiteral;
|
|
||||||
class OrOperator;
|
|
||||||
class XorOperator;
|
|
||||||
class AndOperator;
|
|
||||||
class NotOperator;
|
|
||||||
class AdditionOperator;
|
|
||||||
class SubtractionOperator;
|
|
||||||
class MultiplicationOperator;
|
|
||||||
class DivisionOperator;
|
|
||||||
class ModOperator;
|
|
||||||
class UnaryPlusOperator;
|
|
||||||
class UnaryMinusOperator;
|
|
||||||
class IsNullOperator;
|
|
||||||
class NotEqualOperator;
|
|
||||||
class EqualOperator;
|
|
||||||
class LessOperator;
|
|
||||||
class GreaterOperator;
|
|
||||||
class LessEqualOperator;
|
|
||||||
class GreaterEqualOperator;
|
|
||||||
class InListOperator;
|
|
||||||
class SubscriptOperator;
|
|
||||||
class ListSlicingOperator;
|
|
||||||
class IfOperator;
|
|
||||||
class Delete;
|
|
||||||
class Where;
|
|
||||||
class SetProperty;
|
|
||||||
class SetProperties;
|
|
||||||
class SetLabels;
|
|
||||||
class RemoveProperty;
|
|
||||||
class RemoveLabels;
|
|
||||||
class Merge;
|
|
||||||
class Unwind;
|
|
||||||
class AuthQuery;
|
|
||||||
class ExplainQuery;
|
|
||||||
class ProfileQuery;
|
|
||||||
class IndexQuery;
|
|
||||||
class InfoQuery;
|
|
||||||
class ConstraintQuery;
|
|
||||||
class RegexMatch;
|
|
||||||
class DumpQuery;
|
|
||||||
class ReplicationQuery;
|
|
||||||
class LockPathQuery;
|
|
||||||
class LoadCsv;
|
|
||||||
class FreeMemoryQuery;
|
|
||||||
class TriggerQuery;
|
|
||||||
class IsolationLevelQuery;
|
|
||||||
class CreateSnapshotQuery;
|
|
||||||
class StreamQuery;
|
|
||||||
class SettingQuery;
|
|
||||||
class VersionQuery;
|
|
||||||
class Foreach;
|
|
||||||
class SchemaQuery;
|
|
||||||
|
|
||||||
using TreeCompositeVisitor = memgraph::utils::CompositeVisitor<
|
|
||||||
SingleQuery, CypherUnion, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator, AdditionOperator,
|
|
||||||
SubtractionOperator, MultiplicationOperator, DivisionOperator, ModOperator, NotEqualOperator, EqualOperator,
|
|
||||||
LessOperator, GreaterOperator, LessEqualOperator, GreaterEqualOperator, InListOperator, SubscriptOperator,
|
|
||||||
ListSlicingOperator, IfOperator, UnaryPlusOperator, UnaryMinusOperator, IsNullOperator, ListLiteral, MapLiteral,
|
|
||||||
PropertyLookup, LabelsTest, Aggregation, Function, Reduce, Coalesce, Extract, All, Single, Any, None, CallProcedure,
|
|
||||||
Create, Match, Return, With, Pattern, NodeAtom, EdgeAtom, Delete, Where, SetProperty, SetProperties, SetLabels,
|
|
||||||
RemoveProperty, RemoveLabels, Merge, Unwind, RegexMatch, LoadCsv, Foreach>;
|
|
||||||
|
|
||||||
using TreeLeafVisitor = memgraph::utils::LeafVisitor<Identifier, PrimitiveLiteral, ParameterLookup>;
|
|
||||||
|
|
||||||
class HierarchicalTreeVisitor : public TreeCompositeVisitor, public TreeLeafVisitor {
|
|
||||||
public:
|
|
||||||
using TreeCompositeVisitor::PostVisit;
|
|
||||||
using TreeCompositeVisitor::PreVisit;
|
|
||||||
using TreeLeafVisitor::Visit;
|
|
||||||
using typename TreeLeafVisitor::ReturnType;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <class TResult>
|
|
||||||
class ExpressionVisitor
|
|
||||||
: public memgraph::utils::Visitor<
|
|
||||||
TResult, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator, AdditionOperator,
|
|
||||||
SubtractionOperator, MultiplicationOperator, DivisionOperator, ModOperator, NotEqualOperator, EqualOperator,
|
|
||||||
LessOperator, GreaterOperator, LessEqualOperator, GreaterEqualOperator, InListOperator, SubscriptOperator,
|
|
||||||
ListSlicingOperator, IfOperator, UnaryPlusOperator, UnaryMinusOperator, IsNullOperator, ListLiteral,
|
|
||||||
MapLiteral, PropertyLookup, LabelsTest, Aggregation, Function, Reduce, Coalesce, Extract, All, Single, Any,
|
|
||||||
None, ParameterLookup, Identifier, PrimitiveLiteral, RegexMatch> {};
|
|
||||||
|
|
||||||
template <class TResult>
|
|
||||||
class QueryVisitor
|
|
||||||
: public memgraph::utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery,
|
|
||||||
InfoQuery, ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery,
|
|
||||||
FreeMemoryQuery, TriggerQuery, IsolationLevelQuery, CreateSnapshotQuery,
|
|
||||||
StreamQuery, SettingQuery, VersionQuery, SchemaQuery> {};
|
|
||||||
|
|
||||||
} // namespace MG_INJECTED_NAMESPACE_NAME
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,271 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <type_traits>
|
|
||||||
|
|
||||||
#include "expr/ast.hpp"
|
|
||||||
#include "expr/typed_value.hpp"
|
|
||||||
#include "utils/algorithm.hpp"
|
|
||||||
#include "utils/logging.hpp"
|
|
||||||
#include "utils/string.hpp"
|
|
||||||
|
|
||||||
namespace memgraph::expr {
|
|
||||||
namespace detail {
|
|
||||||
template <typename T>
|
|
||||||
void PrintObject(std::ostream *out, const T &arg) {
|
|
||||||
static_assert(!std::is_convertible<T, Expression *>::value,
|
|
||||||
"This overload shouldn't be called with pointers convertible "
|
|
||||||
"to Expression *. This means your other PrintObject overloads aren't "
|
|
||||||
"being called for certain AST nodes when they should (or perhaps such "
|
|
||||||
"overloads don't exist yet).");
|
|
||||||
*out << arg;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void PrintObject(std::ostream *out, const std::string &str) { *out << utils::Escape(str); }
|
|
||||||
|
|
||||||
inline void PrintObject(std::ostream *out, Aggregation::Op op) { *out << Aggregation::OpToString(op); }
|
|
||||||
|
|
||||||
inline void PrintObject(std::ostream *out, Expression *expr);
|
|
||||||
|
|
||||||
inline void PrintObject(std::ostream *out, Identifier *expr) { PrintObject(out, static_cast<Expression *>(expr)); }
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void PrintObject(std::ostream *out, const std::vector<T> &vec) {
|
|
||||||
*out << "[";
|
|
||||||
utils::PrintIterable(*out, vec, ", ", [](auto &stream, const auto &item) { PrintObject(&stream, item); });
|
|
||||||
*out << "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void PrintObject(std::ostream *out, const std::vector<T, utils::Allocator<T>> &vec) {
|
|
||||||
*out << "[";
|
|
||||||
utils::PrintIterable(*out, vec, ", ", [](auto &stream, const auto &item) { PrintObject(&stream, item); });
|
|
||||||
*out << "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename K, typename V>
|
|
||||||
void PrintObject(std::ostream *out, const std::map<K, V> &map) {
|
|
||||||
*out << "{";
|
|
||||||
utils::PrintIterable(*out, map, ", ", [](auto &stream, const auto &item) {
|
|
||||||
PrintObject(&stream, item.first);
|
|
||||||
stream << ": ";
|
|
||||||
PrintObject(&stream, item.second);
|
|
||||||
});
|
|
||||||
*out << "}";
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void PrintObject(std::ostream *out, const utils::pmr::map<utils::pmr::string, T> &map) {
|
|
||||||
*out << "{";
|
|
||||||
utils::PrintIterable(*out, map, ", ", [](auto &stream, const auto &item) {
|
|
||||||
PrintObject(&stream, item.first);
|
|
||||||
stream << ": ";
|
|
||||||
PrintObject(&stream, item.second);
|
|
||||||
});
|
|
||||||
*out << "}";
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T1, typename T2, typename T3>
|
|
||||||
inline void PrintObject(std::ostream *out, const TypedValueT<T1, T2, T3> &value) {
|
|
||||||
using TypedValue = TypedValueT<T1, T2, T3>;
|
|
||||||
switch (value.type()) {
|
|
||||||
case TypedValue::Type::Null:
|
|
||||||
*out << "null";
|
|
||||||
break;
|
|
||||||
case TypedValue::Type::String:
|
|
||||||
PrintObject(out, value.ValueString());
|
|
||||||
break;
|
|
||||||
case TypedValue::Type::Bool:
|
|
||||||
*out << (value.ValueBool() ? "true" : "false");
|
|
||||||
break;
|
|
||||||
case TypedValue::Type::Int:
|
|
||||||
PrintObject(out, value.ValueInt());
|
|
||||||
break;
|
|
||||||
case TypedValue::Type::Double:
|
|
||||||
PrintObject(out, value.ValueDouble());
|
|
||||||
break;
|
|
||||||
case TypedValue::Type::List:
|
|
||||||
PrintObject(out, value.ValueList());
|
|
||||||
break;
|
|
||||||
case TypedValue::Type::Map:
|
|
||||||
PrintObject(out, value.ValueMap());
|
|
||||||
break;
|
|
||||||
case TypedValue::Type::Date:
|
|
||||||
PrintObject(out, value.ValueDate());
|
|
||||||
break;
|
|
||||||
case TypedValue::Type::Duration:
|
|
||||||
PrintObject(out, value.ValueDuration());
|
|
||||||
break;
|
|
||||||
case TypedValue::Type::LocalTime:
|
|
||||||
PrintObject(out, value.ValueLocalTime());
|
|
||||||
break;
|
|
||||||
case TypedValue::Type::LocalDateTime:
|
|
||||||
PrintObject(out, value.ValueLocalDateTime());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
MG_ASSERT(false, "PrintObject(std::ostream *out, const TypedValue &value) should not reach here");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void PrintOperatorArgs(std::ostream *out, const T &arg) {
|
|
||||||
*out << " ";
|
|
||||||
PrintObject(out, arg);
|
|
||||||
*out << ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T, typename... Ts>
|
|
||||||
void PrintOperatorArgs(std::ostream *out, const T &arg, const Ts &...args) {
|
|
||||||
*out << " ";
|
|
||||||
PrintObject(out, arg);
|
|
||||||
PrintOperatorArgs(out, args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... Ts>
|
|
||||||
void PrintOperator(std::ostream *out, const std::string &name, const Ts &...args) {
|
|
||||||
*out << "(" << name;
|
|
||||||
PrintOperatorArgs(out, args...);
|
|
||||||
}
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
class ExpressionPrettyPrinter : public ExpressionVisitor<void> {
|
|
||||||
public:
|
|
||||||
explicit ExpressionPrettyPrinter(std::ostream *out) : out_(out) {}
|
|
||||||
|
|
||||||
// Unary operators
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
|
||||||
#define UNARY_OPERATOR_VISIT(OP_NODE, OP_STR) \
|
|
||||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
|
|
||||||
void Visit(OP_NODE &op) override { detail::PrintOperator(out_, OP_STR, op.expression_); }
|
|
||||||
|
|
||||||
UNARY_OPERATOR_VISIT(NotOperator, "Not");
|
|
||||||
UNARY_OPERATOR_VISIT(UnaryPlusOperator, "+");
|
|
||||||
UNARY_OPERATOR_VISIT(UnaryMinusOperator, "-");
|
|
||||||
UNARY_OPERATOR_VISIT(IsNullOperator, "IsNull");
|
|
||||||
|
|
||||||
#undef UNARY_OPERATOR_VISIT
|
|
||||||
|
|
||||||
// Binary operators
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
|
||||||
#define BINARY_OPERATOR_VISIT(OP_NODE, OP_STR) \
|
|
||||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
|
|
||||||
void Visit(OP_NODE &op) override { detail::PrintOperator(out_, OP_STR, op.expression1_, op.expression2_); }
|
|
||||||
|
|
||||||
BINARY_OPERATOR_VISIT(OrOperator, "Or");
|
|
||||||
BINARY_OPERATOR_VISIT(XorOperator, "Xor");
|
|
||||||
BINARY_OPERATOR_VISIT(AndOperator, "And");
|
|
||||||
BINARY_OPERATOR_VISIT(AdditionOperator, "+");
|
|
||||||
BINARY_OPERATOR_VISIT(SubtractionOperator, "-");
|
|
||||||
BINARY_OPERATOR_VISIT(MultiplicationOperator, "*");
|
|
||||||
BINARY_OPERATOR_VISIT(DivisionOperator, "/");
|
|
||||||
BINARY_OPERATOR_VISIT(ModOperator, "%");
|
|
||||||
BINARY_OPERATOR_VISIT(NotEqualOperator, "!=");
|
|
||||||
BINARY_OPERATOR_VISIT(EqualOperator, "==");
|
|
||||||
BINARY_OPERATOR_VISIT(LessOperator, "<");
|
|
||||||
BINARY_OPERATOR_VISIT(GreaterOperator, ">");
|
|
||||||
BINARY_OPERATOR_VISIT(LessEqualOperator, "<=");
|
|
||||||
BINARY_OPERATOR_VISIT(GreaterEqualOperator, ">=");
|
|
||||||
BINARY_OPERATOR_VISIT(InListOperator, "In");
|
|
||||||
BINARY_OPERATOR_VISIT(SubscriptOperator, "Subscript");
|
|
||||||
|
|
||||||
#undef BINARY_OPERATOR_VISIT
|
|
||||||
|
|
||||||
// Other
|
|
||||||
void Visit(ListSlicingOperator &op) override {
|
|
||||||
detail::PrintOperator(out_, "ListSlicing", op.list_, op.lower_bound_, op.upper_bound_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Visit(IfOperator &op) override {
|
|
||||||
detail::PrintOperator(out_, "If", op.condition_, op.then_expression_, op.else_expression_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Visit(ListLiteral &op) override { detail::PrintOperator(out_, "ListLiteral", op.elements_); }
|
|
||||||
|
|
||||||
void Visit(MapLiteral &op) override {
|
|
||||||
std::map<std::string, Expression *> map;
|
|
||||||
for (const auto &kv : op.elements_) {
|
|
||||||
map[kv.first.name] = kv.second;
|
|
||||||
}
|
|
||||||
detail::PrintObject(out_, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Visit(LabelsTest &op) override { detail::PrintOperator(out_, "LabelsTest", op.expression_); }
|
|
||||||
|
|
||||||
void Visit(Aggregation &op) override { detail::PrintOperator(out_, "Aggregation", op.op_); }
|
|
||||||
|
|
||||||
void Visit(Function &op) override { detail::PrintOperator(out_, "Function", op.function_name_, op.arguments_); }
|
|
||||||
|
|
||||||
void Visit(Reduce &op) override {
|
|
||||||
detail::PrintOperator(out_, "Reduce", op.accumulator_, op.initializer_, op.identifier_, op.list_, op.expression_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Visit(Coalesce &op) override { detail::PrintOperator(out_, "Coalesce", op.expressions_); }
|
|
||||||
|
|
||||||
void Visit(Extract &op) override { detail::PrintOperator(out_, "Extract", op.identifier_, op.list_, op.expression_); }
|
|
||||||
|
|
||||||
void Visit(All &op) override {
|
|
||||||
detail::PrintOperator(out_, "All", op.identifier_, op.list_expression_, op.where_->expression_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Visit(Single &op) override {
|
|
||||||
detail::PrintOperator(out_, "Single", op.identifier_, op.list_expression_, op.where_->expression_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Visit(Any &op) override {
|
|
||||||
detail::PrintOperator(out_, "Any", op.identifier_, op.list_expression_, op.where_->expression_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Visit(None &op) override {
|
|
||||||
detail::PrintOperator(out_, "None", op.identifier_, op.list_expression_, op.where_->expression_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Visit(Identifier &op) override { detail::PrintOperator(out_, "Identifier", op.name_); }
|
|
||||||
|
|
||||||
void Visit(PrimitiveLiteral &op) override { detail::PrintObject(out_, op.value_); }
|
|
||||||
|
|
||||||
void Visit(PropertyLookup &op) override {
|
|
||||||
detail::PrintOperator(out_, "PropertyLookup", op.expression_, op.property_.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Visit(ParameterLookup &op) override { detail::PrintOperator(out_, "ParameterLookup", op.token_position_); }
|
|
||||||
|
|
||||||
void Visit(NamedExpression &op) override { detail::PrintOperator(out_, "NamedExpression", op.name_, op.expression_); }
|
|
||||||
|
|
||||||
void Visit(RegexMatch &op) override { detail::PrintOperator(out_, "=~", op.string_expr_, op.regex_); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::ostream *out_;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
inline void PrintObject(std::ostream *out, Expression *expr) {
|
|
||||||
if (expr) {
|
|
||||||
ExpressionPrettyPrinter printer{out};
|
|
||||||
expr->Accept(printer);
|
|
||||||
} else {
|
|
||||||
*out << "<null>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
inline void PrintExpression(Expression *expr, std::ostream *out) {
|
|
||||||
ExpressionPrettyPrinter printer{out};
|
|
||||||
expr->Accept(printer);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void PrintExpression(NamedExpression *expr, std::ostream *out) {
|
|
||||||
ExpressionPrettyPrinter printer{out};
|
|
||||||
expr->Accept(printer);
|
|
||||||
}
|
|
||||||
} // namespace memgraph::expr
|
|
||||||
@@ -1,279 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <type_traits>
|
|
||||||
|
|
||||||
#include "expr/ast.hpp"
|
|
||||||
#include "expr/typed_value.hpp"
|
|
||||||
#include "utils/algorithm.hpp"
|
|
||||||
#include "utils/logging.hpp"
|
|
||||||
#include "utils/string.hpp"
|
|
||||||
|
|
||||||
namespace memgraph::expr {
|
|
||||||
inline constexpr const char *identifier_node_symbol = "MG_SYMBOL_NODE";
|
|
||||||
inline constexpr const char *identifier_edge_symbol = "MG_SYMBOL_EDGE";
|
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
template <typename T>
|
|
||||||
void PrintObject(std::ostream *out, const T &arg) {
|
|
||||||
static_assert(!std::is_convertible<T, Expression *>::value,
|
|
||||||
"This overload shouldn't be called with pointers convertible "
|
|
||||||
"to Expression *. This means your other PrintObject overloads aren't "
|
|
||||||
"being called for certain AST nodes when they should (or perhaps such "
|
|
||||||
"overloads don't exist yet).");
|
|
||||||
*out << arg;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void PrintObject(std::ostream *out, const std::string &str) { *out << str; }
|
|
||||||
|
|
||||||
inline void PrintObject(std::ostream * /*out*/, Aggregation::Op /*op*/) {
|
|
||||||
throw utils::NotYetImplemented("PrintObject: Aggregation::Op");
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void PrintObject(std::ostream * /*out*/, Expression * /*expr*/);
|
|
||||||
|
|
||||||
inline void PrintObject(std::ostream *out, Identifier *expr) { PrintObject(out, static_cast<Expression *>(expr)); }
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void PrintObject(std::ostream * /*out*/, const std::vector<T> & /*vec*/) {
|
|
||||||
throw utils::NotYetImplemented("PrintObject: vector<T>");
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void PrintObject(std::ostream * /*out*/, const std::vector<T, utils::Allocator<T>> & /*vec*/) {
|
|
||||||
throw utils::NotYetImplemented("PrintObject: vector<T, utils::Allocator<T>>");
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename K, typename V>
|
|
||||||
void PrintObject(std::ostream * /*out*/, const std::map<K, V> & /*map*/) {
|
|
||||||
throw utils::NotYetImplemented("PrintObject: map<K, V>");
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void PrintObject(std::ostream * /*out*/, const utils::pmr::map<utils::pmr::string, T> & /*map*/) {
|
|
||||||
throw utils::NotYetImplemented("PrintObject: map<utils::pmr::string, V>");
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T1, typename T2, typename T3>
|
|
||||||
inline void PrintObject(std::ostream *out, const TypedValueT<T1, T2, T3> &value) {
|
|
||||||
using TypedValue = TypedValueT<T1, T2, T3>;
|
|
||||||
switch (value.type()) {
|
|
||||||
case TypedValue::Type::Null:
|
|
||||||
*out << "null";
|
|
||||||
break;
|
|
||||||
case TypedValue::Type::String:
|
|
||||||
PrintObject(out, value.ValueString());
|
|
||||||
break;
|
|
||||||
case TypedValue::Type::Bool:
|
|
||||||
*out << (value.ValueBool() ? "true" : "false");
|
|
||||||
break;
|
|
||||||
case TypedValue::Type::Int:
|
|
||||||
PrintObject(out, value.ValueInt());
|
|
||||||
break;
|
|
||||||
case TypedValue::Type::Double:
|
|
||||||
PrintObject(out, value.ValueDouble());
|
|
||||||
break;
|
|
||||||
case TypedValue::Type::List:
|
|
||||||
PrintObject(out, value.ValueList());
|
|
||||||
break;
|
|
||||||
case TypedValue::Type::Map:
|
|
||||||
PrintObject(out, value.ValueMap());
|
|
||||||
break;
|
|
||||||
case TypedValue::Type::Date:
|
|
||||||
PrintObject(out, value.ValueDate());
|
|
||||||
break;
|
|
||||||
case TypedValue::Type::Duration:
|
|
||||||
PrintObject(out, value.ValueDuration());
|
|
||||||
break;
|
|
||||||
case TypedValue::Type::LocalTime:
|
|
||||||
PrintObject(out, value.ValueLocalTime());
|
|
||||||
break;
|
|
||||||
case TypedValue::Type::LocalDateTime:
|
|
||||||
PrintObject(out, value.ValueLocalDateTime());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
MG_ASSERT(false, "PrintObject(std::ostream *out, const TypedValue &value) should not reach here");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void PrintOperatorArgs(const std::string & /*name*/, std::ostream *out, bool with_parenthesis, const T &arg) {
|
|
||||||
PrintObject(out, arg);
|
|
||||||
if (with_parenthesis) {
|
|
||||||
*out << ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T, typename... Ts>
|
|
||||||
void PrintOperatorArgs(const std::string &name, std::ostream *out, bool with_parenthesis, const T &arg,
|
|
||||||
const Ts &...args) {
|
|
||||||
PrintObject(out, arg);
|
|
||||||
*out << " " << name << " ";
|
|
||||||
PrintOperatorArgs(name, out, with_parenthesis, args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... Ts>
|
|
||||||
void PrintOperator(const std::string &name, std::ostream *out, bool with_parenthesis, const Ts &...args) {
|
|
||||||
if (with_parenthesis) {
|
|
||||||
*out << "(";
|
|
||||||
}
|
|
||||||
PrintOperatorArgs(name, out, with_parenthesis, args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
// new
|
|
||||||
template <typename T>
|
|
||||||
void PrintOperatorArgs(std::ostream *out, const T &arg) {
|
|
||||||
PrintObject(out, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T, typename... Ts>
|
|
||||||
void PrintOperatorArgs(std::ostream *out, const T &arg, const Ts &...args) {
|
|
||||||
PrintObject(out, arg);
|
|
||||||
PrintOperatorArgs(out, args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... Ts>
|
|
||||||
void PrintOperator(std::ostream *out, const Ts &...args) {
|
|
||||||
PrintOperatorArgs(out, args...);
|
|
||||||
}
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
class ExpressionPrettyPrinter : public ExpressionVisitor<void> {
|
|
||||||
public:
|
|
||||||
explicit ExpressionPrettyPrinter(std::ostream *out) : out_(out) {}
|
|
||||||
|
|
||||||
// Unary operators
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
|
||||||
#define UNARY_OPERATOR_VISIT(OP_NODE, OP_STR) \
|
|
||||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
|
|
||||||
void Visit(OP_NODE &op) override { detail::PrintOperator(OP_STR, out_, false /*with_parenthesis*/, op.expression_); }
|
|
||||||
|
|
||||||
UNARY_OPERATOR_VISIT(NotOperator, "Not");
|
|
||||||
UNARY_OPERATOR_VISIT(UnaryPlusOperator, "+");
|
|
||||||
UNARY_OPERATOR_VISIT(UnaryMinusOperator, "-");
|
|
||||||
UNARY_OPERATOR_VISIT(IsNullOperator, "IsNull");
|
|
||||||
|
|
||||||
#undef UNARY_OPERATOR_VISIT
|
|
||||||
|
|
||||||
// Binary operators
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
|
||||||
#define BINARY_OPERATOR_VISIT(OP_NODE, OP_STR) \
|
|
||||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
|
|
||||||
void Visit(OP_NODE &op) override { \
|
|
||||||
detail::PrintOperator(OP_STR, out_, true /*with_parenthesis*/, op.expression1_, op.expression2_); \
|
|
||||||
}
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
|
||||||
#define BINARY_OPERATOR_VISIT_NOT_IMPL(OP_NODE, OP_STR) \
|
|
||||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
|
|
||||||
void Visit(OP_NODE & /*op*/) override { throw utils::NotYetImplemented("OP_NODE"); }
|
|
||||||
|
|
||||||
BINARY_OPERATOR_VISIT(OrOperator, "Or");
|
|
||||||
BINARY_OPERATOR_VISIT(XorOperator, "Xor");
|
|
||||||
BINARY_OPERATOR_VISIT(AndOperator, "And");
|
|
||||||
BINARY_OPERATOR_VISIT(AdditionOperator, "+");
|
|
||||||
BINARY_OPERATOR_VISIT(SubtractionOperator, "-");
|
|
||||||
BINARY_OPERATOR_VISIT(MultiplicationOperator, "*");
|
|
||||||
BINARY_OPERATOR_VISIT(DivisionOperator, "/");
|
|
||||||
BINARY_OPERATOR_VISIT(ModOperator, "%");
|
|
||||||
BINARY_OPERATOR_VISIT(NotEqualOperator, "!=");
|
|
||||||
BINARY_OPERATOR_VISIT(EqualOperator, "=");
|
|
||||||
BINARY_OPERATOR_VISIT(LessOperator, "<");
|
|
||||||
BINARY_OPERATOR_VISIT(GreaterOperator, ">");
|
|
||||||
BINARY_OPERATOR_VISIT(LessEqualOperator, "<=");
|
|
||||||
BINARY_OPERATOR_VISIT(GreaterEqualOperator, ">=");
|
|
||||||
BINARY_OPERATOR_VISIT_NOT_IMPL(InListOperator, "In");
|
|
||||||
BINARY_OPERATOR_VISIT_NOT_IMPL(SubscriptOperator, "Subscript");
|
|
||||||
|
|
||||||
#undef BINARY_OPERATOR_VISIT
|
|
||||||
#undef BINARY_OPERATOR_VISIT_NOT_IMPL
|
|
||||||
|
|
||||||
// Other
|
|
||||||
void Visit(ListSlicingOperator & /*op*/) override { throw utils::NotYetImplemented("ListSlicingOperator"); }
|
|
||||||
|
|
||||||
void Visit(IfOperator & /*op*/) override { throw utils::NotYetImplemented("IfOperator"); }
|
|
||||||
|
|
||||||
void Visit(ListLiteral & /*op*/) override { throw utils::NotYetImplemented("ListLiteral"); }
|
|
||||||
|
|
||||||
void Visit(MapLiteral & /*op*/) override { throw utils::NotYetImplemented("MapLiteral"); }
|
|
||||||
|
|
||||||
void Visit(LabelsTest & /*op*/) override { throw utils::NotYetImplemented("LabelsTest"); }
|
|
||||||
|
|
||||||
void Visit(Aggregation & /*op*/) override { throw utils::NotYetImplemented("Aggregation"); }
|
|
||||||
|
|
||||||
void Visit(Function & /*op*/) override { throw utils::NotYetImplemented("Function"); }
|
|
||||||
|
|
||||||
void Visit(Reduce & /*op*/) override { throw utils::NotYetImplemented("Reduce"); }
|
|
||||||
|
|
||||||
void Visit(Coalesce & /*op*/) override { throw utils::NotYetImplemented("Coalesce"); }
|
|
||||||
|
|
||||||
void Visit(Extract & /*op*/) override { throw utils::NotYetImplemented("Extract"); }
|
|
||||||
|
|
||||||
void Visit(All & /*op*/) override { throw utils::NotYetImplemented("All"); }
|
|
||||||
|
|
||||||
void Visit(Single & /*op*/) override { throw utils::NotYetImplemented("Single"); }
|
|
||||||
|
|
||||||
void Visit(Any & /*op*/) override { throw utils::NotYetImplemented("Any"); }
|
|
||||||
|
|
||||||
void Visit(None & /*op*/) override { throw utils::NotYetImplemented("None"); }
|
|
||||||
|
|
||||||
void Visit(Identifier &op) override {
|
|
||||||
auto is_node = true;
|
|
||||||
auto is_edge = false;
|
|
||||||
auto is_other = false;
|
|
||||||
if (is_node) {
|
|
||||||
detail::PrintOperator(out_, identifier_node_symbol);
|
|
||||||
} else if (is_edge) {
|
|
||||||
detail::PrintOperator(out_, identifier_edge_symbol);
|
|
||||||
} else {
|
|
||||||
MG_ASSERT(is_other);
|
|
||||||
detail::PrintOperator(out_, op.name_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Visit(PrimitiveLiteral &op) override { detail::PrintObject(out_, op.value_); }
|
|
||||||
|
|
||||||
void Visit(PropertyLookup &op) override { detail::PrintOperator(out_, op.expression_, ".", op.property_.name); }
|
|
||||||
|
|
||||||
void Visit(ParameterLookup & /*op*/) override { throw utils::NotYetImplemented("ParameterLookup"); }
|
|
||||||
|
|
||||||
void Visit(NamedExpression & /*op*/) override { throw utils::NotYetImplemented("NamedExpression"); }
|
|
||||||
|
|
||||||
void Visit(RegexMatch & /*op*/) override { throw utils::NotYetImplemented("RegexMatch"); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::ostream *out_;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
inline void PrintObject(std::ostream *out, Expression *expr) {
|
|
||||||
if (expr) {
|
|
||||||
ExpressionPrettyPrinter printer{out};
|
|
||||||
expr->Accept(printer);
|
|
||||||
} else {
|
|
||||||
*out << "<null>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
inline void PrintExpressionToOriginalAndReplaceNodeAndEdgeSymbols(Expression *expr, std::ostream *out) {
|
|
||||||
ExpressionPrettyPrinter printer{out};
|
|
||||||
expr->Accept(printer);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::string ExpressiontoStringWhileReplacingNodeAndEdgeSymbols(Expression *expr) {
|
|
||||||
std::ostringstream ss;
|
|
||||||
expr::PrintExpressionToOriginalAndReplaceNodeAndEdgeSymbols(expr, &ss);
|
|
||||||
return ss.str();
|
|
||||||
}
|
|
||||||
} // namespace memgraph::expr
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "utils/exceptions.hpp"
|
|
||||||
|
|
||||||
namespace memgraph::expr {
|
|
||||||
|
|
||||||
class SyntaxException : public utils::BasicException {
|
|
||||||
public:
|
|
||||||
using utils::BasicException::BasicException;
|
|
||||||
SyntaxException() : SyntaxException("") {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class SemanticException : public utils::BasicException {
|
|
||||||
public:
|
|
||||||
using utils::BasicException::BasicException;
|
|
||||||
SemanticException() : BasicException("") {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class ExpressionRuntimeException : public utils::BasicException {
|
|
||||||
public:
|
|
||||||
using utils::BasicException::BasicException;
|
|
||||||
};
|
|
||||||
|
|
||||||
class RedeclareVariableError : public SemanticException {
|
|
||||||
public:
|
|
||||||
explicit RedeclareVariableError(const std::string &name) : SemanticException("Redeclaring variable: " + name + ".") {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class UnboundVariableError : public SemanticException {
|
|
||||||
public:
|
|
||||||
explicit UnboundVariableError(const std::string &name) : SemanticException("Unbound variable: " + name + ".") {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class TypeMismatchError : public SemanticException {
|
|
||||||
public:
|
|
||||||
TypeMismatchError(const std::string &name, const std::string &datum, const std::string &expected)
|
|
||||||
: SemanticException(fmt::format("Type mismatch: {} already defined as {}, expected {}.", name, datum, expected)) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace memgraph::expr
|
|
||||||
@@ -1,821 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
/// @file
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <limits>
|
|
||||||
#include <map>
|
|
||||||
#include <optional>
|
|
||||||
#include <regex>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "expr/ast.hpp"
|
|
||||||
#include "expr/exceptions.hpp"
|
|
||||||
#include "expr/interpret/frame.hpp"
|
|
||||||
#include "expr/semantic/symbol_table.hpp"
|
|
||||||
#include "functions/awesome_memgraph_functions.hpp"
|
|
||||||
#include "utils/exceptions.hpp"
|
|
||||||
|
|
||||||
namespace memgraph::expr {
|
|
||||||
|
|
||||||
struct StorageTag {};
|
|
||||||
struct QueryEngineTag {};
|
|
||||||
|
|
||||||
template <typename TypedValue, typename EvaluationContext, typename DbAccessor, typename StorageView, typename LabelId,
|
|
||||||
typename PropertyValue, typename ConvFunctor, typename Error, typename Tag = StorageTag>
|
|
||||||
class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
|
||||||
public:
|
|
||||||
ExpressionEvaluator(Frame *frame, const SymbolTable &symbol_table, const EvaluationContext &ctx, DbAccessor *dba,
|
|
||||||
StorageView view)
|
|
||||||
: frame_(frame), symbol_table_(&symbol_table), ctx_(&ctx), dba_(dba), view_(view) {}
|
|
||||||
|
|
||||||
using ExpressionVisitor<TypedValue>::Visit;
|
|
||||||
|
|
||||||
utils::MemoryResource *GetMemoryResource() const { return ctx_->memory; }
|
|
||||||
|
|
||||||
TypedValue Visit(NamedExpression &named_expression) override {
|
|
||||||
const auto &symbol = symbol_table_->at(named_expression);
|
|
||||||
auto value = named_expression.expression_->Accept(*this);
|
|
||||||
frame_->at(symbol) = value;
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(Identifier &ident) override {
|
|
||||||
return TypedValue(frame_->at(symbol_table_->at(ident)), ctx_->memory);
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
|
||||||
#define BINARY_OPERATOR_VISITOR(OP_NODE, CPP_OP, CYPHER_OP) \
|
|
||||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
|
|
||||||
TypedValue Visit(OP_NODE &op) override { \
|
|
||||||
auto val1 = op.expression1_->Accept(*this); \
|
|
||||||
auto val2 = op.expression2_->Accept(*this); \
|
|
||||||
try { \
|
|
||||||
return val1 CPP_OP val2; \
|
|
||||||
} catch (const TypedValueException &) { \
|
|
||||||
throw ExpressionRuntimeException("Invalid types: {} and {} for '{}'.", val1.type(), val2.type(), #CYPHER_OP); \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
|
||||||
#define UNARY_OPERATOR_VISITOR(OP_NODE, CPP_OP, CYPHER_OP) \
|
|
||||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
|
|
||||||
TypedValue Visit(OP_NODE &op) override { \
|
|
||||||
auto val = op.expression_->Accept(*this); \
|
|
||||||
try { \
|
|
||||||
return CPP_OP val; \
|
|
||||||
} catch (const TypedValueException &) { \
|
|
||||||
throw ExpressionRuntimeException("Invalid type {} for '{}'.", val.type(), #CYPHER_OP); \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
BINARY_OPERATOR_VISITOR(OrOperator, ||, OR);
|
|
||||||
BINARY_OPERATOR_VISITOR(XorOperator, ^, XOR);
|
|
||||||
BINARY_OPERATOR_VISITOR(AdditionOperator, +, +);
|
|
||||||
BINARY_OPERATOR_VISITOR(SubtractionOperator, -, -);
|
|
||||||
BINARY_OPERATOR_VISITOR(MultiplicationOperator, *, *);
|
|
||||||
BINARY_OPERATOR_VISITOR(DivisionOperator, /, /);
|
|
||||||
BINARY_OPERATOR_VISITOR(ModOperator, %, %);
|
|
||||||
BINARY_OPERATOR_VISITOR(NotEqualOperator, !=, <>);
|
|
||||||
BINARY_OPERATOR_VISITOR(EqualOperator, ==, =);
|
|
||||||
BINARY_OPERATOR_VISITOR(LessOperator, <, <);
|
|
||||||
BINARY_OPERATOR_VISITOR(GreaterOperator, >, >);
|
|
||||||
BINARY_OPERATOR_VISITOR(LessEqualOperator, <=, <=);
|
|
||||||
BINARY_OPERATOR_VISITOR(GreaterEqualOperator, >=, >=);
|
|
||||||
|
|
||||||
UNARY_OPERATOR_VISITOR(NotOperator, !, NOT);
|
|
||||||
UNARY_OPERATOR_VISITOR(UnaryPlusOperator, +, +);
|
|
||||||
UNARY_OPERATOR_VISITOR(UnaryMinusOperator, -, -);
|
|
||||||
|
|
||||||
#undef BINARY_OPERATOR_VISITOR
|
|
||||||
#undef UNARY_OPERATOR_VISITOR
|
|
||||||
|
|
||||||
void HandleObjectAccessError(Error &shard_error, const std::string_view accessed_object) {
|
|
||||||
switch (shard_error) {
|
|
||||||
case Error::DELETED_OBJECT:
|
|
||||||
throw ExpressionRuntimeException("Trying to access {} on a deleted object.", accessed_object);
|
|
||||||
case Error::NONEXISTENT_OBJECT:
|
|
||||||
throw ExpressionRuntimeException("Trying to access {} from a node object doesn't exist.", accessed_object);
|
|
||||||
case Error::SERIALIZATION_ERROR:
|
|
||||||
case Error::VERTEX_HAS_EDGES:
|
|
||||||
case Error::PROPERTIES_DISABLED:
|
|
||||||
case Error::VERTEX_ALREADY_INSERTED:
|
|
||||||
case Error::OBJECT_NOT_FOUND:
|
|
||||||
throw ExpressionRuntimeException("Unexpected error when accessing {}.", accessed_object);
|
|
||||||
case Error::SCHEMA_NO_SCHEMA_DEFINED_FOR_LABEL:
|
|
||||||
case Error::SCHEMA_VERTEX_PROPERTY_WRONG_TYPE:
|
|
||||||
case Error::SCHEMA_VERTEX_UPDATE_PRIMARY_KEY:
|
|
||||||
case Error::SCHEMA_VERTEX_UPDATE_PRIMARY_LABEL:
|
|
||||||
case Error::SCHEMA_VERTEX_SECONDARY_LABEL_IS_PRIMARY:
|
|
||||||
case Error::SCHEMA_VERTEX_PRIMARY_PROPERTIES_UNDEFINED:
|
|
||||||
throw ExpressionRuntimeException("Unexpected schema violation when accessing {}.", accessed_object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(AndOperator &op) override {
|
|
||||||
auto value1 = op.expression1_->Accept(*this);
|
|
||||||
if (value1.IsBool() && !value1.ValueBool()) {
|
|
||||||
// If first expression is false, don't evaluate the second one.
|
|
||||||
return value1;
|
|
||||||
}
|
|
||||||
auto value2 = op.expression2_->Accept(*this);
|
|
||||||
try {
|
|
||||||
return value1 && value2;
|
|
||||||
} catch (const TypedValueException &) {
|
|
||||||
throw ExpressionRuntimeException("Invalid types: {} and {} for AND.", value1.type(), value2.type());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(IfOperator &if_operator) override {
|
|
||||||
auto condition = if_operator.condition_->Accept(*this);
|
|
||||||
if (condition.IsNull()) {
|
|
||||||
return if_operator.else_expression_->Accept(*this);
|
|
||||||
}
|
|
||||||
if (condition.type() != TypedValue::Type::Bool) {
|
|
||||||
// At the moment IfOperator is used only in CASE construct.
|
|
||||||
throw ExpressionRuntimeException("CASE expected boolean expression, got {}.", condition.type());
|
|
||||||
}
|
|
||||||
if (condition.ValueBool()) {
|
|
||||||
return if_operator.then_expression_->Accept(*this);
|
|
||||||
}
|
|
||||||
return if_operator.else_expression_->Accept(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(InListOperator &in_list) override {
|
|
||||||
auto literal = in_list.expression1_->Accept(*this);
|
|
||||||
auto _list = in_list.expression2_->Accept(*this);
|
|
||||||
if (_list.IsNull()) {
|
|
||||||
return TypedValue(ctx_->memory);
|
|
||||||
}
|
|
||||||
// Exceptions have higher priority than returning nulls when list expression
|
|
||||||
// is not null.
|
|
||||||
if (_list.type() != TypedValue::Type::List) {
|
|
||||||
throw ExpressionRuntimeException("IN expected a list, got {}.", _list.type());
|
|
||||||
}
|
|
||||||
const auto &list = _list.ValueList();
|
|
||||||
|
|
||||||
// If literal is NULL there is no need to try to compare it with every
|
|
||||||
// element in the list since result of every comparison will be NULL. There
|
|
||||||
// is one special case that we must test explicitly: if list is empty then
|
|
||||||
// result is false since no comparison will be performed.
|
|
||||||
if (list.empty()) return TypedValue(false, ctx_->memory);
|
|
||||||
if (literal.IsNull()) return TypedValue(ctx_->memory);
|
|
||||||
|
|
||||||
auto has_null = false;
|
|
||||||
for (const auto &element : list) {
|
|
||||||
auto result = literal == element;
|
|
||||||
if (result.IsNull()) {
|
|
||||||
has_null = true;
|
|
||||||
} else if (result.ValueBool()) {
|
|
||||||
return TypedValue(true, ctx_->memory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (has_null) {
|
|
||||||
return TypedValue(ctx_->memory);
|
|
||||||
}
|
|
||||||
return TypedValue(false, ctx_->memory);
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(SubscriptOperator &list_indexing) override {
|
|
||||||
auto lhs = list_indexing.expression1_->Accept(*this);
|
|
||||||
auto index = list_indexing.expression2_->Accept(*this);
|
|
||||||
if (!lhs.IsList() && !lhs.IsMap() && !lhs.IsVertex() && !lhs.IsEdge() && !lhs.IsNull())
|
|
||||||
throw ExpressionRuntimeException(
|
|
||||||
"Expected a list, a map, a node or an edge to index with '[]', got "
|
|
||||||
"{}.",
|
|
||||||
lhs.type());
|
|
||||||
if (lhs.IsNull() || index.IsNull()) return TypedValue(ctx_->memory);
|
|
||||||
if (lhs.IsList()) {
|
|
||||||
if (!index.IsInt())
|
|
||||||
throw ExpressionRuntimeException("Expected an integer as a list index, got {}.", index.type());
|
|
||||||
auto index_int = index.ValueInt();
|
|
||||||
// NOTE: Take non-const reference to list, so that we can move out the
|
|
||||||
// indexed element as the result.
|
|
||||||
auto &list = lhs.ValueList();
|
|
||||||
if (index_int < 0) {
|
|
||||||
index_int += static_cast<int64_t>(list.size());
|
|
||||||
}
|
|
||||||
if (index_int >= static_cast<int64_t>(list.size()) || index_int < 0) return TypedValue(ctx_->memory);
|
|
||||||
// NOTE: Explicit move is needed, so that we return the move constructed
|
|
||||||
// value and preserve the correct MemoryResource.
|
|
||||||
return std::move(list[index_int]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lhs.IsMap()) {
|
|
||||||
if (!index.IsString())
|
|
||||||
throw ExpressionRuntimeException("Expected a string as a map index, got {}.", index.type());
|
|
||||||
// NOTE: Take non-const reference to map, so that we can move out the
|
|
||||||
// looked-up element as the result.
|
|
||||||
auto &map = lhs.ValueMap();
|
|
||||||
auto found = map.find(index.ValueString());
|
|
||||||
if (found == map.end()) return TypedValue(ctx_->memory);
|
|
||||||
// NOTE: Explicit move is needed, so that we return the move constructed
|
|
||||||
// value and preserve the correct MemoryResource.
|
|
||||||
return std::move(found->second);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lhs.IsVertex()) {
|
|
||||||
if (!index.IsString())
|
|
||||||
throw ExpressionRuntimeException("Expected a string as a property name, got {}.", index.type());
|
|
||||||
return TypedValue(GetProperty(lhs.ValueVertex(), index.ValueString()), ctx_->memory);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lhs.IsEdge()) {
|
|
||||||
if (!index.IsString())
|
|
||||||
throw ExpressionRuntimeException("Expected a string as a property name, got {}.", index.type());
|
|
||||||
return TypedValue(GetProperty(lhs.ValueEdge(), index.ValueString()), ctx_->memory);
|
|
||||||
}
|
|
||||||
|
|
||||||
// lhs is Null
|
|
||||||
return TypedValue(ctx_->memory);
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(ListSlicingOperator &op) override {
|
|
||||||
// If some type is null we can't return null, because throwing exception
|
|
||||||
// on illegal type has higher priority.
|
|
||||||
auto is_null = false;
|
|
||||||
auto get_bound = [&](Expression *bound_expr, int64_t default_value) {
|
|
||||||
if (bound_expr) {
|
|
||||||
auto bound = bound_expr->Accept(*this);
|
|
||||||
if (bound.type() == TypedValue::Type::Null) {
|
|
||||||
is_null = true;
|
|
||||||
} else if (bound.type() != TypedValue::Type::Int) {
|
|
||||||
throw ExpressionRuntimeException("Expected an integer for a bound in list slicing, got {}.", bound.type());
|
|
||||||
}
|
|
||||||
return bound;
|
|
||||||
}
|
|
||||||
return TypedValue(default_value, ctx_->memory);
|
|
||||||
};
|
|
||||||
auto _upper_bound = get_bound(op.upper_bound_, std::numeric_limits<int64_t>::max());
|
|
||||||
auto _lower_bound = get_bound(op.lower_bound_, 0);
|
|
||||||
|
|
||||||
auto _list = op.list_->Accept(*this);
|
|
||||||
if (_list.type() == TypedValue::Type::Null) {
|
|
||||||
is_null = true;
|
|
||||||
} else if (_list.type() != TypedValue::Type::List) {
|
|
||||||
throw ExpressionRuntimeException("Expected a list to slice, got {}.", _list.type());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_null) {
|
|
||||||
return TypedValue(ctx_->memory);
|
|
||||||
}
|
|
||||||
const auto &list = _list.ValueList();
|
|
||||||
auto normalise_bound = [&](int64_t bound) {
|
|
||||||
if (bound < 0) {
|
|
||||||
bound = static_cast<int64_t>(list.size()) + bound;
|
|
||||||
}
|
|
||||||
return std::max(static_cast<int64_t>(0), std::min(bound, static_cast<int64_t>(list.size())));
|
|
||||||
};
|
|
||||||
auto lower_bound = normalise_bound(_lower_bound.ValueInt());
|
|
||||||
auto upper_bound = normalise_bound(_upper_bound.ValueInt());
|
|
||||||
if (upper_bound <= lower_bound) {
|
|
||||||
return TypedValue(typename TypedValue::TVector(ctx_->memory), ctx_->memory);
|
|
||||||
}
|
|
||||||
return TypedValue(
|
|
||||||
typename TypedValue::TVector(list.begin() + lower_bound, list.begin() + upper_bound, ctx_->memory));
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(IsNullOperator &is_null) override {
|
|
||||||
auto value = is_null.expression_->Accept(*this);
|
|
||||||
return TypedValue(value.IsNull(), ctx_->memory);
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(PropertyLookup &property_lookup) override {
|
|
||||||
auto expression_result = property_lookup.expression_->Accept(*this);
|
|
||||||
auto maybe_date = [this](const auto &date, const auto &prop_name) -> std::optional<TypedValue> {
|
|
||||||
if (prop_name == "year") {
|
|
||||||
return TypedValue(date.year, ctx_->memory);
|
|
||||||
}
|
|
||||||
if (prop_name == "month") {
|
|
||||||
return TypedValue(date.month, ctx_->memory);
|
|
||||||
}
|
|
||||||
if (prop_name == "day") {
|
|
||||||
return TypedValue(date.day, ctx_->memory);
|
|
||||||
}
|
|
||||||
return std::nullopt;
|
|
||||||
};
|
|
||||||
auto maybe_local_time = [this](const auto <, const auto &prop_name) -> std::optional<TypedValue> {
|
|
||||||
if (prop_name == "hour") {
|
|
||||||
return TypedValue(lt.hour, ctx_->memory);
|
|
||||||
}
|
|
||||||
if (prop_name == "minute") {
|
|
||||||
return TypedValue(lt.minute, ctx_->memory);
|
|
||||||
}
|
|
||||||
if (prop_name == "second") {
|
|
||||||
return TypedValue(lt.second, ctx_->memory);
|
|
||||||
}
|
|
||||||
if (prop_name == "millisecond") {
|
|
||||||
return TypedValue(lt.millisecond, ctx_->memory);
|
|
||||||
}
|
|
||||||
if (prop_name == "microsecond") {
|
|
||||||
return TypedValue(lt.microsecond, ctx_->memory);
|
|
||||||
}
|
|
||||||
return std::nullopt;
|
|
||||||
};
|
|
||||||
auto maybe_duration = [this](const auto &dur, const auto &prop_name) -> std::optional<TypedValue> {
|
|
||||||
if (prop_name == "day") {
|
|
||||||
return TypedValue(dur.Days(), ctx_->memory);
|
|
||||||
}
|
|
||||||
if (prop_name == "hour") {
|
|
||||||
return TypedValue(dur.SubDaysAsHours(), ctx_->memory);
|
|
||||||
}
|
|
||||||
if (prop_name == "minute") {
|
|
||||||
return TypedValue(dur.SubDaysAsMinutes(), ctx_->memory);
|
|
||||||
}
|
|
||||||
if (prop_name == "second") {
|
|
||||||
return TypedValue(dur.SubDaysAsSeconds(), ctx_->memory);
|
|
||||||
}
|
|
||||||
if (prop_name == "millisecond") {
|
|
||||||
return TypedValue(dur.SubDaysAsMilliseconds(), ctx_->memory);
|
|
||||||
}
|
|
||||||
if (prop_name == "microsecond") {
|
|
||||||
return TypedValue(dur.SubDaysAsMicroseconds(), ctx_->memory);
|
|
||||||
}
|
|
||||||
if (prop_name == "nanosecond") {
|
|
||||||
return TypedValue(dur.SubDaysAsNanoseconds(), ctx_->memory);
|
|
||||||
}
|
|
||||||
return std::nullopt;
|
|
||||||
};
|
|
||||||
switch (expression_result.type()) {
|
|
||||||
case TypedValue::Type::Null:
|
|
||||||
return TypedValue(ctx_->memory);
|
|
||||||
case TypedValue::Type::Vertex:
|
|
||||||
return GetProperty(expression_result.ValueVertex(), property_lookup.property_);
|
|
||||||
case TypedValue::Type::Edge:
|
|
||||||
return GetProperty(expression_result.ValueEdge(), property_lookup.property_);
|
|
||||||
case TypedValue::Type::Map: {
|
|
||||||
// NOTE: Take non-const reference to map, so that we can move out the
|
|
||||||
// looked-up element as the result.
|
|
||||||
auto &map = expression_result.ValueMap();
|
|
||||||
auto found = map.find(property_lookup.property_.name.c_str());
|
|
||||||
if (found == map.end()) return TypedValue(ctx_->memory);
|
|
||||||
// NOTE: Explicit move is needed, so that we return the move constructed
|
|
||||||
// value and preserve the correct MemoryResource.
|
|
||||||
return std::move(found->second);
|
|
||||||
}
|
|
||||||
case TypedValue::Type::Duration: {
|
|
||||||
const auto &prop_name = property_lookup.property_.name;
|
|
||||||
const auto &dur = expression_result.ValueDuration();
|
|
||||||
if (auto dur_field = maybe_duration(dur, prop_name); dur_field) {
|
|
||||||
return std::move(*dur_field);
|
|
||||||
}
|
|
||||||
throw ExpressionRuntimeException("Invalid property name {} for Duration", prop_name);
|
|
||||||
}
|
|
||||||
case TypedValue::Type::Date: {
|
|
||||||
const auto &prop_name = property_lookup.property_.name;
|
|
||||||
const auto &date = expression_result.ValueDate();
|
|
||||||
if (auto date_field = maybe_date(date, prop_name); date_field) {
|
|
||||||
return std::move(*date_field);
|
|
||||||
}
|
|
||||||
throw ExpressionRuntimeException("Invalid property name {} for Date", prop_name);
|
|
||||||
}
|
|
||||||
case TypedValue::Type::LocalTime: {
|
|
||||||
const auto &prop_name = property_lookup.property_.name;
|
|
||||||
const auto < = expression_result.ValueLocalTime();
|
|
||||||
if (auto lt_field = maybe_local_time(lt, prop_name); lt_field) {
|
|
||||||
return std::move(*lt_field);
|
|
||||||
}
|
|
||||||
throw ExpressionRuntimeException("Invalid property name {} for LocalTime", prop_name);
|
|
||||||
}
|
|
||||||
case TypedValue::Type::LocalDateTime: {
|
|
||||||
const auto &prop_name = property_lookup.property_.name;
|
|
||||||
const auto &ldt = expression_result.ValueLocalDateTime();
|
|
||||||
if (auto date_field = maybe_date(ldt.date, prop_name); date_field) {
|
|
||||||
return std::move(*date_field);
|
|
||||||
}
|
|
||||||
if (auto lt_field = maybe_local_time(ldt.local_time, prop_name); lt_field) {
|
|
||||||
return std::move(*lt_field);
|
|
||||||
}
|
|
||||||
throw ExpressionRuntimeException("Invalid property name {} for LocalDateTime", prop_name);
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw ExpressionRuntimeException("Only nodes, edges, maps and temporal types have properties to be looked-up.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename VertexAccessor, typename TTag = Tag,
|
|
||||||
typename TReturnType = std::enable_if_t<std::is_same_v<TTag, StorageTag>, bool>>
|
|
||||||
TReturnType HasLabelImpl(const VertexAccessor &vertex, const LabelIx &label, StorageTag /*tag*/) {
|
|
||||||
auto has_label = vertex.HasLabel(view_, GetLabel(label));
|
|
||||||
if (has_label.HasError() && has_label.GetError() == Error::NONEXISTENT_OBJECT) {
|
|
||||||
// This is a very nasty and temporary hack in order to make MERGE
|
|
||||||
// work. The old storage had the following logic when returning an
|
|
||||||
// `OLD` view: `return old ? old : new`. That means that if the
|
|
||||||
// `OLD` view didn't exist, it returned the NEW view. With this hack
|
|
||||||
// we simulate that behavior.
|
|
||||||
// TODO (mferencevic, teon.banek): Remove once MERGE is
|
|
||||||
// reimplemented.
|
|
||||||
has_label = vertex.HasLabel(StorageView::NEW, GetLabel(label));
|
|
||||||
}
|
|
||||||
if (has_label.HasError()) {
|
|
||||||
HandleObjectAccessError(has_label.GetError().code, "labels");
|
|
||||||
}
|
|
||||||
return *has_label;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename VertexAccessor, typename TTag = Tag,
|
|
||||||
typename TReturnType = std::enable_if_t<std::is_same_v<TTag, QueryEngineTag>, bool>>
|
|
||||||
TReturnType HasLabelImpl(const VertexAccessor &vertex, const LabelIx &label_ix, QueryEngineTag /*tag*/) {
|
|
||||||
auto label = typename VertexAccessor::Label{LabelId::FromUint(label_ix.ix)};
|
|
||||||
return vertex.HasLabel(label);
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(LabelsTest &labels_test) override {
|
|
||||||
auto expression_result = labels_test.expression_->Accept(*this);
|
|
||||||
switch (expression_result.type()) {
|
|
||||||
case TypedValue::Type::Null:
|
|
||||||
return TypedValue(ctx_->memory);
|
|
||||||
case TypedValue::Type::Vertex: {
|
|
||||||
const auto &vertex = expression_result.ValueVertex();
|
|
||||||
if (std::ranges::all_of(labels_test.labels_, [&vertex, this](const auto label_test) {
|
|
||||||
return this->HasLabelImpl(vertex, label_test, Tag{});
|
|
||||||
})) {
|
|
||||||
return TypedValue(true, ctx_->memory);
|
|
||||||
}
|
|
||||||
return TypedValue(false, ctx_->memory);
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw ExpressionRuntimeException("Only nodes have labels.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(PrimitiveLiteral &literal) override {
|
|
||||||
// TODO: no need to evaluate constants, we can write it to frame in one
|
|
||||||
// of the previous phases.
|
|
||||||
return TypedValue(literal.value_, ctx_->memory);
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(ListLiteral &literal) override {
|
|
||||||
typename TypedValue::TVector result(ctx_->memory);
|
|
||||||
result.reserve(literal.elements_.size());
|
|
||||||
for (const auto &expression : literal.elements_) result.emplace_back(expression->Accept(*this));
|
|
||||||
return TypedValue(result, ctx_->memory);
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(MapLiteral &literal) override {
|
|
||||||
typename TypedValue::TMap result(ctx_->memory);
|
|
||||||
for (const auto &pair : literal.elements_) result.emplace(pair.first.name, pair.second->Accept(*this));
|
|
||||||
return TypedValue(result, ctx_->memory);
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(Aggregation &aggregation) override {
|
|
||||||
return TypedValue(frame_->at(symbol_table_->at(aggregation)), ctx_->memory);
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(Coalesce &coalesce) override {
|
|
||||||
auto &exprs = coalesce.expressions_;
|
|
||||||
|
|
||||||
if (exprs.size() == 0) {
|
|
||||||
throw ExpressionRuntimeException("'coalesce' requires at least one argument.");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int64_t i = 0; i < exprs.size(); ++i) {
|
|
||||||
TypedValue val(exprs[i]->Accept(*this), ctx_->memory);
|
|
||||||
if (!val.IsNull()) {
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return TypedValue(ctx_->memory);
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(Function &function) override {
|
|
||||||
functions::FunctionContext<DbAccessor> function_ctx{dba_, ctx_->memory, ctx_->timestamp, &ctx_->counters, view_};
|
|
||||||
// Stack allocate evaluated arguments when there's a small number of them.
|
|
||||||
if (function.arguments_.size() <= 8) {
|
|
||||||
TypedValue arguments[8] = {TypedValue(ctx_->memory), TypedValue(ctx_->memory), TypedValue(ctx_->memory),
|
|
||||||
TypedValue(ctx_->memory), TypedValue(ctx_->memory), TypedValue(ctx_->memory),
|
|
||||||
TypedValue(ctx_->memory), TypedValue(ctx_->memory)};
|
|
||||||
for (size_t i = 0; i < function.arguments_.size(); ++i) {
|
|
||||||
arguments[i] = function.arguments_[i]->Accept(*this);
|
|
||||||
}
|
|
||||||
auto res = function.function_(arguments, function.arguments_.size(), function_ctx);
|
|
||||||
MG_ASSERT(res.GetMemoryResource() == ctx_->memory);
|
|
||||||
return res;
|
|
||||||
} else {
|
|
||||||
typename TypedValue::TVector arguments(ctx_->memory);
|
|
||||||
arguments.reserve(function.arguments_.size());
|
|
||||||
for (const auto &argument : function.arguments_) {
|
|
||||||
arguments.emplace_back(argument->Accept(*this));
|
|
||||||
}
|
|
||||||
auto res = function.function_(arguments.data(), arguments.size(), function_ctx);
|
|
||||||
MG_ASSERT(res.GetMemoryResource() == ctx_->memory);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(Reduce &reduce) override {
|
|
||||||
auto list_value = reduce.list_->Accept(*this);
|
|
||||||
if (list_value.IsNull()) {
|
|
||||||
return TypedValue(ctx_->memory);
|
|
||||||
}
|
|
||||||
if (list_value.type() != TypedValue::Type::List) {
|
|
||||||
throw ExpressionRuntimeException("REDUCE expected a list, got {}.", list_value.type());
|
|
||||||
}
|
|
||||||
const auto &list = list_value.ValueList();
|
|
||||||
const auto &element_symbol = symbol_table_->at(*reduce.identifier_);
|
|
||||||
const auto &accumulator_symbol = symbol_table_->at(*reduce.accumulator_);
|
|
||||||
auto accumulator = reduce.initializer_->Accept(*this);
|
|
||||||
for (const auto &element : list) {
|
|
||||||
frame_->at(accumulator_symbol) = accumulator;
|
|
||||||
frame_->at(element_symbol) = element;
|
|
||||||
accumulator = reduce.expression_->Accept(*this);
|
|
||||||
}
|
|
||||||
return accumulator;
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(Extract &extract) override {
|
|
||||||
auto list_value = extract.list_->Accept(*this);
|
|
||||||
if (list_value.IsNull()) {
|
|
||||||
return TypedValue(ctx_->memory);
|
|
||||||
}
|
|
||||||
if (list_value.type() != TypedValue::Type::List) {
|
|
||||||
throw ExpressionRuntimeException("EXTRACT expected a list, got {}.", list_value.type());
|
|
||||||
}
|
|
||||||
const auto &list = list_value.ValueList();
|
|
||||||
const auto &element_symbol = symbol_table_->at(*extract.identifier_);
|
|
||||||
typename TypedValue::TVector result(ctx_->memory);
|
|
||||||
result.reserve(list.size());
|
|
||||||
for (const auto &element : list) {
|
|
||||||
if (element.IsNull()) {
|
|
||||||
result.emplace_back();
|
|
||||||
} else {
|
|
||||||
frame_->at(element_symbol) = element;
|
|
||||||
result.emplace_back(extract.expression_->Accept(*this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return TypedValue(result, ctx_->memory);
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(All &all) override {
|
|
||||||
auto list_value = all.list_expression_->Accept(*this);
|
|
||||||
if (list_value.IsNull()) {
|
|
||||||
return TypedValue(ctx_->memory);
|
|
||||||
}
|
|
||||||
if (list_value.type() != TypedValue::Type::List) {
|
|
||||||
throw ExpressionRuntimeException("ALL expected a list, got {}.", list_value.type());
|
|
||||||
}
|
|
||||||
const auto &list = list_value.ValueList();
|
|
||||||
const auto &symbol = symbol_table_->at(*all.identifier_);
|
|
||||||
bool has_null_elements = false;
|
|
||||||
bool has_value = false;
|
|
||||||
for (const auto &element : list) {
|
|
||||||
frame_->at(symbol) = element;
|
|
||||||
auto result = all.where_->expression_->Accept(*this);
|
|
||||||
if (!result.IsNull() && result.type() != TypedValue::Type::Bool) {
|
|
||||||
throw ExpressionRuntimeException("Predicate of ALL must evaluate to boolean, got {}.", result.type());
|
|
||||||
}
|
|
||||||
if (!result.IsNull()) {
|
|
||||||
has_value = true;
|
|
||||||
if (!result.ValueBool()) {
|
|
||||||
return TypedValue(false, ctx_->memory);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
has_null_elements = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!has_value) {
|
|
||||||
return TypedValue(ctx_->memory);
|
|
||||||
}
|
|
||||||
if (has_null_elements) {
|
|
||||||
return TypedValue(false, ctx_->memory);
|
|
||||||
} else {
|
|
||||||
return TypedValue(true, ctx_->memory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(Single &single) override {
|
|
||||||
auto list_value = single.list_expression_->Accept(*this);
|
|
||||||
if (list_value.IsNull()) {
|
|
||||||
return TypedValue(ctx_->memory);
|
|
||||||
}
|
|
||||||
if (list_value.type() != TypedValue::Type::List) {
|
|
||||||
throw ExpressionRuntimeException("SINGLE expected a list, got {}.", list_value.type());
|
|
||||||
}
|
|
||||||
const auto &list = list_value.ValueList();
|
|
||||||
const auto &symbol = symbol_table_->at(*single.identifier_);
|
|
||||||
bool has_value = false;
|
|
||||||
bool predicate_satisfied = false;
|
|
||||||
for (const auto &element : list) {
|
|
||||||
frame_->at(symbol) = element;
|
|
||||||
auto result = single.where_->expression_->Accept(*this);
|
|
||||||
if (!result.IsNull() && result.type() != TypedValue::Type::Bool) {
|
|
||||||
throw ExpressionRuntimeException("Predicate of SINGLE must evaluate to boolean, got {}.", result.type());
|
|
||||||
}
|
|
||||||
if (result.type() == TypedValue::Type::Bool) {
|
|
||||||
has_value = true;
|
|
||||||
}
|
|
||||||
if (result.IsNull() || !result.ValueBool()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Return false if more than one element satisfies the predicate.
|
|
||||||
if (predicate_satisfied) {
|
|
||||||
return TypedValue(false, ctx_->memory);
|
|
||||||
} else {
|
|
||||||
predicate_satisfied = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!has_value) {
|
|
||||||
return TypedValue(ctx_->memory);
|
|
||||||
} else {
|
|
||||||
return TypedValue(predicate_satisfied, ctx_->memory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(Any &any) override {
|
|
||||||
auto list_value = any.list_expression_->Accept(*this);
|
|
||||||
if (list_value.IsNull()) {
|
|
||||||
return TypedValue(ctx_->memory);
|
|
||||||
}
|
|
||||||
if (list_value.type() != TypedValue::Type::List) {
|
|
||||||
throw ExpressionRuntimeException("ANY expected a list, got {}.", list_value.type());
|
|
||||||
}
|
|
||||||
const auto &list = list_value.ValueList();
|
|
||||||
const auto &symbol = symbol_table_->at(*any.identifier_);
|
|
||||||
bool has_value = false;
|
|
||||||
for (const auto &element : list) {
|
|
||||||
frame_->at(symbol) = element;
|
|
||||||
auto result = any.where_->expression_->Accept(*this);
|
|
||||||
if (!result.IsNull() && result.type() != TypedValue::Type::Bool) {
|
|
||||||
throw ExpressionRuntimeException("Predicate of ANY must evaluate to boolean, got {}.", result.type());
|
|
||||||
}
|
|
||||||
if (!result.IsNull()) {
|
|
||||||
has_value = true;
|
|
||||||
if (result.ValueBool()) {
|
|
||||||
return TypedValue(true, ctx_->memory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Return Null if all elements are Null
|
|
||||||
if (!has_value) {
|
|
||||||
return TypedValue(ctx_->memory);
|
|
||||||
} else {
|
|
||||||
return TypedValue(false, ctx_->memory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(None &none) override {
|
|
||||||
auto list_value = none.list_expression_->Accept(*this);
|
|
||||||
if (list_value.IsNull()) {
|
|
||||||
return TypedValue(ctx_->memory);
|
|
||||||
}
|
|
||||||
if (list_value.type() != TypedValue::Type::List) {
|
|
||||||
throw ExpressionRuntimeException("NONE expected a list, got {}.", list_value.type());
|
|
||||||
}
|
|
||||||
const auto &list = list_value.ValueList();
|
|
||||||
const auto &symbol = symbol_table_->at(*none.identifier_);
|
|
||||||
bool has_value = false;
|
|
||||||
for (const auto &element : list) {
|
|
||||||
frame_->at(symbol) = element;
|
|
||||||
auto result = none.where_->expression_->Accept(*this);
|
|
||||||
if (!result.IsNull() && result.type() != TypedValue::Type::Bool) {
|
|
||||||
throw ExpressionRuntimeException("Predicate of NONE must evaluate to boolean, got {}.", result.type());
|
|
||||||
}
|
|
||||||
if (!result.IsNull()) {
|
|
||||||
has_value = true;
|
|
||||||
if (result.ValueBool()) {
|
|
||||||
return TypedValue(false, ctx_->memory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Return Null if all elements are Null
|
|
||||||
if (!has_value) {
|
|
||||||
return TypedValue(ctx_->memory);
|
|
||||||
} else {
|
|
||||||
return TypedValue(true, ctx_->memory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(ParameterLookup ¶m_lookup) override {
|
|
||||||
return TypedValue(conv_(ctx_->parameters.AtTokenPosition(param_lookup.token_position_)), ctx_->memory);
|
|
||||||
}
|
|
||||||
|
|
||||||
TypedValue Visit(RegexMatch ®ex_match) override {
|
|
||||||
auto target_string_value = regex_match.string_expr_->Accept(*this);
|
|
||||||
auto regex_value = regex_match.regex_->Accept(*this);
|
|
||||||
if (target_string_value.IsNull() || regex_value.IsNull()) {
|
|
||||||
return TypedValue(ctx_->memory);
|
|
||||||
}
|
|
||||||
if (regex_value.type() != TypedValue::Type::String) {
|
|
||||||
throw ExpressionRuntimeException("Regular expression must evaluate to a string, got {}.", regex_value.type());
|
|
||||||
}
|
|
||||||
if (target_string_value.type() != TypedValue::Type::String) {
|
|
||||||
// Instead of error, we return Null which makes it compatible in case we
|
|
||||||
// use indexed lookup which filters out any non-string properties.
|
|
||||||
// Assuming a property lookup is the target_string_value.
|
|
||||||
return TypedValue(ctx_->memory);
|
|
||||||
}
|
|
||||||
const auto &target_string = target_string_value.ValueString();
|
|
||||||
try {
|
|
||||||
std::regex regex(regex_value.ValueString());
|
|
||||||
return TypedValue(std::regex_match(target_string, regex), ctx_->memory);
|
|
||||||
} catch (const std::regex_error &e) {
|
|
||||||
throw ExpressionRuntimeException("Regex error in '{}': {}", regex_value.ValueString(), e.what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
template <class TRecordAccessor, class TTag = Tag,
|
|
||||||
class TReturnType = std::enable_if_t<std::is_same_v<TTag, QueryEngineTag>, TypedValue>>
|
|
||||||
TReturnType GetProperty(const TRecordAccessor &record_accessor, PropertyIx prop) {
|
|
||||||
auto maybe_prop = record_accessor.GetProperty(prop.name);
|
|
||||||
// Handler non existent property
|
|
||||||
return conv_(maybe_prop, dba_);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class TRecordAccessor, class TTag = Tag,
|
|
||||||
class TReturnType = std::enable_if_t<std::is_same_v<TTag, QueryEngineTag>, TypedValue>>
|
|
||||||
TReturnType GetProperty(const TRecordAccessor &record_accessor, const std::string_view name) {
|
|
||||||
auto maybe_prop = record_accessor.GetProperty(std::string(name));
|
|
||||||
// Handler non existent property
|
|
||||||
return conv_(maybe_prop, dba_);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class TRecordAccessor, class TTag = Tag,
|
|
||||||
class TReturnType = std::enable_if_t<std::is_same_v<TTag, StorageTag>, TypedValue>>
|
|
||||||
TypedValue GetProperty(const TRecordAccessor &record_accessor, PropertyIx prop) {
|
|
||||||
auto maybe_prop = record_accessor.GetProperty(view_, ctx_->properties[prop.ix]);
|
|
||||||
if (maybe_prop.HasError() && maybe_prop.GetError() == Error::NONEXISTENT_OBJECT) {
|
|
||||||
// This is a very nasty and temporary hack in order to make MERGE work.
|
|
||||||
// The old storage had the following logic when returning an `OLD` view:
|
|
||||||
// `return old ? old : new`. That means that if the `OLD` view didn't
|
|
||||||
// exist, it returned the NEW view. With this hack we simulate that
|
|
||||||
// behavior.
|
|
||||||
// TODO (mferencevic, teon.banek): Remove once MERGE is reimplemented.
|
|
||||||
maybe_prop = record_accessor.GetProperty(StorageView::NEW, ctx_->properties[prop.ix]);
|
|
||||||
}
|
|
||||||
if (maybe_prop.HasError()) {
|
|
||||||
HandleObjectAccessError(maybe_prop.GetError().code, "property");
|
|
||||||
}
|
|
||||||
return conv_(*maybe_prop, ctx_->memory);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class TRecordAccessor, class TTag = Tag,
|
|
||||||
class TReturnType = std::enable_if_t<std::is_same_v<TTag, StorageTag>, TypedValue>>
|
|
||||||
TypedValue GetProperty(const TRecordAccessor &record_accessor, const std::string_view name) {
|
|
||||||
auto maybe_prop = record_accessor.GetProperty(view_, dba_->NameToProperty(name));
|
|
||||||
if (maybe_prop.HasError() && maybe_prop.GetError() == Error::NONEXISTENT_OBJECT) {
|
|
||||||
// This is a very nasty and temporary hack in order to make MERGE work.
|
|
||||||
// The old storage had the following logic when returning an `OLD` view:
|
|
||||||
// `return old ? old : new`. That means that if the `OLD` view didn't
|
|
||||||
// exist, it returned the NEW view. With this hack we simulate that
|
|
||||||
// behavior.
|
|
||||||
// TODO (mferencevic, teon.banek): Remove once MERGE is reimplemented.
|
|
||||||
maybe_prop = record_accessor.GetProperty(view_, dba_->NameToProperty(name));
|
|
||||||
}
|
|
||||||
if (maybe_prop.HasError()) {
|
|
||||||
HandleObjectAccessError(maybe_prop.GetError().code, "property");
|
|
||||||
}
|
|
||||||
return conv_(*maybe_prop, ctx_->memory);
|
|
||||||
}
|
|
||||||
|
|
||||||
LabelId GetLabel(LabelIx label) { return ctx_->labels[label.ix]; }
|
|
||||||
|
|
||||||
Frame *frame_;
|
|
||||||
const SymbolTable *symbol_table_;
|
|
||||||
const EvaluationContext *ctx_;
|
|
||||||
DbAccessor *dba_;
|
|
||||||
// which switching approach should be used when evaluating
|
|
||||||
StorageView view_;
|
|
||||||
ConvFunctor conv_;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A helper function for evaluating an expression that's an int.
|
|
||||||
///
|
|
||||||
/// @param what - Name of what's getting evaluated. Used for user feedback (via
|
|
||||||
/// exception) when the evaluated value is not an int.
|
|
||||||
/// @throw ExpressionRuntimeException if expression doesn't evaluate to an int.
|
|
||||||
template <typename ExpressionEvaluator>
|
|
||||||
int64_t EvaluateInt(ExpressionEvaluator *evaluator, Expression *expr, const std::string &what) {
|
|
||||||
TypedValue value = expr->Accept(*evaluator);
|
|
||||||
try {
|
|
||||||
return value.ValueInt();
|
|
||||||
} catch (TypedValueException &e) {
|
|
||||||
throw ExpressionRuntimeException(what + " must be an int");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename ExpressionEvaluator>
|
|
||||||
std::optional<size_t> EvaluateMemoryLimit(ExpressionEvaluator *eval, Expression *memory_limit, size_t memory_scale) {
|
|
||||||
if (!memory_limit) return std::nullopt;
|
|
||||||
auto limit_value = memory_limit->Accept(*eval);
|
|
||||||
if (!limit_value.IsInt() || limit_value.ValueInt() <= 0)
|
|
||||||
throw ExpressionRuntimeException("Memory limit must be a non-negative integer.");
|
|
||||||
size_t limit = limit_value.ValueInt();
|
|
||||||
if (std::numeric_limits<size_t>::max() / memory_scale < limit)
|
|
||||||
throw ExpressionRuntimeException("Memory limit overflow.");
|
|
||||||
return limit * memory_scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace memgraph::expr
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
// Copyright 2023 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "expr/semantic/symbol_table.hpp"
|
|
||||||
#include "utils/logging.hpp"
|
|
||||||
#include "utils/memory.hpp"
|
|
||||||
#include "utils/pmr/vector.hpp"
|
|
||||||
|
|
||||||
namespace memgraph::expr {
|
|
||||||
|
|
||||||
class Frame {
|
|
||||||
public:
|
|
||||||
/// Create a Frame of given size backed by a utils::NewDeleteResource()
|
|
||||||
explicit Frame(size_t size) : elems_(size, utils::NewDeleteResource()) { MG_ASSERT(size >= 0); }
|
|
||||||
|
|
||||||
Frame(size_t size, utils::MemoryResource *memory) : elems_(size, memory) { MG_ASSERT(size >= 0); }
|
|
||||||
|
|
||||||
TypedValue &operator[](const Symbol &symbol) { return elems_[symbol.position()]; }
|
|
||||||
const TypedValue &operator[](const Symbol &symbol) const { return elems_[symbol.position()]; }
|
|
||||||
|
|
||||||
TypedValue &at(const Symbol &symbol) { return elems_.at(symbol.position()); }
|
|
||||||
const TypedValue &at(const Symbol &symbol) const { return elems_.at(symbol.position()); }
|
|
||||||
|
|
||||||
auto &elems() { return elems_; }
|
|
||||||
const auto &elems() const { return elems_; }
|
|
||||||
|
|
||||||
utils::MemoryResource *GetMemoryResource() const { return elems_.get_allocator().GetMemoryResource(); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
utils::pmr::vector<TypedValue> elems_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class FrameWithValidity final : public Frame {
|
|
||||||
public:
|
|
||||||
explicit FrameWithValidity(size_t size) : Frame(size), is_valid_(false) {}
|
|
||||||
|
|
||||||
FrameWithValidity(size_t size, utils::MemoryResource *memory) : Frame(size, memory), is_valid_(false) {}
|
|
||||||
|
|
||||||
bool IsValid() const noexcept { return is_valid_; }
|
|
||||||
void MakeValid() noexcept { is_valid_ = true; }
|
|
||||||
void MakeInvalid() noexcept { is_valid_ = false; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool is_valid_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace memgraph::expr
|
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
#include "expr/parsing.hpp"
|
|
||||||
|
|
||||||
#include <cctype>
|
|
||||||
#include <codecvt>
|
|
||||||
#include <locale>
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
#include "expr/exceptions.hpp"
|
|
||||||
#include "utils/logging.hpp"
|
|
||||||
#include "utils/string.hpp"
|
|
||||||
|
|
||||||
namespace memgraph::expr {
|
|
||||||
|
|
||||||
int64_t ParseIntegerLiteral(const std::string &s) {
|
|
||||||
try {
|
|
||||||
// Not really correct since long long can have a bigger range than int64_t.
|
|
||||||
return static_cast<int64_t>(std::stoll(s, 0, 0));
|
|
||||||
} catch (const std::out_of_range &) {
|
|
||||||
throw SemanticException("Integer literal exceeds 64 bits.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ParseStringLiteral(const std::string &s) {
|
|
||||||
// These functions is declared as lambda since its semantics is highly
|
|
||||||
// specific for this conxtext and shouldn't be used elsewhere.
|
|
||||||
auto EncodeEscapedUnicodeCodepointUtf32 = [](const std::string &s, int &i) {
|
|
||||||
const int kLongUnicodeLength = 8;
|
|
||||||
int j = i + 1;
|
|
||||||
while (j < static_cast<int>(s.size()) - 1 && j < i + kLongUnicodeLength + 1 && isxdigit(s[j])) {
|
|
||||||
++j;
|
|
||||||
}
|
|
||||||
if (j - i == kLongUnicodeLength + 1) {
|
|
||||||
char32_t t = stoi(s.substr(i + 1, kLongUnicodeLength), 0, 16);
|
|
||||||
i += kLongUnicodeLength;
|
|
||||||
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> converter;
|
|
||||||
return converter.to_bytes(t);
|
|
||||||
}
|
|
||||||
throw SyntaxException(
|
|
||||||
"Expected 8 hex digits as unicode codepoint started with \\U. "
|
|
||||||
"Use \\u for 4 hex digits format.");
|
|
||||||
};
|
|
||||||
auto EncodeEscapedUnicodeCodepointUtf16 = [](const std::string &s, int &i) {
|
|
||||||
const int kShortUnicodeLength = 4;
|
|
||||||
int j = i + 1;
|
|
||||||
while (j < static_cast<int>(s.size()) - 1 && j < i + kShortUnicodeLength + 1 && isxdigit(s[j])) {
|
|
||||||
++j;
|
|
||||||
}
|
|
||||||
if (j - i >= kShortUnicodeLength + 1) {
|
|
||||||
char16_t t = stoi(s.substr(i + 1, kShortUnicodeLength), 0, 16);
|
|
||||||
if (t >= 0xD800 && t <= 0xDBFF) {
|
|
||||||
// t is high surrogate pair. Expect one more utf16 codepoint.
|
|
||||||
j = i + kShortUnicodeLength + 1;
|
|
||||||
if (j >= static_cast<int>(s.size()) - 1 || s[j] != '\\') {
|
|
||||||
throw SemanticException("Invalid UTF codepoint.");
|
|
||||||
}
|
|
||||||
++j;
|
|
||||||
if (j >= static_cast<int>(s.size()) - 1 || (s[j] != 'u' && s[j] != 'U')) {
|
|
||||||
throw SemanticException("Invalid UTF codepoint.");
|
|
||||||
}
|
|
||||||
++j;
|
|
||||||
int k = j;
|
|
||||||
while (k < static_cast<int>(s.size()) - 1 && k < j + kShortUnicodeLength && isxdigit(s[k])) {
|
|
||||||
++k;
|
|
||||||
}
|
|
||||||
if (k != j + kShortUnicodeLength) {
|
|
||||||
throw SemanticException("Invalid UTF codepoint.");
|
|
||||||
}
|
|
||||||
char16_t surrogates[3] = {t, static_cast<char16_t>(stoi(s.substr(j, kShortUnicodeLength), 0, 16)), 0};
|
|
||||||
i += kShortUnicodeLength + 2 + kShortUnicodeLength;
|
|
||||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter;
|
|
||||||
return converter.to_bytes(surrogates);
|
|
||||||
} else {
|
|
||||||
i += kShortUnicodeLength;
|
|
||||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter;
|
|
||||||
return converter.to_bytes(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw SyntaxException(
|
|
||||||
"Expected 4 hex digits as unicode codepoint started with \\u. "
|
|
||||||
"Use \\U for 8 hex digits format.");
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string unescaped;
|
|
||||||
bool escape = false;
|
|
||||||
|
|
||||||
// First and last char is quote, we don't need to look at them.
|
|
||||||
for (int i = 1; i < static_cast<int>(s.size()) - 1; ++i) {
|
|
||||||
if (escape) {
|
|
||||||
switch (s[i]) {
|
|
||||||
case '\\':
|
|
||||||
unescaped += '\\';
|
|
||||||
break;
|
|
||||||
case '\'':
|
|
||||||
unescaped += '\'';
|
|
||||||
break;
|
|
||||||
case '"':
|
|
||||||
unescaped += '"';
|
|
||||||
break;
|
|
||||||
case 'B':
|
|
||||||
case 'b':
|
|
||||||
unescaped += '\b';
|
|
||||||
break;
|
|
||||||
case 'F':
|
|
||||||
case 'f':
|
|
||||||
unescaped += '\f';
|
|
||||||
break;
|
|
||||||
case 'N':
|
|
||||||
case 'n':
|
|
||||||
unescaped += '\n';
|
|
||||||
break;
|
|
||||||
case 'R':
|
|
||||||
case 'r':
|
|
||||||
unescaped += '\r';
|
|
||||||
break;
|
|
||||||
case 'T':
|
|
||||||
case 't':
|
|
||||||
unescaped += '\t';
|
|
||||||
break;
|
|
||||||
case 'U':
|
|
||||||
try {
|
|
||||||
unescaped += EncodeEscapedUnicodeCodepointUtf32(s, i);
|
|
||||||
} catch (const std::range_error &) {
|
|
||||||
throw SemanticException("Invalid UTF codepoint.");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'u':
|
|
||||||
try {
|
|
||||||
unescaped += EncodeEscapedUnicodeCodepointUtf16(s, i);
|
|
||||||
} catch (const std::range_error &) {
|
|
||||||
throw SemanticException("Invalid UTF codepoint.");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// This should never happen, except grammar changes and we don't
|
|
||||||
// notice change in this production.
|
|
||||||
DLOG_FATAL("can't happen");
|
|
||||||
throw std::exception();
|
|
||||||
}
|
|
||||||
escape = false;
|
|
||||||
} else if (s[i] == '\\') {
|
|
||||||
escape = true;
|
|
||||||
} else {
|
|
||||||
unescaped += s[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return unescaped;
|
|
||||||
}
|
|
||||||
|
|
||||||
double ParseDoubleLiteral(const std::string &s) {
|
|
||||||
try {
|
|
||||||
return utils::ParseDouble(s);
|
|
||||||
} catch (const utils::BasicException &) {
|
|
||||||
throw SemanticException("Couldn't parse string to double.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ParseParameter(const std::string &s) {
|
|
||||||
DMG_ASSERT(s[0] == '$', "Invalid string passed as parameter name");
|
|
||||||
if (s[1] != '`') return s.substr(1);
|
|
||||||
// If parameter name is escaped symbolic name then symbolic name should be
|
|
||||||
// unescaped and leading and trailing backquote should be removed.
|
|
||||||
DMG_ASSERT(s.size() > 3U && s.back() == '`', "Invalid string passed as parameter name");
|
|
||||||
std::string out;
|
|
||||||
for (int i = 2; i < static_cast<int>(s.size()) - 1; ++i) {
|
|
||||||
if (s[i] == '`') {
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
out.push_back(s[i]);
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace memgraph::expr
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
/// @file
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace memgraph::expr {
|
|
||||||
|
|
||||||
// These are the functions for parsing literals and parameter names from
|
|
||||||
// opencypher query.
|
|
||||||
int64_t ParseIntegerLiteral(const std::string &s);
|
|
||||||
std::string ParseStringLiteral(const std::string &s);
|
|
||||||
double ParseDoubleLiteral(const std::string &s);
|
|
||||||
std::string ParseParameter(const std::string &s);
|
|
||||||
|
|
||||||
} // namespace memgraph::expr
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
;; Copyright 2022 Memgraph Ltd.
|
|
||||||
;;
|
|
||||||
;; Use of this software is governed by the Business Source License
|
|
||||||
;; included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
;; License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
;;
|
|
||||||
;; As of the Change Date specified in that file, in accordance with
|
|
||||||
;; the Business Source License, use of this software will be governed
|
|
||||||
;; by the Apache License, Version 2.0, included in the file
|
|
||||||
;; licenses/APL.txt.
|
|
||||||
|
|
||||||
#>cpp
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "utils/typeinfo.hpp"
|
|
||||||
cpp<#
|
|
||||||
|
|
||||||
(lcp:namespace memgraph)
|
|
||||||
(lcp:namespace expr)
|
|
||||||
|
|
||||||
(lcp:define-class symbol ()
|
|
||||||
((name "std::string" :scope :public)
|
|
||||||
(position :int64_t :scope :public)
|
|
||||||
(user-declared :bool :initval "true" :scope :public)
|
|
||||||
(type "Type" :initval "Type::ANY" :scope :public)
|
|
||||||
(token-position :int64_t :initval "-1" :scope :public))
|
|
||||||
(:public
|
|
||||||
;; This is similar to TypedValue::Type, but this has `Any` type.
|
|
||||||
;; TODO: Make a better Type structure which can store a generic List.
|
|
||||||
(lcp:define-enum type (any vertex edge path number edge-list)
|
|
||||||
(:serialize))
|
|
||||||
#>cpp
|
|
||||||
// TODO: Generate enum to string conversion from LCP. Note, that this is
|
|
||||||
// displayed to the end user, so we may want to have a pretty name of each
|
|
||||||
// value.
|
|
||||||
static std::string TypeToString(Type type) {
|
|
||||||
const char *enum_string[] = {"Any", "Vertex", "Edge",
|
|
||||||
"Path", "Number", "EdgeList"};
|
|
||||||
return enum_string[static_cast<int>(type)];
|
|
||||||
}
|
|
||||||
|
|
||||||
Symbol() {}
|
|
||||||
Symbol(const std::string &name, int position, bool user_declared,
|
|
||||||
Type type = Type::ANY, int token_position = -1)
|
|
||||||
: name_(name),
|
|
||||||
position_(position),
|
|
||||||
user_declared_(user_declared),
|
|
||||||
type_(type),
|
|
||||||
token_position_(token_position) {}
|
|
||||||
|
|
||||||
bool operator==(const Symbol &other) const {
|
|
||||||
return position_ == other.position_ && name_ == other.name_ &&
|
|
||||||
type_ == other.type_;
|
|
||||||
}
|
|
||||||
bool operator!=(const Symbol &other) const { return !operator==(other); }
|
|
||||||
|
|
||||||
// TODO: Remove these since members are public
|
|
||||||
const auto &name() const { return name_; }
|
|
||||||
int position() const { return position_; }
|
|
||||||
Type type() const { return type_; }
|
|
||||||
bool user_declared() const { return user_declared_; }
|
|
||||||
int token_position() const { return token_position_; }
|
|
||||||
cpp<#)
|
|
||||||
(:serialize (:slk)))
|
|
||||||
|
|
||||||
(lcp:pop-namespace) ;; expr
|
|
||||||
(lcp:pop-namespace) ;; memgraph
|
|
||||||
|
|
||||||
#>cpp
|
|
||||||
namespace std {
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct hash<memgraph::expr::Symbol> {
|
|
||||||
size_t operator()(const memgraph::expr::Symbol &symbol) const {
|
|
||||||
size_t prime = 265443599u;
|
|
||||||
size_t hash = std::hash<int>{}(symbol.position());
|
|
||||||
hash ^= prime * std::hash<std::string>{}(symbol.name());
|
|
||||||
hash ^= prime * std::hash<int>{}(static_cast<int>(symbol.type()));
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace std
|
|
||||||
|
|
||||||
cpp<#
|
|
||||||
@@ -1,712 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
// Copyright 2017 Memgraph
|
|
||||||
//
|
|
||||||
// Created by Teon Banek on 11-03-2017
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <optional>
|
|
||||||
#include <ranges>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <variant>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "expr/ast.hpp"
|
|
||||||
#include "expr/ast/ast_visitor.hpp"
|
|
||||||
#include "expr/exceptions.hpp"
|
|
||||||
#include "expr/semantic/symbol_table.hpp"
|
|
||||||
|
|
||||||
namespace memgraph::expr {
|
|
||||||
namespace detail {
|
|
||||||
inline std::unordered_map<std::string, Identifier *> GeneratePredefinedIdentifierMap(
|
|
||||||
const std::vector<Identifier *> &predefined_identifiers) {
|
|
||||||
std::unordered_map<std::string, Identifier *> identifier_map;
|
|
||||||
for (const auto &identifier : predefined_identifiers) {
|
|
||||||
identifier_map.emplace(identifier->name_, identifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
return identifier_map;
|
|
||||||
}
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
/// Visits the AST and generates symbols for variables.
|
|
||||||
///
|
|
||||||
/// During the process of symbol generation, simple semantic checks are
|
|
||||||
/// performed. Such as, redeclaring a variable or conflicting expectations of
|
|
||||||
/// variable types.
|
|
||||||
class SymbolGenerator : public HierarchicalTreeVisitor {
|
|
||||||
public:
|
|
||||||
explicit SymbolGenerator(SymbolTable *symbol_table, const std::vector<Identifier *> &predefined_identifiers)
|
|
||||||
: symbol_table_(symbol_table),
|
|
||||||
predefined_identifiers_{detail::GeneratePredefinedIdentifierMap(predefined_identifiers)},
|
|
||||||
scopes_(1, Scope()) {}
|
|
||||||
|
|
||||||
using HierarchicalTreeVisitor::PostVisit;
|
|
||||||
using HierarchicalTreeVisitor::PreVisit;
|
|
||||||
using HierarchicalTreeVisitor::Visit;
|
|
||||||
using typename HierarchicalTreeVisitor::ReturnType;
|
|
||||||
|
|
||||||
// Query
|
|
||||||
bool PreVisit(SingleQuery & /*unused*/) override {
|
|
||||||
prev_return_names_ = curr_return_names_;
|
|
||||||
curr_return_names_.clear();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Union
|
|
||||||
bool PreVisit(CypherUnion & /*unused*/) override {
|
|
||||||
scopes_.back() = Scope();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PostVisit(CypherUnion &cypher_union) override {
|
|
||||||
if (prev_return_names_ != curr_return_names_) {
|
|
||||||
throw SemanticException("All subqueries in an UNION must have the same column names.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// create new symbols for the result of the union
|
|
||||||
for (const auto &name : curr_return_names_) {
|
|
||||||
auto symbol = CreateSymbol(name, false);
|
|
||||||
cypher_union.union_symbols_.push_back(symbol);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clauses
|
|
||||||
bool PreVisit(Create & /*unused*/) override {
|
|
||||||
scopes_.back().in_create = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PostVisit(Create & /*unused*/) override {
|
|
||||||
scopes_.back().in_create = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PreVisit(CallProcedure &call_proc) override {
|
|
||||||
for (auto *expr : call_proc.arguments_) {
|
|
||||||
expr->Accept(*this);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PostVisit(CallProcedure &call_proc) override {
|
|
||||||
for (auto *ident : call_proc.result_identifiers_) {
|
|
||||||
if (HasSymbolLocalScope(ident->name_)) {
|
|
||||||
throw RedeclareVariableError(ident->name_);
|
|
||||||
}
|
|
||||||
ident->MapTo(CreateSymbol(ident->name_, true));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PreVisit(LoadCsv & /*unused*/) override { return false; }
|
|
||||||
|
|
||||||
bool PostVisit(LoadCsv &load_csv) override {
|
|
||||||
if (HasSymbolLocalScope(load_csv.row_var_->name_)) {
|
|
||||||
throw RedeclareVariableError(load_csv.row_var_->name_);
|
|
||||||
}
|
|
||||||
load_csv.row_var_->MapTo(CreateSymbol(load_csv.row_var_->name_, true));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PreVisit(Return &ret) override {
|
|
||||||
auto &scope = scopes_.back();
|
|
||||||
scope.in_return = true;
|
|
||||||
VisitReturnBody(ret.body_);
|
|
||||||
scope.in_return = false;
|
|
||||||
return false; // We handled the traversal ourselves.
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PostVisit(Return & /*unused*/) override {
|
|
||||||
for (const auto &name_symbol : scopes_.back().symbols) curr_return_names_.insert(name_symbol.first);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PreVisit(With &with) override {
|
|
||||||
auto &scope = scopes_.back();
|
|
||||||
scope.in_with = true;
|
|
||||||
VisitReturnBody(with.body_, with.where_);
|
|
||||||
scope.in_with = false;
|
|
||||||
return false; // We handled the traversal ourselves.
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PreVisit(Where & /*unused*/) override {
|
|
||||||
scopes_.back().in_where = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PostVisit(Where & /*unused*/) override {
|
|
||||||
scopes_.back().in_where = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PreVisit(Merge & /*unused*/) override {
|
|
||||||
scopes_.back().in_merge = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PostVisit(Merge & /*unused*/) override {
|
|
||||||
scopes_.back().in_merge = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PostVisit(Unwind &unwind) override {
|
|
||||||
const auto &name = unwind.named_expression_->name_;
|
|
||||||
if (HasSymbolLocalScope(name)) {
|
|
||||||
throw RedeclareVariableError(name);
|
|
||||||
}
|
|
||||||
unwind.named_expression_->MapTo(CreateSymbol(name, true));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PreVisit(Match & /*unused*/) override {
|
|
||||||
scopes_.back().in_match = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PostVisit(Match & /*unused*/) override {
|
|
||||||
auto &scope = scopes_.back();
|
|
||||||
scope.in_match = false;
|
|
||||||
// Check variables in property maps after visiting Match, so that they can
|
|
||||||
// reference symbols out of bind order.
|
|
||||||
for (auto &ident : scope.identifiers_in_match) {
|
|
||||||
if (!HasSymbolLocalScope(ident->name_) && !ConsumePredefinedIdentifier(ident->name_))
|
|
||||||
throw UnboundVariableError(ident->name_);
|
|
||||||
ident->MapTo(scope.symbols[ident->name_]);
|
|
||||||
}
|
|
||||||
scope.identifiers_in_match.clear();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PreVisit(Foreach &for_each) override {
|
|
||||||
const auto &name = for_each.named_expression_->name_;
|
|
||||||
scopes_.emplace_back(Scope());
|
|
||||||
scopes_.back().in_foreach = true;
|
|
||||||
for_each.named_expression_->MapTo(
|
|
||||||
CreateSymbol(name, true, Symbol::Type::ANY, for_each.named_expression_->token_position_));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PostVisit(Foreach & /*unused*/) override {
|
|
||||||
scopes_.pop_back();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expressions
|
|
||||||
ReturnType Visit(Identifier &ident) override {
|
|
||||||
auto &scope = scopes_.back();
|
|
||||||
if (scope.in_skip || scope.in_limit) {
|
|
||||||
throw SemanticException("Variables are not allowed in {}.", scope.in_skip ? "SKIP" : "LIMIT");
|
|
||||||
}
|
|
||||||
Symbol symbol;
|
|
||||||
if (scope.in_pattern && !(scope.in_node_atom || scope.visiting_edge)) {
|
|
||||||
// If we are in the pattern, and outside of a node or an edge, the
|
|
||||||
// identifier is the pattern name.
|
|
||||||
symbol = GetOrCreateSymbolLocalScope(ident.name_, ident.user_declared_, Symbol::Type::PATH);
|
|
||||||
} else if (scope.in_pattern && scope.in_pattern_atom_identifier) {
|
|
||||||
// Patterns used to create nodes and edges cannot redeclare already
|
|
||||||
// established bindings. Declaration only happens in single node
|
|
||||||
// patterns and in edge patterns. OpenCypher example,
|
|
||||||
// `MATCH (n) CREATE (n)` should throw an error that `n` is already
|
|
||||||
// declared. While `MATCH (n) CREATE (n) -[:R]-> (n)` is allowed,
|
|
||||||
// since `n` now references the bound node instead of declaring it.
|
|
||||||
if ((scope.in_create_node || scope.in_create_edge) && HasSymbolLocalScope(ident.name_)) {
|
|
||||||
throw RedeclareVariableError(ident.name_);
|
|
||||||
}
|
|
||||||
auto type = Symbol::Type::VERTEX;
|
|
||||||
if (scope.visiting_edge) {
|
|
||||||
// Edge referencing is not allowed (like in Neo4j):
|
|
||||||
// `MATCH (n) - [r] -> (n) - [r] -> (n) RETURN r` is not allowed.
|
|
||||||
if (HasSymbolLocalScope(ident.name_)) {
|
|
||||||
throw RedeclareVariableError(ident.name_);
|
|
||||||
}
|
|
||||||
type = scope.visiting_edge->IsVariable() ? Symbol::Type::EDGE_LIST : Symbol::Type::EDGE;
|
|
||||||
}
|
|
||||||
symbol = GetOrCreateSymbolLocalScope(ident.name_, ident.user_declared_, type);
|
|
||||||
} else if (scope.in_pattern && !scope.in_pattern_atom_identifier && scope.in_match) {
|
|
||||||
if (scope.in_edge_range && scope.visiting_edge->identifier_->name_ == ident.name_) {
|
|
||||||
// Prevent variable path bounds to reference the identifier which is bound
|
|
||||||
// by the variable path itself.
|
|
||||||
throw UnboundVariableError(ident.name_);
|
|
||||||
}
|
|
||||||
// Variables in property maps or bounds of variable length path during MATCH
|
|
||||||
// can reference symbols bound later in the same MATCH. We collect them
|
|
||||||
// here, so that they can be checked after visiting Match.
|
|
||||||
scope.identifiers_in_match.emplace_back(&ident);
|
|
||||||
} else {
|
|
||||||
// Everything else references a bound symbol.
|
|
||||||
if (!HasSymbol(ident.name_) && !ConsumePredefinedIdentifier(ident.name_)) throw UnboundVariableError(ident.name_);
|
|
||||||
symbol = GetOrCreateSymbol(ident.name_, ident.user_declared_, Symbol::Type::ANY);
|
|
||||||
}
|
|
||||||
ident.MapTo(symbol);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ReturnType Visit(PrimitiveLiteral & /*unused*/) override { return true; }
|
|
||||||
|
|
||||||
ReturnType Visit(ParameterLookup & /*unused*/) override { return true; }
|
|
||||||
|
|
||||||
bool PreVisit(Aggregation &aggr) override {
|
|
||||||
auto &scope = scopes_.back();
|
|
||||||
// Check if the aggregation can be used in this context. This check should
|
|
||||||
// probably move to a separate phase, which checks if the query is well
|
|
||||||
// formed.
|
|
||||||
if ((!scope.in_return && !scope.in_with) || scope.in_order_by || scope.in_skip || scope.in_limit ||
|
|
||||||
scope.in_where) {
|
|
||||||
throw SemanticException("Aggregation functions are only allowed in WITH and RETURN.");
|
|
||||||
}
|
|
||||||
if (scope.in_aggregation) {
|
|
||||||
throw SemanticException(
|
|
||||||
"Using aggregation functions inside aggregation functions is not "
|
|
||||||
"allowed.");
|
|
||||||
}
|
|
||||||
if (scope.num_if_operators) {
|
|
||||||
// Neo allows aggregations here and produces very interesting behaviors.
|
|
||||||
// To simplify implementation at this moment we decided to completely
|
|
||||||
// disallow aggregations inside of the CASE.
|
|
||||||
// However, in some cases aggregation makes perfect sense, for example:
|
|
||||||
// CASE count(n) WHEN 10 THEN "YES" ELSE "NO" END.
|
|
||||||
// TODO: Rethink of allowing aggregations in some parts of the CASE
|
|
||||||
// construct.
|
|
||||||
throw SemanticException("Using aggregation functions inside of CASE is not allowed.");
|
|
||||||
}
|
|
||||||
// Create a virtual symbol for aggregation result.
|
|
||||||
// Currently, we only have aggregation operators which return numbers.
|
|
||||||
auto aggr_name = Aggregation::OpToString(aggr.op_) + std::to_string(aggr.symbol_pos_);
|
|
||||||
aggr.MapTo(CreateSymbol(aggr_name, false, Symbol::Type::NUMBER));
|
|
||||||
scope.in_aggregation = true;
|
|
||||||
scope.has_aggregation = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PostVisit(Aggregation & /*unused*/) override {
|
|
||||||
scopes_.back().in_aggregation = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PreVisit(IfOperator & /*unused*/) override {
|
|
||||||
++scopes_.back().num_if_operators;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PostVisit(IfOperator & /*unused*/) override {
|
|
||||||
--scopes_.back().num_if_operators;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PreVisit(All &all) override {
|
|
||||||
all.list_expression_->Accept(*this);
|
|
||||||
VisitWithIdentifiers(all.where_->expression_, {all.identifier_});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PreVisit(Single &single) override {
|
|
||||||
single.list_expression_->Accept(*this);
|
|
||||||
VisitWithIdentifiers(single.where_->expression_, {single.identifier_});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PreVisit(Any &any) override {
|
|
||||||
any.list_expression_->Accept(*this);
|
|
||||||
VisitWithIdentifiers(any.where_->expression_, {any.identifier_});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PreVisit(None &none) override {
|
|
||||||
none.list_expression_->Accept(*this);
|
|
||||||
VisitWithIdentifiers(none.where_->expression_, {none.identifier_});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PreVisit(Reduce &reduce) override {
|
|
||||||
reduce.initializer_->Accept(*this);
|
|
||||||
reduce.list_->Accept(*this);
|
|
||||||
VisitWithIdentifiers(reduce.expression_, {reduce.accumulator_, reduce.identifier_});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PreVisit(Extract &extract) override {
|
|
||||||
extract.list_->Accept(*this);
|
|
||||||
VisitWithIdentifiers(extract.expression_, {extract.identifier_});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pattern and its subparts.
|
|
||||||
bool PreVisit(Pattern &pattern) override {
|
|
||||||
auto &scope = scopes_.back();
|
|
||||||
scope.in_pattern = true;
|
|
||||||
if ((scope.in_create || scope.in_merge) && pattern.atoms_.size() == 1U) {
|
|
||||||
MG_ASSERT(utils::IsSubtype(*pattern.atoms_[0], NodeAtom::kType), "Expected a single NodeAtom in Pattern");
|
|
||||||
scope.in_create_node = true;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PostVisit(Pattern & /*unused*/) override {
|
|
||||||
auto &scope = scopes_.back();
|
|
||||||
scope.in_pattern = false;
|
|
||||||
scope.in_create_node = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PreVisit(NodeAtom &node_atom) override {
|
|
||||||
auto &scope = scopes_.back();
|
|
||||||
auto check_node_semantic = [&node_atom, &scope, this](const bool props_or_labels) {
|
|
||||||
const auto &node_name = node_atom.identifier_->name_;
|
|
||||||
if ((scope.in_create || scope.in_merge) && props_or_labels && HasSymbolLocalScope(node_name)) {
|
|
||||||
throw SemanticException("Cannot create node '" + node_name +
|
|
||||||
"' with labels or properties, because it is already declared.");
|
|
||||||
}
|
|
||||||
scope.in_pattern_atom_identifier = true;
|
|
||||||
node_atom.identifier_->Accept(*this);
|
|
||||||
scope.in_pattern_atom_identifier = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
scope.in_node_atom = true;
|
|
||||||
if (auto *properties = std::get_if<std::unordered_map<PropertyIx, Expression *>>(&node_atom.properties_)) {
|
|
||||||
bool props_or_labels = !properties->empty() || !node_atom.labels_.empty();
|
|
||||||
|
|
||||||
check_node_semantic(props_or_labels);
|
|
||||||
for (auto kv : *properties) {
|
|
||||||
kv.second->Accept(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
auto &properties_parameter = std::get<ParameterLookup *>(node_atom.properties_);
|
|
||||||
bool props_or_labels = !properties_parameter || !node_atom.labels_.empty();
|
|
||||||
|
|
||||||
check_node_semantic(props_or_labels);
|
|
||||||
properties_parameter->Accept(*this);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PostVisit(NodeAtom & /*unused*/) override {
|
|
||||||
scopes_.back().in_node_atom = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PreVisit(EdgeAtom &edge_atom) override {
|
|
||||||
auto &scope = scopes_.back();
|
|
||||||
scope.visiting_edge = &edge_atom;
|
|
||||||
if (scope.in_create || scope.in_merge) {
|
|
||||||
scope.in_create_edge = true;
|
|
||||||
if (edge_atom.edge_types_.size() != 1U) {
|
|
||||||
throw SemanticException(
|
|
||||||
"A single relationship type must be specified "
|
|
||||||
"when creating an edge.");
|
|
||||||
}
|
|
||||||
if (scope.in_create && // Merge allows bidirectionality
|
|
||||||
edge_atom.direction_ == EdgeAtom::Direction::BOTH) {
|
|
||||||
throw SemanticException(
|
|
||||||
"Bidirectional relationship are not supported "
|
|
||||||
"when creating an edge");
|
|
||||||
}
|
|
||||||
if (edge_atom.IsVariable()) {
|
|
||||||
throw SemanticException(
|
|
||||||
"Variable length relationships are not supported when creating an "
|
|
||||||
"edge.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (auto *properties = std::get_if<std::unordered_map<PropertyIx, Expression *>>(&edge_atom.properties_)) {
|
|
||||||
for (auto kv : *properties) {
|
|
||||||
kv.second->Accept(*this);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
std::get<ParameterLookup *>(edge_atom.properties_)->Accept(*this);
|
|
||||||
}
|
|
||||||
if (edge_atom.IsVariable()) {
|
|
||||||
scope.in_edge_range = true;
|
|
||||||
if (edge_atom.lower_bound_) {
|
|
||||||
edge_atom.lower_bound_->Accept(*this);
|
|
||||||
}
|
|
||||||
if (edge_atom.upper_bound_) {
|
|
||||||
edge_atom.upper_bound_->Accept(*this);
|
|
||||||
}
|
|
||||||
scope.in_edge_range = false;
|
|
||||||
scope.in_pattern = false;
|
|
||||||
if (edge_atom.filter_lambda_.expression) {
|
|
||||||
VisitWithIdentifiers(edge_atom.filter_lambda_.expression,
|
|
||||||
{edge_atom.filter_lambda_.inner_edge, edge_atom.filter_lambda_.inner_node});
|
|
||||||
} else {
|
|
||||||
// Create inner symbols, but don't bind them in scope, since they are to
|
|
||||||
// be used in the missing filter expression.
|
|
||||||
auto *inner_edge = edge_atom.filter_lambda_.inner_edge;
|
|
||||||
inner_edge->MapTo(
|
|
||||||
symbol_table_->CreateSymbol(inner_edge->name_, inner_edge->user_declared_, Symbol::Type::EDGE));
|
|
||||||
auto *inner_node = edge_atom.filter_lambda_.inner_node;
|
|
||||||
inner_node->MapTo(
|
|
||||||
symbol_table_->CreateSymbol(inner_node->name_, inner_node->user_declared_, Symbol::Type::VERTEX));
|
|
||||||
}
|
|
||||||
if (edge_atom.weight_lambda_.expression) {
|
|
||||||
VisitWithIdentifiers(edge_atom.weight_lambda_.expression,
|
|
||||||
{edge_atom.weight_lambda_.inner_edge, edge_atom.weight_lambda_.inner_node});
|
|
||||||
}
|
|
||||||
scope.in_pattern = true;
|
|
||||||
}
|
|
||||||
scope.in_pattern_atom_identifier = true;
|
|
||||||
edge_atom.identifier_->Accept(*this);
|
|
||||||
scope.in_pattern_atom_identifier = false;
|
|
||||||
if (edge_atom.total_weight_) {
|
|
||||||
if (HasSymbolLocalScope(edge_atom.total_weight_->name_)) {
|
|
||||||
throw RedeclareVariableError(edge_atom.total_weight_->name_);
|
|
||||||
}
|
|
||||||
edge_atom.total_weight_->MapTo(GetOrCreateSymbolLocalScope(
|
|
||||||
edge_atom.total_weight_->name_, edge_atom.total_weight_->user_declared_, Symbol::Type::NUMBER));
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PostVisit(EdgeAtom & /*unused*/) override {
|
|
||||||
auto &scope = scopes_.back();
|
|
||||||
scope.visiting_edge = nullptr;
|
|
||||||
scope.in_create_edge = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Scope stores the state of where we are when visiting the AST and a map of
|
|
||||||
// names to symbols.
|
|
||||||
struct Scope {
|
|
||||||
bool in_pattern{false};
|
|
||||||
bool in_merge{false};
|
|
||||||
bool in_create{false};
|
|
||||||
// in_create_node is true if we are creating or merging *only* a node.
|
|
||||||
// Therefore, it is *not* equivalent to (in_create || in_merge) &&
|
|
||||||
// in_node_atom.
|
|
||||||
bool in_create_node{false};
|
|
||||||
// True if creating an edge;
|
|
||||||
// shortcut for (in_create || in_merge) && visiting_edge.
|
|
||||||
bool in_create_edge{false};
|
|
||||||
bool in_node_atom{false};
|
|
||||||
EdgeAtom *visiting_edge{nullptr};
|
|
||||||
bool in_aggregation{false};
|
|
||||||
bool in_return{false};
|
|
||||||
bool in_with{false};
|
|
||||||
bool in_skip{false};
|
|
||||||
bool in_limit{false};
|
|
||||||
bool in_order_by{false};
|
|
||||||
bool in_where{false};
|
|
||||||
bool in_match{false};
|
|
||||||
bool in_foreach{false};
|
|
||||||
// True when visiting a pattern atom (node or edge) identifier, which can be
|
|
||||||
// reused or created in the pattern itself.
|
|
||||||
bool in_pattern_atom_identifier{false};
|
|
||||||
// True when visiting range bounds of a variable path.
|
|
||||||
bool in_edge_range{false};
|
|
||||||
// True if the return/with contains an aggregation in any named expression.
|
|
||||||
bool has_aggregation{false};
|
|
||||||
// Map from variable names to symbols.
|
|
||||||
std::map<std::string, Symbol> symbols;
|
|
||||||
// Identifiers found in property maps of patterns or as variable length path
|
|
||||||
// bounds in a single Match clause. They need to be checked after visiting
|
|
||||||
// Match. Identifiers created by naming vertices, edges and paths are *not*
|
|
||||||
// stored in here.
|
|
||||||
std::vector<Identifier *> identifiers_in_match;
|
|
||||||
// Number of nested IfOperators.
|
|
||||||
int num_if_operators{0};
|
|
||||||
};
|
|
||||||
|
|
||||||
inline static std::optional<Symbol> FindSymbolInScope(const std::string &name, const Scope &scope,
|
|
||||||
Symbol::Type type) {
|
|
||||||
if (auto it = scope.symbols.find(name); it != scope.symbols.end()) {
|
|
||||||
const auto &symbol = it->second;
|
|
||||||
// Unless we have `ANY` type, check that types match.
|
|
||||||
if (type != Symbol::Type::ANY && symbol.type() != Symbol::Type::ANY && type != symbol.type()) {
|
|
||||||
throw TypeMismatchError(name, Symbol::TypeToString(symbol.type()), Symbol::TypeToString(type));
|
|
||||||
}
|
|
||||||
return symbol;
|
|
||||||
}
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HasSymbol(const std::string &name) const {
|
|
||||||
return std::ranges::any_of(scopes_, [&name](const auto &scope) { return scope.symbols.contains(name); });
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HasSymbolLocalScope(const std::string &name) const { return scopes_.back().symbols.contains(name); }
|
|
||||||
|
|
||||||
// @return true if it added a predefined identifier with that name
|
|
||||||
bool ConsumePredefinedIdentifier(const std::string &name) {
|
|
||||||
auto it = predefined_identifiers_.find(name);
|
|
||||||
|
|
||||||
if (it == predefined_identifiers_.end()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// we can only use the predefined identifier in a single scope so we remove it after creating
|
|
||||||
// a symbol for it
|
|
||||||
auto &identifier = it->second;
|
|
||||||
MG_ASSERT(!identifier->user_declared_, "Predefined symbols cannot be user declared!");
|
|
||||||
identifier->MapTo(CreateSymbol(identifier->name_, identifier->user_declared_));
|
|
||||||
predefined_identifiers_.erase(it);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a freshly generated symbol. Previous mapping of the same name to a
|
|
||||||
// different symbol is replaced with the new one.
|
|
||||||
Symbol CreateSymbol(const std::string &name, bool user_declared, Symbol::Type type = Symbol::Type::ANY,
|
|
||||||
int token_position = -1) {
|
|
||||||
auto symbol = symbol_table_->CreateSymbol(name, user_declared, type, token_position);
|
|
||||||
scopes_.back().symbols[name] = symbol;
|
|
||||||
return symbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
Symbol GetOrCreateSymbol(const std::string &name, bool user_declared, Symbol::Type type = Symbol::Type::ANY) {
|
|
||||||
// NOLINTNEXTLINE
|
|
||||||
for (auto scope = scopes_.rbegin(); scope != scopes_.rend(); ++scope) {
|
|
||||||
if (auto maybe_symbol = FindSymbolInScope(name, *scope, type); maybe_symbol) {
|
|
||||||
return *maybe_symbol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return CreateSymbol(name, user_declared, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the symbol by name. If the mapping already exists, checks if the
|
|
||||||
// types match. Otherwise, returns a new symbol.
|
|
||||||
Symbol GetOrCreateSymbolLocalScope(const std::string &name, bool user_declared,
|
|
||||||
Symbol::Type type = Symbol::Type::ANY) {
|
|
||||||
auto &scope = scopes_.back();
|
|
||||||
if (auto maybe_symbol = FindSymbolInScope(name, scope, type); maybe_symbol) {
|
|
||||||
return *maybe_symbol;
|
|
||||||
}
|
|
||||||
return CreateSymbol(name, user_declared, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VisitReturnBody(ReturnBody &body, Where *where = nullptr) {
|
|
||||||
auto &scope = scopes_.back();
|
|
||||||
for (auto &expr : body.named_expressions) {
|
|
||||||
expr->Accept(*this);
|
|
||||||
}
|
|
||||||
std::vector<Symbol> user_symbols;
|
|
||||||
if (body.all_identifiers) {
|
|
||||||
// Carry over user symbols because '*' appeared.
|
|
||||||
for (const auto &sym_pair : scope.symbols) {
|
|
||||||
if (!sym_pair.second.user_declared()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
user_symbols.emplace_back(sym_pair.second);
|
|
||||||
}
|
|
||||||
if (user_symbols.empty()) {
|
|
||||||
throw SemanticException("There are no variables in scope to use for '*'.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// WITH/RETURN clause removes declarations of all the previous variables and
|
|
||||||
// declares only those established through named expressions. New declarations
|
|
||||||
// must not be visible inside named expressions themselves.
|
|
||||||
bool removed_old_names = false;
|
|
||||||
if ((!where && body.order_by.empty()) || scope.has_aggregation) {
|
|
||||||
// WHERE and ORDER BY need to see both the old and new symbols, unless we
|
|
||||||
// have an aggregation. Therefore, we can clear the symbols immediately if
|
|
||||||
// there is neither ORDER BY nor WHERE, or we have an aggregation.
|
|
||||||
scope.symbols.clear();
|
|
||||||
removed_old_names = true;
|
|
||||||
}
|
|
||||||
// Create symbols for named expressions.
|
|
||||||
std::unordered_set<std::string> new_names;
|
|
||||||
for (const auto &user_sym : user_symbols) {
|
|
||||||
new_names.insert(user_sym.name());
|
|
||||||
scope.symbols[user_sym.name()] = user_sym;
|
|
||||||
}
|
|
||||||
for (auto &named_expr : body.named_expressions) {
|
|
||||||
const auto &name = named_expr->name_;
|
|
||||||
if (!new_names.insert(name).second) {
|
|
||||||
throw SemanticException("Multiple results with the same name '{}' are not allowed.", name);
|
|
||||||
}
|
|
||||||
// An improvement would be to infer the type of the expression, so that the
|
|
||||||
// new symbol would have a more specific type.
|
|
||||||
named_expr->MapTo(CreateSymbol(name, true, Symbol::Type::ANY, named_expr->token_position_));
|
|
||||||
}
|
|
||||||
scope.in_order_by = true;
|
|
||||||
for (const auto &order_pair : body.order_by) {
|
|
||||||
order_pair.expression->Accept(*this);
|
|
||||||
}
|
|
||||||
scope.in_order_by = false;
|
|
||||||
if (body.skip) {
|
|
||||||
scope.in_skip = true;
|
|
||||||
body.skip->Accept(*this);
|
|
||||||
scope.in_skip = false;
|
|
||||||
}
|
|
||||||
if (body.limit) {
|
|
||||||
scope.in_limit = true;
|
|
||||||
body.limit->Accept(*this);
|
|
||||||
scope.in_limit = false;
|
|
||||||
}
|
|
||||||
if (where) where->Accept(*this);
|
|
||||||
if (!removed_old_names) {
|
|
||||||
// We have an ORDER BY or WHERE, but no aggregation, which means we didn't
|
|
||||||
// clear the old symbols, so do it now. We cannot just call clear, because
|
|
||||||
// we've added new symbols.
|
|
||||||
for (auto sym_it = scope.symbols.begin(); sym_it != scope.symbols.end();) {
|
|
||||||
if (new_names.find(sym_it->first) == new_names.end()) {
|
|
||||||
sym_it = scope.symbols.erase(sym_it);
|
|
||||||
} else {
|
|
||||||
sym_it++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scopes_.back().has_aggregation = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VisitWithIdentifiers(Expression *expr, const std::vector<Identifier *> &identifiers) {
|
|
||||||
auto &scope = scopes_.back();
|
|
||||||
std::vector<std::pair<std::optional<Symbol>, Identifier *>> prev_symbols;
|
|
||||||
// Collect previous symbols if they exist.
|
|
||||||
for (const auto &identifier : identifiers) {
|
|
||||||
std::optional<Symbol> prev_symbol;
|
|
||||||
auto prev_symbol_it = scope.symbols.find(identifier->name_);
|
|
||||||
if (prev_symbol_it != scope.symbols.end()) {
|
|
||||||
prev_symbol = prev_symbol_it->second;
|
|
||||||
}
|
|
||||||
identifier->MapTo(CreateSymbol(identifier->name_, identifier->user_declared_));
|
|
||||||
prev_symbols.emplace_back(prev_symbol, identifier);
|
|
||||||
}
|
|
||||||
// Visit the expression with the new symbols bound.
|
|
||||||
expr->Accept(*this);
|
|
||||||
// Restore back to previous symbols.
|
|
||||||
for (const auto &prev : prev_symbols) {
|
|
||||||
const auto &prev_symbol = prev.first;
|
|
||||||
const auto &identifier = prev.second;
|
|
||||||
if (prev_symbol) {
|
|
||||||
scope.symbols[identifier->name_] = *prev_symbol;
|
|
||||||
} else {
|
|
||||||
scope.symbols.erase(identifier->name_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SymbolTable *symbol_table_;
|
|
||||||
|
|
||||||
// Identifiers which are injected from outside the query. Each identifier
|
|
||||||
// is mapped by its name.
|
|
||||||
std::unordered_map<std::string, Identifier *> predefined_identifiers_;
|
|
||||||
std::vector<Scope> scopes_;
|
|
||||||
std::unordered_set<std::string> prev_return_names_;
|
|
||||||
std::unordered_set<std::string> curr_return_names_;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline SymbolTable MakeSymbolTable(CypherQuery *query, const std::vector<Identifier *> &predefined_identifiers = {}) {
|
|
||||||
SymbolTable symbol_table;
|
|
||||||
SymbolGenerator symbol_generator(&symbol_table, predefined_identifiers);
|
|
||||||
query->single_query_->Accept(symbol_generator);
|
|
||||||
for (auto *cypher_union : query->cypher_unions_) {
|
|
||||||
cypher_union->Accept(symbol_generator);
|
|
||||||
}
|
|
||||||
return symbol_table;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace memgraph::expr
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "expr/ast.hpp"
|
|
||||||
#include "expr/semantic/symbol.hpp"
|
|
||||||
#include "utils/logging.hpp"
|
|
||||||
|
|
||||||
namespace memgraph::expr {
|
|
||||||
|
|
||||||
class SymbolTable final {
|
|
||||||
public:
|
|
||||||
SymbolTable() {}
|
|
||||||
const Symbol &CreateSymbol(const std::string &name, bool user_declared, Symbol::Type type = Symbol::Type::ANY,
|
|
||||||
int32_t token_position = -1) {
|
|
||||||
MG_ASSERT(table_.size() <= std::numeric_limits<int32_t>::max(),
|
|
||||||
"SymbolTable size doesn't fit into 32-bit integer!");
|
|
||||||
auto got = table_.emplace(position_, Symbol(name, position_, user_declared, type, token_position));
|
|
||||||
MG_ASSERT(got.second, "Duplicate symbol ID!");
|
|
||||||
position_++;
|
|
||||||
return got.first->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(buda): This is the same logic as in the cypher_main_visitor. During
|
|
||||||
// parsing phase symbol table doesn't exist. Figure out a better solution.
|
|
||||||
const Symbol &CreateAnonymousSymbol(Symbol::Type type = Symbol::Type::ANY) {
|
|
||||||
int id = 1;
|
|
||||||
while (true) {
|
|
||||||
static const std::string &kAnonPrefix = "anon";
|
|
||||||
std::string name_candidate = kAnonPrefix + std::to_string(id++);
|
|
||||||
if (std::find_if(std::begin(table_), std::end(table_), [&name_candidate](const auto &item) -> bool {
|
|
||||||
return item.second.name_ == name_candidate;
|
|
||||||
}) == std::end(table_)) {
|
|
||||||
return CreateSymbol(name_candidate, false, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Symbol &at(const Identifier &ident) const { return table_.at(ident.symbol_pos_); }
|
|
||||||
const Symbol &at(const NamedExpression &nexpr) const { return table_.at(nexpr.symbol_pos_); }
|
|
||||||
const Symbol &at(const Aggregation &aggr) const { return table_.at(aggr.symbol_pos_); }
|
|
||||||
|
|
||||||
// TODO: Remove these since members are public
|
|
||||||
int32_t max_position() const { return static_cast<int32_t>(table_.size()); }
|
|
||||||
|
|
||||||
const auto &table() const { return table_; }
|
|
||||||
|
|
||||||
int32_t position_{0};
|
|
||||||
std::map<int32_t, Symbol> table_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace memgraph::expr
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,26 +0,0 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
|
||||||
//
|
|
||||||
// Use of this software is governed by the Business Source License
|
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
|
||||||
// License, and you may not use this file except in compliance with the Business Source License.
|
|
||||||
//
|
|
||||||
// As of the Change Date specified in that file, in accordance with
|
|
||||||
// the Business Source License, use of this software will be governed
|
|
||||||
// by the Apache License, Version 2.0, included in the file
|
|
||||||
// licenses/APL.txt.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "utils/exceptions.hpp"
|
|
||||||
|
|
||||||
namespace memgraph::expr {
|
|
||||||
/**
|
|
||||||
* An exception raised by the TypedValue system. Typically when
|
|
||||||
* trying to perform operations (such as addition) on TypedValues
|
|
||||||
* of incompatible Types.
|
|
||||||
*/
|
|
||||||
class TypedValueException : public utils::BasicException {
|
|
||||||
public:
|
|
||||||
using utils::BasicException::BasicException;
|
|
||||||
};
|
|
||||||
} // namespace memgraph::expr
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
add_library(mg-functions INTERFACE)
|
|
||||||
File diff suppressed because it is too large
Load Diff
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.
|
// licenses/APL.txt.
|
||||||
|
|
||||||
#include "glue/auth.hpp"
|
#include "glue/auth.hpp"
|
||||||
|
#include "auth/models.hpp"
|
||||||
|
|
||||||
namespace memgraph::glue {
|
namespace memgraph::glue {
|
||||||
|
|
||||||
@@ -59,4 +60,20 @@ auth::Permission PrivilegeToPermission(query::AuthQuery::Privilege privilege) {
|
|||||||
return auth::Permission::WEBSOCKET;
|
return auth::Permission::WEBSOCKET;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#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
|
} // namespace memgraph::glue
|
||||||
|
|||||||
@@ -20,4 +20,12 @@ namespace memgraph::glue {
|
|||||||
*/
|
*/
|
||||||
auth::Permission PrivilegeToPermission(query::AuthQuery::Privilege privilege);
|
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
|
} // 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
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user