Compare commits
202 Commits
release/1.
...
T1813-CL-r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f13c5d0b62 | ||
|
|
f560293657 | ||
|
|
2afc1b99f6 | ||
|
|
5a8bda2531 | ||
|
|
0138d277d4 | ||
|
|
09cfca35f8 | ||
|
|
09c58501f1 | ||
|
|
945bbfdc49 | ||
|
|
45ef069372 | ||
|
|
e51954fc94 | ||
|
|
7776160836 | ||
|
|
ae280fd8db | ||
|
|
fb5a2ed4b6 | ||
|
|
6cfec787dc | ||
|
|
13c9bf76af | ||
|
|
2e1a717dcb | ||
|
|
ad32db5168 | ||
|
|
a37755ce43 | ||
|
|
a928c158da | ||
|
|
ac230d0c2d | ||
|
|
d80ff745eb | ||
|
|
d6a6d280dd | ||
|
|
4004e94ca1 | ||
|
|
36afc6c5f3 | ||
|
|
3c9a46f823 | ||
|
|
f994b68ad5 | ||
|
|
3b336e3e0b | ||
|
|
715162e205 | ||
|
|
e016c74e4b | ||
|
|
15911b64dc | ||
|
|
cbf826e0c3 | ||
|
|
644a3a0b2a | ||
|
|
8cd9f696cf | ||
|
|
90a093bd95 | ||
|
|
542a324c96 | ||
|
|
cd03e13443 | ||
|
|
be91126134 | ||
|
|
03cb007339 | ||
|
|
524acb17a1 | ||
|
|
50f6e348dc | ||
|
|
839d45b3f8 | ||
|
|
560eb04f67 | ||
|
|
a3ecc52429 | ||
|
|
e8a1d15a55 | ||
|
|
1abee1ed3a | ||
|
|
5af3d0ff68 | ||
|
|
62a628c51f | ||
|
|
883f9c7ed3 | ||
|
|
b459639968 | ||
|
|
11c0dde11c | ||
|
|
2f3fa656d9 | ||
|
|
7bf40eb5d2 | ||
|
|
7e44434cdf | ||
|
|
2a0b0d969f | ||
|
|
13ea35af2d | ||
|
|
8a99670301 | ||
|
|
c4555f5448 | ||
|
|
999b3ef79f | ||
|
|
30413a7b4f | ||
|
|
1def0c9104 | ||
|
|
782c377f5d | ||
|
|
cc27a04139 | ||
|
|
b71345655f | ||
|
|
ccdd58b336 | ||
|
|
50b6afd73d | ||
|
|
59105f68bd | ||
|
|
8de31092ad | ||
|
|
5c93f81881 | ||
|
|
6d4fe5cdd5 | ||
|
|
7b5263d300 | ||
|
|
e8a41e4457 | ||
|
|
27f09e1c0a | ||
|
|
6dd9d32721 | ||
|
|
276e09d7d3 | ||
|
|
92dfc93b20 | ||
|
|
06f761bdf9 | ||
|
|
50ddd59450 | ||
|
|
60da033010 | ||
|
|
ee555b0c0d | ||
|
|
e8e4cd7f97 | ||
|
|
ad4c80af13 | ||
|
|
9c6bf4b1b8 | ||
|
|
cc56ac3dd8 | ||
|
|
dee885d69c | ||
|
|
bbed7a2397 | ||
|
|
e8810a4152 | ||
|
|
25eb2c147a | ||
|
|
b3914c6b5d | ||
|
|
d913a67e16 | ||
|
|
f950a91732 | ||
|
|
f6d5f576d5 | ||
|
|
dc5eb4befd | ||
|
|
593f7a3499 | ||
|
|
77a0d7b8fa | ||
|
|
c240b5b564 | ||
|
|
866ed45562 | ||
|
|
1598cb24ea | ||
|
|
35d789c56b | ||
|
|
16715d5005 | ||
|
|
a9f5f45b3d | ||
|
|
2e0dd19bac | ||
|
|
f807b495ab | ||
|
|
3f3c55a4aa | ||
|
|
435af8b833 | ||
|
|
cc1c1513ef | ||
|
|
fae407d3fe | ||
|
|
42c245df8a | ||
|
|
e4852cc5e3 | ||
|
|
2caf0e617e | ||
|
|
90d4ebdb1e | ||
|
|
151812da1a | ||
|
|
1e10b9cb77 | ||
|
|
c44dbdda86 | ||
|
|
46b1783618 | ||
|
|
e866526c7a | ||
|
|
28413fd626 | ||
|
|
c0bd59bb09 | ||
|
|
a84ffe86c1 | ||
|
|
8f5b88f24a | ||
|
|
afbf672915 | ||
|
|
10c8256ec9 | ||
|
|
b19cd4f5d1 | ||
|
|
8fc9298832 | ||
|
|
9966ba1d52 | ||
|
|
b7bbd026de | ||
|
|
855c2ea9ca | ||
|
|
adc355a22a | ||
|
|
bff9cf07de | ||
|
|
60d742a2dc | ||
|
|
a0fb3fc463 | ||
|
|
f23e2e12c4 | ||
|
|
87e00f4fef | ||
|
|
f7b764607d | ||
|
|
200ce5f45e | ||
|
|
a0705746cb | ||
|
|
4e36b646df | ||
|
|
ec909ced57 | ||
|
|
7e9175052a | ||
|
|
3c85319701 | ||
|
|
76f0d5873b | ||
|
|
03cc568e39 | ||
|
|
bc0c944910 | ||
|
|
42f6118c00 | ||
|
|
b10255a12f | ||
|
|
c68ed8d94e | ||
|
|
d5b02eafb1 | ||
|
|
1580c46abc | ||
|
|
367cb44983 | ||
|
|
77bda187d2 | ||
|
|
57806544cd | ||
|
|
7bff678cd9 | ||
|
|
647388792a | ||
|
|
b42ee31c1d | ||
|
|
53fa1482cb | ||
|
|
c5f722b724 | ||
|
|
573b6cb045 | ||
|
|
4afcf1b655 | ||
|
|
4e7c569071 | ||
|
|
ea27ac9391 | ||
|
|
958bc870b3 | ||
|
|
f0382c82cd | ||
|
|
6fab357430 | ||
|
|
f273be63a2 | ||
|
|
9b984ab2f2 | ||
|
|
92bad4f62b | ||
|
|
126c9d697b | ||
|
|
0478e89646 | ||
|
|
814bb66ea6 | ||
|
|
98f83e0c88 | ||
|
|
2e7d3822fe | ||
|
|
0a7d4278b1 | ||
|
|
291158160d | ||
|
|
e525818355 | ||
|
|
0bcc1d67bc | ||
|
|
42ac5d4ea3 | ||
|
|
48587d6d5e | ||
|
|
157590a294 | ||
|
|
7f179dc462 | ||
|
|
594f3ec1b0 | ||
|
|
804a44dd6c | ||
|
|
1b50dc60f9 | ||
|
|
9d6b578237 | ||
|
|
f7f861ca71 | ||
|
|
c12e4a49b1 | ||
|
|
4c0fc11f69 | ||
|
|
e0ffc533b9 | ||
|
|
0dcfdb9b89 | ||
|
|
1d973f7e31 | ||
|
|
4f4903803f | ||
|
|
ff0d5962e1 | ||
|
|
04ceb8d4b1 | ||
|
|
fb7025a716 | ||
|
|
89b6262fa4 | ||
|
|
d2ff465f8e | ||
|
|
c12a87e9ca | ||
|
|
63de0b5db4 | ||
|
|
dd9180da32 | ||
|
|
aaf0c1ca08 | ||
|
|
1513a455de | ||
|
|
4f9e9aeafe | ||
|
|
857de23687 | ||
|
|
fba5d75bf4 |
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"project_id" : "memgraph",
|
||||
"conduit_uri" : "https://phabricator.memgraph.io",
|
||||
"phabricator_uri" : "https://phabricator.memgraph.io",
|
||||
"git.default-relative-commit": "origin/master",
|
||||
"arc.land.onto.default": "master"
|
||||
}
|
||||
16
.arclint
16
.arclint
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"linters": {
|
||||
"clang-tidy": {
|
||||
"type": "script-and-regex",
|
||||
"include": "(\\.(cpp|cc|cxx|c|h|hpp|lcp)$)",
|
||||
"script-and-regex.script": "./tools/arc-clang-tidy",
|
||||
"script-and-regex.regex": "/^(?P<file>.*):(?P<line>\\d+):(?P<char>\\d+): (?P<severity>warning|error): (?P<message>.*)$/m"
|
||||
},
|
||||
"clang-format": {
|
||||
"type": "script-and-regex",
|
||||
"include": "(\\.(cpp|cc|cxx|c|h|hpp)$)",
|
||||
"script-and-regex.script": "./tools/arc-clang-format",
|
||||
"script-and-regex.regex": "/^(?P<severity>warning):(?P<offset>\\d+):(?P<message>.*)$/m"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
Standard: "C++11"
|
||||
Standard: "c++20"
|
||||
UseTab: Never
|
||||
DerivePointerAlignment: false
|
||||
PointerAlignment: Right
|
||||
ColumnLimit : 80
|
||||
ColumnLimit : 120
|
||||
IncludeBlocks: Preserve
|
||||
...
|
||||
|
||||
14
.clang-tidy
14
.clang-tidy
@@ -1,5 +1,6 @@
|
||||
---
|
||||
Checks: '*,
|
||||
-altera-struct-pack-align,
|
||||
-android-*,
|
||||
-cert-err58-cpp,
|
||||
-cppcoreguidelines-avoid-c-arrays,
|
||||
@@ -26,6 +27,7 @@ Checks: '*,
|
||||
-fuchsia-virtual-inheritance,
|
||||
-google-explicit-constructor,
|
||||
-google-readability-*,
|
||||
-google-runtime-references,
|
||||
-hicpp-avoid-c-arrays,
|
||||
-hicpp-avoid-goto,
|
||||
-hicpp-braces-around-statements,
|
||||
@@ -34,10 +36,14 @@ Checks: '*,
|
||||
-hicpp-no-assembler,
|
||||
-hicpp-no-malloc,
|
||||
-hicpp-use-equals-default,
|
||||
-hicpp-use-nullptr,
|
||||
-hicpp-vararg,
|
||||
-llvm-header-guard,
|
||||
-llvm-include-order,
|
||||
-llvmlibc-callee-namespace,
|
||||
-llvmlibc-implementation-in-namespace,
|
||||
-llvmlibc-restrict-system-libc-headers,
|
||||
-misc-non-private-member-variables-in-classes,
|
||||
-misc-unused-parameters,
|
||||
-modernize-avoid-c-arrays,
|
||||
-modernize-concat-nested-namespaces,
|
||||
-modernize-pass-by-value,
|
||||
@@ -47,11 +53,13 @@ Checks: '*,
|
||||
-performance-unnecessary-value-param,
|
||||
-readability-braces-around-statements,
|
||||
-readability-else-after-return,
|
||||
-readability-function-cognitive-complexity,
|
||||
-readability-implicit-bool-conversion,
|
||||
-readability-magic-numbers,
|
||||
-readability-named-parameter'
|
||||
-readability-named-parameter,
|
||||
-misc-no-recursion'
|
||||
WarningsAsErrors: ''
|
||||
HeaderFilterRegex: ''
|
||||
HeaderFilterRegex: 'src/.*'
|
||||
AnalyzeTemporaryDtors: false
|
||||
FormatStyle: none
|
||||
CheckOptions:
|
||||
|
||||
35
.githooks/pre-commit
Executable file
35
.githooks/pre-commit
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/bin/sh
|
||||
|
||||
project_folder=$(git rev-parse --show-toplevel)
|
||||
if git rev-parse --verify HEAD >/dev/null 2>&1
|
||||
then
|
||||
against=HEAD
|
||||
else
|
||||
# Initial commit: diff against an empty tree object
|
||||
against=$(git hash-object -t tree /dev/null)
|
||||
fi
|
||||
|
||||
# Redirect output to stderr.
|
||||
exec 1>&2
|
||||
|
||||
tmpdir=$(mktemp -d repo-XXXXXXXX)
|
||||
trap "rm -rf $tmpdir" EXIT INT
|
||||
|
||||
modified_files=$(git diff --cached --name-only --diff-filter=AM $against | sed -nE "/.*\.(cpp|cc|cxx|c|h|hpp)$/p")
|
||||
for file in $modified_files; do
|
||||
echo "Checking $file..."
|
||||
|
||||
cp $project_folder/.clang-format $project_folder/.clang-tidy $tmpdir
|
||||
|
||||
git checkout-index --prefix="$tmpdir/" -- $file
|
||||
|
||||
echo "Running clang-format..."
|
||||
$project_folder/tools/git-clang-format $tmpdir/$file
|
||||
code=$?
|
||||
|
||||
if [ $code -ne 0 ]; then
|
||||
break
|
||||
fi
|
||||
done;
|
||||
|
||||
return $code
|
||||
9
.github/pull_request_template.md
vendored
Normal file
9
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
[master < Epic] PR
|
||||
- [ ] Check, and update documentation if necessary
|
||||
- [ ] Update [changelog](https://docs.memgraph.com/memgraph/changelog)
|
||||
- [ ] Write E2E tests
|
||||
- [ ] Compare the [benchmarking results](https://bench-graph.memgraph.com/) between the master branch and the Epic branch
|
||||
|
||||
[master < Task] PR
|
||||
- [ ] Check, and update documentation if necessary
|
||||
- [ ] Update [changelog](https://docs.memgraph.com/memgraph/changelog)
|
||||
68
.github/workflows/daily_banchmark.yaml
vendored
Normal file
68
.github/workflows/daily_banchmark.yaml
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
name: Daily Benchmark
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 1 * * *"
|
||||
|
||||
jobs:
|
||||
release_benchmarks:
|
||||
name: "Release benchmarks"
|
||||
runs-on: [self-hosted, Linux, X64, Diff, Gen7]
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
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-v3/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: 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 }}"
|
||||
|
||||
- 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 }}"
|
||||
406
.github/workflows/diff.yaml
vendored
Normal file
406
.github/workflows/diff.yaml
vendored
Normal file
@@ -0,0 +1,406 @@
|
||||
name: Diff
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '**/*.md'
|
||||
- '.clang-format'
|
||||
- 'CODEOWNERS'
|
||||
|
||||
jobs:
|
||||
community_build:
|
||||
name: "Community build"
|
||||
runs-on: [self-hosted, Linux, X64, Diff]
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build community binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build community binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release -DMG_ENTERPRISE=OFF ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure -j$THREADS
|
||||
|
||||
- name: Run stress test
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration
|
||||
|
||||
- name: Create community DEB package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
cd build
|
||||
|
||||
# create mgconsole
|
||||
# we use the -B to force the build
|
||||
make -j$THREADS -B mgconsole
|
||||
|
||||
# Create community DEB package.
|
||||
mkdir output && cd output
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save community DEB package
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Community DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
|
||||
code_analysis:
|
||||
name: "Code analysis"
|
||||
runs-on: [self-hosted, Linux, X64, Diff]
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build combined ASAN, UBSAN and coverage binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
cd build
|
||||
cmake -DTEST_COVERAGE=ON -DASAN=ON -DUBSAN=ON ..
|
||||
make -j$THREADS memgraph__unit
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Run unit tests. It is restricted to 2 threads intentionally, because higher concurrency makes the timing related tests unstable.
|
||||
cd build
|
||||
LSAN_OPTIONS=suppressions=$PWD/../tools/lsan.supp UBSAN_OPTIONS=halt_on_error=1 ctest -R memgraph__unit --output-on-failure -j2
|
||||
|
||||
- name: Compute code coverage
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Compute code coverage.
|
||||
cd tools/github
|
||||
./coverage_convert
|
||||
|
||||
# Package code coverage.
|
||||
cd generated
|
||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||
|
||||
- name: Save code coverage
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/generated/code_coverage.tar.gz
|
||||
|
||||
- name: Run clang-tidy
|
||||
run: |
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Restrict clang-tidy results only to the modified parts
|
||||
git diff -U0 master... -- src ':!*.hpp' | ./tools/github/clang-tidy/clang-tidy-diff.py -p 1 -j $THREADS -path build | tee ./build/clang_tidy_output.txt
|
||||
|
||||
# Fail if any warning is reported
|
||||
! cat ./build/clang_tidy_output.txt | ./tools/github/clang-tidy/grep_error_lines.sh > /dev/null
|
||||
|
||||
debug_build:
|
||||
name: "Debug build"
|
||||
runs-on: [self-hosted, Linux, X64, Diff]
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build debug binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build debug binaries.
|
||||
cd build
|
||||
cmake ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run leftover CTest tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Run leftover CTest tests (all except unit and benchmark tests).
|
||||
cd build
|
||||
ctest -E "(memgraph__unit|memgraph__benchmark)" --output-on-failure
|
||||
|
||||
- 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: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Run cppcheck and clang-format.
|
||||
cd tools/github
|
||||
./cppcheck_and_clang_format diff
|
||||
|
||||
- name: Save cppcheck and clang-format errors
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/cppcheck_and_clang_format.txt
|
||||
|
||||
release_build:
|
||||
name: "Release build"
|
||||
runs-on: [self-hosted, Linux, X64, Diff]
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
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-v3/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build release binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run GQL Behave tests
|
||||
run: |
|
||||
cd tests/gql_behave
|
||||
./continuous_integration
|
||||
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
tests/gql_behave/gql_behave_status.csv
|
||||
tests/gql_behave/gql_behave_status.html
|
||||
|
||||
- name: Run e2e tests
|
||||
run: |
|
||||
# TODO(gitbuda): Setup mgclient and pymgclient properly.
|
||||
cd tests
|
||||
./setup.sh
|
||||
source ve3/bin/activate
|
||||
cd e2e
|
||||
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-v3/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@v2
|
||||
with:
|
||||
name: "Enterprise DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
|
||||
- name: Save test data
|
||||
uses: actions/upload-artifact@v2
|
||||
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
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
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-v3/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@v2
|
||||
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
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
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-v3/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: 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 }}"
|
||||
|
||||
- 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 }}"
|
||||
44
.github/workflows/full_clang_tidy.yaml
vendored
Normal file
44
.github/workflows/full_clang_tidy.yaml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
name: Run clang-tidy on the full codebase
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
clang_tidy_check:
|
||||
name: "Clang-tidy check"
|
||||
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build debug binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build debug binaries.
|
||||
|
||||
cd build
|
||||
cmake ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run clang-tidy
|
||||
run: |
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# The results are also written to standard output in order to retain them in the logs
|
||||
./tools/github/clang-tidy/run-clang-tidy.py -p build -j $THREADS -clang-tidy-binary=/opt/toolchain-v3/bin/clang-tidy "$PWD/src/*" |
|
||||
tee ./build/full_clang_tidy_output.txt
|
||||
|
||||
- name: Summarize clang-tidy results
|
||||
run: cat ./build/full_clang_tidy_output.txt | ./tools/github/clang-tidy/count_errors.sh
|
||||
248
.github/workflows/package_all.yaml
vendored
Normal file
248
.github/workflows/package_all.yaml
vendored
Normal file
@@ -0,0 +1,248 @@
|
||||
name: Package All
|
||||
|
||||
# TODO(gitbuda): Cleanup docker container if GHA job was canceled.
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
centos-7_community:
|
||||
runs-on: [self-hosted, DockerMgBuild]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package community centos-7
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: centos-7_community
|
||||
path: build/output/centos-7/memgraph*.rpm
|
||||
|
||||
centos-8_community:
|
||||
runs-on: [self-hosted, DockerMgBuild]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package community centos-8
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: centos-8_community
|
||||
path: build/output/centos-8/memgraph*.rpm
|
||||
|
||||
debian-9_community:
|
||||
runs-on: [self-hosted, DockerMgBuild]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package community debian-9
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: debian-9_community
|
||||
path: build/output/debian-9/memgraph*.deb
|
||||
|
||||
debian-10_community:
|
||||
runs-on: [self-hosted, DockerMgBuild]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package community debian-10
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: debian-10_community
|
||||
path: build/output/debian-10/memgraph*.deb
|
||||
|
||||
docker_community:
|
||||
runs-on: [self-hosted, DockerMgBuild]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
cd release/package
|
||||
./run.sh package community debian-10 --for-docker
|
||||
./run.sh docker
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: docker_community
|
||||
path: build/output/docker/memgraph*.tar.gz
|
||||
|
||||
ubuntu-1804_community:
|
||||
runs-on: [self-hosted, DockerMgBuild]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package community ubuntu-18.04
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ubuntu-1804_community
|
||||
path: build/output/ubuntu-18.04/memgraph*.deb
|
||||
|
||||
ubuntu-2004_community:
|
||||
runs-on: [self-hosted, DockerMgBuild]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package community ubuntu-20.04
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ubuntu-2004_community
|
||||
path: build/output/ubuntu-20.04/memgraph*.deb
|
||||
|
||||
centos-7_enterprise:
|
||||
runs-on: [self-hosted, DockerMgBuild]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package enterprise centos-7
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: centos-7_enterprise
|
||||
path: build/output/centos-7/memgraph*.rpm
|
||||
|
||||
centos-8_enterprise:
|
||||
runs-on: [self-hosted, DockerMgBuild]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package enterprise centos-8
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: centos-8_enterprise
|
||||
path: build/output/centos-8/memgraph*.rpm
|
||||
|
||||
debian-9_enterprise:
|
||||
runs-on: [self-hosted, DockerMgBuild]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package enterprise debian-9
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: debian-9_enterprise
|
||||
path: build/output/debian-9/memgraph*.deb
|
||||
|
||||
debian-10_enterprise:
|
||||
runs-on: [self-hosted, DockerMgBuild]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package enterprise debian-10
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: debian-10_enterprise
|
||||
path: build/output/debian-10/memgraph*.deb
|
||||
|
||||
docker_enterprise:
|
||||
runs-on: [self-hosted, DockerMgBuild]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
cd release/package
|
||||
./run.sh package enterprise debian-10 --for-docker
|
||||
./run.sh docker
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: docker_enterprise
|
||||
path: build/output/docker/memgraph*.tar.gz
|
||||
|
||||
ubuntu-1804_enterprise:
|
||||
runs-on: [self-hosted, DockerMgBuild]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package enterprise ubuntu-18.04
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ubuntu-1804_enterprise
|
||||
path: build/output/ubuntu-18.04/memgraph*.deb
|
||||
|
||||
ubuntu-2004_enterprise:
|
||||
runs-on: [self-hosted, DockerMgBuild]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: "Set up repository"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package enterprise ubuntu-20.04
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ubuntu-2004_enterprise
|
||||
path: build/output/ubuntu-20.04/memgraph*.deb
|
||||
330
.github/workflows/release_centos8.yaml
vendored
Normal file
330
.github/workflows/release_centos8.yaml
vendored
Normal file
@@ -0,0 +1,330 @@
|
||||
name: Release CentOS 8
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 1 * * *"
|
||||
|
||||
jobs:
|
||||
community_build:
|
||||
name: "Community build"
|
||||
runs-on: [self-hosted, Linux, X64, CentOS8]
|
||||
env:
|
||||
THREADS: 24
|
||||
timeout-minutes: 960
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build community binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build community binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release -DMG_ENTERPRISE=OFF ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Create community RPM package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
cd build
|
||||
|
||||
# create mgconsole
|
||||
# we use the -B to force the build
|
||||
make -j$THREADS -B mgconsole
|
||||
|
||||
# Create community RPM package.
|
||||
mkdir output && cd output
|
||||
cpack -G RPM --config ../CPackConfig.cmake
|
||||
rpmlint memgraph*.rpm
|
||||
|
||||
- name: Save community RPM package
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Community RPM package"
|
||||
path: build/output/memgraph*.rpm
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Run stress test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration
|
||||
|
||||
- name: Run stress test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration --large-dataset
|
||||
|
||||
coverage_build:
|
||||
name: "Coverage build"
|
||||
runs-on: [self-hosted, Linux, X64, CentOS8]
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build coverage binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build coverage binaries.
|
||||
cd build
|
||||
cmake -DTEST_COVERAGE=ON ..
|
||||
make -j$THREADS memgraph__unit
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Compute code coverage
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Compute code coverage.
|
||||
cd tools/github
|
||||
./coverage_convert
|
||||
|
||||
# Package code coverage.
|
||||
cd generated
|
||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||
|
||||
- name: Save code coverage
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/generated/code_coverage.tar.gz
|
||||
|
||||
debug_build:
|
||||
name: "Debug build"
|
||||
runs-on: [self-hosted, Linux, X64, CentOS8]
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build debug binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build debug binaries.
|
||||
cd build
|
||||
cmake ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run leftover CTest tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Run leftover CTest tests (all except unit and benchmark tests).
|
||||
cd build
|
||||
ctest -E "(memgraph__unit|memgraph__benchmark)" --output-on-failure
|
||||
|
||||
- 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: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Run cppcheck and clang-format.
|
||||
cd tools/github
|
||||
./cppcheck_and_clang_format diff
|
||||
|
||||
- name: Save cppcheck and clang-format errors
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/cppcheck_and_clang_format.txt
|
||||
|
||||
release_build:
|
||||
name: "Release build"
|
||||
runs-on: [self-hosted, Linux, X64, CentOS8]
|
||||
env:
|
||||
THREADS: 24
|
||||
timeout-minutes: 960
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
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-v3/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build release binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Create enterprise RPM package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
cd build
|
||||
|
||||
# create mgconsole
|
||||
# we use the -B to force the build
|
||||
make -j$THREADS -B mgconsole
|
||||
|
||||
# Create enterprise RPM package.
|
||||
mkdir output && cd output
|
||||
cpack -G RPM --config ../CPackConfig.cmake
|
||||
rpmlint memgraph*.rpm
|
||||
|
||||
- name: Save enterprise RPM package
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Enterprise RPM package"
|
||||
path: build/output/memgraph*.rpm
|
||||
|
||||
- name: Run micro benchmark tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Run micro benchmark tests.
|
||||
cd build
|
||||
# The `eval` benchmark needs a large stack limit.
|
||||
ulimit -s 262144
|
||||
ctest -R memgraph__benchmark -V
|
||||
|
||||
- name: Run macro benchmark tests
|
||||
run: |
|
||||
cd tests/macro_benchmark
|
||||
./harness QuerySuite MemgraphRunner \
|
||||
--groups aggregation 1000_create unwind_create dense_expand match \
|
||||
--no-strict
|
||||
|
||||
- name: Run parallel macro benchmark tests
|
||||
run: |
|
||||
cd tests/macro_benchmark
|
||||
./harness QueryParallelSuite MemgraphRunner \
|
||||
--groups aggregation_parallel create_parallel bfs_parallel \
|
||||
--num-database-workers 9 --num-clients-workers 30 \
|
||||
--no-strict
|
||||
|
||||
- name: Run GQL Behave tests
|
||||
run: |
|
||||
cd tests/gql_behave
|
||||
./continuous_integration
|
||||
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
tests/gql_behave/gql_behave_status.csv
|
||||
tests/gql_behave/gql_behave_status.html
|
||||
|
||||
- name: Run e2e tests
|
||||
run: |
|
||||
# TODO(gitbuda): Setup mgclient and pymgclient properly.
|
||||
cd tests
|
||||
./setup.sh
|
||||
source ve3/bin/activate
|
||||
cd e2e
|
||||
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 stress test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration --large-dataset
|
||||
|
||||
- name: Run durability test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
source ve3/bin/activate
|
||||
python3 durability --num-steps 5
|
||||
|
||||
- name: Run durability test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
source ve3/bin/activate
|
||||
python3 durability --num-steps 20
|
||||
368
.github/workflows/release_debian10.yaml
vendored
Normal file
368
.github/workflows/release_debian10.yaml
vendored
Normal file
@@ -0,0 +1,368 @@
|
||||
name: Release Debian 10
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 1 * * *"
|
||||
|
||||
jobs:
|
||||
community_build:
|
||||
name: "Community build"
|
||||
runs-on: [self-hosted, Linux, X64, Debian10]
|
||||
env:
|
||||
THREADS: 24
|
||||
timeout-minutes: 960
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build community binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build community binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release -DMG_ENTERPRISE=OFF ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Create community DEB package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
cd build
|
||||
|
||||
# create mgconsole
|
||||
# we use the -B to force the build
|
||||
make -j$THREADS -B mgconsole
|
||||
|
||||
# Create community DEB package.
|
||||
mkdir output && cd output
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save community DEB package
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Community DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Run stress test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration
|
||||
|
||||
- name: Run stress test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration --large-dataset
|
||||
|
||||
coverage_build:
|
||||
name: "Coverage build"
|
||||
runs-on: [self-hosted, Linux, X64, Debian10]
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build coverage binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build coverage binaries.
|
||||
cd build
|
||||
cmake -DTEST_COVERAGE=ON ..
|
||||
make -j$THREADS memgraph__unit
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Compute code coverage
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Compute code coverage.
|
||||
cd tools/github
|
||||
./coverage_convert
|
||||
|
||||
# Package code coverage.
|
||||
cd generated
|
||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||
|
||||
- name: Save code coverage
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/generated/code_coverage.tar.gz
|
||||
|
||||
debug_build:
|
||||
name: "Debug build"
|
||||
runs-on: [self-hosted, Linux, X64, Debian10]
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build debug binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build debug binaries.
|
||||
cd build
|
||||
cmake ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run leftover CTest tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Run leftover CTest tests (all except unit and benchmark tests).
|
||||
cd build
|
||||
ctest -E "(memgraph__unit|memgraph__benchmark)" --output-on-failure
|
||||
|
||||
- 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: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Run cppcheck and clang-format.
|
||||
cd tools/github
|
||||
./cppcheck_and_clang_format diff
|
||||
|
||||
- name: Save cppcheck and clang-format errors
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/cppcheck_and_clang_format.txt
|
||||
|
||||
release_build:
|
||||
name: "Release build"
|
||||
runs-on: [self-hosted, Linux, X64, Debian10]
|
||||
env:
|
||||
THREADS: 24
|
||||
timeout-minutes: 960
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
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-v3/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build release binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Create enterprise DEB package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
cd build
|
||||
|
||||
# create mgconsole
|
||||
# we use the -B to force the build
|
||||
make -j$THREADS -B mgconsole
|
||||
|
||||
# Create enterprise DEB package.
|
||||
mkdir output && cd output
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save enterprise DEB package
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Enterprise DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
|
||||
- name: Run micro benchmark tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Run micro benchmark tests.
|
||||
cd build
|
||||
# The `eval` benchmark needs a large stack limit.
|
||||
ulimit -s 262144
|
||||
ctest -R memgraph__benchmark -V
|
||||
|
||||
- name: Run macro benchmark tests
|
||||
run: |
|
||||
cd tests/macro_benchmark
|
||||
./harness QuerySuite MemgraphRunner \
|
||||
--groups aggregation 1000_create unwind_create dense_expand match \
|
||||
--no-strict
|
||||
|
||||
- name: Run parallel macro benchmark tests
|
||||
run: |
|
||||
cd tests/macro_benchmark
|
||||
./harness QueryParallelSuite MemgraphRunner \
|
||||
--groups aggregation_parallel create_parallel bfs_parallel \
|
||||
--num-database-workers 9 --num-clients-workers 30 \
|
||||
--no-strict
|
||||
|
||||
- name: Run GQL Behave tests
|
||||
run: |
|
||||
cd tests/gql_behave
|
||||
./continuous_integration
|
||||
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
tests/gql_behave/gql_behave_status.csv
|
||||
tests/gql_behave/gql_behave_status.html
|
||||
|
||||
- name: Run e2e tests
|
||||
run: |
|
||||
# TODO(gitbuda): Setup mgclient and pymgclient properly.
|
||||
cd tests
|
||||
./setup.sh
|
||||
source ve3/bin/activate
|
||||
cd e2e
|
||||
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 stress test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration --large-dataset
|
||||
|
||||
- name: Run durability test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
source ve3/bin/activate
|
||||
python3 durability --num-steps 5
|
||||
|
||||
- name: Run durability test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
source ve3/bin/activate
|
||||
python3 durability --num-steps 20
|
||||
|
||||
release_jepsen_test:
|
||||
name: "Release Jepsen Test"
|
||||
runs-on: [self-hosted, Linux, X64, Debian10, JepsenControl]
|
||||
env:
|
||||
THREADS: 24
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
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-v3/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build only memgraph release binary.
|
||||
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@v2
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: "Jepsen Report"
|
||||
path: tests/jepsen/Jepsen.tar.gz
|
||||
328
.github/workflows/release_ubuntu2004.yaml
vendored
Normal file
328
.github/workflows/release_ubuntu2004.yaml
vendored
Normal file
@@ -0,0 +1,328 @@
|
||||
name: Release Ubuntu 20.04
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 1 * * *"
|
||||
|
||||
jobs:
|
||||
community_build:
|
||||
name: "Community build"
|
||||
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
||||
env:
|
||||
THREADS: 24
|
||||
timeout-minutes: 960
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build community binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build community binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release -DMG_ENTERPRISE=OFF ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Create community DEB package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
cd build
|
||||
|
||||
# create mgconsole
|
||||
# we use the -B to force the build
|
||||
make -j$THREADS -B mgconsole
|
||||
|
||||
# Create community DEB package.
|
||||
mkdir output && cd output
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save community DEB package
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Community DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Run stress test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration
|
||||
|
||||
- name: Run stress test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration --large-dataset
|
||||
|
||||
coverage_build:
|
||||
name: "Coverage build"
|
||||
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build coverage binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build coverage binaries.
|
||||
cd build
|
||||
cmake -DTEST_COVERAGE=ON ..
|
||||
make -j$THREADS memgraph__unit
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Run unit tests.
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- name: Compute code coverage
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Compute code coverage.
|
||||
cd tools/github
|
||||
./coverage_convert
|
||||
|
||||
# Package code coverage.
|
||||
cd generated
|
||||
tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu
|
||||
|
||||
- name: Save code coverage
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/generated/code_coverage.tar.gz
|
||||
|
||||
debug_build:
|
||||
name: "Debug build"
|
||||
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
||||
env:
|
||||
THREADS: 24
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build debug binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build debug binaries.
|
||||
cd build
|
||||
cmake ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Run leftover CTest tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Run leftover CTest tests (all except unit and benchmark tests).
|
||||
cd build
|
||||
ctest -E "(memgraph__unit|memgraph__benchmark)" --output-on-failure
|
||||
|
||||
- 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: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Run cppcheck and clang-format.
|
||||
cd tools/github
|
||||
./cppcheck_and_clang_format diff
|
||||
|
||||
- name: Save cppcheck and clang-format errors
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Code coverage"
|
||||
path: tools/github/cppcheck_and_clang_format.txt
|
||||
|
||||
release_build:
|
||||
name: "Release build"
|
||||
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
|
||||
env:
|
||||
THREADS: 24
|
||||
timeout-minutes: 960
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
uses: actions/checkout@v2
|
||||
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-v3/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build release binaries.
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=release ..
|
||||
make -j$THREADS
|
||||
|
||||
- name: Create enterprise DEB package
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
cd build
|
||||
|
||||
# create mgconsole
|
||||
# we use the -B to force the build
|
||||
make -j$THREADS -B mgconsole
|
||||
|
||||
# Create enterprise DEB package.
|
||||
mkdir output && cd output
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
|
||||
- name: Save enterprise DEB package
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "Enterprise DEB package"
|
||||
path: build/output/memgraph*.deb
|
||||
|
||||
- name: Run micro benchmark tests
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v3/activate
|
||||
|
||||
# Run micro benchmark tests.
|
||||
cd build
|
||||
# The `eval` benchmark needs a large stack limit.
|
||||
ulimit -s 262144
|
||||
ctest -R memgraph__benchmark -V
|
||||
|
||||
- name: Run macro benchmark tests
|
||||
run: |
|
||||
cd tests/macro_benchmark
|
||||
./harness QuerySuite MemgraphRunner \
|
||||
--groups aggregation 1000_create unwind_create dense_expand match \
|
||||
--no-strict
|
||||
|
||||
- name: Run parallel macro benchmark tests
|
||||
run: |
|
||||
cd tests/macro_benchmark
|
||||
./harness QueryParallelSuite MemgraphRunner \
|
||||
--groups aggregation_parallel create_parallel bfs_parallel \
|
||||
--num-database-workers 9 --num-clients-workers 30 \
|
||||
--no-strict
|
||||
|
||||
- name: Run GQL Behave tests
|
||||
run: |
|
||||
cd tests/gql_behave
|
||||
./continuous_integration
|
||||
|
||||
- name: Save quality assurance status
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: "GQL Behave Status"
|
||||
path: |
|
||||
tests/gql_behave/gql_behave_status.csv
|
||||
tests/gql_behave/gql_behave_status.html
|
||||
|
||||
- name: Run e2e tests
|
||||
run: |
|
||||
# TODO(gitbuda): Setup mgclient and pymgclient properly.
|
||||
cd tests
|
||||
./setup.sh
|
||||
source ve3/bin/activate
|
||||
cd e2e
|
||||
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 stress test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
./continuous_integration --large-dataset
|
||||
|
||||
- name: Run durability test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
source ve3/bin/activate
|
||||
python3 durability --num-steps 5
|
||||
|
||||
- name: Run durability test (large)
|
||||
run: |
|
||||
cd tests/stress
|
||||
source ve3/bin/activate
|
||||
python3 durability --num-steps 20
|
||||
@@ -1,172 +0,0 @@
|
||||
import os
|
||||
import os.path
|
||||
import fnmatch
|
||||
import logging
|
||||
import ycm_core
|
||||
|
||||
BASE_FLAGS = [
|
||||
'-Wall',
|
||||
'-Wextra',
|
||||
'-Werror',
|
||||
'-Wno-long-long',
|
||||
'-Wno-variadic-macros',
|
||||
'-fexceptions',
|
||||
'-ferror-limit=10000',
|
||||
'-std=c++1z',
|
||||
'-xc++',
|
||||
'-I/usr/lib/',
|
||||
'-I/usr/include/',
|
||||
'-I./src',
|
||||
'-I./include',
|
||||
'-I./libs/fmt',
|
||||
'-I./libs/yaml-cpp',
|
||||
'-I./libs/glog/include',
|
||||
'-I./libs/googletest/googletest/include',
|
||||
'-I./libs/googletest/googlemock/include',
|
||||
'-I./libs/benchmark/include',
|
||||
'-I./libs/cereal/include',
|
||||
# We include cppitertools headers directly from libs directory.
|
||||
'-I./libs',
|
||||
'-I./libs/rapidcheck/include',
|
||||
'-I./libs/antlr4/runtime/Cpp/runtime/src',
|
||||
'-I./libs/gflags/include',
|
||||
'-I./experimental/distributed/src',
|
||||
'-I./libs/postgresql/include',
|
||||
'-I./libs/bzip2',
|
||||
'-I./libs/zlib',
|
||||
'-I./libs/rocksdb/include',
|
||||
'-I./libs/librdkafka/include/librdkafka',
|
||||
'-I./build/include'
|
||||
]
|
||||
|
||||
SOURCE_EXTENSIONS = [
|
||||
'.cpp',
|
||||
'.cxx',
|
||||
'.cc',
|
||||
'.c',
|
||||
'.m',
|
||||
'.mm'
|
||||
]
|
||||
|
||||
HEADER_EXTENSIONS = [
|
||||
'.h',
|
||||
'.hxx',
|
||||
'.hpp',
|
||||
'.hh'
|
||||
]
|
||||
|
||||
# set the working directory of YCMD to be this file
|
||||
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
def IsHeaderFile(filename):
|
||||
extension = os.path.splitext(filename)[1]
|
||||
return extension in HEADER_EXTENSIONS
|
||||
|
||||
def GetCompilationInfoForFile(database, filename):
|
||||
if IsHeaderFile(filename):
|
||||
basename = os.path.splitext(filename)[0]
|
||||
for extension in SOURCE_EXTENSIONS:
|
||||
replacement_file = basename + extension
|
||||
if os.path.exists(replacement_file):
|
||||
compilation_info = database.GetCompilationInfoForFile(replacement_file)
|
||||
if compilation_info.compiler_flags_:
|
||||
return compilation_info
|
||||
return None
|
||||
return database.GetCompilationInfoForFile(filename)
|
||||
|
||||
def FindNearest(path, target):
|
||||
candidate = os.path.join(path, target)
|
||||
if(os.path.isfile(candidate) or os.path.isdir(candidate)):
|
||||
logging.info("Found nearest " + target + " at " + candidate)
|
||||
return candidate;
|
||||
else:
|
||||
parent = os.path.dirname(os.path.abspath(path));
|
||||
if(parent == path):
|
||||
raise RuntimeError("Could not find " + target);
|
||||
return FindNearest(parent, target)
|
||||
|
||||
def MakeRelativePathsInFlagsAbsolute(flags, working_directory):
|
||||
if not working_directory:
|
||||
return list(flags)
|
||||
new_flags = []
|
||||
make_next_absolute = False
|
||||
path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ]
|
||||
for flag in flags:
|
||||
new_flag = flag
|
||||
|
||||
if make_next_absolute:
|
||||
make_next_absolute = False
|
||||
if not flag.startswith('/'):
|
||||
new_flag = os.path.join(working_directory, flag)
|
||||
|
||||
for path_flag in path_flags:
|
||||
if flag == path_flag:
|
||||
make_next_absolute = True
|
||||
break
|
||||
|
||||
if flag.startswith(path_flag):
|
||||
path = flag[ len(path_flag): ]
|
||||
new_flag = path_flag + os.path.join(working_directory, path)
|
||||
break
|
||||
|
||||
if new_flag:
|
||||
new_flags.append(new_flag)
|
||||
return new_flags
|
||||
|
||||
|
||||
def FlagsForClangComplete(root):
|
||||
try:
|
||||
clang_complete_path = FindNearest(root, '.clang_complete')
|
||||
clang_complete_flags = open(clang_complete_path, 'r').read().splitlines()
|
||||
return clang_complete_flags
|
||||
except:
|
||||
return None
|
||||
|
||||
def FlagsForInclude(root):
|
||||
try:
|
||||
include_path = FindNearest(root, 'include')
|
||||
flags = []
|
||||
for dirroot, dirnames, filenames in os.walk(include_path):
|
||||
for dir_path in dirnames:
|
||||
real_path = os.path.join(dirroot, dir_path)
|
||||
flags = flags + ["-I" + real_path]
|
||||
return flags
|
||||
except:
|
||||
return None
|
||||
|
||||
def FlagsForCompilationDatabase(root, filename):
|
||||
try:
|
||||
compilation_db_path = FindNearest(root, 'compile_commands.json')
|
||||
compilation_db_dir = os.path.dirname(compilation_db_path)
|
||||
logging.info("Set compilation database directory to " + compilation_db_dir)
|
||||
compilation_db = ycm_core.CompilationDatabase(compilation_db_dir)
|
||||
if not compilation_db:
|
||||
logging.info("Compilation database file found but unable to load")
|
||||
return None
|
||||
compilation_info = GetCompilationInfoForFile(compilation_db, filename)
|
||||
if not compilation_info:
|
||||
logging.info("No compilation info for " + filename + " in compilation database")
|
||||
return None
|
||||
return MakeRelativePathsInFlagsAbsolute(
|
||||
compilation_info.compiler_flags_,
|
||||
compilation_info.compiler_working_dir_)
|
||||
except:
|
||||
return None
|
||||
|
||||
def FlagsForFile(filename):
|
||||
root = os.path.realpath(filename);
|
||||
compilation_db_flags = FlagsForCompilationDatabase(root, filename)
|
||||
if compilation_db_flags:
|
||||
final_flags = compilation_db_flags
|
||||
else:
|
||||
final_flags = BASE_FLAGS
|
||||
clang_flags = FlagsForClangComplete(root)
|
||||
if clang_flags:
|
||||
final_flags = final_flags + clang_flags
|
||||
include_flags = FlagsForInclude(root)
|
||||
if include_flags:
|
||||
final_flags = final_flags + include_flags
|
||||
return {
|
||||
'flags': final_flags,
|
||||
'do_cache': True
|
||||
}
|
||||
388
CHANGELOG.md
388
CHANGELOG.md
@@ -1,385 +1,5 @@
|
||||
# Change Log
|
||||
Change Log for all versions of Memgraph can be found on-line at
|
||||
https://docs.memgraph.com/memgraph/changelog
|
||||
|
||||
## v1.1.0
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* Properties in nodes and edges are now stored encoded and compressed. This
|
||||
change significantly reduces memory usage. Depending on the specific dataset,
|
||||
total memory usage can be reduced up to 50%.
|
||||
* Added support for rescanning query modules. Previously, the query modules
|
||||
directory was scanned only upon startup. Now it is scanned each time the user
|
||||
requests to load a query module. The functions used to load the query modules
|
||||
were renamed to `mg.load()` and `mg.load_all()` (from `mg.reload()` and
|
||||
`mg.reload_all()`).
|
||||
* Improved execution performance of queries that have an IN list filter by
|
||||
using label+property indices.
|
||||
Example: `MATCH (n:Label) WHERE n.property IN [] ...`
|
||||
* Added support for `ANY` and `NONE` openCypher functions. Previously, only
|
||||
`ALL` and `SINGLE` functions were implemented.
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
* Fixed invalid paths returned by variable expansion when the starting node and
|
||||
destination node used the same symbol.
|
||||
Example: `MATCH path = (n:Person {name: "John"})-[:KNOWS*]->(n) RETURN path`
|
||||
* Improved semantics of `ALL` and `SINGLE` functions to be consistent with
|
||||
openCypher when handling lists with `Null`s.
|
||||
* `SHOW CONSTRAINT INFO` now returns property names as a list for unique
|
||||
constraints.
|
||||
* Escaped label/property/edgetype names in `DUMP DATABASE` to support names
|
||||
with spaces in them.
|
||||
* Fixed handling of `DUMP DATABASE` queries in multi-command transactions
|
||||
(`BEGIN`, ..., `COMMIT`).
|
||||
* Fixed handling of various query types in explicit transactions. For example,
|
||||
constraints were allowed to be created in multi-command transactions
|
||||
(`BEGIN`, ..., `COMMIT`) but that isn't a transactional operation and as such
|
||||
can't be allowed in multi-command transactions.
|
||||
* Fixed integer overflow bugs in `COUNT`, `LIMIT` and `SKIP`.
|
||||
* Fixed integer overflow bugs in weighted shortest path expansions.
|
||||
* Fixed various other integer overflow bugs in query execution.
|
||||
* Added Marvel Comic Universe tutorial.
|
||||
* Added FootballTransfers tutorial.
|
||||
|
||||
## v1.0.0
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* [Enterprise Ed.] Exposed authentication username/rolename regex as a flag
|
||||
(`--auth-user-or-role-name-regex`).
|
||||
* [Enterprise Ed.] Improved auth module error handling and added support for
|
||||
relative paths.
|
||||
* Added support for Python query modules. This release of Memgraph supports
|
||||
query modules written using the already existing C API and the new Python
|
||||
API.
|
||||
* Added support for unique constraints. The unique constraint is created with a
|
||||
label and one or more properties.
|
||||
* Implemented support for importing CSV files (`mg_import_csv`). The importer
|
||||
is compatible with the Neo4j batch CSV importer.
|
||||
* Snapshot and write-ahead log format changed (backward compatible with v0.50).
|
||||
* Vertices looked up by their openCypher ID (`MATCH (n) WHERE ID(n) = ...`)
|
||||
will now find the node in O(logn) instead of O(n).
|
||||
* Improved planning of BFS expansion, a faster, specific approach is now
|
||||
favored instead of a ScanAll+Filter operation.
|
||||
* Added syntax for limiting memory of `CALL`.
|
||||
* Exposed server name that should be used for Bolt handshake as flag
|
||||
(`--bolt-server-name-for-init`).
|
||||
* Added several more functions to the query module C API.
|
||||
* Implemented a storage locking mechanism that prevents the user from
|
||||
concurrently starting two Memgraph instances with the same data directory.
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
* [Enterprise Ed.] Fixed a bug that crashed the database when granting
|
||||
privileges to a user.
|
||||
* [Enterprise Ed.] Improved Louvain algorithm for community detection.
|
||||
* Type of variable expansion is now printed in `EXPLAIN` (e.g. ExpandVariable,
|
||||
STShortestPath, BFSExpand, WeightedShortestPath).
|
||||
* Correctly display `CALL` in `EXPLAIN` output.
|
||||
* Correctly delimit arguments when printing the signature of a query module.
|
||||
* Fixed a planning issue when `CALL` preceded filtering.
|
||||
* Fixed spelling mistakes in the storage durability module.
|
||||
* Fixed storage GC indices/constraints subtle race condition.
|
||||
* Reduced memory allocations in storage API and indices.
|
||||
* Memgraph version is now outputted to `stdout` when Memgraph is started.
|
||||
* Improved RPM packaging.
|
||||
* Reduced number of errors reported in production log when loading query
|
||||
modules.
|
||||
* Removed `early access` wording from the Community Offering license.
|
||||
|
||||
## v0.50.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* [Enterprise Ed.] Remove support for Kafka streams.
|
||||
* Snapshot and write-ahead log format changed (not backward compatible).
|
||||
* Removed support for unique constraints.
|
||||
* Label indices aren't created automatically, create them explicitly instead.
|
||||
* Renamed several database flags. Please see the configuration file for a list of current flags.
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* [Enterprise Ed.] Add support for auth module.
|
||||
* [Enterprise Ed.] LDAP support migrated to auth module.
|
||||
* Implemented new graph storage engine.
|
||||
* Add support for disabling properties on edges.
|
||||
* Add support for existence constraints.
|
||||
* Add support for custom openCypher procedures using a C API.
|
||||
* Support loading query modules implementing read-only procedures.
|
||||
* Add `CALL <procedure> YIELD <result>` syntax for invoking loaded procedures.
|
||||
* Add `CREATE INDEX ON :Label` for creating label indices.
|
||||
* Add `DROP INDEX ON :Label` for dropping label indices.
|
||||
* Add `DUMP DATABASE` clause to openCypher.
|
||||
* Add functions for treating character strings as byte strings.
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
* Fix several memory management bugs.
|
||||
* Reduce memory usage in query execution.
|
||||
* Fix bug that crashes the database when `EXPLAIN` is used.
|
||||
|
||||
## v0.15.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Snapshot and write-ahead log format changed (not backward compatible).
|
||||
* `indexInfo()` function replaced with `SHOW INDEX INFO` syntax.
|
||||
* Removed support for unique index. Use unique constraints instead.
|
||||
* `CREATE UNIQUE INDEX ON :label (property)` replaced with `CREATE CONSTRAINT ON (n:label) ASSERT n.property IS UNIQUE`.
|
||||
* Changed semantics for `COUNTER` openCypher function.
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* [Enterprise Ed.] Add new privilege, `STATS` for accessing storage info.
|
||||
* [Enterprise Ed.] LDAP authentication and authorization support.
|
||||
* [Enterprise Ed.] Add audit logging feature.
|
||||
* Add multiple properties unique constraint which replace unique indices.
|
||||
* Add `SHOW STORAGE INFO` feature.
|
||||
* Add `PROFILE` clause to openCypher.
|
||||
* Add `CREATE CONSTRAINT` clause to openCypher.
|
||||
* Add `DROP CONSTRAINT` clause to openCypher.
|
||||
* Add `SHOW CONSTRAINT INFO` feature.
|
||||
* Add `uniformSample` function to openCypher.
|
||||
* Add regex matching to openCypher.
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
* Fix bug in explicit transaction handling.
|
||||
* Fix bug in edge filtering by edge type and destination.
|
||||
* Fix bug in query comment parsing.
|
||||
* Fix bug in query symbol table.
|
||||
* Fix OpenSSL memory leaks.
|
||||
* Make authentication case insensitive.
|
||||
* Remove `COALESCE` function.
|
||||
* Add movie tutorial.
|
||||
* Add backpacking tutorial.
|
||||
|
||||
## v0.14.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Write-ahead log format changed (not backward compatible).
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* [Enterprise Ed.] Reduce memory usage in distributed usage.
|
||||
* Add `DROP INDEX` feature.
|
||||
* Improve SSL error messages.
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
* [Enterprise Ed.] Fix issues with reading and writing in a distributed query.
|
||||
* Correctly handle an edge case with unique constraint checks.
|
||||
* Fix a minor issue with `mg_import_csv`.
|
||||
* Fix an issue with `EXPLAIN`.
|
||||
|
||||
## v0.13.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Write-ahead log format changed (not backward compatible).
|
||||
* Snapshot format changed (not backward compatible).
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* [Enterprise Ed.] Authentication and authorization support.
|
||||
* [Enterprise Ed.] Kafka integration.
|
||||
* [Enterprise Ed.] Support dynamic worker addition in distributed.
|
||||
* Reduce memory usage and improve overall performance.
|
||||
* Add `CREATE UNIQUE INDEX` clause to openCypher.
|
||||
* Add `EXPLAIN` clause to openCypher.
|
||||
* Add `inDegree` and `outDegree` functions to openCypher.
|
||||
* Improve BFS performance when both endpoints are known.
|
||||
* Add new `node-label`, `relationship-type` and `quote` options to
|
||||
`mg_import_csv` tool.
|
||||
* Reduce memory usage of `mg_import_csv`.
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
* [Enterprise Ed.] Fix an edge case in distributed index creation.
|
||||
* [Enterprise Ed.] Fix issues with Cartesian in distributed queries.
|
||||
* Correctly handle large messages in Bolt protocol.
|
||||
* Fix issues when handling explicitly started transactions in queries.
|
||||
* Allow openCypher keywords to be used as variable names.
|
||||
* Revise and make user visible error messages consistent.
|
||||
* Improve aborting time consuming execution.
|
||||
|
||||
## v0.12.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Snapshot format changed (not backward compatible).
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* Improved Id Cypher function.
|
||||
* Added string functions to openCypher (`lTrim`, `left`, `rTrim`, `replace`,
|
||||
`reverse`, `right`, `split`, `substring`, `toLower`, `toUpper`, `trim`).
|
||||
* Added `timestamp` function to openCypher.
|
||||
* Added support for dynamic property access with `[]` operator.
|
||||
|
||||
## v0.11.0
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* [Enterprise Ed.] Improve Cartesian support in distributed queries.
|
||||
* [Enterprise Ed.] Improve distributed execution of BFS.
|
||||
* [Enterprise Ed.] Dynamic graph partitioner added.
|
||||
* Static nodes/edges id generators exposed through the Id Cypher function.
|
||||
* Properties on disk added.
|
||||
* Telemetry added.
|
||||
* SSL support added.
|
||||
* `toString` function added.
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
* Document issues with Docker on OS X.
|
||||
* Add BFS and Dijkstra's algorithm examples to documentation.
|
||||
|
||||
## v0.10.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Snapshot format changed (not backward compatible).
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* [Enterprise Ed.] Distributed storage and execution.
|
||||
* `reduce` and `single` functions added to openCypher.
|
||||
* `wShortest` edge expansion added to openCypher.
|
||||
* Support packaging RPM on CentOS 7.
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
* Report an error if updating a deleted element.
|
||||
* Log an error if reading info on available memory fails.
|
||||
* Fix a bug when `MATCH` would stop matching if a result was empty, but later
|
||||
results still contain data to be matched. The simplest case of this was the
|
||||
query: `UNWIND [1,2,3] AS x MATCH (n :Label {prop: x}) RETURN n`. If there
|
||||
was no node `(:Label {prop: 1})`, then the `MATCH` wouldn't even try to find
|
||||
for `x` being 2 or 3.
|
||||
* Report an error if trying to compare a property value with something that
|
||||
cannot be stored in a property.
|
||||
* Fix crashes in some obscure cases.
|
||||
* Commit log automatically garbage collected.
|
||||
* Add minor performance improvements.
|
||||
|
||||
## v0.9.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Snapshot format changed (not backward compatible).
|
||||
* Snapshot configuration flags changed, general durability flags added.
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* Write-ahead log added.
|
||||
* `nodes` and `relationships` functions added.
|
||||
* `UNION` and `UNION ALL` is implemented.
|
||||
* Concurrent index creation is now enabled.
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
|
||||
## v0.8.0
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* CASE construct (without aggregations).
|
||||
* Named path support added.
|
||||
* Maps can now be stored as node/edge properties.
|
||||
* Map indexing supported.
|
||||
* `rand` function added.
|
||||
* `assert` function added.
|
||||
* `counter` and `counterSet` functions added.
|
||||
* `indexInfo` function added.
|
||||
* `collect` aggregation now supports Map collection.
|
||||
* Changed the BFS syntax.
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
* Use \u to specify 4 digit codepoint and \U for 8 digit
|
||||
* Keywords appearing in header (named expressions) keep original case.
|
||||
* Our Bolt protocol implementation is now completely compatible with the protocol version 1 specification. (https://boltprotocol.org/v1/)
|
||||
* Added a log warning when running out of memory and the `memory_warning_threshold` flag
|
||||
* Edges are no longer additionally filtered after expansion.
|
||||
|
||||
## v0.7.0
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* Variable length path `MATCH`.
|
||||
* Explicitly started transactions (multi-query transactions).
|
||||
* Map literal.
|
||||
* Query parameters (except for parameters in place of property maps).
|
||||
* `all` function in openCypher.
|
||||
* `degree` function in openCypher.
|
||||
* User specified transaction execution timeout.
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
* Concurrent `BUILD INDEX` deadlock now returns an error to the client.
|
||||
* A `MATCH` preceeded by `OPTIONAL MATCH` expansion inconsistencies.
|
||||
* High concurrency Antlr parsing bug.
|
||||
* Indexing improvements.
|
||||
* Query stripping and caching speedups.
|
||||
|
||||
## v0.6.0
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* AST caching.
|
||||
* Label + property index support.
|
||||
* Different logging setup & format.
|
||||
|
||||
## v0.5.0
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* Use label indexes to speed up querying.
|
||||
* Generate multiple query plans and use the cost estimator to select the best.
|
||||
* Snapshots & Recovery.
|
||||
* Abandon old yaml configuration and migrate to gflags.
|
||||
* Query stripping & AST caching support.
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
* Fixed race condition in MVCC. Hints exp+aborted race condition prevented.
|
||||
* Fixed conceptual bug in MVCC GC. Evaluate old records w.r.t. the oldest.
|
||||
transaction's id AND snapshot.
|
||||
* User friendly error messages thrown from the query engine.
|
||||
|
||||
## Build 837
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
* List indexing supported with preceeding IN (for example in query `RETURN 1 IN [[1,2]][0]`).
|
||||
|
||||
## Build 825
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* RETURN *, count(*), OPTIONAL MATCH, UNWIND, DISTINCT (except DISTINCT in aggregate functions), list indexing and slicing, escaped labels, IN LIST operator, range function.
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
* TCP_NODELAY -> import should be faster.
|
||||
* Clear hint bits.
|
||||
|
||||
## Build 783
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* SKIP, LIMIT, ORDER BY.
|
||||
* Math functions.
|
||||
* Initial support for MERGE clause.
|
||||
|
||||
### Bug Fixes and Other Changes
|
||||
|
||||
* Unhandled Lock Timeout Exception.
|
||||
|
||||
## Build 755
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* MATCH, CREATE, WHERE, SET, REMOVE, DELETE.
|
||||
All the updates to the Change Log can be made in the following repository:
|
||||
https://github.com/memgraph/docs
|
||||
|
||||
@@ -35,6 +35,9 @@ else()
|
||||
message(FATAL_ERROR "Couldn't find clang and/or clang++!")
|
||||
endif()
|
||||
|
||||
option(BUILD_FOR_DOCKER "Build Memgraph binary for docker." OFF)
|
||||
message(STATUS "BUILD_FOR_DOCKER: ${BUILD_FOR_DOCKER}")
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
project(memgraph)
|
||||
@@ -67,28 +70,52 @@ set(get_version_script "${CMAKE_SOURCE_DIR}/release/get_version.py")
|
||||
# Get version that should be used in the binary.
|
||||
execute_process(
|
||||
OUTPUT_VARIABLE MEMGRAPH_VERSION
|
||||
RESULT_VARIABLE MEMGRAPH_VERSION_RESULT
|
||||
COMMAND "${get_version_script}" ${get_version_enterprise}
|
||||
"${MEMGRAPH_OVERRIDE_VERSION}"
|
||||
"${MEMGRAPH_OVERRIDE_VERSION_SUFFIX}"
|
||||
"--memgraph-root-dir"
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
)
|
||||
if(MEMGRAPH_VERSION_RESULT AND NOT MEMGRAPH_VERSION_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "Unable to get Memgraph version.")
|
||||
else()
|
||||
MESSAGE(STATUS "Memgraph version: ${MEMGRAPH_VERSION}")
|
||||
endif()
|
||||
|
||||
# Get version that should be used in the DEB package.
|
||||
execute_process(
|
||||
OUTPUT_VARIABLE MEMGRAPH_VERSION_DEB
|
||||
RESULT_VARIABLE MEMGRAPH_VERSION_DEB_RESULT
|
||||
COMMAND "${get_version_script}" ${get_version_enterprise}
|
||||
--variant deb
|
||||
"${MEMGRAPH_OVERRIDE_VERSION}"
|
||||
"${MEMGRAPH_OVERRIDE_VERSION_SUFFIX}"
|
||||
"--memgraph-root-dir"
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
)
|
||||
if(MEMGRAPH_VERSION_DEB_RESULT AND NOT MEMGRAPH_VERSION_DEB_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "Unable to get Memgraph DEB version.")
|
||||
else()
|
||||
MESSAGE(STATUS "Memgraph DEB version: ${MEMGRAPH_VERSION_DEB}")
|
||||
endif()
|
||||
|
||||
# Get version that should be used in the RPM package.
|
||||
execute_process(
|
||||
OUTPUT_VARIABLE MEMGRAPH_VERSION_RPM
|
||||
RESULT_VARIABLE MEMGRAPH_VERSION_RPM_RESULT
|
||||
COMMAND "${get_version_script}" ${get_version_enterprise}
|
||||
--variant rpm
|
||||
"${MEMGRAPH_OVERRIDE_VERSION}"
|
||||
"${MEMGRAPH_OVERRIDE_VERSION_SUFFIX}"
|
||||
"--memgraph-root-dir"
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
)
|
||||
if(MEMGRAPH_VERSION_RPM_RESULT AND NOT MEMGRAPH_VERSION_RPM_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "Unable to get Memgraph RPM version.")
|
||||
else()
|
||||
MESSAGE(STATUS "Memgraph RPM version: ${MEMGRAPH_VERSION_RPM}")
|
||||
endif()
|
||||
|
||||
# We want the above variables to be updated each time something is committed to
|
||||
# the repository. That is why we include a dependency on the current git HEAD
|
||||
@@ -149,20 +176,23 @@ add_custom_target(clean_all
|
||||
# is easier debugging of compilation and linker flags.
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
# c99-designator is disabled because of required mixture of designated and
|
||||
# non-designated initializers in Python Query Module code (`py_module.cpp`).
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall \
|
||||
-Werror=switch -Werror=switch-bool -Werror=return-type \
|
||||
-Werror=return-stack-address")
|
||||
-Werror=return-stack-address \
|
||||
-Wno-c99-designator")
|
||||
|
||||
# Don't omit frame pointer in RelWithDebInfo, for additional callchain debug.
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO
|
||||
"${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -fno-omit-frame-pointer")
|
||||
|
||||
# Statically link libgcc and libstdc++, the GCC allows this according to:
|
||||
# https://gcc.gnu.org/onlinedocs/gcc-8.3.0/libstdc++/manual/manual/license.html
|
||||
# https://gcc.gnu.org/onlinedocs/gcc-10.2.0/libstdc++/manual/manual/license.html
|
||||
# https://www.gnu.org/licenses/gcc-exception-faq.html
|
||||
# Last checked for gcc-8.3 which we are using on the build machines.
|
||||
# Last checked for gcc-10.2 which we are using on the build machines.
|
||||
# ** If we change versions, recheck this! **
|
||||
# ** Static linking is allowed only for executables! **
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++")
|
||||
@@ -282,10 +312,13 @@ if (UBSAN)
|
||||
# runtime library and c++ standard libraries are present.
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fno-omit-frame-pointer -fno-sanitize=vptr")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fno-sanitize=vptr")
|
||||
# Run program with environment variable UBSAN_OPTIONS=print_stacktrace=1
|
||||
# Make sure llvm-symbolizer binary is in path
|
||||
# Run program with environment variable UBSAN_OPTIONS=print_stacktrace=1.
|
||||
# Make sure llvm-symbolizer binary is in path.
|
||||
# To make the program abort on undefined behavior, use UBSAN_OPTIONS=halt_on_error=1.
|
||||
endif()
|
||||
|
||||
set(MG_PYTHON_VERSION "" CACHE STRING "Specify the exact python version used by the query modules")
|
||||
|
||||
# Add subprojects
|
||||
include_directories(src)
|
||||
add_subdirectory(src)
|
||||
@@ -303,3 +336,7 @@ endif()
|
||||
if(QUERY_MODULES)
|
||||
add_subdirectory(query_modules)
|
||||
endif()
|
||||
|
||||
install(FILES ${CMAKE_BINARY_DIR}/bin/mgconsole
|
||||
PERMISSIONS OWNER_EXECUTE OWNER_READ OWNER_WRITE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
|
||||
TYPE BIN)
|
||||
|
||||
1
CODEOWNERS
Normal file
1
CODEOWNERS
Normal file
@@ -0,0 +1 @@
|
||||
* @gitbuda @antonio2368 @antaljanosbenjamin @kostasrim @jbajic
|
||||
2
Doxyfile
2
Doxyfile
@@ -51,7 +51,7 @@ PROJECT_BRIEF = "The World's Most Powerful Graph Database"
|
||||
# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
|
||||
# the logo to the output directory.
|
||||
|
||||
PROJECT_LOGO = Doxylogo.png
|
||||
PROJECT_LOGO = docs/doxygen/memgraph_logo.png
|
||||
|
||||
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
|
||||
# into which the generated documentation will be written. If a relative path is
|
||||
|
||||
29
README.md
29
README.md
@@ -1,24 +1,21 @@
|
||||
# memgraph
|
||||
# Memgraph
|
||||
|
||||
Memgraph is an ACID compliant high performance transactional distributed
|
||||
in-memory graph database featuring runtime native query compiling, lock free
|
||||
data structures, multi-version concurrency control and asynchronous IO.
|
||||
|
||||
## dependencies
|
||||
## Development Documentation
|
||||
|
||||
Memgraph can be compiled using any modern c++ compiler. It mostly relies on
|
||||
the standard template library, however, some things do require external
|
||||
libraries.
|
||||
Please continue in
|
||||
[Notion](https://www.notion.so/memgraph/memgraph-0428591638604c8385550e214ea9f3e6).
|
||||
|
||||
Some code contains linux-specific libraries and the build is only supported
|
||||
on a 64 bit linux kernel.
|
||||
## User Documentation
|
||||
|
||||
* linux
|
||||
* clang 3.8 (good c++11 support, especially lock free atomics)
|
||||
* antlr (compiler frontend)
|
||||
* cppitertools
|
||||
* fmt format
|
||||
* google benchmark
|
||||
* google test
|
||||
* glog
|
||||
* gflags
|
||||
Memgraph user documentation is maintained within
|
||||
[docs](https://github.com/memgraph/docs) repository. The documentation is also
|
||||
available on [GitBook](https://docs.memgraph.com).
|
||||
|
||||
## Licences
|
||||
|
||||
* [Community](release/LICENSE_COMMUNITY.md)
|
||||
* [Enterprise](release/LICENSE_ENTERPRISE.md)
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import os
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
||||
def find_packages(build_output_dir):
|
||||
ret = []
|
||||
output_dir = os.path.join(SCRIPT_DIR, build_output_dir)
|
||||
if os.path.exists(output_dir):
|
||||
for fname in os.listdir(output_dir):
|
||||
if fname.startswith("memgraph") and fname.endswith(".deb"):
|
||||
path = os.path.join(build_output_dir, fname)
|
||||
ret.append({
|
||||
"name": "Release " + fname.split("_")[1] +
|
||||
" (deb package)",
|
||||
"archive": path,
|
||||
})
|
||||
return ret
|
||||
|
||||
|
||||
archives = []
|
||||
# Find enterprise package(s).
|
||||
archives += find_packages(os.path.join("build_release", "output"))
|
||||
# Find community package(s).
|
||||
archives += find_packages(os.path.join("build_community", "output"))
|
||||
|
||||
print(json.dumps(archives, indent=4, sort_keys=True))
|
||||
@@ -1,17 +0,0 @@
|
||||
- name: Binaries
|
||||
archive:
|
||||
- build_debug/memgraph
|
||||
- build_debug/config/memgraph.conf
|
||||
- build_release/memgraph
|
||||
- build_release/config/memgraph.conf
|
||||
- build_release/tools/src/mg_client
|
||||
- build_community/memgraph
|
||||
- build_community/config/memgraph.conf
|
||||
filename: binaries.tar.gz
|
||||
|
||||
- name: Doxygen documentation
|
||||
cd: docs/doxygen/html
|
||||
archive:
|
||||
- .
|
||||
filename: documentation.tar.gz
|
||||
host: true
|
||||
@@ -1,110 +0,0 @@
|
||||
- name: Diff build
|
||||
project: ^mg-master-diff$
|
||||
commands: |
|
||||
# Activate toolchain
|
||||
export PATH=/opt/toolchain-v1/bin:$PATH
|
||||
export LD_LIBRARY_PATH=/opt/toolchain-v1/lib:/opt/toolchain-v1/lib64
|
||||
|
||||
# Copy untouched repository to parent folder.
|
||||
cd ..
|
||||
cp -r memgraph parent
|
||||
cd memgraph
|
||||
|
||||
# Initialize and create documentation.
|
||||
TIMEOUT=1200 ./init
|
||||
doxygen Doxyfile
|
||||
|
||||
# Remove default build directory.
|
||||
rm -r build
|
||||
|
||||
# Build debug binaries.
|
||||
mkdir build_debug
|
||||
cd build_debug
|
||||
cmake ..
|
||||
TIMEOUT=1200 make -j$THREADS
|
||||
|
||||
# Build coverage binaries.
|
||||
cd ..
|
||||
mkdir build_coverage
|
||||
cd build_coverage
|
||||
cmake -DTEST_COVERAGE=ON ..
|
||||
TIMEOUT=1200 make -j$THREADS memgraph__unit
|
||||
|
||||
# Build release binaries.
|
||||
cd ..
|
||||
mkdir build_release
|
||||
cd build_release
|
||||
cmake -DCMAKE_BUILD_TYPE=release ..
|
||||
TIMEOUT=1200 make -j$THREADS
|
||||
|
||||
# Build community binaries.
|
||||
cd ..
|
||||
mkdir build_community
|
||||
cd build_community
|
||||
cmake -DCMAKE_BUILD_TYPE=release -DMG_ENTERPRISE=OFF ..
|
||||
TIMEOUT=1200 make -j$THREADS
|
||||
cd ..
|
||||
|
||||
# Checkout to parent commit and initialize.
|
||||
cd ../parent
|
||||
git checkout HEAD~1
|
||||
TIMEOUT=1200 ./init
|
||||
|
||||
# Build parent release binaries.
|
||||
mkdir build_release
|
||||
cd build_release
|
||||
cmake -DCMAKE_BUILD_TYPE=release ..
|
||||
TIMEOUT=1200 make -j$THREADS memgraph memgraph__macro_benchmark
|
||||
|
||||
|
||||
# release build is the default one
|
||||
- name: Release build
|
||||
commands: |
|
||||
# Activate toolchain
|
||||
export PATH=/opt/toolchain-v1/bin:$PATH
|
||||
export LD_LIBRARY_PATH=/opt/toolchain-v1/lib:/opt/toolchain-v1/lib64
|
||||
|
||||
# Initialize and create documentation.
|
||||
TIMEOUT=1200 ./init
|
||||
doxygen Doxyfile
|
||||
|
||||
# Remove default build directory.
|
||||
rm -r build
|
||||
|
||||
# Build debug binaries.
|
||||
mkdir build_debug
|
||||
cd build_debug
|
||||
cmake ..
|
||||
TIMEOUT=1200 make -j$THREADS
|
||||
|
||||
# Build coverage binaries.
|
||||
cd ..
|
||||
mkdir build_coverage
|
||||
cd build_coverage
|
||||
cmake -DTEST_COVERAGE=ON ..
|
||||
TIMEOUT=1200 make -j$THREADS memgraph__unit
|
||||
|
||||
# Build release binaries.
|
||||
cd ..
|
||||
mkdir build_release
|
||||
cd build_release
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DUSE_READLINE=OFF ..
|
||||
TIMEOUT=1200 make -j$THREADS
|
||||
|
||||
# Create Debian package.
|
||||
mkdir output
|
||||
cd output
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
cd ..
|
||||
|
||||
# Build community binaries.
|
||||
cd ..
|
||||
mkdir build_community
|
||||
cd build_community
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DUSE_READLINE=OFF -DMG_ENTERPRISE=OFF ..
|
||||
TIMEOUT=1200 make -j$THREADS
|
||||
|
||||
# Create Debian package.
|
||||
mkdir output
|
||||
cd output
|
||||
cpack -G DEB --config ../CPackConfig.cmake
|
||||
@@ -39,12 +39,8 @@ modifications:
|
||||
value: "/var/log/memgraph/memgraph.log"
|
||||
override: true
|
||||
|
||||
- name: "bolt_cert_file"
|
||||
value: "/etc/memgraph/ssl/cert.pem"
|
||||
override: true
|
||||
|
||||
- name: "bolt_key_file"
|
||||
value: "/etc/memgraph/ssl/key.pem"
|
||||
- name: "log_level"
|
||||
value: "WARNING"
|
||||
override: true
|
||||
|
||||
- name: "bolt_num_workers"
|
||||
@@ -87,15 +83,21 @@ modifications:
|
||||
value: "/usr/lib/memgraph/auth_module/example.py"
|
||||
override: false
|
||||
|
||||
- name: "memory_limit"
|
||||
value: "0"
|
||||
override: true
|
||||
|
||||
- name: "isolation_level"
|
||||
value: "SNAPSHOT_ISOLATION"
|
||||
override: true
|
||||
|
||||
- name: "allow_load_csv"
|
||||
value: "true"
|
||||
override: false
|
||||
|
||||
undocumented:
|
||||
- "flag_file"
|
||||
- "log_file_mode"
|
||||
- "log_link_basename"
|
||||
- "log_prefix"
|
||||
- "max_log_size"
|
||||
- "min_log_level"
|
||||
- "also_log_to_stderr"
|
||||
- "help"
|
||||
- "help_xml"
|
||||
- "stderr_threshold"
|
||||
- "stop_logging_if_full_disk"
|
||||
- "version"
|
||||
|
||||
@@ -1,269 +0,0 @@
|
||||
# Code Review Guidelines
|
||||
|
||||
This chapter describes some of the things you should be on the lookout when
|
||||
reviewing someone else's code.
|
||||
|
||||
## Exceptions
|
||||
|
||||
Although the Google C++ Style Guide forbids exceptions, we do allow them in
|
||||
our codebase. As a reviewer you should watch out for the following.
|
||||
|
||||
The documentation of throwing functions needs to be in-sync with the
|
||||
implementation. This must be enforced recursively. I.e. if a function A now
|
||||
throws a new exception, and the function B uses A, then B needs to handle that
|
||||
exception or have its documentation updated and so on. Naturally, the same
|
||||
applies when an exception is removed.
|
||||
|
||||
Transitive callers of the function which throws a new exception must be OK
|
||||
with that. This ties into the previous point. You need to check that all users
|
||||
of the new exception either handle it correctly or propagate it.
|
||||
|
||||
Exceptions should not escape out of class destructors, because that will
|
||||
terminate the program. The code should be changed so that such cases are not
|
||||
possible.
|
||||
|
||||
Exceptions being thrown in class constructors. Although this is well defined
|
||||
in C++, it usually implies that a constructor is doing too much work and the
|
||||
class construction & initialization should be redesigned. Usual approaches are
|
||||
using the (Static) Factory Method pattern or having some sort of an
|
||||
initialization method that needs to be called after the construction is done.
|
||||
Prefer the Factory Method.
|
||||
|
||||
Don't forget that STL functions may also throw!
|
||||
|
||||
## Pointers & References
|
||||
|
||||
In cases when some code passes a pointer or reference, or if a code stores a
|
||||
pointer or reference you should take a careful look at the following.
|
||||
|
||||
* Lifetime of the pointed to value (this includes both ownership and
|
||||
multithreaded access).
|
||||
* In case of a class, check validity of destructor and move/copy
|
||||
constructors.
|
||||
* Is the pointed to value mutated, if not it should be `const` (`const Type
|
||||
*` or `const Type &`).
|
||||
|
||||
## Allocators & Memory Resources
|
||||
|
||||
With the introduction of polymorphic allocators (C++17 `<memory_resource>` and
|
||||
our `utils/memory.hpp`) we get a more convenient type signatures for
|
||||
containers so as to keep the outward facing API nice. This convenience comes
|
||||
at a cost of less static checks on the type level due to type erasure.
|
||||
|
||||
For example:
|
||||
|
||||
std::pmr::vector<int> first_vec(std::pmr::null_memory_resource());
|
||||
std::pmr::vector<int> second_vec(std::pmr::new_delete_resource());
|
||||
|
||||
second_vec = first_vec // What happens here?
|
||||
|
||||
// Or with our implementation
|
||||
utils::MonotonicBufferResource monotonic_memory(1024);
|
||||
std::vector<int, utils::Allocator<int>> first_vec(&monotonic_memory);
|
||||
std::vector<int, utils::Allocator<int>> second_vec(utils::NewDeleteResource());
|
||||
|
||||
second_vec = first_vec // What happens here?
|
||||
|
||||
In the above, both `first_vec` and `second_vec` have the same type, but have
|
||||
*different* allocators! This can lead to ambiguity when moving or copying
|
||||
elements between them.
|
||||
|
||||
You need to watch out for the following.
|
||||
|
||||
* Swapping can lead to undefined behaviour if the allocators are not equal.
|
||||
* Is the move construction done with the right allocator.
|
||||
* Is the move assignment done correctly, also it may throw an exception.
|
||||
* Is the copy construction done with the right allocator.
|
||||
* Is the copy assignment done correctly.
|
||||
* Using `auto` makes allocator propagation rules rather ambiguous.
|
||||
|
||||
## Classes & Object Oriented Programming
|
||||
|
||||
A common mistake is to use classes, inheritance and "OOP" when it's not
|
||||
needed. This sections shows examples of encountered cases.
|
||||
|
||||
### Classes without (Meaningful) Members
|
||||
|
||||
class MyCoolClass {
|
||||
public:
|
||||
int BeCool(int a, int b) { return a + b; }
|
||||
|
||||
void SaySomethingCool() { std::cout << "Hello!"; }
|
||||
};
|
||||
|
||||
The above class has no members (i.e. state) which affect the behaviour of
|
||||
methods. This class should need not exist, it can be easily replaced with a
|
||||
more modular (and shorter) design -- top level functions.
|
||||
|
||||
int BeCool(int a, int b) { return a + b; }
|
||||
|
||||
void SaySomethingCool() { std::cout << "Hello!"; }
|
||||
|
||||
### Classes with a Single Public Method
|
||||
|
||||
clas MyAwesomeClass {
|
||||
public:
|
||||
MyAwesomeClass(int state) : state_(state) {}
|
||||
|
||||
int GetAwesome() { return GetAwesomeImpl() + 1; }
|
||||
|
||||
private:
|
||||
int state_;
|
||||
|
||||
int GetAwesomeImpl() { return state_; }
|
||||
};
|
||||
|
||||
The above class has a `state_` and even a private method, but there's only one
|
||||
public method -- `GetAwesome`.
|
||||
|
||||
You should check "Does the stored state have any meaningful influence on the
|
||||
public method?", similarly to the previous point.
|
||||
|
||||
In the above case it doesn't, and the class should be replaced with a public
|
||||
function in `.hpp` while the private method should become a private function
|
||||
in `.cpp` (static or in anonymous namespace).
|
||||
|
||||
// hpp
|
||||
int GetAwesome(int state);
|
||||
|
||||
// cpp
|
||||
namespace {
|
||||
int GetAwesomeImpl(int state) { return state; }
|
||||
}
|
||||
int GetAwesome(int state) { return GetAwesomeImpl(state) + 1; }
|
||||
|
||||
A counterexample is when the state is meaningful.
|
||||
|
||||
class Counter {
|
||||
public:
|
||||
Counter(int state) : state_(state) {}
|
||||
|
||||
int Get() { return state_++; }
|
||||
|
||||
private:
|
||||
int state_;
|
||||
};
|
||||
|
||||
But even that could be replaced with a closure.
|
||||
|
||||
auto MakeCounter(int state) {
|
||||
return [state]() mutable { return state++; };
|
||||
}
|
||||
|
||||
### Private Methods
|
||||
|
||||
Instead of private methods, top level functions should be preferred. The
|
||||
reasoning is completely explained in "Effective C++" Item 23 by Scott Meyers.
|
||||
In our codebase, even improvements to compilation times can be noticed if
|
||||
private methods in interface (`.hpp`) files are replaced with top level
|
||||
functions in implementation (`.cpp`) files.
|
||||
|
||||
### Inheritance
|
||||
|
||||
The rule is simple -- if there are no virtual methods (but maybe destructor),
|
||||
then the class should be marked as `final` and never inherited.
|
||||
|
||||
If there are virtual methods (i.e. class is meant to be inherited), make sure
|
||||
that either a public virtual destructor or a protected non-virtual destructor
|
||||
exist. See "Effective C++" Item 7 by Scott Meyers. Also take a look at
|
||||
"Effective C++" Items 32---39 by Scott Meyers.
|
||||
|
||||
An example of how inheritance with no virtual methods is replaced with
|
||||
composition.
|
||||
|
||||
class MyBase {
|
||||
public:
|
||||
virtual ~MyBase() {}
|
||||
|
||||
void DoSomethingBase() { ... }
|
||||
};
|
||||
|
||||
class MyDerived final : public MyBase {
|
||||
public:
|
||||
void DoSomethingNew() { ... DoSomethingBase(); ... }
|
||||
};
|
||||
|
||||
With composition, the above becomes.
|
||||
|
||||
class MyBase final {
|
||||
public:
|
||||
void DoSomethingBase() { ... }
|
||||
};
|
||||
|
||||
class MyDerived final {
|
||||
MyBase base_;
|
||||
|
||||
public:
|
||||
void DoSomethingNew() { ... base_.DoSomethingBase(); ... }
|
||||
};
|
||||
|
||||
The composition approach is preferred as it encapsulates the fact that
|
||||
`MyBase` is used for the implementation and users only interact with the
|
||||
public interface of `MyDerived`. Additionally, you can easily replace `MyBase
|
||||
base_;` with a C++ PIMPL idiom (`std::unique_ptr<MyBase> base_;`) to make the
|
||||
code more modular with regards to compilation.
|
||||
|
||||
More advanced C++ users will recognize that the encapsulation feature of the
|
||||
non-PIMPL composition can be replaced with private inheritance.
|
||||
|
||||
class MyDerived final : private MyBase {
|
||||
public:
|
||||
void DoSomethingNew() { ... MyBase::DoSomethingBase(); ... }
|
||||
};
|
||||
|
||||
One of the common "counterexample" is the ability to store objects of
|
||||
different type in a container or pass them to a function. Unfortunately, this
|
||||
is not that good of a design. For example.
|
||||
|
||||
class MyBase {
|
||||
... // No virtual methods (but the destructor)
|
||||
};
|
||||
|
||||
class MyFirstClass final : public MyBase { ... };
|
||||
|
||||
class MySecondClass final : public MyBase { ... };
|
||||
|
||||
std::vector<std::unique_ptr<MyBase>> first_and_second_classes;
|
||||
first_and_second_classes.push_back(std::make_unique<MyFirstClass>());
|
||||
first_and_second_classes.push_back(std::make_unique<MySecondClass>());
|
||||
|
||||
void FunctionOnFirstOrSecond(const MyBase &first_or_second, ...) { ... }
|
||||
|
||||
With C++17, the containers for different types should be implemented with
|
||||
`std::variant`, and as before the functions can be templated.
|
||||
|
||||
class MyFirstClass final { ... };
|
||||
|
||||
class MySecondClass final { ... };
|
||||
|
||||
std::vector<std::variant<MyFirstClass, MySecondClass>> first_and_second_classes;
|
||||
// Notice no heap allocation, since we don't store a pointer
|
||||
first_and_second_classes.emplace_back(MyFirstClass());
|
||||
first_and_second_classes.emplace_back(MySecondClass());
|
||||
|
||||
// You can also use `std::variant` here instead of template
|
||||
template <class TFirstOrSecond>
|
||||
void FunctionOnFirstOrSecond(const TFirstOrSecond &first_or_second, ...) { ... }
|
||||
|
||||
Naturally, if the base class has meaningful virtual methods (i.e. other than
|
||||
destructor) it maybe is OK to use inheritance but also consider alternatives.
|
||||
See "Effective C++" Items 32---39 by Scott Meyers.
|
||||
|
||||
### Multiple Inheritance
|
||||
|
||||
Multiple inheritance should not be used unless all base classes are pure
|
||||
interface classes. This decision is inherited from [Google C++ Style
|
||||
Guide](https://google.github.io/styleguide/cppguide.html#Inheritance). For
|
||||
example on how to design with and around multiple inheritance refer to
|
||||
"Effective C++" Item 40 by Scott Meyers.
|
||||
|
||||
Naturally, if there *really* is no better design, then multiple inheritance is
|
||||
allowed. An example of this can be found in our codebase when inheriting
|
||||
Visitor classes (though even that could be replaced with `std::variant` for
|
||||
example).
|
||||
|
||||
## Code Format & Style
|
||||
|
||||
If something doesn't conform to our code formatting and style, just refer the
|
||||
author to either [C++ Style](cpp-code-conventions.md) or [Other Code
|
||||
Conventions](other-code-conventions.md).
|
||||
@@ -1,263 +0,0 @@
|
||||
# C++ Code Conventions
|
||||
|
||||
This chapter describes code conventions which should be followed when writing
|
||||
C++ code.
|
||||
|
||||
## Code Style
|
||||
|
||||
Memgraph uses the
|
||||
[Google Style Guide for C++](https://google.github.io/styleguide/cppguide.html)
|
||||
in most of its code. You should follow them whenever writing new code.
|
||||
Besides following the style guide, take a look at
|
||||
[Code Review Guidelines](code-review.md) for common design issues and pitfalls
|
||||
with C++ as well as [Required Reading](required-reading.md).
|
||||
|
||||
### Often Overlooked Style Conventions
|
||||
|
||||
#### Pointers & References
|
||||
|
||||
References provide a shorter syntax for accessing members and better declare
|
||||
the intent that a pointer *should* not be `nullptr`. They do not prevent
|
||||
accessing a `nullptr` and obfuscate the client/calling code because the
|
||||
reference argument is passed just like a value. Errors with such code have
|
||||
been very difficult to debug. Therefore, pointers are always used. They will
|
||||
not prevent bugs but will make some of them more obvious when reading code.
|
||||
|
||||
The only time a reference can be used is if it is `const`. Note that this
|
||||
kind of reference is not allowed if it is stored somewhere, i.e. in a class.
|
||||
You should use a pointer to `const` then. The primary reason being is that
|
||||
references obscure the semantics of moving an object, thus making bugs with
|
||||
references pointing to invalid memory harder to track down.
|
||||
|
||||
[Style guide reference](https://google.github.io/styleguide/cppguide.html#Reference_Arguments)
|
||||
|
||||
#### Constructors & RAII
|
||||
|
||||
RAII (Resource Acquisition is Initialization) is a nice mechanism for managing
|
||||
resources. It is especially useful when exceptions are used, such as in our
|
||||
code. Unfortunately, they do have 2 major downsides.
|
||||
|
||||
* Only exceptions can be used for to signal failure.
|
||||
* Calls to virtual methods are not resolved as expected.
|
||||
|
||||
For those reasons the style guide recommends minimal work that cannot fail.
|
||||
Using virtual methods or doing a lot more should be delegated to some form of
|
||||
`Init` method, possibly coupled with static factory methods. Similar rules
|
||||
apply to destructors, which are not allowed to even throw exceptions.
|
||||
|
||||
[Style guide reference](https://google.github.io/styleguide/cppguide.html#Doing_Work_in_Constructors)
|
||||
|
||||
### Additional Style Conventions
|
||||
|
||||
Old code may have broken Google C++ Style accidentally, but the new code
|
||||
should adhere to it as close as possible. We do have some exceptions
|
||||
to Google style as well as additions for unspecified conventions.
|
||||
|
||||
#### Using C++ Exceptions
|
||||
|
||||
Unlike Google, we do not forbid using exceptions.
|
||||
|
||||
But, you should be very careful when using them and introducing new ones. They
|
||||
are indeed handy, but cause problems with understanding the control flow since
|
||||
exceptions are another form of `goto`. It also becomes very hard to determine
|
||||
that the program is in correct state after the stack is unwound and the thrown
|
||||
exception handled. Other than those issues, throwing exceptions in destructors
|
||||
will terminate the program. The same will happen if a thread doesn't handle an
|
||||
exception even though it is not the main thread.
|
||||
|
||||
[Style guide reference](https://google.github.io/styleguide/cppguide.html#Exceptions)
|
||||
|
||||
In general, when introducing a new exception, either via `throw` statement or
|
||||
calling a function which throws, you must examine all transitive callers and
|
||||
update their implementation and/or documentation.
|
||||
|
||||
#### Assertions
|
||||
|
||||
We use `CHECK` and `DCHECK` macros from glog library. You are encouraged to
|
||||
use them as often as possible to both document and validate various pre and
|
||||
post conditions of a function.
|
||||
|
||||
`CHECK` remains even in release build and should be preferred over it's cousin
|
||||
`DCHECK` which only exists in debug builds. The primary reason is that you
|
||||
want to trigger assertions in release builds in case the tests didn't
|
||||
completely validate all code paths. It is better to fail fast and crash the
|
||||
program, than to leave it in undefined state and potentially corrupt end
|
||||
user's work. In cases when profiling shows that `CHECK` is causing visible
|
||||
slowdown you should switch to `DCHECK`.
|
||||
|
||||
#### Template Parameter Naming
|
||||
|
||||
Template parameter names should start with capital letter 'T' followed by a
|
||||
short descriptive name. For example:
|
||||
|
||||
```cpp
|
||||
template <typename TKey, typename TValue>
|
||||
class KeyValueStore
|
||||
```
|
||||
|
||||
## Code Formatting
|
||||
|
||||
You should install `clang-format` and run it on code you change or add. The
|
||||
root of Memgraph's project contains the `.clang-format` file, which specifies
|
||||
how formatting should behave. Running `clang-format -style=file` in the
|
||||
project's root will read the file and behave as expected. For ease of use, you
|
||||
should integrate formatting with your favourite editor.
|
||||
|
||||
The code formatting isn't enforced, because sometimes manual formatting may
|
||||
produce better results. Though, running `clang-format` is strongly encouraged.
|
||||
|
||||
## Documentation
|
||||
|
||||
Besides following the comment guidelines from [Google Style
|
||||
Guide](https://google.github.io/styleguide/cppguide.html#Comments), your
|
||||
documentation of the public API should be
|
||||
[Doxygen](https://github.com/doxygen/doxygen) compatible. For private parts of
|
||||
the code or for comments accompanying the implementation, you are free to
|
||||
break doxygen compatibility. In both cases, you should write your
|
||||
documentation as full sentences, correctly written in English.
|
||||
|
||||
## Doxygen
|
||||
|
||||
To start a Doxygen compatible documentation string, you should open your
|
||||
comment with either a JavaDoc style block comment (`/**`) or a line comment
|
||||
containing 3 slashes (`///`). Take a look at the 2 examples below.
|
||||
|
||||
### Block Comment
|
||||
|
||||
```cpp
|
||||
/**
|
||||
* One sentence, brief description.
|
||||
*
|
||||
* Long form description.
|
||||
*/
|
||||
```
|
||||
|
||||
### Line Comment
|
||||
|
||||
```cpp
|
||||
/// One sentence, brief description.
|
||||
///
|
||||
/// Long form description.
|
||||
```
|
||||
|
||||
If you only have a brief description, you may collapse the documentation into
|
||||
a single line.
|
||||
|
||||
### Block Comment
|
||||
|
||||
```cpp
|
||||
/** Brief description. */
|
||||
```
|
||||
|
||||
### Line Comment
|
||||
|
||||
```cpp
|
||||
/// Brief description.
|
||||
```
|
||||
|
||||
Whichever style you choose, keep it consistent across the whole file.
|
||||
|
||||
Doxygen supports various commands in comments, such as `@file` and `@param`.
|
||||
These help Doxygen to render specified things differently or to track them for
|
||||
cross referencing. If you want to learn more, take a look at these two links:
|
||||
|
||||
* http://www.stack.nl/~dimitri/doxygen/manual/docblocks.html
|
||||
* http://www.stack.nl/~dimitri/doxygen/manual/commands.html
|
||||
|
||||
## Examples
|
||||
|
||||
Below are a few examples of documentation from the codebase.
|
||||
|
||||
### Function
|
||||
|
||||
```cpp
|
||||
/**
|
||||
* Removes whitespace characters from the start and from the end of a string.
|
||||
*
|
||||
* @param s String that is going to be trimmed.
|
||||
*
|
||||
* @return Trimmed string.
|
||||
*/
|
||||
inline std::string Trim(const std::string &s);
|
||||
```
|
||||
|
||||
### Class
|
||||
|
||||
```cpp
|
||||
/** Base class for logical operators.
|
||||
*
|
||||
* Each operator describes an operation, which is to be performed on the
|
||||
* database. Operators are iterated over using a @c Cursor. Various operators
|
||||
* can serve as inputs to others and thus a sequence of operations is formed.
|
||||
*/
|
||||
class LogicalOperator
|
||||
: public ::utils::Visitable<HierarchicalLogicalOperatorVisitor> {
|
||||
public:
|
||||
/** Constructs a @c Cursor which is used to run this operator.
|
||||
*
|
||||
* @param GraphDbAccessor Used to perform operations on the database.
|
||||
*/
|
||||
virtual std::unique_ptr<Cursor> MakeCursor(GraphDbAccessor &db) const = 0;
|
||||
|
||||
/** Return @c Symbol vector where the results will be stored.
|
||||
*
|
||||
* Currently, outputs symbols are only generated in @c Produce operator.
|
||||
* @c Skip, @c Limit and @c OrderBy propagate the symbols from @c Produce (if
|
||||
* it exists as input operator). In the future, we may want this method to
|
||||
* return the symbols that will be set in this operator.
|
||||
*
|
||||
* @param SymbolTable used to find symbols for expressions.
|
||||
* @return std::vector<Symbol> used for results.
|
||||
*/
|
||||
virtual std::vector<Symbol> OutputSymbols(const SymbolTable &) const {
|
||||
return std::vector<Symbol>();
|
||||
}
|
||||
|
||||
virtual ~LogicalOperator() {}
|
||||
};
|
||||
```
|
||||
|
||||
### File Header
|
||||
|
||||
```cpp
|
||||
/// @file visitor.hpp
|
||||
///
|
||||
/// This file contains the generic implementation of visitor pattern.
|
||||
///
|
||||
/// There are 2 approaches to the pattern:
|
||||
///
|
||||
/// * classic visitor pattern using @c Accept and @c Visit methods, and
|
||||
/// * hierarchical visitor which also uses @c PreVisit and @c PostVisit
|
||||
/// methods.
|
||||
///
|
||||
/// Classic Visitor
|
||||
/// ===============
|
||||
///
|
||||
/// Explanation on the classic visitor pattern can be found from many
|
||||
/// sources, but here is the link to hopefully most easily accessible
|
||||
/// information: https://en.wikipedia.org/wiki/Visitor_pattern
|
||||
///
|
||||
/// The idea behind the generic implementation of classic visitor pattern is to
|
||||
/// allow returning any type via @c Accept and @c Visit methods. Traversing the
|
||||
/// class hierarchy is relegated to the visitor classes. Therefore, visitor
|
||||
/// should call @c Accept on children when visiting their parents. To implement
|
||||
/// such a visitor refer to @c Visitor and @c Visitable classes.
|
||||
///
|
||||
/// Hierarchical Visitor
|
||||
/// ====================
|
||||
///
|
||||
/// Unlike the classic visitor, the intent of this design is to allow the
|
||||
/// visited structure itself to control the traversal. This way the internal
|
||||
/// children structure of classes can remain private. On the other hand,
|
||||
/// visitors may want to differentiate visiting composite types from leaf types.
|
||||
/// Composite types are those which contain visitable children, unlike the leaf
|
||||
/// nodes. Differentiation is accomplished by providing @c PreVisit and @c
|
||||
/// PostVisit methods, which should be called inside @c Accept of composite
|
||||
/// types. Regular @c Visit is only called inside @c Accept of leaf types.
|
||||
/// To implement such a visitor refer to @c CompositeVisitor, @c LeafVisitor and
|
||||
/// @c Visitable classes.
|
||||
///
|
||||
/// Implementation of hierarchical visiting is modelled after:
|
||||
/// http://wiki.c2.com/?HierarchicalVisitorPattern
|
||||
```
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# Snapshots
|
||||
|
||||
A "snapshot" is a record of the current database state stored in permanent
|
||||
storage. Note that the term "snapshot" is used also in the context of
|
||||
the transaction engine to denote a set of running transactions.
|
||||
|
||||
A snapshot is written to the file by Memgraph periodically if so
|
||||
configured. The snapshot creation process is done within a transaction created
|
||||
specifically for that purpose. The transaction is needed to ensure that
|
||||
the stored state is internally consistent.
|
||||
|
||||
The database state can be recovered from the snapshot during startup, if
|
||||
so configured. This recovery works in conjunction with write-ahead log
|
||||
recovery.
|
||||
|
||||
A single snapshot contains all the data needed to recover a database. In
|
||||
that sense snapshots are independent of each other and old snapshots can
|
||||
be deleted once the new ones are safely stored, if it is not necessary
|
||||
to revert the database to some older state.
|
||||
|
||||
The exact format of the snapshot file is defined inline in the snapshot
|
||||
creation code.
|
||||
@@ -1,55 +0,0 @@
|
||||
# Write-ahead logging
|
||||
|
||||
Typically WAL denotes the process of writing a "log" of database
|
||||
operations (state changes) to persistent storage before committing the
|
||||
transaction, thus ensuring that the state can be recovered (in the case
|
||||
of a crash) for all the transactions which the database committed.
|
||||
|
||||
The WAL is a fine-grained durability format. It's purpose is to store
|
||||
database changes fast. It's primary purpose is not to provide
|
||||
space-efficient storage, nor to support fast recovery. For that reason
|
||||
it's often used in combination with a different persistence mechanism
|
||||
(in Memgraph's case the "snapshot") that has complementary
|
||||
characteristics.
|
||||
|
||||
### Guarantees
|
||||
|
||||
Ensuring that the log is written before the transaction is committed can
|
||||
slow down the database. For that reason this guarantee is most often
|
||||
configurable in databases.
|
||||
|
||||
Memgraph offers two options for the WAL. The default option, where the WAL is
|
||||
flushed to the disk periodically and transactions do not wait for this to
|
||||
complete, introduces the risk of database inconsistency because an operating
|
||||
system or hardware crash might lead to missing transactions in the WAL. Memgraph
|
||||
will handle this as if those transactions never happened. The second option,
|
||||
called synchronous commit, will instruct Memgraph to wait for the WAL to be
|
||||
flushed to the disk when a transactions completes and the transaction will wait
|
||||
for this to complete. This option can be turned on with the
|
||||
`--synchronous-commit` command line flag.
|
||||
|
||||
### Format
|
||||
|
||||
The WAL file contains a series of DB state changes called `StateDelta`s.
|
||||
Each of them describes what the state change is and in which transaction
|
||||
it happened. Also some kinds of meta-information needed to ensure proper
|
||||
state recovery are recorded (transaction beginnings and commits/abort).
|
||||
|
||||
The following is guaranteed w.r.t. `StateDelta` ordering in
|
||||
a single WAL file:
|
||||
- For two ops in the same transaction, if op A happened before B in the
|
||||
database, that ordering is preserved in the log.
|
||||
- Transaction begin/commit/abort messages also appear in exactly the
|
||||
same order as they were executed in the transactional engine.
|
||||
|
||||
### Recovery
|
||||
|
||||
The database can recover from the WAL on startup. This works in
|
||||
conjunction with snapshot recovery. The database attempts to recover from
|
||||
the latest snapshot and then apply as much as possible from the WAL
|
||||
files. Only those transactions that were not recovered from the snapshot
|
||||
are recovered from the WAL, for speed efficiency. It is possible (but
|
||||
inefficient) to recover the database from WAL only, provided all the WAL
|
||||
files created from DB start are available. It is not possible to recover
|
||||
partial database state (i.e. from some suffix of WAL files, without the
|
||||
preceding snapshot).
|
||||
1349
docs/dev/lcp.md
1349
docs/dev/lcp.md
File diff suppressed because it is too large
Load Diff
@@ -1,15 +0,0 @@
|
||||
# Other Code Conventions
|
||||
|
||||
While we are mainly programming in C++, we do use other programming languages
|
||||
when appropriate. This chapter describes conventions for such code.
|
||||
|
||||
## Python
|
||||
|
||||
Code written in Python should adhere to
|
||||
[PEP 8](https://www.python.org/dev/peps/pep-0008/). You should run `flake8` on
|
||||
your code to automatically check compliance.
|
||||
|
||||
## Common Lisp
|
||||
|
||||
Code written in Common Lisp should adhere to
|
||||
[Google Common Lisp Style](https://google.github.io/styleguide/lispguide.xml).
|
||||
1
docs/dev/query/.gitignore
vendored
1
docs/dev/query/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
html/
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
mkdir -p $script_dir/html
|
||||
|
||||
for markdown_file in $(find $script_dir -name '*.md'); do
|
||||
name=$(basename -s .md $markdown_file)
|
||||
sed -e 's/.md/.html/' $markdown_file | \
|
||||
pandoc -s -f markdown -t html -o $script_dir/html/$name.html
|
||||
done
|
||||
|
||||
for dot_file in $(find $script_dir -name '*.dot'); do
|
||||
name=$(basename -s .dot $dot_file)
|
||||
dot -Tpng $dot_file -o $script_dir/html/$name.png
|
||||
done
|
||||
@@ -1,34 +0,0 @@
|
||||
# Query Parsing, Planning and Execution
|
||||
|
||||
This part of the documentation deals with query execution.
|
||||
|
||||
Memgraph currently supports only query interpretation. Each new query is
|
||||
parsed, analysed and translated into a sequence of operations which are then
|
||||
executed on the main database storage. Query execution is organized into the
|
||||
following phases:
|
||||
|
||||
1. [Lexical Analysis (Tokenization)](parsing.md)
|
||||
2. [Syntactic Analysis (Parsing)](parsing.md)
|
||||
3. [Semantic Analysis and Symbol Generation](semantic.md)
|
||||
4. [Logical Planning](planning.md)
|
||||
5. [Logical Plan Execution](execution.md)
|
||||
|
||||
The main entry point is `Interpreter::operator()`, which takes a query text
|
||||
string and produces a `Results` object. To instantiate the object,
|
||||
`Interpreter` needs to perform the above steps from 1 to 4. If any of the
|
||||
steps fail, a `QueryException` is thrown. The complete `LogicalPlan` is
|
||||
wrapped into a `CachedPlan` and stored for reuse. This way we can skip the
|
||||
whole process of analysing a query if it appears to be the same as before.
|
||||
|
||||
When we have valid plan, the client code can invoke `Results::PullAll` with a
|
||||
stream object. The `Results` instance will then execute the plan and fill the
|
||||
stream with the obtained results.
|
||||
|
||||
Since we want to optionally run Memgraph as a distributed database, we have
|
||||
hooks for creating a different plan of logical operators.
|
||||
`DistributedInterpreter` inherits from `Interpreter` and overrides
|
||||
`MakeLogicalPlan` method. This method needs to return a concrete instance of
|
||||
`LogicalPlan`, and in case of distributed database that will be
|
||||
`DistributedLogicalPlan`.
|
||||
|
||||

|
||||
@@ -1,373 +0,0 @@
|
||||
# Logical Plan Execution
|
||||
|
||||
We implement classical iterator style operators. Logical operators define
|
||||
operations on database. They encapsulate the following info: what the input is
|
||||
(another `LogicalOperator`), what to do with the data, and how to do it.
|
||||
|
||||
Currently logical operators can have zero or more input operations, and thus a
|
||||
`LogicalOperator` tree is formed. Most `LogicalOperator` types have only one
|
||||
input, so we are mostly working with chains instead of full fledged trees.
|
||||
You can find information on each operator in `src/query/plan/operator.lcp`.
|
||||
|
||||
## Cursor
|
||||
|
||||
Logical operators do not perform database work themselves. Instead they create
|
||||
`Cursor` objects that do the actual work, based on the info in the operator.
|
||||
Cursors expose a `Pull` method that gets called by the cursor's consumer. The
|
||||
consumer keeps pulling as long as the `Pull` returns `true` (indicating it
|
||||
successfully performed some work and might be eligible for another `Pull`).
|
||||
Most cursors will call the `Pull` function of their input provided cursor, so
|
||||
typically a cursor chain is created that is analogue to the logical operator
|
||||
chain it's created from.
|
||||
|
||||
## Frame
|
||||
|
||||
The `Frame` object contains all the data of the current `Pull` chain. It
|
||||
serves for communicating data between cursors.
|
||||
|
||||
For example, in a `MATCH (n) RETURN n` query the `ScanAllCursor` places a
|
||||
vertex on the `Frame` for each `Pull`. It places it on the place reserved for
|
||||
the `n` symbol. Then the `ProduceCursor` can take that same value from the
|
||||
`Frame` because it knows the appropriate symbol. `Frame` positions are indexed
|
||||
by `Symbol` objects.
|
||||
|
||||
## ExpressionEvaluator
|
||||
|
||||
Expressions results are not placed on the `Frame` since they do not need to be
|
||||
communicated between different `Cursors`. Instead, expressions are evaluated
|
||||
using an instance of `ExpressionEvaluator`. Since generally speaking an
|
||||
expression can be defined by a tree of subexpressions, the
|
||||
`ExpressionEvaluator` is implemented as a tree visitor. There is a performance
|
||||
sub-optimality here because a stack is used to communicate intermediary
|
||||
expression results between elements of the tree. This is one of the reasons
|
||||
why it's planned to use `Frame` for intermediary expression results as well.
|
||||
The other reason is that it might facilitate compilation later on.
|
||||
|
||||
## Cypher Execution Semantics
|
||||
|
||||
Cypher query execution has *mostly* well-defined semantics. Some are
|
||||
explicitly defined by openCypher and its TCK, while others are implicitly
|
||||
defined by Neo4j's implementation of Cypher that we want to be generally
|
||||
compatible with.
|
||||
|
||||
These semantics can in short be described as follows: a Cypher query consists
|
||||
of multiple clauses some of which modify it. Generally, every clause in the
|
||||
query, when reading it left to right, operates on a consistent state of the
|
||||
property graph, untouched by subsequent clauses. This means that a `MATCH`
|
||||
clause in the beginning operates on a graph-state in which modifications by
|
||||
the subsequent `SET` are not visible.
|
||||
|
||||
The stated semantics feel very natural to the end-user, and Neo seems to
|
||||
implement them well. For Memgraph the situation is complex because
|
||||
`LogicalOperator` execution (through a `Cursor`) happens one `Pull` at a time
|
||||
(generally meaning all the query clauses get executed for every top-level
|
||||
`Pull`). This is not inherently consistent with Cypher semantics because a
|
||||
`SET` clause can modify data, and the `MATCH` clause that precedes it might
|
||||
see the modification in a subsequent `Pull`. Also, the `RETURN` clause might
|
||||
want to stream results to the user before all `SET` clauses have been
|
||||
executed, so the user might see some intermediate graph state. There are many
|
||||
edge-cases that Memgraph does its best to avoid to stay true to Cypher
|
||||
semantics, while at the same time using a high-performance streaming approach.
|
||||
The edge-cases are enumerated in this document along with the implementation
|
||||
details they imply.
|
||||
|
||||
## Implementation Peculiarities
|
||||
|
||||
### Once
|
||||
|
||||
An operator that does nothing but whose `Cursor::Pull` returns `true` on the
|
||||
first `Pull` and `false` on subsequent ones. This operator is used when
|
||||
another operator has an optional input, because in Cypher a clause will
|
||||
typically execute once for every input from the preceding clauses, or just
|
||||
once if there was no preceding input. For example, consider the `CREATE`
|
||||
clause. In the query `CREATE (n)` only one node is created, while in the query
|
||||
`MATCH (n) CREATE (m)` a node is created for each existing node. Thus in our
|
||||
`CreateNode` logical operator the input is either a `ScanAll` operator, or a
|
||||
`Once` operator.
|
||||
|
||||
### storage::View
|
||||
|
||||
In the previous section, [Cypher Execution
|
||||
Semantics](#cypher-execution-semantics), we mentioned how the preceding
|
||||
clauses should not see changes made in subsequent ones. For that reason, some
|
||||
operators take a `storage::View` enum value. This value determines which state of
|
||||
the graph an operator sees.
|
||||
|
||||
Consider the query `MATCH (n)--(m) WHERE n.x = 0 SET m.x = 1`. Naive streaming
|
||||
could match a vertex `n` on the given criteria, expand to `m`, update it's
|
||||
property, and in the next iteration consider the vertex previously matched to
|
||||
`m` and skip it because it's newly set property value does not qualify. This
|
||||
is not how Cypher works. To handle this issue properly, Memgraph designed the
|
||||
`VertexAccessor` class that tracks two versions of data: one that was visible
|
||||
before the current transaction+command, and the optional other that was
|
||||
created in the current transaction+command. The `MATCH` clause will be planned
|
||||
as `ScanAll` and `Expand` operations using `storage::View::OLD` value. This
|
||||
will ensure modifications performed in the same query do not affect it. The
|
||||
same applies to edges and the `EdgeAccessor` class.
|
||||
|
||||
### Existing Record Detection
|
||||
|
||||
It's possible that a pattern element has already been declared in the same
|
||||
pattern, or a preceding pattern. For example `MATCH (n)--(m), (n)--(l)` or a
|
||||
cycle-detection match `MATCH (n)-->(n) RETURN n`. Implementation-wise,
|
||||
existing record detection just checks that the expanded record is equal to the
|
||||
one already on the frame.
|
||||
|
||||
### Why Not Use Separate Expansion Ops for Edges and Vertices?
|
||||
|
||||
Expanding an edge and a vertex in separate ops is not feasible when matching a
|
||||
cycle in bi-directional expansions. Consider the query `MATCH (n)--(n) RETURN
|
||||
n`. Let's try to expand first the edge in one op, and vertex in the next. The
|
||||
vertex expansion consumes the edge expansion input. It takes the expanded edge
|
||||
from the frame. It needs to detect a cycle by comparing the vertex existing on
|
||||
the frame with one of the edge vertices (`from` or `to`). But which one? It
|
||||
doesn't know, and can't ensure correct cycle detection.
|
||||
|
||||
### Data Visibility During and After SET
|
||||
|
||||
In Cypher, setting values always works on the latest version of data (from
|
||||
preceding or current clause). That means that within a `SET` clause all the
|
||||
changes from previous clauses must be visible, as well as changes done by the
|
||||
current `SET` clause. Also, if there is a clause after `SET` it must see *all*
|
||||
the changes performed by the preceding `SET`. Both these things are best
|
||||
illustrated with the following queries executed on an empty database:
|
||||
|
||||
CREATE (n:A {x:0})-[:EdgeType]->(m:B {x:0})
|
||||
MATCH (n)--(m) SET m.x = n.x + 1 RETURN labels(n), n.x, labels(m), m.x
|
||||
|
||||
This returns:
|
||||
|
||||
+---------+---+---------+---+
|
||||
|labels(n)|n.x|labels(m)|m.x|
|
||||
+:=======:+:=:+:=======:+:=:+
|
||||
|[A] |2 |[B] |1 |
|
||||
+---------+---+---------+---+
|
||||
|[B] |1 |[A] |2 |
|
||||
+---------+---+---------+---+
|
||||
|
||||
The obtained result implies the following operations:
|
||||
|
||||
1. In the first iteration set the value of the `B.x` to 1
|
||||
2. In the second iteration the we observe `B.x` with the value of 1 and set
|
||||
`A.x` to 2
|
||||
3. In `RETURN` we see all the changes made in both iterations
|
||||
|
||||
To implement the desired behavior Memgraph utilizes two techniques. First is
|
||||
the already mentioned tracking of two versions of data in vertex accessors.
|
||||
Using this approach ensures that the second iteration in the example query
|
||||
sees the data modification performed by the preceding iteration. The second
|
||||
technique is the `Accumulate` operation that accumulates all the iterations
|
||||
from the preceding logical op before passing them to the next logical op. In
|
||||
the example query, `Accumulate` ensures that the results returned to the user
|
||||
reflect changes performed in all iterations of the query (naive streaming
|
||||
could stream results at the end of first iteration producing inconsistent
|
||||
results). Note that `Accumulate` is demanding regarding memory and slows down
|
||||
query execution. For that reason it should be used only when necessary, for
|
||||
example it does not have to be used in a query that has `MATCH` and `SET` but
|
||||
no `RETURN`.
|
||||
|
||||
### Neo4j Inconsistency on Multiple SET Clauses
|
||||
|
||||
Considering the preceding example it could be expected that when a query has
|
||||
multiple `SET` clauses all the changes from those preceding one are visible.
|
||||
This is not the case in Neo4j's implementation. Consider the following queries
|
||||
executed on an empty database:
|
||||
|
||||
CREATE (n:A {x:0})-[:EdgeType]->(m:B {x:0})
|
||||
MATCH (n)--(m) SET n.x = n.x + 1 SET m.x = m.x * 2
|
||||
RETURN labels(n), n.x, labels(m), m.x
|
||||
|
||||
This returns:
|
||||
|
||||
+---------+---+---------+---+
|
||||
|labels(n)|n.x|labels(m)|m.x|
|
||||
+:=======:+:=:+:=======:+:=:+
|
||||
|[A] |2 |[B] |1 |
|
||||
+---------+---+---------+---+
|
||||
|[B] |1 |[A] |2 |
|
||||
+---------+---+---------+---+
|
||||
|
||||
If all the iterations of the first `SET` clause were executed before executing
|
||||
the second, all the resulting values would be 2. This not being the case, we
|
||||
conclude that Neo4j does not use a barrier-like mechanism between `SET`
|
||||
clauses. It is Memgraph's current vision that this is inconsistent and we
|
||||
plan to reduce Neo4j compliance in favour of operation consistency.
|
||||
|
||||
### Double Deletion
|
||||
|
||||
It's possible to match the same graph element multiple times in a single query
|
||||
and delete it. Neo supports this, and so do we. The relevant implementation
|
||||
detail is in the `GraphDbAccessor` class, where the record deletion functions
|
||||
reside, and not in the logical plan execution. It comes down to checking if a
|
||||
record has already been deleted in the current transaction+command and not
|
||||
attempting to do it again (results in a crash).
|
||||
|
||||
### Set + Delete Edge-case
|
||||
|
||||
It's legal for a query to combine `SET` and `DELETE` clauses. Consider the
|
||||
following queries executed on an empty database:
|
||||
|
||||
|
||||
CREATE ()-[:T]->()
|
||||
MATCH (n)--(m) SET n.x = 42 DETACH DELETE m
|
||||
|
||||
Due to the `MATCH` being undirected the second pull will attempt to set data
|
||||
on a deleted vertex. This is not a legal operation in Memgraph storage
|
||||
implementation. For that reason the logical operator for `SET` must check if
|
||||
the record it's trying to set something on has been deleted by the current
|
||||
transaction+command. If so, the modification is not executed.
|
||||
|
||||
### Deletion Accumulation
|
||||
|
||||
Sometimes it's necessary to accumulate deletions of all the matches before
|
||||
attempting to execute them. Consider this the following. Start with an empty
|
||||
database and execute queries:
|
||||
|
||||
CREATE ()-[:T]->()-[:T]->()
|
||||
MATCH (a)-[r1]-(b)-[r2]-(c) DELETE r1, b, c
|
||||
|
||||
Note that the `DELETE` clause attempts to delete node `c`, but it does not
|
||||
detach it by deleting edge `r2`. However, due to undirected edge in the
|
||||
`MATCH`, both edges get pulled and deleted.
|
||||
|
||||
Currently Memgraph does not support this behavior, Neo does. There are a few
|
||||
ways that we could do this.
|
||||
|
||||
* Accumulate on deletion (that sucks because we have to keep track of
|
||||
everything that gets returned after the deletion).
|
||||
* Maybe we could stream through the deletion op, but defer actual deletion
|
||||
until plan-execution end.
|
||||
* Ignore this because it's very edgy (this is the currently selected option).
|
||||
|
||||
### Aggregation Without Input
|
||||
|
||||
It is necessary to define what aggregation ops return when they receive no
|
||||
input. Following is a table that shows what Neo4j's Cypher implementation and
|
||||
SQL produce.
|
||||
|
||||
|
||||
+-------------+------------------------+---------------------+---------------------+------------------+
|
||||
| \<OP\> | 1. Cypher, no group-by | 2. Cypher, group-by | 3. SQL, no group-by | 4. SQL, group-by |
|
||||
+=============+:======================:+:===================:+:===================:+:================:+
|
||||
| Count(\*) | 0 | \<NO\_ROWS> | 0 | \<NO\_ROWS> |
|
||||
+-------------+------------------------+---------------------+---------------------+------------------+
|
||||
| Count(prop) | 0 | \<NO\_ROWS> | 0 | \<NO\_ROWS> |
|
||||
+-------------+------------------------+---------------------+---------------------+------------------+
|
||||
| Sum | 0 | \<NO\_ROWS> | NULL | \<NO\_ROWS> |
|
||||
+-------------+------------------------+---------------------+---------------------+------------------+
|
||||
| Avg | NULL | \<NO\_ROWS> | NULL | \<NO\_ROWS> |
|
||||
+-------------+------------------------+---------------------+---------------------+------------------+
|
||||
| Min | NULL | \<NO\_ROWS> | NULL | \<NO\_ROWS> |
|
||||
+-------------+------------------------+---------------------+---------------------+------------------+
|
||||
| Max | NULL | \<NO\_ROWS> | NULL | \<NO\_ROWS> |
|
||||
+-------------+------------------------+---------------------+---------------------+------------------+
|
||||
| Collect | [] | \<NO\_ROWS> | N/A | N/A |
|
||||
+-------------+------------------------+---------------------+---------------------+------------------+
|
||||
|
||||
Where:
|
||||
|
||||
1. `MATCH (n) RETURN <OP>(n.prop)`
|
||||
2. `MATCH (n) RETURN <OP>(n.prop), (n.prop2)`
|
||||
3. `SELECT <OP>(prop) FROM Table`
|
||||
4. `SELECT <OP>(prop), prop2 FROM Table GROUP BY prop2`
|
||||
|
||||
Neo's Cypher implementation diverges from SQL only when performing `SUM`.
|
||||
Memgraph implements SQL-like behavior. It is considered that `SUM` of
|
||||
arbitrary elements should not be implicitly 0, especially in a property graph
|
||||
without a strict schema (the property in question can contain values of
|
||||
arbitrary types, or no values at all).
|
||||
|
||||
### OrderBy
|
||||
|
||||
The `OrderBy` logical operator sorts the results in the desired order. It
|
||||
occurs in Cypher as part of a `WITH` or `RETURN` clause. Both the concept and
|
||||
the implementation are straightforward. It's necessary for the logical op to
|
||||
`Pull` everything from its input so it can be sorted. It's not necessary to
|
||||
keep the whole `Frame` state of each input, it is sufficient to keep a list of
|
||||
`TypedValues` on which the results will be sorted, and another list of values
|
||||
that need to be remembered and recreated on the `Frame` when yielding.
|
||||
|
||||
The sorting itself is made to reflect that of Neo's implementation which comes
|
||||
down to these points.
|
||||
|
||||
* `Null` comes last (as if it's greater than anything).
|
||||
* Primitive types compare naturally, with no implicit casting except from
|
||||
`int` to `double`.
|
||||
* Complex types are not comparable.
|
||||
* Every unsupported comparison results in an exception that gets propagated
|
||||
to the end user.
|
||||
|
||||
### Limit in Write Queries
|
||||
|
||||
`Limit` can be used as part of a write query, in which case it will *not*
|
||||
reduce the amount of performed updates. For example, consider a database that
|
||||
has 10 vertices. The query `MATCH (n) SET n.x = 1 RETURN n LIMIT 3` will
|
||||
result in all vertices having their property value changed, while returning
|
||||
only the first to the client. This makes sense from the implementation
|
||||
standpoint, because `Accumulate` is planned after `SetProperty` but before
|
||||
`Produce` and `Limit` operations. Note that this behavior can be
|
||||
non-deterministic in some queries, since it relies on the order of iteration
|
||||
over nodes which is undefined when not explicitly specified.
|
||||
|
||||
### Merge
|
||||
|
||||
`MERGE` in Cypher attempts to match a pattern. If it already exists, it does
|
||||
nothing and subsequent clauses like `RETURN` can use the matched pattern
|
||||
elements. If the pattern can't match to any data, it creates it. For detailed
|
||||
information see Neo4j's [merge
|
||||
documentation.](https://neo4j.com/docs/developer-manual/current/cypher/clauses/merge/)
|
||||
|
||||
An important thing about `MERGE` is visibility of modified data. `MERGE` takes
|
||||
an input (typically a `MATCH`) and has two additional *phases*: the merging
|
||||
part, and the subsequent set parts (`ON MATCH SET` and `ON CREATE SET`).
|
||||
Analysis of Neo4j's behavior indicates that each of these three phases (input,
|
||||
merge, set) does not see changes to the graph state done by subsequent phase.
|
||||
The input phase does not see data created by the merge phase, nor the set
|
||||
phase. This is consistent with what seems like the general Cypher philosophy
|
||||
that query clause effects aren't visible in the preceding clauses.
|
||||
|
||||
We define the `Merge` logical operator as a *routing* operator that uses three
|
||||
logical operator branches.
|
||||
|
||||
1. The input from a preceding clause.
|
||||
|
||||
For example in `MATCH (n), (m) MERGE (n)-[:T]-(m)`. This input is
|
||||
optional because `MERGE` is allowed to be the first clause in a query.
|
||||
|
||||
2. The `merge_match` branch.
|
||||
|
||||
This logical operator branch is `Pull`-ed from until exhausted for each
|
||||
successful `Pull` from the input branch.
|
||||
|
||||
3. The `merge_create` branch.
|
||||
|
||||
This branch is `Pull`ed when the `merge_match` branch does not match
|
||||
anything (no successful `Pull`s) for an input `Pull`. It is `Pull`ed only
|
||||
once in such a situation, since only one creation needs to occur for a
|
||||
failed match.
|
||||
|
||||
The `ON MATCH SET` and `ON CREATE SET` parts of the `MERGE` clause are
|
||||
included in the `merge_match` and `merge_create` branches respectively. They
|
||||
are placed on the end of their branches so that they execute only when those
|
||||
branches succeed.
|
||||
|
||||
Memgraph strives to be consistent with Neo in its `MERGE` implementation,
|
||||
while at the same time keeping performance as good as possible. Consistency
|
||||
with Neo w.r.t. graph state visibility is not trivial. Documentation for
|
||||
`Expand` and `Set` describe how Memgraph keeps track of both the updated
|
||||
version of an edge/vertex and the old one, as it was before the current
|
||||
transaction+command. This technique is also used in `Merge`. The input
|
||||
phase/branch of `Merge` always looks at the old data. The merge phase needs to
|
||||
see the new data so it doesn't create more data then necessary.
|
||||
|
||||
For example, consider the query.
|
||||
|
||||
MATCH (p:Person) MERGE (c:City {name: p.lives_in})
|
||||
|
||||
This query needs to create a city node only once for each unique `p.lives_in`.
|
||||
Finally the set phase of a `MERGE` clause should not affect the merge phase.
|
||||
To achieve this the `merge_match` branch of the `Merge` operator should see
|
||||
the latest created nodes, but filter them on their old state (if those nodes
|
||||
were not created by the `create_branch`). Implementation-wise that means that
|
||||
`ScanAll` and `Expand` operators in the `merge_branch` need to look at the new
|
||||
graph state, while `Filter` operators the old, if available.
|
||||
@@ -1,23 +0,0 @@
|
||||
digraph interpreter {
|
||||
node [fontname="dejavusansmono"]
|
||||
edge [fontname="dejavusansmono"]
|
||||
node [shape=record]
|
||||
edge [dir=back,arrowtail=empty,arrowsize=1.5]
|
||||
Interpreter [label="{\N|+ operator(query : string, ...) : Results\l|
|
||||
# MakeLogicalPlan(...) : LogicalPlan\l|
|
||||
- plan_cache_ : Map(QueryHash, CachedPlan)\l}"]
|
||||
Interpreter -> DistributedInterpreter
|
||||
Results [label="{\N|+ PullAll(stream) : void\l|- plan_ : CachedPlan\l}"]
|
||||
Interpreter -> Results
|
||||
[dir=forward,style=dashed,arrowhead=open,label="<<create>>"]
|
||||
CachedPlan -> Results
|
||||
[dir=forward,arrowhead=odiamond,taillabel="1",headlabel="*"]
|
||||
Interpreter -> CachedPlan [arrowtail=diamond,taillabel="1",headlabel="*"]
|
||||
CachedPlan -> LogicalPlan [arrowtail=diamond]
|
||||
LogicalPlan [label="{\N|+ GetRoot() : LogicalOperator
|
||||
\l+ GetCost() : double\l}"]
|
||||
LogicalPlan -> SingleNodeLogicalPlan [style=dashed]
|
||||
LogicalPlan -> DistributedLogicalPlan [style=dashed]
|
||||
DistributedInterpreter -> DistributedLogicalPlan
|
||||
[dir=forward,style=dashed,arrowhead=open,label="<<create>>"]
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
# Lexical and Syntactic Analysis
|
||||
|
||||
## Antlr
|
||||
|
||||
We use Antlr for lexical and syntax analysis of Cypher queries. Antrl uses
|
||||
grammar file `Cypher.g4` downloaded from http://www.opencypher.org to generate
|
||||
the parser and the visitor for the Cypher parse tree. Even though the provided
|
||||
grammar is not very pleasant to work with we decided not to do any drastic
|
||||
changes to it so that our transition to newly published versions of
|
||||
`Cypher.g4` would be easier. Nevertheless, we had to fix some bugs and add
|
||||
features, so our version is not completely the same.
|
||||
|
||||
In addition to using `Cypher.g4`, we have `MemgraphCypher.g4`. This grammar
|
||||
file defines Memgraph specific extensions to the original grammar. Most
|
||||
notable example is the inclusion of syntax for handling authorization. At the
|
||||
moment, some extensions are also found in `Cypher.g4`. For example, the syntax
|
||||
for using a lambda function in relationship patterns. These extensions should
|
||||
be moved out of `Cypher.g4`, so that it remains as close to the original
|
||||
grammar as possible. Additionally, having `MemgraphCypher.g4` may not be
|
||||
enough if we wish to split the functionality for community and enterprise
|
||||
editions of Memgraph.
|
||||
|
||||
## Abstract Syntax Tree (AST)
|
||||
|
||||
Since Antlr generated visitor and the official openCypher grammar are not very
|
||||
practical to use, we translate the Antlr's AST to our own AST. Currently there
|
||||
are ~40 types of nodes in our AST. Their definitions can be found in
|
||||
`src/query/frontend/ast/ast.lcp`.
|
||||
|
||||
Major groups of types can be found under the following base types.
|
||||
|
||||
* `Expression` --- types corresponding to Cypher expressions.
|
||||
* `Clause` --- types corresponding to Cypher clauses.
|
||||
* `PatternAtom` --- node or edge related information.
|
||||
* `Query` --- different kinds of queries, allows extending the language with
|
||||
Memgraph specific query syntax.
|
||||
|
||||
Memory management of created AST nodes is done with `AstStorage`. Each type
|
||||
must be created by invoking `AstStorage::Create` method. This way all of the
|
||||
pointers to nodes and their children are raw pointers. The only owner of
|
||||
allocated memory is the `AstStorage`. When the storage goes out of scope, the
|
||||
pointers become invalid. It may be more natural to handle tree ownership via
|
||||
`unique_ptr`, i.e. each node owns its children. But there are some benefits to
|
||||
having a custom storage and allocation scheme.
|
||||
|
||||
The primary reason we opted for not using `unique_ptr` is the requirement of
|
||||
Antlr's base visitor class that the resulting values must by copyable. The
|
||||
result is wrapped in `antlr::Any` so that the derived visitor classes may
|
||||
return any type they wish when visiting Antlr's AST. Unfortunately,
|
||||
`antlr::Any` does not work with non-copyable types.
|
||||
|
||||
Another benefit of having `AstStorage` is that we can easily add a different
|
||||
allocation scheme for AST nodes. The interface of node creation would not
|
||||
change.
|
||||
|
||||
### AST Translation
|
||||
|
||||
The translation process is done via `CypherMainVisitor` class, which is
|
||||
derived from Antlr generated visitor. Besides instancing our AST types, a
|
||||
minimal number of syntactic checks are done on a query. These checks handle
|
||||
the cases which were valid in original openCypher grammar, but may be invalid
|
||||
when combined with other syntax elements.
|
||||
@@ -1,526 +0,0 @@
|
||||
# Logical Planning
|
||||
|
||||
After the semantic analysis and symbol generation, the AST is converted to a
|
||||
tree of logical operators. This conversion is called *planning* and the tree
|
||||
of logical operators is called a *plan*. The whole planning process is done in
|
||||
the following steps.
|
||||
|
||||
1. [AST Preprocessing](#ast-preprocessing)
|
||||
|
||||
The first step is to preprocess the AST by collecting
|
||||
information on filters, divide the query into parts, normalize patterns
|
||||
in `MATCH` clauses, etc.
|
||||
|
||||
2. [Logical Operator Planning](#logical-operator-planning)
|
||||
|
||||
After the preprocess step, the planning can be done via 2 planners:
|
||||
`VariableStartPlanner` and `RuleBasedPlanner`. The first planner will
|
||||
generate multiple plans where each plan has different starting points for
|
||||
searching the patterns in `MATCH` clauses. The second planner produces a
|
||||
single plan by mapping the query parts as they are to logical operators.
|
||||
|
||||
3. [Logical Plan Postprocessing](#logical-plan-postprocessing)
|
||||
|
||||
In this stage, we perform various transformations on the generated logical
|
||||
plan. Here we want to optimize the operations in order to improve
|
||||
performance during the execution. Naturally, transformations need to
|
||||
preserve the semantic behaviour of the original plan.
|
||||
|
||||
4. [Cost Estimation](#cost-estimation)
|
||||
|
||||
After the generation, the execution cost of each plan is estimated. This
|
||||
estimation is used to select the best plan which will be executed.
|
||||
|
||||
The implementation can be found in the `query/plan` directory, with the public
|
||||
entry point being `query/plan/planner.hpp`.
|
||||
|
||||
## AST Preprocessing
|
||||
|
||||
Each openCypher query consists of at least 1 **single query**. Multiple single
|
||||
queries are chained together using a **query combinator**. Currently, there is
|
||||
only one combinator, `UNION`. The preprocessing step starts in the
|
||||
`CollectQueryParts` function. This function will take a look at each single
|
||||
query and divide it into parts. Each part is separated with `RETURN` and
|
||||
`WITH` clauses. For example:
|
||||
|
||||
MATCH (n) CREATE (m) WITH m MATCH (l)-[]-(m) RETURN l
|
||||
| | |
|
||||
|------- part 1 -----------+-------- part 2 --------|
|
||||
| |
|
||||
|-------------------- single query -----------------|
|
||||
|
||||
Each part is created by collecting all `MATCH` clauses and *normalizing* their
|
||||
patterns. Pattern normalization is the process of converting an arbitrarily
|
||||
long pattern chain of nodes and edges into a list of triplets `(start node,
|
||||
edge, end node)`. The triplets should preserve the semantics of the match. For
|
||||
example:
|
||||
|
||||
MATCH (a)-[p]-(b)-[q]-(c)-[r]-(d)
|
||||
|
||||
is equivalent to:
|
||||
|
||||
MATCH (a)-[p]-(b), (b)-[q]-(c), (c)-[r]-(d)
|
||||
|
||||
With this representation, it becomes easier to reorder the triplets and choose
|
||||
different strategies for pattern matching.
|
||||
|
||||
In addition to normalizing patterns, all of the filter expressions in patterns
|
||||
and inside of the `WHERE` clause (of the accompanying `MATCH`) are extracted
|
||||
and stored separately. During the extraction, symbols used in the filter
|
||||
expression are collected. This allows for planning filters in a valid order,
|
||||
as the matching for triplets is being done. Another important benefit of
|
||||
having extra information on filters, is to recognize when a database index
|
||||
could be used.
|
||||
|
||||
After each `MATCH` is processed, they are all grouped, so that even the whole
|
||||
`MATCH` clauses may be reordered. The important thing is to remember which
|
||||
symbols were used to name edges in each `MATCH`. With those symbols we can
|
||||
plan for *cyphermorphism*, i.e. ensure different edges in the search pattern
|
||||
of a single `MATCH` map to different edges in the graph. This preserves the
|
||||
semantic of the query, even though we may have reordered the matching. The
|
||||
same steps are done for `OPTIONAL MATCH`.
|
||||
|
||||
Another clause which needs processing is `MERGE`. Here we normalize the
|
||||
pattern, since the `MERGE` is a bit like `MATCH` and `CREATE` in one.
|
||||
|
||||
All the other clauses are left as is.
|
||||
|
||||
In the end, each query part consists of:
|
||||
|
||||
* processed and grouped `MATCH` clauses;
|
||||
* processed and grouped `OPTIONAL MATCH` clauses;
|
||||
* processed `MERGE` matching pattern and
|
||||
* unchanged remaining clauses.
|
||||
|
||||
The last stored clause is guaranteed to be either `WITH` or `RETURN`.
|
||||
|
||||
## Logical Operator Planning
|
||||
|
||||
### Variable Start Planner
|
||||
|
||||
The `VariableStartPlanner` generates multiple plans for a single query. Each
|
||||
plan is generated by selecting a different starting point for pattern
|
||||
matching.
|
||||
|
||||
The algorithm works as follows.
|
||||
|
||||
1. For each query part:
|
||||
1. For each node in triplets of collected `MATCH` clauses:
|
||||
i. Add the node to a set of `expanded` nodes
|
||||
ii. Select a triplet `(start node, edge, end node)` whose `start node` is
|
||||
in the `expanded` set
|
||||
iii. If no triplet was selected, choose a new starting node that isn't in
|
||||
`expanded` and continue expanding
|
||||
iv. Repeat steps ii. -- iii. until all triplets have been selected
|
||||
and store that as a variation of the `MATCH` clauses
|
||||
2. Do step 1.1. for `OPTIONAL MATCH` and `MERGE` clauses
|
||||
3. Take all combinations of the generated `MATCH`, `OPTIONAL MATCH` and
|
||||
`MERGE` and store them as variations of the query part.
|
||||
2. For each combination of query part variations:
|
||||
1. Generate a plan using the rule based planner
|
||||
|
||||
### Rule Based Planner
|
||||
|
||||
The `RuleBasedPlanner` generates a single plan for a single query. A plan is
|
||||
generated by following hardcoded rules for producing logical operators. The
|
||||
following sections are an overview on how each openCypher clause is converted
|
||||
to a `LogicalOperator`.
|
||||
|
||||
#### MATCH
|
||||
|
||||
`MATCH` clause is used to specify which patterns need to be searched for in
|
||||
the database. These patterns are normalized in the preprocess step to be
|
||||
represented as triplets `(start node, edge, end node)`. When there is no edge,
|
||||
then the triplet is reduced only to the `start node`. Generating the operators
|
||||
is done by looping over these triplets.
|
||||
|
||||
##### Searching for Nodes
|
||||
|
||||
The simplest search is finding standalone nodes. For example, `MATCH (n)`
|
||||
will find all the nodes in the graph. This is accomplished by generating a
|
||||
`ScanAll` operator and forwarding the node symbol which should store the
|
||||
results. In this case, all the nodes will be referenced by `n`.
|
||||
|
||||
Multiple nodes can be specified in a single match, e.g. `MATCH (n), (m)`.
|
||||
Planning is done by repeating the same steps for each sub pattern (separated
|
||||
by a comma). In this case, we would get 2 `ScanAll` operators chained one
|
||||
after the other. An optimization can be obtained if the node in the pattern is
|
||||
already searched for. In `MATCH (n), (n)` we can drop the second `ScanAll`
|
||||
operator since we have already generated it for the first node.
|
||||
|
||||
##### Searching for Relationships
|
||||
|
||||
A more advanced search includes finding nodes with relationships. For example,
|
||||
`MATCH (n)-[r]-(m)` should find every pair of connected nodes in the database.
|
||||
This means, that if a single node has multiple connections, it will be
|
||||
repeated for each combination of pairs. The generation of operators starts
|
||||
from the first node in the pattern. If we are referencing a new starting node,
|
||||
we need to generate a `ScanAll` which finds all the nodes and stores them
|
||||
into `n`. Then, we generate an `Expand` operator which reads the `n` and
|
||||
traverses all the edges of that node. The edge is stored into `r`, while the
|
||||
destination node is stored in `m`.
|
||||
|
||||
Matching multiple relationships proceeds similarly, by repeating the same
|
||||
steps. The only difference is that we need to ensure different edges in the
|
||||
search pattern, map to different edges in the graph. This means that after each
|
||||
`Expand` operator, we need to generate an `EdgeUniquenessFilter`. We provide
|
||||
this operator with a list of symbols for the previously matched edges and the
|
||||
symbol for the current edge.
|
||||
|
||||
For example.
|
||||
|
||||
MATCH (n)-[r1]-(m)-[r2]-(l)
|
||||
|
||||
The above is preprocessed into
|
||||
|
||||
MATCH (n)-[r1]-(m), (m)-[r2]-(l)
|
||||
|
||||
Then we look at each triplet in order and perform the described steps. This
|
||||
way, we would generate:
|
||||
|
||||
ScanAll (n) > Expand (n, r1, m) > Expand (m, r2, l) >
|
||||
EdgeUniquenessFilter ([r1], r2)
|
||||
|
||||
Note that we don't need to make `EdgeUniquenessFilter` after the first
|
||||
`Expand`, since there are no edges to compare to. This filtering needs to work
|
||||
across multiple pattern, but inside a *single* `MATCH` clause.
|
||||
|
||||
Let's take a look at the following.
|
||||
|
||||
MATCH (n)-[r1]-(m), (m)-[r2]-(l)
|
||||
|
||||
We would also generate the exact same operators.
|
||||
|
||||
ScanAll (n) > Expand (n, r1, m) > Expand (m, r2, l) >
|
||||
EdgeUniquenessFilter ([r1], r2)
|
||||
|
||||
On the other hand,
|
||||
|
||||
MATCH (n)-[r1]-(m) MATCH (m)-[r2]-(l)-[r3]-(i)
|
||||
|
||||
would reset the uniqueness filtering at the start of the second match. This
|
||||
would mean that we output the following:
|
||||
|
||||
ScanAll (n) > Expand (n, r1, m) > Expand (m, r2, l) > Expand (l, r3, i) >
|
||||
EdgeUniquenessFilter ([r2], r3)
|
||||
|
||||
There is a difference in how we handle edge uniqueness compared to Neo4j.
|
||||
Neo4j does not allow searching for a single edge multiple times, but we've
|
||||
decided to support that.
|
||||
|
||||
For example, the user can say the following.
|
||||
|
||||
MATCH (n)-[r]-(m)-[r]-l
|
||||
|
||||
We would ensure that both `r` variables match to the same edge. In our
|
||||
terminology, we call this the *edge cycle*. For the above example, we would
|
||||
generate this plan.
|
||||
|
||||
ScanAll (n) > Expand (n, r, m) > Expand (m, r, l)
|
||||
|
||||
We do not put an `EdgeUniquenessFilter` operator between 2 `Expand`
|
||||
operators and we tell the 2nd `Expand` that it is an edge cycle. This, 2nd
|
||||
`Expand` will ensure we have matched both the same edges.
|
||||
|
||||
##### Filtering
|
||||
|
||||
To narrow the search down, the patterns in `MATCH` can have filtered labels
|
||||
and properties. A more general filtering is done using the accompanying
|
||||
`WHERE` clause. During the preprocess step, all filters are collected and
|
||||
extracted into expressions. Additional information on which symbols are used
|
||||
is also stored. This way, each time we generate a `ScanAll` or `Expand`, we
|
||||
look at all the filters to see if any of them can be used. I.e. if the symbols
|
||||
they use have been bound by a newly produced operator. If a filter expression
|
||||
can be used, we immediately add a `Filter` operator with that expression.
|
||||
|
||||
For example.
|
||||
|
||||
MATCH (n)-[r]-(m :label) WHERE n.prop = 42
|
||||
|
||||
We would produce:
|
||||
|
||||
ScanAll (n) > Filter (n.prop) > Expand (n, r, m) > Filter (m :label)
|
||||
|
||||
This means that the same plan is generated for the query:
|
||||
|
||||
MATCH (n {prop: 42})-[r]-(m :label)
|
||||
|
||||
#### OPTIONAL
|
||||
|
||||
If a `MATCH` clause is preceded by `OPTIONAL`, then we need to generate a plan
|
||||
such that we produce results even if we fail to match anything. This is
|
||||
accomplished by generating an `Optional` operator, which takes 2 operator
|
||||
trees:
|
||||
|
||||
* input operation and
|
||||
* optional operation.
|
||||
|
||||
The input is the operation we generated for the part of the query before
|
||||
`OPTIONAL MATCH`. For the optional operation, we simply generate the `OPTIONAL
|
||||
MATCH` part just like we would for regular `MATCH`. In addition to operations,
|
||||
we need to send the symbols which are set during optional matching to the
|
||||
`Optional` operator. The operator will reset values of those symbols to
|
||||
`null`, when the optional part fails to match.
|
||||
|
||||
#### RETURN & WITH
|
||||
|
||||
`RETURN` and `WITH` clauses are very similar to each other. The only
|
||||
difference is that `WITH` separates parts of the query and can be paired with
|
||||
`WHERE` clause.
|
||||
|
||||
The common part is generating operators for the body of the clause. Separation
|
||||
of query parts is mostly done in semantic analysis, which checks that only the
|
||||
symbols exposed through `WITH` are visible in the query parts after the
|
||||
clause. The minor part is done in planning.
|
||||
|
||||
##### Named Results
|
||||
|
||||
Both clauses contain multiple named expressions (`expr AS name`) which are
|
||||
used to generate `Produce` operator.
|
||||
|
||||
##### Aggregations
|
||||
|
||||
If an expression contains an aggregation operator (`sum`, `avg`, ...) we need
|
||||
to plan the `Aggregate` operator as input to `Produce`. This case is more
|
||||
complex, because aggregation in openCypher can perform implicit grouping of
|
||||
results used for aggregation.
|
||||
|
||||
For example, `WITH/RETURN sum(n.x) AS s, n.y AS group` will implicitly group
|
||||
by `n.y` expression.
|
||||
|
||||
Another, obscure grouping can be achieved with `RETURN sum(n.a) + n.b AS s`.
|
||||
Here, the `n.b` will be used for grouping, even though both the `sum` and
|
||||
`n.b` are in the same named expression.
|
||||
|
||||
Therefore, we need to collect all expressions which do not contain
|
||||
aggregations and use them for grouping. You may have noticed that in the last
|
||||
example `sum` is actually a sub-expression of `+`. `Aggregate` operator does
|
||||
not see that (nor it should), so the responsibility of evaluating that falls
|
||||
on `Produce`. One way is for `Aggregate` to store results of grouping
|
||||
expressions on the frame in addition to aggregation results. Unfortunately,
|
||||
this would require rewiring named expressions in `Produce` to reference
|
||||
already evaluated expressions. In the current implementation, we opted for
|
||||
`Aggregate` to store only aggregation results on the frame, while `Produce`
|
||||
will re-evaluate all the other (grouping) expressions. To handle that, symbols
|
||||
which are used in expressions are passed to `Aggregate`, so that they can be
|
||||
remembered. `Produce` will read those symbols from the frame and use it to
|
||||
re-evaluate the needed expressions.
|
||||
|
||||
##### Accumulation
|
||||
|
||||
After we have `Produce` and potentially `Aggregate`, we need to handle a
|
||||
special case when the part of the query before `RETURN` or `WITH` performs
|
||||
updates. For that, we want to run that part of the query fully, so that we get
|
||||
the latest results. This is accomplished by adding `Accumulate` operator as
|
||||
input to `Aggregate` or `Produce` (if there is no aggregation). Accumulation
|
||||
will store all the values for all the used symbols inside `RETURN` and `WITH`,
|
||||
so that they can be used in the operator which follows. This way, only parts
|
||||
of the frame are copied, instead of the whole frame. Here is a minor
|
||||
difference between planning `WITH`, compared to `RETURN`. Since `WITH` can
|
||||
separate writing from reading, we need to advance the transaction command.
|
||||
This enables the later, read parts of the query to obtain the newest changes.
|
||||
This is supported by passing `advance_command` flag to `Accumulate` operator.
|
||||
|
||||
In the simplest case, common to both clauses, we have `Accumulate > Aggregate
|
||||
> Produce` operators, where `Accumulate` and `Aggregate` may be left out.
|
||||
|
||||
##### Ordering
|
||||
|
||||
Planning `ORDER BY` is simple enough. Since it may see new symbols (filled in
|
||||
`Produce`), we add the `OrderBy` operator at the end. The operator will change
|
||||
the order of produced results, so we pass it the ordering expressions and the
|
||||
output symbols of named expressions.
|
||||
|
||||
##### Filtering
|
||||
|
||||
A final difference in `WITH`, is when it contains a `WHERE` clause. For that,
|
||||
we simply generate the `Filter` operator, appended after `Produce` or
|
||||
`OrderBy` (depending which operator is last).
|
||||
|
||||
##### Skipping and Limiting
|
||||
|
||||
If we have `SKIP` or `LIMIT`, we generate `Skip` or `Limit` operators,
|
||||
respectively. These operators are put at the end of the clause.
|
||||
|
||||
This placement may have some unexpected behaviour when combined with
|
||||
operations that update the graph. For example.
|
||||
|
||||
MATCH (n) SET n.x = n.x + 1 RETURN n LIMIT 1
|
||||
|
||||
The above query may be interpreted as if the `SET` will be done only once.
|
||||
Since this is a write query, we need to accumulate results, so the part before
|
||||
`RETURN` will execute completely. The accumulated results will be yielded up
|
||||
to the given limit, and the user would get only the first `n` that was
|
||||
updated. This may confuse the user because in reality, every node in the
|
||||
database had been updated.
|
||||
|
||||
Note that `Skip` always comes before `Limit`. In the current implementation,
|
||||
they are generated directly one after the other.
|
||||
|
||||
#### CREATE
|
||||
|
||||
`CREATE` clause is used to create nodes and edges (relationships).
|
||||
|
||||
For multiple `CREATE` clauses or multiple creation patterns in a single
|
||||
clause, we perform the same, following steps.
|
||||
|
||||
##### Creating a Single Node
|
||||
|
||||
A node is created by simply specifying a node pattern.
|
||||
|
||||
For example `CREATE (n :label {property: "value"}), ()` would create 2 nodes.
|
||||
The 1st one would be created with a label and a property. This node could be
|
||||
referenced later in the query, by using the variable `n`. The 2nd node cannot
|
||||
be referenced and it would be created without any labels nor properties. For
|
||||
node creation, we generate a `CreateNode` operator and pass it all the details
|
||||
of node creation: variable symbol, labels and properties. In the mentioned
|
||||
example, we would have `CreateNode > CreateNode`.
|
||||
|
||||
##### Creating a Relationship
|
||||
|
||||
To create a relationship, the `CREATE` clause must contain a pattern with a
|
||||
directed edge. Compared to creating a single node, this case is a bit more
|
||||
complicated, because either side of the edge may not exist. By exist, we mean
|
||||
that the endpoint is a variable which already references a node.
|
||||
|
||||
For example, `MATCH (n) CREATE (n)-[r]->(m)` would create an edge `r` and a
|
||||
node `m` for each matched node `n`. If we focus on the `CREATE` part, we
|
||||
generate `CreateExpand (n, r, m)` where `n` already exists (refers to matched
|
||||
node) and `m` would be newly created along with edge `r`. If we had only
|
||||
`CREATE (n)-[r]->(m)`, then we would need to create both nodes of the edge
|
||||
`r`. This is done by generating `CreateNode (n) > CreateExpand(n, r, m)`. The
|
||||
final case is when both endpoints refer to an existing node. For example, when
|
||||
adding a node with a cyclical connection `CREATE (n)-[r]->(n)`. In this case,
|
||||
we would generate `CreateNode (n) > CreateExpand (n, r, n)`. We would tell
|
||||
`CreateExpand` to only create the edge `r` between the already created `n`.
|
||||
|
||||
#### MERGE
|
||||
|
||||
Although the merge operation is complex, planning turns out to be relatively
|
||||
simple. The pattern inside the `MERGE` clause is used for both matching and
|
||||
creating. Therefore, we create 2 operator trees, one for each action.
|
||||
|
||||
For example.
|
||||
|
||||
MERGE (n)-[r:r]-(m)
|
||||
|
||||
We would generate a single `Merge` operator which has the following.
|
||||
|
||||
* No input operation (since it is not preceded by any other clause).
|
||||
|
||||
* On match operation
|
||||
|
||||
`ScanAll (n) > Expand (n, r, m) > Filter (r)`
|
||||
|
||||
* On create operation
|
||||
|
||||
`CreateNode (n) > CreateExpand (n, r, m)`
|
||||
|
||||
In cases when `MERGE` contains `ON MATCH` and `ON CREATE` parts, we simply
|
||||
append their operations to the respective operator trees.
|
||||
|
||||
Observe the following example.
|
||||
|
||||
MERGE (n)-[r:r]-(m) ON MATCH SET n.x = 42 ON CREATE SET m :label
|
||||
|
||||
The `Merge` would be generated with the following.
|
||||
|
||||
* No input operation (again, since there is no clause preceding it).
|
||||
|
||||
* On match operation
|
||||
|
||||
`ScanAll (n) > Expand (n, r, m) > Filter (r) > SetProperty (n.x, 42)`
|
||||
|
||||
* On create operation
|
||||
|
||||
`CreateNode (n) > CreateExpand (n, r, m) > SetLabels (n, :label)`
|
||||
|
||||
When we have preceding clauses, we simply put their operator as input to
|
||||
`Merge`.
|
||||
|
||||
MATCH (n) MERGE (n)-[r:r]-(m)
|
||||
|
||||
The above would be generated as
|
||||
|
||||
ScanAll (n) > Merge (on_match_operation, on_create_operation)
|
||||
|
||||
Here we need to be careful to recognize which symbols are already declared.
|
||||
But, since the `on_match_operation` uses the same algorithm for generating a
|
||||
`Match`, that problem is handled there. The same should hold for
|
||||
`on_create_operation`, which uses the process of generating a `Create`. So,
|
||||
finally for this example, the `Merge` would have:
|
||||
|
||||
* Input operation
|
||||
|
||||
`ScanAll (n)`
|
||||
|
||||
* On match operation
|
||||
|
||||
`Expand (n, r, m) > Filter (r)`
|
||||
|
||||
Note that `ScanAll` is not needed since we get the nodes from input.
|
||||
|
||||
* On create operation
|
||||
|
||||
`CreateExpand (n, r, m)`
|
||||
|
||||
Note that `CreateNode` is dropped, since we want to expand the existing one.
|
||||
|
||||
## Logical Plan Postprocessing
|
||||
|
||||
Postprocessing of a logical plan is done by rewriting the original plan into
|
||||
a more efficient one while preserving the original semantic of operations.
|
||||
The rewriters are found in `query/plan/rewrite` directory, and currently we
|
||||
only have one -- `IndexLookupRewriter`.
|
||||
|
||||
### IndexLookupRewriter
|
||||
|
||||
The job of this rewriter is to merge `Filter` and `ScanAll` operations into
|
||||
equivalent `ScanAllBy<Index>` operations. In almost all cases using indexed
|
||||
lookup will be faster than regular lookup, so `IndexLookupRewriter` simply
|
||||
does the transformations whenever possible. The simplest case being the
|
||||
following, assuming we have an index over `id`.
|
||||
|
||||
* Original Plan
|
||||
|
||||
`ScanAll (n) > Filter (id(n) == 42) > Produce (n)`
|
||||
|
||||
* Rewritten Plan
|
||||
|
||||
`ScanAllById (n, id=42) > Produce (n)`
|
||||
|
||||
Naturally, there are some cases we need to be careful about.
|
||||
|
||||
1. Operators with Multiple Branches
|
||||
|
||||
Here we may not carry `Filter` operations outside of the operator into
|
||||
its branches, so the branches are rewritten as stand alone plans with a
|
||||
branch new `IndexLookupRewriter`. Some of the operators with multiple
|
||||
branches are `Merge`, `Optional`, `Cartesian` and `Union`.
|
||||
|
||||
2. Expand Operators
|
||||
|
||||
Expand operations aren't that tricky to handle, but they have a special
|
||||
case where we want to use an indexed lookup of the destination so that the
|
||||
expansion is performed between known nodes. This decision may depend on
|
||||
various parameters which may need further tweaking as we encounter more
|
||||
use-cases of Cypher queries.
|
||||
|
||||
## Cost Estimation
|
||||
|
||||
Cost estimation is the final step of processing a logical plan. The
|
||||
implementation can be found in `query/plan/cost_estimator.hpp`. We give each
|
||||
operator a cost based on the estimated cardinality of results of that operator
|
||||
and on the preset coefficient of the runtime performance of that operator.
|
||||
|
||||
This scheme is rather simple and works quite well, but there are couple of
|
||||
improvements we may want to do at some point.
|
||||
|
||||
* Track more information about the stored graph and use that to improve the
|
||||
estimates.
|
||||
* Do a quick, partial run of the plan and tweak the estimation based on how
|
||||
much each operator produced results. This may require us having some kind
|
||||
of representative subset of the stored graph.
|
||||
* Write micro benchmarks for each operator and based on the results create
|
||||
sensible preset coefficients. This would replace the current coefficients
|
||||
which are just assumptions on how each operator implementation performs.
|
||||
@@ -1,134 +0,0 @@
|
||||
# Semantic Analysis and Symbol Generation
|
||||
|
||||
In this phase, various semantic and variable type checks are performed.
|
||||
Additionally, we generate symbols which map AST nodes to stored values
|
||||
computed from evaluated expressions.
|
||||
|
||||
## Symbol Generation
|
||||
|
||||
Implementation can be found in `query/frontend/semantic/symbol_generator.cpp`.
|
||||
|
||||
Symbols are generated for each AST node that represents data that needs to
|
||||
have storage. Currently, these are:
|
||||
|
||||
* `NamedExpression`
|
||||
* `CypherUnion`
|
||||
* `Identifier`
|
||||
* `Aggregation`
|
||||
|
||||
You may notice that the above AST nodes may not correspond to something named
|
||||
by a user. For example, `Aggregation` can be a part of larger expression and
|
||||
thus remain unnamed. The reason we still generate symbols is to have a uniform
|
||||
behaviour when executing a query as well as allow for caching the results of
|
||||
expression evaluation.
|
||||
|
||||
AST nodes do not actually store a `Symbol` instance, instead they have a
|
||||
`int32_t` index identifying the symbol in the `SymbolTable` class. This is
|
||||
done to minimize the size of AST types as well as allow easier sharing of same
|
||||
symbols with multiple instances of AST nodes.
|
||||
|
||||
The storage for evaluated data is represented by the `Frame` class. Each
|
||||
symbol determines a unique position in the frame. During interpretation,
|
||||
evaluation of expressions which have a symbol will either read or store values
|
||||
in the frame. For example, instance of an `Identifier` will use the symbol to
|
||||
find and read the value from `Frame`. On the other hand, `NamedExpression`
|
||||
will take the result of evaluating its own expression and store it in the
|
||||
`Frame`.
|
||||
|
||||
When a symbol is created, context of creation is used to assign a type to that
|
||||
symbol. This type is used for simple type checking operations. For example,
|
||||
`MATCH (n)` will create a symbol for variable `n`. Since the `MATCH (n)`
|
||||
represents finding a vertex in the graph, we can set `Symbol::Type::Vertex`
|
||||
for that symbol. Later, for example in `MATCH ()-[n]-()` we see that variable
|
||||
`n` is used as an edge. Since we already have a symbol for that variable, we
|
||||
detect this type mismatch and raise a `SemanticException`.
|
||||
|
||||
Basic rule of symbol generation, is that variables inside `MATCH`, `CREATE`,
|
||||
`MERGE`, `WITH ... AS` and `RETURN ... AS` clauses establish new symbols.
|
||||
|
||||
### Symbols in Patterns
|
||||
|
||||
Inside `MATCH`, symbols are created only if they didn't exist before. For
|
||||
example, patterns in `MATCH (n {a: 5})--(m {b: 5}) RETURN n, m` will create 2
|
||||
symbols: one for `n` and one for `m`. `RETURN` clause will, in turn, reference
|
||||
those symbols. Symbols established in a part of pattern are immediately bound
|
||||
and visible in later parts. For example, `MATCH (n)--(n)` will create a symbol
|
||||
for variable `n` for 1st `(n)`. That symbol is referenced in 2nd `(n)`. Note
|
||||
that the symbol is not bound inside 1st `(n)` itself. What this means is that,
|
||||
for example, `MATCH (n {a: n.b})` should raise an error, because `n` is not
|
||||
yet bound when encountering `n.b`. On the other hand,
|
||||
`MATCH (n)--(n {a: n.b})` is fine.
|
||||
|
||||
The `CREATE` is similar to `MATCH`, but it *always* establishes symbols for
|
||||
variables which create graph elements. What this means is that, for example
|
||||
`MATCH (n) CREATE (n)` is not allowed. `CREATE` wants to create a new node,
|
||||
for which we already have a symbol. In such a case, we need to throw an error
|
||||
that the variable `n` is being redeclared. On the other hand `MATCH (n) CREATE
|
||||
(n)-[r :r]->(n)` is fine, because `CREATE` will only create the edge `r`,
|
||||
connecting the already existing node `n`. Remaining behaviour is the same as
|
||||
in `MATCH`. This means that we can simplify `CREATE` to be like `MATCH` with 2
|
||||
special cases.
|
||||
|
||||
1. Are we creating a node, i.e. `CREATE (n)`? If yes, then the symbol for
|
||||
`n` must not have been created before. Otherwise, we reference the
|
||||
existing symbol.
|
||||
2. Are we creating an edge, i.e. we encounter a variable for an edge inside
|
||||
`CREATE`? If yes, then that variable must not reference a symbol.
|
||||
|
||||
The `MERGE` clause is treated the same as `CREATE` with regards to symbol
|
||||
generation. The only difference is that we allow bidirectional edges in the
|
||||
pattern. When creating such a pattern, the direction of the created edge is
|
||||
arbitrarily determined.
|
||||
|
||||
### Symbols in WITH and RETURN
|
||||
|
||||
In addition to patterns, new symbols are established in the `WITH` clause.
|
||||
This clause makes the new symbols visible *only* to the rest of the query.
|
||||
For example, `MATCH (old) WITH old AS new RETURN new, old` should raise an
|
||||
error that `old` is unbound inside `RETURN`.
|
||||
|
||||
There is a special case with symbol visibility in `WHERE` and `ORDER BY`. They
|
||||
need to see both the old and the new symbols. Therefore `MATCH (old) RETURN
|
||||
old AS new ORDER BY old.prop` needs to work. On the other hand, if we perform
|
||||
aggregations inside `WITH` or `RETURN`, then the old symbols should not be
|
||||
visible neither in `WHERE` nor in `ORDER BY`. Since the aggregation has to go
|
||||
through all the results in order to generate the final value, it makes no
|
||||
sense to store old symbols and their values. A query like `MATCH (old) WITH
|
||||
SUM(old.prop) AS sum WHERE old.prop = 42 RETURN sum` needs to raise an error
|
||||
that `old` is unbound inside `WHERE`.
|
||||
|
||||
For cases when `SKIP` and `LIMIT` appear, we disallow any identifiers from
|
||||
appearing in their expressions. Basically, `SKIP` and `LIMIT` can only be
|
||||
constant expressions[^1]. For example, `MATCH (old) RETURN old AS new SKIP
|
||||
new.prop` needs to raise that variables are not allowed in `SKIP`. It makes no
|
||||
sense to allow variables, since their values may vary on each iteration. On
|
||||
the other hand, we could support variables to constant expressions, but for
|
||||
simplicity we do not. For example, `MATCH (old) RETURN old, 2 AS limit_var
|
||||
LIMIT limit_var` would still throw an error.
|
||||
|
||||
Finally, we generate symbols for names created in `RETURN` clause. These
|
||||
symbols are used for the final results of a query.
|
||||
|
||||
NOTE: New symbols in `WITH` and `RETURN` should be unique. This means that
|
||||
`WITH a AS same, b AS same` is not allowed, neither is a construct like
|
||||
`RETURN 2, 2`
|
||||
|
||||
### Symbols in Functions which Establish New Scope
|
||||
|
||||
Symbols can also be created in some functions. These functions usually take an
|
||||
expression, bind a single variable and run the expression inside the newly
|
||||
established scope.
|
||||
|
||||
The `all` function takes a list, creates a variable for list element and runs
|
||||
the predicate expression. For example:
|
||||
|
||||
MATCH (n) RETURN n, all(n IN n.prop_list WHERE n < 42)
|
||||
|
||||
We create a new symbol for use inside `all`, this means that the `WHERE n <
|
||||
42` uses the `n` which takes values from a `n.prop_list` elements. The
|
||||
original `n` bound by `MATCH` is not visible inside the `all` function, but it
|
||||
is visible outside. Therefore, the `RETURN n` and `n.prop_list` reference the
|
||||
`n` from `MATCH`.
|
||||
|
||||
[^1]: Constant expressions are expressions for which the result can be
|
||||
computed at compile time.
|
||||
@@ -1,108 +0,0 @@
|
||||
# Quick Start
|
||||
|
||||
A short chapter on downloading the Memgraph source, compiling and running.
|
||||
|
||||
## Obtaining the Source Code
|
||||
|
||||
Memgraph uses `git` for source version control. You will need to install `git`
|
||||
on your machine before you can download the source code.
|
||||
|
||||
On Debian systems, you can do it inside a terminal with the following
|
||||
command:
|
||||
|
||||
sudo apt-get install git
|
||||
|
||||
On ArchLinux or Gentoo, you probably already know what to do.
|
||||
|
||||
After installing `git`, you are now ready to fetch your own copy of Memgraph
|
||||
source code. Run the following command:
|
||||
|
||||
git clone https://phabricator.memgraph.io/diffusion/MG/memgraph.git
|
||||
|
||||
The above will create a `memgraph` directory and put all source code there.
|
||||
|
||||
## Compiling Memgraph
|
||||
|
||||
With the source code, you are now ready to compile Memgraph. Well... Not
|
||||
quite. You'll need to download Memgraph's dependencies first.
|
||||
|
||||
In your terminal, position yourself in the obtained memgraph directory.
|
||||
|
||||
cd memgraph
|
||||
|
||||
### Installing Dependencies
|
||||
|
||||
On Debian systems, dependencies that are required by the codebase should be
|
||||
setup by running the `init` script:
|
||||
|
||||
./init -s
|
||||
|
||||
Currently, other systems aren't supported in the `init` script. But you can
|
||||
issue the needed steps manually. First run the `init` script.
|
||||
|
||||
./init
|
||||
|
||||
The script will output the required packages, which you should be able to
|
||||
install via your favorite package manager. For example, `pacman` on ArchLinux.
|
||||
After installing the packages, issue the following commands:
|
||||
|
||||
mkdir -p build
|
||||
./libs/setup.sh
|
||||
|
||||
### Compiling
|
||||
|
||||
Memgraph is compiled using our own custom toolchain that can be obtained from
|
||||
[Toolchain repository](https://deps.memgraph.io/toolchain). You should read
|
||||
the `README.txt` file in the repository and install the apropriate toolchain
|
||||
for your distribution. After you have installed the toolchain you should read
|
||||
the instructions for the toolchain in the toolchain install directory
|
||||
(`/opt/toolchain-vXYZ/README.md`) and install dependencies that are necessary
|
||||
to run the toolchain.
|
||||
|
||||
When you want to compile Memgraph you should activate the toolchain using the
|
||||
prepared toolchain activation script that is also described in the toolchain
|
||||
`README`.
|
||||
|
||||
NOTE: You *must* activate the toolchain every time you want to compile
|
||||
Memgraph!
|
||||
|
||||
You should now activate the toolchain in your console.
|
||||
|
||||
source /opt/toolchain-vXYZ/activate
|
||||
|
||||
With all of the dependencies installed and the build environment set-up, you
|
||||
need to configure the build system. To do that, execute the following:
|
||||
|
||||
cd build
|
||||
cmake ..
|
||||
|
||||
If everything went OK, you can now, finally, compile Memgraph.
|
||||
|
||||
make -j$(nproc)
|
||||
|
||||
### Running
|
||||
|
||||
After the compilation verify that Memgraph works:
|
||||
|
||||
./memgraph --version
|
||||
|
||||
To make extra sure, run the unit tests:
|
||||
|
||||
ctest -R unit -j$(nproc)
|
||||
|
||||
## Problems
|
||||
|
||||
If you have any trouble running the above commands, contact your nearest
|
||||
developer who successfully built Memgraph. Ask for help and insist on getting
|
||||
this document updated with correct steps!
|
||||
|
||||
## Next Steps
|
||||
|
||||
Familiarise yourself with our code conventions and guidelines:
|
||||
|
||||
* [C++ Code](cpp-code-conventions.md)
|
||||
* [Other Code](other-code-conventions.md)
|
||||
* [Code Review Guidelines](code-review.md)
|
||||
|
||||
Take a look at the list of [required reading](required-reading.md) for
|
||||
brushing up on technical skills.
|
||||
@@ -1,129 +0,0 @@
|
||||
# Required Reading
|
||||
|
||||
This chapter lists a few books that should be read by everyone working on
|
||||
Memgraph. Since Memgraph is developed primarily with C++, Python and Common
|
||||
Lisp, books are oriented around those languages. Of course, there are plenty
|
||||
of general books which will help you improve your technical skills (such as
|
||||
"The Pragmatic Programmer", "The Mythical Man-Month", etc.), but they are not
|
||||
listed here. This way the list should be kept short and the *required* part in
|
||||
"Required Reading" more easily honored.
|
||||
|
||||
Some of these books you may find in our office, so feel free to pick them up.
|
||||
If any are missing and you would like a physical copy, don't be afraid to
|
||||
request the book for our office shelves.
|
||||
|
||||
Besides reading, don't get stuck in a rut and be a
|
||||
[Blub Programmer](http://www.paulgraham.com/avg.html).
|
||||
|
||||
## Effective C++ by Scott Meyers
|
||||
|
||||
Required for C++ developers.
|
||||
|
||||
The book is a must-read as it explains most common gotchas of using C++. After
|
||||
reading this book, you are good to write competent C++ which will pass code
|
||||
reviews easily.
|
||||
|
||||
## Effective Modern C++ by Scott Meyers
|
||||
|
||||
Required for C++ developers.
|
||||
|
||||
This is a continuation of the previous book, it covers updates to C++ which
|
||||
came with C++11 and later. The book isn't as imperative as the previous one,
|
||||
but it will make you aware of modern features we are using in our codebase.
|
||||
|
||||
## Practical Common Lisp by Peter Siebel
|
||||
|
||||
Required for Common Lisp developers.
|
||||
|
||||
Free: http://www.gigamonkeys.com/book/
|
||||
|
||||
We use Common Lisp to generate C++ code and make our lives easier.
|
||||
Unfortunately, not many developers are familiar with the language. This book
|
||||
will make you familiar very quickly as it has tons of very practical
|
||||
exercises. E.g. implementing unit testing library, serialization library and
|
||||
bundling all that to create a mp3 music server.
|
||||
|
||||
## Effective Python by Brett Slatkin
|
||||
|
||||
(Almost) required reading for Python developers.
|
||||
|
||||
Why the "almost"? Well, Python is relatively easy to pick up and you will
|
||||
probably learn all the gotchas during code review from someone more
|
||||
experienced. This makes the book less necessary for a newcomer to Memgraph,
|
||||
but the book is not advanced enough to delegate it to
|
||||
[Advanced Reading](#advanced-reading). The book is written in similar vein as
|
||||
the "Effective C++" ones and will make you familiar with nifty Python features
|
||||
that make everyone's lives easier.
|
||||
|
||||
# Advanced Reading
|
||||
|
||||
The books listed below are not required reading, but you may want to read them
|
||||
at some point when you feel comfortable enough.
|
||||
|
||||
## Design Patterns by Gamma et. al.
|
||||
|
||||
Recommended for C++ developers.
|
||||
|
||||
This book is highly divisive because it introduced a culture centered around
|
||||
design patterns. The main issues is overuse of patterns which complicates the
|
||||
code. This has made many Java programs to serve as examples of highly
|
||||
complicated, "enterprise" code.
|
||||
|
||||
Unfortunately, design patterns are pretty much missing
|
||||
language features. This is most evident in dynamic languages such as Python
|
||||
and Lisp, as demonstrated by
|
||||
[Peter Norvig](http://www.norvig.com/design-patterns/).
|
||||
|
||||
Or as [Paul Graham](http://www.paulgraham.com/icad.html) put it:
|
||||
|
||||
```
|
||||
This practice is not only common, but institutionalized. For example, in the
|
||||
OO world you hear a good deal about "patterns". I wonder if these patterns are
|
||||
not sometimes evidence of case (c), the human compiler, at work. When I see
|
||||
patterns in my programs, I consider it a sign of trouble. The shape of a
|
||||
program should reflect only the problem it needs to solve. Any other
|
||||
regularity in the code is a sign, to me at least, that I'm using abstractions
|
||||
that aren't powerful enough-- often that I'm generating by hand the expansions
|
||||
of some macro that I need to write
|
||||
```
|
||||
|
||||
After presenting the book so negatively, why you should even read it then?
|
||||
Well, it is good to be aware of those design patterns and use them when
|
||||
appropriate. They can improve modularity and reuse of the code. You will also
|
||||
find examples of such patterns in our code, primarily Strategy and Visitor
|
||||
patterns. The book is also a good stepping stone to more advanced reading
|
||||
about software design.
|
||||
|
||||
## Modern C++ Design by Andrei Alexandrescu
|
||||
|
||||
Recommended for C++ developers.
|
||||
|
||||
This book can be treated as a continuation of the previous "Design Patterns"
|
||||
book. It introduced "dark arts of template meta-programming" to the world.
|
||||
Many of the patterns are converted to use C++ templates which makes them even
|
||||
better for reuse. But, like the previous book, there are downsides if used too
|
||||
much. You should approach it with a critical eye and it will help you
|
||||
understand ideas that are used in some parts of our codebase.
|
||||
|
||||
## Large Scale C++ Software Design by John Lakos
|
||||
|
||||
Recommended for C++ developers.
|
||||
|
||||
An old book, but well worth the read. Lakos presents a very pragmatic view of
|
||||
writing modular software and how it affects both development time as well as
|
||||
program runtime. Some things are outdated or controversial, but it will help
|
||||
you understand how the whole C++ process of working in a large team, compiling
|
||||
and linking affects development.
|
||||
|
||||
## On Lisp by Paul Graham
|
||||
|
||||
Recommended for Common Lisp developers.
|
||||
|
||||
Free: http://www.paulgraham.com/onlisp.html
|
||||
|
||||
An excellent continuation to "Practical Common Lisp". It starts of slow, as if
|
||||
introducing the language, but very quickly picks up speed. The main meat of
|
||||
the book are macros and their uses. From using macros to define cooperative
|
||||
concurrency to including Prolog as if it's part of Common Lisp. The book will
|
||||
help you understand more advanced macros that are occasionally used in our
|
||||
Lisp C++ Preprocessor (LCP).
|
||||
@@ -1,110 +0,0 @@
|
||||
# DatabaseAccessor
|
||||
|
||||
A `DatabaseAccessor` actually wraps a transactional access to database
|
||||
data, for a single transaction. In that sense the naming is bad. It
|
||||
encapsulates references to the database and the transaction object.
|
||||
|
||||
It contains logic for working with database content (graph element
|
||||
data) in the context of a single transaction. All CRUD operations are
|
||||
performed within a single transaction (as Memgraph is a transactional
|
||||
database), and therefore iteration over data, finding a specific graph
|
||||
element etc are all functionalities of a `GraphDbAccessor`.
|
||||
|
||||
In single-node Memgraph the database accessor also defined the lifetime
|
||||
of a transaction. Even though a `Transaction` object was owned by the
|
||||
transactional engine, it was `GraphDbAccessor`'s lifetime that object
|
||||
was bound to (the transaction was implicitly aborted in
|
||||
`GraphDbAccessor`'s destructor, if it was not explicitly ended before
|
||||
that).
|
||||
|
||||
# RecordAccessor
|
||||
|
||||
It is important to understand data organization and access in the
|
||||
storage layer. This discussion pertains to vertices and edges as graph
|
||||
elements that the end client works with.
|
||||
|
||||
Memgraph uses MVCC (documented on it's own page). This means that for
|
||||
each graph element there could be different versions visible to
|
||||
different currently executing transactions. When we talk about a
|
||||
`Vertex` or `Edge` as a data structure we typically mean one of those
|
||||
versions. In code this semantic is implemented so that both those classes
|
||||
inherit `mvcc::Record`, which in turn inherits `mvcc::Version`.
|
||||
|
||||
Handling MVCC and visibility is not in itself trivial. Next to that,
|
||||
there is other book-keeping to be performed when working with data. For
|
||||
that reason, Memgraph uses "accessors" to define an API of working with
|
||||
data in a safe way. Most of the code in Memgraph (for example the
|
||||
interpretation code) should work with accessors. There is a
|
||||
`RecordAccessor` as a base class for `VertexAccessor` and
|
||||
`EdgeAccessor`. Following is an enumeration of their purpose.
|
||||
|
||||
### Data access
|
||||
|
||||
The client interacts with Memgraph using the Cypher query language. That
|
||||
language has certain semantics which imply that multiple versions of the
|
||||
data need to be visible during the execution of a single query. For
|
||||
example: expansion over the graph is always done over the graph state as
|
||||
it was at the beginning of the transaction.
|
||||
|
||||
The `RecordAccessor` exposes functions to switch between the old and the new
|
||||
versions of the same graph element (intelligently named `SwitchOld` and
|
||||
`SwitchNew`) within a single transaction. In that way the client code
|
||||
(mostly the interpreter) can avoid dealing with the underlying MVCC
|
||||
version concepts.
|
||||
|
||||
### Updates
|
||||
|
||||
Data updates are also done through accessors. Meaning: there are methods
|
||||
on the accessors that modify data, the client code should almost never
|
||||
interact directly with `Vertex` or `Edge` objects.
|
||||
|
||||
The accessor layer takes care of creating version in the MVCC layer and
|
||||
performing updates on appropriate versions.
|
||||
|
||||
Next, for many kinds of updates it is necessary to update the relevant
|
||||
indexes. There are implicit indexes for vertex labels, as
|
||||
well as user-created indexes for (label, property) pairs. The accessor
|
||||
layer takes care of updating the indexes when these values are changed.
|
||||
|
||||
Each update also triggers a log statement in the write-ahead log. This
|
||||
is also handled by the accessor layer.
|
||||
|
||||
### Distributed
|
||||
|
||||
In distributed Memgraph accessors also contain a lot of the remote graph
|
||||
element handling logic. More info on that is available in the
|
||||
documentation for distributed.
|
||||
|
||||
### Deferred MVCC data lookup for Edges
|
||||
|
||||
Vertices and edges are versioned using MVCC. This means that for each
|
||||
transaction an MVCC lookup needs to be done to determine which version
|
||||
is visible to that transaction. This tends to slow things down due to
|
||||
cache invalidations (version lists and versions are stored in arbitrary
|
||||
locations on the heap).
|
||||
|
||||
However, for edges, only the properties are mutable. The edge endpoints
|
||||
and type are fixed once the edge is created. For that reason both edge
|
||||
endpoints and type are available in vertex data, so that when expanding
|
||||
it is not mandatory to do MVCC lookups of versioned, mutable data. This
|
||||
logic is implemented in `RecordAccessor` and `EdgeAccessor`.
|
||||
|
||||
### Exposure
|
||||
|
||||
The original idea and implementation of graph element accessors was that
|
||||
they'd prevent client code from ever interacting with raw `Vertex` or
|
||||
`Edge` data. This however turned out to be impractical when implementing
|
||||
distributed Memgraph and the raw data members have since been exposed
|
||||
(through getters to old and new version pointers). However, refrain from
|
||||
working with that data directly whenever possible! Always consider the
|
||||
accessors to be the first go-to for interacting with data, especially
|
||||
when in the context of a transaction.
|
||||
|
||||
# Skiplist accessor
|
||||
|
||||
The term "accessor" is also used in the context of a skiplist. Every
|
||||
operation on a skiplist must be performed within on an
|
||||
accessor. The skiplist ensures that there will be no physical deletions
|
||||
of an object during the lifetime of an accessor. This mechanism is used
|
||||
to ensure deletion correctness in a highly concurrent container.
|
||||
We only mention that here to avoid confusion regarding terminology.
|
||||
@@ -1,116 +0,0 @@
|
||||
# Label indexes
|
||||
|
||||
These are unsorted indexes that contain all the vertices that have the label
|
||||
the indexes are for (one index per label). These kinds of indexes get
|
||||
automatically generated for each label used in the database.
|
||||
|
||||
### Updating the indexes
|
||||
|
||||
Whenever something gets added to the record we update the index (add that
|
||||
record to index). We keep an index which might contain garbage (not relevant
|
||||
records, because the value got removed or something similar) but we will
|
||||
filter it out when querying the index. We do it like this because we don't
|
||||
have to do bookkeeping and deciding if we update the index on the end of the
|
||||
transaction (commit/abort phase), moreover current interpreter advances the
|
||||
command in transaction and as such assumes that the indexes now contain
|
||||
objects added in the previous command inside this transaction, so we need to
|
||||
update over the whole scope of transaction (whenever something is added to the
|
||||
record).
|
||||
|
||||
### Index Entries Label
|
||||
|
||||
These kinds of indexes are internally keeping track of pair (record, vlist).
|
||||
Why do we need to keep track of exactly those two things?
|
||||
|
||||
Problems with two different approaches
|
||||
|
||||
1) Keep track of just the record:
|
||||
|
||||
- We need the `VersionList` for creating an accessor (this in itself is a
|
||||
deal-breaker).
|
||||
- Semantically it makes sense. An edge/vertex maps bijectionally to a
|
||||
`VersionList`.
|
||||
- We might try to access some members of record while the record is being
|
||||
modified from another thread.
|
||||
- A vertex/edge could get updated, thus expiring the record in the index.
|
||||
The newly created record should be present in the index, but it's not.
|
||||
Without the `VersionList` we can't reach the newly created record.
|
||||
- Probably there are even more reasons... It should be obvious by now that
|
||||
we need the `VersionList` in the index.
|
||||
|
||||
2) Keep track of just the version list:
|
||||
|
||||
- Removing from an index is a problem for two major reasons. First, if we
|
||||
only have the `VersionList`, checking if it should be removed implies
|
||||
checking all the reachable records, which is not thread-safe. Second,
|
||||
there are issues with concurrent removal and insertion. The cleanup thread
|
||||
could determine the vertex/edge should be removed from the index and
|
||||
remove it, while in between those ops another thread attempts to insert
|
||||
the `VersionList` into the index. The insertion does nothing because the
|
||||
`VersionList` is already in, but it gets removed immediately after.
|
||||
|
||||
Because of inability to keep track of just the record, or value, we need to
|
||||
keep track of both of them. Resolution of problems mentioned above, in the
|
||||
same order, with (record, vlist) pair
|
||||
|
||||
- simple `vlist.find(current transaction)` will get us the newest visible
|
||||
record
|
||||
- we'll never try to access some record if it's still being written since we
|
||||
will always operate on vlist.find returned record
|
||||
- newest record will contain that label
|
||||
- since we have (record, vlist) pair as the key in the index when we update
|
||||
and delete in the same time we will never delete the same record, vlist
|
||||
pair we are adding because the record, vlist pair we are deleting is
|
||||
already superseded by a newer record and as such won't be inserted while
|
||||
it's being deleted
|
||||
|
||||
### Querying the index
|
||||
|
||||
We run through the index for the given label and do `vlist.find` operation for
|
||||
the current transaction, and check if the newest return record has that
|
||||
label. If it has it then we return it. By now you are probably wondering
|
||||
aren't we sometimes returning duplicate vlist entries? And you are wondering
|
||||
correctly, we would be returning them, but we are making sure that the entires
|
||||
in the index are sorted by their `vlist*` and as such we can filter consecutive
|
||||
duplicate `vlist*` to only return one of those while still being able to create
|
||||
an iterator to index.
|
||||
|
||||
### Cleaning the index
|
||||
|
||||
Cleaning the index is not as straightforward as it seems as a lot of garbage
|
||||
can accumulate, but it's hard to know when exactly can we delete some (record,
|
||||
vlist) pair. First, let's assume that we are doing the cleaning process at
|
||||
some `transaction_id`, `id` such that there doesn't exist an active transaction
|
||||
with an id lower than `id`.
|
||||
|
||||
We scan through the whole index and for each (record, vlist) pair we first
|
||||
check if it was deleted before the id (i.e. no transaction with an id >= `id`
|
||||
will ever again see that record), if it was deleted before we might naively
|
||||
say that it's safe to delete it, but, we must take into account that when some
|
||||
new record is created from this record (update operation), that record still
|
||||
contains the label but by deleting this record we won't be able to see that
|
||||
vlist because that new record won't add again to index because we didn't
|
||||
explicitly add that label again to it.
|
||||
|
||||
Because of this we have to 'update' this index (record, vlist) pair. We have
|
||||
to update the record to now point to a newer record in vlist, the one that is
|
||||
not deleted yet. We can do that by querying the `version_list` for the last
|
||||
record inside (oldest it has — remember that `mvcc_gc` will re-link not
|
||||
visible records so the last record will be visible for the current GC id).
|
||||
When updating the record inside the index, it's not okay to just update the
|
||||
pointer and leave the index as it is, because with updating the `record*` we
|
||||
might change the relative order of entries inside the index. We first have to
|
||||
re-insert it with new `record*`, and then delete the old entry. And we need to
|
||||
do insertion before the remove operation! Otherwise it could happen that the
|
||||
vlist with a newer record with that label won't exist while some transaction
|
||||
is querying the index.
|
||||
|
||||
Records which we added as a consequence of deleting older records will be
|
||||
eventually removed from the index if they don't contain label because if we
|
||||
see that the record is not deleted we try to check if that record still
|
||||
contains the label. We also need to be careful here because we can't check
|
||||
that while the record is being potentially updated by some transaction (race
|
||||
condition), so we need can check if records still contain label if it's
|
||||
creation id is smaller than our `id`, as that implies that the creating
|
||||
transaction either aborted or committed as our `id` is equal to the oldest
|
||||
active transaction in time of starting the GC.
|
||||
@@ -1,131 +0,0 @@
|
||||
# Property storage
|
||||
|
||||
Although the reader is probably familiar with properties in *Memgraph*, let's
|
||||
briefly recap.
|
||||
|
||||
Both vertices and edges can store an arbitrary number of properties. Properties
|
||||
are, in essence, ordered pairs of property names and property values. Each
|
||||
property name within a single graph element (edge/node) can store a single
|
||||
property value. Property names are represented as strings, while property values
|
||||
must be one of the following types:
|
||||
|
||||
Type | Description
|
||||
-----------|------------
|
||||
`Null` | Denotes that the property has no value. This is the same as if the property does not exist.
|
||||
`String` | A character string, i.e. text.
|
||||
`Boolean` | A boolean value, either `true` or `false`.
|
||||
`Integer` | An integer number.
|
||||
`Float` | A floating-point number, i.e. a real number.
|
||||
`List` | A list containing any number of property values of any supported type. It can be used to store multiple values under a single property name.
|
||||
`Map` | A mapping of string keys to values of any supported type.
|
||||
|
||||
Property values are modeled in a class conveniently called `PropertyValue`.
|
||||
|
||||
## Mapping between property names and property keys.
|
||||
|
||||
Although users think of property names in terms of descriptive strings
|
||||
(e.g. "location" or "department"), *Memgraph* internally converts those names
|
||||
into property keys which are, essentially, unsigned 16-bit integers.
|
||||
|
||||
Property keys are modelled by a not-so-conveniently named class called
|
||||
`Property` which can be found in `storage/types.hpp`. The actual conversion
|
||||
between property names and property keys is done within the `ConcurrentIdMapper`
|
||||
but the internals of that implementation are out of scope for understanding
|
||||
property storage.
|
||||
|
||||
## PropertyValueStore
|
||||
|
||||
Both `Edge` and `Vertex` objects contain an instance of `PropertyValueStore`
|
||||
object which is responsible for storing properties of a corresponding graph
|
||||
element.
|
||||
|
||||
An interface of `PropertyValueStore` is as follows:
|
||||
|
||||
Method | Description
|
||||
-----------|------------
|
||||
`at` | Returns the `PropertyValue` for a given `Property` (key).
|
||||
`set` | Stores a given `PropertyValue` under a given `Property` (key).
|
||||
`erase` | Deletes a given `Property` (key) alongside its corresponding `PropertyValue`.
|
||||
`clear` | Clears the storage.
|
||||
`iterator`| Provides an extension of `std::input_iterator` that iterates over storage.
|
||||
|
||||
## Storage location
|
||||
|
||||
By default, *Memgraph* is an in-memory database and all properties are therefore
|
||||
stored in working memory unless specified otherwise by the user. User has an
|
||||
option to specify via the command line which properties they wish to be stored
|
||||
on disk.
|
||||
|
||||
Storage location of each property is encapsulated within a `Property` object
|
||||
which is ensured by the `ConcurrentIdMapper`. More precisely, the unsigned 16-bit
|
||||
property key has the following format:
|
||||
|
||||
```
|
||||
|---location--|------id------|
|
||||
|-Memory|Disk-|-----2^15-----|
|
||||
```
|
||||
|
||||
In other words, the most significant bit determines the location where the
|
||||
property will be stored.
|
||||
|
||||
### In-memory storage
|
||||
|
||||
The underlying implementation of in-memory storage for the time being is
|
||||
`std::vector<std::pair<Property, PropertyValue>>`. Implementations of`at`, `set`
|
||||
and `erase` are linear in time. This implementation is arguably more efficient
|
||||
than `std::map` or `std::unordered_map` when the average number of properties of
|
||||
a record is relatively small (up to 10) which seems to be the case.
|
||||
|
||||
### On-disk storage
|
||||
|
||||
#### KVStore
|
||||
|
||||
Disk storage is modeled by an abstraction of key-value storage as implemented in
|
||||
`storage/kvstore.hpp'. An interface of this abstraction is as follows:
|
||||
|
||||
Method | Description
|
||||
----------------|------------
|
||||
`Put` | Stores the given value under the given key.
|
||||
`Get` | Obtains the given value stored under the given key.
|
||||
`Delete` | Deletes a given (key, value) pair from storage..
|
||||
`DeletePrefix` | Deletes all (key, value) pairs where key begins with a given prefix.
|
||||
`Size` | Returns the size of the storage or, optionally, the number of stored pairs that begin with a given prefix.
|
||||
`iterator` | Provides an extension of `std::input_iterator` that iterates over storage.
|
||||
|
||||
Keys and values in this context are of type `std::string`.
|
||||
|
||||
The actual underlying implementation of this abstraction uses
|
||||
[RocksDB]{https://rocksdb.org} — a persistent key-value store for fast
|
||||
storage.
|
||||
|
||||
It is worthy to note that the custom iterator implementation allows the user
|
||||
to iterate over a given prefix. Otherwise, the implementation follows familiar
|
||||
c++ constructs and can be used as follows:
|
||||
|
||||
```
|
||||
KVStore storage = ...;
|
||||
for (auto it = storage.begin(); it != storage.end(); ++it) {}
|
||||
for (auto kv : storage) {}
|
||||
for (auto it = storage.begin("prefix"); it != storage.end("prefix"); ++it) {}
|
||||
```
|
||||
|
||||
Note that it is not possible to scan over multiple prefixes. For instance, one
|
||||
might assume that you can scan over all keys that fall in a certain
|
||||
lexicographical range. Unfortunately, that is not the case and running the
|
||||
following code will result in an infinite loop with a touch of undefined
|
||||
behavior.
|
||||
|
||||
```
|
||||
KVStore storage = ...;
|
||||
for (auto it = storage.begin("alpha"); it != storage.end("omega"); ++it) {}
|
||||
```
|
||||
|
||||
#### Data organization on disk
|
||||
|
||||
Each `PropertyValueStore` instance can access a static `KVStore` object that can
|
||||
store `(key, value)` pairs on disk. The key of each property on disk consists of
|
||||
two parts — a unique identifier (unsigned 64-bit integer) of the current
|
||||
record version (see mvcc docummentation for further clarification) and a
|
||||
property key as described above. The actual value of the property is serialized
|
||||
into a bytestring using bolt `BaseEncoder`. Similarly, deserialization is
|
||||
performed by bolt `Decoder`.
|
||||
@@ -1,152 +0,0 @@
|
||||
# Bootstrapping Compilation Toolchain for Memgraph
|
||||
|
||||
Requirements:
|
||||
|
||||
* libstdc++ shipped with gcc-6.3 or gcc-6.4
|
||||
* cmake >= 3.1, Debian Stretch uses cmake-3.7.2
|
||||
* clang-3.9
|
||||
|
||||
## Installing gcc-6.4
|
||||
|
||||
gcc-6.3 has a bug, so use the 6.4 version which is just a bugfix release.
|
||||
|
||||
Requirements on CentOS 7:
|
||||
|
||||
* wget
|
||||
* make
|
||||
* gcc (bootstrap)
|
||||
* gcc-c++ (bootstrap)
|
||||
* gmp-devel (bootstrap)
|
||||
* mpfr-devel (bootstrap)
|
||||
* libmpc-devel (bootstrap)
|
||||
* zip
|
||||
* perl
|
||||
* dejagnu (testing)
|
||||
* expect (testing)
|
||||
* tcl (testing)
|
||||
|
||||
```
|
||||
wget ftp://ftp.mpi-sb.mpg.de/pub/gnu/mirror/gcc.gnu.org/pub/gcc/releases/gcc-6.4.0/gcc-6.4.0.tar.gz
|
||||
tar xf gcc-6.4.0.tar.gz
|
||||
cd gcc-6.4.0
|
||||
mkdir build
|
||||
cd build
|
||||
../configure --disable-multilib --prefix=<install-dst>
|
||||
make
|
||||
# Testing
|
||||
make -k check
|
||||
make install
|
||||
```
|
||||
|
||||
*Do not put gcc + libs on PATH* (unless you know what you are doing).
|
||||
|
||||
## Installing cmake-3.7.2
|
||||
|
||||
Requirements on CentOS 7:
|
||||
|
||||
* wget
|
||||
* make
|
||||
* gcc
|
||||
* gcc-c++
|
||||
* ncurses-devel (optional, for ccmake)
|
||||
|
||||
```
|
||||
wget https://cmake.org/files/v3.7/cmake-3.7.2.tar.gz
|
||||
tar xf cmake-3.7.2.tar.gz
|
||||
cd cmake-3.7.2.tar.gz
|
||||
./bootstrap --prefix<install-dst>
|
||||
make
|
||||
make install
|
||||
```
|
||||
|
||||
Put cmake on PATH (if appropriate)
|
||||
|
||||
**Fix the bug in CpackRPM**
|
||||
|
||||
`"<path-to-cmake>/share/cmake-3.7/Modules/CPackRPM.cmake" line 2273 of 2442`
|
||||
|
||||
The line
|
||||
|
||||
```
|
||||
set(RPMBUILD_FLAGS "-bb")
|
||||
```
|
||||
needs to be before
|
||||
|
||||
```
|
||||
if(CPACK_RPM_GENERATE_USER_BINARY_SPECFILE_TEMPLATE OR NOT CPACK_RPM_USER_BINARY_SPECFILE)
|
||||
```
|
||||
|
||||
It was probably accidentally placed after, and is fixed in later cmake
|
||||
releases.
|
||||
|
||||
## Installing clang-3.9
|
||||
|
||||
Requirements on CentOS 7:
|
||||
|
||||
* wget
|
||||
* make
|
||||
* cmake
|
||||
|
||||
```
|
||||
wget http://releases.llvm.org/3.9.1/llvm-3.9.1.src.tar.xz
|
||||
tar xf llvm-3.9.1.src.tar.xz
|
||||
mv llvm-3.9.1.src llvm
|
||||
|
||||
wget http://releases.llvm.org/3.9.1/cfe-3.9.1.src.tar.xz
|
||||
tar xf cfe-3.9.1.src.tar.xz
|
||||
mv cfe-3.9.1.src llvm/tools/clang
|
||||
|
||||
cd llvm
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE="Release" -DGCC_INSTALL_PREFIX=<gcc-dir> \
|
||||
-DCMAKE_C_COMPILER=<gcc> -DCMAKE_CXX_COMPILER=<g++> \
|
||||
-DCMAKE_CXX_LINK_FLAGS="-L<gcc-dir>/lib64 -Wl,-rpath,<gcc-dir>/lib64" \
|
||||
-DCMAKE_INSTALL_PREFIX=<install-dst> ..
|
||||
make
|
||||
# Testing
|
||||
make check-clang
|
||||
make install
|
||||
```
|
||||
|
||||
Put clang on PATH (if appropriate)
|
||||
|
||||
## Memgraph
|
||||
|
||||
Requirements on CentOS 7:
|
||||
|
||||
* libuuid-devel (antlr4)
|
||||
* java-1.8.0-openjdk (antlr4)
|
||||
* boost-static (too low version --- compile manually)
|
||||
* rpm-build (RPM)
|
||||
* python3 (tests, ...)
|
||||
* which (required for rocksdb)
|
||||
* sbcl (lisp C++ preprocessing)
|
||||
|
||||
### Boost 1.62
|
||||
|
||||
```
|
||||
wget https://netix.dl.sourceforge.net/project/boost/boost/1.62.0/boost_1_62_0.tar.gz
|
||||
tar xf boost_1_62_0.tar.gz
|
||||
cd boost_1_62_0
|
||||
./bootstrap.sh --with-toolset=clang --with-libraries=iostreams,serialization --prefix=<install-dst>
|
||||
./b2
|
||||
# Default installs to /usr/local/
|
||||
./b2 install
|
||||
```
|
||||
|
||||
### Building Memgraph
|
||||
|
||||
clang is *required* to be findable by cmake, i.e. it should be on PATH.
|
||||
cmake isn't required to be on the path, since you run it manually, so can use
|
||||
the full path to executable in order to run it. Obviously, it is convenient to
|
||||
put cmake also on PATH.
|
||||
|
||||
Building is done as explained in [Quick Start](quick-start.md), but each
|
||||
`make` invocation needs to be prepended with:
|
||||
|
||||
`LD_RUN_PATH=<gcc-dir>/lib64 make ...`
|
||||
|
||||
### RPM
|
||||
|
||||
Name format: `memgraph-<version>-<pkg-version>.<arch>.rpm`
|
||||
@@ -1,177 +0,0 @@
|
||||
# Memgraph Workflow
|
||||
|
||||
This chapter describes the usual workflow for working on Memgraph.
|
||||
|
||||
## Git
|
||||
|
||||
Memgraph uses [git](https://git-scm.com/) for source version control. If you
|
||||
obtained the source, you probably already have it installed. Before you can
|
||||
track new changes, you need to setup some basic information.
|
||||
|
||||
First, tell git your name:
|
||||
|
||||
git config --global user.name "FirstName LastName"
|
||||
|
||||
Then, set your Memgraph email:
|
||||
|
||||
git config --global user.email "my.email@memgraph.com"
|
||||
|
||||
Finally, make git aware of your favourite editor:
|
||||
|
||||
git config --global core.editor "vim"
|
||||
|
||||
## Phabricator
|
||||
|
||||
All of the code in Memgraph needs to go through code review before it can be
|
||||
accepted in the codebase. This is done through
|
||||
[Phabricator](https://phacility.com/phabricator/). The command line tool for
|
||||
interfacing with Phabricator is
|
||||
[arcanist](https://phacility.com/phabricator/arcanist/). You should already
|
||||
have it installed if you followed the steps in [Quick Start](quick-start.md).
|
||||
|
||||
The only required setup is to go in the root of Memgraph's project and run:
|
||||
|
||||
arc install-certificate
|
||||
|
||||
## Working on Your Feature Branch
|
||||
|
||||
Git has a concept of source code *branches*. The `master` branch contains all
|
||||
of the changes which were reviewed and accepted in Memgraph's code base. The
|
||||
`master` branch is selected by default.
|
||||
|
||||
### Creating a Branch
|
||||
|
||||
When working on a new feature or fixing a bug, you should create a new branch
|
||||
out of the `master` branch. For example, let's say you are adding static type
|
||||
checking to the query language compiler. You would create a branch called
|
||||
`mg_query_static_typing` with the following command:
|
||||
|
||||
git branch mg_query_static_typing
|
||||
|
||||
To switch to that branch, type:
|
||||
|
||||
git checkout mg_query_static_typing
|
||||
|
||||
Since doing these two steps will happen often, you can use a shortcut command:
|
||||
|
||||
git checkout -b mg_query_static_typing
|
||||
|
||||
Note that a branch is created from the currently selected branch. So, if you
|
||||
wish to create another branch from `master` you need to switch to `master`
|
||||
first.
|
||||
|
||||
The usual convention for naming your branches is `mg_<feature_name>`, you may
|
||||
switch underscores ('\_') for hyphens ('-').
|
||||
|
||||
Do take care not to mix the case of your branch names! Certain operating
|
||||
systems (like Windows) don't distinguish the casing in git branches. This may
|
||||
cause hard to track down issues when trying to switch branches. Therefore, you
|
||||
should always name your branches with lowercase letters.
|
||||
|
||||
### Making and Committing Changes
|
||||
|
||||
When you have a branch for your new addition, you can now actually start
|
||||
implementing it. After some amount of time, you may have created new files,
|
||||
modified others and maybe even deleted unused files. You need to tell git to
|
||||
track those changes. This is accomplished with `git add` and `git rm`
|
||||
commands.
|
||||
|
||||
git add path-to-new-file path-to-modified-file
|
||||
git rm path-to-deleted-file
|
||||
|
||||
To check that everything is correctly tracked, you may use the `git status`
|
||||
command. It will also print the name of the currently selected branch.
|
||||
|
||||
If everything seems OK, you should commit these changes to git.
|
||||
|
||||
git commit
|
||||
|
||||
You will be presented with an editor where you need to type the commit
|
||||
message. Writing a good commit message is an art in itself. You should take a
|
||||
look at the links below. We try to follow these conventions as much as
|
||||
possible.
|
||||
|
||||
* [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/)
|
||||
* [A Note About Git Commit Messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
|
||||
* [stopwritingramblingcommitmessages](http://stopwritingramblingcommitmessages.com/)
|
||||
|
||||
### Sending Changes on a Review
|
||||
|
||||
After finishing your work on your feature branch, you will want to send it on
|
||||
code review. This is done through Arcanist. To do that, run the following
|
||||
command:
|
||||
|
||||
arc diff
|
||||
|
||||
You will, once again, be presented with an editor where you need to describe
|
||||
your whole work. `arc` will by default fill that description with your commit
|
||||
messages. The title and summary of your work should also follow the
|
||||
conventions of git messages as described above. If you followed the
|
||||
guidelines, the message filled by `arc` should be fine.
|
||||
|
||||
In addition to the message, you need to fill the `Reviewers:` line with
|
||||
usernames of people who should do the code review.
|
||||
|
||||
You changes will be visible on Phabricator as a so called "diff". You can find
|
||||
the default view of active diffs
|
||||
[here](https://phabricator.memgraph.io/differential/)
|
||||
|
||||
### Updating Changes Based on Review
|
||||
|
||||
When you get comments in the code review, you will want to make additional
|
||||
modifications to your work. The same workflow as before applies: [Making and
|
||||
Committing Changes](#making-and-committing-changes)
|
||||
|
||||
After making those changes, send them back on code review:
|
||||
|
||||
arc diff
|
||||
|
||||
|
||||
### Updating From New Master
|
||||
|
||||
Let's say that, while you were working, someone else added some new features
|
||||
to the codebase that you would like to use in your current work. To obtain
|
||||
those changes you should update your `master` branch:
|
||||
|
||||
git checkout master
|
||||
git pull origin master
|
||||
|
||||
Now, these changes are on `master`, but you want them in your local branch. To
|
||||
do that, use `git rebase`:
|
||||
|
||||
git checkout mg_query_static_typing
|
||||
git rebase master
|
||||
|
||||
During `git rebase`, you may get reports that some files have conflicting
|
||||
changes. If you need help resolving them, don't be afraid to ask around! After
|
||||
you've resolved them, mark them as done with `git add` command. You may
|
||||
then continue with `git rebase --continue`.
|
||||
|
||||
After the `git rebase` is done, you will now have new changes from `master` on
|
||||
your feature branch as if you just created and started working on that branch.
|
||||
You may continue with the usual workflow of [Making and Committing
|
||||
Changes](#making-and-committing-changes) and [Sending Changes on a
|
||||
Review](#sending-changes-on-a-review).
|
||||
|
||||
### Sending Your Changes on Master Branch
|
||||
|
||||
When your changes pass the code review, you are ready to integrate them in the
|
||||
`master` branch. To do that, run the following command:
|
||||
|
||||
arc land
|
||||
|
||||
Arcanist will take care of obtaining the latest changes from `master` and
|
||||
merging your changes on top. If the `land` was successful, Arcanist will
|
||||
delete your local branch and you will be back on `master`. Continuing from the
|
||||
examples above, the deleted branch would be `mg_query_static_typing`.
|
||||
|
||||
This marks the completion of your changes, and you are ready to work on
|
||||
something else.
|
||||
|
||||
### Note For People Familiar With Git
|
||||
|
||||
Since Arcanist takes care of merging your git commits and pushing them on
|
||||
`master`, you should *never* have to call `git merge` and `git push`. If you
|
||||
find yourself typing those commands, check that you are doing the right thing.
|
||||
The most common mistake is to use `git merge` instead of `git rebase` for the
|
||||
case described in [Updating From New Master](#updating-from-new-master).
|
||||
@@ -1,7 +1,6 @@
|
||||
# Memgraph Code Documentation
|
||||
|
||||
IMPORTANT: auto-generated (run doxygen Doxyfile in the project root)
|
||||
IMPORTANT: Auto-generated (run doxygen Doxyfile in the project root).
|
||||
|
||||
* HTML - just open docs/doxygen/html/index.html
|
||||
|
||||
* Latex - run make inside docs/doxygen/latex
|
||||
* HTML - Open docs/doxygen/html/index.html.
|
||||
* Latex - Run make inside docs/doxygen/latex.
|
||||
|
||||
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
@@ -1,78 +0,0 @@
|
||||
# Distributed Memgraph specs
|
||||
This document describes reasnonings behind Memgraphs distributed concepts.
|
||||
|
||||
## Distributed state machine
|
||||
Memgraphs distributed mode introduces two states of the cluster, recovering and
|
||||
working. The change between states shouldn't happen often, but when it happens
|
||||
it can take a while to make a transition from one to another.
|
||||
|
||||
### Recovering
|
||||
This state is the default state for Memgraph when the cluster starts with
|
||||
recovery flags. If the recovery finishes successfully, the state changes to
|
||||
working. If recovery fails, the user will be presented with a message that
|
||||
explains what happened and what are the next steps.
|
||||
|
||||
Another way to enter this state is failure. If the cluster encounters a failure,
|
||||
the master will enter the Recovering mode. This time, it will wait for all
|
||||
workers to respond with a message saying they are alive and well, and making
|
||||
sure they all have consistent state.
|
||||
|
||||
### Working
|
||||
This state should be the default state of Memgraph most of the time. When in
|
||||
this state, Memgraph accepts connections from Bolt clients and allows query
|
||||
execution.
|
||||
|
||||
If distributed execution fails for a transaction, that transaction, and all
|
||||
other active transactions will be aborted and the cluster will enter the
|
||||
Recovering state.
|
||||
|
||||
## Durability
|
||||
One of the important concepts in distributed Memgraph is durability.
|
||||
|
||||
### Cluster configuration
|
||||
When running Memgraph in distributed mode, the master will store cluster
|
||||
metadata in a persistent store. If fore some reason the cluster shuts down,
|
||||
recovering Memgraph from durability files shouldn't require any additional
|
||||
flags.
|
||||
|
||||
### Database ID
|
||||
Each new and clean run of Memgraph should generate a new globally unique
|
||||
database id. This id will associate all files that have persisted with this
|
||||
run. Adding the database id to snapshots, write-ahead logs and cluster metadata
|
||||
files ties them a specific Memgraph run, and it makes recovery easier to reason
|
||||
about.
|
||||
|
||||
When recovering, the cluster won't generate a new id, but will reuse the one
|
||||
from the snapshot/wal that it was able to recover from.
|
||||
|
||||
### Durability files
|
||||
Memgraph uses snapshots and write-ahead logs for durability.
|
||||
|
||||
When Memgraph recovers it has to make sure all machines in the cluster recover
|
||||
to the same recovery point. This is done by finding a common snapshot and
|
||||
finding common transactions in per-machine available write-ahead logs.
|
||||
|
||||
Since we can not be sure that each machine persisted durability files, we need
|
||||
to be able to negotiate a common recovery point in the cluster. Possible
|
||||
durability file failures could require to start the cluster from scratch,
|
||||
purging everything from storage and recovering from existing durability files.
|
||||
|
||||
We need to ensure that we keep wal files containing information about
|
||||
transactions between all existing snapshots. This will provide better durability
|
||||
in the case of a random machine durability file failure, where the cluster can
|
||||
find a common recovery point that all machines in the cluster have.
|
||||
|
||||
Also, we should suggest and make clear docs that anything less than two
|
||||
snapshots isn't considered safe for recovery.
|
||||
|
||||
### Recovery
|
||||
The recovery happens in following steps:
|
||||
* Master enables worker registration.
|
||||
* Master recovers cluster metadata from the persisted storage.
|
||||
* Master waits all required workers to register.
|
||||
* Master broadcasts a recovery request to all workers.
|
||||
* Workers respond with with a set of possible recovery points.
|
||||
* Master finds a common recovery point for the whole cluster.
|
||||
* Master broadcasts a recovery request with the common recovery point.
|
||||
* Master waits for the cluster to recover.
|
||||
* After a successful cluster recovery, master can enter Working state.
|
||||
@@ -1,75 +0,0 @@
|
||||
# Dynamic Graph Partitioning (abbr. DGP)
|
||||
|
||||
## Implementation
|
||||
|
||||
Take a look under `dev/memgraph/distributed/dynamic_graph_partitioning.md`.
|
||||
|
||||
### Implemented parameters
|
||||
|
||||
--dynamic-graph-partitioner-enabled (If the dynamic graph partitioner should be
|
||||
enabled.) type: bool default: false (start time)
|
||||
--dgp-improvement-threshold (How much better should specific node score be
|
||||
to consider a migration to another worker. This represents the minimal
|
||||
difference between new score that the vertex will have when migrated
|
||||
and the old one such that it's migrated.) type: int32 default: 10
|
||||
(start time)
|
||||
--dgp-max-batch-size (Maximal amount of vertices which should be migrated
|
||||
in one dynamic graph partitioner step.) type: int32 default: 2000
|
||||
(start time)
|
||||
|
||||
## Planning
|
||||
|
||||
### Design decisions
|
||||
|
||||
* Each partitioning session has to be a new transaction.
|
||||
* When and how does an instance perform the moves?
|
||||
* Periodically.
|
||||
* Token sharing (round robin, exactly one instance at a time has an
|
||||
opportunity to perform the moves).
|
||||
* On server-side serialization error (when DGP receives an error).
|
||||
-> Quit partitioning and wait for the next turn.
|
||||
* On client-side serialization error (when end client receives an error).
|
||||
-> The client should never receive an error because of any
|
||||
internal operation.
|
||||
-> For the first implementation, it's good enough to wait until data becomes
|
||||
available again.
|
||||
-> It would be nice to achieve that DGP has lower priority than end client
|
||||
operations.
|
||||
|
||||
### End-user parameters
|
||||
|
||||
* --dynamic-graph-partitioner-enabled (execution time)
|
||||
* --dgp-improvement-threshold (execution time)
|
||||
* --dgp-max-batch-size (execution time)
|
||||
* --dgp-min-batch-size (execution time)
|
||||
-> Minimum number of nodes that will be moved in each step.
|
||||
* --dgp-fitness-threshold (execution time)
|
||||
-> Do not perform moves if partitioning is good enough.
|
||||
* --dgp-delta-turn-time (execution time)
|
||||
-> Time between each turn.
|
||||
* --dgp-delta-step-time (execution time)
|
||||
-> Time between each step.
|
||||
* --dgp-step-time (execution time)
|
||||
-> Time limit per each step.
|
||||
|
||||
### Testing
|
||||
|
||||
The implementation has to provide good enough results in terms of:
|
||||
* How good the partitioning is (numeric value), aka goodness.
|
||||
* Workload execution time.
|
||||
* Stress test correctness.
|
||||
|
||||
Test cases:
|
||||
* N not connected subgraphs
|
||||
-> shuffle nodes to N instances
|
||||
-> run partitioning
|
||||
-> test perfect partitioning.
|
||||
* N connected subgraph
|
||||
-> shuffle nodes to N instance
|
||||
-> run partitioning
|
||||
-> test partitioning.
|
||||
* Take realistic workload (Long Running, LDBC1, LDBC2, Card Fraud, BFS, WSP)
|
||||
-> measure exec time
|
||||
-> run partitioning
|
||||
-> test partitioning
|
||||
-> measure exec time (during and after partitioning).
|
||||
@@ -1,275 +0,0 @@
|
||||
# High Availability (abbr. HA)
|
||||
|
||||
## High Level Context
|
||||
|
||||
High availability is a characteristic of a system which aims to ensure a
|
||||
certain level of operational performance for a higher-than-normal period.
|
||||
Although there are multiple ways to design highly available systems, Memgraph
|
||||
strives to achieve HA by elimination of single points of failure. In essence,
|
||||
this implies adding redundancy to the system so that a failure of a component
|
||||
does not imply the failure of the entire system. To ensure this, HA Memgraph
|
||||
implements the [Raft consensus algorithm](https://raft.github.io/).
|
||||
|
||||
Correct implementation of the algorithm guarantees that the cluster will be
|
||||
fully functional (available) as long as any strong majority of the servers are
|
||||
operational and can communicate with each other and with clients. For example,
|
||||
clusters of three or four machines can tolerate the failure of a single server,
|
||||
clusters of five and six machines can tolerate the failure of any two servers,
|
||||
and so on. Therefore, we strongly recommend a setup of an odd-sized cluster.
|
||||
|
||||
### Performance Implications
|
||||
|
||||
Internally, Raft achieves high availability by keeping a consistent replicated
|
||||
log on each server within the cluster. Therefore, we must successfully replicate
|
||||
a transaction on the majority of servers within the cluster before we actually
|
||||
commit it and report the result back to the client. This operation represents
|
||||
a significant performance hit when compared with single node version of
|
||||
Memgraph.
|
||||
|
||||
Luckily, the algorithm can be tweaked in a way which allows read-only
|
||||
transactions to perform significantly better than those which modify the
|
||||
database state. That being said, the performance of read-only operations
|
||||
is still not going to be on par with single node Memgraph.
|
||||
|
||||
This section will be updated with exact numbers once we integrate HA with
|
||||
new storage.
|
||||
|
||||
With the old storage, write throughput was almost five times lower than read
|
||||
throughput (~30000 reads per second vs ~6000 writes per second).
|
||||
|
||||
## User Facing Setup
|
||||
|
||||
### How to Setup HA Memgraph Cluster?
|
||||
|
||||
First, the user needs to install `memgraph_ha` package on each machine
|
||||
in their cluster. HA Memgraph should be available as a Debian package,
|
||||
so its installation on each machine should be as simple as:
|
||||
|
||||
```plaintext
|
||||
dpkg -i /path/to/memgraph_ha_<version>.deb
|
||||
```
|
||||
|
||||
After successful installation of the `memgraph_ha` package, the user should
|
||||
finish its configuration before attempting to start the cluster.
|
||||
|
||||
There are two main things that need to be configured on every node in order for
|
||||
the cluster to be able to run:
|
||||
|
||||
1. The user has to edit the main configuration file and specify the unique node
|
||||
ID to each server in the cluster
|
||||
2. The user has to create a file that describes all IP addresses of all servers
|
||||
that will be used in the cluster
|
||||
|
||||
The `memgraph_ha` binary loads all main configuration parameters from
|
||||
`/etc/memgraph/memgraph_ha.conf`. On each node of the cluster, the user should
|
||||
uncomment the `--server-id=0` parameter and change its value to the `server_id`
|
||||
of that node.
|
||||
|
||||
The last step before starting the server is to create a `coordination`
|
||||
configuration file. That file is already present as an example in
|
||||
`/etc/memgraph/coordination.json.example` and you have to copy it to
|
||||
`/etc/memgraph/coordination.json` and edit it according to your cluster
|
||||
configuration. The file contains coordination info consisting of a list of
|
||||
`server_id`, `ip_address` and `rpc_port` lists. The assumed contents of the
|
||||
`coordination.json` file are:
|
||||
|
||||
```plaintext
|
||||
[
|
||||
[1, "192.168.0.1", 10000],
|
||||
[2, "192.168.0.2", 10000],
|
||||
[3, "192.168.0.3", 10000]
|
||||
]
|
||||
```
|
||||
Here, each line corresponds to coordination of one server. The first entry is
|
||||
that server's ID, the second is its IP address and the third is the RPC port it
|
||||
listens to. This port should not be confused with the port used for client
|
||||
interaction via the Bolt protocol.
|
||||
|
||||
The `ip_address` entered for each `server_id` *must* match the exact IP address
|
||||
that belongs to that server and that will be used to communicate to other nodes
|
||||
in the cluster. The coordination configuration file *must* be identical on all
|
||||
nodes in the cluster.
|
||||
|
||||
After the user has set the `server_id` on each node in
|
||||
`/etc/memgraph/memgraph_ha.conf` and provided the same
|
||||
`/etc/memgraph/coordination.json` file to each node in the cluster, they can
|
||||
start the Memgraph HA service by issuing the following command on each node in
|
||||
the cluster:
|
||||
|
||||
```plaintext
|
||||
systemctl start memgraph_ha
|
||||
```
|
||||
|
||||
### How to Configure Raft Parameters?
|
||||
|
||||
All Raft configuration parameters can be controlled by modifying
|
||||
`/etc/memgraph/raft.json`. The assumed contents of the `raft.json` file are:
|
||||
|
||||
```plaintext
|
||||
{
|
||||
"election_timeout_min": 750,
|
||||
"election_timeout_max": 1000,
|
||||
"heartbeat_interval": 100,
|
||||
"replication_timeout": 20000,
|
||||
"log_size_snapshot_threshold": 50000
|
||||
}
|
||||
```
|
||||
|
||||
The meaning behind each entry is demystified in the following table:
|
||||
|
||||
Flag | Description
|
||||
------------------------------|------------
|
||||
`election_timeout_min` | Lower bound for the randomly sampled reelection timer given in milliseconds
|
||||
`election_timeout_max` | Upper bound for the randomly sampled reelection timer given in milliseconds
|
||||
`heartbeat_interval` | Time interval between consecutive heartbeats given in milliseconds
|
||||
`replication_timeout` | Time interval allowed for data replication given in milliseconds
|
||||
`log_size_snapshot_threshold` | Allowed number of entries in Raft log before its compaction
|
||||
|
||||
### How to Query HA Memgraph via Proxy?
|
||||
|
||||
This chapter describes how to query HA Memgraph using our proxy server.
|
||||
Note that this is not intended to be a long-term solution. Instead, we will
|
||||
implement a proper Memgraph HA client which is capable of communicating with
|
||||
the HA cluster. Once our own client is implemented, it will no longer be
|
||||
possible to query HA Memgraph using other clients (such as neo4j client).
|
||||
|
||||
The Bolt protocol that is exposed by each Memgraph HA node is an extended
|
||||
version of the standard Bolt protocol. In order to be able to communicate with
|
||||
the highly available cluster of Memgraph HA nodes, the client must have some
|
||||
logic implemented in itself so that it can communicate correctly with all nodes
|
||||
in the cluster. To facilitate a faster start with the HA cluster we will build
|
||||
the Memgraph HA proxy binary that communicates with all nodes in the HA cluster
|
||||
using the extended Bolt protocol and itself exposes a standard Bolt protocol to
|
||||
the user. All standard Bolt clients (libraries and custom systems) can
|
||||
communicate with the Memgraph HA proxy without any code modifications.
|
||||
|
||||
The HA proxy should be deployed on each client machine that is used to
|
||||
communicate with the cluster. It can't be deployed on the Memgraph HA nodes!
|
||||
|
||||
When using the Memgraph HA proxy, the communication flow is described in the
|
||||
following diagram:
|
||||
|
||||
```plaintext
|
||||
Memgraph HA node 1 -----+
|
||||
|
|
||||
Memgraph HA node 2 -----+ Memgraph HA proxy <---> any standard Bolt client (C, Java, PHP, Python, etc.)
|
||||
|
|
||||
Memgraph HA node 3 -----+
|
||||
```
|
||||
|
||||
To setup the Memgraph HA proxy the user should install the `memgraph_ha_proxy`
|
||||
package.
|
||||
|
||||
After its successful installation, the user should enter all endpoints of the
|
||||
HA Memgraph cluster servers into the configuration before attempting to start
|
||||
the HA Memgraph proxy server.
|
||||
|
||||
The HA Memgraph proxy server loads all of its configuration from
|
||||
`/etc/memgraph/memgraph_ha_proxy.conf`. Assuming that the cluster is set up
|
||||
like in the previous examples, the user should uncomment and enter the following
|
||||
value into the `--endpoints` parameter:
|
||||
|
||||
```plaintext
|
||||
--endpoints=192.168.0.1:7687,192.168.0.2:7687,192.168.0.3:7687
|
||||
```
|
||||
|
||||
Note that the IP addresses used in the example match the individual cluster
|
||||
nodes IP addresses, but the ports used are the Bolt server ports exposed by
|
||||
each node (currently the default value of `7687`).
|
||||
|
||||
The user can now start the proxy by using the following command:
|
||||
|
||||
```plaintext
|
||||
systemctl start memgraph_ha_proxy
|
||||
```
|
||||
|
||||
After the proxy has been started, the user can query the HA cluster by
|
||||
connecting to the HA Memgraph proxy IP address using their favorite Bolt
|
||||
client.
|
||||
|
||||
## Integration with Memgraph
|
||||
|
||||
The first thing that should be defined is a single instruction within the
|
||||
context of Raft (i.e. a single entry in a replicated log).
|
||||
These instructions should be completely deterministic when applied
|
||||
to the state machine. We have therefore decided that the appropriate level
|
||||
of abstraction within Memgraph corresponds to `Delta`s (data structures
|
||||
which describe a single change to the Memgraph state, used for durability
|
||||
in WAL). Moreover, a single instruction in a replicated log will consist of a
|
||||
batch of `Delta`s which correspond to a single transaction that's about
|
||||
to be **committed**.
|
||||
|
||||
Apart from `Delta`s, there are certain operations within the storage called
|
||||
`StorageGlobalOperations` which do not conform to usual transactional workflow
|
||||
(e.g. Creating indices). Since our storage engine implementation guarantees
|
||||
that at the moment of their execution no other transactions are active, we can
|
||||
safely replicate them as well. In other words, no additional logic needs to be
|
||||
implemented because of them.
|
||||
|
||||
Therefore, we will introduce a new `RaftDelta` object which can be constructed
|
||||
both from storage `Delta` and `StorageGlobalOperation`. Instead of appending
|
||||
these to WAL (as we do in single node), we will start to replicate them across
|
||||
our cluster. Once we have replicated the corresponding Raft log entry on
|
||||
majority of the cluster, we are able to safely commit the transaction or execute
|
||||
a global operation. If for any reason the replication fails (leadership change,
|
||||
worker failures, etc.) the transaction will be aborted.
|
||||
|
||||
In the follower mode, we need to be able to apply `RaftDelta`s we got from
|
||||
the leader when the protocol allows us to do so. In that case, we will use the
|
||||
same concepts from durability in storage v2, i.e., applying deltas maps
|
||||
completely to recovery from WAL in storage v2.
|
||||
|
||||
## Test and Benchmark Strategy
|
||||
|
||||
We have already implemented some integration and stress tests. These are:
|
||||
|
||||
1. leader election -- Tests whether leader election works properly.
|
||||
2. basic test -- Tests basic leader election and log replication.
|
||||
3. term updates test -- Tests a specific corner case (which used to fail)
|
||||
regarding term updates.
|
||||
4. log compaction test -- Tests whether log compaction works properly.
|
||||
5. large log entries -- Tests whether we can successfully replicate relatively
|
||||
large log entries.
|
||||
6. index test -- Tests whether index creation works in HA.
|
||||
7. normal operation stress test -- Long running concurrent stress test under
|
||||
normal conditions (no failures).
|
||||
8. read benchmark -- Measures read throughput in HA.
|
||||
9. write benchmark -- Measures write throughput in HA.
|
||||
|
||||
At the moment, our main goal is to pass existing tests and have a stable version
|
||||
on our stress test. We should also implement a stress test which occasionally
|
||||
introduces different types of failures in our cluster (we did this kind of
|
||||
testing manually thus far). Passing these tests should convince us that we have
|
||||
a "stable enough" version which we can start pushing to our customers.
|
||||
|
||||
Additional (proper) testing should probably involve some ideas from
|
||||
[here](https://jepsen.io/analyses/dgraph-1-0-2)
|
||||
|
||||
## Possible Future Changes/Improvements/Extensions
|
||||
|
||||
There are two general directions in which we can alter HA Memgraph. The first
|
||||
direction assumes we are going to stick with the Raft protocol. In that case
|
||||
there are a few known ways to extend the basic algorithm in order to gain
|
||||
better performance or achieve extra functionality. In no particular order,
|
||||
these are:
|
||||
|
||||
1. Improving read performance using leader leases [Section 6.4 from Raft thesis]
|
||||
2. Introducing cluster membership changes [Chapter 4 from Raft thesis]
|
||||
3. Introducing a [learner mode](https://etcd.io/docs/v3.3.12/learning/learner/).
|
||||
4. Consider different log compaction strategies [Chapter 5 from Raft thesis]
|
||||
5. Removing HA proxy and implementing our own HA Memgraph client.
|
||||
|
||||
On the other hand, we might decide in the future to base our HA implementation
|
||||
on a completely different protocol which might even offer different guarantees.
|
||||
In that case we probably need to do a bit more of market research and weigh the
|
||||
trade-offs of different solutions.
|
||||
[This](https://www.postgresql.org/docs/9.5/different-replication-solutions.html)
|
||||
might be a good starting point.
|
||||
|
||||
## Reading materials
|
||||
|
||||
1. [Raft paper](https://raft.github.io/raft.pdf)
|
||||
2. [Raft thesis](https://github.com/ongardie/dissertation) (book.pdf)
|
||||
3. [Raft playground](https://raft.github.io/)
|
||||
4. [Leader Leases](https://blog.yugabyte.com/low-latency-reads-in-geo-distributed-sql-with-raft-leader-leases/)
|
||||
5. [Improving Raft ETH](https://pub.tik.ee.ethz.ch/students/2017-FS/SA-2017-80.pdf)
|
||||
@@ -1,80 +0,0 @@
|
||||
# Kafka - openCypher clause
|
||||
|
||||
One must be able to specify the following when importing data from Kafka:
|
||||
|
||||
* Kafka URI
|
||||
* Kafka topic
|
||||
* Transform [script](transform.md) URI
|
||||
|
||||
|
||||
Minimum required syntax looks like:
|
||||
```opencypher
|
||||
CREATE STREAM stream_name AS LOAD DATA KAFKA 'URI'
|
||||
WITH TOPIC 'topic'
|
||||
WITH TRANSFORM 'URI';
|
||||
```
|
||||
|
||||
|
||||
The full openCypher clause for creating a stream is:
|
||||
```opencypher
|
||||
CREATE STREAM stream_name AS
|
||||
LOAD DATA KAFKA 'URI'
|
||||
WITH TOPIC 'topic'
|
||||
WITH TRANSFORM 'URI'
|
||||
[BATCH_INTERVAL milliseconds]
|
||||
[BATCH_SIZE count]
|
||||
```
|
||||
The `CREATE STREAM` clause happens in a transaction.
|
||||
|
||||
`WITH TOPIC` parameter specifies the Kafka topic from which we'll stream
|
||||
data.
|
||||
|
||||
`WITH TRANSFORM` parameter should contain a URI of the transform script.
|
||||
|
||||
`BATCH_INTERVAL` parameter defines the time interval in milliseconds
|
||||
which is the time between two successive stream importing operations.
|
||||
|
||||
`BATCH_SIZE` parameter defines the count of Kafka messages that will be
|
||||
batched together before import.
|
||||
|
||||
If both `BATCH_INTERVAL` and `BATCH_SIZE` parameters are given, the condition
|
||||
that is satisfied first will trigger the batched import.
|
||||
|
||||
Default value for `BATCH_INTERVAL` is 100 milliseconds, and the default value
|
||||
for `BATCH_SIZE` is 10;
|
||||
|
||||
The `DROP` clause deletes a stream:
|
||||
```opencypher
|
||||
DROP STREAM stream_name;
|
||||
```
|
||||
|
||||
The `SHOW` clause enables you to see all configured streams:
|
||||
```opencypher
|
||||
SHOW STREAMS;
|
||||
```
|
||||
|
||||
You can also start/stop streams with the `START` and `STOP` clauses:
|
||||
```opencypher
|
||||
START STREAM stream_name [LIMIT count BATCHES];
|
||||
STOP STREAM stream_name;
|
||||
```
|
||||
A stream needs to be stopped in order to start it and it needs to be started in
|
||||
order to stop it. Starting a started or stopping a stopped stream will not
|
||||
affect that stream.
|
||||
|
||||
There are also convenience clauses to start and stop all streams:
|
||||
```opencypher
|
||||
START ALL STREAMS;
|
||||
STOP ALL STREAMS;
|
||||
```
|
||||
|
||||
Before the actual import, you can also test the stream with the `TEST
|
||||
STREAM` clause:
|
||||
```opencypher
|
||||
TEST STREAM stream_name [LIMIT count BATCHES];
|
||||
```
|
||||
When a stream is tested, data extraction and transformation occurs, but no
|
||||
output is inserted in the graph.
|
||||
|
||||
A stream needs to be stopped in order to test it. When the batch limit is
|
||||
omitted, `TEST STREAM` will run for only one batch by default.
|
||||
@@ -1,34 +0,0 @@
|
||||
# Kafka - data transform
|
||||
|
||||
The transform script is a user defined script written in Python. The script
|
||||
should be aware of the data format in the Kafka message.
|
||||
|
||||
Each Kafka message is byte length encoded, which means that the first eight
|
||||
bytes of each message contain the length of the message.
|
||||
|
||||
A sample code for a streaming transform script could look like this:
|
||||
|
||||
```python
|
||||
def create_vertex(vertex_id):
|
||||
return ("CREATE (:Node {id: $id})", {"id": vertex_id})
|
||||
|
||||
|
||||
def create_edge(from_id, to_id):
|
||||
return ("MATCH (n:Node {id: $from_id}), (m:Node {id: $to_id}) "\
|
||||
"CREATE (n)-[:Edge]->(m)", {"from_id": from_id, "to_id": to_id})
|
||||
|
||||
|
||||
def stream(batch):
|
||||
result = []
|
||||
for item in batch:
|
||||
message = item.decode('utf-8').strip().split()
|
||||
if len(message) == 1:
|
||||
result.append(create_vertex(message[0])))
|
||||
else:
|
||||
result.append(create_edge(message[0], message[1]))
|
||||
return result
|
||||
|
||||
```
|
||||
|
||||
The script should output openCypher query strings based on the type of the
|
||||
records.
|
||||
@@ -1,185 +0,0 @@
|
||||
# Python 3 Query Modules
|
||||
|
||||
## Introduction
|
||||
|
||||
Memgraph exposes a C API for writing the so called Query Modules. These
|
||||
modules contain definitions of procedures which can be invoked through the
|
||||
query language using the `CALL ... YIELD ...` syntax. This mechanism allows
|
||||
database users to extend Memgraph with their own algorithms and
|
||||
functionalities.
|
||||
|
||||
Using a low level language like C can be quite cumbersome for writing modules,
|
||||
so it seems natural to add support for a higher level language on top of the
|
||||
existing C API.
|
||||
|
||||
There are languages written exactly for this purpose of extending C with high
|
||||
level constructs, for example Lua and Guile. Instead of those, we have chosen
|
||||
Python 3 to be the first high level language we will support. The primary reason
|
||||
being that it's very popular, so more people should be able to write modules.
|
||||
Another benefit of Python which comes out of its popularity is the large
|
||||
ecosystem of libraries, especially graph algorithm related ones like NetworkX.
|
||||
Python does have significant performance and implementation downsides compared
|
||||
to Lua and Guile, but these are described in more detail later in this
|
||||
document.
|
||||
|
||||
## Python 3 API Overview
|
||||
|
||||
The Python 3 API should be as user friendly as possible as well as look
|
||||
Pythonic. This implies that some functions from the C API will not map to the
|
||||
exact same functions. The most obvious case for a Pythonic approach is
|
||||
registering procedures of a query module. Let's take a look at the C example
|
||||
and its transformation to Python.
|
||||
|
||||
```c
|
||||
static void procedure(const struct mgp_list *args,
|
||||
const struct mgp_graph *graph, struct mgp_result *result,
|
||||
struct mgp_memory *memory);
|
||||
|
||||
int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) {
|
||||
struct mgp_proc *proc =
|
||||
mgp_module_add_read_procedure(module, "procedure", procedure);
|
||||
if (!proc) return 1;
|
||||
if (!mgp_proc_add_arg(proc, "required_arg",
|
||||
mgp_type_nullable(mgp_type_any())))
|
||||
return 1;
|
||||
struct mgp_value *null_value = mgp_value_make_null(memory);
|
||||
if (!mgp_proc_add_opt_arg(proc, "optional_arg",
|
||||
mgp_type_nullable(mgp_type_any()), null_value)) {
|
||||
mgp_value_destroy(null_value);
|
||||
return 1;
|
||||
}
|
||||
mgp_value_destroy(null_value);
|
||||
if (!mgp_proc_add_result(proc, "result", mgp_type_string())) return 1;
|
||||
if (!mgp_proc_add_result(proc, "args",
|
||||
mgp_type_list(mgp_type_nullable(mgp_type_any()))))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
In Python things should be a lot simpler.
|
||||
|
||||
```Python
|
||||
# mgp.read_proc obtains the procedure name via __name__ attribute of a function.
|
||||
@mgp.read_proc(# Arguments passed to multiple mgp_proc_add_arg calls
|
||||
(('required_arg', mgp.Nullable(mgp.Any)), ('optional_arg', mgp.Nullable(mgp.Any), None)),
|
||||
# Result fields passed to multiple mgp_proc_add_result calls
|
||||
(('result', str), ('args', mgp.List(mgp.Nullable(mgp.Any)))))
|
||||
def procedure(args, graph, result, memory):
|
||||
pass
|
||||
```
|
||||
|
||||
Here we have replaced `mgp_module_*` and `mgp_proc_*` C API with a much
|
||||
simpler decorator function in Python -- `mgp.read_proc`. The types of
|
||||
arguments and result fields can both be our types as well as Python builtin
|
||||
types which can map to supported `mgp_value` types. The expected builtin types
|
||||
we ought to support are: `bool`, `str`, `int`, `float` and `map`. While the
|
||||
rest of the types are provided via our Python API. Optionally, we can add
|
||||
convenience support for `object` type which would map to
|
||||
`mgp.Nullable(mgp.Any)` and `list` which would map to
|
||||
`mgp.List(mgp.Nullable(mgp.Any))`. Also, it makes sense to take a look if we
|
||||
can leverage Python's `typing` module here.
|
||||
|
||||
Another Pythonic change is to remove `mgp_value` C API from Python altogether.
|
||||
This means that the arguments a Python procedure receives are not `mgp_value`
|
||||
instances but rather `PyObject` instances. In other words, our implementation
|
||||
would immediately marshal `mgp_value` to corresponding type in Python.
|
||||
Obviously we would need to provide our own Python types for non-builtin
|
||||
things like `mgp.Vertex` (equivalent to `mgp_vertex`) and other.
|
||||
|
||||
Continuing from our example above, let's say the procedure was invoked through
|
||||
Cypher using the following query.
|
||||
|
||||
MATCH (n) CALL py_module.procedure(42, n) YIELD *;
|
||||
|
||||
The Python procedure could then do the following and complete without throwing
|
||||
neither the AssertionError nor the ValueError.
|
||||
|
||||
```Python
|
||||
def procedure(args, graph, result, memory):
|
||||
assert isinstance(args, list)
|
||||
# Unpacking throws ValueError if args does not contain exactly 2 values.
|
||||
required_arg, optional_arg = args
|
||||
assert isintance(required_arg, int)
|
||||
assert isinstance(optional_arg, mgp.Vertex)
|
||||
```
|
||||
|
||||
The rest of the C API should naturally map to either top level functions or
|
||||
class methods as appropriate.
|
||||
|
||||
## Loading Python Query Modules
|
||||
|
||||
Our current mechanism for loading the modules is to look for `.so` files in
|
||||
the directory specified by `--query-modules` flag. This is done when Memgraph
|
||||
is started. We can extend this mechanism to look for `.py` files in addition
|
||||
to `.so` files in the same directory and import them in the embedded Python
|
||||
interpreter. The only issue is embedding the interpreter in Memgraph. There
|
||||
are multiple choices:
|
||||
|
||||
1. Building Memgraph and statically linking to Python.
|
||||
2. Building Memgraph and dynamically linking to Python, and distributing
|
||||
Python with Memgraph's installation.
|
||||
3. Building Memgraph and dynamically linking to Python, but without
|
||||
distributing the Python library.
|
||||
4. Building Memgraph and optionally loading Python library by trying to
|
||||
`dlopen` it.
|
||||
|
||||
The first two options are only viable if the Python license allows, and this
|
||||
will need further investigation.
|
||||
|
||||
The third option adds Python as an installation dependency for Memgraph, and
|
||||
without it Memgraph will not run. This is problematic for users which cannot
|
||||
or do not want to install Python 3.
|
||||
|
||||
The fourth option avoids all of the issues present in the first 3 options, but
|
||||
comes at a higher implementation cost. We would need to try to `dlopen` the
|
||||
Python library and setup function pointers. If we succeed we would import
|
||||
`.py` files from the `--query-modules` directory. On the other hand, if the
|
||||
user does not have Python, `dlopen` would fail and Memgraph would run without
|
||||
Python support.
|
||||
|
||||
After live discussion, we've decided to go with option 3. This way we don't
|
||||
have to worry about mismatching Python versions we support and what the users
|
||||
expect. Also, we should target Python 3.5 as that should be common between
|
||||
Debian and CentOS for which we ship installation packages.
|
||||
|
||||
## Performance and Implementation Problems
|
||||
|
||||
As previously mentioned, embedding Python introduces usability issues compared
|
||||
to other embeddable languages.
|
||||
|
||||
The first, major issue is Global Interpreter Lock (GIL). Initializing Python
|
||||
will start a single global interpreter and running multiple threads will
|
||||
require acquiring GIL. In practice, this means that when multiple users run a
|
||||
procedure written in Python in parallel the execution will not actually be
|
||||
parallel. Python's interpreter will jump between executing one user's
|
||||
procedure and the other's. This can be quite an issue for long running
|
||||
procedures when multiple users are querying Memgraph. The solution for this
|
||||
issue is Python's API for sub-interpreters. Unfortunately, the support for
|
||||
them is rather poor and the API contains a lot of critical bugs when we tried
|
||||
to use them. For the time being, we will have to accept GIL and its downsides.
|
||||
Perhaps in the future we will gain more knowledge on how we could reduce the
|
||||
acquire rate of GIL or the sub-interpreter API will get improved.
|
||||
|
||||
Another major issue is memory allocation. Python's C API does not have support
|
||||
for setting up a temporary allocator during execution of a single function.
|
||||
It only has support for setting up a global heap allocator. This obviously
|
||||
impacts our control of memory during a query procedure invocation. Besides
|
||||
potential performance penalty, a procedure could allocate much more memory
|
||||
than we would actually allow for execution of a single query. This means that
|
||||
options controlling the memory limit during query execution are useless. On
|
||||
the bright side, Python does use block style allocators and reference
|
||||
counting, so the performance penalty and global memory usage should not be
|
||||
that terrible.
|
||||
|
||||
The final issue that isn't as major as the ones above is the global state of
|
||||
the interpreter. In practice this means that any registered procedure and
|
||||
imported module has access to any other procedure and module. This may pollute
|
||||
the namespace for other users, but it should not be much of a problem because
|
||||
Python always has things under a module scope. The other, slightly bigger
|
||||
downside is that a malicious user could use this knowledge to modify other
|
||||
modules and procedures. This seems like a major issue, but if we take the
|
||||
bigger picture into consideration, we already have a security issue in general
|
||||
by invoking `dlopen` on `.so` and potentially running arbitrary code. This was
|
||||
the trade off we chose to allow users to extend Memgraph. It's up to the users
|
||||
to write sane extensions and protect their servers from access.
|
||||
@@ -1,61 +0,0 @@
|
||||
# Tensorflow Op - Technicalities
|
||||
|
||||
The final result should be a shared object (".so") file that can be
|
||||
dynamically loaded by the Tensorflow runtime in order to directly
|
||||
access the bolt client.
|
||||
|
||||
## About Tensorflow
|
||||
|
||||
Tensorflow is usually used with Python such that the Python code is used
|
||||
to define a directed acyclic computation graph. Basically no computation
|
||||
is done in Python. Instead, values from Python are copied into the graph
|
||||
structure as constants to be used by other Ops. The directed acyclic graph
|
||||
naturally ends up with two sets of border nodes, one for inputs, one for
|
||||
outputs. These are sometimes called "feeds".
|
||||
|
||||
Following the Python definition of the graph, during training, the entire
|
||||
data processing graph/pipeline is called from Python as a single expression.
|
||||
This leads to lazy evaluation since the called result has already been
|
||||
defined for a while.
|
||||
|
||||
Tensorflow internally works with tensors, i.e. n-dimensional arrays. That
|
||||
means all of its inputs need to be matrices as well as its outputs. While
|
||||
it is possible to feed data directly from Python's numpy matrices straight
|
||||
into Tensorflow, this is less desirable than using the Tensorflow data API
|
||||
(which defines data input and processing as a Tensorflow graph) because:
|
||||
|
||||
1. The data API is written in C++ and entirely avoids Python and as such
|
||||
is faster
|
||||
2. The data API, unlike Python is available in "Tensorflow serving". The
|
||||
default way to serve Tensorflow models in production.
|
||||
|
||||
Once the entire input pipeline is defined via the tf.data API, its input
|
||||
is basically a list of node IDs the model is supposed to work with. The
|
||||
model, through the data API knows how to connect to Memgraph and execute
|
||||
openCypher queries in order to get the remaining data it needs.
|
||||
(For example features of neighbouring nodes.)
|
||||
|
||||
## The Interface
|
||||
|
||||
I think it's best you read the official guide...
|
||||
<https://www.tensorflow.org/extend/adding_an_op>
|
||||
And especially the addition that specifies how data ops are special
|
||||
<https://www.tensorflow.org/extend/new_data_formats>
|
||||
|
||||
## Compiling the TF Op
|
||||
|
||||
There are two options for compiling a custom op.
|
||||
One of them involves pulling the TF source, adding your code to it and
|
||||
compiling via bazel.
|
||||
This is probably awkward to do for us and would
|
||||
significantly slow down compilation.
|
||||
|
||||
The other method involves installing Tensorflow as a Python package and
|
||||
pulling the required headers from for example:
|
||||
`/usr/local/lib/python3.6/site-packages/tensorflow/include`
|
||||
We can then compile our Op with our regular build system.
|
||||
|
||||
This is practical since we can copy the required headers to our repo.
|
||||
If necessary, we can have several versions of the headers to build several
|
||||
versions of our Op for every TF version which we want to support.
|
||||
(But this is unlikely to be required as the API should be stable).
|
||||
@@ -1,142 +0,0 @@
|
||||
# Example for Using the Bolt Client Tensorflow Op
|
||||
|
||||
## Dynamic Loading
|
||||
|
||||
``` python3
|
||||
import tensorflow as tf
|
||||
|
||||
mg_ops = tf.load_op_library('/usr/bin/memgraph/tensorflow_ops.so')
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
``` python3
|
||||
dataset = mg_ops.OpenCypherDataset(
|
||||
# This is probably unfortunate as the username and password
|
||||
# get hardcoded into the graph, but for the simple case it's fine
|
||||
"hostname:7687", auth=("user", "pass"),
|
||||
|
||||
# Our query
|
||||
'''
|
||||
MATCH (n:Train) RETURN n.id, n.features
|
||||
''',
|
||||
|
||||
# Cast return values to these types
|
||||
(tf.string, tf.float32))
|
||||
|
||||
# Some Tensorflow data api boilerplate
|
||||
iterator = dataset.make_one_shot_iterator()
|
||||
next_element = iterator.get_next()
|
||||
|
||||
# Up to now we have only defined our computation graph which basically
|
||||
# just connects to Memgraph
|
||||
# `next_element` is not really data but a handle to a node in the Tensorflow
|
||||
# graph, which we can and do evaluate
|
||||
# It is a Tensorflow tensor with shape=(None, 2)
|
||||
# and dtype=(tf.string, tf.float)
|
||||
# shape `None` means the shape of the tensor is unknown at definition time
|
||||
# and is dynamic and will only be known once the tensor has been evaluated
|
||||
|
||||
with tf.Session() as sess:
|
||||
node_ids = sess.run(next_element)
|
||||
# `node_ids` contains IDs and features of all the nodes
|
||||
# in the graph with the label "Train"
|
||||
# It is a numpy.ndarray with a shape ($n_matching_nodes, 2)
|
||||
```
|
||||
|
||||
## Memgraph Client as a Generic Tensorflow Op
|
||||
|
||||
Other than the Tensorflow Data Op, we'll want to support a generic Tensorflow
|
||||
Op which can be put anywhere in the Tensorflow computation Graph. It takes in
|
||||
an arbitrary tensor and produces a tensor. This would be used in the GraphSage
|
||||
algorithm to fetch the lowest level features into Tensorflow
|
||||
|
||||
```python3
|
||||
requested_ids = np.array([1, 2, 3])
|
||||
ids_placeholder = tf.placeholder(tf.int32)
|
||||
|
||||
model = mg_ops.OpenCypher()
|
||||
"hostname:7687", auth=("user", "pass"),
|
||||
"""
|
||||
UNWIND $node_ids as nid
|
||||
MATCH (n:Train {id: nid})
|
||||
RETURN n.features
|
||||
""",
|
||||
|
||||
# What to call the input tensor as an openCypher parameter
|
||||
parameter_name="node_ids",
|
||||
|
||||
# Type of our resulting tensor
|
||||
dtype=(tf.float32)
|
||||
)
|
||||
|
||||
features = model(ids_placeholder)
|
||||
|
||||
with tf.Session() as sess:
|
||||
result = sess.run(features,
|
||||
feed_dict={ids_placeholder: requested_ids})
|
||||
```
|
||||
|
||||
This is probably easier to implement than the Data Op, so it might be a good
|
||||
idea to start with.
|
||||
|
||||
## Production Usage
|
||||
|
||||
During training, in the GraphSage algorithm at least, Memgraph is at the
|
||||
beginning and at the end of the Tensorflow computation graph.
|
||||
At the beginning, the Data Op provides the node IDs which are fed into the
|
||||
generic Tensorflow Op to find their neighbours and their neighbours and
|
||||
their features.
|
||||
|
||||
Production usage differs in that we don't use the Data Op. The Data Op is
|
||||
effectively cut off and the initial input is fed by Tensorflow serving,
|
||||
with the data found in the request.
|
||||
|
||||
For example a JSON request to classify a node might look like:
|
||||
|
||||
`POST http://host:port/v1/models/GraphSage/versions/v1:classify`
|
||||
|
||||
With the contents:
|
||||
|
||||
```json
|
||||
{
|
||||
"examples": [
|
||||
{"node_id": 1},
|
||||
{"node_id": 2}
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
Every element of the "examples" list is an example to be computed. Each is
|
||||
represented by a dict with keys matching names of feeds in the Tensorflow
|
||||
graph and values being the values we want fed in for each example
|
||||
|
||||
The REST API then replies in kind with the classification result in JSON
|
||||
|
||||
Note about adding our custom Op to Tensorflow serving.
|
||||
Our Ops .so can be added into the Bazel build to link with Tensorflow serving
|
||||
or it can be dynamically loaded by starting Tensorflow serving with a flag
|
||||
`--custom_op_paths`
|
||||
|
||||
## Considerations
|
||||
|
||||
There might be issues here that the url to connect to Memgraph is
|
||||
hardcoded into the op and would thus be wrong when moved to production,
|
||||
requiring some type of a hack to make work. We probably want to solve
|
||||
this by having the client op take in another tf.Variable as an input
|
||||
which would contain a connection url and username/password.
|
||||
We have to research whether this makes it easy enough to move to
|
||||
production, as the connection string variable is still a part of the
|
||||
graph, but maybe easier to replace.
|
||||
|
||||
It is probably the best idea to utilize openCypher parameters to make
|
||||
our queries flexible. The exact API as to how to declare the parameters
|
||||
in Python is open to discussion.
|
||||
|
||||
The Data Op might not even be necessary to implement as it is not
|
||||
key for production use. It can be replaced in training mode with
|
||||
feed dicts and either
|
||||
|
||||
1. Getting the initial list of nodes via a Python Bolt client
|
||||
2. Creating a separate Tensorflow computation graph that gets all the
|
||||
relevant node IDs into Python
|
||||
@@ -1,22 +0,0 @@
|
||||
# Memgraph LaTeX Beamer Template
|
||||
|
||||
This folder contains all of the needed files for creating a presentation with
|
||||
Memgraph styling. You should use this style for any public presentations.
|
||||
|
||||
Feel free to improve it according to style guidelines and raise issues if you
|
||||
find any.
|
||||
|
||||
## Usage
|
||||
|
||||
Copy the contents of this folder (excluding this README file) to where you
|
||||
want to write your own presentation. After copying, you can start editing the
|
||||
`template.tex` with your content.
|
||||
|
||||
To compile the presentation to a PDF, run `latexmk -pdf -xelatex`. Some
|
||||
directives require XeLaTeX, so you need to pass `-xelatex` as the final option
|
||||
of `latexmk`. You may also need to install some packages if the compilation
|
||||
complains about missing packages.
|
||||
|
||||
To clean up the generated files, use `latexmk -C`. This will also delete the
|
||||
generated PDF. If you wish to remove generated files except the PDF, use
|
||||
`latexmk -c`.
|
||||
@@ -1,82 +0,0 @@
|
||||
\NeedsTeXFormat{LaTeX2e}
|
||||
\ProvidesClass{mg-beamer}[2018/03/26 Memgraph Beamer]
|
||||
|
||||
\DeclareOption*{\PassOptionsToClass{\CurrentOption}{beamer}}
|
||||
|
||||
\ProcessOptions \relax
|
||||
|
||||
\LoadClass{beamer}
|
||||
|
||||
\usetheme{Pittsburgh}
|
||||
|
||||
% Memgraph color palette
|
||||
\definecolor{mg-purple}{HTML}{720096}
|
||||
\definecolor{mg-red}{HTML}{DD2222}
|
||||
\definecolor{mg-orange}{HTML}{FB6E00}
|
||||
\definecolor{mg-yellow}{HTML}{FFC500}
|
||||
\definecolor{mg-gray}{HTML}{857F87}
|
||||
\definecolor{mg-black}{HTML}{231F20}
|
||||
|
||||
\RequirePackage{fontspec}
|
||||
% Title fonts
|
||||
\setbeamerfont{frametitle}{family={\fontspec[Path = ./mg-style/fonts/]{EncodeSansSemiCondensed-Regular.ttf}}}
|
||||
\setbeamerfont{title}{family={\fontspec[Path = ./mg-style/fonts/]{EncodeSansSemiCondensed-Regular.ttf}}}
|
||||
% Body font
|
||||
\RequirePackage[sfdefault,light]{roboto}
|
||||
% Roboto is pretty bad for monospace font. We will find a replacement.
|
||||
% \setmonofont{RobotoMono-Regular.ttf}[Path = ./mg-style/fonts/]
|
||||
|
||||
% Title slide styles
|
||||
% \setbeamerfont{frametitle}{size=\huge}
|
||||
% \setbeamerfont{title}{size=\huge}
|
||||
% \setbeamerfont{date}{size=\tiny}
|
||||
|
||||
% Other typography styles
|
||||
\setbeamertemplate{frametitle}[default][center]
|
||||
\setbeamercolor{frametitle}{fg=mg-black}
|
||||
\setbeamercolor{title}{fg=mg-black}
|
||||
\setbeamercolor{section in toc}{fg=mg-black}
|
||||
\setbeamercolor{local structure}{fg=mg-orange}
|
||||
\setbeamercolor{alert text}{fg=mg-red}
|
||||
|
||||
% Commands
|
||||
\newcommand{\mgalert}[1]{{\usebeamercolor[fg]{alert text}#1}}
|
||||
\newcommand{\titleframe}{\frame[plain]{\titlepage}}
|
||||
\newcommand{\mgtexttt}[1]{{\textcolor{mg-gray}{\texttt{#1}}}}
|
||||
|
||||
% Title slide background
|
||||
\RequirePackage{tikz,calc}
|
||||
% Use title-slide-169 if aspect ration is 16:9
|
||||
\pgfdeclareimage[interpolate=true,width=\paperwidth,height=\paperheight]{logo}{mg-style/title-slide-169}
|
||||
\setbeamertemplate{background}{
|
||||
\begin{tikzpicture}
|
||||
\useasboundingbox (0,0) rectangle (\the\paperwidth,\the\paperheight);
|
||||
\pgftext[at=\pgfpoint{0}{0},left,base]{\pgfuseimage{logo}};
|
||||
\ifnum\thepage>1\relax
|
||||
\useasboundingbox (0,0) rectangle (\the\paperwidth,\the\paperheight);
|
||||
\fill[white, opacity=1](0,\the\paperheight)--(\the\paperwidth,\the\paperheight)--(\the\paperwidth,0)--(0,0)--(0,\the\paperheight);
|
||||
\fi
|
||||
\end{tikzpicture}
|
||||
}
|
||||
|
||||
% Footline content
|
||||
\setbeamertemplate{navigation symbols}{}%remove navigation symbols
|
||||
\setbeamertemplate{footline}{
|
||||
\begin{beamercolorbox}[ht=1.6cm,wd=\paperwidth]{footlinecolor}
|
||||
\vspace{0.1cm}
|
||||
\hfill
|
||||
\begin{minipage}[c]{3cm}
|
||||
\begin{center}
|
||||
\includegraphics[height=0.8cm]{mg-style/memgraph-logo.png}
|
||||
\end{center}
|
||||
\end{minipage}
|
||||
\begin{minipage}[c]{7cm}
|
||||
\insertshorttitle\ --- \insertsection
|
||||
\end{minipage}
|
||||
\begin{minipage}[c]{2cm}
|
||||
\tiny{\insertframenumber{} of \inserttotalframenumber}
|
||||
\end{minipage}
|
||||
\end{beamercolorbox}
|
||||
}
|
||||
|
||||
\endinput
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 185 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 189 KiB |
@@ -1,40 +0,0 @@
|
||||
% Set 16:9 aspect ratio
|
||||
\documentclass[aspectratio=169]{mg-beamer}
|
||||
% Default directive sets the regular 4:3 aspect ratio
|
||||
% \documentclass{mg-beamer}
|
||||
\mode<presentation>
|
||||
|
||||
% requires xelatex
|
||||
\usepackage{ccicons}
|
||||
|
||||
\title{Insert Presentation Title}
|
||||
\titlegraphic{\ccbyncnd}
|
||||
\author{Insert Name}
|
||||
|
||||
% Institute doesn't look good in our current styling class.
|
||||
% \institute[Memgraph Ltd.]{\pgfimage[height=1.5cm]{mg-logo.png}}
|
||||
|
||||
% Date is autogenerated on compilation, so no need to set it explicitly,
|
||||
% unless you wish to override it with a different date.
|
||||
% \date{March 23, 2018}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\titleframe
|
||||
|
||||
\section{Intro}
|
||||
|
||||
\begin{frame}{Contents}
|
||||
\tableofcontents
|
||||
\end{frame}
|
||||
|
||||
\begin{frame}{Memgraph Markup Test}
|
||||
\begin{itemize}
|
||||
\item \mgtexttt{Prefer \\mgtexttt for monospace}
|
||||
\item Replace this slide with your own
|
||||
\item Add even more slides in different sections
|
||||
\item Make sure you spellcheck your presentation
|
||||
\end{itemize}
|
||||
\end{frame}
|
||||
|
||||
\end{document}
|
||||
4
environment/README.md
Normal file
4
environment/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Memgraph Build and Run Environments
|
||||
|
||||
Please continue in
|
||||
[Notion](https://www.notion.so/memgraph/Tools-05e0baafb78a49b386e0063b4833d23d).
|
||||
3
environment/os/.gitignore
vendored
Normal file
3
environment/os/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*.deb
|
||||
*.rpm
|
||||
*.tar.gz
|
||||
134
environment/os/centos-7.sh
Executable file
134
environment/os/centos-7.sh
Executable file
@@ -0,0 +1,134 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc 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 libipt-devel libbabeltrace-devel xz-devel python3-devel # gdb
|
||||
texinfo # gdb
|
||||
libcurl-devel # cmake
|
||||
readline-devel # cmake and llvm
|
||||
libffi-devel libxml2-devel perl-Digest-MD5 # llvm
|
||||
libedit-devel pcre-devel automake bison # swig
|
||||
)
|
||||
|
||||
TOOLCHAIN_RUN_DEPS=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz # used for archive unpacking
|
||||
zlib # zlib library used for all builds
|
||||
expat libipt libbabeltrace xz-libs python3 # for gdb
|
||||
readline # for cmake and llvm
|
||||
libffi libxml2 # for llvm
|
||||
)
|
||||
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
git # source code control
|
||||
make pkgconfig # build system
|
||||
curl 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 python-virtualenv python3-pip nmap-ncat # for qa, macro_benchmark and stress tests
|
||||
#
|
||||
# IMPORTANT: python3-yaml does NOT exist on CentOS
|
||||
# Install it using `pip3 install PyYAML`
|
||||
#
|
||||
PyYAML # Package name here does not correspond to the yum package!
|
||||
libcurl-devel # mg-requests
|
||||
sbcl # for custom Lisp C++ preprocessing
|
||||
rpm-build rpmlint # for RPM package building
|
||||
doxygen graphviz # source documentation generators
|
||||
which mono-complete dotnet-sdk-3.1 golang nodejs zip unzip java-11-openjdk-devel # for driver tests
|
||||
autoconf # for jemalloc code generation
|
||||
)
|
||||
|
||||
list() {
|
||||
echo "$1"
|
||||
}
|
||||
|
||||
check() {
|
||||
local missing=""
|
||||
for pkg in $1; do
|
||||
if [ "$pkg" == git ]; then
|
||||
if ! which "git" >/dev/null; then
|
||||
missing="git $missing"
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if [ "$pkg" == "PyYAML" ]; then
|
||||
if ! python3 -c "import yaml" >/dev/null 2>/dev/null; then
|
||||
missing="$pkg $missing"
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if ! yum list installed "$pkg" >/dev/null 2>/dev/null; then
|
||||
missing="$pkg $missing"
|
||||
fi
|
||||
done
|
||||
if [ "$missing" != "" ]; then
|
||||
echo "MISSING PACKAGES: $missing"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
install() {
|
||||
cd "$DIR"
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run as root."
|
||||
exit 1
|
||||
fi
|
||||
# If GitHub Actions runner is installed, append LANG to the environment.
|
||||
# Python related tests 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
|
||||
yum install -y epel-release
|
||||
yum update -y
|
||||
yum install -y wget git python3 python3-pip
|
||||
for pkg in $1; do
|
||||
if [ "$pkg" == libipt ]; then
|
||||
if ! yum list installed libipt >/dev/null 2>/dev/null; then
|
||||
yum install -y http://repo.okay.com.mx/centos/8/x86_64/release/libipt-1.6.1-8.el8.x86_64.rpm
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if [ "$pkg" == libipt-devel ]; then
|
||||
if ! yum list installed libipt-devel >/dev/null 2>/dev/null; then
|
||||
yum install -y http://repo.okay.com.mx/centos/8/x86_64/release/libipt-devel-1.6.1-8.el8.x86_64.rpm
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if [ "$pkg" == dotnet-sdk-3.1 ]; then
|
||||
if ! yum list installed dotnet-sdk-3.1 >/dev/null 2>/dev/null; then
|
||||
wget -nv https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm -O packages-microsoft-prod.rpm
|
||||
rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm
|
||||
yum update -y
|
||||
yum install -y dotnet-sdk-3.1
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if [ "$pkg" == PyYAML ]; then
|
||||
if [ -z ${SUDO_USER+x} ]; then # Running as root (e.g. Docker).
|
||||
pip3 install --user PyYAML
|
||||
else # Running using sudo.
|
||||
sudo -H -u "$SUDO_USER" bash -c "pip3 install --user PyYAML"
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
yum install -y "$pkg"
|
||||
done
|
||||
}
|
||||
|
||||
deps=$2"[*]"
|
||||
"$1" "${!deps}"
|
||||
152
environment/os/centos-8.sh
Executable file
152
environment/os/centos-8.sh
Executable file
@@ -0,0 +1,152 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc 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 libipt-devel libbabeltrace-devel xz-devel python36-devel texinfo # for gdb
|
||||
libcurl-devel # for cmake
|
||||
readline-devel # for cmake and llvm
|
||||
libffi-devel libxml2-devel # for llvm
|
||||
libedit-devel pcre-devel automake bison # for swig
|
||||
)
|
||||
|
||||
TOOLCHAIN_RUN_DEPS=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz # used for archive unpacking
|
||||
zlib # zlib library used for all builds
|
||||
expat libipt libbabeltrace xz-libs python36 # for gdb
|
||||
readline # for cmake and llvm
|
||||
libffi libxml2 # for llvm
|
||||
)
|
||||
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
git # source code control
|
||||
make pkgconf-pkg-config # build system
|
||||
curl wget # for downloading libs
|
||||
libuuid-devel java-11-openjdk # required by antlr
|
||||
readline-devel # for memgraph console
|
||||
python36-devel # for query modules
|
||||
openssl-devel
|
||||
libseccomp-devel
|
||||
python36 python3-virtualenv python3-pip nmap-ncat # for qa, macro_benchmark and stress tests
|
||||
#
|
||||
# IMPORTANT: python3-yaml does NOT exist on CentOS
|
||||
# Install it manually using `pip3 install PyYAML`
|
||||
#
|
||||
PyYAML # Package name here does not correspond to the yum package!
|
||||
libcurl-devel # mg-requests
|
||||
rpm-build rpmlint # for RPM package building
|
||||
doxygen graphviz # source documentation generators
|
||||
which mono-complete dotnet-sdk-3.1 nodejs golang zip unzip java-11-openjdk-devel # for driver tests
|
||||
sbcl # for custom Lisp C++ preprocessing
|
||||
autoconf # for jemalloc code generation
|
||||
)
|
||||
|
||||
list() {
|
||||
echo "$1"
|
||||
}
|
||||
|
||||
check() {
|
||||
local missing=""
|
||||
for pkg in $1; do
|
||||
if [ "$pkg" == "PyYAML" ]; then
|
||||
if ! python3 -c "import yaml" >/dev/null 2>/dev/null; then
|
||||
missing="$pkg $missing"
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if ! yum list installed "$pkg" >/dev/null 2>/dev/null; then
|
||||
missing="$pkg $missing"
|
||||
fi
|
||||
done
|
||||
if [ "$missing" != "" ]; then
|
||||
echo "MISSING PACKAGES: $missing"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
install() {
|
||||
cd "$DIR"
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run as root."
|
||||
exit 1
|
||||
fi
|
||||
# If GitHub Actions runner is installed, append LANG to the environment.
|
||||
# Python related tests 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
|
||||
dnf install -y epel-release
|
||||
dnf install -y 'dnf-command(config-manager)'
|
||||
dnf config-manager --set-enabled powertools # Required to install texinfo.
|
||||
dnf update -y
|
||||
dnf install -y wget git python36 python3-pip
|
||||
for pkg in $1; do
|
||||
if [ "$pkg" == libipt ]; then
|
||||
if ! dnf list installed libipt >/dev/null 2>/dev/null; then
|
||||
dnf install -y http://repo.okay.com.mx/centos/8/x86_64/release/libipt-1.6.1-8.el8.x86_64.rpm
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if [ "$pkg" == libipt-devel ]; then
|
||||
if ! yum list installed libipt-devel >/dev/null 2>/dev/null; then
|
||||
dnf install -y http://repo.okay.com.mx/centos/8/x86_64/release/libipt-devel-1.6.1-8.el8.x86_64.rpm
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
# Install GDB dependencies not present in the standard repos.
|
||||
# https://bugs.centos.org/view.php?id=17068
|
||||
# https://centos.pkgs.org
|
||||
# Since 2020, there is Babeltrace2 (https://babeltrace.org). Not used
|
||||
# within GDB yet (an assumption).
|
||||
if [ "$pkg" == libbabeltrace-devel ]; then
|
||||
if ! dnf list installed libbabeltrace-devel >/dev/null 2>/dev/null; then
|
||||
dnf install -y http://mirror.centos.org/centos/8/PowerTools/x86_64/os/Packages/libbabeltrace-devel-1.5.4-3.el8.x86_64.rpm
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if [ "$pkg" == sbcl ]; then
|
||||
if ! dnf list installed cl-asdf >/dev/null 2>/dev/null; then
|
||||
dnf install -y https://pkgs.dyn.su/el8/base/x86_64/cl-asdf-20101028-18.el8.noarch.rpm
|
||||
fi
|
||||
if ! dnf list installed common-lisp-controller >/dev/null 2>/dev/null; then
|
||||
dnf install -y https://pkgs.dyn.su/el8/base/x86_64/common-lisp-controller-7.4-20.el8.noarch.rpm
|
||||
fi
|
||||
if ! dnf list installed sbcl >/dev/null 2>/dev/null; then
|
||||
dnf install -y https://pkgs.dyn.su/el8/base/x86_64/sbcl-2.0.1-4.el8.x86_64.rpm
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if [ "$pkg" == dotnet-sdk-3.1 ]; then
|
||||
if ! dnf list installed dotnet-sdk-3.1 >/dev/null 2>/dev/null; then
|
||||
wget -nv https://packages.microsoft.com/config/centos/8/packages-microsoft-prod.rpm -O packages-microsoft-prod.rpm
|
||||
rpm -Uvh https://packages.microsoft.com/config/centos/8/packages-microsoft-prod.rpm
|
||||
dnf update -y
|
||||
dnf install -y dotnet-sdk-3.1
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if [ "$pkg" == PyYAML ]; then
|
||||
if [ -z ${SUDO_USER+x} ]; then # Running as root (e.g. Docker).
|
||||
pip3 install --user PyYAML
|
||||
else # Running using sudo.
|
||||
sudo -H -u "$SUDO_USER" bash -c "pip3 install --user PyYAML"
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
dnf install -y "$pkg"
|
||||
done
|
||||
}
|
||||
|
||||
deps=$2"[*]"
|
||||
"$1" "${!deps}"
|
||||
93
environment/os/debian-10.sh
Executable file
93
environment/os/debian-10.sh
Executable file
@@ -0,0 +1,93 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc g++ build-essential make # generic build tools
|
||||
wget # used for archive download
|
||||
gnupg # used for archive signature verification
|
||||
tar gzip bzip2 xz-utils unzip # used for archive unpacking
|
||||
zlib1g-dev # zlib library used for all builds
|
||||
libexpat1-dev libipt-dev libbabeltrace-dev liblzma-dev python3-dev texinfo # for gdb
|
||||
libcurl4-openssl-dev # for cmake
|
||||
libreadline-dev # for cmake and llvm
|
||||
libffi-dev libxml2-dev # for llvm
|
||||
libedit-dev libpcre3-dev automake bison # for swig
|
||||
)
|
||||
|
||||
TOOLCHAIN_RUN_DEPS=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz-utils # used for archive unpacking
|
||||
zlib1g # zlib library used for all builds
|
||||
libexpat1 libipt2 libbabeltrace1 liblzma5 python3 # for gdb
|
||||
libcurl4 # for cmake
|
||||
libreadline7 # for cmake and llvm
|
||||
libffi6 libxml2 # for llvm
|
||||
)
|
||||
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
git # source code control
|
||||
make pkg-config # build system
|
||||
curl wget # for downloading libs
|
||||
uuid-dev default-jre-headless # required by antlr
|
||||
libreadline-dev # for memgraph console
|
||||
libpython3-dev python3-dev # for query modules
|
||||
libssl-dev
|
||||
libseccomp-dev
|
||||
netcat # tests are using nc to wait for memgraph
|
||||
python3 virtualenv python3-virtualenv python3-pip # for qa, macro_benchmark and stress tests
|
||||
python3-yaml # for the configuration generator
|
||||
libcurl4-openssl-dev # mg-requests
|
||||
sbcl # for custom Lisp C++ preprocessing
|
||||
doxygen graphviz # source documentation generators
|
||||
mono-runtime mono-mcs zip unzip default-jdk-headless # for driver tests
|
||||
dotnet-sdk-3.1 golang nodejs npm
|
||||
autoconf # for jemalloc code generation
|
||||
)
|
||||
|
||||
list() {
|
||||
echo "$1"
|
||||
}
|
||||
|
||||
check() {
|
||||
check_all_dpkg "$1"
|
||||
}
|
||||
|
||||
install() {
|
||||
cat >/etc/apt/sources.list <<EOF
|
||||
deb http://deb.debian.org/debian/ buster main non-free contrib
|
||||
deb-src http://deb.debian.org/debian/ buster main non-free contrib
|
||||
deb http://deb.debian.org/debian/ buster-updates main contrib non-free
|
||||
deb-src http://deb.debian.org/debian/ buster-updates main contrib non-free
|
||||
deb http://security.debian.org/debian-security buster/updates main contrib non-free
|
||||
deb-src http://security.debian.org/debian-security buster/updates main contrib non-free
|
||||
EOF
|
||||
cd "$DIR"
|
||||
apt update
|
||||
# If GitHub Actions runner is installed, append LANG to the environment.
|
||||
# Python related tests doesn't work the LANG export.
|
||||
if [ -d "/home/gh/actions-runner" ]; then
|
||||
echo "LANG=en_US.utf8" >> /home/gh/actions-runner/.env
|
||||
else
|
||||
echo "NOTE: export LANG=en_US.utf8"
|
||||
fi
|
||||
apt install -y wget
|
||||
for pkg in $1; do
|
||||
if [ "$pkg" == dotnet-sdk-3.1 ]; then
|
||||
if ! dpkg -s "$pkg" 2>/dev/null >/dev/null; then
|
||||
wget -nv https://packages.microsoft.com/config/debian/10/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
|
||||
dpkg -i packages-microsoft-prod.deb
|
||||
apt-get update
|
||||
apt-get install -y apt-transport-https dotnet-sdk-3.1
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
apt install -y "$pkg"
|
||||
done
|
||||
}
|
||||
|
||||
deps=$2"[*]"
|
||||
"$1" "${!deps}"
|
||||
62
environment/os/debian-9.sh
Executable file
62
environment/os/debian-9.sh
Executable file
@@ -0,0 +1,62 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc g++ build-essential make # generic build tools
|
||||
wget # used for archive download
|
||||
gnupg # used for archive signature verification
|
||||
tar gzip bzip2 xz-utils unzip # used for archive unpacking
|
||||
zlib1g-dev # zlib library used for all builds
|
||||
libexpat1-dev libipt-dev libbabeltrace-dev libbabeltrace-ctf-dev liblzma-dev python3-dev texinfo # for gdb
|
||||
libcurl4-openssl-dev # for cmake
|
||||
libreadline-dev # for cmake and llvm
|
||||
libffi-dev libxml2-dev # for llvm
|
||||
libedit-dev libpcre3-dev automake bison # for swig
|
||||
)
|
||||
|
||||
TOOLCHAIN_RUN_DEPS=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz-utils # used for archive unpacking
|
||||
zlib1g # zlib library used for all builds
|
||||
libexpat1 libipt1 libbabeltrace1 libbabeltrace-ctf1 liblzma5 python3 # for gdb
|
||||
libcurl3 # for cmake
|
||||
libreadline7 # for cmake and llvm
|
||||
libffi6 libxml2 # for llvm
|
||||
)
|
||||
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
git # source code control
|
||||
make pkg-config # build system
|
||||
curl wget # for downloading libs
|
||||
uuid-dev default-jre-headless # required by antlr
|
||||
libreadline-dev # for memgraph console
|
||||
libpython3-dev python3-dev # for query modules
|
||||
libssl-dev
|
||||
libseccomp-dev
|
||||
python3 python-virtualenv python3-pip # for qa, macro_benchmark and stress tests
|
||||
python3-yaml # for the configuration generator
|
||||
libcurl4-openssl-dev # mg-requests
|
||||
sbcl # for custom Lisp C++ preprocessing
|
||||
doxygen graphviz # source documentation generators
|
||||
mono-runtime mono-mcs nodejs zip unzip default-jdk-headless # for driver tests
|
||||
autoconf # for jemalloc code generation
|
||||
)
|
||||
|
||||
list() {
|
||||
echo "$1"
|
||||
}
|
||||
|
||||
check() {
|
||||
check_all_dpkg "$1"
|
||||
}
|
||||
|
||||
install() {
|
||||
install_all_apt "$1"
|
||||
}
|
||||
|
||||
deps=$2"[*]"
|
||||
"$1" "${!deps}"
|
||||
36
environment/os/template.sh
Executable file
36
environment/os/template.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
pkg
|
||||
)
|
||||
|
||||
TOOLCHAIN_RUN_DEPS=(
|
||||
pkg
|
||||
)
|
||||
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
pkg
|
||||
)
|
||||
|
||||
list() {
|
||||
echo "$1"
|
||||
}
|
||||
|
||||
check() {
|
||||
echo "TODO: Implement ${FUNCNAME[0]}."
|
||||
exit 1
|
||||
}
|
||||
|
||||
install() {
|
||||
echo "TODO: Implement ${FUNCNAME[0]}."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# http://ahmed.amayem.com/bash-indirect-expansion-exploration
|
||||
deps=$2"[*]"
|
||||
"$1" "${!deps}"
|
||||
63
environment/os/ubuntu-18.04.sh
Executable file
63
environment/os/ubuntu-18.04.sh
Executable file
@@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc g++ build-essential make # generic build tools
|
||||
wget # archive download
|
||||
gnupg # archive signature verification
|
||||
tar gzip bzip2 xz-utils unzip # archive unpacking
|
||||
zlib1g-dev # zlib library used for all builds
|
||||
libexpat1-dev libipt-dev libbabeltrace-dev liblzma-dev python3-dev # gdb
|
||||
texinfo # gdb
|
||||
libcurl4-openssl-dev # cmake
|
||||
libreadline-dev # cmake and llvm
|
||||
libffi-dev libxml2-dev # llvm
|
||||
libedit-dev libpcre3-dev automake bison # swig
|
||||
)
|
||||
|
||||
TOOLCHAIN_RUN_DEPS=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz-utils # used for archive unpacking
|
||||
zlib1g # zlib library used for all builds
|
||||
libexpat1 libipt1 libbabeltrace1 liblzma5 python3 # for gdb
|
||||
libcurl4 # for cmake
|
||||
libreadline7 # for cmake and llvm
|
||||
libffi6 libxml2 # for llvm
|
||||
)
|
||||
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
git # source code control
|
||||
make pkg-config # build system
|
||||
curl wget # downloading libs
|
||||
uuid-dev default-jre-headless # required by antlr
|
||||
libreadline-dev # memgraph console
|
||||
libpython3-dev python3-dev # for query modules
|
||||
libssl-dev
|
||||
libseccomp-dev
|
||||
python3 virtualenv python3-virtualenv python3-pip # qa, macro bench and stress tests
|
||||
python3-yaml # the configuration generator
|
||||
libcurl4-openssl-dev # mg-requests
|
||||
sbcl # custom Lisp C++ preprocessing
|
||||
doxygen graphviz # source documentation generators
|
||||
mono-runtime mono-mcs nodejs zip unzip default-jdk-headless # driver tests
|
||||
autoconf # for jemalloc code generation
|
||||
)
|
||||
|
||||
list() {
|
||||
echo "$1"
|
||||
}
|
||||
|
||||
check() {
|
||||
check_all_dpkg "$1"
|
||||
}
|
||||
|
||||
install() {
|
||||
apt install -y $1
|
||||
}
|
||||
|
||||
deps=$2"[*]"
|
||||
"$1" "${!deps}"
|
||||
85
environment/os/ubuntu-20.04.sh
Executable file
85
environment/os/ubuntu-20.04.sh
Executable file
@@ -0,0 +1,85 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
source "$DIR/../util.sh"
|
||||
|
||||
TOOLCHAIN_BUILD_DEPS=(
|
||||
coreutils gcc g++ build-essential make # generic build tools
|
||||
wget # used for archive download
|
||||
gnupg # used for archive signature verification
|
||||
tar gzip bzip2 xz-utils unzip # used for archive unpacking
|
||||
zlib1g-dev # zlib library used for all builds
|
||||
libexpat1-dev libipt-dev libbabeltrace-dev liblzma-dev python3-dev texinfo # for gdb
|
||||
libcurl4-openssl-dev # for cmake
|
||||
libreadline-dev # for cmake and llvm
|
||||
libffi-dev libxml2-dev # for llvm
|
||||
libedit-dev libpcre3-dev automake bison # for swig
|
||||
)
|
||||
|
||||
TOOLCHAIN_RUN_DEPS=(
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz-utils # used for archive unpacking
|
||||
zlib1g # zlib library used for all builds
|
||||
libexpat1 libipt2 libbabeltrace1 liblzma5 python3 # for gdb
|
||||
libcurl4 # for cmake
|
||||
libreadline8 # for cmake and llvm
|
||||
libffi7 libxml2 # for llvm
|
||||
)
|
||||
|
||||
MEMGRAPH_BUILD_DEPS=(
|
||||
git # source code control
|
||||
make pkg-config # build system
|
||||
curl wget # for downloading libs
|
||||
uuid-dev default-jre-headless # required by antlr
|
||||
libreadline-dev # for memgraph console
|
||||
libpython3-dev python3-dev # for query modules
|
||||
libssl-dev
|
||||
libseccomp-dev
|
||||
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-3.1 golang nodejs npm
|
||||
autoconf # for jemalloc 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-3.1 ]; then
|
||||
if ! dpkg -s dotnet-sdk-3.1 2>/dev/null >/dev/null; then
|
||||
wget -nv https://packages.microsoft.com/config/ubuntu/20.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-3.1
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
apt install -y "$pkg"
|
||||
done
|
||||
}
|
||||
|
||||
deps=$2"[*]"
|
||||
"$1" "${!deps}"
|
||||
@@ -105,7 +105,8 @@ case "$DISTRO" in
|
||||
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 libipt-devel libbabeltrace-devel xz-devel python36-devel texinfo # for gdb
|
||||
expat-devel libipt-devel libbabeltrace-devel xz-devel python3-devel texinfo # for gdb
|
||||
libcurl-devel # for cmake
|
||||
readline-devel # for cmake and llvm
|
||||
libffi-devel libxml2-devel # for llvm
|
||||
libedit-devel pcre-devel automake bison # for swig
|
||||
@@ -114,7 +115,7 @@ case "$DISTRO" in
|
||||
make # generic build tools
|
||||
tar gzip bzip2 xz # used for archive unpacking
|
||||
zlib # zlib library used for all builds
|
||||
expat libipt libbabeltrace xz-libs python36 # for gdb
|
||||
expat libipt libbabeltrace xz-libs python3 # for gdb
|
||||
readline # for cmake and llvm
|
||||
libffi libxml2 # for llvm
|
||||
)
|
||||
@@ -203,6 +204,7 @@ if [ ! -f llvm-$LLVM_VERSION.src.tar.xz ]; then
|
||||
wget https://releases.llvm.org/$LLVM_VERSION/llvm-$LLVM_VERSION.src.tar.xz
|
||||
wget https://releases.llvm.org/$LLVM_VERSION/cfe-$LLVM_VERSION.src.tar.xz
|
||||
wget https://releases.llvm.org/$LLVM_VERSION/lld-$LLVM_VERSION.src.tar.xz
|
||||
wget https://releases.llvm.org/$LLVM_VERSION/compiler-rt-$LLVM_VERSION.src.tar.xz
|
||||
wget https://releases.llvm.org/$LLVM_VERSION/clang-tools-extra-$LLVM_VERSION.src.tar.xz
|
||||
fi
|
||||
if [ ! -f pahole-gdb-master.zip ]; then
|
||||
@@ -223,7 +225,7 @@ if [ ! -f gcc-$GCC_VERSION.tar.gz.sig ]; then
|
||||
wget https://ftp.gnu.org/gnu/gcc/gcc-$GCC_VERSION/gcc-$GCC_VERSION.tar.gz.sig
|
||||
fi
|
||||
# list of valid gcc gnupg keys: https://gcc.gnu.org/mirrors.html
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0xC3C45C06
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0xA328C3A2C3C45C06
|
||||
$GPG --verify gcc-$GCC_VERSION.tar.gz.sig gcc-$GCC_VERSION.tar.gz
|
||||
# verify binutils
|
||||
if [ ! -f binutils-$BINUTILS_VERSION.tar.gz.sig ]; then
|
||||
@@ -245,7 +247,7 @@ if [ ! -f cmake-$CMAKE_VERSION-SHA-256.txt ] || [ ! -f cmake-$CMAKE_VERSION-SHA-
|
||||
# we filter out the missing files from the sums here manually.
|
||||
cat cmake-$CMAKE_VERSION-SHA-256.txt | grep "cmake-$CMAKE_VERSION.tar.gz" > cmake-$CMAKE_VERSION-SHA-256-filtered.txt
|
||||
fi
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0x7BFB4EDA
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0xC6C265324BBEBDC350B513D02D2CEF1034921684
|
||||
sha256sum -c cmake-$CMAKE_VERSION-SHA-256-filtered.txt
|
||||
$GPG --verify cmake-$CMAKE_VERSION-SHA-256.txt.asc cmake-$CMAKE_VERSION-SHA-256.txt
|
||||
# verify llvm, cfe, lld, clang-tools-extra
|
||||
@@ -253,6 +255,7 @@ if [ ! -f llvm-$LLVM_VERSION.src.tar.xz.sig ]; then
|
||||
wget https://releases.llvm.org/$LLVM_VERSION/llvm-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://releases.llvm.org/$LLVM_VERSION/cfe-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://releases.llvm.org/$LLVM_VERSION/lld-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://releases.llvm.org/$LLVM_VERSION/compiler-rt-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://releases.llvm.org/$LLVM_VERSION/clang-tools-extra-$LLVM_VERSION.src.tar.xz.sig
|
||||
fi
|
||||
# list of valid llvm gnupg keys: https://releases.llvm.org/download.html
|
||||
@@ -260,6 +263,7 @@ $GPG --keyserver $KEYSERVER --recv-keys 0x345AD05D
|
||||
$GPG --verify llvm-$LLVM_VERSION.src.tar.xz.sig llvm-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify cfe-$LLVM_VERSION.src.tar.xz.sig cfe-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify lld-$LLVM_VERSION.src.tar.xz.sig lld-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify compiler-rt-$LLVM_VERSION.src.tar.xz.sig compiler-rt-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify clang-tools-extra-$LLVM_VERSION.src.tar.xz.sig clang-tools-extra-$LLVM_VERSION.src.tar.xz
|
||||
popd
|
||||
|
||||
@@ -500,6 +504,8 @@ if [ ! -f $PREFIX/bin/clang ]; then
|
||||
mv cfe-$LLVM_VERSION.src llvm-$LLVM_VERSION/tools/clang
|
||||
tar -xvf ../archives/lld-$LLVM_VERSION.src.tar.xz
|
||||
mv lld-$LLVM_VERSION.src/ llvm-$LLVM_VERSION/tools/lld
|
||||
tar -xvf ../archives/compiler-rt-$LLVM_VERSION.src.tar.xz
|
||||
mv compiler-rt-$LLVM_VERSION.src/ llvm-$LLVM_VERSION/projects/compiler-rt
|
||||
tar -xvf ../archives/clang-tools-extra-$LLVM_VERSION.src.tar.xz
|
||||
mv clang-tools-extra-$LLVM_VERSION.src/ llvm-$LLVM_VERSION/tools/clang/tools/extra
|
||||
pushd llvm-$LLVM_VERSION
|
||||
@@ -546,7 +552,7 @@ if [ ! -f $PREFIX/README.md ]; then
|
||||
- GDB $GDB_VERSION
|
||||
- CMake $CMAKE_VERSION
|
||||
- Cppcheck $CPPCHECK_VERSION
|
||||
- LLVM (Clang, LLD, Clang tools extra) $LLVM_VERSION
|
||||
- LLVM (Clang, LLD, compiler-rt, Clang tools extra) $LLVM_VERSION
|
||||
|
||||
## Required libraries
|
||||
|
||||
533
environment/toolchain/v2.sh
Executable file
533
environment/toolchain/v2.sh
Executable file
@@ -0,0 +1,533 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
# helpers
|
||||
pushd () { command pushd "$@" > /dev/null; }
|
||||
popd () { command popd "$@" > /dev/null; }
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
CPUS=$( grep -c processor < /proc/cpuinfo )
|
||||
cd "$DIR"
|
||||
|
||||
source "$DIR/../util.sh"
|
||||
DISTRO="$(operating_system)"
|
||||
|
||||
# toolchain version
|
||||
TOOLCHAIN_VERSION=2
|
||||
|
||||
# package versions used
|
||||
GCC_VERSION=10.2.0
|
||||
BINUTILS_VERSION=2.35.1
|
||||
case "$DISTRO" in
|
||||
centos-7) # because GDB >= 9 does NOT compile with readline6.
|
||||
GDB_VERSION=8.3
|
||||
;;
|
||||
*)
|
||||
GDB_VERSION=10.1
|
||||
;;
|
||||
esac
|
||||
CMAKE_VERSION=3.18.4
|
||||
CPPCHECK_VERSION=2.2
|
||||
LLVM_VERSION=11.0.0
|
||||
SWIG_VERSION=4.0.2 # used only for LLVM compilation
|
||||
|
||||
# Check for the dependencies.
|
||||
echo "ALL BUILD PACKAGES: $($DIR/../os/$DISTRO.sh list TOOLCHAIN_BUILD_DEPS)"
|
||||
$DIR/../os/$DISTRO.sh check TOOLCHAIN_BUILD_DEPS
|
||||
echo "ALL RUN PACKAGES: $($DIR/../os/$DISTRO.sh list TOOLCHAIN_RUN_DEPS)"
|
||||
$DIR/../os/$DISTRO.sh check TOOLCHAIN_RUN_DEPS
|
||||
|
||||
# check installation directory
|
||||
NAME=toolchain-v$TOOLCHAIN_VERSION
|
||||
PREFIX=/opt/$NAME
|
||||
mkdir -p $PREFIX >/dev/null 2>/dev/null || true
|
||||
if [ ! -d $PREFIX ] || [ ! -w $PREFIX ]; then
|
||||
echo "Please make sure that the directory '$PREFIX' exists and is writable by the current user!"
|
||||
echo
|
||||
echo "If unsure, execute these commands as root:"
|
||||
echo " mkdir $PREFIX && chown $USER:$USER $PREFIX"
|
||||
echo
|
||||
echo "Press <return> when you have created the directory and granted permissions."
|
||||
# wait for the directory to be created
|
||||
while true; do
|
||||
read
|
||||
if [ ! -d $PREFIX ] || [ ! -w $PREFIX ]; then
|
||||
echo
|
||||
echo "You can't continue before you have created the directory and granted permissions!"
|
||||
echo
|
||||
echo "Press <return> when you have created the directory and granted permissions."
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# create archives directory
|
||||
mkdir -p archives
|
||||
|
||||
# download all archives
|
||||
pushd archives
|
||||
if [ ! -f gcc-$GCC_VERSION.tar.gz ]; then
|
||||
wget https://ftp.gnu.org/gnu/gcc/gcc-$GCC_VERSION/gcc-$GCC_VERSION.tar.gz
|
||||
fi
|
||||
if [ ! -f binutils-$BINUTILS_VERSION.tar.gz ]; then
|
||||
wget https://ftp.gnu.org/gnu/binutils/binutils-$BINUTILS_VERSION.tar.gz
|
||||
fi
|
||||
if [ ! -f gdb-$GDB_VERSION.tar.gz ]; then
|
||||
wget https://ftp.gnu.org/gnu/gdb/gdb-$GDB_VERSION.tar.gz
|
||||
fi
|
||||
if [ ! -f cmake-$CMAKE_VERSION.tar.gz ]; then
|
||||
wget https://github.com/Kitware/CMake/releases/download/v$CMAKE_VERSION/cmake-$CMAKE_VERSION.tar.gz
|
||||
fi
|
||||
if [ ! -f swig-$SWIG_VERSION.tar.gz ]; then
|
||||
wget https://github.com/swig/swig/archive/rel-$SWIG_VERSION.tar.gz -O swig-$SWIG_VERSION.tar.gz
|
||||
fi
|
||||
if [ ! -f cppcheck-$CPPCHECK_VERSION.tar.gz ]; then
|
||||
wget https://github.com/danmar/cppcheck/archive/$CPPCHECK_VERSION.tar.gz -O cppcheck-$CPPCHECK_VERSION.tar.gz
|
||||
fi
|
||||
if [ ! -f llvm-$LLVM_VERSION.src.tar.xz ]; then
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/llvm-$LLVM_VERSION.src.tar.xz
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/clang-$LLVM_VERSION.src.tar.xz
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/lld-$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
|
||||
fi
|
||||
if [ ! -f pahole-gdb-master.zip ]; then
|
||||
wget https://github.com/PhilArmstrong/pahole-gdb/archive/master.zip -O pahole-gdb-master.zip
|
||||
fi
|
||||
|
||||
# verify all archives
|
||||
# NOTE: Verification can fail if the archive is signed by another developer. I
|
||||
# haven't added commands to download all developer GnuPG keys because the
|
||||
# download is very slow. If the verification fails for you, figure out who has
|
||||
# signed the archive and download their public key instead.
|
||||
GPG="gpg --homedir .gnupg"
|
||||
KEYSERVER="hkp://keyserver.ubuntu.com"
|
||||
mkdir -p .gnupg
|
||||
chmod 700 .gnupg
|
||||
# verify gcc
|
||||
if [ ! -f gcc-$GCC_VERSION.tar.gz.sig ]; then
|
||||
wget https://ftp.gnu.org/gnu/gcc/gcc-$GCC_VERSION/gcc-$GCC_VERSION.tar.gz.sig
|
||||
fi
|
||||
# list of valid gcc gnupg keys: https://gcc.gnu.org/mirrors.html
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0x3AB00996FC26A641
|
||||
$GPG --verify gcc-$GCC_VERSION.tar.gz.sig gcc-$GCC_VERSION.tar.gz
|
||||
# verify binutils
|
||||
if [ ! -f binutils-$BINUTILS_VERSION.tar.gz.sig ]; then
|
||||
wget https://ftp.gnu.org/gnu/binutils/binutils-$BINUTILS_VERSION.tar.gz.sig
|
||||
fi
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0xDD9E3C4F
|
||||
$GPG --verify binutils-$BINUTILS_VERSION.tar.gz.sig binutils-$BINUTILS_VERSION.tar.gz
|
||||
# verify gdb
|
||||
if [ ! -f gdb-$GDB_VERSION.tar.gz.sig ]; then
|
||||
wget https://ftp.gnu.org/gnu/gdb/gdb-$GDB_VERSION.tar.gz.sig
|
||||
fi
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0xFF325CF3
|
||||
$GPG --verify gdb-$GDB_VERSION.tar.gz.sig gdb-$GDB_VERSION.tar.gz
|
||||
# verify cmake
|
||||
if [ ! -f cmake-$CMAKE_VERSION-SHA-256.txt ] || [ ! -f cmake-$CMAKE_VERSION-SHA-256.txt.asc ]; then
|
||||
wget https://github.com/Kitware/CMake/releases/download/v$CMAKE_VERSION/cmake-$CMAKE_VERSION-SHA-256.txt
|
||||
wget https://github.com/Kitware/CMake/releases/download/v$CMAKE_VERSION/cmake-$CMAKE_VERSION-SHA-256.txt.asc
|
||||
# Because CentOS 7 doesn't have the `--ignore-missing` flag for `sha256sum`
|
||||
# we filter out the missing files from the sums here manually.
|
||||
cat cmake-$CMAKE_VERSION-SHA-256.txt | grep "cmake-$CMAKE_VERSION.tar.gz" > cmake-$CMAKE_VERSION-SHA-256-filtered.txt
|
||||
fi
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0xC6C265324BBEBDC350B513D02D2CEF1034921684
|
||||
sha256sum -c cmake-$CMAKE_VERSION-SHA-256-filtered.txt
|
||||
$GPG --verify cmake-$CMAKE_VERSION-SHA-256.txt.asc cmake-$CMAKE_VERSION-SHA-256.txt
|
||||
# verify llvm, cfe, lld, clang-tools-extra
|
||||
if [ ! -f llvm-$LLVM_VERSION.src.tar.xz.sig ]; then
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/llvm-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/clang-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/lld-$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
|
||||
fi
|
||||
# list of valid llvm gnupg keys: https://releases.llvm.org/download.html
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0x345AD05D
|
||||
$GPG --verify llvm-$LLVM_VERSION.src.tar.xz.sig llvm-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify clang-$LLVM_VERSION.src.tar.xz.sig clang-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify lld-$LLVM_VERSION.src.tar.xz.sig lld-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify clang-tools-extra-$LLVM_VERSION.src.tar.xz.sig clang-tools-extra-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify compiler-rt-$LLVM_VERSION.src.tar.xz.sig compiler-rt-$LLVM_VERSION.src.tar.xz
|
||||
popd
|
||||
|
||||
# create build directory
|
||||
mkdir -p build
|
||||
pushd build
|
||||
|
||||
# compile gcc
|
||||
if [ ! -f $PREFIX/bin/gcc ]; then
|
||||
if [ -d gcc-$GCC_VERSION ]; then
|
||||
rm -rf gcc-$GCC_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/gcc-$GCC_VERSION.tar.gz
|
||||
pushd gcc-$GCC_VERSION
|
||||
./contrib/download_prerequisites
|
||||
mkdir build && pushd build
|
||||
# influenced by: https://buildd.debian.org/status/fetch.php?pkg=gcc-8&arch=amd64&ver=8.3.0-6&stamp=1554588545
|
||||
../configure -v \
|
||||
--build=x86_64-linux-gnu \
|
||||
--host=x86_64-linux-gnu \
|
||||
--target=x86_64-linux-gnu \
|
||||
--prefix=$PREFIX \
|
||||
--disable-multilib \
|
||||
--with-system-zlib \
|
||||
--enable-checking=release \
|
||||
--enable-languages=c,c++,fortran \
|
||||
--enable-gold=yes \
|
||||
--enable-ld=yes \
|
||||
--enable-lto \
|
||||
--enable-bootstrap \
|
||||
--disable-vtable-verify \
|
||||
--disable-werror \
|
||||
--without-included-gettext \
|
||||
--enable-threads=posix \
|
||||
--enable-nls \
|
||||
--enable-clocale=gnu \
|
||||
--enable-libstdcxx-debug \
|
||||
--enable-libstdcxx-time=yes \
|
||||
--enable-gnu-unique-object \
|
||||
--enable-libmpx \
|
||||
--enable-plugin \
|
||||
--enable-default-pie \
|
||||
--with-target-system-zlib \
|
||||
--with-tune=generic \
|
||||
--without-cuda-driver
|
||||
#--program-suffix=$( printf "$GCC_VERSION" | cut -d '.' -f 1,2 ) \
|
||||
make -j$CPUS
|
||||
# make -k check # run test suite
|
||||
make install
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# activate toolchain
|
||||
export PATH=$PREFIX/bin:$PATH
|
||||
export LD_LIBRARY_PATH=$PREFIX/lib64
|
||||
|
||||
# compile binutils
|
||||
if [ ! -f $PREFIX/bin/ld.gold ]; then
|
||||
if [ -d binutils-$BINUTILS_VERSION ]; then
|
||||
rm -rf binutils-$BINUTILS_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/binutils-$BINUTILS_VERSION.tar.gz
|
||||
pushd binutils-$BINUTILS_VERSION
|
||||
mkdir build && pushd build
|
||||
# influenced by: https://buildd.debian.org/status/fetch.php?pkg=binutils&arch=amd64&ver=2.32-7&stamp=1553247092
|
||||
env \
|
||||
CC=gcc \
|
||||
CXX=g++ \
|
||||
CFLAGS="-g -O2" \
|
||||
CXXFLAGS="-g -O2" \
|
||||
LDFLAGS="" \
|
||||
../configure \
|
||||
--build=x86_64-linux-gnu \
|
||||
--host=x86_64-linux-gnu \
|
||||
--prefix=$PREFIX \
|
||||
--enable-ld=default \
|
||||
--enable-gold \
|
||||
--enable-lto \
|
||||
--enable-plugins \
|
||||
--enable-shared \
|
||||
--enable-threads \
|
||||
--with-system-zlib \
|
||||
--enable-deterministic-archives \
|
||||
--disable-compressed-debug-sections \
|
||||
--enable-new-dtags \
|
||||
--disable-werror
|
||||
make -j$CPUS
|
||||
# make -k check # run test suite
|
||||
make install
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# compile gdb
|
||||
if [ ! -f $PREFIX/bin/gdb ]; then
|
||||
if [ -d gdb-$GDB_VERSION ]; then
|
||||
rm -rf gdb-$GDB_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/gdb-$GDB_VERSION.tar.gz
|
||||
pushd gdb-$GDB_VERSION
|
||||
mkdir build && pushd build
|
||||
# https://buildd.debian.org/status/fetch.php?pkg=gdb&arch=amd64&ver=8.2.1-2&stamp=1550831554&raw=0
|
||||
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-system-readline \
|
||||
--with-expat \
|
||||
--with-system-zlib \
|
||||
--with-lzma \
|
||||
--with-babeltrace \
|
||||
--with-intel-pt \
|
||||
--enable-tui \
|
||||
--with-python=python3
|
||||
make -j$CPUS
|
||||
make install
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# install pahole
|
||||
if [ ! -d $PREFIX/share/pahole-gdb ]; then
|
||||
unzip ../archives/pahole-gdb-master.zip
|
||||
mv pahole-gdb-master $PREFIX/share/pahole-gdb
|
||||
fi
|
||||
|
||||
# setup system gdbinit
|
||||
if [ ! -f $PREFIX/etc/gdb/gdbinit ]; then
|
||||
mkdir -p $PREFIX/etc/gdb
|
||||
cat >$PREFIX/etc/gdb/gdbinit <<EOF
|
||||
# improve formatting
|
||||
set print pretty on
|
||||
set print object on
|
||||
set print static-members on
|
||||
set print vtbl on
|
||||
set print demangle on
|
||||
set demangle-style gnu-v3
|
||||
set print sevenbit-strings off
|
||||
|
||||
# load libstdc++ pretty printers
|
||||
add-auto-load-scripts-directory $PREFIX/lib64
|
||||
add-auto-load-safe-path $PREFIX
|
||||
|
||||
# load pahole
|
||||
python
|
||||
sys.path.insert(0, "$PREFIX/share/pahole-gdb")
|
||||
import offsets
|
||||
import pahole
|
||||
end
|
||||
EOF
|
||||
fi
|
||||
|
||||
# compile cmake
|
||||
if [ ! -f $PREFIX/bin/cmake ]; then
|
||||
if [ -d cmake-$CMAKE_VERSION ]; then
|
||||
rm -rf cmake-$CMAKE_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/cmake-$CMAKE_VERSION.tar.gz
|
||||
pushd cmake-$CMAKE_VERSION
|
||||
# influenced by: https://buildd.debian.org/status/fetch.php?pkg=cmake&arch=amd64&ver=3.13.4-1&stamp=1549799837
|
||||
echo 'set(CMAKE_SKIP_RPATH ON CACHE BOOL "Skip rpath" FORCE)' >> build-flags.cmake
|
||||
echo 'set(CMAKE_USE_RELATIVE_PATHS ON CACHE BOOL "Use relative paths" FORCE)' >> build-flags.cmake
|
||||
echo 'set(CMAKE_C_FLAGS "-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2" CACHE STRING "C flags" FORCE)' >> build-flags.cmake
|
||||
echo 'set(CMAKE_CXX_FLAGS "-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2" CACHE STRING "C++ flags" FORCE)' >> build-flags.cmake
|
||||
echo 'set(CMAKE_SKIP_BOOTSTRAP_TEST ON CACHE BOOL "Skip BootstrapTest" FORCE)' >> build-flags.cmake
|
||||
echo 'set(BUILD_CursesDialog ON CACHE BOOL "Build curses GUI" FORCE)' >> build-flags.cmake
|
||||
mkdir build && pushd build
|
||||
../bootstrap \
|
||||
--prefix=$PREFIX \
|
||||
--init=../build-flags.cmake \
|
||||
--parallel=$CPUS \
|
||||
--system-curl
|
||||
make -j$CPUS
|
||||
# make test # run test suite
|
||||
make install
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# compile cppcheck
|
||||
if [ ! -f $PREFIX/bin/cppcheck ]; then
|
||||
if [ -d cppcheck-$CPPCHECK_VERSION ]; then
|
||||
rm -rf cppcheck-$CPPCHECK_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/cppcheck-$CPPCHECK_VERSION.tar.gz
|
||||
pushd cppcheck-$CPPCHECK_VERSION
|
||||
env \
|
||||
CC=gcc \
|
||||
CXX=g++ \
|
||||
PREFIX=$PREFIX \
|
||||
FILESDIR=$PREFIX/share/cppcheck \
|
||||
CFGDIR=$PREFIX/share/cppcheck/cfg \
|
||||
make -j$CPUS
|
||||
env \
|
||||
CC=gcc \
|
||||
CXX=g++ \
|
||||
PREFIX=$PREFIX \
|
||||
FILESDIR=$PREFIX/share/cppcheck \
|
||||
CFGDIR=$PREFIX/share/cppcheck/cfg \
|
||||
make install
|
||||
popd
|
||||
fi
|
||||
|
||||
# compile swig
|
||||
if [ ! -d swig-$SWIG_VERSION/install ]; then
|
||||
if [ -d swig-$SWIG_VERSION ]; then
|
||||
rm -rf swig-$SWIG_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/swig-$SWIG_VERSION.tar.gz
|
||||
mv swig-rel-$SWIG_VERSION swig-$SWIG_VERSION
|
||||
pushd swig-$SWIG_VERSION
|
||||
./autogen.sh
|
||||
mkdir build && pushd build
|
||||
../configure --prefix=$DIR/build/swig-$SWIG_VERSION/install
|
||||
make -j$CPUS
|
||||
make install
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# compile llvm
|
||||
if [ ! -f $PREFIX/bin/clang ]; then
|
||||
if [ -d llvm-$LLVM_VERSION ]; then
|
||||
rm -rf llvm-$LLVM_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/llvm-$LLVM_VERSION.src.tar.xz
|
||||
mv llvm-$LLVM_VERSION.src llvm-$LLVM_VERSION
|
||||
tar -xvf ../archives/clang-$LLVM_VERSION.src.tar.xz
|
||||
mv clang-$LLVM_VERSION.src llvm-$LLVM_VERSION/tools/clang
|
||||
tar -xvf ../archives/lld-$LLVM_VERSION.src.tar.xz
|
||||
mv lld-$LLVM_VERSION.src/ llvm-$LLVM_VERSION/tools/lld
|
||||
tar -xvf ../archives/clang-tools-extra-$LLVM_VERSION.src.tar.xz
|
||||
mv clang-tools-extra-$LLVM_VERSION.src/ llvm-$LLVM_VERSION/tools/clang/tools/extra
|
||||
tar -xvf ../archives/compiler-rt-$LLVM_VERSION.src.tar.xz
|
||||
mv compiler-rt-$LLVM_VERSION.src/ llvm-$LLVM_VERSION/projects/compiler-rt
|
||||
pushd llvm-$LLVM_VERSION
|
||||
mkdir build && pushd build
|
||||
# activate swig
|
||||
export PATH=$DIR/build/swig-$SWIG_VERSION/install/bin:$PATH
|
||||
# influenced by: https://buildd.debian.org/status/fetch.php?pkg=llvm-toolchain-7&arch=amd64&ver=1%3A7.0.1%7E%2Brc2-1%7Eexp1&stamp=1541506173&raw=0
|
||||
cmake .. \
|
||||
-DCMAKE_C_COMPILER=$PREFIX/bin/gcc \
|
||||
-DCMAKE_CXX_COMPILER=$PREFIX/bin/g++ \
|
||||
-DCMAKE_CXX_LINK_FLAGS="-L$PREFIX/lib64 -Wl,-rpath,$PREFIX/lib64" \
|
||||
-DCMAKE_INSTALL_PREFIX=$PREFIX \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_CXX_FLAGS_RELWITHDEBINFO="-O2 -DNDEBUG" \
|
||||
-DCMAKE_CXX_FLAGS=' -fuse-ld=gold -fPIC -Wno-unused-command-line-argument -Wno-unknown-warning-option' \
|
||||
-DCMAKE_C_FLAGS=' -fuse-ld=gold -fPIC -Wno-unused-command-line-argument -Wno-unknown-warning-option' \
|
||||
-DLLVM_LINK_LLVM_DYLIB=ON \
|
||||
-DLLVM_INSTALL_UTILS=ON \
|
||||
-DLLVM_VERSION_SUFFIX= \
|
||||
-DLLVM_BUILD_LLVM_DYLIB=ON \
|
||||
-DLLVM_ENABLE_RTTI=ON \
|
||||
-DLLVM_ENABLE_FFI=ON \
|
||||
-DLLVM_BINUTILS_INCDIR=$PREFIX/include/ \
|
||||
-DLLVM_USE_PERF=yes
|
||||
make -j$CPUS
|
||||
make -j$CPUS check-clang # run clang test suite
|
||||
make -j$CPUS check-lld # run lld test suite
|
||||
make install
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# create README
|
||||
if [ ! -f $PREFIX/README.md ]; then
|
||||
cat >$PREFIX/README.md <<EOF
|
||||
# Memgraph Toolchain v$TOOLCHAIN_VERSION
|
||||
|
||||
## Included tools
|
||||
|
||||
- GCC $GCC_VERSION
|
||||
- Binutils $BINUTILS_VERSION
|
||||
- GDB $GDB_VERSION
|
||||
- CMake $CMAKE_VERSION
|
||||
- Cppcheck $CPPCHECK_VERSION
|
||||
- LLVM (Clang, LLD, compiler-rt, Clang tools extra) $LLVM_VERSION
|
||||
|
||||
## Required libraries
|
||||
|
||||
In order to be able to run all of these tools you should install the following
|
||||
packages:
|
||||
|
||||
\`\`\`
|
||||
$($DIR/../os/$DISTRO.sh list TOOLCHAIN_RUN_DEPS)
|
||||
\`\`\`
|
||||
|
||||
## Usage
|
||||
|
||||
In order to use the toolchain you just have to source the activation script:
|
||||
|
||||
\`\`\`
|
||||
source $PREFIX/activate
|
||||
\`\`\`
|
||||
EOF
|
||||
fi
|
||||
|
||||
# create activation script
|
||||
if [ ! -f $PREFIX/activate ]; then
|
||||
cat >$PREFIX/activate <<EOF
|
||||
# This file must be used with "source $PREFIX/activate" *from bash*
|
||||
# You can't run it directly!
|
||||
|
||||
# check for active virtual environments
|
||||
if [ "\$( type -t deactivate )" != "" ]; then
|
||||
echo "You already have an active virtual environment!"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# check that we aren't root
|
||||
if [ "\$USER" == "root" ]; then
|
||||
echo "You shouldn't use the toolchan as root!"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# save original environment
|
||||
export ORIG_PATH=\$PATH
|
||||
export ORIG_PS1=\$PS1
|
||||
export ORIG_LD_LIBRARY_PATH=\$LD_LIBRARY_PATH
|
||||
|
||||
# activate new environment
|
||||
export PATH=$PREFIX/bin:\$PATH
|
||||
export PS1="($NAME) \$PS1"
|
||||
export LD_LIBRARY_PATH=$PREFIX/lib:$PREFIX/lib64
|
||||
|
||||
# disable root
|
||||
function su () {
|
||||
echo "You don't want to use root functions while using the toolchain!"
|
||||
return 1
|
||||
}
|
||||
function sudo () {
|
||||
echo "You don't want to use root functions while using the toolchain!"
|
||||
return 1
|
||||
}
|
||||
|
||||
# create deactivation function
|
||||
function deactivate() {
|
||||
export PATH=\$ORIG_PATH
|
||||
export PS1=\$ORIG_PS1
|
||||
export LD_LIBRARY_PATH=\$ORIG_LD_LIBRARY_PATH
|
||||
unset ORIG_PATH ORIG_PS1 ORIG_LD_LIBRARY_PATH
|
||||
unset -f su sudo deactivate
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
# create toolchain archive
|
||||
if [ ! -f $NAME-binaries-$DISTRO.tar.gz ]; then
|
||||
tar --owner=root --group=root -cpvzf $NAME-binaries-$DISTRO.tar.gz -C /opt $NAME
|
||||
fi
|
||||
|
||||
# output final instructions
|
||||
echo -e "\n\n"
|
||||
echo "All tools have been built. They are installed in '$PREFIX'."
|
||||
echo "In order to distribute the tools to someone else, an archive with the toolchain was created in the 'build' directory."
|
||||
echo "If you want to install the packed tools you should execute the following command:"
|
||||
echo
|
||||
echo " tar -xvzf build/$NAME-binaries.tar.gz -C /opt"
|
||||
echo
|
||||
echo "Because the tools were built on this machine, you should probably change the permissions of the installation directory using:"
|
||||
echo
|
||||
echo " OPTIONAL: chown -R root:root $PREFIX"
|
||||
echo
|
||||
echo "In order to use all of the newly compiled tools you should use the prepared activation script:"
|
||||
echo
|
||||
echo " source $PREFIX/activate"
|
||||
echo
|
||||
echo "Or, for more advanced uses, you can add the following lines to your script:"
|
||||
echo
|
||||
echo " export PATH=$PREFIX/bin:\$PATH"
|
||||
echo " export LD_LIBRARY_PATH=$PREFIX/lib:$PREFIX/lib64"
|
||||
echo
|
||||
echo "Enjoy!"
|
||||
556
environment/toolchain/v3.sh
Executable file
556
environment/toolchain/v3.sh
Executable file
@@ -0,0 +1,556 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
# helpers
|
||||
pushd () { command pushd "$@" > /dev/null; }
|
||||
popd () { command popd "$@" > /dev/null; }
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
CPUS=$( grep -c processor < /proc/cpuinfo )
|
||||
cd "$DIR"
|
||||
|
||||
source "$DIR/../util.sh"
|
||||
DISTRO="$(operating_system)"
|
||||
|
||||
# toolchain version
|
||||
TOOLCHAIN_VERSION=3
|
||||
|
||||
# package versions used
|
||||
GCC_VERSION=11.1.0
|
||||
BINUTILS_VERSION=2.36.1
|
||||
case "$DISTRO" in
|
||||
centos-7) # because GDB >= 9 does NOT compile with readline6.
|
||||
GDB_VERSION=8.3
|
||||
;;
|
||||
*)
|
||||
GDB_VERSION=10.2
|
||||
;;
|
||||
esac
|
||||
CMAKE_VERSION=3.20.5
|
||||
CPPCHECK_VERSION=2.4.1
|
||||
LLVM_VERSION=12.0.1rc4
|
||||
LLVM_VERSION_LONG=12.0.1-rc4
|
||||
SWIG_VERSION=4.0.2 # used only for LLVM compilation
|
||||
|
||||
# Check for the dependencies.
|
||||
echo "ALL BUILD PACKAGES: $($DIR/../os/$DISTRO.sh list TOOLCHAIN_BUILD_DEPS)"
|
||||
$DIR/../os/$DISTRO.sh check TOOLCHAIN_BUILD_DEPS
|
||||
echo "ALL RUN PACKAGES: $($DIR/../os/$DISTRO.sh list TOOLCHAIN_RUN_DEPS)"
|
||||
$DIR/../os/$DISTRO.sh check TOOLCHAIN_RUN_DEPS
|
||||
|
||||
# check installation directory
|
||||
NAME=toolchain-v$TOOLCHAIN_VERSION
|
||||
PREFIX=/opt/$NAME
|
||||
mkdir -p $PREFIX >/dev/null 2>/dev/null || true
|
||||
if [ ! -d $PREFIX ] || [ ! -w $PREFIX ]; then
|
||||
echo "Please make sure that the directory '$PREFIX' exists and is writable by the current user!"
|
||||
echo
|
||||
echo "If unsure, execute these commands as root:"
|
||||
echo " mkdir $PREFIX && chown $USER:$USER $PREFIX"
|
||||
echo
|
||||
echo "Press <return> when you have created the directory and granted permissions."
|
||||
# wait for the directory to be created
|
||||
while true; do
|
||||
read
|
||||
if [ ! -d $PREFIX ] || [ ! -w $PREFIX ]; then
|
||||
echo
|
||||
echo "You can't continue before you have created the directory and granted permissions!"
|
||||
echo
|
||||
echo "Press <return> when you have created the directory and granted permissions."
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# create archives directory
|
||||
mkdir -p archives
|
||||
|
||||
# download all archives
|
||||
pushd archives
|
||||
if [ ! -f gcc-$GCC_VERSION.tar.gz ]; then
|
||||
wget https://ftp.gnu.org/gnu/gcc/gcc-$GCC_VERSION/gcc-$GCC_VERSION.tar.gz
|
||||
fi
|
||||
if [ ! -f binutils-$BINUTILS_VERSION.tar.gz ]; then
|
||||
wget https://ftp.gnu.org/gnu/binutils/binutils-$BINUTILS_VERSION.tar.gz
|
||||
fi
|
||||
if [ ! -f gdb-$GDB_VERSION.tar.gz ]; then
|
||||
wget https://ftp.gnu.org/gnu/gdb/gdb-$GDB_VERSION.tar.gz
|
||||
fi
|
||||
if [ ! -f cmake-$CMAKE_VERSION.tar.gz ]; then
|
||||
wget https://github.com/Kitware/CMake/releases/download/v$CMAKE_VERSION/cmake-$CMAKE_VERSION.tar.gz
|
||||
fi
|
||||
if [ ! -f swig-$SWIG_VERSION.tar.gz ]; then
|
||||
wget https://github.com/swig/swig/archive/rel-$SWIG_VERSION.tar.gz -O swig-$SWIG_VERSION.tar.gz
|
||||
fi
|
||||
if [ ! -f cppcheck-$CPPCHECK_VERSION.tar.gz ]; then
|
||||
wget https://github.com/danmar/cppcheck/archive/$CPPCHECK_VERSION.tar.gz -O cppcheck-$CPPCHECK_VERSION.tar.gz
|
||||
fi
|
||||
if [ ! -f llvm-$LLVM_VERSION.src.tar.xz ]; then
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION_LONG/llvm-$LLVM_VERSION.src.tar.xz
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION_LONG/clang-$LLVM_VERSION.src.tar.xz
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION_LONG/lld-$LLVM_VERSION.src.tar.xz
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION_LONG/clang-tools-extra-$LLVM_VERSION.src.tar.xz
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION_LONG/compiler-rt-$LLVM_VERSION.src.tar.xz
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION_LONG/libunwind-$LLVM_VERSION.src.tar.xz
|
||||
fi
|
||||
if [ ! -f pahole-gdb-master.zip ]; then
|
||||
wget https://github.com/PhilArmstrong/pahole-gdb/archive/master.zip -O pahole-gdb-master.zip
|
||||
fi
|
||||
|
||||
# verify all archives
|
||||
# NOTE: Verification can fail if the archive is signed by another developer. I
|
||||
# haven't added commands to download all developer GnuPG keys because the
|
||||
# download is very slow. If the verification fails for you, figure out who has
|
||||
# signed the archive and download their public key instead.
|
||||
GPG="gpg --homedir .gnupg"
|
||||
KEYSERVER="hkp://keyserver.ubuntu.com"
|
||||
|
||||
mkdir -p .gnupg
|
||||
chmod 700 .gnupg
|
||||
# verify gcc
|
||||
if [ ! -f gcc-$GCC_VERSION.tar.gz.sig ]; then
|
||||
wget https://ftp.gnu.org/gnu/gcc/gcc-$GCC_VERSION/gcc-$GCC_VERSION.tar.gz.sig
|
||||
fi
|
||||
# list of valid gcc gnupg keys: https://gcc.gnu.org/mirrors.html
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0x6C35B99309B5FA62
|
||||
$GPG --verify gcc-$GCC_VERSION.tar.gz.sig gcc-$GCC_VERSION.tar.gz
|
||||
# verify binutils
|
||||
if [ ! -f binutils-$BINUTILS_VERSION.tar.gz.sig ]; then
|
||||
wget https://ftp.gnu.org/gnu/binutils/binutils-$BINUTILS_VERSION.tar.gz.sig
|
||||
fi
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0xDD9E3C4F
|
||||
$GPG --verify binutils-$BINUTILS_VERSION.tar.gz.sig binutils-$BINUTILS_VERSION.tar.gz
|
||||
# verify gdb
|
||||
if [ ! -f gdb-$GDB_VERSION.tar.gz.sig ]; then
|
||||
wget https://ftp.gnu.org/gnu/gdb/gdb-$GDB_VERSION.tar.gz.sig
|
||||
fi
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0xFF325CF3
|
||||
$GPG --verify gdb-$GDB_VERSION.tar.gz.sig gdb-$GDB_VERSION.tar.gz
|
||||
# verify cmake
|
||||
if [ ! -f cmake-$CMAKE_VERSION-SHA-256.txt ] || [ ! -f cmake-$CMAKE_VERSION-SHA-256.txt.asc ]; then
|
||||
wget https://github.com/Kitware/CMake/releases/download/v$CMAKE_VERSION/cmake-$CMAKE_VERSION-SHA-256.txt
|
||||
wget https://github.com/Kitware/CMake/releases/download/v$CMAKE_VERSION/cmake-$CMAKE_VERSION-SHA-256.txt.asc
|
||||
# Because CentOS 7 doesn't have the `--ignore-missing` flag for `sha256sum`
|
||||
# we filter out the missing files from the sums here manually.
|
||||
cat cmake-$CMAKE_VERSION-SHA-256.txt | grep "cmake-$CMAKE_VERSION.tar.gz" > cmake-$CMAKE_VERSION-SHA-256-filtered.txt
|
||||
fi
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0xC6C265324BBEBDC350B513D02D2CEF1034921684
|
||||
sha256sum -c cmake-$CMAKE_VERSION-SHA-256-filtered.txt
|
||||
$GPG --verify cmake-$CMAKE_VERSION-SHA-256.txt.asc cmake-$CMAKE_VERSION-SHA-256.txt
|
||||
# verify llvm, cfe, lld, clang-tools-extra
|
||||
if [ ! -f llvm-$LLVM_VERSION.src.tar.xz.sig ]; then
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION_LONG/llvm-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION_LONG/clang-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION_LONG/lld-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION_LONG/clang-tools-extra-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION_LONG/compiler-rt-$LLVM_VERSION.src.tar.xz.sig
|
||||
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION_LONG/libunwind-$LLVM_VERSION.src.tar.xz.sig
|
||||
fi
|
||||
# list of valid llvm gnupg keys: https://releases.llvm.org/download.html
|
||||
$GPG --keyserver $KEYSERVER --recv-keys 0x474E22316ABF4785A88C6E8EA2C794A986419D8A
|
||||
$GPG --verify llvm-$LLVM_VERSION.src.tar.xz.sig llvm-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify clang-$LLVM_VERSION.src.tar.xz.sig clang-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify lld-$LLVM_VERSION.src.tar.xz.sig lld-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify clang-tools-extra-$LLVM_VERSION.src.tar.xz.sig clang-tools-extra-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify compiler-rt-$LLVM_VERSION.src.tar.xz.sig compiler-rt-$LLVM_VERSION.src.tar.xz
|
||||
$GPG --verify libunwind-$LLVM_VERSION.src.tar.xz.sig libunwind-$LLVM_VERSION.src.tar.xz
|
||||
popd
|
||||
|
||||
# create build directory
|
||||
mkdir -p build
|
||||
pushd build
|
||||
|
||||
# compile gcc
|
||||
if [ ! -f $PREFIX/bin/gcc ]; then
|
||||
if [ -d gcc-$GCC_VERSION ]; then
|
||||
rm -rf gcc-$GCC_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/gcc-$GCC_VERSION.tar.gz
|
||||
pushd gcc-$GCC_VERSION
|
||||
./contrib/download_prerequisites
|
||||
mkdir build && pushd build
|
||||
# influenced by: https://buildd.debian.org/status/fetch.php?pkg=gcc-8&arch=amd64&ver=8.3.0-6&stamp=1554588545
|
||||
../configure -v \
|
||||
--build=x86_64-linux-gnu \
|
||||
--host=x86_64-linux-gnu \
|
||||
--target=x86_64-linux-gnu \
|
||||
--prefix=$PREFIX \
|
||||
--disable-multilib \
|
||||
--with-system-zlib \
|
||||
--enable-checking=release \
|
||||
--enable-languages=c,c++,fortran \
|
||||
--enable-gold=yes \
|
||||
--enable-ld=yes \
|
||||
--enable-lto \
|
||||
--enable-bootstrap \
|
||||
--disable-vtable-verify \
|
||||
--disable-werror \
|
||||
--without-included-gettext \
|
||||
--enable-threads=posix \
|
||||
--enable-nls \
|
||||
--enable-clocale=gnu \
|
||||
--enable-libstdcxx-debug \
|
||||
--enable-libstdcxx-time=yes \
|
||||
--enable-gnu-unique-object \
|
||||
--enable-libmpx \
|
||||
--enable-plugin \
|
||||
--enable-default-pie \
|
||||
--with-target-system-zlib \
|
||||
--with-tune=generic \
|
||||
--without-cuda-driver
|
||||
#--program-suffix=$( printf "$GCC_VERSION" | cut -d '.' -f 1,2 ) \
|
||||
make -j$CPUS
|
||||
# make -k check # run test suite
|
||||
make install
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# activate toolchain
|
||||
export PATH=$PREFIX/bin:$PATH
|
||||
export LD_LIBRARY_PATH=$PREFIX/lib64
|
||||
|
||||
# compile binutils
|
||||
if [ ! -f $PREFIX/bin/ld.gold ]; then
|
||||
if [ -d binutils-$BINUTILS_VERSION ]; then
|
||||
rm -rf binutils-$BINUTILS_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/binutils-$BINUTILS_VERSION.tar.gz
|
||||
pushd binutils-$BINUTILS_VERSION
|
||||
mkdir build && pushd build
|
||||
# influenced by: https://buildd.debian.org/status/fetch.php?pkg=binutils&arch=amd64&ver=2.32-7&stamp=1553247092
|
||||
env \
|
||||
CC=gcc \
|
||||
CXX=g++ \
|
||||
CFLAGS="-g -O2" \
|
||||
CXXFLAGS="-g -O2" \
|
||||
LDFLAGS="" \
|
||||
../configure \
|
||||
--build=x86_64-linux-gnu \
|
||||
--host=x86_64-linux-gnu \
|
||||
--prefix=$PREFIX \
|
||||
--enable-ld=default \
|
||||
--enable-gold \
|
||||
--enable-lto \
|
||||
--enable-plugins \
|
||||
--enable-shared \
|
||||
--enable-threads \
|
||||
--with-system-zlib \
|
||||
--enable-deterministic-archives \
|
||||
--disable-compressed-debug-sections \
|
||||
--enable-new-dtags \
|
||||
--disable-werror
|
||||
make -j$CPUS
|
||||
# make -k check # run test suite
|
||||
make install
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# compile gdb
|
||||
if [ ! -f $PREFIX/bin/gdb ]; then
|
||||
if [ -d gdb-$GDB_VERSION ]; then
|
||||
rm -rf gdb-$GDB_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/gdb-$GDB_VERSION.tar.gz
|
||||
pushd gdb-$GDB_VERSION
|
||||
mkdir build && pushd build
|
||||
# https://buildd.debian.org/status/fetch.php?pkg=gdb&arch=amd64&ver=8.2.1-2&stamp=1550831554&raw=0
|
||||
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-system-readline \
|
||||
--with-expat \
|
||||
--with-system-zlib \
|
||||
--with-lzma \
|
||||
--with-babeltrace \
|
||||
--with-intel-pt \
|
||||
--enable-tui \
|
||||
--with-python=python3
|
||||
make -j$CPUS
|
||||
make install
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# install pahole
|
||||
if [ ! -d $PREFIX/share/pahole-gdb ]; then
|
||||
unzip ../archives/pahole-gdb-master.zip
|
||||
mv pahole-gdb-master $PREFIX/share/pahole-gdb
|
||||
fi
|
||||
|
||||
# setup system gdbinit
|
||||
if [ ! -f $PREFIX/etc/gdb/gdbinit ]; then
|
||||
mkdir -p $PREFIX/etc/gdb
|
||||
cat >$PREFIX/etc/gdb/gdbinit <<EOF
|
||||
# improve formatting
|
||||
set print pretty on
|
||||
set print object on
|
||||
set print static-members on
|
||||
set print vtbl on
|
||||
set print demangle on
|
||||
set demangle-style gnu-v3
|
||||
set print sevenbit-strings off
|
||||
|
||||
# load libstdc++ pretty printers
|
||||
add-auto-load-scripts-directory $PREFIX/lib64
|
||||
add-auto-load-safe-path $PREFIX
|
||||
|
||||
# load pahole
|
||||
python
|
||||
sys.path.insert(0, "$PREFIX/share/pahole-gdb")
|
||||
import offsets
|
||||
import pahole
|
||||
end
|
||||
EOF
|
||||
fi
|
||||
|
||||
# compile cmake
|
||||
if [ ! -f $PREFIX/bin/cmake ]; then
|
||||
if [ -d cmake-$CMAKE_VERSION ]; then
|
||||
rm -rf cmake-$CMAKE_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/cmake-$CMAKE_VERSION.tar.gz
|
||||
pushd cmake-$CMAKE_VERSION
|
||||
# influenced by: https://buildd.debian.org/status/fetch.php?pkg=cmake&arch=amd64&ver=3.13.4-1&stamp=1549799837
|
||||
echo 'set(CMAKE_SKIP_RPATH ON CACHE BOOL "Skip rpath" FORCE)' >> build-flags.cmake
|
||||
echo 'set(CMAKE_USE_RELATIVE_PATHS ON CACHE BOOL "Use relative paths" FORCE)' >> build-flags.cmake
|
||||
echo 'set(CMAKE_C_FLAGS "-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2" CACHE STRING "C flags" FORCE)' >> build-flags.cmake
|
||||
echo 'set(CMAKE_CXX_FLAGS "-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2" CACHE STRING "C++ flags" FORCE)' >> build-flags.cmake
|
||||
echo 'set(CMAKE_SKIP_BOOTSTRAP_TEST ON CACHE BOOL "Skip BootstrapTest" FORCE)' >> build-flags.cmake
|
||||
echo 'set(BUILD_CursesDialog ON CACHE BOOL "Build curses GUI" FORCE)' >> build-flags.cmake
|
||||
mkdir build && pushd build
|
||||
../bootstrap \
|
||||
--prefix=$PREFIX \
|
||||
--init=../build-flags.cmake \
|
||||
--parallel=$CPUS \
|
||||
--system-curl
|
||||
make -j$CPUS
|
||||
# make test # run test suite
|
||||
make install
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# compile cppcheck
|
||||
if [ ! -f $PREFIX/bin/cppcheck ]; then
|
||||
if [ -d cppcheck-$CPPCHECK_VERSION ]; then
|
||||
rm -rf cppcheck-$CPPCHECK_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/cppcheck-$CPPCHECK_VERSION.tar.gz
|
||||
pushd cppcheck-$CPPCHECK_VERSION
|
||||
# this was fixed in cppcheck 2.5, remove this in toolchain-v4 after the lib is updated
|
||||
# to 2.5+ version.
|
||||
sed -i '/#include <iostream>/ a #include <limits>' lib/symboldatabase.cpp
|
||||
env \
|
||||
CC=gcc \
|
||||
CXX=g++ \
|
||||
PREFIX=$PREFIX \
|
||||
FILESDIR=$PREFIX/share/cppcheck \
|
||||
CFGDIR=$PREFIX/share/cppcheck/cfg \
|
||||
make -j$CPUS
|
||||
env \
|
||||
CC=gcc \
|
||||
CXX=g++ \
|
||||
PREFIX=$PREFIX \
|
||||
FILESDIR=$PREFIX/share/cppcheck \
|
||||
CFGDIR=$PREFIX/share/cppcheck/cfg \
|
||||
make install
|
||||
popd
|
||||
fi
|
||||
|
||||
# compile swig
|
||||
if [ ! -d swig-$SWIG_VERSION/install ]; then
|
||||
if [ -d swig-$SWIG_VERSION ]; then
|
||||
rm -rf swig-$SWIG_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/swig-$SWIG_VERSION.tar.gz
|
||||
mv swig-rel-$SWIG_VERSION swig-$SWIG_VERSION
|
||||
pushd swig-$SWIG_VERSION
|
||||
./autogen.sh
|
||||
mkdir build && pushd build
|
||||
../configure --prefix=$DIR/build/swig-$SWIG_VERSION/install
|
||||
make -j$CPUS
|
||||
make install
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# compile llvm
|
||||
if [ ! -f $PREFIX/bin/clang ]; then
|
||||
if [ -d llvm-$LLVM_VERSION ]; then
|
||||
rm -rf llvm-$LLVM_VERSION
|
||||
fi
|
||||
tar -xvf ../archives/llvm-$LLVM_VERSION.src.tar.xz
|
||||
mv llvm-$LLVM_VERSION.src llvm-$LLVM_VERSION
|
||||
tar -xvf ../archives/clang-$LLVM_VERSION.src.tar.xz
|
||||
mv clang-$LLVM_VERSION.src llvm-$LLVM_VERSION/tools/clang
|
||||
tar -xvf ../archives/lld-$LLVM_VERSION.src.tar.xz
|
||||
mv lld-$LLVM_VERSION.src/ llvm-$LLVM_VERSION/tools/lld
|
||||
tar -xvf ../archives/clang-tools-extra-$LLVM_VERSION.src.tar.xz
|
||||
mv clang-tools-extra-$LLVM_VERSION.src/ llvm-$LLVM_VERSION/tools/clang/tools/extra
|
||||
tar -xvf ../archives/compiler-rt-$LLVM_VERSION.src.tar.xz
|
||||
mv compiler-rt-$LLVM_VERSION.src/ llvm-$LLVM_VERSION/projects/compiler-rt
|
||||
tar -xvf ../archives/libunwind-$LLVM_VERSION.src.tar.xz
|
||||
mv libunwind-$LLVM_VERSION.src/include/mach-o llvm-$LLVM_VERSION/tools/lld/include
|
||||
pushd llvm-$LLVM_VERSION
|
||||
mkdir build && pushd build
|
||||
# activate swig
|
||||
export PATH=$DIR/build/swig-$SWIG_VERSION/install/bin:$PATH
|
||||
# influenced by: https://buildd.debian.org/status/fetch.php?pkg=llvm-toolchain-7&arch=amd64&ver=1%3A7.0.1%7E%2Brc2-1%7Eexp1&stamp=1541506173&raw=0
|
||||
cmake .. \
|
||||
-DCMAKE_C_COMPILER=$PREFIX/bin/gcc \
|
||||
-DCMAKE_CXX_COMPILER=$PREFIX/bin/g++ \
|
||||
-DCMAKE_CXX_LINK_FLAGS="-L$PREFIX/lib64 -Wl,-rpath,$PREFIX/lib64" \
|
||||
-DCMAKE_INSTALL_PREFIX=$PREFIX \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_CXX_FLAGS_RELWITHDEBINFO="-O2 -DNDEBUG" \
|
||||
-DCMAKE_CXX_FLAGS=' -fuse-ld=gold -fPIC -Wno-unused-command-line-argument -Wno-unknown-warning-option' \
|
||||
-DCMAKE_C_FLAGS=' -fuse-ld=gold -fPIC -Wno-unused-command-line-argument -Wno-unknown-warning-option' \
|
||||
-DLLVM_LINK_LLVM_DYLIB=ON \
|
||||
-DLLVM_INSTALL_UTILS=ON \
|
||||
-DLLVM_VERSION_SUFFIX= \
|
||||
-DLLVM_BUILD_LLVM_DYLIB=ON \
|
||||
-DLLVM_ENABLE_RTTI=ON \
|
||||
-DLLVM_ENABLE_FFI=ON \
|
||||
-DLLVM_BINUTILS_INCDIR=$PREFIX/include/ \
|
||||
-DLLVM_USE_PERF=yes
|
||||
make -j$CPUS
|
||||
make -j$CPUS check-clang # run clang test suite
|
||||
make -j$CPUS check-lld # run lld test suite
|
||||
make install
|
||||
popd && popd
|
||||
fi
|
||||
|
||||
# create README
|
||||
if [ ! -f $PREFIX/README.md ]; then
|
||||
cat >$PREFIX/README.md <<EOF
|
||||
# Memgraph Toolchain v$TOOLCHAIN_VERSION
|
||||
|
||||
## Included tools
|
||||
|
||||
- GCC $GCC_VERSION
|
||||
- Binutils $BINUTILS_VERSION
|
||||
- GDB $GDB_VERSION
|
||||
- CMake $CMAKE_VERSION
|
||||
- Cppcheck $CPPCHECK_VERSION
|
||||
- LLVM (Clang, LLD, compiler-rt, Clang tools extra) $LLVM_VERSION
|
||||
|
||||
## Required libraries
|
||||
|
||||
In order to be able to run all of these tools you should install the following
|
||||
packages:
|
||||
|
||||
\`\`\`
|
||||
$($DIR/../os/$DISTRO.sh list TOOLCHAIN_RUN_DEPS)
|
||||
\`\`\`
|
||||
|
||||
## Usage
|
||||
|
||||
In order to use the toolchain you just have to source the activation script:
|
||||
|
||||
\`\`\`
|
||||
source $PREFIX/activate
|
||||
\`\`\`
|
||||
EOF
|
||||
fi
|
||||
|
||||
# create activation script
|
||||
if [ ! -f $PREFIX/activate ]; then
|
||||
cat >$PREFIX/activate <<EOF
|
||||
# This file must be used with "source $PREFIX/activate" *from bash*
|
||||
# You can't run it directly!
|
||||
|
||||
env_error="You already have an active virtual environment!"
|
||||
|
||||
# zsh does not recognize the option -t of the command type
|
||||
# therefore we use the alternative whence -w
|
||||
if [[ "\$ZSH_NAME" == "zsh" ]]; then
|
||||
# check for active virtual environments
|
||||
if [ "\$( whence -w deactivate )" != "deactivate: none" ]; then
|
||||
echo \$env_error
|
||||
return 0;
|
||||
fi
|
||||
# any other shell
|
||||
else
|
||||
# check for active virtual environments
|
||||
if [ "\$( type -t deactivate )" != "" ]; then
|
||||
echo \$env_error
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# check that we aren't root
|
||||
if [[ "\$USER" == "root" ]]; then
|
||||
echo "You shouldn't use the toolchain as root!"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# save original environment
|
||||
export ORIG_PATH=\$PATH
|
||||
export ORIG_PS1=\$PS1
|
||||
export ORIG_LD_LIBRARY_PATH=\$LD_LIBRARY_PATH
|
||||
|
||||
# activate new environment
|
||||
export PATH=$PREFIX/bin:\$PATH
|
||||
export PS1="($NAME) \$PS1"
|
||||
export LD_LIBRARY_PATH=$PREFIX/lib:$PREFIX/lib64
|
||||
|
||||
# disable root
|
||||
function su () {
|
||||
echo "You don't want to use root functions while using the toolchain!"
|
||||
return 1
|
||||
}
|
||||
function sudo () {
|
||||
echo "You don't want to use root functions while using the toolchain!"
|
||||
return 1
|
||||
}
|
||||
|
||||
# create deactivation function
|
||||
function deactivate() {
|
||||
export PATH=\$ORIG_PATH
|
||||
export PS1=\$ORIG_PS1
|
||||
export LD_LIBRARY_PATH=\$ORIG_LD_LIBRARY_PATH
|
||||
unset ORIG_PATH ORIG_PS1 ORIG_LD_LIBRARY_PATH
|
||||
unset -f su sudo deactivate
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
# create toolchain archive
|
||||
if [ ! -f $NAME-binaries-$DISTRO.tar.gz ]; then
|
||||
tar --owner=root --group=root -cpvzf $NAME-binaries-$DISTRO.tar.gz -C /opt $NAME
|
||||
fi
|
||||
|
||||
# output final instructions
|
||||
echo -e "\n\n"
|
||||
echo "All tools have been built. They are installed in '$PREFIX'."
|
||||
echo "In order to distribute the tools to someone else, an archive with the toolchain was created in the 'build' directory."
|
||||
echo "If you want to install the packed tools you should execute the following command:"
|
||||
echo
|
||||
echo " tar -xvzf build/$NAME-binaries.tar.gz -C /opt"
|
||||
echo
|
||||
echo "Because the tools were built on this machine, you should probably change the permissions of the installation directory using:"
|
||||
echo
|
||||
echo " OPTIONAL: chown -R root:root $PREFIX"
|
||||
echo
|
||||
echo "In order to use all of the newly compiled tools you should use the prepared activation script:"
|
||||
echo
|
||||
echo " source $PREFIX/activate"
|
||||
echo
|
||||
echo "Or, for more advanced uses, you can add the following lines to your script:"
|
||||
echo
|
||||
echo " export PATH=$PREFIX/bin:\$PATH"
|
||||
echo " export LD_LIBRARY_PATH=$PREFIX/lib:$PREFIX/lib64"
|
||||
echo
|
||||
echo "Enjoy!"
|
||||
50
environment/util.sh
Normal file
50
environment/util.sh
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
|
||||
operating_system() {
|
||||
grep -E '^(VERSION_)?ID=' /etc/os-release | \
|
||||
sort | cut -d '=' -f 2- | sed 's/"//g' | paste -s -d '-'
|
||||
}
|
||||
|
||||
check_all_yum() {
|
||||
local missing=""
|
||||
for pkg in $1; do
|
||||
if ! yum list installed "$pkg" >/dev/null 2>/dev/null; then
|
||||
missing="$pkg $missing"
|
||||
fi
|
||||
done
|
||||
if [ "$missing" != "" ]; then
|
||||
echo "MISSING PACKAGES: $missing"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_all_dpkg() {
|
||||
local missing=""
|
||||
for pkg in $1; do
|
||||
if ! dpkg -s "$pkg" >/dev/null 2>/dev/null; then
|
||||
missing="$pkg $missing"
|
||||
fi
|
||||
done
|
||||
if [ "$missing" != "" ]; then
|
||||
echo "MISSING PACKAGES: $missing"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_all_dnf() {
|
||||
local missing=""
|
||||
for pkg in $1; do
|
||||
if ! dnf list installed "$pkg" >/dev/null 2>/dev/null; then
|
||||
missing="$pkg $missing"
|
||||
fi
|
||||
done
|
||||
if [ "$missing" != "" ]; then
|
||||
echo "MISSING PACKAGES: $missing"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
install_all_apt() {
|
||||
for pkg in $1; do
|
||||
apt install -y "$pkg"
|
||||
done
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
212
include/mgp.py
212
include/mgp.py
@@ -64,6 +64,13 @@ class Properties:
|
||||
self._len = None
|
||||
self._vertex_or_edge = vertex_or_edge
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
# This is the same as the shallow copy, as the underlying C API should
|
||||
# not support deepcopy. Besides, it doesn't make much sense to actually
|
||||
# copy _mgp.Edge and _mgp.Vertex types as they are actually references
|
||||
# to graph elements and not proper values.
|
||||
return Properties(self._vertex_or_edge)
|
||||
|
||||
def get(self, property_name: str, default=None) -> object:
|
||||
'''Get the value of a property with the given name or return default.
|
||||
|
||||
@@ -183,7 +190,8 @@ class Edge:
|
||||
|
||||
def __init__(self, edge):
|
||||
if not isinstance(edge, _mgp.Edge):
|
||||
raise TypeError("Expected '_mgp.Edge', got '{}'".format(type(edge)))
|
||||
raise TypeError(
|
||||
"Expected '_mgp.Edge', got '{}'".format(type(edge)))
|
||||
self._edge = edge
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
@@ -236,6 +244,8 @@ class Edge:
|
||||
'''Raise InvalidContextError.'''
|
||||
if not self.is_valid():
|
||||
raise InvalidContextError()
|
||||
if not isinstance(other, Edge):
|
||||
return NotImplemented
|
||||
return self._edge == other._edge
|
||||
|
||||
def __hash__(self) -> int:
|
||||
@@ -259,7 +269,8 @@ class Vertex:
|
||||
|
||||
def __init__(self, vertex):
|
||||
if not isinstance(vertex, _mgp.Vertex):
|
||||
raise TypeError("Expected '_mgp.Vertex', got '{}'".format(type(vertex)))
|
||||
raise TypeError(
|
||||
"Expected '_mgp.Vertex', got '{}'".format(type(vertex)))
|
||||
self._vertex = vertex
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
@@ -325,6 +336,8 @@ class Vertex:
|
||||
'''Raise InvalidContextError'''
|
||||
if not self.is_valid():
|
||||
raise InvalidContextError()
|
||||
if not isinstance(other, Vertex):
|
||||
return NotImplemented
|
||||
return self._vertex == other._vertex
|
||||
|
||||
def __hash__(self) -> int:
|
||||
@@ -372,8 +385,8 @@ class Path:
|
||||
pass
|
||||
# This is the same as the shallow copy, as the underlying C API should
|
||||
# not support deepcopy. Besides, it doesn't make much sense to actually
|
||||
# copy Edge and Vertex types as they are actually references to graph
|
||||
# elements and not proper values.
|
||||
# copy _mgp.Edge and _mgp.Vertex types as they are actually references
|
||||
# to graph elements and not proper values.
|
||||
path = self.__copy__()
|
||||
memo[id(self._path)] = path._path
|
||||
return path
|
||||
@@ -393,7 +406,8 @@ class Path:
|
||||
passed in edge is invalid.
|
||||
'''
|
||||
if not isinstance(edge, Edge):
|
||||
raise TypeError("Expected '_mgp.Edge', got '{}'".format(type(edge)))
|
||||
raise TypeError(
|
||||
"Expected '_mgp.Edge', got '{}'".format(type(edge)))
|
||||
if not self.is_valid() or not edge.is_valid():
|
||||
raise InvalidContextError()
|
||||
self._path.expand(edge._edge)
|
||||
@@ -443,7 +457,8 @@ class Vertices:
|
||||
|
||||
def __init__(self, graph):
|
||||
if not isinstance(graph, _mgp.Graph):
|
||||
raise TypeError("Expected '_mgp.Graph', got '{}'".format(type(graph)))
|
||||
raise TypeError(
|
||||
"Expected '_mgp.Graph', got '{}'".format(type(graph)))
|
||||
self._graph = graph
|
||||
self._len = None
|
||||
|
||||
@@ -471,7 +486,7 @@ class Vertices:
|
||||
|
||||
def __contains__(self, vertex):
|
||||
try:
|
||||
_ = self.graph.get_vertex_by_id(vertex.id)
|
||||
_ = self._graph.get_vertex_by_id(vertex.id)
|
||||
return True
|
||||
except IndexError:
|
||||
return False
|
||||
@@ -488,7 +503,8 @@ class Graph:
|
||||
|
||||
def __init__(self, graph):
|
||||
if not isinstance(graph, _mgp.Graph):
|
||||
raise TypeError("Expected '_mgp.Graph', got '{}'".format(type(graph)))
|
||||
raise TypeError(
|
||||
"Expected '_mgp.Graph', got '{}'".format(type(graph)))
|
||||
self._graph = graph
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
@@ -546,7 +562,8 @@ class ProcCtx:
|
||||
|
||||
def __init__(self, graph):
|
||||
if not isinstance(graph, _mgp.Graph):
|
||||
raise TypeError("Expected '_mgp.Graph', got '{}'".format(type(graph)))
|
||||
raise TypeError(
|
||||
"Expected '_mgp.Graph', got '{}'".format(type(graph)))
|
||||
self._graph = Graph(graph)
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
@@ -616,8 +633,11 @@ def _typing_to_cypher_type(type_):
|
||||
if complex_type == typing.Union:
|
||||
# If we have a Union with NoneType inside, it means we are building
|
||||
# a nullable type.
|
||||
if isinstance(None, type_args):
|
||||
types = tuple(t for t in type_args if not isinstance(None, t))
|
||||
# isinstance doesn't work here because subscripted generics cannot
|
||||
# be used with class and instance checks. type comparison should be
|
||||
# fine because subclasses are not used.
|
||||
if type(None) in type_args:
|
||||
types = tuple(t for t in type_args if t is not type(None)) # noqa E721
|
||||
if len(types) == 1:
|
||||
type_arg, = types
|
||||
else:
|
||||
@@ -662,17 +682,27 @@ def _typing_to_cypher_type(type_):
|
||||
type_args_as_str = parse_type_args(type_as_str)
|
||||
none_type_as_str = type(None).__name__
|
||||
if none_type_as_str in type_args_as_str:
|
||||
types = tuple(t for t in type_args_as_str if t != none_type_as_str)
|
||||
types = tuple(
|
||||
t for t in type_args_as_str if t != none_type_as_str)
|
||||
if len(types) == 1:
|
||||
type_arg_as_str, = types
|
||||
else:
|
||||
type_arg_as_str = 'typing.Union[' + ', '.join(types) + ']'
|
||||
type_arg_as_str = 'typing.Union[' + \
|
||||
', '.join(types) + ']'
|
||||
simple_type = get_simple_type(type_arg_as_str)
|
||||
if simple_type is not None:
|
||||
return _mgp.type_nullable(simple_type)
|
||||
return _mgp.type_nullable(parse_typing(type_arg_as_str))
|
||||
elif type_as_str.startswith('typing.List'):
|
||||
type_arg_as_str, = parse_type_args(type_as_str)
|
||||
type_arg_as_str = parse_type_args(type_as_str)
|
||||
|
||||
if len(type_arg_as_str) > 1:
|
||||
# Nested object could be a type consisting of a list of types (e.g. mgp.Map)
|
||||
# so we need to join the parts.
|
||||
type_arg_as_str = ', '.join(type_arg_as_str)
|
||||
else:
|
||||
type_arg_as_str = type_arg_as_str[0]
|
||||
|
||||
simple_type = get_simple_type(type_arg_as_str)
|
||||
if simple_type is not None:
|
||||
return _mgp.type_list(simple_type)
|
||||
@@ -692,6 +722,19 @@ class Deprecated:
|
||||
self.field_type = type_
|
||||
|
||||
|
||||
def raise_if_does_not_meet_requirements(func: typing.Callable[..., Record]):
|
||||
if not callable(func):
|
||||
raise TypeError("Expected a callable object, got an instance of '{}'"
|
||||
.format(type(func)))
|
||||
if inspect.iscoroutinefunction(func):
|
||||
raise TypeError("Callable must not be 'async def' function")
|
||||
if sys.version_info >= (3, 6):
|
||||
if inspect.isasyncgenfunction(func):
|
||||
raise TypeError("Callable must not be 'async def' function")
|
||||
if inspect.isgeneratorfunction(func):
|
||||
raise NotImplementedError("Generator functions are not supported")
|
||||
|
||||
|
||||
def read_proc(func: typing.Callable[..., Record]):
|
||||
'''
|
||||
Register `func` as a a read-only procedure of the current module.
|
||||
@@ -732,16 +775,7 @@ def read_proc(func: typing.Callable[..., Record]):
|
||||
CALL example.procedure(1) YIELD args, result;
|
||||
Naturally, you may pass in different arguments or yield less fields.
|
||||
'''
|
||||
if not callable(func):
|
||||
raise TypeError("Expected a callable object, got an instance of '{}'"
|
||||
.format(type(func)))
|
||||
if inspect.iscoroutinefunction(func):
|
||||
raise TypeError("Callable must not be 'async def' function")
|
||||
if sys.version_info >= (3, 6):
|
||||
if inspect.isasyncgenfunction(func):
|
||||
raise TypeError("Callable must not be 'async def' function")
|
||||
if inspect.isgeneratorfunction(func):
|
||||
raise NotImplementedError("Generator functions are not supported")
|
||||
raise_if_does_not_meet_requirements(func)
|
||||
sig = inspect.signature(func)
|
||||
params = tuple(sig.parameters.values())
|
||||
if params and params[0].annotation is ProcCtx:
|
||||
@@ -777,3 +811,133 @@ def read_proc(func: typing.Callable[..., Record]):
|
||||
else:
|
||||
mgp_proc.add_result(name, _typing_to_cypher_type(type_))
|
||||
return func
|
||||
|
||||
|
||||
class InvalidMessageError(Exception):
|
||||
'''Signals using a message instance outside of the registered transformation.'''
|
||||
pass
|
||||
|
||||
|
||||
class Message:
|
||||
'''Represents a message from a stream.'''
|
||||
__slots__ = ('_message',)
|
||||
|
||||
def __init__(self, message):
|
||||
if not isinstance(message, _mgp.Message):
|
||||
raise TypeError(
|
||||
"Expected '_mgp.Message', got '{}'".format(type(message)))
|
||||
self._message = message
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
# This is the same as the shallow copy, because we want to share the
|
||||
# underlying C struct. Besides, it doesn't make much sense to actually
|
||||
# copy _mgp.Messages as that always references all the messages.
|
||||
return Message(self._message)
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
'''Return True if `self` is in valid context and may be used.'''
|
||||
return self._message.is_valid()
|
||||
|
||||
def payload(self) -> bytes:
|
||||
if not self.is_valid():
|
||||
raise InvalidMessageError()
|
||||
return self._message.payload()
|
||||
|
||||
def topic_name(self) -> str:
|
||||
if not self.is_valid():
|
||||
raise InvalidMessageError()
|
||||
return self._message.topic_name()
|
||||
|
||||
def key(self) -> bytes:
|
||||
if not self.is_valid():
|
||||
raise InvalidMessageError()
|
||||
return self._message.key()
|
||||
|
||||
def timestamp(self) -> int:
|
||||
if not self.is_valid():
|
||||
raise InvalidMessageError()
|
||||
return self._message.timestamp()
|
||||
|
||||
|
||||
class InvalidMessagesError(Exception):
|
||||
'''Signals using a messages instance outside of the registered transformation.'''
|
||||
pass
|
||||
|
||||
|
||||
class Messages:
|
||||
'''Represents a list of messages from a stream.'''
|
||||
__slots__ = ('_messages',)
|
||||
|
||||
def __init__(self, messages):
|
||||
if not isinstance(messages, _mgp.Messages):
|
||||
raise TypeError(
|
||||
"Expected '_mgp.Messages', got '{}'".format(type(messages)))
|
||||
self._messages = messages
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
# This is the same as the shallow copy, because we want to share the
|
||||
# underlying C struct. Besides, it doesn't make much sense to actually
|
||||
# copy _mgp.Messages as that always references all the messages.
|
||||
return Messages(self._messages)
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
'''Return True if `self` is in valid context and may be used.'''
|
||||
return self._messages.is_valid()
|
||||
|
||||
def message_at(self, id: int) -> Message:
|
||||
'''Raise InvalidMessagesError if context is invalid.'''
|
||||
if not self.is_valid():
|
||||
raise InvalidMessagesError()
|
||||
return Message(self._messages.message_at(id))
|
||||
|
||||
def total_messages(self) -> int:
|
||||
'''Raise InvalidContextError if context is invalid.'''
|
||||
if not self.is_valid():
|
||||
raise InvalidMessagesError()
|
||||
return self._messages.total_messages()
|
||||
|
||||
|
||||
class TransCtx:
|
||||
'''Context of a transformation being executed.
|
||||
|
||||
Access to a TransCtx is only valid during a single execution of a transformation.
|
||||
You should not globally store a TransCtx instance.
|
||||
'''
|
||||
__slots__ = ('_graph')
|
||||
|
||||
def __init__(self, graph):
|
||||
if not isinstance(graph, _mgp.Graph):
|
||||
raise TypeError(
|
||||
"Expected '_mgp.Graph', got '{}'".format(type(graph)))
|
||||
self._graph = Graph(graph)
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
return self._graph.is_valid()
|
||||
|
||||
@property
|
||||
def graph(self) -> Graph:
|
||||
'''Raise InvalidContextError if context is invalid.'''
|
||||
if not self.is_valid():
|
||||
raise InvalidContextError()
|
||||
return self._graph
|
||||
|
||||
|
||||
def transformation(func: typing.Callable[..., Record]):
|
||||
raise_if_does_not_meet_requirements(func)
|
||||
sig = inspect.signature(func)
|
||||
params = tuple(sig.parameters.values())
|
||||
if not params or not params[0].annotation is Messages:
|
||||
if not len(params) == 2 or not params[1].annotation is Messages:
|
||||
raise NotImplementedError(
|
||||
"Valid signatures for transformations are (TransCtx, Messages) or (Messages)")
|
||||
if params[0].annotation is TransCtx:
|
||||
@functools.wraps(func)
|
||||
def wrapper(graph, messages):
|
||||
return func(TransCtx(graph), messages)
|
||||
_mgp._MODULE.add_transformation(wrapper)
|
||||
else:
|
||||
@functools.wraps(func)
|
||||
def wrapper(graph, messages):
|
||||
return func(messages)
|
||||
_mgp._MODULE.add_transformation(wrapper)
|
||||
return func
|
||||
|
||||
92
init
92
init
@@ -1,35 +1,14 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
required_pkgs=(git arcanist # 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
|
||||
libssl-dev
|
||||
libseccomp-dev
|
||||
python3 python-virtualenv python3-pip # for qa, macro_benchmark and stress tests
|
||||
python3-yaml # for the configuration generator
|
||||
uuid-dev # mg-utils
|
||||
libcurl4-openssl-dev # mg-requests
|
||||
sbcl # for custom Lisp C++ preprocessing
|
||||
php-xml # for arcanist linters
|
||||
)
|
||||
|
||||
optional_pkgs=(doxygen graphviz # source documentation generators
|
||||
php-cli # for user technical documentation generators
|
||||
mono-runtime mono-mcs nodejs # for driver tests
|
||||
)
|
||||
|
||||
use_sudo=0
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
cd "$DIR"
|
||||
|
||||
source "$DIR/environment/util.sh"
|
||||
|
||||
function print_help () {
|
||||
echo "Usage: $0 [OPTION]"
|
||||
echo -e "Check for missing packages and install them if possible.\n"
|
||||
echo -e "Check for missing packages and setup the project.\n"
|
||||
echo "Optional arguments:"
|
||||
echo -e " -s\tuse sudo apt-get for installing packages"
|
||||
echo -e " -h\tdisplay this help and exit"
|
||||
}
|
||||
|
||||
@@ -45,10 +24,7 @@ function setup_virtualenv () {
|
||||
# create new virtualenv
|
||||
virtualenv -p python3 ve3 || exit 1
|
||||
source ve3/bin/activate
|
||||
# we need to increase the timeout for pip because our local cache server
|
||||
# can sometimes be stupid, see: https://github.com/devpi/devpi/issues/208
|
||||
pip --timeout 1000 install -i http://deps.memgraph.io:3141/root/pypi \
|
||||
--trusted-host deps.memgraph.io -r requirements.txt || exit 1
|
||||
pip --timeout 1000 install -r requirements.txt || exit 1
|
||||
deactivate
|
||||
|
||||
popd > /dev/null
|
||||
@@ -59,9 +35,6 @@ if [[ $# -gt 1 ]]; then
|
||||
exit 1
|
||||
elif [[ $# -eq 1 ]]; then
|
||||
case "$1" in
|
||||
-s)
|
||||
use_sudo=1
|
||||
;;
|
||||
-h)
|
||||
print_help
|
||||
exit 0
|
||||
@@ -74,39 +47,10 @@ elif [[ $# -eq 1 ]]; then
|
||||
esac
|
||||
fi
|
||||
|
||||
echo "Started installing dependencies for Memgraph"
|
||||
|
||||
required_missing=0
|
||||
|
||||
# install all dependencies on debian based operating systems
|
||||
for pkg in ${required_pkgs[@]}; do
|
||||
if dpkg -s $pkg 2>/dev/null >/dev/null; then
|
||||
echo "Found $pkg"
|
||||
elif (( $use_sudo )); then
|
||||
echo "Installing $pkg"
|
||||
if [[ ! `sudo apt-get -y install $pkg` ]]; then
|
||||
echo "Didn't install $pkg [required]"
|
||||
required_missing=1
|
||||
fi
|
||||
else
|
||||
echo "Missing $pkg [required]"
|
||||
required_missing=1
|
||||
fi
|
||||
done
|
||||
|
||||
for pkg in ${optional_pkgs[@]}; do
|
||||
if dpkg -s $pkg 2>/dev/null >/dev/null; then
|
||||
echo "Found $pkg [optional]"
|
||||
else
|
||||
echo "Missing $pkg [optional]"
|
||||
fi
|
||||
done
|
||||
|
||||
if (( $required_missing )); then
|
||||
echo "Missing required packages. EXITING!"
|
||||
echo "Please, install required packages and rerun $0 again."
|
||||
exit 2
|
||||
fi
|
||||
DISTRO=$(operating_system)
|
||||
echo "ALL BUILD PACKAGES: $($DIR/environment/os/$DISTRO.sh list MEMGRAPH_BUILD_DEPS)"
|
||||
$DIR/environment/os/$DISTRO.sh check MEMGRAPH_BUILD_DEPS
|
||||
echo "All packages are in-place..."
|
||||
|
||||
# create a default build directory
|
||||
mkdir -p ./build
|
||||
@@ -136,14 +80,14 @@ echo \
|
||||
(ql:quickload '(:lcp :lcp/test) :silent t)
|
||||
" | sbcl --script
|
||||
|
||||
# setup libs (download)
|
||||
# Setup libs (download).
|
||||
cd libs
|
||||
./cleanup.sh
|
||||
./setup.sh
|
||||
cd ..
|
||||
|
||||
# setup qa dependencies
|
||||
setup_virtualenv tests/qa
|
||||
# setup gql_behave dependencies
|
||||
setup_virtualenv tests/gql_behave
|
||||
|
||||
# setup stress dependencies
|
||||
setup_virtualenv tests/stress
|
||||
@@ -151,4 +95,18 @@ setup_virtualenv tests/stress
|
||||
# setup integration/ldap dependencies
|
||||
setup_virtualenv tests/integration/ldap
|
||||
|
||||
# Setup tests dependencies.
|
||||
# cd tests
|
||||
# ./setup.sh
|
||||
# cd ..
|
||||
# TODO(gitbuda): Remove setup_virtualenv, replace it with tests/ve3. Take care
|
||||
# of the build order because tests/setup.py builds pymgclient which depends on
|
||||
# mgclient which is build after this script by calling make.
|
||||
|
||||
echo "Done installing dependencies for Memgraph"
|
||||
|
||||
echo "Linking git hooks"
|
||||
for hook in $(find $DIR/.githooks -type f -printf "%f\n"); do
|
||||
ln -s -f "$DIR/.githooks/$hook" "$DIR/.git/hooks/$hook"
|
||||
echo "Added $hook hook"
|
||||
done;
|
||||
|
||||
1
libs/.gitignore
vendored
1
libs/.gitignore
vendored
@@ -4,3 +4,4 @@
|
||||
!cleanup.sh
|
||||
!CMakeLists.txt
|
||||
!__main.cpp
|
||||
!jemalloc.cmake
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
include(ExternalProject)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
include(ProcessorCount)
|
||||
ProcessorCount(NPROC)
|
||||
if (NPROC EQUAL 0)
|
||||
set(NPROC 1)
|
||||
endif()
|
||||
|
||||
set(LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
# convenience functions
|
||||
function(import_header_library name include_dir)
|
||||
add_library(${name} INTERFACE IMPORTED GLOBAL)
|
||||
@@ -87,11 +91,9 @@ import_external_library(antlr4 STATIC
|
||||
CMAKE_ARGS # http://stackoverflow.com/questions/37096062/get-a-basic-c-program-to-compile-using-clang-on-ubuntu-16/38385967#38385967
|
||||
-DWITH_LIBCXX=OFF # because of debian bug
|
||||
-DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=true
|
||||
-DCMAKE_CXX_STANDARD=20
|
||||
BUILD_COMMAND $(MAKE) antlr4_static
|
||||
# Make a License.txt out of thin air, so that antlr4.6 knows how to build.
|
||||
# When we upgrade antlr, this will no longer be needed.
|
||||
INSTALL_COMMAND touch ${CMAKE_CURRENT_SOURCE_DIR}/antlr4/runtime/Cpp/License.txt
|
||||
COMMAND $(MAKE) install)
|
||||
INSTALL_COMMAND $(MAKE) install)
|
||||
|
||||
# Setup google benchmark.
|
||||
import_external_library(benchmark STATIC
|
||||
@@ -102,7 +104,7 @@ import_external_library(benchmark STATIC
|
||||
|
||||
# setup fmt format
|
||||
import_external_library(fmt STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/fmt/lib/libfmt.a
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/fmt/${CMAKE_INSTALL_LIBDIR}/libfmt.a
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/fmt/include
|
||||
# Skip testing.
|
||||
CMAKE_ARGS -DFMT_TEST=OFF)
|
||||
@@ -149,24 +151,6 @@ import_external_library(gflags STATIC
|
||||
-DBUILD_gflags_nothreads_LIB=OFF
|
||||
-DGFLAGS_NO_FILENAMES=${GFLAGS_NO_FILENAMES})
|
||||
|
||||
# Setup google logging after gflags (so that glog can use it).
|
||||
set(GLOG_DISABLE_OPTIONS "0")
|
||||
if ("${CMAKE_BUILD_TYPE}" MATCHES "^(R|r)(E|e)(L|l).+")
|
||||
set(GLOG_DISABLE_OPTIONS "1")
|
||||
endif()
|
||||
|
||||
# Setup google logging after gflags (so that glog can use it).
|
||||
import_external_library(glog STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/glog/lib/libglog.a
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/glog/include
|
||||
DEPENDS gflags-proj
|
||||
CMAKE_ARGS -Dgflags_DIR=${CMAKE_CURRENT_SOURCE_DIR}/gflags/lib/cmake/gflags
|
||||
-DBUILD_TESTING=OFF
|
||||
-DGLOG_NO_FILENAMES=${GLOG_DISABLE_OPTIONS}
|
||||
-DGLOG_NO_STACKTRACE=${GLOG_DISABLE_OPTIONS}
|
||||
-DGLOG_NO_BUFFER_SETTINGS=${GLOG_DISABLE_OPTIONS}
|
||||
-DGLOG_NO_TIME_PID_FILENAME=${GLOG_DISABLE_OPTIONS})
|
||||
|
||||
# Setup cppitertools
|
||||
import_header_library(cppitertools ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
@@ -195,7 +179,9 @@ import_external_library(zlib STATIC
|
||||
import_external_library(rocksdb STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/rocksdb/lib/librocksdb.a
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/rocksdb/include
|
||||
CMAKE_ARGS -DUSE_RTTI=ON
|
||||
DEPENDS gflags-proj
|
||||
CMAKE_ARGS -Dgflags_DIR=${CMAKE_CURRENT_SOURCE_DIR}/gflags/lib/cmake/gflags
|
||||
-DUSE_RTTI=ON
|
||||
-DWITH_TESTS=OFF
|
||||
-DCMAKE_INSTALL_LIBDIR=lib
|
||||
-DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=true
|
||||
@@ -220,3 +206,38 @@ import_external_library(mgclient STATIC
|
||||
-DBUILD_TESTING=OFF)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
target_link_libraries(mgclient INTERFACE ${OPENSSL_LIBRARIES})
|
||||
|
||||
add_external_project(mgconsole
|
||||
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/mgconsole
|
||||
CMAKE_ARGS
|
||||
-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_BINARY_DIR}
|
||||
BUILD_COMMAND $(MAKE) mgconsole)
|
||||
|
||||
add_custom_target(mgconsole DEPENDS mgconsole-proj)
|
||||
|
||||
# Setup spdlog
|
||||
import_external_library(spdlog STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/spdlog/${CMAKE_INSTALL_LIBDIR}/libspdlog.a
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/spdlog/include
|
||||
BUILD_COMMAND $(MAKE) spdlog)
|
||||
|
||||
include(jemalloc.cmake)
|
||||
|
||||
# Setup librdkafka.
|
||||
import_external_library(librdkafka STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/librdkafka/lib/librdkafka.a
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/librdkafka/include
|
||||
CMAKE_ARGS -DRDKAFKA_BUILD_STATIC=ON
|
||||
-DRDKAFKA_BUILD_EXAMPLES=OFF
|
||||
-DRDKAFKA_BUILD_TESTS=OFF
|
||||
-DCMAKE_INSTALL_LIBDIR=lib
|
||||
-DWITH_SSL=ON
|
||||
# If we want SASL, we need to install it on build machines
|
||||
-DWITH_SASL=OFF)
|
||||
target_link_libraries(librdkafka INTERFACE ${OPENSSL_LIBRARIES} zlib)
|
||||
|
||||
import_library(librdkafka++ STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/librdkafka/lib/librdkafka++.a
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/librdkafka/include
|
||||
)
|
||||
target_link_libraries(librdkafka++ INTERFACE librdkafka)
|
||||
|
||||
55
libs/jemalloc.cmake
Normal file
55
libs/jemalloc.cmake
Normal file
@@ -0,0 +1,55 @@
|
||||
set(JEMALLOC_DIR "${LIB_DIR}/jemalloc")
|
||||
|
||||
set(JEMALLOC_SRCS
|
||||
${JEMALLOC_DIR}/src/arena.c
|
||||
${JEMALLOC_DIR}/src/background_thread.c
|
||||
${JEMALLOC_DIR}/src/base.c
|
||||
${JEMALLOC_DIR}/src/bin.c
|
||||
${JEMALLOC_DIR}/src/bitmap.c
|
||||
${JEMALLOC_DIR}/src/ckh.c
|
||||
${JEMALLOC_DIR}/src/ctl.c
|
||||
${JEMALLOC_DIR}/src/div.c
|
||||
${JEMALLOC_DIR}/src/extent.c
|
||||
${JEMALLOC_DIR}/src/extent_dss.c
|
||||
${JEMALLOC_DIR}/src/extent_mmap.c
|
||||
${JEMALLOC_DIR}/src/hash.c
|
||||
${JEMALLOC_DIR}/src/hook.c
|
||||
${JEMALLOC_DIR}/src/jemalloc.c
|
||||
${JEMALLOC_DIR}/src/large.c
|
||||
${JEMALLOC_DIR}/src/log.c
|
||||
${JEMALLOC_DIR}/src/malloc_io.c
|
||||
${JEMALLOC_DIR}/src/mutex.c
|
||||
${JEMALLOC_DIR}/src/mutex_pool.c
|
||||
${JEMALLOC_DIR}/src/nstime.c
|
||||
${JEMALLOC_DIR}/src/pages.c
|
||||
${JEMALLOC_DIR}/src/prng.c
|
||||
${JEMALLOC_DIR}/src/prof.c
|
||||
${JEMALLOC_DIR}/src/rtree.c
|
||||
${JEMALLOC_DIR}/src/sc.c
|
||||
${JEMALLOC_DIR}/src/stats.c
|
||||
${JEMALLOC_DIR}/src/sz.c
|
||||
${JEMALLOC_DIR}/src/tcache.c
|
||||
${JEMALLOC_DIR}/src/test_hooks.c
|
||||
${JEMALLOC_DIR}/src/ticker.c
|
||||
${JEMALLOC_DIR}/src/tsd.c
|
||||
${JEMALLOC_DIR}/src/witness.c
|
||||
${JEMALLOC_DIR}/src/safety_check.c
|
||||
)
|
||||
|
||||
add_library(jemalloc ${JEMALLOC_SRCS})
|
||||
target_include_directories(jemalloc PUBLIC "${JEMALLOC_DIR}/include")
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(jemalloc PUBLIC Threads::Threads)
|
||||
|
||||
target_compile_definitions(jemalloc PRIVATE -DJEMALLOC_NO_PRIVATE_NAMESPACE)
|
||||
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "DEBUG")
|
||||
target_compile_definitions(jemalloc PRIVATE -DJEMALLOC_DEBUG=1 -DJEMALLOC_PROF=1)
|
||||
endif()
|
||||
|
||||
target_compile_options(jemalloc PRIVATE -Wno-redundant-decls)
|
||||
# for RTLD_NEXT
|
||||
target_compile_definitions(jemalloc PRIVATE _GNU_SOURCE)
|
||||
|
||||
set_property(TARGET jemalloc APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS USE_JEMALLOC=1)
|
||||
223
libs/setup.sh
223
libs/setup.sh
@@ -1,9 +1,11 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
# Download external dependencies.
|
||||
# Don't forget to add/update the license in release/third-party-licenses of added/updated libs!
|
||||
|
||||
local_cache_host=${MGDEPS_CACHE_HOST_PORT:-mgdeps-cache:8000}
|
||||
working_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
cd ${working_dir}
|
||||
cd "${working_dir}"
|
||||
|
||||
# Clones a git repository and optionally cherry picks additional commits. The
|
||||
# function will try to preserve any local changes in the repo.
|
||||
@@ -15,7 +17,11 @@ clone () {
|
||||
shift 3
|
||||
# Clone if there's no repo.
|
||||
if [[ ! -d "$dir_name" ]]; then
|
||||
git clone "$git_repo" "$dir_name"
|
||||
echo "Cloning from $git_repo"
|
||||
# If the clone fails, it doesn't make sense to continue with the function
|
||||
# execution but the whole script should continue executing because we might
|
||||
# clone the same repo from a different source.
|
||||
git clone "$git_repo" "$dir_name" || return 1
|
||||
fi
|
||||
pushd "$dir_name"
|
||||
# Just fetch new commits from remote repository. Don't merge/pull them in, so
|
||||
@@ -29,12 +35,17 @@ clone () {
|
||||
# Stash regardless of local_changes, so that a user gets a message on stdout.
|
||||
git stash
|
||||
# Checkout the primary commit (there's no need to pull/merge).
|
||||
git checkout $checkout_id
|
||||
# The checkout fail should exit this script immediately because the target
|
||||
# commit is not there and that will most likely create build-time errors.
|
||||
git checkout "$checkout_id" || exit 1
|
||||
# Apply any optional cherry pick fixes.
|
||||
while [[ $# -ne 0 ]]; do
|
||||
local cherry_pick_id=$1
|
||||
shift
|
||||
git cherry-pick -n $cherry_pick_id
|
||||
# The cherry-pick fail should exit this script immediately because the
|
||||
# target commit is not there and that will most likely create build-time
|
||||
# errors.
|
||||
git cherry-pick -n "$cherry_pick_id" || exit 1
|
||||
done
|
||||
# Reapply any local changes.
|
||||
if [[ $local_changes == true ]]; then
|
||||
@@ -43,96 +54,198 @@ clone () {
|
||||
popd
|
||||
}
|
||||
|
||||
file_get_try_double () {
|
||||
primary_url="$1"
|
||||
secondary_url="$2"
|
||||
echo "Download primary from $primary_url secondary from $secondary_url"
|
||||
if [ -z "$primary_url" ]; then echo "Primary should not be empty." && exit 1; fi
|
||||
if [ -z "$secondary_url" ]; then echo "Secondary should not be empty." && exit 1; fi
|
||||
filename="$(basename "$secondary_url")"
|
||||
wget -nv "$primary_url" -O "$filename" || wget -nv "$secondary_url" -O "$filename" || exit 1
|
||||
echo ""
|
||||
}
|
||||
|
||||
repo_clone_try_double () {
|
||||
primary_url="$1"
|
||||
secondary_url="$2"
|
||||
folder_name="$3"
|
||||
ref="$4"
|
||||
echo "Cloning primary from $primary_url secondary from $secondary_url"
|
||||
if [ -z "$primary_url" ]; then echo "Primary should not be empty." && exit 1; fi
|
||||
if [ -z "$secondary_url" ]; then echo "Secondary should not be empty." && exit 1; fi
|
||||
if [ -z "$folder_name" ]; then echo "Clone folder should not be empty." && exit 1; fi
|
||||
if [ -z "$ref" ]; then echo "Git clone ref should not be empty." && exit 1; fi
|
||||
clone "$primary_url" "$folder_name" "$ref" || clone "$secondary_url" "$folder_name" "$ref" || exit 1
|
||||
echo ""
|
||||
}
|
||||
|
||||
# List all dependencies.
|
||||
|
||||
# The reason for introducing primary and secondary urls are:
|
||||
# * HTTPS is hard to cache
|
||||
# * Remote development workflow is more flexible if people don't have to connect to VPN
|
||||
# * Direct download from the "source of truth" is slower and unreliable because of the whole internet in-between
|
||||
# * When a new dependency has to be added, both urls could be the same, later someone could optimize if required
|
||||
|
||||
# The goal of having primary urls is to have links to the "local" cache of
|
||||
# dependencies where these dependencies could be downloaded as fast as
|
||||
# possible. The actual cache server could be on your local machine, on a
|
||||
# dedicated machine inside the build cluster or on the actual build machine.
|
||||
# Download from primary_urls might fail because the cache is not installed.
|
||||
declare -A primary_urls=(
|
||||
["antlr4-code"]="http://$local_cache_host/git/antlr4.git"
|
||||
["antlr4-generator"]="http://$local_cache_host/file/antlr-4.9.2-complete.jar"
|
||||
["cppitertools"]="http://$local_cache_host/git/cppitertools.git"
|
||||
["fmt"]="http://$local_cache_host/git/fmt.git"
|
||||
["rapidcheck"]="http://$local_cache_host/git/rapidcheck.git"
|
||||
["gbenchmark"]="http://$local_cache_host/git/benchmark.git"
|
||||
["gtest"]="http://$local_cache_host/git/googletest.git"
|
||||
["gflags"]="http://$local_cache_host/git/gflags.git"
|
||||
["libbcrypt"]="http://$local_cache_host/git/libbcrypt.git"
|
||||
["bzip2"]="http://$local_cache_host/git/bzip2.git"
|
||||
["zlib"]="http://$local_cache_host/git/zlib.git"
|
||||
["rocksdb"]="http://$local_cache_host/git/rocksdb.git"
|
||||
["mgclient"]="http://$local_cache_host/git/mgclient.git"
|
||||
["pymgclient"]="http://$local_cache_host/git/pymgclient.git"
|
||||
["mgconsole"]="http://$local_cache_host/git/mgconsole.git"
|
||||
["spdlog"]="http://$local_cache_host/git/spdlog"
|
||||
["jemalloc"]="http://$local_cache_host/git/jemalloc.git"
|
||||
["nlohmann"]="http://$local_cache_host/file/nlohmann/json/b3e5cb7f20dcc5c806e418df34324eca60d17d4e/single_include/nlohmann/json.hpp"
|
||||
["neo4j"]="http://$local_cache_host/file/neo4j-community-3.2.3-unix.tar.gz"
|
||||
["librdkafka"]="http://$local_cache_host/git/librdkafka.git"
|
||||
)
|
||||
|
||||
# The goal of secondary urls is to have links to the "source of truth" of
|
||||
# dependencies, e.g., Github or S3. Download from secondary urls, if happens
|
||||
# at all, should never fail. In other words, if it fails, the whole build
|
||||
# should fail.
|
||||
declare -A secondary_urls=(
|
||||
["antlr4-code"]="https://github.com/antlr/antlr4.git"
|
||||
["antlr4-generator"]="http://www.antlr.org/download/antlr-4.9.2-complete.jar"
|
||||
["cppitertools"]="https://github.com/ryanhaining/cppitertools.git"
|
||||
["fmt"]="https://github.com/fmtlib/fmt.git"
|
||||
["rapidcheck"]="https://github.com/emil-e/rapidcheck.git"
|
||||
["gbenchmark"]="https://github.com/google/benchmark.git"
|
||||
["gtest"]="https://github.com/google/googletest.git"
|
||||
["gflags"]="https://github.com/memgraph/gflags.git"
|
||||
["libbcrypt"]="https://github.com/rg3/libbcrypt"
|
||||
["bzip2"]="https://github.com/VFR-maniac/bzip2"
|
||||
["zlib"]="https://github.com/madler/zlib.git"
|
||||
["rocksdb"]="https://github.com/facebook/rocksdb.git"
|
||||
["mgclient"]="https://github.com/memgraph/mgclient.git"
|
||||
["pymgclient"]="https://github.com/memgraph/pymgclient.git"
|
||||
["mgconsole"]="http://github.com/memgraph/mgconsole.git"
|
||||
["spdlog"]="https://github.com/gabime/spdlog"
|
||||
["jemalloc"]="https://github.com/jemalloc/jemalloc.git"
|
||||
["nlohmann"]="https://raw.githubusercontent.com/nlohmann/json/b3e5cb7f20dcc5c806e418df34324eca60d17d4e/single_include/nlohmann/json.hpp"
|
||||
["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"
|
||||
)
|
||||
|
||||
# antlr
|
||||
antlr_generator_filename="antlr-4.6-complete.jar"
|
||||
# wget -O ${antlr_generator_filename} http://www.antlr.org/download/${antlr_generator_filename}
|
||||
wget -nv -O ${antlr_generator_filename} http://deps.memgraph.io/${antlr_generator_filename}
|
||||
# git clone https://github.com/antlr/antlr4.git
|
||||
antlr4_tag="aacd2a2c95816d8dc1c05814051d631bfec4cf3e" # v4.6
|
||||
clone git://deps.memgraph.io/antlr4.git antlr4 $antlr4_tag
|
||||
# fix missing include
|
||||
sed -i 's/^#pragma once/#pragma once\n#include <functional>/' antlr4/runtime/Cpp/runtime/src/support/CPPUtils.h
|
||||
file_get_try_double "${primary_urls[antlr4-generator]}" "${secondary_urls[antlr4-generator]}"
|
||||
|
||||
antlr4_tag="5e5b6d35b4183fd330102c40947b95c4b5c6abb5" # v4.9.2
|
||||
repo_clone_try_double "${primary_urls[antlr4-code]}" "${secondary_urls[antlr4-code]}" "antlr4" "$antlr4_tag"
|
||||
# remove shared library from install dependencies
|
||||
sed -i 's/install(TARGETS antlr4_shared/install(TARGETS antlr4_shared OPTIONAL/' antlr4/runtime/Cpp/runtime/CMakeLists.txt
|
||||
# fix issue https://github.com/antlr/antlr4/issues/3194 - should update Antlr commit once the PR related to the issue gets merged
|
||||
sed -i 's/std::is_nothrow_copy_constructible/std::is_copy_constructible/' antlr4/runtime/Cpp/runtime/src/support/Any.h
|
||||
# replace the utf8cpp version which is used because the older one uses gtest that doesn't
|
||||
# compile with the newer compilers because of uninitialized variable
|
||||
# the newer utf8cpp switched to ftest
|
||||
sed -i 's/v3.1.1/v3.2.1/' antlr4/runtime/Cpp/runtime/CMakeLists.txt
|
||||
|
||||
# cppitertools
|
||||
# Use our fork that uses experimental/optional instead of unique_ptr in
|
||||
# DerefHolder. Once we move memgraph to c++17 we can use cpp17 branch from
|
||||
# original repo.
|
||||
# git clone https://github.com/memgraph/cppitertools.git
|
||||
cppitertools_tag="4231e0bc6fba2737b2a7a8a1576cf06186b0de6a" # experimental_optional 17 Aug 2017
|
||||
clone git://deps.memgraph.io/cppitertools.git cppitertools $cppitertools_tag
|
||||
# cppitertools v2.0 2019-12-23
|
||||
cppitertools_ref="cb3635456bdb531121b82b4d2e3afc7ae1f56d47"
|
||||
repo_clone_try_double "${primary_urls[cppitertools]}" "${secondary_urls[cppitertools]}" "cppitertools" "$cppitertools_ref"
|
||||
|
||||
# fmt
|
||||
# git clone https://github.com/fmtlib/fmt.git
|
||||
fmt_tag="7fa8f8fa48b0903deab5bb42e6760477173ac485" # v3.0.1
|
||||
# Commit which fixes an issue when compiling with C++14 and higher.
|
||||
fmt_cxx14_fix="b9aaa507fc49680d037fd84c043f747a395bce04"
|
||||
clone git://deps.memgraph.io/fmt.git fmt $fmt_tag $fmt_cxx14_fix
|
||||
fmt_tag="7bdf0628b1276379886c7f6dda2cef2b3b374f0b" # (2020-11-25)
|
||||
repo_clone_try_double "${primary_urls[fmt]}" "${secondary_urls[fmt]}" "fmt" "$fmt_tag"
|
||||
|
||||
# rapidcheck
|
||||
# git clone https://github.com/emil-e/rapidcheck.git
|
||||
rapidcheck_tag="853e14f0f4313a9eb3c71e24848373e7b843dfd1" # Jun 23, 2017
|
||||
clone git://deps.memgraph.io/rapidcheck.git rapidcheck $rapidcheck_tag
|
||||
rapidcheck_tag="7bc7d302191a4f3d0bf005692677126136e02f60" # (2020-05-04)
|
||||
repo_clone_try_double "${primary_urls[rapidcheck]}" "${secondary_urls[rapidcheck]}" "rapidcheck" "$rapidcheck_tag"
|
||||
|
||||
# google benchmark
|
||||
# git clone https://github.com/google/benchmark.git
|
||||
benchmark_tag="4f8bfeae470950ef005327973f15b0044eceaceb" # v1.1.0
|
||||
clone git://deps.memgraph.io/benchmark.git benchmark $benchmark_tag
|
||||
repo_clone_try_double "${primary_urls[gbenchmark]}" "${secondary_urls[gbenchmark]}" "benchmark" "$benchmark_tag"
|
||||
|
||||
# google test
|
||||
# git clone https://github.com/google/googletest.git
|
||||
googletest_tag="ec44c6c1675c25b9827aacd08c02433cccde7780" # v1.8.0
|
||||
clone git://deps.memgraph.io/googletest.git googletest $googletest_tag
|
||||
|
||||
# google logging
|
||||
# git clone https://github.com/memgraph/glog.git
|
||||
glog_tag="042a21657e79784226babab8b942f7bd0949635f" # custom version (v0.3.5+)
|
||||
clone git://deps.memgraph.io/glog.git glog $glog_tag
|
||||
repo_clone_try_double "${primary_urls[gtest]}" "${secondary_urls[gtest]}" "googletest" "$googletest_tag"
|
||||
|
||||
# google flags
|
||||
# git clone https://github.com/memgraph/gflags.git
|
||||
gflags_tag="b37ceb03a0e56c9f15ce80409438a555f8a67b7c" # custom version (May 6, 2017)
|
||||
clone git://deps.memgraph.io/gflags.git gflags $gflags_tag
|
||||
repo_clone_try_double "${primary_urls[gflags]}" "${secondary_urls[gflags]}" "gflags" "$gflags_tag"
|
||||
|
||||
# libbcrypt
|
||||
# git clone https://github.com/rg3/libbcrypt
|
||||
libbcrypt_tag="8aa32ad94ebe06b76853b0767c910c9fbf7ccef4" # custom version (Dec 16, 2016)
|
||||
clone git://deps.memgraph.io/libbcrypt.git libbcrypt $libbcrypt_tag
|
||||
repo_clone_try_double "${primary_urls[libbcrypt]}" "${secondary_urls[libbcrypt]}" "libbcrypt" "$libbcrypt_tag"
|
||||
|
||||
# neo4j
|
||||
wget -nv http://deps.memgraph.io/neo4j-community-3.2.3-unix.tar.gz -O neo4j.tar.gz
|
||||
tar -xzf neo4j.tar.gz
|
||||
rm -rf neo4j
|
||||
file_get_try_double "${primary_urls[neo4j]}" "${secondary_urls[neo4j]}"
|
||||
tar -xzf neo4j-community-3.2.3-unix.tar.gz
|
||||
mv neo4j-community-3.2.3 neo4j
|
||||
rm neo4j.tar.gz
|
||||
rm neo4j-community-3.2.3-unix.tar.gz
|
||||
|
||||
# nlohmann json
|
||||
# We wget header instead of cloning repo since repo is huge (lots of test data).
|
||||
# We use head on Sep 1, 2017 instead of last release since it was long time ago.
|
||||
mkdir -p json
|
||||
cd json
|
||||
# wget "https://raw.githubusercontent.com/nlohmann/json/91e003285312167ad8365f387438ea371b465a7e/src/json.hpp"
|
||||
wget -nv http://deps.memgraph.io/json.hpp
|
||||
file_get_try_double "${primary_urls[nlohmann]}" "${secondary_urls[nlohmann]}"
|
||||
cd ..
|
||||
|
||||
bzip2_tag="0405487e2b1de738e7f1c8afb50d19cf44e8d580" # v1.0.6 (May 26, 2011)
|
||||
clone git://deps.memgraph.io/bzip2.git bzip2 $bzip2_tag
|
||||
repo_clone_try_double "${primary_urls[bzip2]}" "${secondary_urls[bzip2]}" "bzip2" "$bzip2_tag"
|
||||
|
||||
zlib_tag="cacf7f1d4e3d44d871b605da3b647f07d718623f" # v1.2.11.
|
||||
clone git://deps.memgraph.io/zlib.git zlib $zlib_tag
|
||||
repo_clone_try_double "${primary_urls[zlib]}" "${secondary_urls[zlib]}" "zlib" "$zlib_tag"
|
||||
# remove shared library from install dependencies
|
||||
sed -i 's/install(TARGETS zlib zlibstatic/install(TARGETS zlibstatic/g' zlib/CMakeLists.txt
|
||||
|
||||
rocksdb_tag="641fae60f63619ed5d0c9d9e4c4ea5a0ffa3e253" # v5.18.3 Feb 11, 2019
|
||||
clone git://deps.memgraph.io/rocksdb.git rocksdb $rocksdb_tag
|
||||
# fix compilation flags to work with clang 8
|
||||
sed -i 's/-Wshadow/-Wno-defaulted-function-deleted/' rocksdb/CMakeLists.txt
|
||||
rocksdb_tag="f3e33549c151f30ac4eb7c22356c6d0331f37652" # (2020-10-14)
|
||||
repo_clone_try_double "${primary_urls[rocksdb]}" "${secondary_urls[rocksdb]}" "rocksdb" "$rocksdb_tag"
|
||||
# remove shared library from install dependencies
|
||||
sed -i 's/TARGETS ${ROCKSDB_SHARED_LIB}/TARGETS ${ROCKSDB_SHARED_LIB} OPTIONAL/' rocksdb/CMakeLists.txt
|
||||
|
||||
# mgclient
|
||||
mgclient_tag="fe94b3631385ef5dbe40a3d8458860dbcc33e6ea" # May 27, 2019
|
||||
# git clone https://github.com/memgraph/mgclient.git
|
||||
clone git://deps.memgraph.io/mgclient.git mgclient $mgclient_tag
|
||||
mgclient_tag="v1.2.0" # (2021-01-14)
|
||||
repo_clone_try_double "${primary_urls[mgclient]}" "${secondary_urls[mgclient]}" "mgclient" "$mgclient_tag"
|
||||
sed -i 's/\${CMAKE_INSTALL_LIBDIR}/lib/' mgclient/src/CMakeLists.txt
|
||||
|
||||
# pymgclient
|
||||
pymgclient_tag="4f85c179e56302d46a1e3e2cf43509db65f062b3" # (2021-01-15)
|
||||
repo_clone_try_double "${primary_urls[pymgclient]}" "${secondary_urls[pymgclient]}" "pymgclient" "$pymgclient_tag"
|
||||
|
||||
# mgconsole
|
||||
mgconsole_tag="01ae99bfce772e540e75c076ba03cf06c0c2ac7d" # (2021-05-26)
|
||||
repo_clone_try_double "${primary_urls[mgconsole]}" "${secondary_urls[mgconsole]}" "mgconsole" "$mgconsole_tag"
|
||||
|
||||
spdlog_tag="46d418164dd4cd9822cf8ca62a116a3f71569241" # (2020-12-01)
|
||||
repo_clone_try_double "${primary_urls[spdlog]}" "${secondary_urls[spdlog]}" "spdlog" "$spdlog_tag"
|
||||
|
||||
jemalloc_tag="ea6b3e973b477b8061e0076bb257dbd7f3faa756" # (2021-02-11)
|
||||
repo_clone_try_double "${primary_urls[jemalloc]}" "${secondary_urls[jemalloc]}" "jemalloc" "$jemalloc_tag"
|
||||
pushd jemalloc
|
||||
# ThreadPool select job randomly, and there can be some threads that had been
|
||||
# performed some memory heavy task before and will be inactive for some time,
|
||||
# but until it will became active again, the memory will not be freed since by
|
||||
# default each thread has it's own arena, but there should be not more then
|
||||
# 4*CPU arenas (see opt.nareans description).
|
||||
#
|
||||
# By enabling percpu_arena number of arenas limited to number of CPUs and hence
|
||||
# this problem should go away.
|
||||
#
|
||||
# muzzy_decay_ms -- use MADV_FREE when available on newer Linuxes, to
|
||||
# avoid spurious latencies and additional work associated with
|
||||
# MADV_DONTNEED. See
|
||||
# https://github.com/ClickHouse/ClickHouse/issues/11121 for motivation.
|
||||
./autogen.sh --with-malloc-conf="percpu_arena:percpu,oversize_threshold:0,muzzy_decay_ms:5000,dirty_decay_ms:5000"
|
||||
popd
|
||||
|
||||
# librdkafka
|
||||
librdkafka_tag="v1.7.0" # (2021-05-06)
|
||||
repo_clone_try_double "${primary_urls[librdkafka]}" "${secondary_urls[librdkafka]}" "librdkafka" "$librdkafka_tag"
|
||||
|
||||
@@ -32,10 +32,6 @@ install(FILES example.py DESTINATION lib/memgraph/query_modules RENAME py_exampl
|
||||
|
||||
# Install the Python modules
|
||||
install(FILES graph_analyzer.py DESTINATION lib/memgraph/query_modules)
|
||||
install(FILES pagerank.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 wcc.py DESTINATION lib/memgraph/query_modules)
|
||||
|
||||
if (MG_ENTERPRISE)
|
||||
add_subdirectory(louvain)
|
||||
add_subdirectory(connectivity)
|
||||
endif()
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
set(MODULE src/connectivity_module.cpp)
|
||||
|
||||
include_directories(src)
|
||||
|
||||
add_library(connectivity SHARED ${MODULE})
|
||||
target_include_directories(connectivity PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
|
||||
# Strip the library in release build.
|
||||
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
|
||||
if (lower_build_type STREQUAL "release")
|
||||
add_custom_command(TARGET connectivity POST_BUILD
|
||||
COMMAND strip -s $<TARGET_FILE:connectivity>
|
||||
COMMENT "Stripping symbols and sections from connectivity module")
|
||||
endif()
|
||||
|
||||
install(PROGRAMS $<TARGET_FILE:connectivity>
|
||||
DESTINATION lib/memgraph/query_modules
|
||||
RENAME connectivity.so)
|
||||
@@ -1,131 +0,0 @@
|
||||
#include "mg_procedure.h"
|
||||
|
||||
#include <queue>
|
||||
#include <unordered_map>
|
||||
|
||||
// Finds weakly connected components of a graph.
|
||||
// Time complexity: O(|V|+|E|)
|
||||
static void weak(const mgp_list *args, const mgp_graph *graph,
|
||||
mgp_result *result, mgp_memory *memory) {
|
||||
std::unordered_map<int64_t, int64_t> vertex_component;
|
||||
mgp_vertices_iterator *vertices_iterator =
|
||||
mgp_graph_iter_vertices(graph, memory);
|
||||
if (vertices_iterator == nullptr) {
|
||||
mgp_result_set_error_msg(result, "Not enough memory");
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t curr_component = 0;
|
||||
for (const mgp_vertex *vertex = mgp_vertices_iterator_get(vertices_iterator);
|
||||
vertex != nullptr;
|
||||
vertex = mgp_vertices_iterator_next(vertices_iterator)) {
|
||||
mgp_vertex_id vertex_id = mgp_vertex_get_id(vertex);
|
||||
if (vertex_component.find(vertex_id.as_int) != vertex_component.end())
|
||||
continue;
|
||||
|
||||
// run bfs from current vertex
|
||||
std::queue<int64_t> q;
|
||||
q.push(vertex_id.as_int);
|
||||
vertex_component[vertex_id.as_int] = curr_component;
|
||||
while (!q.empty()) {
|
||||
mgp_vertex *v = mgp_graph_get_vertex_by_id(graph, {q.front()}, memory);
|
||||
if (v == nullptr) {
|
||||
mgp_vertices_iterator_destroy(vertices_iterator);
|
||||
mgp_result_set_error_msg(result, "Not enough memory");
|
||||
return;
|
||||
}
|
||||
|
||||
q.pop();
|
||||
|
||||
// iterate over inbound edges
|
||||
mgp_edges_iterator *edges_iterator = mgp_vertex_iter_in_edges(v, memory);
|
||||
if (edges_iterator == nullptr) {
|
||||
mgp_vertex_destroy(v);
|
||||
mgp_vertices_iterator_destroy(vertices_iterator);
|
||||
mgp_result_set_error_msg(result, "Not enough memory");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const mgp_edge *edge = mgp_edges_iterator_get(edges_iterator);
|
||||
edge != nullptr; edge = mgp_edges_iterator_next(edges_iterator)) {
|
||||
mgp_vertex_id next_id = mgp_vertex_get_id(mgp_edge_get_from(edge));
|
||||
if (vertex_component.find(next_id.as_int) != vertex_component.end())
|
||||
continue;
|
||||
vertex_component[next_id.as_int] = curr_component;
|
||||
q.push(next_id.as_int);
|
||||
}
|
||||
|
||||
// iterate over outbound edges
|
||||
mgp_edges_iterator_destroy(edges_iterator);
|
||||
edges_iterator = mgp_vertex_iter_out_edges(v, memory);
|
||||
if (edges_iterator == nullptr) {
|
||||
mgp_vertex_destroy(v);
|
||||
mgp_vertices_iterator_destroy(vertices_iterator);
|
||||
mgp_result_set_error_msg(result, "Not enough memory");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const mgp_edge *edge = mgp_edges_iterator_get(edges_iterator);
|
||||
edge != nullptr; edge = mgp_edges_iterator_next(edges_iterator)) {
|
||||
mgp_vertex_id next_id = mgp_vertex_get_id(mgp_edge_get_to(edge));
|
||||
if (vertex_component.find(next_id.as_int) != vertex_component.end())
|
||||
continue;
|
||||
vertex_component[next_id.as_int] = curr_component;
|
||||
q.push(next_id.as_int);
|
||||
}
|
||||
|
||||
mgp_vertex_destroy(v);
|
||||
mgp_edges_iterator_destroy(edges_iterator);
|
||||
}
|
||||
|
||||
++curr_component;
|
||||
}
|
||||
|
||||
mgp_vertices_iterator_destroy(vertices_iterator);
|
||||
|
||||
for (const auto &p : vertex_component) {
|
||||
mgp_result_record *record = mgp_result_new_record(result);
|
||||
if (record == nullptr) {
|
||||
mgp_result_set_error_msg(result, "Not enough memory");
|
||||
return;
|
||||
}
|
||||
|
||||
mgp_value *mem_id_value = mgp_value_make_int(p.first, memory);
|
||||
if (mem_id_value == nullptr) {
|
||||
mgp_result_set_error_msg(result, "Not enough memory");
|
||||
return;
|
||||
}
|
||||
|
||||
mgp_value *comp_value = mgp_value_make_int(p.second, memory);
|
||||
if (comp_value == nullptr) {
|
||||
mgp_value_destroy(mem_id_value);
|
||||
mgp_result_set_error_msg(result, "Not enough memory");
|
||||
return;
|
||||
}
|
||||
|
||||
int mem_id_inserted = mgp_result_record_insert(record, "id", mem_id_value);
|
||||
int comp_inserted =
|
||||
mgp_result_record_insert(record, "component", comp_value);
|
||||
|
||||
mgp_value_destroy(mem_id_value);
|
||||
mgp_value_destroy(comp_value);
|
||||
|
||||
if (!mem_id_inserted || !comp_inserted) {
|
||||
mgp_result_set_error_msg(result, "Not enough memory");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int mgp_init_module(struct mgp_module *module,
|
||||
struct mgp_memory *memory) {
|
||||
struct mgp_proc *wcc_proc =
|
||||
mgp_module_add_read_procedure(module, "weak", weak);
|
||||
if (!mgp_proc_add_result(wcc_proc, "id", mgp_type_int())) return 1;
|
||||
if (!mgp_proc_add_result(wcc_proc, "component", mgp_type_int())) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int mgp_shutdown_module() {
|
||||
return 0;
|
||||
}
|
||||
@@ -16,61 +16,101 @@
|
||||
// CALL example.procedure(1, 2) YIELD args, result;
|
||||
// CALL example.procedure(1) YIELD args, result;
|
||||
// Naturally, you may pass in different arguments or yield less fields.
|
||||
static void procedure(const struct mgp_list *args,
|
||||
const struct mgp_graph *graph, struct mgp_result *result,
|
||||
static void procedure(const struct mgp_list *args, const struct mgp_graph *graph, struct mgp_result *result,
|
||||
struct mgp_memory *memory) {
|
||||
struct mgp_list *args_copy = mgp_list_make_empty(mgp_list_size(args), memory);
|
||||
if (args_copy == NULL) goto error_memory;
|
||||
for (size_t i = 0; i < mgp_list_size(args); ++i) {
|
||||
int success = mgp_list_append(args_copy, mgp_list_at(args, i));
|
||||
if (!success) goto error_free_list;
|
||||
size_t args_size = 0;
|
||||
if (mgp_list_size(args, &args_size) != MGP_ERROR_NO_ERROR) {
|
||||
goto error_something_went_wrong;
|
||||
}
|
||||
struct mgp_list *args_copy = NULL;
|
||||
if (mgp_list_make_empty(args_size, memory, &args_copy) != MGP_ERROR_NO_ERROR) {
|
||||
goto error_something_went_wrong;
|
||||
}
|
||||
for (size_t i = 0; i < args_size; ++i) {
|
||||
const struct mgp_value *value = NULL;
|
||||
if (mgp_list_at(args, i, &value) != MGP_ERROR_NO_ERROR) {
|
||||
goto error_free_list;
|
||||
}
|
||||
if (mgp_list_append(args_copy, value) != MGP_ERROR_NO_ERROR) {
|
||||
goto error_free_list;
|
||||
}
|
||||
}
|
||||
struct mgp_result_record *record = NULL;
|
||||
if (mgp_result_new_record(result, &record) != MGP_ERROR_NO_ERROR) {
|
||||
goto error_free_list;
|
||||
}
|
||||
struct mgp_result_record *record = mgp_result_new_record(result);
|
||||
if (record == NULL) goto error_free_list;
|
||||
// Transfer ownership of args_copy to mgp_value.
|
||||
struct mgp_value *args_value = mgp_value_make_list(args_copy);
|
||||
if (args_value == NULL) goto error_free_list;
|
||||
int args_inserted = mgp_result_record_insert(record, "args", args_value);
|
||||
struct mgp_value *args_value = NULL;
|
||||
if (mgp_value_make_list(args_copy, &args_value) != MGP_ERROR_NO_ERROR) {
|
||||
goto error_free_list;
|
||||
}
|
||||
// Release `args_value` and contained `args_copy`.
|
||||
if (mgp_result_record_insert(record, "args", args_value) != MGP_ERROR_NO_ERROR) {
|
||||
mgp_value_destroy(args_value);
|
||||
goto error_something_went_wrong;
|
||||
}
|
||||
mgp_value_destroy(args_value);
|
||||
if (!args_inserted) goto error_memory;
|
||||
struct mgp_value *hello_world_value =
|
||||
mgp_value_make_string("Hello World!", memory);
|
||||
if (hello_world_value == NULL) goto error_memory;
|
||||
int result_inserted =
|
||||
mgp_result_record_insert(record, "result", hello_world_value);
|
||||
struct mgp_value *hello_world_value = NULL;
|
||||
if (mgp_value_make_string("Hello World!", memory, &hello_world_value) != MGP_ERROR_NO_ERROR) {
|
||||
goto error_something_went_wrong;
|
||||
}
|
||||
enum mgp_error insert_result = mgp_result_record_insert(record, "result", hello_world_value);
|
||||
mgp_value_destroy(hello_world_value);
|
||||
if (!result_inserted) goto error_memory;
|
||||
if (insert_result != MGP_ERROR_NO_ERROR) {
|
||||
goto error_something_went_wrong;
|
||||
}
|
||||
// We have successfully finished, so return without error reporting.
|
||||
return;
|
||||
|
||||
error_free_list:
|
||||
mgp_list_destroy(args_copy);
|
||||
error_memory:
|
||||
mgp_result_set_error_msg(result, "Not enough memory!");
|
||||
error_something_went_wrong:
|
||||
mgp_result_set_error_msg(result, "Something went wrong!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Each module needs to define mgp_init_module function.
|
||||
// Here you can register multiple procedures your module supports.
|
||||
int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) {
|
||||
struct mgp_proc *proc =
|
||||
mgp_module_add_read_procedure(module, "procedure", procedure);
|
||||
if (!proc) return 1;
|
||||
if (!mgp_proc_add_arg(proc, "required_arg",
|
||||
mgp_type_nullable(mgp_type_any())))
|
||||
struct mgp_proc *proc = NULL;
|
||||
if (mgp_module_add_read_procedure(module, "procedure", procedure, &proc) != MGP_ERROR_NO_ERROR) {
|
||||
return 1;
|
||||
struct mgp_value *null_value = mgp_value_make_null(memory);
|
||||
if (!mgp_proc_add_opt_arg(proc, "optional_arg",
|
||||
mgp_type_nullable(mgp_type_any()), null_value)) {
|
||||
}
|
||||
const struct mgp_type *any_type = NULL;
|
||||
if (mgp_type_any(&any_type) != MGP_ERROR_NO_ERROR) {
|
||||
return 1;
|
||||
}
|
||||
const struct mgp_type *nullable_any_type = NULL;
|
||||
if (mgp_type_nullable(any_type, &nullable_any_type) != MGP_ERROR_NO_ERROR) {
|
||||
return 1;
|
||||
}
|
||||
if (mgp_proc_add_arg(proc, "required_arg", nullable_any_type) != MGP_ERROR_NO_ERROR) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct mgp_value *null_value = NULL;
|
||||
if (mgp_value_make_null(memory, &null_value) != MGP_ERROR_NO_ERROR) {
|
||||
return 1;
|
||||
}
|
||||
if (mgp_proc_add_opt_arg(proc, "optional_arg", nullable_any_type, null_value) != MGP_ERROR_NO_ERROR) {
|
||||
mgp_value_destroy(null_value);
|
||||
return 1;
|
||||
}
|
||||
mgp_value_destroy(null_value);
|
||||
if (!mgp_proc_add_result(proc, "result", mgp_type_string())) return 1;
|
||||
if (!mgp_proc_add_result(proc, "args",
|
||||
mgp_type_list(mgp_type_nullable(mgp_type_any()))))
|
||||
const struct mgp_type *string = NULL;
|
||||
if (mgp_type_string(&string) != MGP_ERROR_NO_ERROR) {
|
||||
return 1;
|
||||
}
|
||||
if (mgp_proc_add_result(proc, "result", string) != MGP_ERROR_NO_ERROR) {
|
||||
return 1;
|
||||
}
|
||||
const struct mgp_type *list_of_anything = NULL;
|
||||
if (mgp_type_list(nullable_any_type, &list_of_anything) != MGP_ERROR_NO_ERROR) {
|
||||
return 1;
|
||||
}
|
||||
if (mgp_proc_add_result(proc, "args", list_of_anything)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
import sys
|
||||
import mgp
|
||||
|
||||
from collections import OrderedDict
|
||||
from itertools import chain, repeat
|
||||
from inspect import cleandoc
|
||||
from typing import List, Tuple, Optional
|
||||
import networkx as nx
|
||||
from typing import List, Tuple
|
||||
try:
|
||||
import networkx as nx
|
||||
except ImportError as import_error:
|
||||
sys.stderr.write((
|
||||
'\n'
|
||||
'NOTE: Please install networkx to be able to use graph_analyzer '
|
||||
'module. Using Python:\n'
|
||||
+ sys.version +
|
||||
'\n'))
|
||||
raise import_error
|
||||
# Imported last because it also depends on networkx.
|
||||
from mgp_networkx import MemgraphMultiDiGraph # noqa E402
|
||||
|
||||
|
||||
_MAX_LIST_SIZE = 10
|
||||
@@ -47,7 +58,7 @@ def analyze(context: mgp.ProcCtx,
|
||||
Example call (with parameter):
|
||||
CALL graph_analyzer.analyze(['nodes', 'edges']) YIELD *;
|
||||
'''
|
||||
g = _convert_to_multidigraph(context)
|
||||
g = MemgraphMultiDiGraph(ctx=context)
|
||||
recs = _analyze_graph(context, g, analyses)
|
||||
return [mgp.Record(name=name, value=value) for name, value in recs]
|
||||
|
||||
@@ -81,7 +92,11 @@ def analyze_subgraph(context: mgp.ProcCtx,
|
||||
YIELD *
|
||||
RETURN name, value;
|
||||
'''
|
||||
g = _convert_to_subgraph_multidigraph(context, vertices, edges)
|
||||
vertices, edges = map(set, [vertices, edges])
|
||||
g = nx.subgraph_view(
|
||||
MemgraphMultiDiGraph(ctx=context),
|
||||
lambda n: n in vertices,
|
||||
lambda n1, n2, e: e in edges)
|
||||
recs = _analyze_graph(context, g, analyses)
|
||||
return [mgp.Record(name=name, value=value) for name, value in recs]
|
||||
|
||||
@@ -263,36 +278,3 @@ def _strongly_components(g: nx.MultiDiGraph):
|
||||
'''Returns number of strongly connected components.'''
|
||||
comps = nx.algorithms.components.number_strongly_connected_components(g)
|
||||
return 'Number of strongly connected components', comps
|
||||
|
||||
|
||||
def _convert_to_multidigraph(context: mgp.ProcCtx,
|
||||
) -> Optional[nx.MultiDiGraph]:
|
||||
g = nx.MultiDiGraph()
|
||||
|
||||
for v in context.graph.vertices:
|
||||
context.check_must_abort()
|
||||
g.add_node(v.id)
|
||||
|
||||
for v in context.graph.vertices:
|
||||
context.check_must_abort()
|
||||
for e in v.out_edges:
|
||||
g.add_edge(e.from_vertex.id, e.to_vertex.id)
|
||||
|
||||
return g
|
||||
|
||||
|
||||
def _convert_to_subgraph_multidigraph(context: mgp.ProcCtx,
|
||||
vertices: mgp.List[mgp.Vertex],
|
||||
edges: mgp.List[mgp.Edge]
|
||||
) -> Optional[nx.MultiDiGraph]:
|
||||
g = nx.MultiDiGraph()
|
||||
|
||||
for v in vertices:
|
||||
context.check_must_abort()
|
||||
g.add_node(v.id)
|
||||
|
||||
for e in edges:
|
||||
context.check_must_abort()
|
||||
g.add_edge(e.from_vertex.id, e.to_vertex.id)
|
||||
|
||||
return g
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
set(MAIN src/main.cpp)
|
||||
set(MODULE src/louvain_module.cpp)
|
||||
set(SOURCES src/algorithms/louvain.cpp
|
||||
src/data_structures/graph.cpp)
|
||||
|
||||
include_directories(src)
|
||||
|
||||
add_library(louvain-core STATIC ${SOURCES})
|
||||
set_target_properties(louvain-core PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
add_executable(louvain-main ${MAIN})
|
||||
target_link_libraries(louvain-main louvain-core)
|
||||
|
||||
enable_testing()
|
||||
add_subdirectory(test)
|
||||
|
||||
add_library(louvain SHARED ${MODULE})
|
||||
target_link_libraries(louvain louvain-core)
|
||||
target_include_directories(louvain PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
|
||||
# Strip the library in release build.
|
||||
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
|
||||
if (lower_build_type STREQUAL "release")
|
||||
add_custom_command(TARGET louvain POST_BUILD
|
||||
COMMAND strip -s $<TARGET_FILE:louvain>
|
||||
COMMENT "Stripping symbols and sections from louvain module")
|
||||
endif()
|
||||
|
||||
if (NOT MG_COMMUNITY)
|
||||
install(PROGRAMS $<TARGET_FILE:louvain>
|
||||
DESTINATION lib/memgraph/query_modules
|
||||
RENAME louvain.so)
|
||||
endif()
|
||||
@@ -1,18 +0,0 @@
|
||||
/// @file
|
||||
///
|
||||
/// The file contains function declarations of several community-detection
|
||||
/// graph algorithms.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "data_structures/graph.hpp"
|
||||
|
||||
namespace algorithms {
|
||||
/// Detects communities of an unidrected, weighted graph using the Louvain
|
||||
/// algorithm. The algorithm attempts to maximze the modularity of a weighted
|
||||
/// graph.
|
||||
///
|
||||
/// @param graph pointer to an undirected, weighted graph which may contain
|
||||
/// self-loops.
|
||||
void Louvain(comdata::Graph *graph);
|
||||
} // namespace algorithms
|
||||
@@ -1,163 +0,0 @@
|
||||
#include "algorithms/algorithms.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <random>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace {
|
||||
|
||||
void OptimizeLocally(comdata::Graph *graph) {
|
||||
// We will consider local optimizations uniformly at random.
|
||||
std::random_device rd;
|
||||
std::mt19937 g(rd());
|
||||
std::vector<uint32_t> p(graph->Size());
|
||||
std::iota(p.begin(), p.end(), 0);
|
||||
std::shuffle(p.begin(), p.end(), g);
|
||||
|
||||
// Modularity of a graph can be expressed as:
|
||||
//
|
||||
// Q = 1 / (2m) * sum_over_pairs_of_nodes[(Aij - ki * kj / 2m) * delta(ci, cj)]
|
||||
//
|
||||
// where m is the sum of all weights in the graph,
|
||||
// Aij is the weight on edge that connects i and j (i=j for a self-loop)
|
||||
// ki is the sum of weights incident to node i
|
||||
// ci is the community of node i
|
||||
// delta(a, b) is the Kronecker delta function.
|
||||
//
|
||||
// With some simple algebraic manipulations, we can transform the formula into:
|
||||
//
|
||||
// Q = sum_over_components[M * ((sum_over_pairs(Aij + M * ki * kj)))] =
|
||||
// = sum_over_components[M * (sum_over_pairs(Aij) + M * sum_over_nodes^2(ki))] =
|
||||
// = sum_over_components[M * (w_contrib(ci) + M * k_contrib^2(ci))]
|
||||
//
|
||||
// where M = 1 / (2m)
|
||||
//
|
||||
// Therefore, we could store for each community the following:
|
||||
// * Weight contribution (w_contrib)
|
||||
// * Weighted degree contribution (k_contrib)
|
||||
//
|
||||
// This allows us to efficiently remove a node from one community and insert
|
||||
// it into a community of its neighbour without the need to recalculate
|
||||
// modularity from scratch.
|
||||
|
||||
std::unordered_map<uint32_t, double> w_contrib;
|
||||
std::unordered_map<uint32_t, double> k_contrib;
|
||||
|
||||
for (uint32_t node_id = 0; node_id < graph->Size(); ++node_id) {
|
||||
k_contrib[graph->Community(node_id)] += graph->IncidentWeight(node_id);
|
||||
for (const auto &neigh : graph->Neighbours(node_id)) {
|
||||
uint32_t nxt_id = neigh.dest;
|
||||
double w = neigh.weight;
|
||||
if (graph->Community(node_id) == graph->Community(nxt_id))
|
||||
w_contrib[graph->Community(node_id)] += w;
|
||||
}
|
||||
}
|
||||
|
||||
bool stable = false;
|
||||
double total_w = graph->TotalWeight();
|
||||
|
||||
while (!stable) {
|
||||
stable = true;
|
||||
for (uint32_t node_id : p) {
|
||||
std::unordered_map<uint32_t, double> sum_w;
|
||||
double self_loop = 0;
|
||||
sum_w[graph->Community(node_id)] = 0;
|
||||
for (const auto &neigh : graph->Neighbours(node_id)) {
|
||||
uint32_t nxt_id = neigh.dest;
|
||||
double weight = neigh.weight;
|
||||
if (nxt_id == node_id) {
|
||||
self_loop += weight;
|
||||
continue;
|
||||
}
|
||||
sum_w[graph->Community(nxt_id)] += weight;
|
||||
}
|
||||
|
||||
uint32_t my_c = graph->Community(node_id);
|
||||
|
||||
uint32_t best_c = my_c;
|
||||
double best_dq = 0;
|
||||
|
||||
for (const auto &p : sum_w) {
|
||||
if (p.first == my_c) continue;
|
||||
uint32_t nxt_c = p.first;
|
||||
double dq = 0;
|
||||
|
||||
// contributions before swap (dq = d_after - d_before)
|
||||
for (uint32_t c : {my_c, nxt_c})
|
||||
dq -= w_contrib[c] - k_contrib[c] * k_contrib[c] / (2.0 * total_w);
|
||||
|
||||
// leave the current community
|
||||
dq += (w_contrib[my_c] - 2.0 * sum_w[my_c] - self_loop) -
|
||||
(k_contrib[my_c] - graph->IncidentWeight(node_id)) *
|
||||
(k_contrib[my_c] - graph->IncidentWeight(node_id)) /
|
||||
(2.0 * total_w);
|
||||
|
||||
// join a new community
|
||||
dq += (w_contrib[nxt_c] + 2.0 * sum_w[nxt_c] + self_loop) -
|
||||
(k_contrib[nxt_c] + graph->IncidentWeight(node_id)) *
|
||||
(k_contrib[nxt_c] + graph->IncidentWeight(node_id)) /
|
||||
(2.0 * total_w);
|
||||
|
||||
if (dq > best_dq) {
|
||||
best_dq = dq;
|
||||
best_c = nxt_c;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_c != my_c) {
|
||||
graph->SetCommunity(node_id, best_c);
|
||||
w_contrib[my_c] -= 2.0 * sum_w[my_c] + self_loop;
|
||||
k_contrib[my_c] -= graph->IncidentWeight(node_id);
|
||||
w_contrib[best_c] += 2.0 * sum_w[best_c] + self_loop;
|
||||
k_contrib[best_c] += graph->IncidentWeight(node_id);
|
||||
stable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace algorithms {
|
||||
|
||||
void Louvain(comdata::Graph *graph) {
|
||||
OptimizeLocally(graph);
|
||||
|
||||
// Collapse the locally optimized graph.
|
||||
uint32_t collapsed_nodes = graph->NormalizeCommunities();
|
||||
if (collapsed_nodes == graph->Size()) return;
|
||||
comdata::Graph collapsed_graph(collapsed_nodes);
|
||||
std::map<std::pair<uint32_t, uint32_t>, double> collapsed_edges;
|
||||
|
||||
for (uint32_t node_id = 0; node_id < graph->Size(); ++node_id) {
|
||||
std::unordered_map<uint32_t, double> edges;
|
||||
for (const auto &neigh : graph->Neighbours(node_id)) {
|
||||
uint32_t nxt_id = neigh.dest;
|
||||
double weight = neigh.weight;
|
||||
if (graph->Community(nxt_id) < graph->Community(node_id)) continue;
|
||||
edges[graph->Community(nxt_id)] += weight;
|
||||
}
|
||||
for (const auto &neigh : edges) {
|
||||
uint32_t a = std::min(graph->Community(node_id), neigh.first);
|
||||
uint32_t b = std::max(graph->Community(node_id), neigh.first);
|
||||
collapsed_edges[{a, b}] += neigh.second;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &p : collapsed_edges)
|
||||
collapsed_graph.AddEdge(p.first.first, p.first.second, p.second);
|
||||
|
||||
// Repeat until no local optimizations can be found.
|
||||
Louvain(&collapsed_graph);
|
||||
|
||||
// Propagate results from collapsed graph.
|
||||
for (uint32_t node_id = 0; node_id < graph->Size(); ++node_id) {
|
||||
graph->SetCommunity(node_id,
|
||||
collapsed_graph.Community(graph->Community(node_id)));
|
||||
}
|
||||
|
||||
graph->NormalizeCommunities();
|
||||
}
|
||||
|
||||
} // namespace algorithms
|
||||
@@ -1,105 +0,0 @@
|
||||
#include "data_structures/graph.hpp"
|
||||
|
||||
#include <exception>
|
||||
#include <numeric>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace comdata {
|
||||
|
||||
Graph::Graph(uint32_t n_nodes) : n_nodes_(n_nodes), total_w_(0) {
|
||||
adj_list_.resize(n_nodes, {});
|
||||
inc_w_.resize(n_nodes, 0);
|
||||
|
||||
// each node starts as its own separate community.
|
||||
community_.resize(n_nodes);
|
||||
std::iota(community_.begin(), community_.end(), 0);
|
||||
}
|
||||
|
||||
uint32_t Graph::Size() const {
|
||||
return n_nodes_;
|
||||
}
|
||||
|
||||
uint32_t Graph::Community(uint32_t node) const { return community_.at(node); }
|
||||
|
||||
void Graph::SetCommunity(uint32_t node, uint32_t c) { community_.at(node) = c; }
|
||||
|
||||
uint32_t Graph::NormalizeCommunities() {
|
||||
std::set<uint32_t> c_id(community_.begin(), community_.end());
|
||||
std::unordered_map<uint32_t, uint32_t> cmap;
|
||||
uint32_t id = 0;
|
||||
for (uint32_t c : c_id) {
|
||||
cmap[c] = id;
|
||||
++id;
|
||||
}
|
||||
for (uint32_t node_id = 0; node_id < n_nodes_; ++node_id)
|
||||
community_[node_id] = cmap[community_[node_id]];
|
||||
return id;
|
||||
}
|
||||
|
||||
void Graph::AddEdge(uint32_t node1, uint32_t node2, double weight) {
|
||||
if (node1 >= n_nodes_ || node2 >= n_nodes_)
|
||||
throw std::out_of_range("Node index out of range");
|
||||
if (weight <= 0) throw std::out_of_range("Weights must be positive");
|
||||
if (edges_.find({node1, node2}) != edges_.end())
|
||||
throw std::invalid_argument("Edge already exists");
|
||||
|
||||
edges_.emplace(node1, node2);
|
||||
edges_.emplace(node2, node1);
|
||||
|
||||
total_w_ += weight;
|
||||
|
||||
adj_list_[node1].emplace_back(node2, weight);
|
||||
inc_w_[node1] += weight;
|
||||
|
||||
if (node1 != node2) {
|
||||
adj_list_[node2].emplace_back(node1, weight);
|
||||
inc_w_[node2] += weight;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t Graph::Degree(uint32_t node) const {
|
||||
return static_cast<uint32_t>(adj_list_.at(node).size());
|
||||
}
|
||||
|
||||
double Graph::IncidentWeight(uint32_t node) const {
|
||||
return inc_w_.at(node);
|
||||
}
|
||||
|
||||
double Graph::TotalWeight() const {
|
||||
return total_w_;
|
||||
}
|
||||
|
||||
double Graph::Modularity() const {
|
||||
double ret = 0;
|
||||
// Since all weights should be positive, this implies that our graph has
|
||||
// no edges.
|
||||
if (total_w_ == 0)
|
||||
return 0;
|
||||
|
||||
std::unordered_map<uint32_t, double> weight_c;
|
||||
std::unordered_map<uint32_t, double> degree_c;
|
||||
|
||||
for (uint32_t i = 0; i < n_nodes_; ++i) {
|
||||
degree_c[Community(i)] += IncidentWeight(i);
|
||||
for (const auto &neigh : adj_list_[i]) {
|
||||
uint32_t j = neigh.dest;
|
||||
double w = neigh.weight;
|
||||
if (Community(i) != Community(j)) continue;
|
||||
weight_c[Community(i)] += w;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &p : degree_c)
|
||||
ret += weight_c[p.first] - (p.second * p.second) / (2 * total_w_);
|
||||
|
||||
ret /= 2 * total_w_;
|
||||
return ret;
|
||||
}
|
||||
|
||||
const std::vector<Neighbour> &Graph::Neighbours(uint32_t node) const {
|
||||
return adj_list_.at(node);
|
||||
}
|
||||
|
||||
} // namespace comdata
|
||||
@@ -1,125 +0,0 @@
|
||||
/// @file
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace comdata {
|
||||
|
||||
struct Neighbour {
|
||||
uint32_t dest;
|
||||
double weight;
|
||||
Neighbour(uint32_t d, double w) : dest(d), weight(w) {}
|
||||
};
|
||||
|
||||
/// Class which models a weighted, undirected graph with necessary
|
||||
/// functionalities for community detection algorithms.
|
||||
class Graph {
|
||||
public:
|
||||
/// Constructs a new graph with a given number of nodes and no edges between
|
||||
/// them.
|
||||
///
|
||||
/// The implementation assumes (and enforces) that all nodes
|
||||
/// are indexed from 0 to n_nodes.
|
||||
///
|
||||
/// @param n_nodes Number of nodes in the graph.
|
||||
explicit Graph(uint32_t n_nodes);
|
||||
|
||||
/// @return number of nodes in the graph.
|
||||
uint32_t Size() const;
|
||||
|
||||
/// Adds a bidirectional, weighted edge to the graph between the given
|
||||
/// nodes. If both given nodes are the same, the method inserts a weighted
|
||||
/// self-loop.
|
||||
///
|
||||
/// There should be no edges between the given nodes when before invoking
|
||||
/// this method.
|
||||
///
|
||||
/// @param node1 index of an incident node.
|
||||
/// @param node2 index of an incident node.
|
||||
/// @param weight real value which represents the weight of the edge.
|
||||
///
|
||||
/// @throw std::out_of_range
|
||||
/// @throw std::invalid_argument
|
||||
void AddEdge(uint32_t node1, uint32_t node2, double weight);
|
||||
|
||||
/// @param node index of node.
|
||||
///
|
||||
/// @return community where the node belongs to.
|
||||
///
|
||||
/// @throw std::out_of_range
|
||||
uint32_t Community(uint32_t node) const;
|
||||
|
||||
/// Adds a given node to a given community.
|
||||
///
|
||||
/// @param node index of node.
|
||||
/// @param c community where the given node should go in.
|
||||
///
|
||||
/// @throw std::out_of_range
|
||||
void SetCommunity(uint32_t node, uint32_t c);
|
||||
|
||||
/// Normalizes the values of communities. More precisely, after invoking this
|
||||
/// method communities will be indexed by successive integers starting from 0.
|
||||
///
|
||||
/// Note: this method is computationally expensive and takes O(|V|)
|
||||
/// time, i.e., it traverses all nodes in the graph.
|
||||
///
|
||||
/// @return number of communities in the graph
|
||||
uint32_t NormalizeCommunities();
|
||||
|
||||
/// Returns the number of incident edges to a given node. Self-loops
|
||||
/// contribute a single edge to the degree.
|
||||
///
|
||||
/// @param node index of node.
|
||||
///
|
||||
/// @return degree of given node.
|
||||
///
|
||||
/// @throw std::out_of_range
|
||||
uint32_t Degree(uint32_t node) const;
|
||||
|
||||
/// Returns the total weight of incident edges to a given node. Weight
|
||||
/// of a self loop contributes once to the total sum.
|
||||
///
|
||||
/// @param node index of node.
|
||||
///
|
||||
/// @return total incident weight of a given node.
|
||||
///
|
||||
/// @throw std::out_of_range
|
||||
double IncidentWeight(uint32_t node) const;
|
||||
|
||||
/// @return total weight of all edges in a graph.
|
||||
double TotalWeight() const;
|
||||
|
||||
/// Calculates the modularity of the graph which is defined as a real value
|
||||
/// between -1 and 1 that measures the density of links inside communities
|
||||
/// compared to links between communities.
|
||||
///
|
||||
/// Note: this method is computationally expensive and takes O(|V| + |E|)
|
||||
/// time, i.e., it traverses the entire graph.
|
||||
///
|
||||
/// @return modularity of the graph.
|
||||
double Modularity() const;
|
||||
|
||||
/// Returns nodes adjacent to a given node.
|
||||
///
|
||||
/// @param node index of node.
|
||||
///
|
||||
/// @return list of neighbouring nodes.
|
||||
///
|
||||
/// @throw std::out_of_range
|
||||
const std::vector<Neighbour>& Neighbours(uint32_t node) const;
|
||||
|
||||
private:
|
||||
uint32_t n_nodes_;
|
||||
double total_w_;
|
||||
|
||||
std::vector<std::vector<Neighbour>> adj_list_;
|
||||
std::set<std::pair<uint32_t, uint32_t>> edges_;
|
||||
|
||||
std::vector<double> inc_w_;
|
||||
std::vector<uint32_t> community_;
|
||||
};
|
||||
|
||||
} // namespace comdata
|
||||
@@ -1,228 +0,0 @@
|
||||
#include "mg_procedure.h"
|
||||
|
||||
#include <exception>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "algorithms/algorithms.hpp"
|
||||
#include "data_structures/graph.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
std::optional<std::unordered_map<int64_t, uint32_t>> NormalizeVertexIds(
|
||||
const mgp_graph *graph, mgp_result *result, mgp_memory *memory) {
|
||||
std::unordered_map<int64_t, uint32_t> mem_to_louv_id;
|
||||
mgp_vertices_iterator *vertices_iterator =
|
||||
mgp_graph_iter_vertices(graph, memory);
|
||||
if (vertices_iterator == nullptr) {
|
||||
mgp_result_set_error_msg(result, "Not enough memory!");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
uint32_t louv_id = 0;
|
||||
for (const mgp_vertex *vertex = mgp_vertices_iterator_get(vertices_iterator);
|
||||
vertex != nullptr;
|
||||
vertex = mgp_vertices_iterator_next(vertices_iterator)) {
|
||||
mgp_vertex_id mem_id = mgp_vertex_get_id(vertex);
|
||||
mem_to_louv_id[mem_id.as_int] = louv_id;
|
||||
++louv_id;
|
||||
}
|
||||
|
||||
mgp_vertices_iterator_destroy(vertices_iterator);
|
||||
return mem_to_louv_id;
|
||||
}
|
||||
|
||||
std::optional<comdata::Graph> RunLouvain(
|
||||
const mgp_graph *graph, mgp_result *result, mgp_memory *memory,
|
||||
const std::unordered_map<int64_t, uint32_t> &mem_to_louv_id) {
|
||||
comdata::Graph louvain_graph(mem_to_louv_id.size());
|
||||
// Extract the graph structure
|
||||
// TODO(ipaljak): consider filtering nodes and edges by labels.
|
||||
for (const auto &p : mem_to_louv_id) {
|
||||
mgp_vertex *vertex = mgp_graph_get_vertex_by_id(graph, {p.first}, memory);
|
||||
if (!vertex) {
|
||||
mgp_result_set_error_msg(result, "Not enough memory!");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// iterate over inbound edges. This is enough because we will eventually
|
||||
// iterate over outbound edges in another direction.
|
||||
mgp_edges_iterator *edges_iterator =
|
||||
mgp_vertex_iter_in_edges(vertex, memory);
|
||||
if (edges_iterator == nullptr) {
|
||||
mgp_vertex_destroy(vertex);
|
||||
mgp_result_set_error_msg(result, "Not enough memory!");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (const mgp_edge *edge = mgp_edges_iterator_get(edges_iterator);
|
||||
edge != nullptr; edge = mgp_edges_iterator_next(edges_iterator)) {
|
||||
const mgp_vertex *next_vertex = mgp_edge_get_from(edge);
|
||||
mgp_vertex_id next_mem_id = mgp_vertex_get_id(next_vertex);
|
||||
uint32_t next_louv_id;
|
||||
try {
|
||||
next_louv_id = mem_to_louv_id.at(next_mem_id.as_int);
|
||||
} catch (const std::exception &e) {
|
||||
const auto msg = std::string("[Internal error] ") + e.what();
|
||||
mgp_result_set_error_msg(result, msg.c_str());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// retrieve edge weight (default to 1)
|
||||
mgp_value *weight_prop = mgp_edge_get_property(edge, "weight", memory);
|
||||
if (!weight_prop) {
|
||||
mgp_vertex_destroy(vertex);
|
||||
mgp_edges_iterator_destroy(edges_iterator);
|
||||
mgp_result_set_error_msg(result, "Not enough memory");
|
||||
}
|
||||
|
||||
double weight = 1;
|
||||
if (mgp_value_is_double(weight_prop))
|
||||
weight = mgp_value_get_double(weight_prop);
|
||||
if (mgp_value_is_int(weight_prop))
|
||||
weight = static_cast<double>(mgp_value_get_int(weight_prop));
|
||||
|
||||
mgp_value_destroy(weight_prop);
|
||||
|
||||
try {
|
||||
louvain_graph.AddEdge(p.second, next_louv_id, weight);
|
||||
} catch (const std::exception &e) {
|
||||
mgp_vertex_destroy(vertex);
|
||||
mgp_edges_iterator_destroy(edges_iterator);
|
||||
mgp_result_set_error_msg(result, e.what());
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
mgp_vertex_destroy(vertex);
|
||||
mgp_edges_iterator_destroy(edges_iterator);
|
||||
}
|
||||
|
||||
try {
|
||||
algorithms::Louvain(&louvain_graph);
|
||||
} catch (const std::exception &e) {
|
||||
const auto msg = std::string("[Internal error] ") + e.what();
|
||||
mgp_result_set_error_msg(result, msg.c_str());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return louvain_graph;
|
||||
}
|
||||
|
||||
void communities(const mgp_list *args, const mgp_graph *graph,
|
||||
mgp_result *result, mgp_memory *memory) {
|
||||
try {
|
||||
// Normalize vertex ids
|
||||
auto mem_to_louv_id = NormalizeVertexIds(graph, result, memory);
|
||||
if (!mem_to_louv_id) return;
|
||||
|
||||
// Run louvain
|
||||
auto louvain_graph = RunLouvain(graph, result, memory, *mem_to_louv_id);
|
||||
if (!louvain_graph) return;
|
||||
|
||||
// Return node ids and their corresponding communities.
|
||||
for (const auto &p : *mem_to_louv_id) {
|
||||
mgp_result_record *record = mgp_result_new_record(result);
|
||||
if (record == nullptr) {
|
||||
mgp_result_set_error_msg(result, "Not enough memory!");
|
||||
return;
|
||||
}
|
||||
|
||||
mgp_value *mem_id_value = mgp_value_make_int(p.first, memory);
|
||||
if (mem_id_value == nullptr) {
|
||||
mgp_result_set_error_msg(result, "Not enough memory!");
|
||||
return;
|
||||
}
|
||||
|
||||
mgp_value *com_value =
|
||||
mgp_value_make_int(louvain_graph->Community(p.second), memory);
|
||||
if (com_value == nullptr) {
|
||||
mgp_value_destroy(mem_id_value);
|
||||
mgp_result_set_error_msg(result, "Not enough memory!");
|
||||
return;
|
||||
}
|
||||
|
||||
int mem_id_inserted =
|
||||
mgp_result_record_insert(record, "id", mem_id_value);
|
||||
int com_inserted =
|
||||
mgp_result_record_insert(record, "community", com_value);
|
||||
|
||||
mgp_value_destroy(mem_id_value);
|
||||
mgp_value_destroy(com_value);
|
||||
|
||||
if (!mem_id_inserted || !com_inserted) {
|
||||
mgp_result_set_error_msg(result, "Not enough memory!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
mgp_result_set_error_msg(result, e.what());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void modularity(const mgp_list *args, const mgp_graph *graph,
|
||||
mgp_result *result, mgp_memory *memory) {
|
||||
try {
|
||||
// Normalize vertex ids
|
||||
auto mem_to_louv_id = NormalizeVertexIds(graph, result, memory);
|
||||
if (!mem_to_louv_id) return;
|
||||
|
||||
// Run louvain
|
||||
auto louvain_graph = RunLouvain(graph, result, memory, *mem_to_louv_id);
|
||||
if (!louvain_graph) return;
|
||||
|
||||
// Return graph modularity after Louvain
|
||||
// TODO(ipaljak) - consider allowing the user to specify seed communities and
|
||||
// yield modularity values both before and after running
|
||||
// louvain.
|
||||
mgp_result_record *record = mgp_result_new_record(result);
|
||||
if (record == nullptr) {
|
||||
mgp_result_set_error_msg(result, "Not enough memory!");
|
||||
return;
|
||||
}
|
||||
|
||||
mgp_value *modularity_value =
|
||||
mgp_value_make_double(louvain_graph->Modularity(), memory);
|
||||
if (modularity_value == nullptr) {
|
||||
mgp_result_set_error_msg(result, "Not enough memory!");
|
||||
return;
|
||||
}
|
||||
|
||||
int value_inserted =
|
||||
mgp_result_record_insert(record, "modularity", modularity_value);
|
||||
|
||||
mgp_value_destroy(modularity_value);
|
||||
|
||||
if (!value_inserted) {
|
||||
mgp_result_set_error_msg(result, "Not enough memory!");
|
||||
return;
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
mgp_result_set_error_msg(result, e.what());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" int mgp_init_module(struct mgp_module *module,
|
||||
struct mgp_memory *memory) {
|
||||
struct mgp_proc *community_proc =
|
||||
mgp_module_add_read_procedure(module, "communities", communities);
|
||||
if (!community_proc) return 1;
|
||||
if (!mgp_proc_add_result(community_proc, "id", mgp_type_int())) return 1;
|
||||
if (!mgp_proc_add_result(community_proc, "community", mgp_type_int()))
|
||||
return 1;
|
||||
|
||||
struct mgp_proc *modularity_proc =
|
||||
mgp_module_add_read_procedure(module, "modularity", modularity);
|
||||
if (!modularity_proc) return 1;
|
||||
if (!mgp_proc_add_result(modularity_proc, "modularity", mgp_type_float()))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int mgp_shutdown_module() {
|
||||
return 0;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
#include "algorithms/algorithms.hpp"
|
||||
#include "data_structures/graph.hpp"
|
||||
|
||||
// A simple program that reads the graph from STDIN and
|
||||
// outputs the detected communities from louvain along with
|
||||
// its modularity measure on STDOUT.
|
||||
int main() {
|
||||
int n;
|
||||
int m;
|
||||
std::cin >> n >> m;
|
||||
comdata::Graph graph(n);
|
||||
for (int i = 0; i < m; ++i) {
|
||||
int a;
|
||||
int b;
|
||||
double c;
|
||||
std::cin >> a >> b >> c;
|
||||
graph.AddEdge(a, b, c);
|
||||
}
|
||||
|
||||
algorithms::Louvain(&graph);
|
||||
|
||||
for (int i = 0; i < n; ++i)
|
||||
std::cout << i << " " << graph.Community(i) << "\n";
|
||||
std::cout << graph.Modularity() << "\n";
|
||||
return 0;
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
---
|
||||
Checks: '*,
|
||||
-android-*,
|
||||
-cert-err58-cpp,
|
||||
-cppcoreguidelines-avoid-c-arrays,
|
||||
-cppcoreguidelines-avoid-goto,
|
||||
-cppcoreguidelines-avoid-magic-numbers,
|
||||
-cppcoreguidelines-macro-usage,
|
||||
-cppcoreguidelines-no-malloc,
|
||||
-cppcoreguidelines-non-private-member-variables-in-classes,
|
||||
-cppcoreguidelines-owning-memory,
|
||||
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
||||
-cppcoreguidelines-pro-bounds-constant-array-index,
|
||||
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
|
||||
-cppcoreguidelines-pro-type-member-init,
|
||||
-cppcoreguidelines-pro-type-reinterpret-cast,
|
||||
-cppcoreguidelines-pro-type-static-cast-downcast,
|
||||
-cppcoreguidelines-pro-type-union-access,
|
||||
-cppcoreguidelines-pro-type-vararg,
|
||||
-cppcoreguidelines-special-member-functions,
|
||||
-fuchsia-default-arguments,
|
||||
-fuchsia-default-arguments-calls,
|
||||
-fuchsia-default-arguments-declarations,
|
||||
-fuchsia-overloaded-operator,
|
||||
-fuchsia-statically-constructed-objects,
|
||||
-fuchsia-trailing-return,
|
||||
-fuchsia-virtual-inheritance,
|
||||
-google-explicit-constructor,
|
||||
-google-readability-*,
|
||||
-hicpp-avoid-c-arrays,
|
||||
-hicpp-avoid-goto,
|
||||
-hicpp-braces-around-statements,
|
||||
-hicpp-member-init,
|
||||
-hicpp-no-array-decay,
|
||||
-hicpp-no-assembler,
|
||||
-hicpp-no-malloc,
|
||||
-hicpp-special-member-functions,
|
||||
-hicpp-use-equals-default,
|
||||
-hicpp-vararg,
|
||||
-llvm-header-guard,
|
||||
-misc-non-private-member-variables-in-classes,
|
||||
-misc-unused-parameters,
|
||||
-modernize-avoid-c-arrays,
|
||||
-modernize-concat-nested-namespaces,
|
||||
-modernize-pass-by-value,
|
||||
-modernize-use-equals-default,
|
||||
-modernize-use-nodiscard,
|
||||
-modernize-use-trailing-return-type,
|
||||
-performance-unnecessary-value-param,
|
||||
-readability-braces-around-statements,
|
||||
-readability-else-after-return,
|
||||
-readability-implicit-bool-conversion,
|
||||
-readability-magic-numbers,
|
||||
-readability-named-parameter'
|
||||
WarningsAsErrors: ''
|
||||
HeaderFilterRegex: ''
|
||||
AnalyzeTemporaryDtors: false
|
||||
FormatStyle: none
|
||||
CheckOptions:
|
||||
- key: google-readability-braces-around-statements.ShortStatementLines
|
||||
value: '1'
|
||||
- key: google-readability-function-size.StatementThreshold
|
||||
value: '800'
|
||||
- key: google-readability-namespace-comments.ShortNamespaceLines
|
||||
value: '10'
|
||||
- key: google-readability-namespace-comments.SpacesBeforeComments
|
||||
value: '2'
|
||||
- key: modernize-loop-convert.MaxCopySize
|
||||
value: '16'
|
||||
- key: modernize-loop-convert.MinConfidence
|
||||
value: reasonable
|
||||
- key: modernize-loop-convert.NamingStyle
|
||||
value: CamelCase
|
||||
- key: modernize-pass-by-value.IncludeStyle
|
||||
value: llvm
|
||||
- key: modernize-replace-auto-ptr.IncludeStyle
|
||||
value: llvm
|
||||
- key: modernize-use-nullptr.NullMacros
|
||||
value: 'NULL'
|
||||
...
|
||||
@@ -1,3 +0,0 @@
|
||||
include_directories(${GTEST_INCLUDE_DIR})
|
||||
|
||||
add_subdirectory(unit)
|
||||
@@ -1,28 +0,0 @@
|
||||
set(test_prefix louvain__unit__)
|
||||
|
||||
add_custom_target(louvain__unit)
|
||||
|
||||
add_library(louvain-test STATIC utils.cpp)
|
||||
set_target_properties(louvain-test PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
function(add_unit_test test_cpp)
|
||||
# get exec name (remove extension from the abs path)
|
||||
get_filename_component(exec_name ${test_cpp} NAME_WE)
|
||||
set(target_name ${test_prefix}${exec_name})
|
||||
add_executable(${target_name} ${test_cpp})
|
||||
# OUTPUT_NAME sets the real name of a target when it is built and can be
|
||||
# used to help create two targets of the same name even though CMake
|
||||
# requires unique logical target names
|
||||
set_target_properties(${target_name} PROPERTIES OUTPUT_NAME ${exec_name})
|
||||
# TODO: this is a temporary workaround the test build warnings
|
||||
target_compile_options(${target_name} PRIVATE -Wno-comment -Wno-sign-compare
|
||||
-Wno-unused-variable)
|
||||
target_link_libraries(${target_name} glog gflags gtest gtest_main Threads::Threads
|
||||
louvain-core louvain-test)
|
||||
# register test
|
||||
add_test(${target_name} ${exec_name})
|
||||
# add to unit target
|
||||
add_dependencies(louvain__unit ${target_name})
|
||||
endfunction(add_unit_test)
|
||||
|
||||
add_unit_test(graph.cpp)
|
||||
@@ -1,365 +0,0 @@
|
||||
#include <glog/logging.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "data_structures/graph.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
// Checks if commmunities of nodes in graph correspond to a given community
|
||||
// vector.
|
||||
bool CommunityCheck(const comdata::Graph &graph,
|
||||
const std::vector<uint32_t> &c) {
|
||||
if (graph.Size() != c.size()) return false;
|
||||
for (uint32_t node_id = 0; node_id < graph.Size(); ++node_id)
|
||||
if (graph.Community(node_id) != c[node_id])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Checks if degrees of nodes in graph correspond to a given degree vector.
|
||||
bool DegreeCheck(const comdata::Graph &graph,
|
||||
const std::vector<uint32_t> °) {
|
||||
if (graph.Size() != deg.size()) return false;
|
||||
for (uint32_t node_id = 0; node_id < graph.Size(); ++node_id)
|
||||
if (graph.Degree(node_id) != deg[node_id])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Checks if incident weights of nodes in graph correspond to a given weight
|
||||
// vector.
|
||||
bool IncidentWeightCheck(const comdata::Graph &graph,
|
||||
const std::vector<double> &inc_w) {
|
||||
if (graph.Size() != inc_w.size()) return false;
|
||||
for (uint32_t node_id = 0; node_id < graph.Size(); ++node_id)
|
||||
if (std::abs(graph.IncidentWeight(node_id) - inc_w[node_id]) > 1e-6)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Sets communities of nodes in graph. Returns true on success.
|
||||
bool SetCommunities(comdata::Graph *graph, const std::vector<uint32_t> &c) {
|
||||
if (graph->Size() != c.size()) return false;
|
||||
for (uint32_t node_id = 0; node_id < graph->Size(); ++node_id)
|
||||
graph->SetCommunity(node_id, c[node_id]);
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST(Graph, Constructor) {
|
||||
uint32_t nodes = 100;
|
||||
comdata::Graph graph(nodes);
|
||||
ASSERT_EQ(graph.Size(), nodes);
|
||||
for (uint32_t node_id = 0; node_id < nodes; ++node_id) {
|
||||
ASSERT_EQ(graph.IncidentWeight(node_id), 0);
|
||||
ASSERT_EQ(graph.Community(node_id), node_id);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Graph, Size) {
|
||||
comdata::Graph graph1 = GenRandomUnweightedGraph(0, 0);
|
||||
comdata::Graph graph2 = GenRandomUnweightedGraph(42, 41);
|
||||
comdata::Graph graph3 = GenRandomUnweightedGraph(100, 250);
|
||||
ASSERT_EQ(graph1.Size(), 0);
|
||||
ASSERT_EQ(graph2.Size(), 42);
|
||||
ASSERT_EQ(graph3.Size(), 100);
|
||||
}
|
||||
|
||||
TEST(Graph, Communities) {
|
||||
comdata::Graph graph = GenRandomUnweightedGraph(100, 250);
|
||||
|
||||
for (int i = 0; i < 100; ++i) graph.SetCommunity(i, i % 5);
|
||||
for (int i = 0; i < 100; ++i) ASSERT_EQ(graph.Community(i), i % 5);
|
||||
|
||||
// Try to set communities on non-existing nodes
|
||||
EXPECT_THROW({ graph.SetCommunity(100, 2); }, std::out_of_range);
|
||||
EXPECT_THROW({ graph.SetCommunity(150, 0); }, std::out_of_range);
|
||||
|
||||
// Try to get a the community of a non-existing node
|
||||
EXPECT_THROW({ graph.Community(100); }, std::out_of_range);
|
||||
EXPECT_THROW({ graph.Community(150); }, std::out_of_range);
|
||||
}
|
||||
|
||||
TEST(Graph, CommunityNormalization) {
|
||||
// Communities are already normalized.
|
||||
comdata::Graph graph = GenRandomUnweightedGraph(5, 10);
|
||||
std::vector<uint32_t> init_c = {0, 2, 1, 3, 4};
|
||||
std::vector<uint32_t> final_c = {0, 2, 1, 3, 4};
|
||||
ASSERT_TRUE(SetCommunities(&graph, init_c));
|
||||
graph.NormalizeCommunities();
|
||||
ASSERT_TRUE(CommunityCheck(graph, final_c));
|
||||
|
||||
// Each node in its own community.
|
||||
graph = GenRandomUnweightedGraph(5, 10);
|
||||
init_c = {20, 30, 10, 40, 50};
|
||||
final_c = {1, 2, 0, 3, 4};
|
||||
ASSERT_TRUE(SetCommunities(&graph, init_c));
|
||||
graph.NormalizeCommunities();
|
||||
ASSERT_TRUE(CommunityCheck(graph, final_c));
|
||||
|
||||
// Multiple nodes in the same community
|
||||
graph = GenRandomUnweightedGraph(7, 10);
|
||||
init_c = {13, 99, 13, 13, 1, 99, 1};
|
||||
final_c = {1, 2, 1, 1, 0, 2, 0};
|
||||
ASSERT_TRUE(SetCommunities(&graph, init_c));
|
||||
graph.NormalizeCommunities();
|
||||
ASSERT_TRUE(CommunityCheck(graph, final_c));
|
||||
}
|
||||
|
||||
TEST(Graph, AddEdge) {
|
||||
comdata::Graph graph = GenRandomUnweightedGraph(5, 0);
|
||||
|
||||
// Node out of bounds.
|
||||
EXPECT_THROW({ graph.AddEdge(1, 5, 7); }, std::out_of_range);
|
||||
|
||||
// Repeated edge
|
||||
graph.AddEdge(1, 2, 1);
|
||||
EXPECT_THROW({ graph.AddEdge(1, 2, 7); }, std::invalid_argument);
|
||||
|
||||
// Non-positive edge weight
|
||||
EXPECT_THROW({ graph.AddEdge(2, 3, -7); }, std::out_of_range);
|
||||
EXPECT_THROW({ graph.AddEdge(3, 4, 0); }, std::out_of_range);
|
||||
}
|
||||
|
||||
TEST(Graph, Degrees) {
|
||||
// Graph without edges
|
||||
comdata::Graph graph = GenRandomUnweightedGraph(5, 0);
|
||||
std::vector<uint32_t> deg = {0, 0, 0, 0, 0};
|
||||
ASSERT_TRUE(DegreeCheck(graph, deg));
|
||||
|
||||
// Chain
|
||||
// (0)--(1)--(2)--(3)--(4)
|
||||
graph = BuildGraph(5, {{0, 1, 1},
|
||||
{1, 2, 1},
|
||||
{2, 3, 1},
|
||||
{3, 4, 1}});
|
||||
deg = {1, 2, 2, 2, 1};
|
||||
ASSERT_TRUE(DegreeCheck(graph, deg));
|
||||
|
||||
// Tree
|
||||
// (0)--(3)
|
||||
// / \
|
||||
// (1) (2)
|
||||
// | / \
|
||||
// (4) (5) (6)
|
||||
graph = BuildGraph(7, {{0, 1, 1},
|
||||
{0, 2, 1},
|
||||
{0, 3, 1},
|
||||
{1, 4, 1},
|
||||
{2, 5, 1},
|
||||
{2, 6, 1}});
|
||||
deg = {3, 2, 3, 1, 1, 1, 1};
|
||||
ASSERT_TRUE(DegreeCheck(graph, deg));
|
||||
|
||||
// Graph without self-loops
|
||||
// (0)--(1)
|
||||
// | \ | \
|
||||
// | \ | \
|
||||
// (2)--(3)-(4)
|
||||
graph = BuildGraph(5, {{0, 1, 1},
|
||||
{0, 2, 1},
|
||||
{0, 3, 1},
|
||||
{1, 3, 1},
|
||||
{1, 4, 1},
|
||||
{2, 3, 1},
|
||||
{3, 4, 1}});
|
||||
deg = {3, 3, 2, 4, 2};
|
||||
ASSERT_TRUE(DegreeCheck(graph, deg));
|
||||
|
||||
// Graph with self loop [*nodes have self loops]
|
||||
// (0)--(1*)
|
||||
// | \ | \
|
||||
// | \ | \
|
||||
// (2*)--(3)-(4*)
|
||||
graph = BuildGraph(5, {{0, 1, 1},
|
||||
{0, 2, 1},
|
||||
{0, 3, 1},
|
||||
{1, 3, 1},
|
||||
{1, 4, 1},
|
||||
{2, 3, 1},
|
||||
{3, 4, 1},
|
||||
{1, 1, 1},
|
||||
{2, 2, 2},
|
||||
{4, 4, 4}});
|
||||
deg = {3, 4, 3, 4, 3};
|
||||
ASSERT_TRUE(DegreeCheck(graph, deg));
|
||||
|
||||
// Try to get degree of non-existing nodes
|
||||
EXPECT_THROW({ graph.Degree(5); }, std::out_of_range);
|
||||
EXPECT_THROW({ graph.Degree(100); }, std::out_of_range);
|
||||
}
|
||||
|
||||
TEST(Graph, Weights) {
|
||||
// Graph without edges
|
||||
comdata::Graph graph = GenRandomUnweightedGraph(5, 0);
|
||||
std::vector<double> inc_w = {0, 0, 0, 0, 0};
|
||||
ASSERT_TRUE(IncidentWeightCheck(graph, inc_w));
|
||||
ASSERT_EQ(graph.TotalWeight(), 0);
|
||||
|
||||
// Chain
|
||||
// (0)--(1)--(2)--(3)--(4)
|
||||
graph = BuildGraph(5, {{0, 1, 0.1},
|
||||
{1, 2, 0.5},
|
||||
{2, 3, 2.3},
|
||||
{3, 4, 4.2}});
|
||||
inc_w = {0.1, 0.6, 2.8, 6.5, 4.2};
|
||||
ASSERT_TRUE(IncidentWeightCheck(graph, inc_w));
|
||||
ASSERT_NEAR(graph.TotalWeight(), 7.1, 1e-6);
|
||||
|
||||
// Tree
|
||||
// (0)--(3)
|
||||
// / \
|
||||
// (1) (2)
|
||||
// | / \
|
||||
// (4) (5) (6)
|
||||
graph = BuildGraph(7, {{0, 1, 1.3},
|
||||
{0, 2, 0.2},
|
||||
{0, 3, 1},
|
||||
{1, 4, 3.2},
|
||||
{2, 5, 4.2},
|
||||
{2, 6, 0.7}});
|
||||
inc_w = {2.5, 4.5, 5.1, 1, 3.2, 4.2, 0.7};
|
||||
ASSERT_TRUE(IncidentWeightCheck(graph, inc_w));
|
||||
EXPECT_NEAR(graph.TotalWeight(), 10.6, 1e-6);
|
||||
|
||||
// Graph without self-loops
|
||||
// (0)--(1)
|
||||
// | \ | \
|
||||
// | \ | \
|
||||
// (2)--(3)-(4)
|
||||
graph = BuildGraph(5, {{0, 1, 0.1},
|
||||
{0, 2, 0.2},
|
||||
{0, 3, 0.3},
|
||||
{1, 3, 0.4},
|
||||
{1, 4, 0.5},
|
||||
{2, 3, 0.6},
|
||||
{3, 4, 0.7}});
|
||||
inc_w = {0.6, 1, 0.8, 2, 1.2};
|
||||
ASSERT_TRUE(IncidentWeightCheck(graph, inc_w));
|
||||
EXPECT_NEAR(graph.TotalWeight(), 2.8, 1e-6);
|
||||
|
||||
// Graph with self loop [*nodes have self loops]
|
||||
// (0)--(1*)
|
||||
// | \ | \
|
||||
// | \ | \
|
||||
// (2*)--(3)-(4*)
|
||||
graph = BuildGraph(5, {{0, 1, 0.1},
|
||||
{0, 2, 0.2},
|
||||
{0, 3, 0.3},
|
||||
{1, 3, 0.4},
|
||||
{1, 4, 0.5},
|
||||
{2, 3, 0.6},
|
||||
{3, 4, 0.7},
|
||||
{1, 1, 0.8},
|
||||
{2, 2, 0.9},
|
||||
{4, 4, 1}});
|
||||
inc_w = {0.6, 1.8, 1.7, 2, 2.2};
|
||||
ASSERT_TRUE(IncidentWeightCheck(graph, inc_w));
|
||||
EXPECT_NEAR(graph.TotalWeight(), 5.5, 1e-6);
|
||||
|
||||
// Try to get incident weight of non-existing node
|
||||
EXPECT_THROW({ graph.IncidentWeight(5); }, std::out_of_range);
|
||||
EXPECT_THROW({ graph.IncidentWeight(100); }, std::out_of_range);
|
||||
}
|
||||
|
||||
TEST(Graph, Modularity) {
|
||||
// Graph without edges
|
||||
comdata::Graph graph = GenRandomUnweightedGraph(5, 0);
|
||||
ASSERT_EQ(graph.Modularity(), 0);
|
||||
|
||||
// Chain
|
||||
// (0)--(1)--(2)--(3)--(4)
|
||||
graph = BuildGraph(5, {{0, 1, 0.1},
|
||||
{1, 2, 0.5},
|
||||
{2, 3, 2.3},
|
||||
{3, 4, 4.2}});
|
||||
std::vector<uint32_t> c = {0, 1, 1, 2, 2};
|
||||
SetCommunities(&graph, c);
|
||||
EXPECT_NEAR(graph.Modularity(), 0.036798254314620096, 1e-6);
|
||||
|
||||
// Tree
|
||||
// (0)--(3)
|
||||
// / \
|
||||
// (1) (2)
|
||||
// | / \
|
||||
// (4) (5) (6)
|
||||
graph = BuildGraph(7, {{0, 1, 1.3},
|
||||
{0, 2, 0.2},
|
||||
{0, 3, 1},
|
||||
{1, 4, 3.2},
|
||||
{2, 5, 4.2},
|
||||
{2, 6, 0.7}});
|
||||
c = {0, 0, 1, 0, 0, 1, 2};
|
||||
SetCommunities(&graph, c);
|
||||
EXPECT_NEAR(graph.Modularity(), 0.4424617301530794, 1e-6);
|
||||
|
||||
// Graph without self-loops
|
||||
// (0)--(1)
|
||||
// | \ | \
|
||||
// | \ | \
|
||||
// (2)--(3)-(4)
|
||||
graph = BuildGraph(5, {{0, 1, 0.1},
|
||||
{0, 2, 0.2},
|
||||
{0, 3, 0.3},
|
||||
{1, 3, 0.4},
|
||||
{1, 4, 0.5},
|
||||
{2, 3, 0.6},
|
||||
{3, 4, 0.7}});
|
||||
c = {0, 1, 1, 1, 1};
|
||||
SetCommunities(&graph, c);
|
||||
EXPECT_NEAR(graph.Modularity(), -0.022959183673469507, 1e-6);
|
||||
|
||||
// Graph with self loop [*nodes have self loops]
|
||||
// (0)--(1*)
|
||||
// | \ | \
|
||||
// | \ | \
|
||||
// (2*)--(3)-(4*)
|
||||
graph = BuildGraph(5, {{0, 1, 0.1},
|
||||
{0, 2, 0.2},
|
||||
{0, 3, 0.3},
|
||||
{1, 3, 0.4},
|
||||
{1, 4, 0.5},
|
||||
{2, 3, 0.6},
|
||||
{3, 4, 0.7},
|
||||
{1, 1, 0.8},
|
||||
{2, 2, 0.9},
|
||||
{4, 4, 1}});
|
||||
c = {0, 0, 0, 0, 1};
|
||||
SetCommunities(&graph, c);
|
||||
EXPECT_NEAR(graph.Modularity(), 0.188842975206611, 1e-6);
|
||||
|
||||
// Neo4j example graph
|
||||
// (0)--(1)---(3)--(4)
|
||||
// \ / \ /
|
||||
// (2) (5)
|
||||
graph = BuildGraph(6, {{0, 1, 1},
|
||||
{1, 2, 1},
|
||||
{0, 2, 1},
|
||||
{1, 3, 1},
|
||||
{3, 5, 1},
|
||||
{5, 4, 1},
|
||||
{3, 4, 1}});
|
||||
c = {0, 0, 0, 1, 1, 1};
|
||||
SetCommunities(&graph, c);
|
||||
EXPECT_NEAR(graph.Modularity(), 0.3571428571428571, 1e-6);
|
||||
|
||||
// Example graph from wikipedia
|
||||
// (0)--(1)--(3)--(4)--(5)
|
||||
// \ / | \ /
|
||||
// (2) (7) (6)
|
||||
// / \
|
||||
// (8)--(9)
|
||||
graph = BuildGraph(10, {{0, 1, 1},
|
||||
{1, 2, 1},
|
||||
{0, 2, 1},
|
||||
{1, 3, 1},
|
||||
{3, 4, 1},
|
||||
{4, 5, 1},
|
||||
{5, 6, 1},
|
||||
{6, 4, 1},
|
||||
{3, 7, 1},
|
||||
{7, 8, 1},
|
||||
{7, 9, 1},
|
||||
{8, 9, 1}});
|
||||
c = {0, 0, 0, 0, 1, 1, 1, 2, 2, 2};
|
||||
SetCommunities(&graph, c);
|
||||
EXPECT_NEAR(graph.Modularity(), 0.4896, 1e-4);
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#include "utils.hpp"
|
||||
|
||||
#include <random>
|
||||
|
||||
comdata::Graph BuildGraph(
|
||||
uint32_t nodes, std::vector<std::tuple<uint32_t, uint32_t, double>> edges) {
|
||||
comdata::Graph G(nodes);
|
||||
for (auto &edge : edges)
|
||||
G.AddEdge(std::get<0>(edge), std::get<1>(edge), std::get<2>(edge));
|
||||
return G;
|
||||
}
|
||||
|
||||
comdata::Graph GenRandomUnweightedGraph(uint32_t nodes, uint32_t edges) {
|
||||
auto seed =
|
||||
std::chrono::high_resolution_clock::now().time_since_epoch().count();
|
||||
std::mt19937 rng(seed);
|
||||
std::uniform_int_distribution<uint32_t> dist(0, nodes - 1);
|
||||
std::set<std::tuple<uint32_t, uint32_t, double>> E;
|
||||
for (uint32_t i = 0; i < edges; ++i) {
|
||||
int u;
|
||||
int v;
|
||||
do {
|
||||
u = dist(rng);
|
||||
v = dist(rng);
|
||||
if (u > v) std::swap(u, v);
|
||||
} while (u == v || E.find({u, v, 1}) != E.end());
|
||||
E.insert({u, v, 1});
|
||||
}
|
||||
return BuildGraph(nodes, std::vector<std::tuple<uint32_t, uint32_t, double>>(
|
||||
E.begin(), E.end()));
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
|
||||
#include "data_structures/graph.hpp"
|
||||
|
||||
/// Builds the graph from a given number of nodes and a list of edges.
|
||||
/// Nodes should be 0-indexed and each edge should be provided only once.
|
||||
comdata::Graph BuildGraph(
|
||||
uint32_t nodes, std::vector<std::tuple<uint32_t, uint32_t, double>> edges);
|
||||
|
||||
/// Generates random undirected graph with a given number of nodes and edges.
|
||||
/// The generated graph is not picked out of a uniform distribution. All weights
|
||||
/// are the same and equal to one.
|
||||
comdata::Graph GenRandomUnweightedGraph(uint32_t nodes, uint32_t edges);
|
||||
289
query_modules/mgp_networkx.py
Normal file
289
query_modules/mgp_networkx.py
Normal file
@@ -0,0 +1,289 @@
|
||||
import sys
|
||||
import mgp
|
||||
import collections
|
||||
try:
|
||||
import networkx as nx
|
||||
except ImportError as import_error:
|
||||
sys.stderr.write((
|
||||
'\n'
|
||||
'NOTE: Please install networkx to be able to use Memgraph NetworkX '
|
||||
'wrappers. Using Python:\n'
|
||||
+ sys.version +
|
||||
'\n'))
|
||||
raise import_error
|
||||
|
||||
|
||||
class MemgraphAdjlistOuterDict(collections.abc.Mapping):
|
||||
__slots__ = ('_ctx', '_succ', '_multi')
|
||||
|
||||
def __init__(self, ctx, succ=True, multi=True):
|
||||
self._ctx = ctx
|
||||
self._succ = succ
|
||||
self._multi = multi
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key not in self:
|
||||
raise KeyError
|
||||
return MemgraphAdjlistInnerDict(key, succ=self._succ,
|
||||
multi=self._multi)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._ctx.graph.vertices)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._ctx.graph.vertices)
|
||||
|
||||
def __contains__(self, key):
|
||||
if not isinstance(key, mgp.Vertex):
|
||||
raise TypeError
|
||||
return key in self._ctx.graph.vertices
|
||||
|
||||
|
||||
class MemgraphAdjlistInnerDict(collections.abc.Mapping):
|
||||
__slots__ = ('_node', '_succ', '_multi', '_neighbors')
|
||||
|
||||
def __init__(self, node, succ=True, multi=True):
|
||||
self._node = node
|
||||
self._succ = succ
|
||||
self._multi = multi
|
||||
self._neighbors = None
|
||||
|
||||
def __getitem__(self, key):
|
||||
# NOTE: NetworkX 2.4, classes/coreviews.py:143. UnionAtlas expects a
|
||||
# KeyError when indexing with a vertex that is not a neighbor.
|
||||
if key not in self:
|
||||
raise KeyError
|
||||
if not self._multi:
|
||||
return UnhashableProperties(self._get_edge(key).properties)
|
||||
return MemgraphEdgeKeyDict(self._node, key, self._succ)
|
||||
|
||||
def __iter__(self):
|
||||
yield from self._get_neighbors()
|
||||
|
||||
def __len__(self):
|
||||
return len(self._get_neighbors())
|
||||
|
||||
def __contains__(self, key):
|
||||
if not isinstance(key, mgp.Vertex):
|
||||
raise TypeError
|
||||
return key in self._get_neighbors()
|
||||
|
||||
def _get_neighbors(self):
|
||||
if not self._neighbors:
|
||||
if self._succ:
|
||||
self._neighbors = set(
|
||||
e.to_vertex for e in self._node.out_edges)
|
||||
else:
|
||||
self._neighbors = set(
|
||||
e.from_vertex for e in self._node.in_edges)
|
||||
return self._neighbors
|
||||
|
||||
def _get_edge(self, neighbor):
|
||||
if self._succ:
|
||||
edge = list(filter(lambda e: e.to_vertex == neighbor,
|
||||
self._node.out_edges))
|
||||
else:
|
||||
edge = list(filter(lambda e: e.from_vertex == neighbor,
|
||||
self._node.in_edges))
|
||||
|
||||
assert len(edge) >= 1
|
||||
if len(edge) > 1:
|
||||
raise RuntimeError('Graph contains multiedges but '
|
||||
'is of non-multigraph type: {}'.format(edge))
|
||||
|
||||
return edge[0]
|
||||
|
||||
|
||||
class MemgraphEdgeKeyDict(collections.abc.Mapping):
|
||||
__slots__ = ('_node', '_neighbor', '_succ', '_edges')
|
||||
|
||||
def __init__(self, node, neighbor, succ=True):
|
||||
self._node = node
|
||||
self._neighbor = neighbor
|
||||
self._succ = succ
|
||||
self._edges = None
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key not in self:
|
||||
raise KeyError
|
||||
return UnhashableProperties(key.properties)
|
||||
|
||||
def __iter__(self):
|
||||
yield from self._get_edges()
|
||||
|
||||
def __len__(self):
|
||||
return len(self._get_edges())
|
||||
|
||||
def __contains__(self, key):
|
||||
if not isinstance(key, mgp.Edge):
|
||||
raise TypeError
|
||||
return key in self._get_edges()
|
||||
|
||||
def _get_edges(self):
|
||||
if not self._edges:
|
||||
if self._succ:
|
||||
self._edges = list(filter(
|
||||
lambda e: e.to_vertex == self._neighbor,
|
||||
self._node.out_edges))
|
||||
else:
|
||||
self._edges = list(filter(
|
||||
lambda e: e.from_vertex == self._neighbor,
|
||||
self._node.in_edges))
|
||||
return self._edges
|
||||
|
||||
|
||||
class UnhashableProperties(collections.abc.Mapping):
|
||||
__slots__ = ('_properties')
|
||||
|
||||
def __init__(self, properties):
|
||||
self._properties = properties
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._properties[key]
|
||||
|
||||
def __iter__(self):
|
||||
yield from self._properties
|
||||
|
||||
def __len__(self):
|
||||
return len(self._properties)
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self._properties
|
||||
|
||||
# NOTE: Explicitly disable hashing. See the comment in MemgraphNodeDict.
|
||||
__hash__ = None
|
||||
|
||||
|
||||
class MemgraphNodeDict(collections.abc.Mapping):
|
||||
__slots__ = ('_ctx',)
|
||||
|
||||
def __init__(self, ctx):
|
||||
self._ctx = ctx
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key not in self:
|
||||
raise KeyError
|
||||
# NOTE: NetworkX 2.4, classes/digraph.py:484. NetworkX expects the
|
||||
# tuples provided to add_nodes_from to be unhashable and cause a
|
||||
# TypeError when trying to index the node dictionary. This happens
|
||||
# because the data dictionary element of the tuple is unhashable. We do
|
||||
# the same thing by returning an unhashable data dictionary.
|
||||
return UnhashableProperties(key.properties)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._ctx.graph.vertices)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._ctx.graph.vertices)
|
||||
|
||||
def __contains__(self, key):
|
||||
# NOTE: NetworkX 2.4, graph.py:425. Graph.__contains__ relies on
|
||||
# self._node's (i.e. the dictionary produced by node_dict_factory)
|
||||
# __contains__ to raise a TypeError when indexing with something weird,
|
||||
# e.g. with sets. This is the behavior of dict.
|
||||
if not isinstance(key, mgp.Vertex):
|
||||
raise TypeError
|
||||
return key in self._ctx.graph.vertices
|
||||
|
||||
|
||||
class MemgraphDiGraphBase:
|
||||
def __init__(self, incoming_graph_data=None, ctx=None, multi=True,
|
||||
**kwargs):
|
||||
# NOTE: We assume that our graph will never be given any initial data
|
||||
# because we already pull our data from the Memgraph database. This
|
||||
# assert is triggered by certain NetworkX procedures because they
|
||||
# create a new instance of our class and try to populate it with their
|
||||
# own data.
|
||||
assert incoming_graph_data is None
|
||||
|
||||
# NOTE: We allow for ctx to be None in order to allow certain NetworkX
|
||||
# procedures (such as `subgraph`) to work. Such procedures directly
|
||||
# modify the graph's internal attributes and don't try to populate it
|
||||
# with initial data or modify it.
|
||||
|
||||
self.node_dict_factory = lambda: MemgraphNodeDict(ctx) \
|
||||
if ctx else self._error
|
||||
self.node_attr_dict_factory = self._error
|
||||
|
||||
self.adjlist_outer_dict_factory = \
|
||||
lambda: MemgraphAdjlistOuterDict(ctx, multi=multi) \
|
||||
if ctx else self._error
|
||||
self.adjlist_inner_dict_factory = self._error
|
||||
self.edge_key_dict_factory = self._error
|
||||
self.edge_attr_dict_factory = self._error
|
||||
|
||||
# NOTE: We forbid any mutating operations because our graph is
|
||||
# immutable and pulls its data from the Memgraph database.
|
||||
for f in ['add_node', 'add_nodes_from', 'remove_node',
|
||||
'remove_nodes_from', 'add_edge', 'add_edges_from',
|
||||
'add_weighted_edges_from', 'new_edge_key', 'remove_edge',
|
||||
'remove_edges_from', 'update', 'clear']:
|
||||
setattr(self, f, lambda *args, **kwargs: self._error())
|
||||
|
||||
super().__init__(None, **kwargs)
|
||||
|
||||
# NOTE: This is a necessary hack because NetworkX assumes that the
|
||||
# customizable factory functions will only ever return *empty*
|
||||
# dictionaries. In our case, the factory functions return our custom,
|
||||
# already populated, dictionaries. Because self._pred and self._end are
|
||||
# initialized by the same factory function, they end up storing the
|
||||
# same adjacency lists which is not good. We correct that here.
|
||||
self._pred = MemgraphAdjlistOuterDict(ctx, succ=False, multi=multi)
|
||||
|
||||
def _error(self):
|
||||
raise RuntimeError('Modification operations are not supported')
|
||||
|
||||
|
||||
class MemgraphMultiDiGraph(MemgraphDiGraphBase, nx.MultiDiGraph):
|
||||
def __init__(self, incoming_graph_data=None, ctx=None, **kwargs):
|
||||
super().__init__(incoming_graph_data=incoming_graph_data,
|
||||
ctx=ctx, multi=True, **kwargs)
|
||||
|
||||
|
||||
def MemgraphMultiGraph(incoming_graph_data=None, ctx=None, **kwargs):
|
||||
return MemgraphMultiDiGraph(incoming_graph_data=incoming_graph_data,
|
||||
ctx=ctx, **kwargs).to_undirected(as_view=True)
|
||||
|
||||
|
||||
class MemgraphDiGraph(MemgraphDiGraphBase, nx.DiGraph):
|
||||
def __init__(self, incoming_graph_data=None, ctx=None, **kwargs):
|
||||
super().__init__(incoming_graph_data=incoming_graph_data,
|
||||
ctx=ctx, multi=False, **kwargs)
|
||||
|
||||
|
||||
def MemgraphGraph(incoming_graph_data=None, ctx=None, **kwargs):
|
||||
return MemgraphDiGraph(incoming_graph_data=incoming_graph_data,
|
||||
ctx=ctx, **kwargs).to_undirected(as_view=True)
|
||||
|
||||
|
||||
class PropertiesDictionary(collections.abc.Mapping):
|
||||
__slots__ = ('_ctx', '_prop', '_len')
|
||||
|
||||
def __init__(self, ctx, prop):
|
||||
self._ctx = ctx
|
||||
self._prop = prop
|
||||
self._len = None
|
||||
|
||||
def __getitem__(self, vertex):
|
||||
if vertex not in self:
|
||||
raise KeyError
|
||||
try:
|
||||
return vertex.properties[self._prop]
|
||||
except KeyError:
|
||||
raise KeyError(("{} doesn\t have the required " +
|
||||
"property '{}'").format(vertex, self._prop))
|
||||
|
||||
def __iter__(self):
|
||||
for v in self._ctx.graph.vertices:
|
||||
if self._prop in v.properties:
|
||||
yield v
|
||||
|
||||
def __len__(self):
|
||||
if not self._len:
|
||||
self._len = sum(1 for _ in self)
|
||||
return self._len
|
||||
|
||||
def __contains__(self, vertex):
|
||||
if not isinstance(vertex, mgp.Vertex):
|
||||
raise TypeError
|
||||
return self._prop in vertex.properties
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user