diff --git a/.github/workflows/daily_benchmark.yaml b/.github/workflows/daily_benchmark.yaml index 4cded7b0c..aa2686977 100644 --- a/.github/workflows/daily_benchmark.yaml +++ b/.github/workflows/daily_benchmark.yaml @@ -16,7 +16,7 @@ jobs: steps: - name: Set up repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Number of commits to fetch. `0` indicates all history for all # branches and tags. (default: 1) diff --git a/.github/workflows/diff.yaml b/.github/workflows/diff.yaml index 340d11711..411bed79c 100644 --- a/.github/workflows/diff.yaml +++ b/.github/workflows/diff.yaml @@ -27,7 +27,7 @@ jobs: steps: - name: Set up repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Number of commits to fetch. `0` indicates all history for all # branches and tags. (default: 1) @@ -65,7 +65,7 @@ jobs: steps: - name: Set up repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Number of commits to fetch. `0` indicates all history for all # branches and tags. (default: 1) @@ -96,7 +96,7 @@ jobs: - name: Python code analysis run: | - CHANGED_FILES=$(git diff -U0 ${{ env.BASE_BRANCH }}... --name-only) + CHANGED_FILES=$(git diff -U0 ${{ env.BASE_BRANCH }}... --name-only --diff-filter=d) for file in ${CHANGED_FILES}; do echo ${file} if [[ ${file} == *.py ]]; then @@ -137,9 +137,9 @@ jobs: tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu - name: Save code coverage - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: "Code coverage" + name: "Code coverage(Code analysis)" path: tools/github/generated/code_coverage.tar.gz - name: Run clang-tidy @@ -162,7 +162,7 @@ jobs: steps: - name: Set up repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Number of commits to fetch. `0` indicates all history for all # branches and tags. (default: 1) @@ -208,9 +208,9 @@ jobs: ./cppcheck_and_clang_format diff - name: Save cppcheck and clang-format errors - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: "Code coverage" + name: "Code coverage(Debug build)" path: tools/github/cppcheck_and_clang_format.txt release_build: @@ -223,16 +223,12 @@ jobs: steps: - name: Set up repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Number of commits to fetch. `0` indicates all history for all # branches and tags. (default: 1) fetch-depth: 0 - - name: Check e2e service dependencies - run: | - cd tests/e2e - ./dependency_check.sh - name: Build release binaries run: | @@ -255,7 +251,7 @@ jobs: ./continuous_integration - name: Save quality assurance status - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: "GQL Behave Status" path: | @@ -271,7 +267,16 @@ jobs: cd build ctest -R memgraph__unit --output-on-failure -j$THREADS + - name: Ensure Kafka and Pulsar are up + if: false + run: | + cd tests/e2e/streams/kafka + docker-compose up -d + cd ../pulsar + docker-compose up -d + - name: Run e2e tests + if: false run: | cd tests ./setup.sh /opt/toolchain-v4/activate @@ -279,6 +284,14 @@ jobs: cd e2e ./run.sh + - name: Ensure Kafka and Pulsar are down + if: false + run: | + cd tests/e2e/streams/kafka + docker-compose down + cd ../pulsar + docker-compose down + - name: Run stress test (plain) run: | cd tests/stress @@ -312,16 +325,128 @@ jobs: cpack -G DEB --config ../CPackConfig.cmake - name: Save enterprise DEB package - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: "Enterprise DEB package" path: build/output/memgraph*.deb - name: Save test data - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() with: - name: "Test data" + name: "Test data(Release build)" + path: | + # multiple paths could be defined + build/logs + + experimental_build_ha: + name: "High availability build" + runs-on: [self-hosted, Linux, X64, Diff] + env: + THREADS: 24 + MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }} + MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }} + + steps: + - name: Set up repository + uses: actions/checkout@v4 + 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: | + source /opt/toolchain-v4/activate + ./init + cd build + cmake -DCMAKE_BUILD_TYPE=Release -DMG_EXPERIMENTAL_HIGH_AVAILABILITY=ON .. + make -j$THREADS + - name: Run unit tests + run: | + source /opt/toolchain-v4/activate + cd build + ctest -R memgraph__unit --output-on-failure -j$THREADS + - name: Run e2e tests + if: false + run: | + cd tests + ./setup.sh /opt/toolchain-v4/activate + source ve3/bin/activate_e2e + cd e2e + ./run.sh "Coordinator" + ./run.sh "Client initiated failover" + ./run.sh "Uninitialized cluster" + - name: Save test data + uses: actions/upload-artifact@v4 + if: always() + with: + name: "Test data(High availability build)" + path: | + # multiple paths could be defined + build/logs + + experimental_build_mt: + name: "MultiTenancy replication build" + runs-on: [self-hosted, Linux, X64, Diff] + env: + THREADS: 24 + MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }} + MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }} + + steps: + - name: Set up repository + uses: actions/checkout@v4 + with: + # Number of commits to fetch. `0` indicates all history for all + # branches and tags. (default: 1) + fetch-depth: 0 + + + - name: Build release binaries + run: | + # Activate toolchain. + source /opt/toolchain-v4/activate + + # Initialize dependencies. + ./init + + # Build MT replication experimental binaries. + cd build + cmake -DCMAKE_BUILD_TYPE=Release -D MG_EXPERIMENTAL_REPLICATION_MULTITENANCY=ON .. + make -j$THREADS + + - name: Run unit tests + run: | + # Activate toolchain. + source /opt/toolchain-v4/activate + + # Run unit tests. + cd build + ctest -R memgraph__unit --output-on-failure -j$THREADS + + - name: Run e2e tests + if: false + run: | + cd tests + ./setup.sh /opt/toolchain-v4/activate + source ve3/bin/activate_e2e + cd e2e + + # Just the replication based e2e tests + ./run.sh "Replicate multitenancy" + ./run.sh "Show" + ./run.sh "Show while creating invalid state" + ./run.sh "Delete edge replication" + ./run.sh "Read-write benchmark" + ./run.sh "Index replication" + ./run.sh "Constraints" + + - name: Save test data + uses: actions/upload-artifact@v4 + if: always() + with: + name: "Test data(MultiTenancy replication build)" path: | # multiple paths could be defined build/logs @@ -337,7 +462,7 @@ jobs: steps: - name: Set up repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Number of commits to fetch. `0` indicates all history for all # branches and tags. (default: 1) @@ -354,13 +479,18 @@ jobs: cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo .. make -j$THREADS memgraph + - name: Refresh Jepsen Cluster + run: | + cd tests/jepsen + ./run.sh cluster-refresh + - name: Run Jepsen tests run: | cd tests/jepsen ./run.sh test-all-individually --binary ../../build/memgraph --ignore-run-stdout-logs --ignore-run-stderr-logs - name: Save Jepsen report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{ always() }} with: name: "Jepsen Report" @@ -376,7 +506,7 @@ jobs: steps: - name: Set up repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Number of commits to fetch. `0` indicates all history for all # branches and tags. (default: 1) diff --git a/.github/workflows/full_clang_tidy.yaml b/.github/workflows/full_clang_tidy.yaml index bce2a4a0a..10816cd7a 100644 --- a/.github/workflows/full_clang_tidy.yaml +++ b/.github/workflows/full_clang_tidy.yaml @@ -14,7 +14,7 @@ jobs: steps: - name: Set up repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Number of commits to fetch. `0` indicates all history for all # branches and tags. (default: 1) diff --git a/.github/workflows/package_memgraph.yaml b/.github/workflows/package_memgraph.yaml index 48a61ca53..45a62f037 100644 --- a/.github/workflows/package_memgraph.yaml +++ b/.github/workflows/package_memgraph.yaml @@ -42,14 +42,14 @@ jobs: timeout-minutes: 60 steps: - name: "Set up repository" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 # Required because of release/get_version.py - name: "Build package" run: | ./release/package/run.sh package amzn-2 ${{ github.event.inputs.build_type }} - name: "Upload package" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: amzn-2 path: build/output/amzn-2/memgraph*.rpm @@ -60,14 +60,14 @@ jobs: timeout-minutes: 60 steps: - name: "Set up repository" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 # Required because of release/get_version.py - name: "Build package" run: | ./release/package/run.sh package centos-7 ${{ github.event.inputs.build_type }} - name: "Upload package" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: centos-7 path: build/output/centos-7/memgraph*.rpm @@ -78,14 +78,14 @@ jobs: timeout-minutes: 60 steps: - name: "Set up repository" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 # Required because of release/get_version.py - name: "Build package" run: | ./release/package/run.sh package centos-9 ${{ github.event.inputs.build_type }} - name: "Upload package" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: centos-9 path: build/output/centos-9/memgraph*.rpm @@ -96,14 +96,14 @@ jobs: timeout-minutes: 60 steps: - name: "Set up repository" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 # Required because of release/get_version.py - name: "Build package" run: | ./release/package/run.sh package debian-10 ${{ github.event.inputs.build_type }} - name: "Upload package" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: debian-10 path: build/output/debian-10/memgraph*.deb @@ -114,14 +114,14 @@ jobs: timeout-minutes: 60 steps: - name: "Set up repository" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 # Required because of release/get_version.py - name: "Build package" run: | ./release/package/run.sh package debian-11 ${{ github.event.inputs.build_type }} - name: "Upload package" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: debian-11 path: build/output/debian-11/memgraph*.deb @@ -132,14 +132,14 @@ jobs: timeout-minutes: 120 steps: - name: "Set up repository" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 # Required because of release/get_version.py - name: "Build package" run: | ./release/package/run.sh package debian-11-arm ${{ github.event.inputs.build_type }} - name: "Upload package" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: debian-11-aarch64 path: build/output/debian-11-arm/memgraph*.deb @@ -150,14 +150,14 @@ jobs: timeout-minutes: 60 steps: - name: "Set up repository" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 # Required because of release/get_version.py - name: "Build package" run: | ./release/package/run.sh package debian-11 ${{ github.event.inputs.build_type }} --for-platform - name: "Upload package" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: debian-11-platform path: build/output/debian-11/memgraph*.deb @@ -168,7 +168,7 @@ jobs: timeout-minutes: 60 steps: - name: "Set up repository" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 # Required because of release/get_version.py - name: "Build package" @@ -177,7 +177,7 @@ jobs: ./run.sh package debian-11 ${{ github.event.inputs.build_type }} --for-docker ./run.sh docker - name: "Upload package" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: docker path: build/output/docker/memgraph*.tar.gz @@ -188,14 +188,14 @@ jobs: timeout-minutes: 60 steps: - name: "Set up repository" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 # Required because of release/get_version.py - name: "Build package" run: | ./release/package/run.sh package fedora-36 ${{ github.event.inputs.build_type }} - name: "Upload package" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: fedora-36 path: build/output/fedora-36/memgraph*.rpm @@ -206,14 +206,14 @@ jobs: timeout-minutes: 60 steps: - name: "Set up repository" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 # Required because of release/get_version.py - name: "Build package" run: | ./release/package/run.sh package ubuntu-18.04 ${{ github.event.inputs.build_type }} - name: "Upload package" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ubuntu-18.04 path: build/output/ubuntu-18.04/memgraph*.deb @@ -224,14 +224,14 @@ jobs: timeout-minutes: 60 steps: - name: "Set up repository" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 # Required because of release/get_version.py - name: "Build package" run: | ./release/package/run.sh package ubuntu-20.04 ${{ github.event.inputs.build_type }} - name: "Upload package" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ubuntu-20.04 path: build/output/ubuntu-20.04/memgraph*.deb @@ -242,14 +242,14 @@ jobs: timeout-minutes: 60 steps: - name: "Set up repository" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 # Required because of release/get_version.py - name: "Build package" run: | ./release/package/run.sh package ubuntu-22.04 ${{ github.event.inputs.build_type }} - name: "Upload package" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ubuntu-22.04 path: build/output/ubuntu-22.04/memgraph*.deb @@ -260,14 +260,14 @@ jobs: timeout-minutes: 120 steps: - name: "Set up repository" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 # Required because of release/get_version.py - name: "Build package" run: | ./release/package/run.sh package ubuntu-22.04-arm ${{ github.event.inputs.build_type }} - name: "Upload package" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ubuntu-22.04-aarch64 path: build/output/ubuntu-22.04-arm/memgraph*.deb @@ -279,7 +279,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: # name: # if name input parameter is not provided, all artifacts are downloaded # and put in directories named after each one. diff --git a/.github/workflows/performance_benchmarks.yaml b/.github/workflows/performance_benchmarks.yaml index d31c661de..cc030c180 100644 --- a/.github/workflows/performance_benchmarks.yaml +++ b/.github/workflows/performance_benchmarks.yaml @@ -14,7 +14,7 @@ jobs: steps: - name: Set up repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Number of commits to fetch. `0` indicates all history for all # branches and tags. (default: 1) diff --git a/.github/workflows/release_centos8.yaml b/.github/workflows/release_centos8.yaml deleted file mode 100644 index c82916df6..000000000 --- a/.github/workflows/release_centos8.yaml +++ /dev/null @@ -1,318 +0,0 @@ -name: Release CentOS 8 - -on: - workflow_dispatch: - inputs: - build_type: - type: choice - description: "Memgraph Build type. Default value is Release." - default: 'Release' - options: - - Release - - RelWithDebInfo - - schedule: - - cron: "0 22 * * *" - -jobs: - community_build: - name: "Community build" - runs-on: [self-hosted, Linux, X64, CentOS8] - env: - THREADS: 24 - MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }} - MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }} - timeout-minutes: 960 - - steps: - - name: Set up repository - uses: actions/checkout@v3 - with: - # Number of commits to fetch. `0` indicates all history for all - # branches and tags. (default: 1) - fetch-depth: 0 - - - name: Build community binaries - run: | - # Activate toolchain. - source /opt/toolchain-v4/activate - - # Initialize dependencies. - ./init - - # Set default build_type to Release - INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }} - BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"} - - # Build community binaries. - cd build - cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DMG_ENTERPRISE=OFF .. - make -j$THREADS - - - name: Run unit tests - run: | - # Activate toolchain. - source /opt/toolchain-v4/activate - - # Run unit tests. - cd build - ctest -R memgraph__unit --output-on-failure - - coverage_build: - name: "Coverage build" - runs-on: [self-hosted, Linux, X64, CentOS8] - env: - THREADS: 24 - MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }} - MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }} - - steps: - - name: Set up repository - uses: actions/checkout@v3 - with: - # Number of commits to fetch. `0` indicates all history for all - # branches and tags. (default: 1) - fetch-depth: 0 - - - name: Build coverage binaries - run: | - # Activate toolchain. - source /opt/toolchain-v4/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-v4/activate - - # Run unit tests. - cd build - ctest -R memgraph__unit --output-on-failure - - - name: Compute code coverage - run: | - # Activate toolchain. - source /opt/toolchain-v4/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@v3 - 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 - MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }} - MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }} - - steps: - - name: Set up repository - uses: actions/checkout@v3 - with: - # Number of commits to fetch. `0` indicates all history for all - # branches and tags. (default: 1) - fetch-depth: 0 - - - name: Build debug binaries - run: | - # Activate toolchain. - source /opt/toolchain-v4/activate - - # Initialize dependencies. - ./init - - # Build debug binaries. - cd build - cmake .. - make -j$THREADS - - - name: Run leftover CTest tests - run: | - # Activate toolchain. - source /opt/toolchain-v4/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: | - tests/integration/run.sh - - - name: Run cppcheck and clang-format - run: | - # Activate toolchain. - source /opt/toolchain-v4/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@v3 - 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 - MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }} - MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }} - timeout-minutes: 960 - - steps: - - name: Set up repository - uses: actions/checkout@v3 - with: - # Number of commits to fetch. `0` indicates all history for all - # branches and tags. (default: 1) - fetch-depth: 0 - - - name: Build release binaries - run: | - # Activate toolchain. - source /opt/toolchain-v4/activate - - # Initialize dependencies. - ./init - - # Set default build_type to Release - INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }} - BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"} - - # Build release binaries. - cd build - cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. - make -j$THREADS - - - name: Create enterprise RPM package - run: | - # Activate toolchain. - source /opt/toolchain-v4/activate - - cd build - - # create mgconsole - # we use the -B to force the build - make -j$THREADS -B mgconsole - - # Create enterprise RPM package. - mkdir output && cd output - cpack -G RPM --config ../CPackConfig.cmake - rpmlint memgraph*.rpm - - - name: Save enterprise RPM package - uses: actions/upload-artifact@v3 - with: - name: "Enterprise RPM package" - path: build/output/memgraph*.rpm - - - name: Run micro benchmark tests - run: | - # Activate toolchain. - source /opt/toolchain-v4/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 - ./setup.sh /opt/toolchain-v4/activate - cd gql_behave - ./continuous_integration - - - name: Save quality assurance status - uses: actions/upload-artifact@v3 - with: - name: "GQL Behave Status" - path: | - tests/gql_behave/gql_behave_status.csv - tests/gql_behave/gql_behave_status.html - - - name: Run unit tests - run: | - # Activate toolchain. - source /opt/toolchain-v4/activate - - # Run unit tests. - cd build - ctest -R memgraph__unit --output-on-failure - - - name: Run e2e tests - run: | - cd tests - ./setup.sh /opt/toolchain-v4/activate - source ve3/bin/activate_e2e - cd e2e - ./run.sh - - - 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 diff --git a/.github/workflows/release_debian10.yaml b/.github/workflows/release_debian10.yaml index 9a38e4cfb..36b9148c3 100644 --- a/.github/workflows/release_debian10.yaml +++ b/.github/workflows/release_debian10.yaml @@ -14,19 +14,21 @@ on: schedule: - cron: "0 22 * * *" +env: + THREADS: 24 + MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }} + MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }} + BUILD_TYPE: ${{ github.event.inputs.build_type || 'Release' }} + jobs: community_build: name: "Community build" runs-on: [self-hosted, Linux, X64, Debian10] - env: - THREADS: 24 - MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }} - MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }} - timeout-minutes: 960 + timeout-minutes: 90 steps: - name: Set up repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Number of commits to fetch. `0` indicates all history for all # branches and tags. (default: 1) @@ -40,10 +42,6 @@ jobs: # Initialize dependencies. ./init - # Set default build_type to Release - INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }} - BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"} - # Build community binaries. cd build cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DMG_ENTERPRISE=OFF .. @@ -65,10 +63,11 @@ jobs: THREADS: 24 MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }} MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }} + timeout-minutes: 90 steps: - name: Set up repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Number of commits to fetch. `0` indicates all history for all # branches and tags. (default: 1) @@ -110,22 +109,19 @@ jobs: tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu - name: Save code coverage - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: "Code coverage" + name: "Code coverage(Coverage build)" path: tools/github/generated/code_coverage.tar.gz debug_build: name: "Debug build" runs-on: [self-hosted, Linux, X64, Debian10] - env: - THREADS: 24 - MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }} - MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }} + timeout-minutes: 90 steps: - name: Set up repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Number of commits to fetch. `0` indicates all history for all # branches and tags. (default: 1) @@ -157,10 +153,6 @@ jobs: run: | ./tests/drivers/run.sh - - name: Run integration tests - run: | - tests/integration/run.sh - - name: Run cppcheck and clang-format run: | # Activate toolchain. @@ -171,23 +163,49 @@ jobs: ./cppcheck_and_clang_format diff - name: Save cppcheck and clang-format errors - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: "Code coverage" + name: "Code coverage(Debug build)" path: tools/github/cppcheck_and_clang_format.txt - release_build: - name: "Release build" - runs-on: [self-hosted, Linux, X64, Debian10, BigMemory] - env: - THREADS: 24 - MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }} - MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }} - timeout-minutes: 960 + debug_integration_test: + name: "Debug integration tests" + runs-on: [self-hosted, Linux, X64, Debian10] + timeout-minutes: 90 steps: - name: Set up repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 + 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-v4/activate + + # Initialize dependencies. + ./init + + # Build debug binaries. + cd build + cmake .. + make -j$THREADS + + - name: Run integration tests + run: | + tests/integration/run.sh + + release_build: + name: "Release build" + runs-on: [self-hosted, Linux, X64, Debian10] + timeout-minutes: 90 + + steps: + - name: Set up repository + uses: actions/checkout@v4 with: # Number of commits to fetch. `0` indicates all history for all # branches and tags. (default: 1) @@ -201,10 +219,6 @@ jobs: # Initialize dependencies. ./init - # Set default build_type to Release - INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }} - BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"} - # Build release binaries. cd build cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. @@ -226,11 +240,60 @@ jobs: cpack -G DEB --config ../CPackConfig.cmake - name: Save enterprise DEB package - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: "Enterprise DEB package" path: build/output/memgraph*.deb + - name: Run GQL Behave tests + run: | + cd tests + ./setup.sh /opt/toolchain-v4/activate + cd gql_behave + ./continuous_integration + + - name: Save quality assurance status + uses: actions/upload-artifact@v4 + with: + name: "GQL Behave Status" + path: | + tests/gql_behave/gql_behave_status.csv + tests/gql_behave/gql_behave_status.html + + - name: Run unit tests + run: | + # Activate toolchain. + source /opt/toolchain-v4/activate + + # Run unit tests. + cd build + ctest -R memgraph__unit --output-on-failure + + release_benchmark_tests: + name: "Release Benchmark Tests" + runs-on: [self-hosted, Linux, X64, Debian10] + timeout-minutes: 90 + + steps: + - name: Set up repository + uses: actions/checkout@v4 + with: + # Number of commits to fetch. `0` indicates all history for all + # branches and tags. (default: 1) + fetch-depth: 0 + + - name: Build release binaries + run: | + # Activate toolchain. + source /opt/toolchain-v4/activate + # Initialize dependencies. + ./init + + # Build release binaries + cd build + cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. + make -j$THREADS + - name: Run micro benchmark tests run: | # Activate toolchain. @@ -257,29 +320,38 @@ jobs: --num-database-workers 9 --num-clients-workers 30 \ --no-strict - - name: Run GQL Behave tests - run: | - cd tests - ./setup.sh /opt/toolchain-v4/activate - cd gql_behave - ./continuous_integration + release_e2e_test: + if: false + name: "Release End-to-end Test" + runs-on: [self-hosted, Linux, X64, Debian10] + timeout-minutes: 90 - - name: Save quality assurance status - uses: actions/upload-artifact@v3 + steps: + - name: Set up repository + uses: actions/checkout@v4 with: - name: "GQL Behave Status" - path: | - tests/gql_behave/gql_behave_status.csv - tests/gql_behave/gql_behave_status.html + # Number of commits to fetch. `0` indicates all history for all + # branches and tags. (default: 1) + fetch-depth: 0 - - name: Run unit tests + - name: Build release binaries run: | # Activate toolchain. source /opt/toolchain-v4/activate + # Initialize dependencies. + ./init - # Run unit tests. + # Build release binaries cd build - ctest -R memgraph__unit --output-on-failure + cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. + make -j$THREADS + + - name: Ensure Kafka and Pulsar are up + run: | + cd tests/e2e/streams/kafka + docker-compose up -d + cd ../pulsar + docker-compose up -d - name: Run e2e tests run: | @@ -289,6 +361,40 @@ jobs: cd e2e ./run.sh + - name: Ensure Kafka and Pulsar are down + if: always() + run: | + cd tests/e2e/streams/kafka + docker-compose down + cd ../pulsar + docker-compose down + + release_durability_stress_tests: + name: "Release durability and stress tests" + runs-on: [self-hosted, Linux, X64, Debian10] + timeout-minutes: 90 + + steps: + - name: Set up repository + uses: actions/checkout@v4 + with: + # Number of commits to fetch. `0` indicates all history for all + # branches and tags. (default: 1) + fetch-depth: 0 + + - name: Build release binaries + run: | + # Activate toolchain. + source /opt/toolchain-v4/activate + + # Initialize dependencies. + ./init + + # Build release binaries. + cd build + cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. + make -j$THREADS + - name: Run stress test (plain) run: | cd tests/stress @@ -299,11 +405,6 @@ jobs: 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 @@ -319,15 +420,11 @@ jobs: release_jepsen_test: name: "Release Jepsen Test" runs-on: [self-hosted, Linux, X64, Debian10, JepsenControl] - env: - THREADS: 24 - MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }} - MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }} - timeout-minutes: 60 + timeout-minutes: 90 steps: - name: Set up repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Number of commits to fetch. `0` indicates all history for all # branches and tags. (default: 1) @@ -340,22 +437,23 @@ jobs: # Initialize dependencies. ./init - # Set default build_type to Release - INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }} - BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"} - # Build only memgraph release binary. cd build cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. make -j$THREADS memgraph + - name: Refresh Jepsen Cluster + run: | + cd tests/jepsen + ./run.sh cluster-refresh + - name: Run Jepsen tests run: | cd tests/jepsen ./run.sh test-all-individually --binary ../../build/memgraph --ignore-run-stdout-logs --ignore-run-stderr-logs - name: Save Jepsen report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{ always() }} with: name: "Jepsen Report" diff --git a/.github/workflows/release_docker.yaml b/.github/workflows/release_docker.yaml index 2ebb2e804..d5e02254b 100644 --- a/.github/workflows/release_docker.yaml +++ b/.github/workflows/release_docker.yaml @@ -19,7 +19,7 @@ jobs: DOCKER_REPOSITORY_NAME: memgraph steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v2 diff --git a/.github/workflows/release_mgbench_client.yaml b/.github/workflows/release_mgbench_client.yaml index 2abdad2b9..88c65f7fe 100644 --- a/.github/workflows/release_mgbench_client.yaml +++ b/.github/workflows/release_mgbench_client.yaml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v2 diff --git a/.github/workflows/release_ubuntu2004.yaml b/.github/workflows/release_ubuntu2004.yaml index 225a4473c..96f85bdf3 100644 --- a/.github/workflows/release_ubuntu2004.yaml +++ b/.github/workflows/release_ubuntu2004.yaml @@ -14,19 +14,21 @@ on: schedule: - cron: "0 22 * * *" +env: + THREADS: 24 + MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }} + MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }} + BUILD_TYPE: ${{ github.event.inputs.build_type || 'Release' }} + jobs: community_build: name: "Community build" runs-on: [self-hosted, Linux, X64, Ubuntu20.04] - env: - THREADS: 24 - MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }} - MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }} - timeout-minutes: 960 + timeout-minutes: 90 steps: - name: Set up repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Number of commits to fetch. `0` indicates all history for all # branches and tags. (default: 1) @@ -40,10 +42,6 @@ jobs: # Initialize dependencies. ./init - # Set default build_type to Release - INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }} - BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"} - # Build community binaries. cd build cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DMG_ENTERPRISE=OFF .. @@ -61,14 +59,11 @@ jobs: coverage_build: name: "Coverage build" runs-on: [self-hosted, Linux, X64, Ubuntu20.04] - env: - THREADS: 24 - MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }} - MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }} + timeout-minutes: 90 steps: - name: Set up repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Number of commits to fetch. `0` indicates all history for all # branches and tags. (default: 1) @@ -110,22 +105,19 @@ jobs: tar -czf code_coverage.tar.gz coverage.json html report.json summary.rmu - name: Save code coverage - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: "Code coverage" + name: "Code coverage(Coverage build)" path: tools/github/generated/code_coverage.tar.gz debug_build: name: "Debug build" runs-on: [self-hosted, Linux, X64, Ubuntu20.04] - env: - THREADS: 24 - MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }} - MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }} + timeout-minutes: 90 steps: - name: Set up repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Number of commits to fetch. `0` indicates all history for all # branches and tags. (default: 1) @@ -157,10 +149,6 @@ jobs: run: | ./tests/drivers/run.sh - - name: Run integration tests - run: | - tests/integration/run.sh - - name: Run cppcheck and clang-format run: | # Activate toolchain. @@ -171,23 +159,49 @@ jobs: ./cppcheck_and_clang_format diff - name: Save cppcheck and clang-format errors - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: "Code coverage" + name: "Code coverage(Debug build)" path: tools/github/cppcheck_and_clang_format.txt + debug_integration_test: + name: "Debug integration tests" + runs-on: [self-hosted, Linux, X64, Ubuntu20.04] + timeout-minutes: 90 + + steps: + - name: Set up repository + uses: actions/checkout@v4 + 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-v4/activate + + # Initialize dependencies. + ./init + + # Build debug binaries. + cd build + cmake .. + make -j$THREADS + + - name: Run integration tests + run: | + tests/integration/run.sh + release_build: name: "Release build" runs-on: [self-hosted, Linux, X64, Ubuntu20.04] - env: - THREADS: 24 - MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }} - MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }} - timeout-minutes: 960 + timeout-minutes: 90 steps: - name: Set up repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Number of commits to fetch. `0` indicates all history for all # branches and tags. (default: 1) @@ -201,10 +215,6 @@ jobs: # Initialize dependencies. ./init - # Set default build_type to Release - INPUT_BUILD_TYPE=${{ github.event.inputs.build_type }} - BUILD_TYPE=${INPUT_BUILD_TYPE:-"Release"} - # Build release binaries. cd build cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. @@ -226,11 +236,60 @@ jobs: cpack -G DEB --config ../CPackConfig.cmake - name: Save enterprise DEB package - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: "Enterprise DEB package" path: build/output/memgraph*.deb + - name: Run GQL Behave tests + run: | + cd tests + ./setup.sh /opt/toolchain-v4/activate + cd gql_behave + ./continuous_integration + + - name: Save quality assurance status + uses: actions/upload-artifact@v4 + with: + name: "GQL Behave Status" + path: | + tests/gql_behave/gql_behave_status.csv + tests/gql_behave/gql_behave_status.html + + - name: Run unit tests + run: | + # Activate toolchain. + source /opt/toolchain-v4/activate + + # Run unit tests. + cd build + ctest -R memgraph__unit --output-on-failure + + release_benchmark_tests: + name: "Release Benchmark Tests" + runs-on: [self-hosted, Linux, X64, Ubuntu20.04] + timeout-minutes: 90 + + steps: + - name: Set up repository + uses: actions/checkout@v4 + with: + # Number of commits to fetch. `0` indicates all history for all + # branches and tags. (default: 1) + fetch-depth: 0 + + - name: Build release binaries + run: | + # Activate toolchain. + source /opt/toolchain-v4/activate + # Initialize dependencies. + ./init + + # Build release binaries + cd build + cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. + make -j$THREADS + - name: Run micro benchmark tests run: | # Activate toolchain. @@ -257,29 +316,38 @@ jobs: --num-database-workers 9 --num-clients-workers 30 \ --no-strict - - name: Run GQL Behave tests - run: | - cd tests - ./setup.sh /opt/toolchain-v4/activate - cd gql_behave - ./continuous_integration + release_e2e_test: + if: false + name: "Release End-to-end Test" + runs-on: [self-hosted, Linux, X64, Ubuntu20.04] + timeout-minutes: 90 - - name: Save quality assurance status - uses: actions/upload-artifact@v3 + steps: + - name: Set up repository + uses: actions/checkout@v4 with: - name: "GQL Behave Status" - path: | - tests/gql_behave/gql_behave_status.csv - tests/gql_behave/gql_behave_status.html + # Number of commits to fetch. `0` indicates all history for all + # branches and tags. (default: 1) + fetch-depth: 0 - - name: Run unit tests + - name: Build release binaries run: | # Activate toolchain. source /opt/toolchain-v4/activate + # Initialize dependencies. + ./init - # Run unit tests. + # Build release binaries cd build - ctest -R memgraph__unit --output-on-failure + cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. + make -j$THREADS + + - name: Ensure Kafka and Pulsar are up + run: | + cd tests/e2e/streams/kafka + docker-compose up -d + cd ../pulsar + docker-compose up -d - name: Run e2e tests run: | @@ -289,6 +357,40 @@ jobs: cd e2e ./run.sh + - name: Ensure Kafka and Pulsar are down + if: always() + run: | + cd tests/e2e/streams/kafka + docker-compose down + cd ../pulsar + docker-compose down + + release_durability_stress_tests: + name: "Release durability and stress tests" + runs-on: [self-hosted, Linux, X64, Ubuntu20.04] + timeout-minutes: 90 + + steps: + - name: Set up repository + uses: actions/checkout@v4 + with: + # Number of commits to fetch. `0` indicates all history for all + # branches and tags. (default: 1) + fetch-depth: 0 + + - name: Build release binaries + run: | + # Activate toolchain. + source /opt/toolchain-v4/activate + + # Initialize dependencies. + ./init + + # Build release binaries. + cd build + cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. + make -j$THREADS + - name: Run stress test (plain) run: | cd tests/stress @@ -299,11 +401,6 @@ jobs: 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 diff --git a/.github/workflows/stress_test_large.yaml b/.github/workflows/stress_test_large.yaml new file mode 100644 index 000000000..54a6c55ba --- /dev/null +++ b/.github/workflows/stress_test_large.yaml @@ -0,0 +1,62 @@ +name: Stress test large + +on: + workflow_dispatch: + inputs: + build_type: + type: choice + description: "Memgraph Build type. Default value is Release." + default: 'Release' + options: + - Release + - RelWithDebInfo + + schedule: + - cron: "0 22 * * *" + +env: + THREADS: 24 + MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }} + MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }} + BUILD_TYPE: ${{ github.event.inputs.build_type || 'Release' }} + +jobs: + stress_test_large: + name: "Stress test large" + timeout-minutes: 600 + strategy: + matrix: + os: [Debian10, Ubuntu20.04] + extra: [BigMemory, Gen8] + exclude: + - os: Debian10 + extra: Gen8 + - os: Ubuntu20.04 + extra: BigMemory + runs-on: [self-hosted, Linux, X64, "${{ matrix.os }}", "${{ matrix.extra }}"] + + steps: + - name: Set up repository + uses: actions/checkout@v4 + with: + # Number of commits to fetch. `0` indicates all history for all + # branches and tags. (default: 1) + fetch-depth: 0 + + - name: Build release binaries + run: | + # Activate toolchain. + source /opt/toolchain-v4/activate + + # Initialize dependencies. + ./init + + # Build release binaries. + cd build + cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. + make -j$THREADS + + - name: Run stress test (large) + run: | + cd tests/stress + ./continuous_integration --large-dataset diff --git a/.github/workflows/upload_to_s3.yaml b/.github/workflows/upload_to_s3.yaml index 978af7d66..b6bda4ca5 100644 --- a/.github/workflows/upload_to_s3.yaml +++ b/.github/workflows/upload_to_s3.yaml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download artifacts - uses: dawidd6/action-download-artifact@v2 + uses: dawidd6/action-download-artifact@v4 with: workflow: package_all.yaml workflow_conclusion: success diff --git a/ADRs/001_tantivy.md b/ADRs/001_tantivy.md new file mode 100644 index 000000000..ac887c861 --- /dev/null +++ b/ADRs/001_tantivy.md @@ -0,0 +1,32 @@ +# Tantivy ADR + +**Author** +Marko Budiselic (github.com/gitbuda) + +**Status** +APPROVED + +**Date** +January 5, 2024 + +**Problem** + +For some of Memgraph workloads, text search is a required feature. We don't +want to build a new text search engine because that's not Memgraph's core +value. + +**Criteria** + +- easy integration with our C++ codebase +- ability to operate in-memory and on-disk +- sufficient features (regex, full-text search, fuzzy search, aggregations over + text data) +- production-ready + +**Decision** + +All known C++ libraries are not production-ready. Recent Rust libraries, in +particular [Tantivy](https://github.com/quickwit-oss/tantivy), seem to provide +much more features, it is production ready. The way how we'll integrate Tantivy +into the current Memgraph codebase is via +[cxx](https://github.com/dtolnay/cxx). **We select Tantivy.** diff --git a/ADRs/002_nuraft.md b/ADRs/002_nuraft.md new file mode 100644 index 000000000..9be239e17 --- /dev/null +++ b/ADRs/002_nuraft.md @@ -0,0 +1,34 @@ +# NuRaft ADR + +**Author** +Marko Budiselic (github.com/gitbuda) + +**Status** +PROPOSED + +**Date** +January 10, 2024 + +**Problem** + +In order to enhance Memgraph to have High Availability features as requested by +customers, we want to have reliable coordinators backed by RAFT consensus algorithm. Implementing +RAFT to be correct and performant is a very challenging task. Skillful Memgraph +engineers already tried 3 times and failed to deliver in a reasonable timeframe +all three times (approximately 4 person-weeks of engineering work each time). + +**Criteria** + +- easy integration with our C++ codebase +- heavily tested in production environments +- implementation of performance optimizations on top of the canonical Raft + implementation + +**Decision** + +There are a few, robust C++ implementations of Raft but as a part of other +projects or bigger libraries. **We select +[NuRaft](https://github.com/eBay/NuRaft)** because it focuses on delivering +Raft without bloatware, and it's used by +[Clickhouse](https://github.com/ClickHouse/ClickHouse) (an comparable peer to +Memgraph, a very well-established product). diff --git a/ADRs/README.md b/ADRs/README.md new file mode 100644 index 000000000..74b300037 --- /dev/null +++ b/ADRs/README.md @@ -0,0 +1,67 @@ +# Architecture Decision Records + +Also known as ADRs. This practice has become widespread in many +high performing engineering teams. It is a technique for communicating +between software engineers. ADRs provide a clear and documented +history of architectural choices, ensuring that everyone on the +team is on the same page. This improves communication and reduces +misunderstandings. The act of recording decisions encourages +thoughtful consideration before making choices. This can lead to +more robust and better-informed architectural decisions. + +Links must be created, pointing both to and from the Github Issues +and/or the Notion Program Management "Initiative" database. + +ADRs are complimentary to any tech specs that get written while +designing a solution. ADRs are very short and to the point, while +tech specs will include diagrams and can be quite verbose. + +## HOWTO + +Each ADR will be assigned a monotonically increasing unique numeric +identifier, which will be zero-padded to 3 digits. Each ADR will +be in a single markdown file containing no more than one page of +text, and the filename will start with that unique identifier, +followed by a camel case phrase summarizing the problem. For +example: `001_architecture_decision_records.md` or +`002_big_integration_cap_theorem.md`. + +We want to use an ADR when: +1. Significant Impact: This includes choices that affect scalability, performance, or fundamental design principles. +1. Long-Term Ramifications: When a decision is expected to have long-term ramifications or is difficult to reverse. +1. Architectural Principles: ADRs are suitable for documenting decisions related to architectural principles, frameworks, or patterns that shape the system's structure. +1. Controversial Choices: When a decision is likely to be controversial or may require justification in the future. + +The most senior engineer on a project will evaluate and decide +whether or not an ADR is needed. + +## Do + +1. Keep them brief and concise. +1. Explain the trade-offs. +1. Each ADR should be about one AD, not multiple ADs +1. Don't alter existing information in an ADR. Instead, amend the ADR by adding new information, or supersede the ADR by creating a new ADR. +1. Explain your organization's situation and business priorities. +1. Include rationale and considerations based on social and skills makeups of your teams. +1. Include pros and cons that are relevant, and describe them in terms that align with your needs and goals. +1. Explain what follows from making the decision. This can include the effects, outcomes, outputs, follow ups, and more. + +## Don't + +1. Try to guess what the executive leader wants, and then attempt to please them. Be objective. +1. Try to solve everything all at once. A pretty good solution now is MUCH BETTER than a perfect solution later. Carpe diem! +1. Hide any doubts or unanswered questions. +1. Make it a sales pitch. Everything has upsides and downsides - be authentic and honest about them. +1. Perform merely a superficial investigation. If an ADR doesn't call for some deep thinking, then it probably shouldn't exist. +1. Ignore the long-term costs such as performance, tech debt or hardware and maintenance. +1. Get tunnel vision where creative or surprising approaches are not explored. + +# Template - use the format below for each new ADR + +1. **Author** - who has written the ADR +1. **Status** - one of: PROPOSED, ACCEPTED, REJECTED, SUPERSEDED-BY or DEPRECATED +1. **Date** - when the status was most recently updated +1. **Problem** - a concise paragraph explaining the context +1. **Criteria** - a list of the two or three metrics by which the solution was evaluated, and their relative weights (importance) +1. **Decision** - what was chosen as the way forward, and what the consequences are of the decision + diff --git a/CMakeLists.txt b/CMakeLists.txt index a5ad2612a..266a3bedb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -271,6 +271,17 @@ endif() set(libs_dir ${CMAKE_SOURCE_DIR}/libs) add_subdirectory(libs EXCLUDE_FROM_ALL) +option(MG_EXPERIMENTAL_HIGH_AVAILABILITY "Feature flag for experimental high availability" OFF) + +if (NOT MG_ENTERPRISE AND MG_EXPERIMENTAL_HIGH_AVAILABILITY) + set(MG_EXPERIMENTAL_HIGH_AVAILABILITY OFF) + message(FATAL_ERROR "MG_EXPERIMENTAL_HIGH_AVAILABILITY must be used with enterpise version of the code.") +endif () + +if (MG_EXPERIMENTAL_HIGH_AVAILABILITY) + add_compile_definitions(MG_EXPERIMENTAL_HIGH_AVAILABILITY) +endif () + # Optional subproject configuration ------------------------------------------- option(TEST_COVERAGE "Generate coverage reports from running memgraph" OFF) option(TOOLS "Build tools binaries" ON) @@ -279,6 +290,18 @@ option(ASAN "Build with Address Sanitizer. To get a reasonable performance optio option(TSAN "Build with Thread Sanitizer. To get a reasonable performance option should be used only in Release or RelWithDebInfo build " OFF) option(UBSAN "Build with Undefined Behaviour Sanitizer" OFF) +# Build feature flags +option(MG_EXPERIMENTAL_REPLICATION_MULTITENANCY "Feature flag for experimental replicaition of multitenacy" OFF) + +if (NOT MG_ENTERPRISE AND MG_EXPERIMENTAL_REPLICATION_MULTITENANCY) + set(MG_EXPERIMENTAL_REPLICATION_MULTITENANCY OFF) + message(FATAL_ERROR "MG_EXPERIMENTAL_REPLICATION_MULTITENANCY with community edition build isn't possible") +endif () + +if (MG_EXPERIMENTAL_REPLICATION_MULTITENANCY) + add_compile_definitions(MG_EXPERIMENTAL_REPLICATION_MULTITENANCY) +endif () + if (TEST_COVERAGE) string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type) if (NOT lower_build_type STREQUAL "debug") diff --git a/include/mg_exceptions.hpp b/include/mg_exceptions.hpp index 53a338b86..d526d4e43 100644 --- a/include/mg_exceptions.hpp +++ b/include/mg_exceptions.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -25,7 +25,7 @@ namespace mg_exception { template <typename FirstArg, typename... Args> std::string StringSerialize(FirstArg &&firstArg, Args &&...args) { std::stringstream stream; - stream << firstArg; + stream << std::forward<FirstArg>(firstArg); ((stream << " " << args), ...); return stream.str(); } diff --git a/libs/setup.sh b/libs/setup.sh index 028797679..2f968f71c 100755 --- a/libs/setup.sh +++ b/libs/setup.sh @@ -259,7 +259,7 @@ repo_clone_try_double "${primary_urls[absl]}" "${secondary_urls[absl]}" "absl" " # jemalloc ea6b3e973b477b8061e0076bb257dbd7f3faa756 JEMALLOC_COMMIT_VERSION="5.2.1" -repo_clone_try_double "${secondary_urls[jemalloc]}" "${secondary_urls[jemalloc]}" "jemalloc" "$JEMALLOC_COMMIT_VERSION" +repo_clone_try_double "${primary_urls[jemalloc]}" "${secondary_urls[jemalloc]}" "jemalloc" "$JEMALLOC_COMMIT_VERSION" # this is hack for cmake in libs to set path, and for FindJemalloc to use Jemalloc_INCLUDE_DIR pushd jemalloc diff --git a/licenses/BSL.txt b/licenses/BSL.txt index 9cca9dccf..c41a08af1 100644 --- a/licenses/BSL.txt +++ b/licenses/BSL.txt @@ -36,7 +36,7 @@ ADDITIONAL USE GRANT: You may use the Licensed Work in accordance with the 3. using the Licensed Work to create a work or solution which competes (or might reasonably be expected to compete) with the Licensed Work. -CHANGE DATE: 2027-08-12 +CHANGE DATE: 2028-21-01 CHANGE LICENSE: Apache License, Version 2.0 For information about alternative licensing arrangements, please visit: https://memgraph.com/legal. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ba8784b19..4685da727 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,6 +22,8 @@ add_subdirectory(dbms) add_subdirectory(flags) add_subdirectory(distributed) add_subdirectory(replication) +add_subdirectory(coordination) +add_subdirectory(replication_coordination_glue) string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type) @@ -53,7 +55,10 @@ set_target_properties(memgraph PROPERTIES OUTPUT_NAME "memgraph-${MEMGRAPH_VERSION}_${CMAKE_BUILD_TYPE}" # Output the executable in main binary dir. - RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} + + POSITION_INDEPENDENT_CODE ON + ) # Create symlink to the built executable. add_custom_command(TARGET memgraph POST_BUILD diff --git a/src/auth/auth.cpp b/src/auth/auth.cpp index cfe9dbdbe..88f0c4410 100644 --- a/src/auth/auth.cpp +++ b/src/auth/auth.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Licensed as a Memgraph Enterprise file under the Memgraph Enterprise // License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use @@ -8,17 +8,15 @@ #include "auth/auth.hpp" -#include <cstring> #include <iostream> -#include <limits> #include <utility> #include <fmt/format.h> +#include "auth/crypto.hpp" #include "auth/exceptions.hpp" #include "license/license.hpp" #include "utils/flag_validation.hpp" -#include "utils/logging.hpp" #include "utils/message.hpp" #include "utils/settings.hpp" #include "utils/string.hpp" @@ -46,6 +44,9 @@ namespace memgraph::auth { const std::string kUserPrefix = "user:"; const std::string kRolePrefix = "role:"; const std::string kLinkPrefix = "link:"; +const std::string kVersion = "version"; + +static constexpr auto kVersionV1 = "V1"; /** * All data stored in the `Auth` storage is stored in an underlying @@ -64,7 +65,59 @@ const std::string kLinkPrefix = "link:"; * key="link:<username>", value="<rolename>" */ -Auth::Auth(const std::string &storage_directory) : storage_(storage_directory), module_(FLAGS_auth_module_executable) {} +namespace { +void MigrateVersions(kvstore::KVStore &store) { + static constexpr auto kPasswordHashV0V1 = "password_hash"; + auto version_str = store.Get(kVersion); + + if (!version_str) { + using namespace std::string_literals; + + // pre versioning, add version to the store + auto puts = std::map<std::string, std::string>{{kVersion, kVersionV1}}; + + // also add hash kind into durability + + auto it = store.begin(kUserPrefix); + auto const e = store.end(kUserPrefix); + + if (it != e) { + const auto hash_algo = CurrentHashAlgorithm(); + spdlog::info("Updating auth durability, assuming previously stored as {}", AsString(hash_algo)); + + for (; it != e; ++it) { + auto const &[key, value] = *it; + try { + auto user_data = nlohmann::json::parse(value); + + auto password_hash = user_data[kPasswordHashV0V1]; + if (!password_hash.is_string()) { + throw AuthException("Couldn't load user data!"); + } + // upgrade the password_hash to include the hash algortihm + if (password_hash.empty()) { + user_data[kPasswordHashV0V1] = nullptr; + } else { + user_data[kPasswordHashV0V1] = HashedPassword{hash_algo, password_hash}; + } + puts.emplace(key, user_data.dump()); + } catch (const nlohmann::json::parse_error &e) { + throw AuthException("Couldn't load user data!"); + } + } + } + + // Perform migration to V1 + store.PutMultiple(puts); + version_str = kVersionV1; + } +} +}; // namespace + +Auth::Auth(std::string storage_directory, Config config) + : storage_(std::move(storage_directory)), module_(FLAGS_auth_module_executable), config_{std::move(config)} { + MigrateVersions(storage_); +} std::optional<User> Auth::Authenticate(const std::string &username, const std::string &password) { if (module_.IsUsed()) { @@ -113,7 +166,7 @@ std::optional<User> Auth::Authenticate(const std::string &username, const std::s return std::nullopt; } } else { - user->UpdatePassword(password); + UpdatePassword(*user, password); } if (FLAGS_auth_module_manage_roles) { if (!rolename.empty()) { @@ -155,6 +208,10 @@ std::optional<User> Auth::Authenticate(const std::string &username, const std::s username, "https://memgr.ph/auth")); return std::nullopt; } + if (user->UpgradeHash(password)) { + SaveUser(*user); + } + return user; } } @@ -197,13 +254,46 @@ void Auth::SaveUser(const User &user) { } } +void Auth::UpdatePassword(auth::User &user, const std::optional<std::string> &password) { + // Check if null + if (!password) { + if (!config_.password_permit_null) { + throw AuthException("Null passwords aren't permitted!"); + } + } else { + // Check if compliant with our filter + if (config_.custom_password_regex) { + if (const auto license_check_result = license::global_license_checker.IsEnterpriseValid(utils::global_settings); + license_check_result.HasError()) { + throw AuthException( + "Custom password regex is a Memgraph Enterprise feature. Please set the config " + "(\"--auth-password-strength-regex\") to its default value (\"{}\") or remove the flag.\n{}", + glue::kDefaultPasswordRegex, + license::LicenseCheckErrorToString(license_check_result.GetError(), "password regex")); + } + } + if (!std::regex_match(*password, config_.password_regex)) { + throw AuthException( + "The user password doesn't conform to the required strength! Regex: " + "\"{}\"", + config_.password_regex_str); + } + } + + // All checks passed; update + user.UpdatePassword(password); +} + std::optional<User> Auth::AddUser(const std::string &username, const std::optional<std::string> &password) { + if (!NameRegexMatch(username)) { + throw AuthException("Invalid user name."); + } auto existing_user = GetUser(username); if (existing_user) return std::nullopt; auto existing_role = GetRole(username); if (existing_role) return std::nullopt; auto new_user = User(username); - new_user.UpdatePassword(password); + UpdatePassword(new_user, password); SaveUser(new_user); return new_user; } @@ -255,10 +345,11 @@ void Auth::SaveRole(const Role &role) { } std::optional<Role> Auth::AddRole(const std::string &rolename) { - auto existing_role = GetRole(rolename); - if (existing_role) return std::nullopt; - auto existing_user = GetUser(rolename); - if (existing_user) return std::nullopt; + if (!NameRegexMatch(rolename)) { + throw AuthException("Invalid role name."); + } + if (auto existing_role = GetRole(rolename)) return std::nullopt; + if (auto existing_user = GetUser(rolename)) return std::nullopt; auto new_role = Role(rolename); SaveRole(new_role); return new_role; @@ -285,8 +376,7 @@ std::vector<auth::Role> Auth::AllRoles() const { for (auto it = storage_.begin(kRolePrefix); it != storage_.end(kRolePrefix); ++it) { auto rolename = it->first.substr(kRolePrefix.size()); if (rolename != utils::ToLowerCase(rolename)) continue; - auto role = GetRole(rolename); - if (role) { + if (auto role = GetRole(rolename)) { ret.push_back(*role); } else { throw AuthException("Couldn't load role '{}'!", rolename); @@ -296,15 +386,14 @@ std::vector<auth::Role> Auth::AllRoles() const { } std::vector<auth::User> Auth::AllUsersForRole(const std::string &rolename_orig) const { - auto rolename = utils::ToLowerCase(rolename_orig); + const auto rolename = utils::ToLowerCase(rolename_orig); std::vector<auth::User> ret; for (auto it = storage_.begin(kLinkPrefix); it != storage_.end(kLinkPrefix); ++it) { auto username = it->first.substr(kLinkPrefix.size()); if (username != utils::ToLowerCase(username)) continue; if (it->second != utils::ToLowerCase(it->second)) continue; if (it->second == rolename) { - auto user = GetUser(username); - if (user) { + if (auto user = GetUser(username)) { ret.push_back(std::move(*user)); } else { throw AuthException("Couldn't load user '{}'!", username); @@ -316,8 +405,7 @@ std::vector<auth::User> Auth::AllUsersForRole(const std::string &rolename_orig) #ifdef MG_ENTERPRISE bool Auth::GrantDatabaseToUser(const std::string &db, const std::string &name) { - auto user = GetUser(name); - if (user) { + if (auto user = GetUser(name)) { if (db == kAllDatabases) { user->db_access().GrantAll(); } else { @@ -330,8 +418,7 @@ bool Auth::GrantDatabaseToUser(const std::string &db, const std::string &name) { } bool Auth::RevokeDatabaseFromUser(const std::string &db, const std::string &name) { - auto user = GetUser(name); - if (user) { + if (auto user = GetUser(name)) { if (db == kAllDatabases) { user->db_access().DenyAll(); } else { @@ -346,17 +433,15 @@ bool Auth::RevokeDatabaseFromUser(const std::string &db, const std::string &name void Auth::DeleteDatabase(const std::string &db) { for (auto it = storage_.begin(kUserPrefix); it != storage_.end(kUserPrefix); ++it) { auto username = it->first.substr(kUserPrefix.size()); - auto user = GetUser(username); - if (user) { + if (auto user = GetUser(username)) { user->db_access().Delete(db); SaveUser(*user); } } } -bool Auth::SetMainDatabase(const std::string &db, const std::string &name) { - auto user = GetUser(name); - if (user) { +bool Auth::SetMainDatabase(std::string_view db, const std::string &name) { + if (auto user = GetUser(name)) { if (!user->db_access().SetDefault(db)) { throw AuthException("Couldn't set default database '{}' for user '{}'!", db, name); } @@ -367,4 +452,19 @@ bool Auth::SetMainDatabase(const std::string &db, const std::string &name) { } #endif +bool Auth::NameRegexMatch(const std::string &user_or_role) const { + if (config_.custom_name_regex) { + if (const auto license_check_result = + memgraph::license::global_license_checker.IsEnterpriseValid(memgraph::utils::global_settings); + license_check_result.HasError()) { + throw memgraph::auth::AuthException( + "Custom user/role regex is a Memgraph Enterprise feature. Please set the config " + "(\"--auth-user-or-role-name-regex\") to its default value (\"{}\") or remove the flag.\n{}", + glue::kDefaultUserRoleRegex, + memgraph::license::LicenseCheckErrorToString(license_check_result.GetError(), "user/role regex")); + } + } + return std::regex_match(user_or_role, config_.name_regex); +} + } // namespace memgraph::auth diff --git a/src/auth/auth.hpp b/src/auth/auth.hpp index 8d2a9d91c..aa90c349a 100644 --- a/src/auth/auth.hpp +++ b/src/auth/auth.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Licensed as a Memgraph Enterprise file under the Memgraph Enterprise // License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use @@ -10,11 +10,13 @@ #include <mutex> #include <optional> +#include <regex> #include <vector> #include "auth/exceptions.hpp" #include "auth/models.hpp" #include "auth/module.hpp" +#include "glue/auth_global.hpp" #include "kvstore/kvstore.hpp" #include "utils/settings.hpp" @@ -31,7 +33,40 @@ static const constexpr char *const kAllDatabases = "*"; */ class Auth final { public: - explicit Auth(const std::string &storage_directory); + struct Config { + Config() {} + Config(std::string name_regex, std::string password_regex, bool password_permit_null) + : name_regex_str{std::move(name_regex)}, + password_regex_str{std::move(password_regex)}, + password_permit_null{password_permit_null}, + custom_name_regex{name_regex_str != glue::kDefaultUserRoleRegex}, + name_regex{name_regex_str}, + custom_password_regex{password_regex_str != glue::kDefaultPasswordRegex}, + password_regex{password_regex_str} {} + + std::string name_regex_str{glue::kDefaultUserRoleRegex}; + std::string password_regex_str{glue::kDefaultPasswordRegex}; + bool password_permit_null{true}; + + private: + friend class Auth; + bool custom_name_regex{false}; + std::regex name_regex{name_regex_str}; + bool custom_password_regex{false}; + std::regex password_regex{password_regex_str}; + }; + + explicit Auth(std::string storage_directory, Config config); + + /** + * @brief Set the Config object + * + * @param config + */ + void SetConfig(Config config) { + // NOTE: The Auth class itself is not thread-safe, higher-level code needs to synchronize it when using it. + config_ = std::move(config); + } /** * Authenticates a user using his username and password. @@ -85,6 +120,14 @@ class Auth final { */ bool RemoveUser(const std::string &username); + /** + * @brief + * + * @param user + * @param password + */ + void UpdatePassword(auth::User &user, const std::optional<std::string> &password); + /** * Gets all users from the storage. * @@ -195,14 +238,24 @@ class Auth final { * @return true on success * @throw AuthException if unable to find or update the user */ - bool SetMainDatabase(const std::string &db, const std::string &name); + bool SetMainDatabase(std::string_view db, const std::string &name); #endif private: + /** + * @brief + * + * @param user_or_role + * @return true + * @return false + */ + bool NameRegexMatch(const std::string &user_or_role) const; + // Even though the `kvstore::KVStore` class is guaranteed to be thread-safe, // Auth is not thread-safe because modifying users and roles might require // more than one operation on the storage. kvstore::KVStore storage_; auth::Module module_; + Config config_; }; } // namespace memgraph::auth diff --git a/src/auth/crypto.cpp b/src/auth/crypto.cpp index c433eaf62..a8351635a 100644 --- a/src/auth/crypto.cpp +++ b/src/auth/crypto.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Licensed as a Memgraph Enterprise file under the Memgraph Enterprise // License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use @@ -22,10 +22,14 @@ namespace { using namespace std::literals; -inline constexpr std::array password_encryption_mappings{ - std::pair{"bcrypt"sv, memgraph::auth::PasswordEncryptionAlgorithm::BCRYPT}, - std::pair{"sha256"sv, memgraph::auth::PasswordEncryptionAlgorithm::SHA256}, - std::pair{"sha256-multiple"sv, memgraph::auth::PasswordEncryptionAlgorithm::SHA256_MULTIPLE}}; + +constexpr auto kHashAlgo = "hash_algo"; +constexpr auto kPasswordHash = "password_hash"; + +inline constexpr std::array password_hash_mappings{ + std::pair{"bcrypt"sv, memgraph::auth::PasswordHashAlgorithm::BCRYPT}, + std::pair{"sha256"sv, memgraph::auth::PasswordHashAlgorithm::SHA256}, + std::pair{"sha256-multiple"sv, memgraph::auth::PasswordHashAlgorithm::SHA256_MULTIPLE}}; inline constexpr uint64_t ONE_SHA_ITERATION = 1; inline constexpr uint64_t MULTIPLE_SHA_ITERATIONS = 1024; @@ -35,7 +39,7 @@ inline constexpr uint64_t MULTIPLE_SHA_ITERATIONS = 1024; DEFINE_VALIDATED_string(password_encryption_algorithm, "bcrypt", "The password encryption algorithm used for authentication.", { if (const auto result = - memgraph::utils::IsValidEnumValueString(value, password_encryption_mappings); + memgraph::utils::IsValidEnumValueString(value, password_hash_mappings); result.HasError()) { const auto error = result.GetError(); switch (error) { @@ -45,7 +49,7 @@ DEFINE_VALIDATED_string(password_encryption_algorithm, "bcrypt", } case memgraph::utils::ValidationError::InvalidValue: { std::cout << "Invalid value for password encryption algorithm. Allowed values: " - << memgraph::utils::GetAllowedEnumValuesString(password_encryption_mappings) + << memgraph::utils::GetAllowedEnumValuesString(password_hash_mappings) << std::endl; break; } @@ -58,7 +62,7 @@ DEFINE_VALIDATED_string(password_encryption_algorithm, "bcrypt", namespace memgraph::auth { namespace BCrypt { -std::string EncryptPassword(const std::string &password) { +std::string HashPassword(const std::string &password) { char salt[BCRYPT_HASHSIZE]; char hash[BCRYPT_HASHSIZE]; @@ -86,16 +90,30 @@ bool VerifyPassword(const std::string &password, const std::string &hash) { } // namespace BCrypt namespace SHA { + +namespace { + +constexpr auto SHA_LENGTH = 64U; +constexpr auto SALT_SIZE = 16U; +constexpr auto SALT_SIZE_DURABLE = SALT_SIZE * 2; + #if OPENSSL_VERSION_MAJOR >= 3 -std::string EncryptPasswordOpenSSL3(const std::string &password, const uint64_t number_of_iterations) { +std::string HashPasswordOpenSSL3(std::string_view password, const uint64_t number_of_iterations, + std::string_view salt) { unsigned char hash[SHA256_DIGEST_LENGTH]; EVP_MD_CTX *ctx = EVP_MD_CTX_new(); EVP_MD *md = EVP_MD_fetch(nullptr, "SHA2-256", nullptr); EVP_DigestInit_ex(ctx, md, nullptr); + + if (!salt.empty()) { + DMG_ASSERT(salt.size() == SALT_SIZE); + EVP_DigestUpdate(ctx, salt.data(), salt.size()); + } + for (auto i = 0; i < number_of_iterations; i++) { - EVP_DigestUpdate(ctx, password.c_str(), password.size()); + EVP_DigestUpdate(ctx, password.data(), password.size()); } EVP_DigestFinal_ex(ctx, hash, nullptr); @@ -103,6 +121,11 @@ std::string EncryptPasswordOpenSSL3(const std::string &password, const uint64_t EVP_MD_CTX_free(ctx); std::stringstream result_stream; + + for (unsigned char salt_char : salt) { + result_stream << std::hex << std::setw(2) << std::setfill('0') << (((unsigned int)salt_char) & 0xFFU); + } + for (auto hash_char : hash) { result_stream << std::hex << std::setw(2) << std::setfill('0') << (int)hash_char; } @@ -110,17 +133,27 @@ std::string EncryptPasswordOpenSSL3(const std::string &password, const uint64_t return result_stream.str(); } #else -std::string EncryptPasswordOpenSSL1_1(const std::string &password, const uint64_t number_of_iterations) { +std::string HashPasswordOpenSSL1_1(std::string_view password, const uint64_t number_of_iterations, + std::string_view salt) { unsigned char hash[SHA256_DIGEST_LENGTH]; SHA256_CTX sha256; SHA256_Init(&sha256); + + if (!salt.empty()) { + DMG_ASSERT(salt.size() == SALT_SIZE); + SHA256_Update(&sha256, salt.data(), salt.size()); + } + for (auto i = 0; i < number_of_iterations; i++) { - SHA256_Update(&sha256, password.c_str(), password.size()); + SHA256_Update(&sha256, password.data(), password.size()); } SHA256_Final(hash, &sha256); std::stringstream ss; + for (unsigned char salt_char : salt) { + ss << std::hex << std::setw(2) << std::setfill('0') << (((unsigned int)salt_char) & 0xFFU); + } for (auto hash_char : hash) { ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash_char; } @@ -129,55 +162,144 @@ std::string EncryptPasswordOpenSSL1_1(const std::string &password, const uint64_ } #endif -std::string EncryptPassword(const std::string &password, const uint64_t number_of_iterations) { +std::string HashPassword(std::string_view password, const uint64_t number_of_iterations, std::string_view salt) { #if OPENSSL_VERSION_MAJOR >= 3 - return EncryptPasswordOpenSSL3(password, number_of_iterations); + return HashPasswordOpenSSL3(password, number_of_iterations, salt); #else - return EncryptPasswordOpenSSL1_1(password, number_of_iterations); + return HashPasswordOpenSSL1_1(password, number_of_iterations, salt); #endif } -bool VerifyPassword(const std::string &password, const std::string &hash, const uint64_t number_of_iterations) { - auto password_hash = EncryptPassword(password, number_of_iterations); +auto ExtractSalt(std::string_view salt_durable) -> std::array<char, SALT_SIZE> { + static_assert(SALT_SIZE_DURABLE % 2 == 0); + static_assert(SALT_SIZE_DURABLE / 2 == SALT_SIZE); + + MG_ASSERT(salt_durable.size() == SALT_SIZE_DURABLE); + auto const *b = salt_durable.cbegin(); + auto const *const e = salt_durable.cend(); + + auto salt = std::array<char, SALT_SIZE>{}; + auto *inserter = salt.begin(); + + auto const toval = [](char a) -> uint8_t { + if ('0' <= a && a <= '9') { + return a - '0'; + } + if ('a' <= a && a <= 'f') { + return 10 + (a - 'a'); + } + MG_ASSERT(false, "Currupt hash, can't extract salt"); + __builtin_unreachable(); + }; + + for (; b != e; b += 2, ++inserter) { + *inserter = static_cast<char>(static_cast<uint8_t>(toval(b[0]) << 4U) | toval(b[1])); + } + return salt; +} + +bool IsSalted(std::string_view hash) { return hash.size() == SHA_LENGTH + SALT_SIZE_DURABLE; } + +bool VerifyPassword(std::string_view password, std::string_view hash, const uint64_t number_of_iterations) { + auto password_hash = std::invoke([&] { + if (hash.size() == SHA_LENGTH) [[unlikely]] { + // Just SHA256 + return HashPassword(password, number_of_iterations, {}); + } else { + // SHA256 + SALT + MG_ASSERT(IsSalted(hash)); + auto const salt_durable = std::string_view{hash.data(), SALT_SIZE_DURABLE}; + std::array<char, SALT_SIZE> salt = ExtractSalt(salt_durable); + return HashPassword(password, number_of_iterations, {salt.data(), salt.size()}); + } + }); return password_hash == hash; } + +} // namespace + } // namespace SHA -bool VerifyPassword(const std::string &password, const std::string &hash) { - const auto password_encryption_algorithm = utils::StringToEnum<PasswordEncryptionAlgorithm>( - FLAGS_password_encryption_algorithm, password_encryption_mappings); +HashedPassword HashPassword(const std::string &password, std::optional<PasswordHashAlgorithm> override_algo) { + auto const hash_algo = override_algo.value_or(CurrentHashAlgorithm()); + auto password_hash = std::invoke([&] { + switch (hash_algo) { + case PasswordHashAlgorithm::BCRYPT: { + return BCrypt::HashPassword(password); + } + case PasswordHashAlgorithm::SHA256: + case PasswordHashAlgorithm::SHA256_MULTIPLE: { + auto gen = std::mt19937(std::random_device{}()); + auto salt = std::array<char, SHA::SALT_SIZE>{}; + auto dis = std::uniform_int_distribution<unsigned char>(0, 255); + std::generate(salt.begin(), salt.end(), [&]() { return dis(gen); }); + auto iterations = (hash_algo == PasswordHashAlgorithm::SHA256) ? ONE_SHA_ITERATION : MULTIPLE_SHA_ITERATIONS; + return SHA::HashPassword(password, iterations, {salt.data(), salt.size()}); + } + } + }); + return HashedPassword{hash_algo, std::move(password_hash)}; +}; - if (!password_encryption_algorithm.has_value()) { - throw AuthException("Invalid password encryption flag '{}'!", FLAGS_password_encryption_algorithm); +namespace { + +auto InternalParseHashAlgorithm(std::string_view algo) -> PasswordHashAlgorithm { + auto maybe_parsed = utils::StringToEnum<PasswordHashAlgorithm>(algo, password_hash_mappings); + if (!maybe_parsed) { + throw AuthException("Invalid password encryption '{}'!", algo); } - - switch (password_encryption_algorithm.value()) { - case PasswordEncryptionAlgorithm::BCRYPT: - return BCrypt::VerifyPassword(password, hash); - case PasswordEncryptionAlgorithm::SHA256: - return SHA::VerifyPassword(password, hash, ONE_SHA_ITERATION); - case PasswordEncryptionAlgorithm::SHA256_MULTIPLE: - return SHA::VerifyPassword(password, hash, MULTIPLE_SHA_ITERATIONS); - } - - throw AuthException("Invalid password encryption flag '{}'!", FLAGS_password_encryption_algorithm); + return *maybe_parsed; } -std::string EncryptPassword(const std::string &password) { - const auto password_encryption_algorithm = utils::StringToEnum<PasswordEncryptionAlgorithm>( - FLAGS_password_encryption_algorithm, password_encryption_mappings); +PasswordHashAlgorithm &InternalCurrentHashAlgorithm() { + static auto current = PasswordHashAlgorithm::BCRYPT; + static std::once_flag flag; + std::call_once(flag, [] { current = InternalParseHashAlgorithm(FLAGS_password_encryption_algorithm); }); + return current; +} +} // namespace - if (!password_encryption_algorithm.has_value()) { - throw AuthException("Invalid password encryption flag '{}'!", FLAGS_password_encryption_algorithm); +auto CurrentHashAlgorithm() -> PasswordHashAlgorithm { return InternalCurrentHashAlgorithm(); } + +void SetHashAlgorithm(std::string_view algo) { + auto ¤t = InternalCurrentHashAlgorithm(); + current = InternalParseHashAlgorithm(algo); +} + +auto AsString(PasswordHashAlgorithm hash_algo) -> std::string_view { + return *utils::EnumToString<PasswordHashAlgorithm>(hash_algo, password_hash_mappings); +} + +bool HashedPassword::VerifyPassword(const std::string &password) { + switch (hash_algo) { + case PasswordHashAlgorithm::BCRYPT: + return BCrypt::VerifyPassword(password, password_hash); + case PasswordHashAlgorithm::SHA256: + return SHA::VerifyPassword(password, password_hash, ONE_SHA_ITERATION); + case PasswordHashAlgorithm::SHA256_MULTIPLE: + return SHA::VerifyPassword(password, password_hash, MULTIPLE_SHA_ITERATIONS); } +} - switch (password_encryption_algorithm.value()) { - case PasswordEncryptionAlgorithm::BCRYPT: - return BCrypt::EncryptPassword(password); - case PasswordEncryptionAlgorithm::SHA256: - return SHA::EncryptPassword(password, ONE_SHA_ITERATION); - case PasswordEncryptionAlgorithm::SHA256_MULTIPLE: - return SHA::EncryptPassword(password, MULTIPLE_SHA_ITERATIONS); +void to_json(nlohmann::json &j, const HashedPassword &p) { + j = nlohmann::json{{kHashAlgo, p.hash_algo}, {kPasswordHash, p.password_hash}}; +} + +void from_json(const nlohmann::json &j, HashedPassword &p) { + // NOLINTNEXTLINE(cppcoreguidelines-init-variables) + PasswordHashAlgorithm hash_algo; + j.at(kHashAlgo).get_to(hash_algo); + auto password_hash = j.value(kPasswordHash, std::string()); + p = HashedPassword{hash_algo, std::move(password_hash)}; +} + +bool HashedPassword::IsSalted() const { + switch (hash_algo) { + case PasswordHashAlgorithm::BCRYPT: + return true; + case PasswordHashAlgorithm::SHA256: + case PasswordHashAlgorithm::SHA256_MULTIPLE: + return SHA::IsSalted(password_hash); } } diff --git a/src/auth/crypto.hpp b/src/auth/crypto.hpp index dbceb128b..c5dfc1c05 100644 --- a/src/auth/crypto.hpp +++ b/src/auth/crypto.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Licensed as a Memgraph Enterprise file under the Memgraph Enterprise // License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use @@ -8,14 +8,45 @@ #pragma once +#include <json/json.hpp> +#include <optional> #include <string> namespace memgraph::auth { -enum class PasswordEncryptionAlgorithm : uint8_t { BCRYPT, SHA256, SHA256_MULTIPLE }; +/// Need to be stable, auth durability depends on this +enum class PasswordHashAlgorithm : uint8_t { BCRYPT = 0, SHA256 = 1, SHA256_MULTIPLE = 2 }; -/// @throw AuthException if unable to encrypt the password. -std::string EncryptPassword(const std::string &password); +void SetHashAlgorithm(std::string_view algo); -/// @throw AuthException if unable to verify the password. -bool VerifyPassword(const std::string &password, const std::string &hash); +auto CurrentHashAlgorithm() -> PasswordHashAlgorithm; + +auto AsString(PasswordHashAlgorithm hash_algo) -> std::string_view; + +struct HashedPassword { + HashedPassword() = default; + HashedPassword(PasswordHashAlgorithm hash_algo, std::string password_hash) + : hash_algo{hash_algo}, password_hash{std::move(password_hash)} {} + HashedPassword(HashedPassword const &) = default; + HashedPassword(HashedPassword &&) = default; + HashedPassword &operator=(HashedPassword const &) = default; + HashedPassword &operator=(HashedPassword &&) = default; + + friend bool operator==(HashedPassword const &, HashedPassword const &) = default; + + bool VerifyPassword(const std::string &password); + + bool IsSalted() const; + + auto HashAlgo() const -> PasswordHashAlgorithm { return hash_algo; } + + friend void to_json(nlohmann::json &j, const HashedPassword &p); + friend void from_json(const nlohmann::json &j, HashedPassword &p); + + private: + PasswordHashAlgorithm hash_algo{PasswordHashAlgorithm::BCRYPT}; + std::string password_hash{}; +}; + +/// @throw AuthException if unable to hash the password. +HashedPassword HashPassword(const std::string &password, std::optional<PasswordHashAlgorithm> override_algo = {}); } // namespace memgraph::auth diff --git a/src/auth/models.cpp b/src/auth/models.cpp index 5415dc08d..a59a73c7b 100644 --- a/src/auth/models.cpp +++ b/src/auth/models.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Licensed as a Memgraph Enterprise file under the Memgraph Enterprise // License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use @@ -9,7 +9,6 @@ #include "auth/models.hpp" #include <cstdint> -#include <regex> #include <utility> #include <gflags/gflags.h> @@ -21,22 +20,26 @@ #include "query/constants.hpp" #include "spdlog/spdlog.h" #include "utils/cast.hpp" -#include "utils/logging.hpp" -#include "utils/settings.hpp" #include "utils/string.hpp" -// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) -DEFINE_bool(auth_password_permit_null, true, "Set to false to disable null passwords."); - -inline constexpr std::string_view default_password_regex = ".+"; -// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) -DEFINE_string(auth_password_strength_regex, default_password_regex.data(), - "The regular expression that should be used to match the entire " - "entered password to ensure its strength."); - namespace memgraph::auth { namespace { +constexpr auto kRoleName = "rolename"; +constexpr auto kPermissions = "permissions"; +constexpr auto kGrants = "grants"; +constexpr auto kDenies = "denies"; +constexpr auto kUsername = "username"; +constexpr auto kPasswordHash = "password_hash"; + +#ifdef MG_ENTERPRISE +constexpr auto kGlobalPermission = "global_permission"; +constexpr auto kFineGrainedAccessHandler = "fine_grained_access_handler"; +constexpr auto kAllowAll = "allow_all"; +constexpr auto kDefault = "default"; +constexpr auto kDatabases = "databases"; +#endif + // Constant list of all available permissions. const std::vector<Permission> kPermissionsAll = {Permission::MATCH, Permission::CREATE, @@ -62,7 +65,8 @@ const std::vector<Permission> kPermissionsAll = {Permission::MATCH, Permission::TRANSACTION_MANAGEMENT, Permission::STORAGE_MODE, Permission::MULTI_DATABASE_EDIT, - Permission::MULTI_DATABASE_USE}; + Permission::MULTI_DATABASE_USE, + Permission::COORDINATOR}; } // namespace @@ -118,6 +122,8 @@ std::string PermissionToString(Permission permission) { return "MULTI_DATABASE_EDIT"; case Permission::MULTI_DATABASE_USE: return "MULTI_DATABASE_USE"; + case Permission::COORDINATOR: + return "COORDINATOR"; } } @@ -242,8 +248,9 @@ std::vector<Permission> Permissions::GetDenies() const { nlohmann::json Permissions::Serialize() const { nlohmann::json data = nlohmann::json::object(); - data["grants"] = grants_; - data["denies"] = denies_; + + data[kGrants] = grants_; + data[kDenies] = denies_; return data; } @@ -251,10 +258,10 @@ Permissions Permissions::Deserialize(const nlohmann::json &data) { if (!data.is_object()) { throw AuthException("Couldn't load permissions data!"); } - if (!data["grants"].is_number_unsigned() || !data["denies"].is_number_unsigned()) { + if (!data[kGrants].is_number_unsigned() || !data[kDenies].is_number_unsigned()) { throw AuthException("Couldn't load permissions data!"); } - return Permissions{data["grants"], data["denies"]}; + return Permissions{data[kGrants], data[kDenies]}; } uint64_t Permissions::grants() const { return grants_; } @@ -316,8 +323,8 @@ nlohmann::json FineGrainedAccessPermissions::Serialize() const { return {}; } nlohmann::json data = nlohmann::json::object(); - data["permissions"] = permissions_; - data["global_permission"] = global_permission_.has_value() ? global_permission_.value() : -1; + data[kPermissions] = permissions_; + data[kGlobalPermission] = global_permission_.has_value() ? global_permission_.value() : -1; return data; } @@ -330,13 +337,13 @@ FineGrainedAccessPermissions FineGrainedAccessPermissions::Deserialize(const nlo } std::optional<uint64_t> global_permission; - if (data["global_permission"].empty() || data["global_permission"] == -1) { + if (data[kGlobalPermission].empty() || data[kGlobalPermission] == -1) { global_permission = std::nullopt; } else { - global_permission = data["global_permission"]; + global_permission = data[kGlobalPermission]; } - return FineGrainedAccessPermissions(data["permissions"], global_permission); + return FineGrainedAccessPermissions(data[kPermissions], global_permission); } const std::unordered_map<std::string, uint64_t> &FineGrainedAccessPermissions::GetPermissions() const { @@ -442,13 +449,13 @@ const FineGrainedAccessPermissions &Role::GetFineGrainedAccessEdgeTypePermission nlohmann::json Role::Serialize() const { nlohmann::json data = nlohmann::json::object(); - data["rolename"] = rolename_; - data["permissions"] = permissions_.Serialize(); + data[kRoleName] = rolename_; + data[kPermissions] = permissions_.Serialize(); #ifdef MG_ENTERPRISE if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) { - data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize(); + data[kFineGrainedAccessHandler] = fine_grained_access_handler_.Serialize(); } else { - data["fine_grained_access_handler"] = {}; + data[kFineGrainedAccessHandler] = {}; } #endif return data; @@ -458,21 +465,21 @@ Role Role::Deserialize(const nlohmann::json &data) { if (!data.is_object()) { throw AuthException("Couldn't load role data!"); } - if (!data["rolename"].is_string() || !data["permissions"].is_object()) { + if (!data[kRoleName].is_string() || !data[kPermissions].is_object()) { throw AuthException("Couldn't load role data!"); } - auto permissions = Permissions::Deserialize(data["permissions"]); + auto permissions = Permissions::Deserialize(data[kPermissions]); #ifdef MG_ENTERPRISE if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) { FineGrainedAccessHandler fine_grained_access_handler; // We can have an empty fine_grained if the user was created without a valid license - if (data["fine_grained_access_handler"].is_object()) { - fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]); + if (data[kFineGrainedAccessHandler].is_object()) { + fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data[kFineGrainedAccessHandler]); } - return {data["rolename"], permissions, std::move(fine_grained_access_handler)}; + return {data[kRoleName], permissions, std::move(fine_grained_access_handler)}; } #endif - return {data["rolename"], permissions}; + return {data[kRoleName], permissions}; } bool operator==(const Role &first, const Role &second) { @@ -486,13 +493,13 @@ bool operator==(const Role &first, const Role &second) { } #ifdef MG_ENTERPRISE -void Databases::Add(const std::string &db) { +void Databases::Add(std::string_view db) { if (allow_all_) { grants_dbs_.clear(); allow_all_ = false; } grants_dbs_.emplace(db); - denies_dbs_.erase(db); + denies_dbs_.erase(std::string{db}); // TODO: C++23 use transparent key compare } void Databases::Remove(const std::string &db) { @@ -523,13 +530,13 @@ void Databases::DenyAll() { denies_dbs_.clear(); } -bool Databases::SetDefault(const std::string &db) { +bool Databases::SetDefault(std::string_view db) { if (!Contains(db)) return false; default_db_ = db; return true; } -[[nodiscard]] bool Databases::Contains(const std::string &db) const { +[[nodiscard]] bool Databases::Contains(std::string_view db) const { return !denies_dbs_.contains(db) && (allow_all_ || grants_dbs_.contains(db)); } @@ -542,10 +549,10 @@ const std::string &Databases::GetDefault() const { nlohmann::json Databases::Serialize() const { nlohmann::json data = nlohmann::json::object(); - data["grants"] = grants_dbs_; - data["denies"] = denies_dbs_; - data["allow_all"] = allow_all_; - data["default"] = default_db_; + data[kGrants] = grants_dbs_; + data[kDenies] = denies_dbs_; + data[kAllowAll] = allow_all_; + data[kDefault] = default_db_; return data; } @@ -553,22 +560,22 @@ Databases Databases::Deserialize(const nlohmann::json &data) { if (!data.is_object()) { throw AuthException("Couldn't load database data!"); } - if (!data["grants"].is_structured() || !data["denies"].is_structured() || !data["allow_all"].is_boolean() || - !data["default"].is_string()) { + if (!data[kGrants].is_structured() || !data[kDenies].is_structured() || !data[kAllowAll].is_boolean() || + !data[kDefault].is_string()) { throw AuthException("Couldn't load database data!"); } - return {data["allow_all"], data["grants"], data["denies"], data["default"]}; + return {data[kAllowAll], data[kGrants], data[kDenies], data[kDefault]}; } #endif User::User() = default; User::User(const std::string &username) : username_(utils::ToLowerCase(username)) {} -User::User(const std::string &username, std::string password_hash, const Permissions &permissions) +User::User(const std::string &username, std::optional<HashedPassword> password_hash, const Permissions &permissions) : username_(utils::ToLowerCase(username)), password_hash_(std::move(password_hash)), permissions_(permissions) {} #ifdef MG_ENTERPRISE -User::User(const std::string &username, std::string password_hash, const Permissions &permissions, +User::User(const std::string &username, std::optional<HashedPassword> password_hash, const Permissions &permissions, FineGrainedAccessHandler fine_grained_access_handler, Databases db_access) : username_(utils::ToLowerCase(username)), password_hash_(std::move(password_hash)), @@ -578,38 +585,16 @@ User::User(const std::string &username, std::string password_hash, const Permiss #endif bool User::CheckPassword(const std::string &password) { - if (password_hash_.empty()) return true; - return VerifyPassword(password, password_hash_); + return password_hash_ ? password_hash_->VerifyPassword(password) : true; } -void User::UpdatePassword(const std::optional<std::string> &password) { +void User::UpdatePassword(const std::optional<std::string> &password, + std::optional<PasswordHashAlgorithm> algo_override) { if (!password) { - if (!FLAGS_auth_password_permit_null) { - throw AuthException("Null passwords aren't permitted!"); - } - password_hash_ = ""; + password_hash_.reset(); return; } - - if (FLAGS_auth_password_strength_regex != default_password_regex) { - if (const auto license_check_result = license::global_license_checker.IsEnterpriseValid(utils::global_settings); - license_check_result.HasError()) { - throw AuthException( - "Custom password regex is a Memgraph Enterprise feature. Please set the config " - "(\"--auth-password-strength-regex\") to its default value (\"{}\") or remove the flag.\n{}", - default_password_regex, - license::LicenseCheckErrorToString(license_check_result.GetError(), "password regex")); - } - } - std::regex re(FLAGS_auth_password_strength_regex); - if (!std::regex_match(*password, re)) { - throw AuthException( - "The user password doesn't conform to the required strength! Regex: " - "\"{}\"", - FLAGS_auth_password_strength_regex); - } - - password_hash_ = EncryptPassword(*password); + password_hash_ = HashPassword(*password, algo_override); } void User::SetRole(const Role &role) { role_.emplace(role); } @@ -668,16 +653,20 @@ const Role *User::role() const { nlohmann::json User::Serialize() const { nlohmann::json data = nlohmann::json::object(); - data["username"] = username_; - data["password_hash"] = password_hash_; - data["permissions"] = permissions_.Serialize(); + data[kUsername] = username_; + if (password_hash_.has_value()) { + data[kPasswordHash] = *password_hash_; + } else { + data[kPasswordHash] = nullptr; + } + data[kPermissions] = permissions_.Serialize(); #ifdef MG_ENTERPRISE if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) { - data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize(); - data["databases"] = database_access_.Serialize(); + data[kFineGrainedAccessHandler] = fine_grained_access_handler_.Serialize(); + data[kDatabases] = database_access_.Serialize(); } else { - data["fine_grained_access_handler"] = {}; - data["databases"] = {}; + data[kFineGrainedAccessHandler] = {}; + data[kDatabases] = {}; } #endif // The role shouldn't be serialized here, it is stored as a foreign key. @@ -688,15 +677,23 @@ User User::Deserialize(const nlohmann::json &data) { if (!data.is_object()) { throw AuthException("Couldn't load user data!"); } - if (!data["username"].is_string() || !data["password_hash"].is_string() || !data["permissions"].is_object()) { + auto password_hash_json = data[kPasswordHash]; + if (!data[kUsername].is_string() || !(password_hash_json.is_object() || password_hash_json.is_null()) || + !data[kPermissions].is_object()) { throw AuthException("Couldn't load user data!"); } - auto permissions = Permissions::Deserialize(data["permissions"]); + + std::optional<HashedPassword> password_hash{}; + if (password_hash_json.is_object()) { + password_hash = password_hash_json.get<HashedPassword>(); + } + + auto permissions = Permissions::Deserialize(data[kPermissions]); #ifdef MG_ENTERPRISE if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) { Databases db_access; - if (data["databases"].is_structured()) { - db_access = Databases::Deserialize(data["databases"]); + if (data[kDatabases].is_structured()) { + db_access = Databases::Deserialize(data[kDatabases]); } else { // Back-compatibility spdlog::warn("User without specified database access. Given access to the default database."); @@ -705,13 +702,13 @@ User User::Deserialize(const nlohmann::json &data) { } FineGrainedAccessHandler fine_grained_access_handler; // We can have an empty fine_grained if the user was created without a valid license - if (data["fine_grained_access_handler"].is_object()) { - fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]); + if (data[kFineGrainedAccessHandler].is_object()) { + fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data[kFineGrainedAccessHandler]); } - return {data["username"], data["password_hash"], permissions, std::move(fine_grained_access_handler), db_access}; + return {data[kUsername], std::move(password_hash), permissions, std::move(fine_grained_access_handler), db_access}; } #endif - return {data["username"], data["password_hash"], permissions}; + return {data[kUsername], std::move(password_hash), permissions}; } bool operator==(const User &first, const User &second) { diff --git a/src/auth/models.hpp b/src/auth/models.hpp index 9f66d3119..bb6dd2a7a 100644 --- a/src/auth/models.hpp +++ b/src/auth/models.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Licensed as a Memgraph Enterprise file under the Memgraph Enterprise // License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use @@ -15,6 +15,7 @@ #include <json/json.hpp> #include <utility> +#include "crypto.hpp" #include "dbms/constants.hpp" #include "utils/logging.hpp" @@ -48,6 +49,7 @@ enum class Permission : uint64_t { STORAGE_MODE = 1U << 22U, MULTI_DATABASE_EDIT = 1U << 23U, MULTI_DATABASE_USE = 1U << 24U, + COORDINATOR = 1U << 25U, }; // clang-format on @@ -246,7 +248,7 @@ bool operator==(const Role &first, const Role &second); #ifdef MG_ENTERPRISE class Databases final { public: - Databases() : grants_dbs_({dbms::kDefaultDB}), allow_all_(false), default_db_(dbms::kDefaultDB) {} + Databases() : grants_dbs_{std::string{dbms::kDefaultDB}}, allow_all_(false), default_db_(dbms::kDefaultDB) {} Databases(const Databases &) = default; Databases &operator=(const Databases &) = default; @@ -259,7 +261,7 @@ class Databases final { * * @param db name of the database to grant access to */ - void Add(const std::string &db); + void Add(std::string_view db); /** * @brief Remove database to the list of granted access. @@ -291,7 +293,7 @@ class Databases final { /** * @brief Set the default database. */ - bool SetDefault(const std::string &db); + bool SetDefault(std::string_view db); /** * @brief Checks if access is grated to the database. @@ -299,7 +301,7 @@ class Databases final { * @param db name of the database * @return true if allow_all and not denied or granted */ - bool Contains(const std::string &db) const; + bool Contains(std::string_view db) const; bool GetAllowAll() const { return allow_all_; } const std::set<std::string, std::less<>> &GetGrants() const { return grants_dbs_; } @@ -312,7 +314,7 @@ class Databases final { private: Databases(bool allow_all, std::set<std::string, std::less<>> grant, std::set<std::string, std::less<>> deny, - std::string default_db = dbms::kDefaultDB) + std::string default_db = std::string{dbms::kDefaultDB}) : grants_dbs_(std::move(grant)), denies_dbs_(std::move(deny)), allow_all_(allow_all), @@ -331,9 +333,9 @@ class User final { User(); explicit User(const std::string &username); - User(const std::string &username, std::string password_hash, const Permissions &permissions); + User(const std::string &username, std::optional<HashedPassword> password_hash, const Permissions &permissions); #ifdef MG_ENTERPRISE - User(const std::string &username, std::string password_hash, const Permissions &permissions, + User(const std::string &username, std::optional<HashedPassword> password_hash, const Permissions &permissions, FineGrainedAccessHandler fine_grained_access_handler, Databases db_access = {}); #endif User(const User &) = default; @@ -345,8 +347,18 @@ class User final { /// @throw AuthException if unable to verify the password. bool CheckPassword(const std::string &password); + bool UpgradeHash(const std::string password) { + if (!password_hash_) return false; + if (password_hash_->IsSalted()) return false; + + auto const algo = password_hash_->HashAlgo(); + UpdatePassword(password, algo); + return true; + } + /// @throw AuthException if unable to set the password. - void UpdatePassword(const std::optional<std::string> &password = std::nullopt); + void UpdatePassword(const std::optional<std::string> &password = {}, + std::optional<PasswordHashAlgorithm> algo_override = std::nullopt); void SetRole(const Role &role); @@ -381,7 +393,7 @@ class User final { private: std::string username_; - std::string password_hash_; + std::optional<HashedPassword> password_hash_; Permissions permissions_; #ifdef MG_ENTERPRISE FineGrainedAccessHandler fine_grained_access_handler_; diff --git a/src/communication/bolt/v1/states/handlers.hpp b/src/communication/bolt/v1/states/handlers.hpp index 3b5a67b17..3ffcb6f55 100644 --- a/src/communication/bolt/v1/states/handlers.hpp +++ b/src/communication/bolt/v1/states/handlers.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -170,6 +170,7 @@ inline State HandleFailure(TSession &session, const std::exception &e) { spdlog::trace("Error trace: {}", p->trace()); } session.encoder_buffer_.Clear(); + auto code_message = ExceptionToErrorMessage(e); bool fail_sent = session.encoder_.MessageFailure({{"code", code_message.first}, {"message", code_message.second}}); if (!fail_sent) { diff --git a/src/communication/client.cpp b/src/communication/client.cpp index bd7fd6e7f..85c0d826a 100644 --- a/src/communication/client.cpp +++ b/src/communication/client.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -12,6 +12,7 @@ #include "communication/client.hpp" #include "communication/helpers.hpp" +#include "io/network/network_error.hpp" #include "utils/logging.hpp" namespace memgraph::communication { @@ -25,7 +26,9 @@ Client::~Client() { bool Client::Connect(const io::network::Endpoint &endpoint) { // Try to establish a socket connection. - if (!socket_.Connect(endpoint)) return false; + if (!socket_.Connect(endpoint)) { + return false; + } // Enable TCP keep alive for all connections. // Because we manually always set the `have_more` flag to the socket diff --git a/src/communication/http/session.hpp b/src/communication/http/session.hpp index b08ce8f30..cef457065 100644 --- a/src/communication/http/session.hpp +++ b/src/communication/http/session.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -107,7 +107,7 @@ class Session : public std::enable_shared_from_this<Session<TRequestHandler, TSe void DoRead() { req_ = {}; - ExecuteForStream([this](auto &&stream) { + ExecuteForStream([this](auto &stream) { boost::beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(kSSLExpirySeconds)); boost::beast::http::async_read( @@ -129,7 +129,7 @@ class Session : public std::enable_shared_from_this<Session<TRequestHandler, TSe } auto async_write = [this](boost::beast::http::response<boost::beast::http::string_body> msg) { - ExecuteForStream([this, &msg](auto &&stream) { + ExecuteForStream([this, &msg](auto &stream) { // The lifetime of the message has to extend // for the duration of the async operation so // we use a shared_ptr to manage it. @@ -171,7 +171,7 @@ class Session : public std::enable_shared_from_this<Session<TRequestHandler, TSe } auto GetExecutor() { - return std::visit(utils::Overloaded{[](auto &&stream) { return stream.get_executor(); }}, stream_); + return std::visit(utils::Overloaded{[](auto &stream) { return stream.get_executor(); }}, stream_); } template <typename F> diff --git a/src/communication/result_stream_faker.hpp b/src/communication/result_stream_faker.hpp index f8786dd43..779d039cc 100644 --- a/src/communication/result_stream_faker.hpp +++ b/src/communication/result_stream_faker.hpp @@ -44,7 +44,7 @@ class ResultStreamFaker { std::vector<memgraph::communication::bolt::Value> bvalues; bvalues.reserve(values.size()); for (const auto &value : values) { - auto maybe_value = memgraph::glue::ToBoltValue(value, *store_, memgraph::storage::View::NEW); + auto maybe_value = memgraph::glue::ToBoltValue(value, store_, memgraph::storage::View::NEW); MG_ASSERT(maybe_value.HasValue()); bvalues.push_back(std::move(*maybe_value)); } @@ -56,7 +56,7 @@ class ResultStreamFaker { void Summary(const std::map<std::string, memgraph::query::TypedValue> &summary) { std::map<std::string, memgraph::communication::bolt::Value> bsummary; for (const auto &item : summary) { - auto maybe_value = memgraph::glue::ToBoltValue(item.second, *store_, memgraph::storage::View::NEW); + auto maybe_value = memgraph::glue::ToBoltValue(item.second, store_, memgraph::storage::View::NEW); MG_ASSERT(maybe_value.HasValue()); bsummary.insert({item.first, std::move(*maybe_value)}); } diff --git a/src/coordination/CMakeLists.txt b/src/coordination/CMakeLists.txt new file mode 100644 index 000000000..e8c4b3735 --- /dev/null +++ b/src/coordination/CMakeLists.txt @@ -0,0 +1,29 @@ +add_library(mg-coordination STATIC) +add_library(mg::coordination ALIAS mg-coordination) +target_sources(mg-coordination + PUBLIC + include/coordination/coordinator_client.hpp + include/coordination/coordinator_state.hpp + include/coordination/coordinator_rpc.hpp + include/coordination/coordinator_server.hpp + include/coordination/coordinator_config.hpp + include/coordination/coordinator_exceptions.hpp + include/coordination/coordinator_instance.hpp + include/coordination/coordinator_slk.hpp + include/coordination/coordinator_data.hpp + include/coordination/constants.hpp + include/coordination/failover_status.hpp + include/coordination/coordinator_cluster_config.hpp + + PRIVATE + coordinator_client.cpp + coordinator_state.cpp + coordinator_rpc.cpp + coordinator_server.cpp + coordinator_data.cpp +) +target_include_directories(mg-coordination PUBLIC include) + +target_link_libraries(mg-coordination + PUBLIC mg::utils mg::rpc mg::slk mg::io mg::repl_coord_glue lib::rangev3 +) diff --git a/src/coordination/coordinator_client.cpp b/src/coordination/coordinator_client.cpp new file mode 100644 index 000000000..93ef3e3af --- /dev/null +++ b/src/coordination/coordinator_client.cpp @@ -0,0 +1,109 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#ifdef MG_ENTERPRISE + +#include "coordination/coordinator_client.hpp" + +#include "coordination/coordinator_config.hpp" +#include "coordination/coordinator_rpc.hpp" +#include "replication_coordination_glue/messages.hpp" + +namespace memgraph::coordination { + +namespace { +auto CreateClientContext(const memgraph::coordination::CoordinatorClientConfig &config) + -> communication::ClientContext { + return (config.ssl) ? communication::ClientContext{config.ssl->key_file, config.ssl->cert_file} + : communication::ClientContext{}; +} +} // namespace + +CoordinatorClient::CoordinatorClient(CoordinatorData *coord_data, CoordinatorClientConfig config, + HealthCheckCallback succ_cb, HealthCheckCallback fail_cb) + : rpc_context_{CreateClientContext(config)}, + rpc_client_{io::network::Endpoint(io::network::Endpoint::needs_resolving, config.ip_address, config.port), + &rpc_context_}, + config_{std::move(config)}, + coord_data_{coord_data}, + succ_cb_{std::move(succ_cb)}, + fail_cb_{std::move(fail_cb)} {} + +auto CoordinatorClient::InstanceName() const -> std::string { return config_.instance_name; } +auto CoordinatorClient::SocketAddress() const -> std::string { return rpc_client_.Endpoint().SocketAddress(); } + +void CoordinatorClient::StartFrequentCheck() { + MG_ASSERT(config_.health_check_frequency_sec > std::chrono::seconds(0), + "Health check frequency must be greater than 0"); + + instance_checker_.Run( + "Coord checker", config_.health_check_frequency_sec, [this, instance_name = config_.instance_name] { + try { + spdlog::trace("Sending frequent heartbeat to machine {} on {}", instance_name, + rpc_client_.Endpoint().SocketAddress()); + auto stream{rpc_client_.Stream<memgraph::replication_coordination_glue::FrequentHeartbeatRpc>()}; + stream.AwaitResponse(); + succ_cb_(coord_data_, instance_name); + } catch (const rpc::RpcFailedException &) { + fail_cb_(coord_data_, instance_name); + } + }); +} + +void CoordinatorClient::StopFrequentCheck() { instance_checker_.Stop(); } + +void CoordinatorClient::PauseFrequentCheck() { instance_checker_.Pause(); } +void CoordinatorClient::ResumeFrequentCheck() { instance_checker_.Resume(); } + +auto CoordinatorClient::SetSuccCallback(HealthCheckCallback succ_cb) -> void { succ_cb_ = std::move(succ_cb); } +auto CoordinatorClient::SetFailCallback(HealthCheckCallback fail_cb) -> void { fail_cb_ = std::move(fail_cb); } + +auto CoordinatorClient::ReplicationClientInfo() const -> const CoordinatorClientConfig::ReplicationClientInfo & { + return config_.replication_client_info; +} + +auto CoordinatorClient::ResetReplicationClientInfo() -> void { + // TODO (antoniofilipovic) Sync with Andi on this one + // config_.replication_client_info.reset(); +} + +auto CoordinatorClient::SendPromoteReplicaToMainRpc( + std::vector<CoordinatorClientConfig::ReplicationClientInfo> replication_clients_info) const -> bool { + try { + auto stream{rpc_client_.Stream<PromoteReplicaToMainRpc>(std::move(replication_clients_info))}; + if (!stream.AwaitResponse().success) { + spdlog::error("Failed to receive successful RPC failover response!"); + return false; + } + return true; + } catch (const rpc::RpcFailedException &) { + spdlog::error("RPC error occurred while sending failover RPC!"); + } + return false; +} + +auto CoordinatorClient::SendSetToReplicaRpc(CoordinatorClient::ReplClientInfo replication_client_info) const -> bool { + try { + auto stream{rpc_client_.Stream<SetMainToReplicaRpc>(std::move(replication_client_info))}; + if (!stream.AwaitResponse().success) { + spdlog::error("Failed to set main to replica!"); + return false; + } + spdlog::info("Sent request RPC from coordinator to instance to set it as replica!"); + return true; + } catch (const rpc::RpcFailedException &) { + spdlog::error("Failed to send failover RPC from coordinator to new main!"); + } + return false; +} + +} // namespace memgraph::coordination +#endif diff --git a/src/coordination/coordinator_data.cpp b/src/coordination/coordinator_data.cpp new file mode 100644 index 000000000..c236cf753 --- /dev/null +++ b/src/coordination/coordinator_data.cpp @@ -0,0 +1,220 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include "coordination/coordinator_instance.hpp" +#include "coordination/register_main_replica_coordinator_status.hpp" +#ifdef MG_ENTERPRISE + +#include "coordination/coordinator_data.hpp" + +#include <range/v3/view.hpp> +#include <shared_mutex> + +namespace memgraph::coordination { + +CoordinatorData::CoordinatorData() { + auto find_instance = [](CoordinatorData *coord_data, std::string_view instance_name) -> CoordinatorInstance & { + auto instance = std::ranges::find_if( + coord_data->registered_instances_, + [instance_name](const CoordinatorInstance &instance) { return instance.InstanceName() == instance_name; }); + + MG_ASSERT(instance != coord_data->registered_instances_.end(), "Instance {} not found during callback!", + instance_name); + return *instance; + }; + + replica_succ_cb_ = [find_instance](CoordinatorData *coord_data, std::string_view instance_name) -> void { + auto lock = std::lock_guard{coord_data->coord_data_lock_}; + spdlog::trace("Instance {} performing replica successful callback", instance_name); + auto &instance = find_instance(coord_data, instance_name); + MG_ASSERT(instance.IsReplica(), "Instance {} is not a replica!", instance_name); + instance.UpdateLastResponseTime(); + }; + + replica_fail_cb_ = [find_instance](CoordinatorData *coord_data, std::string_view instance_name) -> void { + auto lock = std::lock_guard{coord_data->coord_data_lock_}; + spdlog::trace("Instance {} performing replica failure callback", instance_name); + auto &instance = find_instance(coord_data, instance_name); + MG_ASSERT(instance.IsReplica(), "Instance {} is not a replica!", instance_name); + instance.UpdateInstanceStatus(); + }; + + main_succ_cb_ = [find_instance](CoordinatorData *coord_data, std::string_view instance_name) -> void { + auto lock = std::lock_guard{coord_data->coord_data_lock_}; + spdlog::trace("Instance {} performing main successful callback", instance_name); + auto &instance = find_instance(coord_data, instance_name); + MG_ASSERT(instance.IsMain(), "Instance {} is not a main!", instance_name); + instance.UpdateLastResponseTime(); + }; + + main_fail_cb_ = [this, find_instance](CoordinatorData *coord_data, std::string_view instance_name) -> void { + auto lock = std::lock_guard{coord_data->coord_data_lock_}; + spdlog::trace("Instance {} performing main failure callback", instance_name); + auto &instance = find_instance(coord_data, instance_name); + MG_ASSERT(instance.IsMain(), "Instance {} is not a main!", instance_name); + if (bool main_alive = instance.UpdateInstanceStatus(); !main_alive) { + spdlog::info("Main instance {} is not alive, starting automatic failover", instance_name); + switch (auto failover_status = DoFailover(); failover_status) { + using enum DoFailoverStatus; + case ALL_REPLICAS_DOWN: + spdlog::warn("Failover aborted since all replicas are down!"); + break; + case MAIN_ALIVE: + spdlog::warn("Failover aborted since main is alive!"); + break; + case RPC_FAILED: + spdlog::warn("Failover aborted since promoting replica to main failed!"); + break; + case SUCCESS: + break; + } + } + }; +} + +auto CoordinatorData::DoFailover() -> DoFailoverStatus { + using ReplicationClientInfo = CoordinatorClientConfig::ReplicationClientInfo; + + auto replica_instances = registered_instances_ | ranges::views::filter(&CoordinatorInstance::IsReplica); + + auto chosen_replica_instance = std::ranges::find_if(replica_instances, &CoordinatorInstance::IsAlive); + if (chosen_replica_instance == replica_instances.end()) { + return DoFailoverStatus::ALL_REPLICAS_DOWN; + } + + chosen_replica_instance->PrepareForFailover(); + + std::vector<ReplicationClientInfo> repl_clients_info; + repl_clients_info.reserve(std::ranges::distance(replica_instances)); + + auto const not_chosen_replica_instance = [&chosen_replica_instance](const CoordinatorInstance &instance) { + return instance != *chosen_replica_instance; + }; + auto const not_main = [](const CoordinatorInstance &instance) { return !instance.IsMain(); }; + + // TODO (antoniofilipovic): Should we send also data on old MAIN??? + // TODO: (andi) Don't send replicas which aren't alive + for (const auto &unchosen_replica_instance : + replica_instances | ranges::views::filter(not_chosen_replica_instance) | ranges::views::filter(not_main)) { + repl_clients_info.emplace_back(unchosen_replica_instance.client_.ReplicationClientInfo()); + } + + if (!chosen_replica_instance->client_.SendPromoteReplicaToMainRpc(std::move(repl_clients_info))) { + chosen_replica_instance->RestoreAfterFailedFailover(); + return DoFailoverStatus::RPC_FAILED; + } + + auto old_main = std::ranges::find_if(registered_instances_, &CoordinatorInstance::IsMain); + // TODO: (andi) For performing restoration we will have to improve this + old_main->client_.PauseFrequentCheck(); + + chosen_replica_instance->PostFailover(main_succ_cb_, main_fail_cb_); + + return DoFailoverStatus::SUCCESS; +} + +auto CoordinatorData::ShowInstances() const -> std::vector<CoordinatorInstanceStatus> { + std::vector<CoordinatorInstanceStatus> instances_status; + instances_status.reserve(registered_instances_.size()); + + auto const stringify_repl_role = [](const CoordinatorInstance &instance) -> std::string { + if (!instance.IsAlive()) return ""; + if (instance.IsMain()) return "main"; + return "replica"; + }; + + auto const instance_to_status = + [&stringify_repl_role](const CoordinatorInstance &instance) -> CoordinatorInstanceStatus { + return {.instance_name = instance.InstanceName(), + .socket_address = instance.SocketAddress(), + .replication_role = stringify_repl_role(instance), + .is_alive = instance.IsAlive()}; + }; + + { + auto lock = std::shared_lock{coord_data_lock_}; + std::ranges::transform(registered_instances_, std::back_inserter(instances_status), instance_to_status); + } + + return instances_status; +} + +auto CoordinatorData::SetInstanceToMain(std::string instance_name) -> SetInstanceToMainCoordinatorStatus { + auto lock = std::lock_guard{coord_data_lock_}; + + // Find replica we already registered + auto registered_replica = std::find_if( + registered_instances_.begin(), registered_instances_.end(), + [instance_name](const CoordinatorInstance &instance) { return instance.InstanceName() == instance_name; }); + + // if replica not found... + if (registered_replica == registered_instances_.end()) { + spdlog::error("You didn't register instance with given name {}", instance_name); + return SetInstanceToMainCoordinatorStatus::NO_INSTANCE_WITH_NAME; + } + + registered_replica->client_.PauseFrequentCheck(); + + std::vector<CoordinatorClientConfig::ReplicationClientInfo> repl_clients_info; + repl_clients_info.reserve(registered_instances_.size() - 1); + std::ranges::for_each(registered_instances_, + [registered_replica, &repl_clients_info](const CoordinatorInstance &replica) { + if (replica != *registered_replica) { + repl_clients_info.emplace_back(replica.client_.ReplicationClientInfo()); + } + }); + + // PROMOTE REPLICA TO MAIN + // THIS SHOULD FAIL HERE IF IT IS DOWN + if (auto result = registered_replica->client_.SendPromoteReplicaToMainRpc(std::move(repl_clients_info)); !result) { + registered_replica->client_.ResumeFrequentCheck(); + return SetInstanceToMainCoordinatorStatus::COULD_NOT_PROMOTE_TO_MAIN; + } + + registered_replica->client_.SetSuccCallback(main_succ_cb_); + registered_replica->client_.SetFailCallback(main_fail_cb_); + registered_replica->replication_role_ = replication_coordination_glue::ReplicationRole::MAIN; + registered_replica->client_.ResumeFrequentCheck(); + + return SetInstanceToMainCoordinatorStatus::SUCCESS; +} + +auto CoordinatorData::RegisterInstance(CoordinatorClientConfig config) -> RegisterInstanceCoordinatorStatus { + auto lock = std::lock_guard{coord_data_lock_}; + if (std::ranges::any_of(registered_instances_, [&config](const CoordinatorInstance &instance) { + return instance.InstanceName() == config.instance_name; + })) { + return RegisterInstanceCoordinatorStatus::NAME_EXISTS; + } + + if (std::ranges::any_of(registered_instances_, [&config](const CoordinatorInstance &instance) { + spdlog::trace("Comparing {} with {}", instance.SocketAddress(), config.SocketAddress()); + return instance.SocketAddress() == config.SocketAddress(); + })) { + return RegisterInstanceCoordinatorStatus::END_POINT_EXISTS; + } + + CoordinatorClientConfig::ReplicationClientInfo replication_client_info_copy = config.replication_client_info; + + // TODO (antoniofilipovic) create and then push back + auto *instance = ®istered_instances_.emplace_back(this, std::move(config), replica_succ_cb_, replica_fail_cb_, + replication_coordination_glue::ReplicationRole::REPLICA); + if (auto res = instance->client_.SendSetToReplicaRpc(replication_client_info_copy); !res) { + return RegisterInstanceCoordinatorStatus::RPC_FAILED; + } + + instance->client_.StartFrequentCheck(); + + return RegisterInstanceCoordinatorStatus::SUCCESS; +} + +} // namespace memgraph::coordination +#endif diff --git a/src/coordination/coordinator_rpc.cpp b/src/coordination/coordinator_rpc.cpp new file mode 100644 index 000000000..e8a16f0e2 --- /dev/null +++ b/src/coordination/coordinator_rpc.cpp @@ -0,0 +1,107 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#ifdef MG_ENTERPRISE + +#include "coordination/coordinator_rpc.hpp" + +#include "coordination/coordinator_slk.hpp" +#include "slk/serialization.hpp" + +namespace memgraph { + +namespace coordination { + +void PromoteReplicaToMainReq::Save(const PromoteReplicaToMainReq &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self, builder); +} + +void PromoteReplicaToMainReq::Load(PromoteReplicaToMainReq *self, memgraph::slk::Reader *reader) { + memgraph::slk::Load(self, reader); +} + +void PromoteReplicaToMainRes::Save(const PromoteReplicaToMainRes &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self, builder); +} + +void PromoteReplicaToMainRes::Load(PromoteReplicaToMainRes *self, memgraph::slk::Reader *reader) { + memgraph::slk::Load(self, reader); +} + +void SetMainToReplicaReq::Save(const SetMainToReplicaReq &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self, builder); +} + +void SetMainToReplicaReq::Load(SetMainToReplicaReq *self, memgraph::slk::Reader *reader) { + memgraph::slk::Load(self, reader); +} + +void SetMainToReplicaRes::Save(const SetMainToReplicaRes &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self, builder); +} + +void SetMainToReplicaRes::Load(SetMainToReplicaRes *self, memgraph::slk::Reader *reader) { + memgraph::slk::Load(self, reader); +} + +} // namespace coordination + +constexpr utils::TypeInfo coordination::PromoteReplicaToMainReq::kType{utils::TypeId::COORD_FAILOVER_REQ, + "CoordPromoteReplicaToMainReq", nullptr}; + +constexpr utils::TypeInfo coordination::PromoteReplicaToMainRes::kType{utils::TypeId::COORD_FAILOVER_RES, + "CoordPromoteReplicaToMainRes", nullptr}; + +constexpr utils::TypeInfo coordination::SetMainToReplicaReq::kType{utils::TypeId::COORD_SET_REPL_MAIN_REQ, + "CoordSetReplMainReq", nullptr}; + +constexpr utils::TypeInfo coordination::SetMainToReplicaRes::kType{utils::TypeId::COORD_SET_REPL_MAIN_RES, + "CoordSetReplMainRes", nullptr}; + +namespace slk { + +void Save(const memgraph::coordination::PromoteReplicaToMainRes &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self.success, builder); +} + +void Load(memgraph::coordination::PromoteReplicaToMainRes *self, memgraph::slk::Reader *reader) { + memgraph::slk::Load(&self->success, reader); +} + +void Save(const memgraph::coordination::PromoteReplicaToMainReq &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self.replication_clients_info, builder); +} + +void Load(memgraph::coordination::PromoteReplicaToMainReq *self, memgraph::slk::Reader *reader) { + memgraph::slk::Load(&self->replication_clients_info, reader); +} + +void Save(const memgraph::coordination::SetMainToReplicaReq &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self.replication_client_info, builder); +} + +void Load(memgraph::coordination::SetMainToReplicaReq *self, memgraph::slk::Reader *reader) { + memgraph::slk::Load(&self->replication_client_info, reader); +} + +void Save(const memgraph::coordination::SetMainToReplicaRes &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self.success, builder); +} + +void Load(memgraph::coordination::SetMainToReplicaRes *self, memgraph::slk::Reader *reader) { + memgraph::slk::Load(&self->success, reader); +} + +} // namespace slk + +} // namespace memgraph + +#endif diff --git a/src/coordination/coordinator_server.cpp b/src/coordination/coordinator_server.cpp new file mode 100644 index 000000000..a8253cf25 --- /dev/null +++ b/src/coordination/coordinator_server.cpp @@ -0,0 +1,57 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#ifdef MG_ENTERPRISE + +#include "coordination/coordinator_server.hpp" +#include "replication_coordination_glue/messages.hpp" + +namespace memgraph::coordination { + +namespace { + +auto CreateServerContext(const memgraph::coordination::CoordinatorServerConfig &config) + -> communication::ServerContext { + return (config.ssl) ? communication::ServerContext{config.ssl->key_file, config.ssl->cert_file, config.ssl->ca_file, + config.ssl->verify_peer} + : communication::ServerContext{}; +} + +// NOTE: The coordinator server doesn't more than 1 processing thread - each replica can +// have only a single coordinator server. Also, the single-threaded guarantee +// simplifies the rest of the implementation. +constexpr auto kCoordinatorServerThreads = 1; + +} // namespace + +CoordinatorServer::CoordinatorServer(const CoordinatorServerConfig &config) + : rpc_server_context_{CreateServerContext(config)}, + rpc_server_{io::network::Endpoint{config.ip_address, config.port}, &rpc_server_context_, + kCoordinatorServerThreads} { + rpc_server_.Register<replication_coordination_glue::FrequentHeartbeatRpc>([](auto *req_reader, auto *res_builder) { + spdlog::debug("Received FrequentHeartbeatRpc on coordinator server"); + replication_coordination_glue::FrequentHeartbeatHandler(req_reader, res_builder); + }); +} + +CoordinatorServer::~CoordinatorServer() { + if (rpc_server_.IsRunning()) { + auto const &endpoint = rpc_server_.endpoint(); + spdlog::trace("Closing coordinator server on {}:{}", endpoint.address, endpoint.port); + rpc_server_.Shutdown(); + } + rpc_server_.AwaitShutdown(); +} + +bool CoordinatorServer::Start() { return rpc_server_.Start(); } + +} // namespace memgraph::coordination +#endif diff --git a/src/coordination/coordinator_state.cpp b/src/coordination/coordinator_state.cpp new file mode 100644 index 000000000..60ec458ac --- /dev/null +++ b/src/coordination/coordinator_state.cpp @@ -0,0 +1,89 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#ifdef MG_ENTERPRISE + +#include "coordination/coordinator_state.hpp" + +#include "coordination/coordinator_config.hpp" +#include "coordination/register_main_replica_coordinator_status.hpp" +#include "flags/replication.hpp" +#include "spdlog/spdlog.h" +#include "utils/logging.hpp" +#include "utils/variant_helpers.hpp" + +#include <algorithm> + +namespace memgraph::coordination { + +CoordinatorState::CoordinatorState() { + MG_ASSERT(!(FLAGS_coordinator && FLAGS_coordinator_server_port), + "Instance cannot be a coordinator and have registered coordinator server."); + + spdlog::info("Executing coordinator constructor"); + if (FLAGS_coordinator_server_port) { + spdlog::info("Coordinator server port set"); + auto const config = CoordinatorServerConfig{ + .ip_address = kDefaultReplicationServerIp, + .port = static_cast<uint16_t>(FLAGS_coordinator_server_port), + }; + spdlog::info("Executing coordinator constructor main replica"); + + data_ = CoordinatorMainReplicaData{.coordinator_server_ = std::make_unique<CoordinatorServer>(config)}; + } +} + +auto CoordinatorState::RegisterInstance(CoordinatorClientConfig config) -> RegisterInstanceCoordinatorStatus { + MG_ASSERT(std::holds_alternative<CoordinatorData>(data_), + "Coordinator cannot register replica since variant holds wrong alternative"); + + return std::visit( + memgraph::utils::Overloaded{ + [](const CoordinatorMainReplicaData & /*coordinator_main_replica_data*/) { + return RegisterInstanceCoordinatorStatus::NOT_COORDINATOR; + }, + [config](CoordinatorData &coordinator_data) { return coordinator_data.RegisterInstance(config); }}, + data_); +} + +auto CoordinatorState::SetInstanceToMain(std::string instance_name) -> SetInstanceToMainCoordinatorStatus { + MG_ASSERT(std::holds_alternative<CoordinatorData>(data_), + "Coordinator cannot register replica since variant holds wrong alternative"); + + return std::visit( + memgraph::utils::Overloaded{[](const CoordinatorMainReplicaData & /*coordinator_main_replica_data*/) { + return SetInstanceToMainCoordinatorStatus::NOT_COORDINATOR; + }, + [&instance_name](CoordinatorData &coordinator_data) { + return coordinator_data.SetInstanceToMain(instance_name); + }}, + data_); +} + +auto CoordinatorState::ShowInstances() const -> std::vector<CoordinatorInstanceStatus> { + MG_ASSERT(std::holds_alternative<CoordinatorData>(data_), + "Can't call show instances on data_, as variant holds wrong alternative"); + return std::get<CoordinatorData>(data_).ShowInstances(); +} + +[[nodiscard]] auto CoordinatorState::DoFailover() -> DoFailoverStatus { + MG_ASSERT(std::holds_alternative<CoordinatorData>(data_), "Cannot do failover since variant holds wrong alternative"); + auto &coord_state = std::get<CoordinatorData>(data_); + return coord_state.DoFailover(); +} + +auto CoordinatorState::GetCoordinatorServer() const -> CoordinatorServer & { + MG_ASSERT(std::holds_alternative<CoordinatorMainReplicaData>(data_), + "Cannot get coordinator server since variant holds wrong alternative"); + return *std::get<CoordinatorMainReplicaData>(data_).coordinator_server_; +} +} // namespace memgraph::coordination +#endif diff --git a/src/coordination/include/coordination/constants.hpp b/src/coordination/include/coordination/constants.hpp new file mode 100644 index 000000000..819b9fa05 --- /dev/null +++ b/src/coordination/include/coordination/constants.hpp @@ -0,0 +1,22 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +namespace memgraph::coordination { + +#ifdef MG_EXPERIMENTAL_HIGH_AVAILABILITY +constexpr bool allow_ha = true; +#else +constexpr bool allow_ha = false; +#endif + +} // namespace memgraph::coordination diff --git a/src/coordination/include/coordination/coordinator_client.hpp b/src/coordination/include/coordination/coordinator_client.hpp new file mode 100644 index 000000000..1bc361a57 --- /dev/null +++ b/src/coordination/include/coordination/coordinator_client.hpp @@ -0,0 +1,77 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#ifdef MG_ENTERPRISE + +#include "coordination/coordinator_config.hpp" +#include "rpc/client.hpp" +#include "utils/scheduler.hpp" + +namespace memgraph::coordination { + +class CoordinatorData; +using HealthCheckCallback = std::function<void(CoordinatorData *, std::string_view)>; + +class CoordinatorClient { + public: + using ReplClientInfo = CoordinatorClientConfig::ReplicationClientInfo; + using ReplicationClientsInfo = std::vector<ReplClientInfo>; + + explicit CoordinatorClient(CoordinatorData *coord_data_, CoordinatorClientConfig config, HealthCheckCallback succ_cb, + HealthCheckCallback fail_cb); + + ~CoordinatorClient() = default; + + CoordinatorClient(CoordinatorClient &) = delete; + CoordinatorClient &operator=(CoordinatorClient const &) = delete; + + CoordinatorClient(CoordinatorClient &&) noexcept = delete; + CoordinatorClient &operator=(CoordinatorClient &&) noexcept = delete; + + void StartFrequentCheck(); + void StopFrequentCheck(); + void PauseFrequentCheck(); + void ResumeFrequentCheck(); + + auto InstanceName() const -> std::string; + auto SocketAddress() const -> std::string; + + auto SendPromoteReplicaToMainRpc(ReplicationClientsInfo replication_clients_info) const -> bool; + + auto ReplicationClientInfo() const -> const ReplClientInfo &; + auto ResetReplicationClientInfo() -> void; + + auto SendSetToReplicaRpc(ReplClientInfo replication_client_info) const -> bool; + + auto SetSuccCallback(HealthCheckCallback succ_cb) -> void; + auto SetFailCallback(HealthCheckCallback fail_cb) -> void; + + friend bool operator==(CoordinatorClient const &first, CoordinatorClient const &second) { + return first.config_ == second.config_; + } + + private: + utils::Scheduler instance_checker_; + + // TODO: (andi) Pimpl? + communication::ClientContext rpc_context_; + mutable rpc::Client rpc_client_; + + CoordinatorClientConfig config_; + CoordinatorData *coord_data_; + HealthCheckCallback succ_cb_; + HealthCheckCallback fail_cb_; +}; + +} // namespace memgraph::coordination +#endif diff --git a/src/coordination/include/coordination/coordinator_cluster_config.hpp b/src/coordination/include/coordination/coordinator_cluster_config.hpp new file mode 100644 index 000000000..e1d91ff7d --- /dev/null +++ b/src/coordination/include/coordination/coordinator_cluster_config.hpp @@ -0,0 +1,22 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#ifdef MG_ENTERPRISE +namespace memgraph::coordination { + +struct CoordinatorClusterConfig { + static constexpr int alive_response_time_difference_sec_{5}; +}; + +} // namespace memgraph::coordination +#endif diff --git a/src/coordination/include/coordination/coordinator_config.hpp b/src/coordination/include/coordination/coordinator_config.hpp new file mode 100644 index 000000000..bbbed9dd7 --- /dev/null +++ b/src/coordination/include/coordination/coordinator_config.hpp @@ -0,0 +1,78 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#ifdef MG_ENTERPRISE + +#include "replication_coordination_glue/mode.hpp" + +#include <chrono> +#include <cstdint> +#include <optional> +#include <string> + +namespace memgraph::coordination { + +inline constexpr auto *kDefaultReplicationServerIp = "0.0.0.0"; + +struct CoordinatorClientConfig { + std::string instance_name; + std::string ip_address; + uint16_t port{}; + std::chrono::seconds health_check_frequency_sec{1}; + + auto SocketAddress() const -> std::string { return ip_address + ":" + std::to_string(port); } + + // Info which coordinator will send to new main when performing failover + struct ReplicationClientInfo { + // Must be the same as CoordinatorClientConfig's instance_name + std::string instance_name; + replication_coordination_glue::ReplicationMode replication_mode{}; + std::string replication_ip_address; + uint16_t replication_port{}; + + friend bool operator==(ReplicationClientInfo const &, ReplicationClientInfo const &) = default; + }; + + // Each instance has replication config in case it fails + ReplicationClientInfo replication_client_info; + + struct SSL { + std::string key_file; + std::string cert_file; + + friend bool operator==(const SSL &, const SSL &) = default; + }; + + std::optional<SSL> ssl; + + friend bool operator==(CoordinatorClientConfig const &, CoordinatorClientConfig const &) = default; +}; + +struct CoordinatorServerConfig { + std::string ip_address; + uint16_t port{}; + struct SSL { + std::string key_file; + std::string cert_file; + std::string ca_file; + bool verify_peer{}; + friend bool operator==(SSL const &, SSL const &) = default; + }; + + std::optional<SSL> ssl; + + friend bool operator==(CoordinatorServerConfig const &, CoordinatorServerConfig const &) = default; +}; + +} // namespace memgraph::coordination +#endif diff --git a/src/coordination/include/coordination/coordinator_data.hpp b/src/coordination/include/coordination/coordinator_data.hpp new file mode 100644 index 000000000..d14f5e1db --- /dev/null +++ b/src/coordination/include/coordination/coordinator_data.hpp @@ -0,0 +1,49 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#ifdef MG_ENTERPRISE + +#include "coordination/coordinator_instance.hpp" +#include "coordination/coordinator_instance_status.hpp" +#include "coordination/coordinator_server.hpp" +#include "coordination/failover_status.hpp" +#include "coordination/register_main_replica_coordinator_status.hpp" +#include "utils/rw_lock.hpp" + +#include <list> + +namespace memgraph::coordination { +class CoordinatorData { + public: + CoordinatorData(); + + [[nodiscard]] auto DoFailover() -> DoFailoverStatus; + + [[nodiscard]] auto RegisterInstance(CoordinatorClientConfig config) -> RegisterInstanceCoordinatorStatus; + [[nodiscard]] auto SetInstanceToMain(std::string instance_name) -> SetInstanceToMainCoordinatorStatus; + + auto ShowInstances() const -> std::vector<CoordinatorInstanceStatus>; + + private: + mutable utils::RWLock coord_data_lock_{utils::RWLock::Priority::READ}; + HealthCheckCallback main_succ_cb_, main_fail_cb_, replica_succ_cb_, replica_fail_cb_; + // Must be std::list because we rely on pointer stability + std::list<CoordinatorInstance> registered_instances_; +}; + +struct CoordinatorMainReplicaData { + std::unique_ptr<CoordinatorServer> coordinator_server_; +}; + +} // namespace memgraph::coordination +#endif diff --git a/src/coordination/include/coordination/coordinator_exceptions.hpp b/src/coordination/include/coordination/coordinator_exceptions.hpp new file mode 100644 index 000000000..708fb81f3 --- /dev/null +++ b/src/coordination/include/coordination/coordinator_exceptions.hpp @@ -0,0 +1,32 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#ifdef MG_ENTERPRISE + +#include "utils/exceptions.hpp" + +namespace memgraph::coordination { +class CoordinatorFailoverException final : public utils::BasicException { + public: + explicit CoordinatorFailoverException(const std::string_view what) noexcept + : BasicException("Failover didn't complete successfully: " + std::string(what)) {} + + template <class... Args> + explicit CoordinatorFailoverException(fmt::format_string<Args...> fmt, Args &&...args) noexcept + : CoordinatorFailoverException(fmt::format(fmt, std::forward<Args>(args)...)) {} + + SPECIALIZE_GET_EXCEPTION_NAME(CoordinatorFailoverException) +}; + +} // namespace memgraph::coordination +#endif diff --git a/src/coordination/include/coordination/coordinator_instance.hpp b/src/coordination/include/coordination/coordinator_instance.hpp new file mode 100644 index 000000000..31a6d8204 --- /dev/null +++ b/src/coordination/include/coordination/coordinator_instance.hpp @@ -0,0 +1,77 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#ifdef MG_ENTERPRISE + +#include "coordination/coordinator_client.hpp" +#include "coordination/coordinator_cluster_config.hpp" +#include "replication_coordination_glue/role.hpp" + +namespace memgraph::coordination { + +class CoordinatorData; + +class CoordinatorInstance { + public: + CoordinatorInstance(CoordinatorData *data, CoordinatorClientConfig config, HealthCheckCallback succ_cb, + HealthCheckCallback fail_cb, replication_coordination_glue::ReplicationRole replication_role) + : client_(data, std::move(config), std::move(succ_cb), std::move(fail_cb)), + replication_role_(replication_role), + is_alive_(true) {} + + CoordinatorInstance(CoordinatorInstance const &other) = delete; + CoordinatorInstance &operator=(CoordinatorInstance const &other) = delete; + CoordinatorInstance(CoordinatorInstance &&other) noexcept = delete; + CoordinatorInstance &operator=(CoordinatorInstance &&other) noexcept = delete; + ~CoordinatorInstance() = default; + + auto UpdateInstanceStatus() -> bool { + is_alive_ = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - last_response_time_) + .count() < CoordinatorClusterConfig::alive_response_time_difference_sec_; + return is_alive_; + } + auto UpdateLastResponseTime() -> void { last_response_time_ = std::chrono::system_clock::now(); } + + auto InstanceName() const -> std::string { return client_.InstanceName(); } + auto SocketAddress() const -> std::string { return client_.SocketAddress(); } + auto IsAlive() const -> bool { return is_alive_; } + + auto IsReplica() const -> bool { + return replication_role_ == replication_coordination_glue::ReplicationRole::REPLICA; + } + auto IsMain() const -> bool { return replication_role_ == replication_coordination_glue::ReplicationRole::MAIN; } + + auto PrepareForFailover() -> void { client_.PauseFrequentCheck(); } + auto RestoreAfterFailedFailover() -> void { client_.ResumeFrequentCheck(); } + + auto PostFailover(HealthCheckCallback main_succ_cb, HealthCheckCallback main_fail_cb) -> void { + replication_role_ = replication_coordination_glue::ReplicationRole::MAIN; + client_.SetSuccCallback(std::move(main_succ_cb)); + client_.SetFailCallback(std::move(main_fail_cb)); + // Comment with Andi but we shouldn't delete this, what if this MAIN FAILS AGAIN + // client_.ResetReplicationClientInfo(); + client_.ResumeFrequentCheck(); + } + + CoordinatorClient client_; + replication_coordination_glue::ReplicationRole replication_role_; + std::chrono::system_clock::time_point last_response_time_{}; + bool is_alive_{false}; + + friend bool operator==(CoordinatorInstance const &first, CoordinatorInstance const &second) { + return first.client_ == second.client_ && first.replication_role_ == second.replication_role_; + } +}; + +} // namespace memgraph::coordination +#endif diff --git a/src/coordination/include/coordination/coordinator_instance_status.hpp b/src/coordination/include/coordination/coordinator_instance_status.hpp new file mode 100644 index 000000000..2a0a3a985 --- /dev/null +++ b/src/coordination/include/coordination/coordinator_instance_status.hpp @@ -0,0 +1,31 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#ifdef MG_ENTERPRISE + +#include "io/network/endpoint.hpp" + +#include <string_view> + +namespace memgraph::coordination { + +struct CoordinatorInstanceStatus { + std::string instance_name; + std::string socket_address; + std::string replication_role; + bool is_alive; +}; + +} // namespace memgraph::coordination + +#endif diff --git a/src/coordination/include/coordination/coordinator_rpc.hpp b/src/coordination/include/coordination/coordinator_rpc.hpp new file mode 100644 index 000000000..99996ef52 --- /dev/null +++ b/src/coordination/include/coordination/coordinator_rpc.hpp @@ -0,0 +1,104 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#ifdef MG_ENTERPRISE + +#include "coordination/coordinator_config.hpp" +#include "rpc/messages.hpp" +#include "slk/serialization.hpp" + +namespace memgraph::coordination { + +struct PromoteReplicaToMainReq { + static const utils::TypeInfo kType; + static const utils::TypeInfo &GetTypeInfo() { return kType; } + + static void Load(PromoteReplicaToMainReq *self, memgraph::slk::Reader *reader); + static void Save(const PromoteReplicaToMainReq &self, memgraph::slk::Builder *builder); + + explicit PromoteReplicaToMainReq(std::vector<CoordinatorClientConfig::ReplicationClientInfo> replication_clients_info) + : replication_clients_info(std::move(replication_clients_info)) {} + PromoteReplicaToMainReq() = default; + + std::vector<CoordinatorClientConfig::ReplicationClientInfo> replication_clients_info; +}; + +struct PromoteReplicaToMainRes { + static const utils::TypeInfo kType; + static const utils::TypeInfo &GetTypeInfo() { return kType; } + + static void Load(PromoteReplicaToMainRes *self, memgraph::slk::Reader *reader); + static void Save(const PromoteReplicaToMainRes &self, memgraph::slk::Builder *builder); + + explicit PromoteReplicaToMainRes(bool success) : success(success) {} + PromoteReplicaToMainRes() = default; + + bool success; +}; + +using PromoteReplicaToMainRpc = rpc::RequestResponse<PromoteReplicaToMainReq, PromoteReplicaToMainRes>; + +struct SetMainToReplicaReq { + static const utils::TypeInfo kType; + static const utils::TypeInfo &GetTypeInfo() { return kType; } + + static void Load(SetMainToReplicaReq *self, memgraph::slk::Reader *reader); + static void Save(const SetMainToReplicaReq &self, memgraph::slk::Builder *builder); + + explicit SetMainToReplicaReq(CoordinatorClientConfig::ReplicationClientInfo replication_client_info) + : replication_client_info(std::move(replication_client_info)) {} + + SetMainToReplicaReq() = default; + + CoordinatorClientConfig::ReplicationClientInfo replication_client_info; +}; + +struct SetMainToReplicaRes { + static const utils::TypeInfo kType; + static const utils::TypeInfo &GetTypeInfo() { return kType; } + + static void Load(SetMainToReplicaRes *self, memgraph::slk::Reader *reader); + static void Save(const SetMainToReplicaRes &self, memgraph::slk::Builder *builder); + + explicit SetMainToReplicaRes(bool success) : success(success) {} + SetMainToReplicaRes() = default; + + bool success; +}; + +using SetMainToReplicaRpc = rpc::RequestResponse<SetMainToReplicaReq, SetMainToReplicaRes>; + +} // namespace memgraph::coordination + +// SLK serialization declarations +namespace memgraph::slk { + +void Save(const memgraph::coordination::PromoteReplicaToMainRes &self, memgraph::slk::Builder *builder); + +void Load(memgraph::coordination::PromoteReplicaToMainRes *self, memgraph::slk::Reader *reader); + +void Save(const memgraph::coordination::PromoteReplicaToMainReq &self, memgraph::slk::Builder *builder); + +void Load(memgraph::coordination::PromoteReplicaToMainReq *self, memgraph::slk::Reader *reader); + +void Save(const memgraph::coordination::SetMainToReplicaRes &self, memgraph::slk::Builder *builder); + +void Load(memgraph::coordination::SetMainToReplicaRes *self, memgraph::slk::Reader *reader); + +void Save(const memgraph::coordination::SetMainToReplicaReq &self, memgraph::slk::Builder *builder); + +void Load(memgraph::coordination::SetMainToReplicaReq *self, memgraph::slk::Reader *reader); + +} // namespace memgraph::slk + +#endif diff --git a/src/coordination/include/coordination/coordinator_server.hpp b/src/coordination/include/coordination/coordinator_server.hpp new file mode 100644 index 000000000..2a261bc32 --- /dev/null +++ b/src/coordination/include/coordination/coordinator_server.hpp @@ -0,0 +1,44 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#ifdef MG_ENTERPRISE + +#include "coordination/coordinator_config.hpp" +#include "rpc/server.hpp" + +namespace memgraph::coordination { + +class CoordinatorServer { + public: + explicit CoordinatorServer(const CoordinatorServerConfig &config); + CoordinatorServer(const CoordinatorServer &) = delete; + CoordinatorServer(CoordinatorServer &&) = delete; + CoordinatorServer &operator=(const CoordinatorServer &) = delete; + CoordinatorServer &operator=(CoordinatorServer &&) = delete; + + virtual ~CoordinatorServer(); + + bool Start(); + + template <typename TRequestResponse, typename F> + void Register(F &&callback) { + rpc_server_.Register<TRequestResponse>(std::forward<F>(callback)); + } + + private: + communication::ServerContext rpc_server_context_; + rpc::Server rpc_server_; +}; + +} // namespace memgraph::coordination +#endif diff --git a/src/coordination/include/coordination/coordinator_slk.hpp b/src/coordination/include/coordination/coordinator_slk.hpp new file mode 100644 index 000000000..49834be41 --- /dev/null +++ b/src/coordination/include/coordination/coordinator_slk.hpp @@ -0,0 +1,38 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#ifdef MG_ENTERPRISE + +#include "coordination/coordinator_config.hpp" +#include "slk/serialization.hpp" +#include "slk/streams.hpp" + +namespace memgraph::slk { + +using ReplicationClientInfo = coordination::CoordinatorClientConfig::ReplicationClientInfo; + +inline void Save(const ReplicationClientInfo &obj, Builder *builder) { + Save(obj.instance_name, builder); + Save(obj.replication_mode, builder); + Save(obj.replication_ip_address, builder); + Save(obj.replication_port, builder); +} + +inline void Load(ReplicationClientInfo *obj, Reader *reader) { + Load(&obj->instance_name, reader); + Load(&obj->replication_mode, reader); + Load(&obj->replication_ip_address, reader); + Load(&obj->replication_port, reader); +} +} // namespace memgraph::slk +#endif diff --git a/src/coordination/include/coordination/coordinator_state.hpp b/src/coordination/include/coordination/coordinator_state.hpp new file mode 100644 index 000000000..9cf2d2471 --- /dev/null +++ b/src/coordination/include/coordination/coordinator_state.hpp @@ -0,0 +1,53 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#ifdef MG_ENTERPRISE + +#include "coordination/coordinator_data.hpp" +#include "coordination/coordinator_instance_status.hpp" +#include "coordination/coordinator_server.hpp" +#include "coordination/failover_status.hpp" +#include "coordination/register_main_replica_coordinator_status.hpp" + +#include <variant> + +namespace memgraph::coordination { + +class CoordinatorState { + public: + CoordinatorState(); + ~CoordinatorState() = default; + + CoordinatorState(const CoordinatorState &) = delete; + CoordinatorState &operator=(const CoordinatorState &) = delete; + + CoordinatorState(CoordinatorState &&) noexcept = delete; + CoordinatorState &operator=(CoordinatorState &&) noexcept = delete; + + [[nodiscard]] auto RegisterInstance(CoordinatorClientConfig config) -> RegisterInstanceCoordinatorStatus; + + [[nodiscard]] auto SetInstanceToMain(std::string instance_name) -> SetInstanceToMainCoordinatorStatus; + + auto ShowInstances() const -> std::vector<CoordinatorInstanceStatus>; + + // The client code must check that the server exists before calling this method. + auto GetCoordinatorServer() const -> CoordinatorServer &; + + [[nodiscard]] auto DoFailover() -> DoFailoverStatus; + + private: + std::variant<CoordinatorData, CoordinatorMainReplicaData> data_; +}; + +} // namespace memgraph::coordination +#endif diff --git a/src/replication/include/replication/role.hpp b/src/coordination/include/coordination/failover_status.hpp similarity index 70% rename from src/replication/include/replication/role.hpp rename to src/coordination/include/coordination/failover_status.hpp index 54fbab9f0..9cfa0ffe6 100644 --- a/src/replication/include/replication/role.hpp +++ b/src/coordination/include/coordination/failover_status.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -11,8 +11,11 @@ #pragma once -#include <cstdint> -namespace memgraph::replication { +#ifdef MG_ENTERPRISE -enum class ReplicationRole : uint8_t { MAIN, REPLICA }; -} +#include <cstdint> + +namespace memgraph::coordination { +enum class DoFailoverStatus : uint8_t { SUCCESS, ALL_REPLICAS_DOWN, MAIN_ALIVE, RPC_FAILED }; +} // namespace memgraph::coordination +#endif diff --git a/src/coordination/include/coordination/register_main_replica_coordinator_status.hpp b/src/coordination/include/coordination/register_main_replica_coordinator_status.hpp new file mode 100644 index 000000000..acb191bfd --- /dev/null +++ b/src/coordination/include/coordination/register_main_replica_coordinator_status.hpp @@ -0,0 +1,37 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#ifdef MG_ENTERPRISE + +#include <cstdint> + +namespace memgraph::coordination { + +enum class RegisterInstanceCoordinatorStatus : uint8_t { + NAME_EXISTS, + END_POINT_EXISTS, + COULD_NOT_BE_PERSISTED, + NOT_COORDINATOR, + RPC_FAILED, + SUCCESS +}; + +enum class SetInstanceToMainCoordinatorStatus : uint8_t { + NO_INSTANCE_WITH_NAME, + NOT_COORDINATOR, + SUCCESS, + COULD_NOT_PROMOTE_TO_MAIN, +}; + +} // namespace memgraph::coordination +#endif diff --git a/src/dbms/CMakeLists.txt b/src/dbms/CMakeLists.txt index f1df4985a..9cd94c44c 100644 --- a/src/dbms/CMakeLists.txt +++ b/src/dbms/CMakeLists.txt @@ -1,3 +1,2 @@ - -add_library(mg-dbms STATIC dbms_handler.cpp database.cpp replication_handler.cpp replication_client.cpp inmemory/replication_handlers.cpp) -target_link_libraries(mg-dbms mg-utils mg-storage-v2 mg-query) +add_library(mg-dbms STATIC dbms_handler.cpp database.cpp replication_handler.cpp coordinator_handler.cpp replication_client.cpp inmemory/replication_handlers.cpp coordinator_handlers.cpp) +target_link_libraries(mg-dbms mg-utils mg-storage-v2 mg-query mg-replication mg-coordination) diff --git a/src/dbms/constants.hpp b/src/dbms/constants.hpp index e7ea9987b..a0e9f6f22 100644 --- a/src/dbms/constants.hpp +++ b/src/dbms/constants.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -13,7 +13,8 @@ namespace memgraph::dbms { -constexpr static const char *kDefaultDB = "memgraph"; //!< Name of the default database +constexpr std::string_view kDefaultDB = "memgraph"; //!< Name of the default database +constexpr std::string_view kMultiTenantDir = "databases"; //!< Name of the multi-tenant directory #ifdef MG_EXPERIMENTAL_REPLICATION_MULTITENANCY constexpr bool allow_mt_repl = true; diff --git a/src/dbms/coordinator_handler.cpp b/src/dbms/coordinator_handler.cpp new file mode 100644 index 000000000..1c062c074 --- /dev/null +++ b/src/dbms/coordinator_handler.cpp @@ -0,0 +1,38 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include "coordination/register_main_replica_coordinator_status.hpp" +#ifdef MG_ENTERPRISE + +#include "dbms/coordinator_handler.hpp" + +#include "dbms/dbms_handler.hpp" + +namespace memgraph::dbms { + +CoordinatorHandler::CoordinatorHandler(DbmsHandler &dbms_handler) : dbms_handler_(dbms_handler) {} + +auto CoordinatorHandler::RegisterInstance(memgraph::coordination::CoordinatorClientConfig config) + -> coordination::RegisterInstanceCoordinatorStatus { + return dbms_handler_.CoordinatorState().RegisterInstance(config); +} + +auto CoordinatorHandler::SetInstanceToMain(std::string instance_name) + -> coordination::SetInstanceToMainCoordinatorStatus { + return dbms_handler_.CoordinatorState().SetInstanceToMain(std::move(instance_name)); +} + +auto CoordinatorHandler::ShowInstances() const -> std::vector<coordination::CoordinatorInstanceStatus> { + return dbms_handler_.CoordinatorState().ShowInstances(); +} +} // namespace memgraph::dbms + +#endif diff --git a/src/dbms/coordinator_handler.hpp b/src/dbms/coordinator_handler.hpp new file mode 100644 index 000000000..233532cbc --- /dev/null +++ b/src/dbms/coordinator_handler.hpp @@ -0,0 +1,47 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#ifdef MG_ENTERPRISE + +#include "utils/result.hpp" + +#include "coordination/coordinator_config.hpp" +#include "coordination/coordinator_instance_status.hpp" +#include "coordination/failover_status.hpp" +#include "coordination/register_main_replica_coordinator_status.hpp" + +#include <cstdint> +#include <optional> +#include <vector> + +namespace memgraph::dbms { + +class DbmsHandler; + +class CoordinatorHandler { + public: + explicit CoordinatorHandler(DbmsHandler &dbms_handler); + + auto RegisterInstance(coordination::CoordinatorClientConfig config) + -> coordination::RegisterInstanceCoordinatorStatus; + + auto SetInstanceToMain(std::string instance_name) -> coordination::SetInstanceToMainCoordinatorStatus; + + auto ShowInstances() const -> std::vector<coordination::CoordinatorInstanceStatus>; + + private: + DbmsHandler &dbms_handler_; +}; + +} // namespace memgraph::dbms +#endif diff --git a/src/dbms/coordinator_handlers.cpp b/src/dbms/coordinator_handlers.cpp new file mode 100644 index 000000000..5c051408e --- /dev/null +++ b/src/dbms/coordinator_handlers.cpp @@ -0,0 +1,153 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#ifdef MG_ENTERPRISE + +#include "dbms/coordinator_handlers.hpp" +#include "dbms/utils.hpp" + +#include "coordination/coordinator_exceptions.hpp" +#include "coordination/coordinator_rpc.hpp" +#include "dbms/dbms_handler.hpp" +#include "dbms/replication_client.hpp" + +#include "range/v3/view.hpp" + +namespace memgraph::dbms { + +void CoordinatorHandlers::Register(DbmsHandler &dbms_handler) { + auto &server = dbms_handler.CoordinatorState().GetCoordinatorServer(); + + server.Register<coordination::PromoteReplicaToMainRpc>( + [&dbms_handler](slk::Reader *req_reader, slk::Builder *res_builder) -> void { + spdlog::info("Received PromoteReplicaToMainRpc"); + CoordinatorHandlers::PromoteReplicaToMainHandler(dbms_handler, req_reader, res_builder); + }); + + server.Register<coordination::SetMainToReplicaRpc>( + [&dbms_handler](slk::Reader *req_reader, slk::Builder *res_builder) -> void { + spdlog::info("Received SetMainToReplicaRpc from coordinator server"); + CoordinatorHandlers::SetMainToReplicaHandler(dbms_handler, req_reader, res_builder); + }); +} + +void CoordinatorHandlers::SetMainToReplicaHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, + slk::Builder *res_builder) { + auto &repl_state = dbms_handler.ReplicationState(); + + if (!repl_state.IsMain()) { + spdlog::error("Setting to replica must be performed on main."); + slk::Save(coordination::SetMainToReplicaRes{false}, res_builder); + return; + } + + coordination::SetMainToReplicaReq req; + slk::Load(&req, req_reader); + + replication::ReplicationServerConfig clients_config{.ip_address = req.replication_client_info.replication_ip_address, + .port = req.replication_client_info.replication_port}; + + if (bool success = memgraph::dbms::SetReplicationRoleReplica(dbms_handler, clients_config); !success) { + spdlog::error("Setting main to replica failed!"); + slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder); + return; + } + + slk::Save(coordination::PromoteReplicaToMainRes{true}, res_builder); +} + +void CoordinatorHandlers::PromoteReplicaToMainHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, + slk::Builder *res_builder) { + auto &repl_state = dbms_handler.ReplicationState(); + + if (!repl_state.IsReplica()) { + spdlog::error("Failover must be performed on replica!"); + slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder); + return; + } + + auto repl_server_config = std::get<replication::RoleReplicaData>(repl_state.ReplicationData()).config; + + // This can fail because of disk. If it does, the cluster state could get inconsistent. + // We don't handle disk issues. + if (bool success = memgraph::dbms::DoReplicaToMainPromotion(dbms_handler); !success) { + spdlog::error("Promoting replica to main failed!"); + slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder); + return; + } + + coordination::PromoteReplicaToMainReq req; + slk::Load(&req, req_reader); + + auto const converter = [](const auto &repl_info_config) { + return replication::ReplicationClientConfig{ + .name = repl_info_config.instance_name, + .mode = repl_info_config.replication_mode, + .ip_address = repl_info_config.replication_ip_address, + .port = repl_info_config.replication_port, + }; + }; + + MG_ASSERT( + std::get<replication::RoleMainData>(repl_state.ReplicationData()).registered_replicas_.empty(), + "No replicas should be registered after promoting replica to main and before registering replication clients!"); + + // registering replicas + for (auto const &config : req.replication_clients_info | ranges::views::transform(converter)) { + auto instance_client = repl_state.RegisterReplica(config); + if (instance_client.HasError()) { + switch (instance_client.GetError()) { + // Can't happen, we are already replica + case memgraph::replication::RegisterReplicaError::NOT_MAIN: + spdlog::error("Failover must be performed to main!"); + slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder); + return; + // Can't happen, checked on the coordinator side + case memgraph::replication::RegisterReplicaError::NAME_EXISTS: + spdlog::error("Replica with the same name already exists!"); + slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder); + return; + // Can't happen, checked on the coordinator side + case memgraph::replication::RegisterReplicaError::ENDPOINT_EXISTS: + spdlog::error("Replica with the same endpoint already exists!"); + slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder); + return; + // We don't handle disk issues + case memgraph::replication::RegisterReplicaError::COULD_NOT_BE_PERSISTED: + spdlog::error("Registered replica could not be persisted!"); + slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder); + return; + case memgraph::replication::RegisterReplicaError::SUCCESS: + break; + } + } + if (!allow_mt_repl && dbms_handler.All().size() > 1) { + spdlog::warn("Multi-tenant replication is currently not supported!"); + } + + auto &instance_client_ref = *instance_client.GetValue(); + + // Update system before enabling individual storage <-> replica clients + dbms_handler.SystemRestore(instance_client_ref); + + // TODO: (andi) Policy for register all databases + // Will be resolved after deciding about choosing new replica + const bool all_clients_good = memgraph::dbms::RegisterAllDatabasesClients(dbms_handler, instance_client_ref); + MG_ASSERT(all_clients_good, "Failed to register one or more databases to the REPLICA \"{}\".", config.name); + + StartReplicaClient(dbms_handler, instance_client_ref); + } + + slk::Save(coordination::PromoteReplicaToMainRes{true}, res_builder); +} + +} // namespace memgraph::dbms +#endif diff --git a/src/dbms/coordinator_handlers.hpp b/src/dbms/coordinator_handlers.hpp new file mode 100644 index 000000000..ae4c59a0a --- /dev/null +++ b/src/dbms/coordinator_handlers.hpp @@ -0,0 +1,34 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#ifdef MG_ENTERPRISE + +#include "slk/serialization.hpp" + +namespace memgraph::dbms { + +class DbmsHandler; + +class CoordinatorHandlers { + public: + static void Register(DbmsHandler &dbms_handler); + + private: + static void PromoteReplicaToMainHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, + slk::Builder *res_builder); + static void SetMainToReplicaHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder); +}; + +} // namespace memgraph::dbms + +#endif diff --git a/src/dbms/database.cpp b/src/dbms/database.cpp index 74ee13892..9a56d400a 100644 --- a/src/dbms/database.cpp +++ b/src/dbms/database.cpp @@ -26,7 +26,7 @@ Database::Database(storage::Config config, replication::ReplicationState &repl_s streams_{config.durability.storage_directory / "streams"}, plan_cache_{FLAGS_query_plan_cache_max_size}, repl_state_(&repl_state) { - if (config.storage_mode == memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL || config.force_on_disk || + if (config.salient.storage_mode == memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL || config.force_on_disk || utils::DirExists(config.disk.main_storage_directory)) { storage_ = std::make_unique<storage::DiskStorage>(std::move(config)); } else { diff --git a/src/dbms/database.hpp b/src/dbms/database.hpp index 878fe7672..2d7d3fe88 100644 --- a/src/dbms/database.hpp +++ b/src/dbms/database.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -68,12 +68,12 @@ class Database { */ std::unique_ptr<storage::Storage::Accessor> Access( std::optional<storage::IsolationLevel> override_isolation_level = {}) { - return storage_->Access(override_isolation_level, repl_state_->IsMain()); + return storage_->Access(repl_state_->GetRole(), override_isolation_level); } std::unique_ptr<storage::Storage::Accessor> UniqueAccess( std::optional<storage::IsolationLevel> override_isolation_level = {}) { - return storage_->UniqueAccess(override_isolation_level, repl_state_->IsMain()); + return storage_->UniqueAccess(repl_state_->GetRole(), override_isolation_level); } /** @@ -81,7 +81,14 @@ class Database { * * @return const std::string& */ - const std::string &id() const { return storage_->id(); } + const std::string &name() const { return storage_->name(); } + + /** + * @brief Unique storage identified (uuid) + * + * @return const utils::UUID& + */ + const utils::UUID &uuid() const { return storage_->uuid(); } /** * @brief Returns the storage configuration @@ -103,9 +110,9 @@ class Database { * @param force_directory Use the configured directory, do not try to decipher the multi-db version * @return DatabaseInfo */ - DatabaseInfo GetInfo(bool force_directory = false) const { + DatabaseInfo GetInfo(bool force_directory, replication_coordination_glue::ReplicationRole replication_role) const { DatabaseInfo info; - info.storage_info = storage_->GetInfo(force_directory); + info.storage_info = storage_->GetInfo(force_directory, replication_role); info.triggers = trigger_store_.GetTriggerInfo().size(); info.streams = streams_.GetStreamInfo().size(); return info; diff --git a/src/dbms/database_handler.hpp b/src/dbms/database_handler.hpp index 617e614c3..de5f813ba 100644 --- a/src/dbms/database_handler.hpp +++ b/src/dbms/database_handler.hpp @@ -51,7 +51,7 @@ class DatabaseHandler : public Handler<Database> { * @param config Storage configuration * @return HandlerT::NewResult */ - HandlerT::NewResult New(std::string_view name, storage::Config config, replication::ReplicationState &repl_state) { + HandlerT::NewResult New(storage::Config config, replication::ReplicationState &repl_state) { // Control that no one is using the same data directory if (std::any_of(begin(), end(), [&](auto &elem) { auto db_acc = elem.second.access(); @@ -61,8 +61,7 @@ class DatabaseHandler : public Handler<Database> { spdlog::info("Tried to generate new storage using a claimed directory."); return NewError::EXISTS; } - config.name = name; // Set storage id via config - return HandlerT::New(std::piecewise_construct, name, config, repl_state); + return HandlerT::New(std::piecewise_construct, config.salient.name, config, repl_state); } /** diff --git a/src/dbms/dbms_handler.cpp b/src/dbms/dbms_handler.cpp index 0af9364bf..7222c4461 100644 --- a/src/dbms/dbms_handler.cpp +++ b/src/dbms/dbms_handler.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -11,56 +11,205 @@ #include "dbms/dbms_handler.hpp" +#include "dbms/coordinator_handlers.hpp" +#include "flags/replication.hpp" + +#include <cstdint> +#include <filesystem> + +#include "dbms/constants.hpp" +#include "dbms/global.hpp" +#include "dbms/replication_client.hpp" +#include "spdlog/spdlog.h" +#include "utils/exceptions.hpp" +#include "utils/logging.hpp" +#include "utils/uuid.hpp" + namespace memgraph::dbms { + #ifdef MG_ENTERPRISE + +namespace { +constexpr std::string_view kDBPrefix = "database:"; // Key prefix for database durability +constexpr std::string_view kLastCommitedSystemTsKey = "last_commited_system_ts"; // Key for timestamp durability +} // namespace + +struct Durability { + enum class DurabilityVersion : uint8_t { + V0 = 0, + V1, + }; + + struct VersionException : public utils::BasicException { + VersionException() : utils::BasicException("Unsupported durability version!") {} + }; + + struct UnknownVersionException : public utils::BasicException { + UnknownVersionException() : utils::BasicException("Unable to parse the durability version!") {} + }; + + struct MigrationException : public utils::BasicException { + MigrationException() : utils::BasicException("Failed to migrate to the current durability version!") {} + }; + + static DurabilityVersion VersionCheck(std::optional<std::string_view> val) { + if (!val) { + return DurabilityVersion::V0; + } + if (val == "V1") { + return DurabilityVersion::V1; + } + throw UnknownVersionException(); + }; + + static auto GenKey(std::string_view name) -> std::string { return fmt::format("{}{}", kDBPrefix, name); } + + static auto GenVal(utils::UUID uuid, std::filesystem::path rel_dir) { + nlohmann::json json; + json["uuid"] = uuid; + json["rel_dir"] = rel_dir; + // TODO: Serialize the configuration + return json.dump(); + } + + static void Migrate(kvstore::KVStore *durability, const std::filesystem::path &root) { + const auto ver_val = durability->Get("version"); + const auto ver = VersionCheck(ver_val); + + std::map<std::string, std::string> to_put; + std::vector<std::string> to_delete; + + // Update from V0 to V1 + if (ver == DurabilityVersion::V0) { + for (const auto &[key, val] : *durability) { + if (key == "version") continue; // Reserved key + // Generate a UUID + auto const uuid = utils::UUID(); + // New json values + auto new_key = GenKey(key); + auto path = root; + if (key != kDefaultDB) { // Special case for non-default DBs + // Move directory to new UUID dir + path = root / kMultiTenantDir / std::string{uuid}; + std::filesystem::path old_dir(root / kMultiTenantDir / key); + std::error_code ec; + std::filesystem::rename(old_dir, path, ec); + MG_ASSERT(!ec, "Failed to upgrade durability: cannot move default directory."); + } + // Generate json and update value + auto new_data = GenVal(uuid, std::filesystem::relative(path, root)); + to_put.emplace(std::move(new_key), std::move(new_data)); + to_delete.emplace_back(key); + } + } + + // Set version + durability->Put("version", "V1"); + // Update to the new key-value pairs + if (!durability->PutAndDeleteMultiple(to_put, to_delete)) { + throw MigrationException(); + } + } +}; + DbmsHandler::DbmsHandler( storage::Config config, memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth, - bool recovery_on_startup, bool delete_on_drop) - : default_config_{std::move(config)}, - delete_on_drop_(delete_on_drop), - repl_state_{ReplicationStateRootPath(default_config_)} { + bool recovery_on_startup) + : default_config_{std::move(config)}, repl_state_{ReplicationStateRootPath(default_config_)} { // TODO: Decouple storage config from dbms config // TODO: Save individual db configs inside the kvstore and restore from there - storage::UpdatePaths(default_config_, default_config_.durability.storage_directory / "databases"); - const auto &db_dir = default_config_.durability.storage_directory; + + /* + * FILESYSTEM MANIPULATION + */ + const auto &root = default_config_.durability.storage_directory; + storage::UpdatePaths(default_config_, root); + const auto &db_dir = default_config_.durability.storage_directory / kMultiTenantDir; + // TODO: Unify durability and wal const auto durability_dir = db_dir / ".durability"; utils::EnsureDirOrDie(db_dir); utils::EnsureDirOrDie(durability_dir); durability_ = std::make_unique<kvstore::KVStore>(durability_dir); - // Generate the default database - MG_ASSERT(!NewDefault_().HasError(), "Failed while creating the default DB."); + /* + * DURABILITY + */ + // Migrate durability + Durability::Migrate(durability_.get(), root); + auto directories = std::set{std::string{kDefaultDB}}; // Recover previous databases if (recovery_on_startup) { - for (const auto &[name, _] : *durability_) { - if (name == kDefaultDB) continue; // Already set - spdlog::info("Restoring database {}.", name); - MG_ASSERT(!New_(name).HasError(), "Failed while creating database {}.", name); + auto it = durability_->begin(std::string(kDBPrefix)); + auto end = durability_->end(std::string(kDBPrefix)); + for (; it != end; ++it) { + const auto &[key, config_json] = *it; + const auto name = key.substr(kDBPrefix.size()); + auto json = nlohmann::json::parse(config_json); + const auto uuid = json.at("uuid").get<utils::UUID>(); + const auto rel_dir = json.at("rel_dir").get<std::filesystem::path>(); + spdlog::info("Restoring database {} at {}.", name, rel_dir); + auto new_db = New_(name, uuid, rel_dir); + MG_ASSERT(!new_db.HasError(), "Failed while creating database {}.", name); + directories.emplace(rel_dir.filename()); spdlog::info("Database {} restored.", name); } + // Read the last timestamp + auto lcst = durability_->Get(kLastCommitedSystemTsKey); + if (lcst) { + last_commited_system_timestamp_ = std::stoul(*lcst); + system_timestamp_ = last_commited_system_timestamp_; + } } else { // Clear databases from the durability list and auth auto locked_auth = auth->Lock(); - for (const auto &[name, _] : *durability_) { + auto it = durability_->begin(std::string{kDBPrefix}); + auto end = durability_->end(std::string{kDBPrefix}); + for (; it != end; ++it) { + const auto &[key, _] = *it; + const auto name = key.substr(kDBPrefix.size()); if (name == kDefaultDB) continue; locked_auth->DeleteDatabase(name); - durability_->Delete(name); + durability_->Delete(key); + } + // Delete the last timestamp + durability_->Delete(kLastCommitedSystemTsKey); + } + + /* + * DATABASES CLEAN UP + */ + // Clean the unused directories + for (const auto &entry : std::filesystem::directory_iterator(db_dir)) { + const auto &name = entry.path().filename().string(); + if (entry.is_directory() && !name.empty() && name.front() != '.') { + auto itr = directories.find(name); + if (itr == directories.end()) { + std::error_code dummy; + std::filesystem::remove_all(entry, dummy); + } else { + directories.erase(itr); + } } } + /* + * DEFAULT DB SETUP + */ + // Setup the default DB + SetupDefault_(); + + /* + * REPLICATION RECOVERY AND STARTUP + */ // Startup replication state (if recovered at startup) - auto replica = [this](replication::RoleReplicaData const &data) { - // Register handlers - InMemoryReplicationHandlers::Register(this, *data.server); - if (!data.server->Start()) { - spdlog::error("Unable to start the replication server."); - return false; - } - return true; - }; - // Replication frequent check start + auto replica = [this](replication::RoleReplicaData const &data) { return StartRpcServer(*this, data); }; + // Replication recovery and frequent check start auto main = [this](replication::RoleMainData &data) { + for (auto &client : data.registered_replicas_) { + SystemRestore(client); + } + ForEach([this](DatabaseAccess db) { RecoverReplication(db); }); for (auto &client : data.registered_replicas_) { StartReplicaClient(*this, client); } @@ -69,7 +218,232 @@ DbmsHandler::DbmsHandler( // Startup proccess for main/replica MG_ASSERT(std::visit(memgraph::utils::Overloaded{replica, main}, repl_state_.ReplicationData()), "Replica recovery failure!"); -} -#endif + // Warning + if (default_config_.durability.snapshot_wal_mode == storage::Config::Durability::SnapshotWalMode::DISABLED && + repl_state_.IsMain()) { + spdlog::warn( + "The instance has the MAIN replication role, but durability logs and snapshots are disabled. Please " + "consider " + "enabling durability by using --storage-snapshot-interval-sec and --storage-wal-enabled flags because " + "without write-ahead logs this instance is not replicating any data."); + } + + // MAIN or REPLICA instance + if (FLAGS_coordinator_server_port) { + CoordinatorHandlers::Register(*this); + MG_ASSERT(coordinator_state_.GetCoordinatorServer().Start(), "Failed to start coordinator server!"); + } +} + +DbmsHandler::DeleteResult DbmsHandler::TryDelete(std::string_view db_name) { + std::lock_guard<LockT> wr(lock_); + if (db_name == kDefaultDB) { + // MSG cannot delete the default db + return DeleteError::DEFAULT_DB; + } + + // Get DB config for the UUID and disk clean up + const auto conf = db_handler_.GetConfig(db_name); + if (!conf) { + return DeleteError::NON_EXISTENT; + } + const auto &storage_path = conf->durability.storage_directory; + const auto &uuid = conf->salient.uuid; + + // Check if db exists + try { + // Low level handlers + if (!db_handler_.TryDelete(db_name)) { + return DeleteError::USING; + } + } catch (utils::BasicException &) { + return DeleteError::NON_EXISTENT; + } + + // Remove from durability list + if (durability_) durability_->Delete(Durability::GenKey(db_name)); + + // Delete disk storage + std::error_code ec; + (void)std::filesystem::remove_all(storage_path, ec); + if (ec) { + spdlog::error(R"(Failed to clean disk while deleting database "{}" stored in {})", db_name, storage_path); + } + + // Success + // Save delta + if (system_transaction_) { + system_transaction_->delta.emplace(SystemTransaction::Delta::drop_database, uuid); + } + return {}; +} + +DbmsHandler::DeleteResult DbmsHandler::Delete(std::string_view db_name) { + auto wr = std::lock_guard(lock_); + return Delete_(db_name); +} + +DbmsHandler::DeleteResult DbmsHandler::Delete(utils::UUID uuid) { + auto wr = std::lock_guard(lock_); + std::string db_name; + try { + const auto db = Get_(uuid); + db_name = db->name(); + } catch (const UnknownDatabaseException &) { + return DeleteError::NON_EXISTENT; + } + return Delete_(db_name); +} + +DbmsHandler::NewResultT DbmsHandler::New_(storage::Config storage_config) { + auto new_db = db_handler_.New(storage_config, repl_state_); + + if (new_db.HasValue()) { // Success + // Save delta + if (system_transaction_) { + system_transaction_->delta.emplace(SystemTransaction::Delta::create_database, storage_config.salient); + } + UpdateDurability(storage_config); + return new_db.GetValue(); + } + return new_db.GetError(); +} + +DbmsHandler::DeleteResult DbmsHandler::Delete_(std::string_view db_name) { + if (db_name == kDefaultDB) { + // MSG cannot delete the default db + return DeleteError::DEFAULT_DB; + } + + const auto storage_path = StorageDir_(db_name); + if (!storage_path) return DeleteError::NON_EXISTENT; + + { + auto db = db_handler_.Get(db_name); + if (!db) return DeleteError::NON_EXISTENT; + // TODO: ATM we assume REPLICA won't have streams, + // this is a best effort approach just in case they do + // there is still subtle data race we stream manipulation + // can occur while we are dropping the database + db->prepare_for_deletion(); + auto &database = *db->get(); + database.streams()->StopAll(); + database.streams()->DropAll(); + database.thread_pool()->Shutdown(); + } + + // Remove from durability list + if (durability_) durability_->Delete(Durability::GenKey(db_name)); + + // Check if db exists + // Low level handlers + db_handler_.DeferDelete(db_name, [storage_path = *storage_path, db_name = std::string{db_name}]() { + // Delete disk storage + std::error_code ec; + (void)std::filesystem::remove_all(storage_path, ec); + if (ec) { + spdlog::error(R"(Failed to clean disk while deleting database "{}" stored in {})", db_name, storage_path); + } + }); + + return {}; // Success +} + +void DbmsHandler::UpdateDurability(const storage::Config &config, std::optional<std::filesystem::path> rel_dir) { + if (!durability_) return; + // Save database in a list of active databases + const auto &key = Durability::GenKey(config.salient.name); + if (rel_dir == std::nullopt) + rel_dir = + std::filesystem::relative(config.durability.storage_directory, default_config_.durability.storage_directory); + const auto &val = Durability::GenVal(config.salient.uuid, *rel_dir); + durability_->Put(key, val); +} + +AllSyncReplicaStatus DbmsHandler::Commit() { + if (system_transaction_ == std::nullopt || system_transaction_->delta == std::nullopt) + return AllSyncReplicaStatus::AllCommitsConfirmed; // Nothing to commit + const auto &delta = *system_transaction_->delta; + + auto sync_status = AllSyncReplicaStatus::AllCommitsConfirmed; + // TODO Create a system client that can handle all of this automatically + switch (delta.action) { + using enum SystemTransaction::Delta::Action; + case CREATE_DATABASE: { + // Replication + auto main_handler = [&](memgraph::replication::RoleMainData &main_data) { + // TODO: data race issue? registered_replicas_ access not protected + // This is sync in any case, as this is the startup + for (auto &client : main_data.registered_replicas_) { + bool completed = SteamAndFinalizeDelta<storage::replication::CreateDatabaseRpc>( + client, + [](const storage::replication::CreateDatabaseRes &response) { + return response.result != storage::replication::CreateDatabaseRes::Result::FAILURE; + }, + std::string(main_data.epoch_.id()), last_commited_system_timestamp_, + system_transaction_->system_timestamp, delta.config); + // TODO: reduce duplicate code + if (!completed && client.mode_ == replication_coordination_glue::ReplicationMode::SYNC) { + sync_status = AllSyncReplicaStatus::SomeCommitsUnconfirmed; + } + } + // Sync database with REPLICAs + RecoverReplication(Get_(delta.config.name)); + }; + auto replica_handler = [](memgraph::replication::RoleReplicaData &) { /* Nothing to do */ }; + std::visit(utils::Overloaded{main_handler, replica_handler}, repl_state_.ReplicationData()); + } break; + case DROP_DATABASE: { + // Replication + auto main_handler = [&](memgraph::replication::RoleMainData &main_data) { + // TODO: data race issue? registered_replicas_ access not protected + // This is sync in any case, as this is the startup + for (auto &client : main_data.registered_replicas_) { + bool completed = SteamAndFinalizeDelta<storage::replication::DropDatabaseRpc>( + client, + [](const storage::replication::DropDatabaseRes &response) { + return response.result != storage::replication::DropDatabaseRes::Result::FAILURE; + }, + std::string(main_data.epoch_.id()), last_commited_system_timestamp_, + system_transaction_->system_timestamp, delta.uuid); + // TODO: reduce duplicate code + if (!completed && client.mode_ == replication_coordination_glue::ReplicationMode::SYNC) { + sync_status = AllSyncReplicaStatus::SomeCommitsUnconfirmed; + } + } + }; + auto replica_handler = [](memgraph::replication::RoleReplicaData &) { /* Nothing to do */ }; + std::visit(utils::Overloaded{main_handler, replica_handler}, repl_state_.ReplicationData()); + } break; + } + + durability_->Put(kLastCommitedSystemTsKey, std::to_string(system_transaction_->system_timestamp)); + last_commited_system_timestamp_ = system_transaction_->system_timestamp; + ResetSystemTransaction(); + return sync_status; +} + +#else // not MG_ENTERPRISE + +AllSyncReplicaStatus DbmsHandler::Commit() { + if (system_transaction_ == std::nullopt || system_transaction_->delta == std::nullopt) { + return AllSyncReplicaStatus::AllCommitsConfirmed; // Nothing to commit + } + const auto &delta = *system_transaction_->delta; + + switch (delta.action) { + using enum SystemTransaction::Delta::Action; + case CREATE_DATABASE: + case DROP_DATABASE: + /* Community edition doesn't support multi-tenant replication */ + break; + } + + last_commited_system_timestamp_ = system_transaction_->system_timestamp; + ResetSystemTransaction(); + return AllSyncReplicaStatus::AllCommitsConfirmed; +} + +#endif } // namespace memgraph::dbms diff --git a/src/dbms/dbms_handler.hpp b/src/dbms/dbms_handler.hpp index 3151398ab..2066321e2 100644 --- a/src/dbms/dbms_handler.hpp +++ b/src/dbms/dbms_handler.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -12,34 +12,37 @@ #pragma once #include <algorithm> -#include <concepts> +#include <atomic> #include <cstdint> #include <filesystem> #include <memory> #include <mutex> #include <optional> -#include <ostream> -#include <stdexcept> #include <system_error> -#include <unordered_map> +#include <utility> #include "auth/auth.hpp" #include "constants.hpp" #include "dbms/database.hpp" #include "dbms/inmemory/replication_handlers.hpp" +#include "dbms/replication_handler.hpp" +#include "kvstore/kvstore.hpp" +#include "replication/replication_client.hpp" +#include "storage/v2/config.hpp" +#include "storage/v2/replication/enums.hpp" +#include "storage/v2/replication/rpc.hpp" +#include "storage/v2/transaction.hpp" +#include "utils/thread_pool.hpp" #ifdef MG_ENTERPRISE +#include "coordination/coordinator_state.hpp" #include "dbms/database_handler.hpp" #endif -#include "dbms/replication_client.hpp" +#include "dbms/transaction.hpp" #include "global.hpp" #include "query/config.hpp" #include "query/interpreter_context.hpp" #include "spdlog/spdlog.h" -#include "storage/v2/durability/durability.hpp" -#include "storage/v2/durability/paths.hpp" #include "storage/v2/isolation_level.hpp" -#include "utils/exceptions.hpp" -#include "utils/file.hpp" #include "utils/logging.hpp" #include "utils/result.hpp" #include "utils/rw_lock.hpp" @@ -48,6 +51,11 @@ namespace memgraph::dbms { +enum class AllSyncReplicaStatus { + AllCommitsConfirmed, + SomeCommitsUnconfirmed, +}; + struct Statistics { uint64_t num_vertex; //!< Sum of vertexes in every database uint64_t num_edges; //!< Sum of edges in every database @@ -102,11 +110,10 @@ class DbmsHandler { * @param configs storage configuration * @param auth pointer to the global authenticator * @param recovery_on_startup restore databases (and its content) and authentication data - * @param delete_on_drop when dropping delete any associated directories on disk */ DbmsHandler(storage::Config config, memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth, - bool recovery_on_startup, bool delete_on_drop); // TODO If more arguments are added use a config strut + bool recovery_on_startup); // TODO If more arguments are added use a config struct #else /** * @brief Initialize the handler. A single database is supported in community edition. @@ -116,10 +123,12 @@ class DbmsHandler { DbmsHandler(storage::Config config) : repl_state_{ReplicationStateRootPath(config)}, db_gatekeeper_{[&] { - config.name = kDefaultDB; + config.salient.name = kDefaultDB; return std::move(config); }(), - repl_state_} {} + repl_state_} { + RecoverReplication(Get()); + } #endif #ifdef MG_ENTERPRISE @@ -131,9 +140,56 @@ class DbmsHandler { */ NewResultT New(const std::string &name) { std::lock_guard<LockT> wr(lock_); - return New_(name, name); + const auto uuid = utils::UUID{}; + return New_(name, uuid); } + /** + * @brief Create new if name/uuid do not match any database. Drop and recreate if database already present. + * @note Default database is not dropped, only its UUID is updated and only if the database is clean. + * + * @param config desired salient config + * @return NewResultT context on success, error on failure + */ + NewResultT Update(const storage::SalientConfig &config) { + std::lock_guard<LockT> wr(lock_); + auto new_db = New_(config); + if (new_db.HasValue() || new_db.GetError() != NewError::EXISTS) { + // NOTE: If db already exists we retry below + return new_db; + } + + spdlog::debug("Trying to create db '{}' on replica which already exists.", config.name); + + auto db = Get_(config.name); + if (db->uuid() == config.uuid) { // Same db + return db; + } + + spdlog::debug("Different UUIDs"); + + // TODO: Fix this hack + if (config.name == kDefaultDB) { + if (db->storage()->repl_storage_state_.last_commit_timestamp_ != storage::kTimestampInitialId) { + spdlog::debug("Default storage is not clean, cannot update UUID..."); + return NewError::GENERIC; // Update error + } + spdlog::debug("Update default db's UUID"); + // Default db cannot be deleted and remade, have to just update the UUID + db->storage()->config_.salient.uuid = config.uuid; + UpdateDurability(db->storage()->config_, "."); + return db; + } + + spdlog::debug("Drop database and recreate with the correct UUID"); + // Defer drop + (void)Delete_(db->name()); + // Second attempt + return New_(config); + } + + void UpdateDurability(const storage::Config &config, std::optional<std::filesystem::path> rel_dir = {}); + /** * @brief Get the context associated with the "name" database * @@ -145,6 +201,19 @@ class DbmsHandler { std::shared_lock<LockT> rd(lock_); return Get_(name); } + + /** + * @brief Get the context associated with the UUID database + * + * @param uuid + * @return DatabaseAccess + * @throw UnknownDatabaseException if database not found + */ + DatabaseAccess Get(const utils::UUID &uuid) { + std::shared_lock<LockT> rd(lock_); + return Get_(uuid); + } + #else /** * @brief Get the context associated with the default database @@ -160,50 +229,28 @@ class DbmsHandler { #ifdef MG_ENTERPRISE /** - * @brief Delete database. + * @brief Attempt to delete database. * * @param db_name database name * @return DeleteResult error on failure */ - DeleteResult Delete(const std::string &db_name) { - std::lock_guard<LockT> wr(lock_); - if (db_name == kDefaultDB) { - // MSG cannot delete the default db - return DeleteError::DEFAULT_DB; - } + DeleteResult TryDelete(std::string_view db_name); - const auto storage_path = StorageDir_(db_name); - if (!storage_path) return DeleteError::NON_EXISTENT; + /** + * @brief Delete or defer deletion of database. + * + * @param db_name database name + * @return DeleteResult error on failure + */ + DeleteResult Delete(std::string_view db_name); - // Check if db exists - try { - // Low level handlers - if (!db_handler_.Delete(db_name)) { - return DeleteError::USING; - } - } catch (utils::BasicException &) { - return DeleteError::NON_EXISTENT; - } - - // Remove from durability list - if (durability_) durability_->Delete(db_name); - - // Delete disk storage - if (delete_on_drop_) { - std::error_code ec; - (void)std::filesystem::remove_all(*storage_path, ec); - if (ec) { - spdlog::error("Failed to clean disk while deleting database \"{}\".", db_name); - defunct_dbs_.emplace(db_name); - return DeleteError::DISK_FAIL; - } - } - - // Delete from defunct_dbs_ (in case a second delete call was successful) - defunct_dbs_.erase(db_name); - - return {}; // Success - } + /** + * @brief Delete or defer deletion of database. + * + * @param uuid database UUID + * @return DeleteResult error on failure + */ + DeleteResult Delete(utils::UUID uuid); #endif /** @@ -216,7 +263,7 @@ class DbmsHandler { std::shared_lock<LockT> rd(lock_); return db_handler_.All(); #else - return {db_gatekeeper_.access()->get()->id()}; + return {db_gatekeeper_.access()->get()->name()}; #endif } @@ -226,12 +273,17 @@ class DbmsHandler { bool IsMain() const { return repl_state_.IsMain(); } bool IsReplica() const { return repl_state_.IsReplica(); } +#ifdef MG_ENTERPRISE + coordination::CoordinatorState &CoordinatorState() { return coordinator_state_; } +#endif + /** * @brief Return the statistics all databases. * * @return Statistics */ Statistics Stats() { + auto const replication_role = repl_state_.GetRole(); Statistics stats{}; // TODO: Handle overflow? #ifdef MG_ENTERPRISE @@ -244,7 +296,7 @@ class DbmsHandler { auto db_acc_opt = db_gk.access(); if (db_acc_opt) { auto &db_acc = *db_acc_opt; - const auto &info = db_acc->GetInfo(); + const auto &info = db_acc->GetInfo(false, replication_role); const auto &storage_info = info.storage_info; stats.num_vertex += storage_info.vertex_count; stats.num_edges += storage_info.edge_count; @@ -268,6 +320,7 @@ class DbmsHandler { * @return std::vector<DatabaseInfo> */ std::vector<DatabaseInfo> Info() { + auto const replication_role = repl_state_.GetRole(); std::vector<DatabaseInfo> res; #ifdef MG_ENTERPRISE std::shared_lock<LockT> rd(lock_); @@ -280,7 +333,7 @@ class DbmsHandler { auto db_acc_opt = db_gk.access(); if (db_acc_opt) { auto &db_acc = *db_acc_opt; - res.push_back(db_acc->GetInfo()); + res.push_back(db_acc->GetInfo(false, replication_role)); } } return res; @@ -303,7 +356,7 @@ class DbmsHandler { auto db_acc_opt = db_gk.access(); if (db_acc_opt) { auto &db_acc = *db_acc_opt; - spdlog::debug("Restoring trigger for database \"{}\"", db_acc->id()); + spdlog::debug("Restoring trigger for database \"{}\"", db_acc->name()); auto storage_accessor = db_acc->Access(); auto dba = memgraph::query::DbAccessor{storage_accessor.get()}; db_acc->trigger_store()->RestoreTriggers(&ic->ast_cache, &dba, ic->config.query, ic->auth_checker); @@ -328,7 +381,7 @@ class DbmsHandler { auto db_acc = db_gk.access(); if (db_acc) { auto *db = db_acc->get(); - spdlog::debug("Restoring streams for database \"{}\"", db->id()); + spdlog::debug("Restoring streams for database \"{}\"", db->name()); db->streams()->RestoreStreams(*db_acc, ic); } } @@ -339,7 +392,7 @@ class DbmsHandler { * * @param f */ - void ForEach(auto f) { + void ForEach(std::invocable<DatabaseAccess> auto f) { #ifdef MG_ENTERPRISE std::shared_lock<LockT> rd(lock_); for (auto &[_, db_gk] : db_handler_) { @@ -349,33 +402,103 @@ class DbmsHandler { #endif auto db_acc = db_gk.access(); if (db_acc) { // This isn't an error, just a defunct db - f(db_acc->get()); + f(*db_acc); } } } - /** - * @brief todo - * - * @param f - */ - void ForOne(auto f) { + void NewSystemTransaction() { + DMG_ASSERT(!system_transaction_, "Already running a system transaction"); + system_transaction_.emplace(++system_timestamp_); + } + + void ResetSystemTransaction() { system_transaction_.reset(); } + + //! \tparam RPC An rpc::RequestResponse + //! \tparam Args the args type + //! \param client the client to use for rpc communication + //! \param check predicate to check response is ok + //! \param args arguments to forward to the rpc request + //! \return If replica stream is completed or enqueued + template <typename RPC, typename... Args> + bool SteamAndFinalizeDelta(auto &client, auto &&check, Args &&...args) { + try { + auto stream = client.rpc_client_.template Stream<RPC>(std::forward<Args>(args)...); + auto task = [&client, check = std::forward<decltype(check)>(check), stream = std::move(stream)]() mutable { + if (stream.IsDefunct()) { + client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; }); + return false; + } + try { + if (check(stream.AwaitResponse())) { + return true; + } + } catch (memgraph::rpc::GenericRpcFailedException const &e) { + // swallow error, fallthrough to error handling + } + // This replica needs SYSTEM recovery + client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; }); + return false; + }; + + if (client.mode_ == memgraph::replication_coordination_glue::ReplicationMode::ASYNC) { + client.thread_pool_.AddTask([task = utils::CopyMovableFunctionWrapper{std::move(task)}]() mutable { task(); }); + return true; + } + + return task(); + } catch (memgraph::rpc::GenericRpcFailedException const &e) { + // This replica needs SYSTEM recovery + client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; }); + return false; + } + }; + + AllSyncReplicaStatus Commit(); + + auto LastCommitedTS() const -> uint64_t { return last_commited_system_timestamp_; } + void SetLastCommitedTS(uint64_t new_ts) { last_commited_system_timestamp_.store(new_ts); } + #ifdef MG_ENTERPRISE - std::shared_lock<LockT> rd(lock_); - for (auto &[_, db_gk] : db_handler_) { - auto db_acc = db_gk.access(); - if (db_acc) { // This isn't an error, just a defunct db - if (f(db_acc->get())) break; // Run until the first successful one + // When being called by intepreter no need to gain lock, it should already be under a system transaction + // But concurrently the FrequentCheck is running and will need to lock before reading last_commited_system_timestamp_ + template <bool REQUIRE_LOCK = false> + void SystemRestore(replication::ReplicationClient &client) { + // Check if system is up to date + if (client.state_.WithLock( + [](auto &state) { return state == memgraph::replication::ReplicationClient::State::READY; })) + return; + + // Try to recover... + { + auto [database_configs, last_commited_system_timestamp] = std::invoke([&] { + auto sys_guard = + std::unique_lock{system_lock_, std::defer_lock}; // ensure no other system transaction in progress + if constexpr (REQUIRE_LOCK) { + sys_guard.lock(); + } + auto configs = std::vector<storage::SalientConfig>{}; + ForEach([&configs](DatabaseAccess acc) { configs.emplace_back(acc->config().salient); }); + return std::pair{configs, last_commited_system_timestamp_.load()}; + }); + try { + auto stream = client.rpc_client_.Stream<storage::replication::SystemRecoveryRpc>(last_commited_system_timestamp, + std::move(database_configs)); + const auto response = stream.AwaitResponse(); + if (response.result == storage::replication::SystemRecoveryRes::Result::FAILURE) { + client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; }); + return; + } + } catch (memgraph::rpc::GenericRpcFailedException const &e) { + client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; }); + return; } } -#else - { - auto db_acc = db_gatekeeper_.access(); - MG_ASSERT(db_acc, "Should always have the database"); - f(db_acc->get()); - } -#endif + + // Successfully recovered + client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::READY; }); } +#endif private: #ifdef MG_ENTERPRISE @@ -385,7 +508,7 @@ class DbmsHandler { * @param name Database name * @return std::optional<std::filesystem::path> */ - std::optional<std::filesystem::path> StorageDir_(const std::string &name) { + std::optional<std::filesystem::path> StorageDir_(std::string_view name) { const auto conf = db_handler_.GetConfig(name); if (conf) { return conf->durability.storage_directory; @@ -398,105 +521,108 @@ class DbmsHandler { * @brief Create a new Database associated with the "name" database * * @param name name of the database + * @param uuid undelying RocksDB directory * @return NewResultT context on success, error on failure */ - NewResultT New_(const std::string &name) { return New_(name, name); } + NewResultT New_(std::string_view name, utils::UUID uuid, std::optional<std::filesystem::path> rel_dir = {}) { + auto config_copy = default_config_; + config_copy.salient.name = name; + config_copy.salient.uuid = uuid; + spdlog::debug("Creating database '{}' - '{}'", name, std::string{uuid}); + if (rel_dir) { + storage::UpdatePaths(config_copy, default_config_.durability.storage_directory / *rel_dir); + } else { + storage::UpdatePaths(config_copy, + default_config_.durability.storage_directory / kMultiTenantDir / std::string{uuid}); + } + return New_(std::move(config_copy)); + } /** - * @brief Create a new Database associated with the "name" database + * @brief Create a new Database using the passed configuration * - * @param name name of the database - * @param storage_subdir undelying RocksDB directory + * @param config configuration to be used * @return NewResultT context on success, error on failure */ - NewResultT New_(const std::string &name, std::filesystem::path storage_subdir) { + NewResultT New_(const storage::SalientConfig &config) { auto config_copy = default_config_; - storage::UpdatePaths(config_copy, default_config_.durability.storage_directory / storage_subdir); - return New_(name, config_copy); + config_copy.salient = config; // name, uuid, mode, etc + UpdatePaths(config_copy, config_copy.durability.storage_directory / kMultiTenantDir / std::string{config.uuid}); + return New_(std::move(config_copy)); } /** * @brief Create a new Database associated with the "name" database * - * @param name name of the database * @param storage_config storage configuration * @return NewResultT context on success, error on failure */ - NewResultT New_(const std::string &name, storage::Config &storage_config) { - if (defunct_dbs_.contains(name)) { - spdlog::warn("Failed to generate database due to the unknown state of the previously defunct database \"{}\".", - name); - return NewError::DEFUNCT; - } + NewResultT New_(storage::Config storage_config); - auto new_db = db_handler_.New(name, storage_config, repl_state_); - if (new_db.HasValue()) { - // Success - if (durability_) durability_->Put(name, "ok"); // TODO: Serialize the configuration? - return new_db.GetValue(); - } - return new_db.GetError(); - } + // TODO: new overload of Delete_ with DatabaseAccess + DeleteResult Delete_(std::string_view db_name); /** * @brief Create a new Database associated with the default database * * @return NewResultT context on success, error on failure */ - NewResultT NewDefault_() { - // Create the default DB in the root (this is how it was done pre multi-tenancy) - auto res = New_(kDefaultDB, ".."); - if (res.HasValue()) { - // For back-compatibility... - // Recreate the dbms layout for the default db and symlink to the root - const auto dir = StorageDir_(kDefaultDB); - MG_ASSERT(dir, "Failed to find storage path."); - const auto main_dir = *dir / "databases" / kDefaultDB; + void SetupDefault_() { + try { + Get(kDefaultDB); + } catch (const UnknownDatabaseException &) { + // No default DB restored, create it + MG_ASSERT(New_(kDefaultDB, {/* random UUID */}, ".").HasValue(), "Failed while creating the default database"); + } - if (!std::filesystem::exists(main_dir)) { - std::filesystem::create_directory(main_dir); - } + // For back-compatibility... + // Recreate the dbms layout for the default db and symlink to the root + const auto dir = StorageDir_(kDefaultDB); + MG_ASSERT(dir, "Failed to find storage path."); + const auto main_dir = *dir / kMultiTenantDir / kDefaultDB; - // Force link on-disk directories - const auto conf = db_handler_.GetConfig(kDefaultDB); - MG_ASSERT(conf, "No configuration for the default database."); - const auto &tmp_conf = conf->disk; - std::vector<std::filesystem::path> to_link{ - tmp_conf.main_storage_directory, tmp_conf.label_index_directory, - tmp_conf.label_property_index_directory, tmp_conf.unique_constraints_directory, - tmp_conf.name_id_mapper_directory, tmp_conf.id_name_mapper_directory, - tmp_conf.durability_directory, tmp_conf.wal_directory, - }; + if (!std::filesystem::exists(main_dir)) { + std::filesystem::create_directory(main_dir); + } - // Add in-memory paths - // Some directories are redundant (skip those) - const std::vector<std::string> skip{".lock", "audit_log", "auth", "databases", "internal_modules", "settings"}; - for (auto const &item : std::filesystem::directory_iterator{*dir}) { - const auto dir_name = std::filesystem::relative(item.path(), item.path().parent_path()); - if (std::find(skip.begin(), skip.end(), dir_name) != skip.end()) continue; - to_link.push_back(item.path()); - } + // Force link on-disk directories + const auto conf = db_handler_.GetConfig(kDefaultDB); + MG_ASSERT(conf, "No configuration for the default database."); + const auto &tmp_conf = conf->disk; + std::vector<std::filesystem::path> to_link{ + tmp_conf.main_storage_directory, tmp_conf.label_index_directory, + tmp_conf.label_property_index_directory, tmp_conf.unique_constraints_directory, + tmp_conf.name_id_mapper_directory, tmp_conf.id_name_mapper_directory, + tmp_conf.durability_directory, tmp_conf.wal_directory, + }; - // Symlink to root dir - for (auto const &item : to_link) { - const auto dir_name = std::filesystem::relative(item, item.parent_path()); - const auto link = main_dir / dir_name; - const auto to = std::filesystem::relative(item, main_dir); - if (!std::filesystem::is_symlink(link) && !std::filesystem::exists(link)) { - std::filesystem::create_directory_symlink(to, link); - } else { // Check existing link - std::error_code ec; - const auto test_link = std::filesystem::read_symlink(link, ec); - if (ec || test_link != to) { - MG_ASSERT(false, - "Memgraph storage directory incompatible with new version.\n" - "Please use a clean directory or remove \"{}\" and try again.", - link.string()); - } + // Add in-memory paths + // Some directories are redundant (skip those) + const std::vector<std::string> skip{".lock", "audit_log", "auth", "databases", "internal_modules", "settings"}; + for (auto const &item : std::filesystem::directory_iterator{*dir}) { + const auto dir_name = std::filesystem::relative(item.path(), item.path().parent_path()); + if (std::find(skip.begin(), skip.end(), dir_name) != skip.end()) continue; + to_link.push_back(item.path()); + } + + // Symlink to root dir + for (auto const &item : to_link) { + const auto dir_name = std::filesystem::relative(item, item.parent_path()); + const auto link = main_dir / dir_name; + const auto to = std::filesystem::relative(item, main_dir); + if (!std::filesystem::is_symlink(link) && !std::filesystem::exists(link)) { + std::filesystem::create_directory_symlink(to, link); + } else { // Check existing link + std::error_code ec; + const auto test_link = std::filesystem::read_symlink(link, ec); + if (ec || test_link != to) { + MG_ASSERT(false, + "Memgraph storage directory incompatible with new version.\n" + "Please use a clean directory or remove \"{}\" and try again.", + link.string()); } } } - return res; } /** @@ -514,17 +640,57 @@ class DbmsHandler { throw UnknownDatabaseException("Tried to retrieve an unknown database \"{}\".", name); } + /** + * @brief Get the context associated with the UUID database + * + * @param uuid + * @return DatabaseAccess + * @throw UnknownDatabaseException if database not found + */ + DatabaseAccess Get_(const utils::UUID &uuid) { + // TODO Speed up + for (auto &[_, db_gk] : db_handler_) { + auto acc = db_gk.access(); + if (acc->get()->uuid() == uuid) { + return std::move(*acc); + } + } + throw UnknownDatabaseException("Tried to retrieve an unknown database with UUID \"{}\".", std::string{uuid}); + } +#endif + + void RecoverReplication(DatabaseAccess db_acc) { + if (allow_mt_repl || db_acc->name() == dbms::kDefaultDB) { + // Handle global replication state + spdlog::info("Replication configuration will be stored and will be automatically restored in case of a crash."); + // RECOVER REPLICA CONNECTIONS + memgraph::dbms::RestoreReplication(repl_state_, std::move(db_acc)); + } else if (const ::memgraph::replication::RoleMainData *data = + std::get_if<::memgraph::replication::RoleMainData>(&repl_state_.ReplicationData()); + data && !data->registered_replicas_.empty()) { + spdlog::warn("Multi-tenant replication is currently not supported!"); + } + } + +#ifdef MG_ENTERPRISE mutable LockT lock_{utils::RWLock::Priority::READ}; //!< protective lock storage::Config default_config_; //!< Storage configuration used when creating new databases DatabaseHandler db_handler_; //!< multi-tenancy storage handler std::unique_ptr<kvstore::KVStore> durability_; //!< list of active dbs (pointer so we can postpone its creation) - bool delete_on_drop_; //!< Flag defining if dropping storage also deletes its directory - std::set<std::string> defunct_dbs_; //!< Databases that are in an unknown state due to various failures + coordination::CoordinatorState coordinator_state_; //!< Replication coordinator #endif + // TODO: Make an api + public: + utils::ResourceLock system_lock_{}; //!> Ensure exclusive access for system queries + private: + std::optional<SystemTransaction> system_transaction_; //!< Current system transaction (only one at a time) + uint64_t system_timestamp_{storage::kTimestampInitialId}; //!< System timestamp + std::atomic_uint64_t last_commited_system_timestamp_{ + storage::kTimestampInitialId}; //!< Last commited system timestamp replication::ReplicationState repl_state_; //!< Global replication state #ifndef MG_ENTERPRISE mutable utils::Gatekeeper<Database> db_gatekeeper_; //!< Single databases gatekeeper #endif -}; +}; // namespace memgraph::dbms } // namespace memgraph::dbms diff --git a/src/dbms/handler.hpp b/src/dbms/handler.hpp index 568b2fc7c..53724dabe 100644 --- a/src/dbms/handler.hpp +++ b/src/dbms/handler.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -21,6 +21,7 @@ #include "utils/exceptions.hpp" #include "utils/gatekeeper.hpp" #include "utils/result.hpp" +#include "utils/thread_pool.hpp" namespace memgraph::dbms { @@ -82,7 +83,7 @@ class Handler { * @return true on success * @throw BasicException */ - bool Delete(const std::string &name) { + bool TryDelete(std::string_view name) { if (auto itr = items_.find(name); itr != items_.end()) { auto db_acc = itr->second.access(); if (db_acc && db_acc->try_delete()) { @@ -92,9 +93,42 @@ class Handler { } return false; } + // TODO: Change to return enum throw utils::BasicException("Unknown item \"{}\".", name); } + /** + * @brief Delete or defunct the context associated with the name. + * + * @param name Name associated with the context to delete + * @param post_delete_func What to do after deletion has happened + */ + template <typename Func> + void DeferDelete(std::string_view name, Func &&post_delete_func) { + auto itr = items_.find(name); + if (itr == items_.end()) return; + + auto db_acc = itr->second.access(); + if (!db_acc) return; + + if (db_acc->try_delete()) { + // Delete the database now + db_acc->reset(); + post_delete_func(); + } else { + // Defer deletion + db_acc->reset(); + // TODO: Make sure this shuts down correctly + auto task = [gk = std::move(itr->second), post_delete_func = std::forward<Func>(post_delete_func)]() mutable { + gk.~Gatekeeper<T>(); + post_delete_func(); + }; + defer_pool_.AddTask(utils::CopyMovableFunctionWrapper{std::move(task)}); + } + // In any case remove from handled map + items_.erase(itr); + } + /** * @brief Check if a name is already used. * @@ -120,6 +154,7 @@ class Handler { private: std::unordered_map<std::string, utils::Gatekeeper<T>, string_hash, std::equal_to<>> items_; //!< map to all active items + utils::ThreadPool defer_pool_{1}; }; } // namespace memgraph::dbms diff --git a/src/dbms/inmemory/replication_handlers.cpp b/src/dbms/inmemory/replication_handlers.cpp index 5eba61878..cef2bf8c6 100644 --- a/src/dbms/inmemory/replication_handlers.cpp +++ b/src/dbms/inmemory/replication_handlers.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -10,6 +10,7 @@ // licenses/APL.txt. #include "dbms/inmemory/replication_handlers.hpp" +#include <chrono> #include <optional> #include "dbms/constants.hpp" #include "dbms/dbms_handler.hpp" @@ -22,6 +23,7 @@ #include "storage/v2/inmemory/storage.hpp" #include "storage/v2/inmemory/unique_constraints.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; using memgraph::storage::Delta; using memgraph::storage::EdgeAccessor; using memgraph::storage::EdgeRef; @@ -48,29 +50,29 @@ std::pair<uint64_t, WalDeltaData> ReadDelta(storage::durability::BaseDecoder *de } }; -std::optional<DatabaseAccess> GetDatabaseAccessor(dbms::DbmsHandler *dbms_handler, std::string_view db_name) { +std::optional<DatabaseAccess> GetDatabaseAccessor(dbms::DbmsHandler *dbms_handler, const utils::UUID &uuid) { try { #ifdef MG_ENTERPRISE - auto acc = dbms_handler->Get(db_name); -#else - if (db_name != dbms::kDefaultDB) { - spdlog::warn("Trying to replicate a non-default database on a community replica."); - return std::nullopt; - } - auto acc = dbms_handler->Get(); -#endif + auto acc = dbms_handler->Get(uuid); if (!acc) { - spdlog::error("Failed to get access to ", db_name); + spdlog::error("Failed to get access to UUID ", std::string{uuid}); return std::nullopt; } +#else + auto acc = dbms_handler->Get(); + if (!acc) { + spdlog::warn("Failed to get access to the default db."); + return std::nullopt; + } +#endif auto *inmem_storage = dynamic_cast<storage::InMemoryStorage *>(acc.get()->storage()); if (!inmem_storage || inmem_storage->storage_mode_ != storage::StorageMode::IN_MEMORY_TRANSACTIONAL) { - spdlog::error("Database \"{}\" is not IN_MEMORY_TRANSACTIONAL.", db_name); + spdlog::error("Database is not IN_MEMORY_TRANSACTIONAL."); return std::nullopt; } return std::optional{std::move(acc)}; } catch (const dbms::UnknownDatabaseException &e) { - spdlog::warn("No database \"{}\" on replica!", db_name); + spdlog::warn("No database with UUID \"{}\" on replica!", std::string{uuid}); return std::nullopt; } } @@ -108,13 +110,16 @@ void InMemoryReplicationHandlers::HeartbeatHandler(dbms::DbmsHandler *dbms_handl slk::Builder *res_builder) { storage::replication::HeartbeatReq req; slk::Load(&req, req_reader); - auto const db_acc = GetDatabaseAccessor(dbms_handler, req.db_name); - if (!db_acc) return; + auto const db_acc = GetDatabaseAccessor(dbms_handler, req.uuid); + if (!db_acc) { + storage::replication::HeartbeatRes res{false, 0, ""}; + slk::Save(res, res_builder); + return; + } // TODO: this handler is agnostic of InMemory, move to be reused by on-disk auto const *storage = db_acc->get()->storage(); - storage::replication::HeartbeatRes res{storage->id(), true, - storage->repl_storage_state_.last_commit_timestamp_.load(), + storage::replication::HeartbeatRes res{true, storage->repl_storage_state_.last_commit_timestamp_.load(), std::string{storage->repl_storage_state_.epoch_.id()}}; slk::Save(res, res_builder); } @@ -123,8 +128,12 @@ void InMemoryReplicationHandlers::AppendDeltasHandler(dbms::DbmsHandler *dbms_ha slk::Builder *res_builder) { storage::replication::AppendDeltasReq req; slk::Load(&req, req_reader); - auto db_acc = GetDatabaseAccessor(dbms_handler, req.db_name); - if (!db_acc) return; + auto db_acc = GetDatabaseAccessor(dbms_handler, req.uuid); + if (!db_acc) { + storage::replication::AppendDeltasRes res{false, 0}; + slk::Save(res, res_builder); + return; + } storage::replication::Decoder decoder(req_reader); @@ -164,7 +173,7 @@ void InMemoryReplicationHandlers::AppendDeltasHandler(dbms::DbmsHandler *dbms_ha storage::durability::kVersion); // TODO: Check if we are always using the latest version when replicating } - storage::replication::AppendDeltasRes res{storage->id(), false, repl_storage_state.last_commit_timestamp_.load()}; + storage::replication::AppendDeltasRes res{false, repl_storage_state.last_commit_timestamp_.load()}; slk::Save(res, res_builder); return; } @@ -173,7 +182,7 @@ void InMemoryReplicationHandlers::AppendDeltasHandler(dbms::DbmsHandler *dbms_ha storage, &decoder, storage::durability::kVersion); // TODO: Check if we are always using the latest version when replicating - storage::replication::AppendDeltasRes res{storage->id(), true, repl_storage_state.last_commit_timestamp_.load()}; + storage::replication::AppendDeltasRes res{true, repl_storage_state.last_commit_timestamp_.load()}; slk::Save(res, res_builder); spdlog::debug("Replication recovery from append deltas finished, replica is now up to date!"); } @@ -182,8 +191,12 @@ void InMemoryReplicationHandlers::SnapshotHandler(dbms::DbmsHandler *dbms_handle slk::Builder *res_builder) { storage::replication::SnapshotReq req; slk::Load(&req, req_reader); - auto db_acc = GetDatabaseAccessor(dbms_handler, req.db_name); - if (!db_acc) return; + auto db_acc = GetDatabaseAccessor(dbms_handler, req.uuid); + if (!db_acc) { + storage::replication::SnapshotRes res{false, 0}; + slk::Save(res, res_builder); + return; + } storage::replication::Decoder decoder(req_reader); @@ -231,8 +244,7 @@ void InMemoryReplicationHandlers::SnapshotHandler(dbms::DbmsHandler *dbms_handle } storage_guard.unlock(); - storage::replication::SnapshotRes res{storage->id(), true, - storage->repl_storage_state_.last_commit_timestamp_.load()}; + storage::replication::SnapshotRes res{true, storage->repl_storage_state_.last_commit_timestamp_.load()}; slk::Save(res, res_builder); spdlog::trace("Deleting old snapshot files due to snapshot recovery."); @@ -262,8 +274,12 @@ void InMemoryReplicationHandlers::WalFilesHandler(dbms::DbmsHandler *dbms_handle slk::Builder *res_builder) { storage::replication::WalFilesReq req; slk::Load(&req, req_reader); - auto db_acc = GetDatabaseAccessor(dbms_handler, req.db_name); - if (!db_acc) return; + auto db_acc = GetDatabaseAccessor(dbms_handler, req.uuid); + if (!db_acc) { + storage::replication::WalFilesRes res{false, 0}; + slk::Save(res, res_builder); + return; + } const auto wal_file_number = req.file_number; spdlog::debug("Received WAL files: {}", wal_file_number); @@ -277,8 +293,7 @@ void InMemoryReplicationHandlers::WalFilesHandler(dbms::DbmsHandler *dbms_handle LoadWal(storage, &decoder); } - storage::replication::WalFilesRes res{storage->id(), true, - storage->repl_storage_state_.last_commit_timestamp_.load()}; + storage::replication::WalFilesRes res{true, storage->repl_storage_state_.last_commit_timestamp_.load()}; slk::Save(res, res_builder); spdlog::debug("Replication recovery from WAL files ended successfully, replica is now up to date!"); } @@ -287,8 +302,12 @@ void InMemoryReplicationHandlers::CurrentWalHandler(dbms::DbmsHandler *dbms_hand slk::Builder *res_builder) { storage::replication::CurrentWalReq req; slk::Load(&req, req_reader); - auto db_acc = GetDatabaseAccessor(dbms_handler, req.db_name); - if (!db_acc) return; + auto db_acc = GetDatabaseAccessor(dbms_handler, req.uuid); + if (!db_acc) { + storage::replication::CurrentWalRes res{false, 0}; + slk::Save(res, res_builder); + return; + } storage::replication::Decoder decoder(req_reader); @@ -297,8 +316,7 @@ void InMemoryReplicationHandlers::CurrentWalHandler(dbms::DbmsHandler *dbms_hand LoadWal(storage, &decoder); - storage::replication::CurrentWalRes res{storage->id(), true, - storage->repl_storage_state_.last_commit_timestamp_.load()}; + storage::replication::CurrentWalRes res{true, storage->repl_storage_state_.last_commit_timestamp_.load()}; slk::Save(res, res_builder); spdlog::debug("Replication recovery from current WAL ended successfully, replica is now up to date!"); } @@ -317,6 +335,8 @@ void InMemoryReplicationHandlers::LoadWal(storage::InMemoryStorage *storage, sto } auto &replica_epoch = storage->repl_storage_state_.epoch_; if (wal_info.epoch_id != replica_epoch.id()) { + // questionable behaviour, we trust that any change in epoch implies change in who is MAIN + // when we use high availability, this assumption need to be checked. auto prev_epoch = replica_epoch.SetEpoch(wal_info.epoch_id); storage->repl_storage_state_.AddEpochToHistoryForce(prev_epoch); } @@ -354,13 +374,16 @@ void InMemoryReplicationHandlers::TimestampHandler(dbms::DbmsHandler *dbms_handl slk::Builder *res_builder) { storage::replication::TimestampReq req; slk::Load(&req, req_reader); - auto const db_acc = GetDatabaseAccessor(dbms_handler, req.db_name); - if (!db_acc) return; + auto const db_acc = GetDatabaseAccessor(dbms_handler, req.uuid); + if (!db_acc) { + storage::replication::TimestampRes res{false, 0}; + slk::Save(res, res_builder); + return; + } // TODO: this handler is agnostic of InMemory, move to be reused by on-disk auto const *storage = db_acc->get()->storage(); - storage::replication::TimestampRes res{storage->id(), true, - storage->repl_storage_state_.last_commit_timestamp_.load()}; + storage::replication::TimestampRes res{true, storage->repl_storage_state_.last_commit_timestamp_.load()}; slk::Save(res, res_builder); } @@ -380,9 +403,9 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage if (!commit_timestamp_and_accessor) { std::unique_ptr<storage::Storage::Accessor> acc = nullptr; if (unique) { - acc = storage->UniqueAccess(std::nullopt, false /*not main*/); + acc = storage->UniqueAccess(ReplicationRole::REPLICA); } else { - acc = storage->Access(std::nullopt, false /*not main*/); + acc = storage->Access(ReplicationRole::REPLICA); } auto inmem_acc = std::unique_ptr<storage::InMemoryStorage::InMemoryAccessor>( static_cast<storage::InMemoryStorage::InMemoryAccessor *>(acc.release())); @@ -504,7 +527,7 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage case WalDeltaData::Type::EDGE_SET_PROPERTY: { spdlog::trace(" Edge {} set property {} to {}", delta.vertex_edge_set_property.gid.AsUint(), delta.vertex_edge_set_property.property, delta.vertex_edge_set_property.value); - if (!storage->config_.items.properties_on_edges) + if (!storage->config_.salient.items.properties_on_edges) throw utils::BasicException( "Can't set properties on edges because properties on edges " "are disabled!"); @@ -571,8 +594,8 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage spdlog::trace(" Transaction end"); if (!commit_timestamp_and_accessor || commit_timestamp_and_accessor->first != timestamp) throw utils::BasicException("Invalid commit data!"); - auto ret = - commit_timestamp_and_accessor->second.Commit(commit_timestamp_and_accessor->first, false /* not main */); + auto ret = commit_timestamp_and_accessor->second.Commit( + {.desired_commit_timestamp = commit_timestamp_and_accessor->first, .is_main = false}); if (ret.HasError()) throw utils::BasicException("Invalid transaction! Please raise an issue, {}:{}", __FILE__, __LINE__); commit_timestamp_and_accessor = std::nullopt; diff --git a/src/dbms/inmemory/replication_handlers.hpp b/src/dbms/inmemory/replication_handlers.hpp index fc76d2b3a..4f6523747 100644 --- a/src/dbms/inmemory/replication_handlers.hpp +++ b/src/dbms/inmemory/replication_handlers.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -12,7 +12,6 @@ #pragma once #include "replication/replication_server.hpp" -#include "replication/state.hpp" #include "storage/v2/replication/serialization.hpp" namespace memgraph::storage { diff --git a/src/dbms/inmemory/storage_helper.hpp b/src/dbms/inmemory/storage_helper.hpp index 1cd9f9f4e..fa1b9646a 100644 --- a/src/dbms/inmemory/storage_helper.hpp +++ b/src/dbms/inmemory/storage_helper.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -24,37 +24,15 @@ namespace memgraph::dbms { inline std::unique_ptr<storage::Storage> CreateInMemoryStorage(storage::Config config, ::memgraph::replication::ReplicationState &repl_state) { - const auto wal_mode = config.durability.snapshot_wal_mode; - const auto name = config.name; + const auto name = config.salient.name; auto storage = std::make_unique<storage::InMemoryStorage>(std::move(config)); // Connect replication state and storage storage->CreateSnapshotHandler( [storage = storage.get(), &repl_state]() -> utils::BasicResult<storage::InMemoryStorage::CreateSnapshotError> { - if (repl_state.IsReplica()) { - return storage::InMemoryStorage::CreateSnapshotError::DisabledForReplica; - } - return storage->CreateSnapshot(); + return storage->CreateSnapshot(repl_state.GetRole()); }); - if (allow_mt_repl || name == dbms::kDefaultDB) { - // Handle global replication state - spdlog::info("Replication configuration will be stored and will be automatically restored in case of a crash."); - // RECOVER REPLICA CONNECTIONS - memgraph::dbms::RestoreReplication(repl_state, *storage); - } else if (const ::memgraph::replication::RoleMainData *data = - std::get_if<::memgraph::replication::RoleMainData>(&repl_state.ReplicationData()); - data && !data->registered_replicas_.empty()) { - spdlog::warn("Multi-tenant replication is currently not supported!"); - } - - if (wal_mode == storage::Config::Durability::SnapshotWalMode::DISABLED && repl_state.IsMain()) { - spdlog::warn( - "The instance has the MAIN replication role, but durability logs and snapshots are disabled. Please consider " - "enabling durability by using --storage-snapshot-interval-sec and --storage-wal-enabled flags because " - "without write-ahead logs this instance is not replicating any data."); - } - return std::move(storage); } diff --git a/src/dbms/replication_client.cpp b/src/dbms/replication_client.cpp index bfa4c622f..fa0c30daa 100644 --- a/src/dbms/replication_client.cpp +++ b/src/dbms/replication_client.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -10,6 +10,7 @@ // licenses/APL.txt. #include "dbms/replication_client.hpp" +#include "replication/replication_client.hpp" namespace memgraph::dbms { @@ -17,18 +18,26 @@ void StartReplicaClient(DbmsHandler &dbms_handler, replication::ReplicationClien // No client error, start instance level client auto const &endpoint = client.rpc_client_.Endpoint(); spdlog::trace("Replication client started at: {}:{}", endpoint.address, endpoint.port); - client.StartFrequentCheck([&dbms_handler](std::string_view name) { - // Working connection, check if any database has been left behind - dbms_handler.ForEach([name](dbms::Database *db) { + client.StartFrequentCheck([&dbms_handler](bool reconnect, replication::ReplicationClient &client) { + // Working connection + // Check if system needs restoration + if (reconnect) { + client.state_.WithLock([](auto &state) { state = memgraph::replication::ReplicationClient::State::BEHIND; }); + } +#ifdef MG_ENTERPRISE + dbms_handler.SystemRestore<true>(client); +#endif + // Check if any database has been left behind + dbms_handler.ForEach([&name = client.name_, reconnect](dbms::DatabaseAccess db_acc) { // Specific database <-> replica client - db->storage()->repl_storage_state_.WithClient(name, [&](storage::ReplicationStorageClient *client) { - if (client->State() == storage::replication::ReplicaState::MAYBE_BEHIND) { + db_acc->storage()->repl_storage_state_.WithClient(name, [&](storage::ReplicationStorageClient *client) { + if (reconnect || client->State() == storage::replication::ReplicaState::MAYBE_BEHIND) { // Database <-> replica might be behind, check and recover - client->TryCheckReplicaStateAsync(db->storage()); + client->TryCheckReplicaStateAsync(db_acc->storage(), db_acc); } }); }); }); -} +} // namespace memgraph::dbms } // namespace memgraph::dbms diff --git a/src/dbms/replication_handler.cpp b/src/dbms/replication_handler.cpp index 2cbe2c432..285752f76 100644 --- a/src/dbms/replication_handler.cpp +++ b/src/dbms/replication_handler.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -11,15 +11,21 @@ #include "dbms/replication_handler.hpp" +#include <algorithm> + #include "dbms/constants.hpp" #include "dbms/dbms_handler.hpp" +#include "dbms/global.hpp" #include "dbms/inmemory/replication_handlers.hpp" -#include "dbms/inmemory/storage_helper.hpp" #include "dbms/replication_client.hpp" +#include "dbms/utils.hpp" +#include "replication/messages.hpp" #include "replication/state.hpp" +#include "spdlog/spdlog.h" +#include "storage/v2/config.hpp" +#include "storage/v2/replication/rpc.hpp" +#include "utils/on_scope_exit.hpp" -using memgraph::replication::ReplicationClientConfig; -using memgraph::replication::ReplicationState; using memgraph::replication::RoleMainData; using memgraph::replication::RoleReplicaData; @@ -32,8 +38,8 @@ std::string RegisterReplicaErrorToString(RegisterReplicaError error) { using enum RegisterReplicaError; case NAME_EXISTS: return "NAME_EXISTS"; - case END_POINT_EXISTS: - return "END_POINT_EXISTS"; + case ENDPOINT_EXISTS: + return "ENDPOINT_EXISTS"; case CONNECTION_FAILED: return "CONNECTION_FAILED"; case COULD_NOT_BE_PERSISTED: @@ -45,34 +51,13 @@ std::string RegisterReplicaErrorToString(RegisterReplicaError error) { ReplicationHandler::ReplicationHandler(DbmsHandler &dbms_handler) : dbms_handler_(dbms_handler) {} bool ReplicationHandler::SetReplicationRoleMain() { - auto const main_handler = [](RoleMainData const &) { + auto const main_handler = [](RoleMainData &) { // If we are already MAIN, we don't want to change anything return false; }; + auto const replica_handler = [this](RoleReplicaData const &) { - // STEP 1) bring down all REPLICA servers - dbms_handler_.ForEach([](Database *db) { - auto *storage = db->storage(); - // Remember old epoch + storage timestamp association - storage->PrepareForNewEpoch(); - }); - - // STEP 2) Change to MAIN - // TODO: restore replication servers if false? - if (!dbms_handler_.ReplicationState().SetReplicationRoleMain()) { - // TODO: Handle recovery on failure??? - return false; - } - - // STEP 3) We are now MAIN, update storage local epoch - const auto &epoch = - std::get<RoleMainData>(std::as_const(dbms_handler_.ReplicationState()).ReplicationData()).epoch_; - dbms_handler_.ForEach([&](Database *db) { - auto *storage = db->storage(); - storage->repl_storage_state_.epoch_ = epoch; - }); - - return true; + return memgraph::dbms::DoReplicaToMainPromotion(dbms_handler_); }; // TODO: under lock @@ -89,8 +74,8 @@ bool ReplicationHandler::SetReplicationRoleReplica(const memgraph::replication:: // TODO StorageState needs to be synched. Could have a dangling reference if someone adds a database as we are // deleting the replica. // Remove database specific clients - dbms_handler_.ForEach([&](Database *db) { - auto *storage = db->storage(); + dbms_handler_.ForEach([&](DatabaseAccess db_acc) { + auto *storage = db_acc->storage(); storage->repl_storage_state_.replication_clients_.WithLock([](auto &clients) { clients.clear(); }); }); // Remove instance level clients @@ -105,15 +90,7 @@ bool ReplicationHandler::SetReplicationRoleReplica(const memgraph::replication:: // ASSERT return false; }, - [this](RoleReplicaData const &data) { - // Register handlers - InMemoryReplicationHandlers::Register(&dbms_handler_, *data.server); - if (!data.server->Start()) { - spdlog::error("Unable to start the replication server."); - return false; - } - return true; - }}, + [this](RoleReplicaData const &data) { return StartRpcServer(dbms_handler_, data); }}, dbms_handler_.ReplicationState().ReplicationData()); // TODO Handle error (restore to main?) return success; @@ -123,60 +100,48 @@ auto ReplicationHandler::RegisterReplica(const memgraph::replication::Replicatio -> memgraph::utils::BasicResult<RegisterReplicaError> { MG_ASSERT(dbms_handler_.ReplicationState().IsMain(), "Only main instance can register a replica!"); - auto instance_client = dbms_handler_.ReplicationState().RegisterReplica(config); - if (instance_client.HasError()) switch (instance_client.GetError()) { + auto maybe_client = dbms_handler_.ReplicationState().RegisterReplica(config); + if (maybe_client.HasError()) { + switch (maybe_client.GetError()) { case memgraph::replication::RegisterReplicaError::NOT_MAIN: MG_ASSERT(false, "Only main instance can register a replica!"); return {}; case memgraph::replication::RegisterReplicaError::NAME_EXISTS: return memgraph::dbms::RegisterReplicaError::NAME_EXISTS; - case memgraph::replication::RegisterReplicaError::END_POINT_EXISTS: - return memgraph::dbms::RegisterReplicaError::END_POINT_EXISTS; + case memgraph::replication::RegisterReplicaError::ENDPOINT_EXISTS: + return memgraph::dbms::RegisterReplicaError::ENDPOINT_EXISTS; case memgraph::replication::RegisterReplicaError::COULD_NOT_BE_PERSISTED: return memgraph::dbms::RegisterReplicaError::COULD_NOT_BE_PERSISTED; case memgraph::replication::RegisterReplicaError::SUCCESS: break; } + } if (!allow_mt_repl && dbms_handler_.All().size() > 1) { spdlog::warn("Multi-tenant replication is currently not supported!"); } - bool all_clients_good = true; +#ifdef MG_ENTERPRISE + // Update system before enabling individual storage <-> replica clients + dbms_handler_.SystemRestore(*maybe_client.GetValue()); +#endif - // Add database specific clients (NOTE Currently all databases are connected to each replica) - dbms_handler_.ForEach([&](Database *db) { - auto *storage = db->storage(); - if (!allow_mt_repl && storage->id() != kDefaultDB) { - return; - } - // TODO: ATM only IN_MEMORY_TRANSACTIONAL, fix other modes - if (storage->storage_mode_ != storage::StorageMode::IN_MEMORY_TRANSACTIONAL) return; - - all_clients_good &= - storage->repl_storage_state_.replication_clients_.WithLock([storage, &instance_client](auto &storage_clients) { - auto client = std::make_unique<storage::ReplicationStorageClient>(*instance_client.GetValue()); - client->Start(storage); - // After start the storage <-> replica state should be READY or RECOVERING (if correctly started) - // MAYBE_BEHIND isn't a statement of the current state, this is the default value - // Failed to start due to branching of MAIN and REPLICA - if (client->State() == storage::replication::ReplicaState::MAYBE_BEHIND) { - return false; - } - storage_clients.push_back(std::move(client)); - return true; - }); - }); + const auto dbms_error = memgraph::dbms::HandleRegisterReplicaStatus(maybe_client); + if (dbms_error.has_value()) { + return *dbms_error; + } + auto &instance_client_ptr = maybe_client.GetValue(); + const bool all_clients_good = memgraph::dbms::RegisterAllDatabasesClients(dbms_handler_, *instance_client_ptr); // NOTE Currently if any databases fails, we revert back if (!all_clients_good) { - spdlog::error("Failed to register all databases to the REPLICA \"{}\"", config.name); + spdlog::error("Failed to register all databases on the REPLICA \"{}\"", config.name); UnregisterReplica(config.name); return RegisterReplicaError::CONNECTION_FAILED; } // No client error, start instance level client - StartReplicaClient(dbms_handler_, *instance_client.GetValue()); + StartReplicaClient(dbms_handler_, *instance_client_ptr); return {}; } @@ -189,8 +154,8 @@ auto ReplicationHandler::UnregisterReplica(std::string_view name) -> UnregisterR return UnregisterReplicaResult::COULD_NOT_BE_PERSISTED; } // Remove database specific clients - dbms_handler_.ForEach([name](Database *db) { - db->storage()->repl_storage_state_.replication_clients_.WithLock([&name](auto &clients) { + dbms_handler_.ForEach([name](DatabaseAccess db_acc) { + db_acc->storage()->repl_storage_state_.replication_clients_.WithLock([&name](auto &clients) { std::erase_if(clients, [name](const auto &client) { return client->Name() == name; }); }); }); @@ -204,7 +169,7 @@ auto ReplicationHandler::UnregisterReplica(std::string_view name) -> UnregisterR dbms_handler_.ReplicationState().ReplicationData()); } -auto ReplicationHandler::GetRole() const -> memgraph::replication::ReplicationRole { +auto ReplicationHandler::GetRole() const -> memgraph::replication_coordination_glue::ReplicationRole { return dbms_handler_.ReplicationState().GetRole(); } @@ -214,20 +179,20 @@ bool ReplicationHandler::IsReplica() const { return dbms_handler_.ReplicationSta // Per storage // NOTE Storage will connect to all replicas. Future work might change this -void RestoreReplication(replication::ReplicationState &repl_state, storage::Storage &storage) { +void RestoreReplication(replication::ReplicationState &repl_state, DatabaseAccess db_acc) { spdlog::info("Restoring replication role."); /// MAIN - auto const recover_main = [&storage](RoleMainData &mainData) { + auto const recover_main = [db_acc = std::move(db_acc)](RoleMainData &mainData) mutable { // NOLINT // Each individual client has already been restored and started. Here we just go through each database and start its // client for (auto &instance_client : mainData.registered_replicas_) { - spdlog::info("Replica {} restoration started for {}.", instance_client.name_, storage.id()); - - const auto &ret = storage.repl_storage_state_.replication_clients_.WithLock( - [&](auto &storage_clients) -> utils::BasicResult<RegisterReplicaError> { + spdlog::info("Replica {} restoration started for {}.", instance_client.name_, db_acc->name()); + const auto &ret = db_acc->storage()->repl_storage_state_.replication_clients_.WithLock( + [&, db_acc](auto &storage_clients) mutable -> utils::BasicResult<RegisterReplicaError> { auto client = std::make_unique<storage::ReplicationStorageClient>(instance_client); - client->Start(&storage); + auto *storage = db_acc->storage(); + client->Start(storage, std::move(db_acc)); // After start the storage <-> replica state should be READY or RECOVERING (if correctly started) // MAYBE_BEHIND isn't a statement of the current state, this is the default value // Failed to start due to branching of MAIN and REPLICA @@ -244,7 +209,7 @@ void RestoreReplication(replication::ReplicationState &repl_state, storage::Stor LOG_FATAL("Failure when restoring replica {}: {}.", instance_client.name_, RegisterReplicaErrorToString(ret.GetError())); } - spdlog::info("Replica {} restored for {}.", instance_client.name_, storage.id()); + spdlog::info("Replica {} restored for {}.", instance_client.name_, db_acc->name()); } spdlog::info("Replication role restored to MAIN."); }; @@ -259,4 +224,177 @@ void RestoreReplication(replication::ReplicationState &repl_state, storage::Stor }, repl_state.ReplicationData()); } + +namespace system_replication { +#ifdef MG_ENTERPRISE +void SystemHeartbeatHandler(const uint64_t ts, slk::Reader *req_reader, slk::Builder *res_builder) { + replication::SystemHeartbeatReq req; + replication::SystemHeartbeatReq::Load(&req, req_reader); + + replication::SystemHeartbeatRes res(ts); + memgraph::slk::Save(res, res_builder); +} + +void CreateDatabaseHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder) { + memgraph::storage::replication::CreateDatabaseReq req; + memgraph::slk::Load(&req, req_reader); + + using memgraph::storage::replication::CreateDatabaseRes; + CreateDatabaseRes res(CreateDatabaseRes::Result::FAILURE); + + // Note: No need to check epoch, recovery mechanism is done by a full uptodate snapshot + // of the set of databases. Hence no history exists to maintain regarding epoch change. + // If MAIN has changed we need to check this new group_timestamp is consistent with + // what we have so far. + + if (req.expected_group_timestamp != dbms_handler.LastCommitedTS()) { + spdlog::debug("CreateDatabaseHandler: bad expected timestamp {},{}", req.expected_group_timestamp, + dbms_handler.LastCommitedTS()); + memgraph::slk::Save(res, res_builder); + return; + } + + try { + // Create new + auto new_db = dbms_handler.Update(req.config); + if (new_db.HasValue()) { + // Successfully create db + dbms_handler.SetLastCommitedTS(req.new_group_timestamp); + res = CreateDatabaseRes(CreateDatabaseRes::Result::SUCCESS); + spdlog::debug("CreateDatabaseHandler: SUCCESS updated LCTS to {}", req.new_group_timestamp); + } + } catch (...) { + // Failure + } + + memgraph::slk::Save(res, res_builder); +} + +void DropDatabaseHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder) { + memgraph::storage::replication::DropDatabaseReq req; + memgraph::slk::Load(&req, req_reader); + + using memgraph::storage::replication::DropDatabaseRes; + DropDatabaseRes res(DropDatabaseRes::Result::FAILURE); + + // Note: No need to check epoch, recovery mechanism is done by a full uptodate snapshot + // of the set of databases. Hence no history exists to maintain regarding epoch change. + // If MAIN has changed we need to check this new group_timestamp is consistent with + // what we have so far. + + if (req.expected_group_timestamp != dbms_handler.LastCommitedTS()) { + spdlog::debug("DropDatabaseHandler: bad expected timestamp {},{}", req.expected_group_timestamp, + dbms_handler.LastCommitedTS()); + memgraph::slk::Save(res, res_builder); + return; + } + + try { + // NOTE: Single communication channel can exist at a time, no other database can be deleted/created at the moment. + auto new_db = dbms_handler.Delete(req.uuid); + if (new_db.HasError()) { + if (new_db.GetError() == DeleteError::NON_EXISTENT) { + // Nothing to drop + dbms_handler.SetLastCommitedTS(req.new_group_timestamp); + res = DropDatabaseRes(DropDatabaseRes::Result::NO_NEED); + } + } else { + // Successfully drop db + dbms_handler.SetLastCommitedTS(req.new_group_timestamp); + res = DropDatabaseRes(DropDatabaseRes::Result::SUCCESS); + spdlog::debug("DropDatabaseHandler: SUCCESS updated LCTS to {}", req.new_group_timestamp); + } + } catch (...) { + // Failure + } + + memgraph::slk::Save(res, res_builder); +} + +void SystemRecoveryHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder) { + // TODO Speed up + memgraph::storage::replication::SystemRecoveryReq req; + memgraph::slk::Load(&req, req_reader); + + using memgraph::storage::replication::SystemRecoveryRes; + SystemRecoveryRes res(SystemRecoveryRes::Result::FAILURE); + + utils::OnScopeExit send_on_exit([&]() { memgraph::slk::Save(res, res_builder); }); + + // Get all current dbs + auto old = dbms_handler.All(); + + // Check/create the incoming dbs + for (const auto &config : req.database_configs) { + // Missing db + try { + if (dbms_handler.Update(config).HasError()) { + spdlog::debug("SystemRecoveryHandler: Failed to update database \"{}\".", config.name); + return; // Send failure on exit + } + } catch (const UnknownDatabaseException &) { + spdlog::debug("SystemRecoveryHandler: UnknownDatabaseException"); + return; // Send failure on exit + } + const auto it = std::find(old.begin(), old.end(), config.name); + if (it != old.end()) old.erase(it); + } + + // Delete all the leftover old dbs + for (const auto &remove_db : old) { + const auto del = dbms_handler.Delete(remove_db); + if (del.HasError()) { + // Some errors are not terminal + if (del.GetError() == DeleteError::DEFAULT_DB || del.GetError() == DeleteError::NON_EXISTENT) { + spdlog::debug("SystemRecoveryHandler: Dropped database \"{}\".", remove_db); + continue; + } + spdlog::debug("SystemRecoveryHandler: Failed to drop database \"{}\".", remove_db); + return; // Send failure on exit + } + } + // Successfully recovered + dbms_handler.SetLastCommitedTS(req.forced_group_timestamp); + spdlog::debug("SystemRecoveryHandler: SUCCESS updated LCTS to {}", req.forced_group_timestamp); + res = SystemRecoveryRes(SystemRecoveryRes::Result::SUCCESS); +} +#endif + +void Register(replication::RoleReplicaData const &data, dbms::DbmsHandler &dbms_handler) { +#ifdef MG_ENTERPRISE + data.server->rpc_server_.Register<replication::SystemHeartbeatRpc>( + [&dbms_handler](auto *req_reader, auto *res_builder) { + spdlog::debug("Received SystemHeartbeatRpc"); + SystemHeartbeatHandler(dbms_handler.LastCommitedTS(), req_reader, res_builder); + }); + data.server->rpc_server_.Register<storage::replication::CreateDatabaseRpc>( + [&dbms_handler](auto *req_reader, auto *res_builder) { + spdlog::debug("Received CreateDatabaseRpc"); + CreateDatabaseHandler(dbms_handler, req_reader, res_builder); + }); + data.server->rpc_server_.Register<storage::replication::DropDatabaseRpc>( + [&dbms_handler](auto *req_reader, auto *res_builder) { + spdlog::debug("Received DropDatabaseRpc"); + DropDatabaseHandler(dbms_handler, req_reader, res_builder); + }); + data.server->rpc_server_.Register<storage::replication::SystemRecoveryRpc>( + [&dbms_handler](auto *req_reader, auto *res_builder) { + spdlog::debug("Received SystemRecoveryRpc"); + SystemRecoveryHandler(dbms_handler, req_reader, res_builder); + }); +#endif +} +} // namespace system_replication + +bool StartRpcServer(DbmsHandler &dbms_handler, const replication::RoleReplicaData &data) { + // Register handlers + InMemoryReplicationHandlers::Register(&dbms_handler, *data.server); + system_replication::Register(data, dbms_handler); + // Start server + if (!data.server->Start()) { + spdlog::error("Unable to start the replication server."); + return false; + } + return true; +} } // namespace memgraph::dbms diff --git a/src/dbms/replication_handler.hpp b/src/dbms/replication_handler.hpp index dc95407b1..53c64e34b 100644 --- a/src/dbms/replication_handler.hpp +++ b/src/dbms/replication_handler.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -11,11 +11,10 @@ #pragma once -#include "replication/role.hpp" -#include "storage/v2/storage.hpp" +#include "replication_coordination_glue/role.hpp" +#include "dbms/database.hpp" #include "utils/result.hpp" -// BEGIN fwd declares namespace memgraph::replication { struct ReplicationState; struct ReplicationServerConfig; @@ -23,9 +22,11 @@ struct ReplicationClientConfig; } // namespace memgraph::replication namespace memgraph::dbms { + class DbmsHandler; -enum class RegisterReplicaError : uint8_t { NAME_EXISTS, END_POINT_EXISTS, CONNECTION_FAILED, COULD_NOT_BE_PERSISTED }; +enum class RegisterReplicaError : uint8_t { NAME_EXISTS, ENDPOINT_EXISTS, CONNECTION_FAILED, COULD_NOT_BE_PERSISTED }; + enum class UnregisterReplicaResult : uint8_t { NOT_MAIN, COULD_NOT_BE_PERSISTED, @@ -52,7 +53,7 @@ struct ReplicationHandler { auto UnregisterReplica(std::string_view name) -> UnregisterReplicaResult; // Helper pass-through (TODO: remove) - auto GetRole() const -> memgraph::replication::ReplicationRole; + auto GetRole() const -> memgraph::replication_coordination_glue::ReplicationRole; bool IsMain() const; bool IsReplica() const; @@ -62,6 +63,20 @@ struct ReplicationHandler { /// A handler type that keep in sync current ReplicationState and the MAIN/REPLICA-ness of Storage /// TODO: extend to do multiple storages -void RestoreReplication(replication::ReplicationState &repl_state, storage::Storage &storage); +void RestoreReplication(replication::ReplicationState &repl_state, DatabaseAccess db_acc); + +namespace system_replication { +// System handlers +#ifdef MG_ENTERPRISE +void CreateDatabaseHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder); +void SystemHeartbeatHandler(uint64_t ts, slk::Reader *req_reader, slk::Builder *res_builder); +void SystemRecoveryHandler(DbmsHandler &dbms_handler, slk::Reader *req_reader, slk::Builder *res_builder); +#endif + +/// Register all DBMS level RPC handlers +void Register(replication::RoleReplicaData const &data, DbmsHandler &dbms_handler); +} // namespace system_replication + +bool StartRpcServer(DbmsHandler &dbms_handler, const replication::RoleReplicaData &data); } // namespace memgraph::dbms diff --git a/src/dbms/transaction.hpp b/src/dbms/transaction.hpp new file mode 100644 index 000000000..7167d9ec5 --- /dev/null +++ b/src/dbms/transaction.hpp @@ -0,0 +1,64 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include <memory> +#include "storage/v2/config.hpp" + +namespace memgraph::dbms { +struct SystemTransaction { + struct Delta { + enum class Action { + CREATE_DATABASE, + DROP_DATABASE, + }; + + static constexpr struct CreateDatabase { + } create_database; + static constexpr struct DropDatabase { + } drop_database; + + Delta(CreateDatabase /*tag*/, storage::SalientConfig config) + : action(Action::CREATE_DATABASE), config(std::move(config)) {} + Delta(DropDatabase /*tag*/, const utils::UUID &uuid) : action(Action::DROP_DATABASE), uuid(uuid) {} + + Delta(const Delta &) = delete; + Delta(Delta &&) = delete; + Delta &operator=(const Delta &) = delete; + Delta &operator=(Delta &&) = delete; + + ~Delta() { + switch (action) { + case Action::CREATE_DATABASE: + std::destroy_at(&config); + break; + case Action::DROP_DATABASE: + break; + // Some deltas might have special destructor handling + } + } + + Action action; + union { + storage::SalientConfig config; + utils::UUID uuid; + }; + }; + + explicit SystemTransaction(uint64_t timestamp) : system_timestamp(timestamp) {} + + // Currently system transitions support a single delta + std::optional<Delta> delta{}; + uint64_t system_timestamp; +}; + +} // namespace memgraph::dbms diff --git a/src/dbms/utils.hpp b/src/dbms/utils.hpp new file mode 100644 index 000000000..fd5db9cf1 --- /dev/null +++ b/src/dbms/utils.hpp @@ -0,0 +1,133 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. +#pragma once + +#include "dbms/dbms_handler.hpp" +#include "dbms/replication_handler.hpp" +#include "replication/include/replication/state.hpp" +#include "utils/result.hpp" + +namespace memgraph::dbms { + +inline bool DoReplicaToMainPromotion(dbms::DbmsHandler &dbms_handler) { + auto &repl_state = dbms_handler.ReplicationState(); + // STEP 1) bring down all REPLICA servers + dbms_handler.ForEach([](DatabaseAccess db_acc) { + auto *storage = db_acc->storage(); + // Remember old epoch + storage timestamp association + storage->PrepareForNewEpoch(); + }); + + // STEP 2) Change to MAIN + // TODO: restore replication servers if false? + if (!repl_state.SetReplicationRoleMain()) { + // TODO: Handle recovery on failure??? + return false; + } + + // STEP 3) We are now MAIN, update storage local epoch + const auto &epoch = + std::get<replication::RoleMainData>(std::as_const(dbms_handler.ReplicationState()).ReplicationData()).epoch_; + dbms_handler.ForEach([&](DatabaseAccess db_acc) { + auto *storage = db_acc->storage(); + storage->repl_storage_state_.epoch_ = epoch; + }); + + return true; +}; + +inline bool SetReplicationRoleReplica(dbms::DbmsHandler &dbms_handler, + const memgraph::replication::ReplicationServerConfig &config) { + if (dbms_handler.ReplicationState().IsReplica()) { + return false; + } + + // TODO StorageState needs to be synched. Could have a dangling reference if someone adds a database as we are + // deleting the replica. + // Remove database specific clients + dbms_handler.ForEach([&](DatabaseAccess db_acc) { + auto *storage = db_acc->storage(); + storage->repl_storage_state_.replication_clients_.WithLock([](auto &clients) { clients.clear(); }); + }); + // Remove instance level clients + std::get<replication::RoleMainData>(dbms_handler.ReplicationState().ReplicationData()).registered_replicas_.clear(); + + // Creates the server + dbms_handler.ReplicationState().SetReplicationRoleReplica(config); + + // Start + const auto success = std::visit(utils::Overloaded{[](replication::RoleMainData const &) { + // ASSERT + return false; + }, + [&dbms_handler](replication::RoleReplicaData const &data) { + return StartRpcServer(dbms_handler, data); + }}, + dbms_handler.ReplicationState().ReplicationData()); + // TODO Handle error (restore to main?) + return success; +} + +inline bool RegisterAllDatabasesClients(dbms::DbmsHandler &dbms_handler, + replication::ReplicationClient &instance_client) { + if (!allow_mt_repl && dbms_handler.All().size() > 1) { + spdlog::warn("Multi-tenant replication is currently not supported!"); + } + + bool all_clients_good = true; + + // Add database specific clients (NOTE Currently all databases are connected to each replica) + dbms_handler.ForEach([&](DatabaseAccess db_acc) { + auto *storage = db_acc->storage(); + if (!allow_mt_repl && storage->name() != kDefaultDB) { + return; + } + // TODO: ATM only IN_MEMORY_TRANSACTIONAL, fix other modes + if (storage->storage_mode_ != storage::StorageMode::IN_MEMORY_TRANSACTIONAL) return; + + all_clients_good &= storage->repl_storage_state_.replication_clients_.WithLock( + [storage, &instance_client, db_acc = std::move(db_acc)](auto &storage_clients) mutable { // NOLINT + auto client = std::make_unique<storage::ReplicationStorageClient>(instance_client); + // All good, start replica client + client->Start(storage, std::move(db_acc)); + // After start the storage <-> replica state should be READY or RECOVERING (if correctly started) + // MAYBE_BEHIND isn't a statement of the current state, this is the default value + // Failed to start due an error like branching of MAIN and REPLICA + if (client->State() == storage::replication::ReplicaState::MAYBE_BEHIND) { + return false; // TODO: sometimes we need to still add to storage_clients + } + storage_clients.push_back(std::move(client)); + return true; + }); + }); + + return all_clients_good; +} + +inline std::optional<RegisterReplicaError> HandleRegisterReplicaStatus( + utils::BasicResult<replication::RegisterReplicaError, replication::ReplicationClient *> &instance_client) { + if (instance_client.HasError()) switch (instance_client.GetError()) { + case replication::RegisterReplicaError::NOT_MAIN: + MG_ASSERT(false, "Only main instance can register a replica!"); + return {}; + case replication::RegisterReplicaError::NAME_EXISTS: + return dbms::RegisterReplicaError::NAME_EXISTS; + case replication::RegisterReplicaError::ENDPOINT_EXISTS: + return dbms::RegisterReplicaError::ENDPOINT_EXISTS; + case replication::RegisterReplicaError::COULD_NOT_BE_PERSISTED: + return dbms::RegisterReplicaError::COULD_NOT_BE_PERSISTED; + case replication::RegisterReplicaError::SUCCESS: + break; + } + return {}; +} + +} // namespace memgraph::dbms diff --git a/src/flags/CMakeLists.txt b/src/flags/CMakeLists.txt index e8988756f..e80438d1d 100644 --- a/src/flags/CMakeLists.txt +++ b/src/flags/CMakeLists.txt @@ -6,6 +6,7 @@ add_library(mg-flags STATIC audit.cpp memory_limit.cpp run_time_configurable.cpp storage_mode.cpp - query.cpp) + query.cpp + replication.cpp) target_include_directories(mg-flags PUBLIC ${CMAKE_SOURCE_DIR}/include) target_link_libraries(mg-flags PUBLIC spdlog::spdlog mg-settings mg-utils) diff --git a/src/flags/all.hpp b/src/flags/all.hpp index f7b44272a..f60f059d6 100644 --- a/src/flags/all.hpp +++ b/src/flags/all.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -17,5 +17,6 @@ #include "flags/log_level.hpp" #include "flags/memory_limit.hpp" #include "flags/query.hpp" +#include "flags/replication.hpp" #include "flags/run_time_configurable.hpp" #include "flags/storage_mode.hpp" diff --git a/src/flags/general.cpp b/src/flags/general.cpp index 6bee2e5b3..cd2c95c60 100644 --- a/src/flags/general.cpp +++ b/src/flags/general.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -66,6 +66,9 @@ DEFINE_bool(allow_load_csv, true, "Controls whether LOAD CSV clause is allowed i // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) DEFINE_VALIDATED_uint64(storage_gc_cycle_sec, 30, "Storage garbage collector interval (in seconds).", FLAG_IN_RANGE(1, 24UL * 3600)); +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +DEFINE_VALIDATED_uint64(storage_python_gc_cycle_sec, 180, + "Storage python full garbage collection interval (in seconds).", FLAG_IN_RANGE(1, 24UL * 3600)); // NOTE: The `storage_properties_on_edges` flag must be the same here and in // `mg_import_csv`. If you change it, make sure to change it there as well. // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) @@ -128,12 +131,6 @@ DEFINE_uint64(storage_recovery_thread_count, DEFINE_bool(storage_enable_schema_metadata, false, "Controls whether metadata should be collected about the resident labels and edge types."); -#ifdef MG_ENTERPRISE -// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) -DEFINE_bool(storage_delete_on_drop, true, - "If set to true the query 'DROP DATABASE x' will delete the underlying storage as well."); -#endif - // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) DEFINE_bool(telemetry_enabled, false, "Set to true to enable telemetry. We collect information about the " @@ -159,13 +156,6 @@ DEFINE_string(pulsar_service_url, "", "Default URL used while connecting to Puls // Query flags. -// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) -DEFINE_uint64(replication_replica_check_frequency_sec, 1, - "The time duration between two replica checks/pings. If < 1, replicas will NOT be checked at all. NOTE: " - "The MAIN instance allocates a new thread for each REPLICA."); -// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) -DEFINE_bool(replication_restore_state_on_startup, false, "Restore replication state on startup, e.g. recover replica"); - DEFINE_VALIDATED_string(query_modules_directory, "", "Directory where modules with custom query procedures are stored. " "NOTE: Multiple comma-separated directories can be defined.", @@ -205,3 +195,9 @@ DEFINE_HIDDEN_string(organization_name, "", "Organization name."); // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) DEFINE_string(auth_user_or_role_name_regex, memgraph::glue::kDefaultUserRoleRegex.data(), "Set to the regular expression that each user or role name must fulfill."); +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +DEFINE_bool(auth_password_permit_null, true, "Set to false to disable null passwords."); +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +DEFINE_string(auth_password_strength_regex, memgraph::glue::kDefaultPasswordRegex.data(), + "The regular expression that should be used to match the entire " + "entered password to ensure its strength."); diff --git a/src/flags/general.hpp b/src/flags/general.hpp index 890f32cd6..a1e8729ab 100644 --- a/src/flags/general.hpp +++ b/src/flags/general.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -52,6 +52,8 @@ DECLARE_bool(allow_load_csv); // Storage flags. // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) DECLARE_uint64(storage_gc_cycle_sec); +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +DECLARE_uint64(storage_python_gc_cycle_sec); // NOTE: The `storage_properties_on_edges` flag must be the same here and in // `mg_import_csv`. If you change it, make sure to change it there as well. // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) @@ -82,10 +84,6 @@ DECLARE_bool(storage_parallel_schema_recovery); DECLARE_uint64(storage_recovery_thread_count); // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) DECLARE_bool(storage_enable_schema_metadata); -#ifdef MG_ENTERPRISE -// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) -DECLARE_bool(storage_delete_on_drop); -#endif // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) DECLARE_bool(telemetry_enabled); @@ -114,14 +112,13 @@ namespace memgraph::flags { auto ParseQueryModulesDirectory() -> std::vector<std::filesystem::path>; } // namespace memgraph::flags -// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) -DECLARE_uint64(replication_replica_check_frequency_sec); -// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) -DECLARE_bool(replication_restore_state_on_startup); - // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) DECLARE_string(license_key); // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) DECLARE_string(organization_name); // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) DECLARE_string(auth_user_or_role_name_regex); +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +DECLARE_bool(auth_password_permit_null); +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +DECLARE_string(auth_password_strength_regex); diff --git a/src/flags/replication.cpp b/src/flags/replication.cpp new file mode 100644 index 000000000..3cd5187f3 --- /dev/null +++ b/src/flags/replication.cpp @@ -0,0 +1,26 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include "replication.hpp" + +#ifdef MG_ENTERPRISE +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +DEFINE_bool(coordinator, false, "Controls whether the instance is a replication coordinator."); +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +DEFINE_uint32(coordinator_server_port, 0, "Port on which coordinator servers will be started."); +#endif + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +DEFINE_uint64(replication_replica_check_frequency_sec, 1, + "The time duration between two replica checks/pings. If < 1, replicas will NOT be checked at all. NOTE: " + "The MAIN instance allocates a new thread for each REPLICA."); +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +DEFINE_bool(replication_restore_state_on_startup, false, "Restore replication state on startup, e.g. recover replica"); diff --git a/src/flags/replication.hpp b/src/flags/replication.hpp new file mode 100644 index 000000000..16f4c74d2 --- /dev/null +++ b/src/flags/replication.hpp @@ -0,0 +1,26 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include "gflags/gflags.h" + +#ifdef MG_ENTERPRISE +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +DECLARE_bool(coordinator); +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +DECLARE_uint32(coordinator_server_port); +#endif + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +DECLARE_uint64(replication_replica_check_frequency_sec); +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +DECLARE_bool(replication_restore_state_on_startup); diff --git a/src/flags/storage_mode.cpp b/src/flags/storage_mode.cpp index b342719dd..63e9948fd 100644 --- a/src/flags/storage_mode.cpp +++ b/src/flags/storage_mode.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -17,20 +17,14 @@ #include "gflags/gflags.h" -#include <array> - -inline constexpr std::array storage_mode_mappings{ - std::pair{std::string_view{"IN_MEMORY_TRANSACTIONAL"}, memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL}, - std::pair{std::string_view{"IN_MEMORY_ANALYTICAL"}, memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL}, - std::pair{std::string_view{"ON_DISK_TRANSACTIONAL"}, memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL}}; - const std::string storage_mode_help_string = fmt::format("Default storage mode Memgraph uses. Allowed values: {}", - memgraph::utils::GetAllowedEnumValuesString(storage_mode_mappings)); + memgraph::utils::GetAllowedEnumValuesString(memgraph::storage::storage_mode_mappings)); // NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables) DEFINE_VALIDATED_string(storage_mode, "IN_MEMORY_TRANSACTIONAL", storage_mode_help_string.c_str(), { - if (const auto result = memgraph::utils::IsValidEnumValueString(value, storage_mode_mappings); result.HasError()) { + if (const auto result = memgraph::utils::IsValidEnumValueString(value, memgraph::storage::storage_mode_mappings); + result.HasError()) { switch (result.GetError()) { case memgraph::utils::ValidationError::EmptyValue: { std::cout << "Storage mode cannot be empty." << std::endl; @@ -38,7 +32,7 @@ DEFINE_VALIDATED_string(storage_mode, "IN_MEMORY_TRANSACTIONAL", storage_mode_he } case memgraph::utils::ValidationError::InvalidValue: { std::cout << "Invalid value for storage mode. Allowed values: " - << memgraph::utils::GetAllowedEnumValuesString(storage_mode_mappings) << std::endl; + << memgraph::utils::GetAllowedEnumValuesString(memgraph::storage::storage_mode_mappings) << std::endl; break; } } @@ -48,8 +42,8 @@ DEFINE_VALIDATED_string(storage_mode, "IN_MEMORY_TRANSACTIONAL", storage_mode_he }); memgraph::storage::StorageMode memgraph::flags::ParseStorageMode() { - const auto storage_mode = - memgraph::utils::StringToEnum<memgraph::storage::StorageMode>(FLAGS_storage_mode, storage_mode_mappings); + const auto storage_mode = memgraph::utils::StringToEnum<memgraph::storage::StorageMode>( + FLAGS_storage_mode, memgraph::storage::storage_mode_mappings); MG_ASSERT(storage_mode, "Invalid storage mode"); return *storage_mode; } diff --git a/src/glue/SessionHL.cpp b/src/glue/SessionHL.cpp index bff12d188..61c1ab26f 100644 --- a/src/glue/SessionHL.cpp +++ b/src/glue/SessionHL.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -80,7 +80,7 @@ std::vector<memgraph::communication::bolt::Value> TypedValueResultStreamBase::De std::vector<memgraph::communication::bolt::Value> decoded_values; decoded_values.reserve(values.size()); for (const auto &v : values) { - auto maybe_value = memgraph::glue::ToBoltValue(v, *storage_, memgraph::storage::View::NEW); + auto maybe_value = memgraph::glue::ToBoltValue(v, storage_, memgraph::storage::View::NEW); if (maybe_value.HasError()) { switch (maybe_value.GetError()) { case memgraph::storage::Error::DELETED_OBJECT: @@ -112,14 +112,14 @@ std::string SessionHL::GetDefaultDB() { if (user_.has_value()) { return user_->db_access().GetDefault(); } - return memgraph::dbms::kDefaultDB; + return std::string{memgraph::dbms::kDefaultDB}; } #endif std::string SessionHL::GetCurrentDB() const { if (!interpreter_.current_db_.db_acc_) return ""; const auto *db = interpreter_.current_db_.db_acc_->get(); - return db->id(); + return db->name(); } std::optional<std::string> SessionHL::GetServerNameForInit() { @@ -167,10 +167,10 @@ std::map<std::string, memgraph::communication::bolt::Value> SessionHL::Discard(s std::map<std::string, memgraph::communication::bolt::Value> SessionHL::Pull(SessionHL::TEncoder *encoder, std::optional<int> n, std::optional<int> qid) { - // TODO: Update once interpreter can handle non-database queries (db_acc will be nullopt) - auto *db = interpreter_.current_db_.db_acc_->get(); try { - TypedValueResultStream<TEncoder> stream(encoder, db->storage()); + auto &db = interpreter_.current_db_.db_acc_; + auto *storage = db ? db->get()->storage() : nullptr; + TypedValueResultStream<TEncoder> stream(encoder, storage); return DecodeSummary(interpreter_.Pull(&stream, n, qid)); } catch (const memgraph::query::QueryException &e) { // Count the number of specific exceptions thrown @@ -193,17 +193,17 @@ std::pair<std::vector<std::string>, std::optional<int>> SessionHL::Interpret( for (const auto &[key, bolt_param] : params) { params_pv.emplace(key, ToPropertyValue(bolt_param)); } + +#ifdef MG_ENTERPRISE const std::string *username{nullptr}; if (user_) { username = &user_->username(); } -#ifdef MG_ENTERPRISE - // TODO: Update once interpreter can handle non-database queries (db_acc will be nullopt) - auto *db = interpreter_.current_db_.db_acc_->get(); if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) { + auto &db = interpreter_.current_db_.db_acc_; audit_log_->Record(endpoint_.address().to_string(), user_ ? *username : "", query, - memgraph::storage::PropertyValue(params_pv), db->id()); + memgraph::storage::PropertyValue(params_pv), db ? db->get()->name() : "no known database"); } #endif try { @@ -351,11 +351,11 @@ SessionHL::~SessionHL() { std::map<std::string, memgraph::communication::bolt::Value> SessionHL::DecodeSummary( const std::map<std::string, memgraph::query::TypedValue> &summary) { - // TODO: Update once interpreter can handle non-database queries (db_acc will be nullopt) - auto *db = interpreter_.current_db_.db_acc_->get(); + auto &db_acc = interpreter_.current_db_.db_acc_; + auto *storage = db_acc ? db_acc->get()->storage() : nullptr; std::map<std::string, memgraph::communication::bolt::Value> decoded_summary; for (const auto &kv : summary) { - auto maybe_value = ToBoltValue(kv.second, *db->storage(), memgraph::storage::View::NEW); + auto maybe_value = ToBoltValue(kv.second, storage, memgraph::storage::View::NEW); if (maybe_value.HasError()) { switch (maybe_value.GetError()) { case memgraph::storage::Error::DELETED_OBJECT: diff --git a/src/glue/auth.cpp b/src/glue/auth.cpp index 8344ad49d..9be5cd87b 100644 --- a/src/glue/auth.cpp +++ b/src/glue/auth.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -66,6 +66,8 @@ auth::Permission PrivilegeToPermission(query::AuthQuery::Privilege privilege) { return auth::Permission::MULTI_DATABASE_EDIT; case query::AuthQuery::Privilege::MULTI_DATABASE_USE: return auth::Permission::MULTI_DATABASE_USE; + case query::AuthQuery::Privilege::COORDINATOR: + return auth::Permission::COORDINATOR; } } diff --git a/src/glue/auth_global.hpp b/src/glue/auth_global.hpp index 4675b6978..008960c76 100644 --- a/src/glue/auth_global.hpp +++ b/src/glue/auth_global.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -13,4 +13,5 @@ namespace memgraph::glue { inline constexpr std::string_view kDefaultUserRoleRegex = "[a-zA-Z0-9_.+-@]+"; +static constexpr std::string_view kDefaultPasswordRegex = ".+"; } // namespace memgraph::glue diff --git a/src/glue/auth_handler.cpp b/src/glue/auth_handler.cpp index b4ebfcd2a..f3efb6ba0 100644 --- a/src/glue/auth_handler.cpp +++ b/src/glue/auth_handler.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -249,25 +249,10 @@ std::vector<std::vector<memgraph::query::TypedValue>> ShowFineGrainedRolePrivile namespace memgraph::glue { AuthQueryHandler::AuthQueryHandler( - memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth, - std::string name_regex_string) - : auth_(auth), name_regex_string_(std::move(name_regex_string)), name_regex_(name_regex_string_) {} + memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth) + : auth_(auth) {} bool AuthQueryHandler::CreateUser(const std::string &username, const std::optional<std::string> &password) { - if (name_regex_string_ != kDefaultUserRoleRegex) { - if (const auto license_check_result = - memgraph::license::global_license_checker.IsEnterpriseValid(memgraph::utils::global_settings); - license_check_result.HasError()) { - throw memgraph::auth::AuthException( - "Custom user/role regex is a Memgraph Enterprise feature. Please set the config " - "(\"--auth-user-or-role-name-regex\") to its default value (\"{}\") or remove the flag.\n{}", - kDefaultUserRoleRegex, - memgraph::license::LicenseCheckErrorToString(license_check_result.GetError(), "user/role regex")); - } - } - if (!std::regex_match(username, name_regex_)) { - throw query::QueryRuntimeException("Invalid user name."); - } try { const auto [first_user, user_added] = std::invoke([&, this] { auto locked_auth = auth_->Lock(); @@ -294,7 +279,7 @@ bool AuthQueryHandler::CreateUser(const std::string &username, const std::option ); #ifdef MG_ENTERPRISE GrantDatabaseToUser(auth::kAllDatabases, username); - SetMainDatabase(username, dbms::kDefaultDB); + SetMainDatabase(dbms::kDefaultDB, username); #endif } @@ -305,9 +290,6 @@ bool AuthQueryHandler::CreateUser(const std::string &username, const std::option } bool AuthQueryHandler::DropUser(const std::string &username) { - if (!std::regex_match(username, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid user name."); - } try { auto locked_auth = auth_->Lock(); auto user = locked_auth->GetUser(username); @@ -319,16 +301,13 @@ bool AuthQueryHandler::DropUser(const std::string &username) { } void AuthQueryHandler::SetPassword(const std::string &username, const std::optional<std::string> &password) { - if (!std::regex_match(username, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid user name."); - } try { auto locked_auth = auth_->Lock(); auto user = locked_auth->GetUser(username); if (!user) { throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist.", username); } - user->UpdatePassword(password); + locked_auth->UpdatePassword(*user, password); locked_auth->SaveUser(*user); } catch (const memgraph::auth::AuthException &e) { throw memgraph::query::QueryRuntimeException(e.what()); @@ -336,9 +315,6 @@ void AuthQueryHandler::SetPassword(const std::string &username, const std::optio } bool AuthQueryHandler::CreateRole(const std::string &rolename) { - if (!std::regex_match(rolename, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid role name."); - } try { auto locked_auth = auth_->Lock(); return locked_auth->AddRole(rolename).has_value(); @@ -349,9 +325,6 @@ bool AuthQueryHandler::CreateRole(const std::string &rolename) { #ifdef MG_ENTERPRISE bool AuthQueryHandler::RevokeDatabaseFromUser(const std::string &db, const std::string &username) { - if (!std::regex_match(username, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid user name."); - } try { auto locked_auth = auth_->Lock(); auto user = locked_auth->GetUser(username); @@ -363,9 +336,6 @@ bool AuthQueryHandler::RevokeDatabaseFromUser(const std::string &db, const std:: } bool AuthQueryHandler::GrantDatabaseToUser(const std::string &db, const std::string &username) { - if (!std::regex_match(username, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid user name."); - } try { auto locked_auth = auth_->Lock(); auto user = locked_auth->GetUser(username); @@ -378,9 +348,6 @@ bool AuthQueryHandler::GrantDatabaseToUser(const std::string &db, const std::str std::vector<std::vector<memgraph::query::TypedValue>> AuthQueryHandler::GetDatabasePrivileges( const std::string &username) { - if (!std::regex_match(username, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid user or role name."); - } try { auto locked_auth = auth_->ReadLock(); auto user = locked_auth->GetUser(username); @@ -393,10 +360,7 @@ std::vector<std::vector<memgraph::query::TypedValue>> AuthQueryHandler::GetDatab } } -bool AuthQueryHandler::SetMainDatabase(const std::string &db, const std::string &username) { - if (!std::regex_match(username, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid user name."); - } +bool AuthQueryHandler::SetMainDatabase(std::string_view db, const std::string &username) { try { auto locked_auth = auth_->Lock(); auto user = locked_auth->GetUser(username); @@ -417,9 +381,6 @@ void AuthQueryHandler::DeleteDatabase(std::string_view db) { #endif bool AuthQueryHandler::DropRole(const std::string &rolename) { - if (!std::regex_match(rolename, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid role name."); - } try { auto locked_auth = auth_->Lock(); auto role = locked_auth->GetRole(rolename); @@ -465,9 +426,6 @@ std::vector<memgraph::query::TypedValue> AuthQueryHandler::GetRolenames() { } std::optional<std::string> AuthQueryHandler::GetRolenameForUser(const std::string &username) { - if (!std::regex_match(username, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid user name."); - } try { auto locked_auth = auth_->ReadLock(); auto user = locked_auth->GetUser(username); @@ -485,9 +443,6 @@ std::optional<std::string> AuthQueryHandler::GetRolenameForUser(const std::strin } std::vector<memgraph::query::TypedValue> AuthQueryHandler::GetUsernamesForRole(const std::string &rolename) { - if (!std::regex_match(rolename, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid role name."); - } try { auto locked_auth = auth_->ReadLock(); auto role = locked_auth->GetRole(rolename); @@ -507,12 +462,6 @@ std::vector<memgraph::query::TypedValue> AuthQueryHandler::GetUsernamesForRole(c } void AuthQueryHandler::SetRole(const std::string &username, const std::string &rolename) { - if (!std::regex_match(username, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid user name."); - } - if (!std::regex_match(rolename, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid role name."); - } try { auto locked_auth = auth_->Lock(); auto user = locked_auth->GetUser(username); @@ -535,9 +484,6 @@ void AuthQueryHandler::SetRole(const std::string &username, const std::string &r } void AuthQueryHandler::ClearRole(const std::string &username) { - if (!std::regex_match(username, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid user name."); - } try { auto locked_auth = auth_->Lock(); auto user = locked_auth->GetUser(username); @@ -552,9 +498,6 @@ void AuthQueryHandler::ClearRole(const std::string &username) { } std::vector<std::vector<memgraph::query::TypedValue>> AuthQueryHandler::GetPrivileges(const std::string &user_or_role) { - if (!std::regex_match(user_or_role, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid user or role name."); - } try { auto locked_auth = auth_->ReadLock(); std::vector<std::vector<memgraph::query::TypedValue>> grants; @@ -704,9 +647,6 @@ void AuthQueryHandler::EditPermissions( const TEditFineGrainedPermissionsFun &edit_fine_grained_permissions_fun #endif ) { - if (!std::regex_match(user_or_role, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid user or role name."); - } try { std::vector<memgraph::auth::Permission> permissions; permissions.reserve(privileges.size()); diff --git a/src/glue/auth_handler.hpp b/src/glue/auth_handler.hpp index 8798c150a..c226a4560 100644 --- a/src/glue/auth_handler.hpp +++ b/src/glue/auth_handler.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -24,12 +24,9 @@ namespace memgraph::glue { class AuthQueryHandler final : public memgraph::query::AuthQueryHandler { memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_; - std::string name_regex_string_; - std::regex name_regex_; public: - AuthQueryHandler(memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth, - std::string name_regex_string); + AuthQueryHandler(memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth); bool CreateUser(const std::string &username, const std::optional<std::string> &password) override; @@ -44,7 +41,7 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler { std::vector<std::vector<memgraph::query::TypedValue>> GetDatabasePrivileges(const std::string &username) override; - bool SetMainDatabase(const std::string &db, const std::string &username) override; + bool SetMainDatabase(std::string_view db, const std::string &username) override; void DeleteDatabase(std::string_view db) override; #endif diff --git a/src/glue/communication.cpp b/src/glue/communication.cpp index 60181e877..2c71e37c7 100644 --- a/src/glue/communication.cpp +++ b/src/glue/communication.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -73,8 +73,14 @@ storage::Result<communication::bolt::Edge> ToBoltEdge(const query::EdgeAccessor return ToBoltEdge(edge.impl_, db, view); } -storage::Result<Value> ToBoltValue(const query::TypedValue &value, const storage::Storage &db, storage::View view) { +storage::Result<Value> ToBoltValue(const query::TypedValue &value, const storage::Storage *db, storage::View view) { + auto check_db = [db]() { + if (db == nullptr) [[unlikely]] + throw communication::bolt::ValueException("Database needed for TypeValue conversion."); + }; + switch (value.type()) { + // No database needed case query::TypedValue::Type::Null: return Value(); case query::TypedValue::Type::Bool: @@ -85,16 +91,16 @@ storage::Result<Value> ToBoltValue(const query::TypedValue &value, const storage return Value(value.ValueDouble()); case query::TypedValue::Type::String: return Value(std::string(value.ValueString())); - case query::TypedValue::Type::List: { - std::vector<Value> values; - values.reserve(value.ValueList().size()); - for (const auto &v : value.ValueList()) { - auto maybe_value = ToBoltValue(v, db, view); - if (maybe_value.HasError()) return maybe_value.GetError(); - values.emplace_back(std::move(*maybe_value)); - } - return Value(std::move(values)); - } + case query::TypedValue::Type::Date: + return Value(value.ValueDate()); + case query::TypedValue::Type::LocalTime: + return Value(value.ValueLocalTime()); + case query::TypedValue::Type::LocalDateTime: + return Value(value.ValueLocalDateTime()); + case query::TypedValue::Type::Duration: + return Value(value.ValueDuration()); + + // Database potentially not required case query::TypedValue::Type::Map: { std::map<std::string, Value> map; for (const auto &kv : value.ValueMap()) { @@ -104,35 +110,48 @@ storage::Result<Value> ToBoltValue(const query::TypedValue &value, const storage } return Value(std::move(map)); } + + // Database is required + case query::TypedValue::Type::List: { + check_db(); + std::vector<Value> values; + values.reserve(value.ValueList().size()); + for (const auto &v : value.ValueList()) { + auto maybe_value = ToBoltValue(v, db, view); + if (maybe_value.HasError()) return maybe_value.GetError(); + values.emplace_back(std::move(*maybe_value)); + } + return Value(std::move(values)); + } case query::TypedValue::Type::Vertex: { - auto maybe_vertex = ToBoltVertex(value.ValueVertex(), db, view); + check_db(); + auto maybe_vertex = ToBoltVertex(value.ValueVertex(), *db, view); if (maybe_vertex.HasError()) return maybe_vertex.GetError(); return Value(std::move(*maybe_vertex)); } case query::TypedValue::Type::Edge: { - auto maybe_edge = ToBoltEdge(value.ValueEdge(), db, view); + check_db(); + auto maybe_edge = ToBoltEdge(value.ValueEdge(), *db, view); if (maybe_edge.HasError()) return maybe_edge.GetError(); return Value(std::move(*maybe_edge)); } case query::TypedValue::Type::Path: { - auto maybe_path = ToBoltPath(value.ValuePath(), db, view); + check_db(); + auto maybe_path = ToBoltPath(value.ValuePath(), *db, view); if (maybe_path.HasError()) return maybe_path.GetError(); return Value(std::move(*maybe_path)); } - case query::TypedValue::Type::Date: - return Value(value.ValueDate()); - case query::TypedValue::Type::LocalTime: - return Value(value.ValueLocalTime()); - case query::TypedValue::Type::LocalDateTime: - return Value(value.ValueLocalDateTime()); - case query::TypedValue::Type::Duration: - return Value(value.ValueDuration()); - case query::TypedValue::Type::Function: - throw communication::bolt::ValueException("Unsupported conversion from TypedValue::Function to Value"); - case query::TypedValue::Type::Graph: - auto maybe_graph = ToBoltGraph(value.ValueGraph(), db, view); + case query::TypedValue::Type::Graph: { + check_db(); + auto maybe_graph = ToBoltGraph(value.ValueGraph(), *db, view); if (maybe_graph.HasError()) return maybe_graph.GetError(); return Value(std::move(*maybe_graph)); + } + + // Unsupported conversions + case query::TypedValue::Type::Function: { + throw communication::bolt::ValueException("Unsupported conversion from TypedValue::Function to Value"); + } } } diff --git a/src/glue/communication.hpp b/src/glue/communication.hpp index 0e3b39f4d..737f32db2 100644 --- a/src/glue/communication.hpp +++ b/src/glue/communication.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -65,7 +65,7 @@ storage::Result<std::map<std::string, communication::bolt::Value>> ToBoltGraph(c /// @param storage::View for ToBoltVertex and ToBoltEdge. /// /// @throw std::bad_alloc -storage::Result<communication::bolt::Value> ToBoltValue(const query::TypedValue &value, const storage::Storage &db, +storage::Result<communication::bolt::Value> ToBoltValue(const query::TypedValue &value, const storage::Storage *db, storage::View view); query::TypedValue ToTypedValue(const communication::bolt::Value &value); diff --git a/src/io/CMakeLists.txt b/src/io/CMakeLists.txt index 128e87114..428cad4b2 100644 --- a/src/io/CMakeLists.txt +++ b/src/io/CMakeLists.txt @@ -8,4 +8,5 @@ find_package(fmt REQUIRED) find_package(Threads REQUIRED) add_library(mg-io STATIC ${io_src_files}) +add_library(mg::io ALIAS mg-io) target_link_libraries(mg-io stdc++fs Threads::Threads fmt::fmt mg-utils) diff --git a/src/io/network/endpoint.cpp b/src/io/network/endpoint.cpp index e719180f5..e9032e42e 100644 --- a/src/io/network/endpoint.cpp +++ b/src/io/network/endpoint.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -139,18 +139,13 @@ Endpoint::Endpoint(std::string ip_address, uint16_t port) : address(std::move(ip } // NOLINTNEXTLINE -Endpoint::Endpoint(needs_resolving_t, std::string hostname, uint16_t port) : port(port) { - address = ResolveHostnameIntoIpAddress(hostname, port); - IpFamily ip_family = GetIpFamily(address); - if (ip_family == IpFamily::NONE) { - throw NetworkError("Not a valid IPv4 or IPv6 address: {}", address); - } - family = ip_family; -} +Endpoint::Endpoint(needs_resolving_t, std::string hostname, uint16_t port) + : address(std::move(hostname)), port(port), family{GetIpFamily(address)} {} std::ostream &operator<<(std::ostream &os, const Endpoint &endpoint) { // no need to cover the IpFamily::NONE case, as you can't even construct an // Endpoint object if the IpFamily is NONE (i.e. the IP address is invalid) + // unless you use DNS hostname if (endpoint.family == Endpoint::IpFamily::IP6) { return os << "[" << endpoint.address << "]" << ":" << endpoint.port; @@ -166,12 +161,12 @@ bool Endpoint::IsResolvableAddress(const std::string &address, uint16_t port) { }; addrinfo *info = nullptr; auto status = getaddrinfo(address.c_str(), std::to_string(port).c_str(), &hints, &info); - freeaddrinfo(info); + if (info) freeaddrinfo(info); return status == 0; } std::optional<std::pair<std::string, uint16_t>> Endpoint::ParseSocketOrAddress( - const std::string &address, const std::optional<uint16_t> default_port = {}) { + const std::string &address, const std::optional<uint16_t> default_port) { const std::string delimiter = ":"; std::vector<std::string> parts = utils::Split(address, delimiter); if (parts.size() == 1) { @@ -189,34 +184,4 @@ std::optional<std::pair<std::string, uint16_t>> Endpoint::ParseSocketOrAddress( return std::nullopt; } -std::string Endpoint::ResolveHostnameIntoIpAddress(const std::string &address, uint16_t port) { - addrinfo hints{ - .ai_flags = AI_PASSIVE, - .ai_family = AF_UNSPEC, // IPv4 and IPv6 - .ai_socktype = SOCK_STREAM // TCP socket - }; - addrinfo *info = nullptr; - auto status = getaddrinfo(address.c_str(), std::to_string(port).c_str(), &hints, &info); - if (status != 0) throw NetworkError(gai_strerror(status)); - - for (auto *result = info; result != nullptr; result = result->ai_next) { - if (result->ai_family == AF_INET) { - char ipstr[INET_ADDRSTRLEN]; - auto *ipv4 = reinterpret_cast<struct sockaddr_in *>(result->ai_addr); - inet_ntop(AF_INET, &(ipv4->sin_addr), ipstr, sizeof(ipstr)); - freeaddrinfo(info); - return ipstr; - } - if (result->ai_family == AF_INET6) { - char ipstr[INET6_ADDRSTRLEN]; - auto *ipv6 = reinterpret_cast<struct sockaddr_in6 *>(result->ai_addr); - inet_ntop(AF_INET6, &(ipv6->sin6_addr), ipstr, sizeof(ipstr)); - freeaddrinfo(info); - return ipstr; - } - } - freeaddrinfo(info); - throw NetworkError("Not a valid address: {}", address); -} - } // namespace memgraph::io::network diff --git a/src/io/network/socket.cpp b/src/io/network/socket.cpp index 7394ea39d..2d949cd4d 100644 --- a/src/io/network/socket.cpp +++ b/src/io/network/socket.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -15,6 +15,7 @@ #include <poll.h> #include "io/network/addrinfo.hpp" +#include "io/network/network_error.hpp" #include "io/network/socket.hpp" #include "utils/likely.hpp" #include "utils/logging.hpp" @@ -55,17 +56,21 @@ bool Socket::IsOpen() const { return socket_ != -1; } bool Socket::Connect(const Endpoint &endpoint) { if (socket_ != -1) return false; - for (const auto &it : AddrInfo{endpoint}) { - int sfd = socket(it.ai_family, it.ai_socktype, it.ai_protocol); - if (sfd == -1) continue; - if (connect(sfd, it.ai_addr, it.ai_addrlen) == 0) { - socket_ = sfd; - endpoint_ = endpoint; - break; + try { + for (const auto &it : AddrInfo{endpoint}) { + int sfd = socket(it.ai_family, it.ai_socktype, it.ai_protocol); + if (sfd == -1) continue; + if (connect(sfd, it.ai_addr, it.ai_addrlen) == 0) { + socket_ = sfd; + endpoint_ = endpoint; + break; + } + // If the connect failed close the file descriptor to prevent file + // descriptors being leaked + close(sfd); } - // If the connect failed close the file descriptor to prevent file - // descriptors being leaked - close(sfd); + } catch (const NetworkError &e) { + return false; } return !(socket_ == -1); diff --git a/src/kvstore/kvstore.cpp b/src/kvstore/kvstore.cpp index 877d6f9bd..1219b8527 100644 --- a/src/kvstore/kvstore.cpp +++ b/src/kvstore/kvstore.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -51,7 +51,7 @@ KVStore &KVStore::operator=(KVStore &&other) { return *this; } -bool KVStore::Put(const std::string &key, const std::string &value) { +bool KVStore::Put(std::string_view key, std::string_view value) { auto s = pimpl_->db->Put(rocksdb::WriteOptions(), key, value); return s.ok(); } @@ -65,7 +65,7 @@ bool KVStore::PutMultiple(const std::map<std::string, std::string> &items) { return s.ok(); } -std::optional<std::string> KVStore::Get(const std::string &key) const noexcept { +std::optional<std::string> KVStore::Get(std::string_view key) const noexcept { std::string value; auto s = pimpl_->db->Get(rocksdb::ReadOptions(), key, &value); if (!s.ok()) return std::nullopt; diff --git a/src/kvstore/kvstore.hpp b/src/kvstore/kvstore.hpp index a67d01c8c..b9675d75b 100644 --- a/src/kvstore/kvstore.hpp +++ b/src/kvstore/kvstore.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -61,7 +61,7 @@ class KVStore final { * @return true if the value has been successfully stored. * In case of any error false is going to be returned. */ - bool Put(const std::string &key, const std::string &value); + bool Put(std::string_view key, std::string_view value); /** * Store values under the given keys. @@ -81,7 +81,7 @@ class KVStore final { * @return Value for the given key. std::nullopt in case of any error * OR the value doesn't exist. */ - std::optional<std::string> Get(const std::string &key) const noexcept; + std::optional<std::string> Get(std::string_view key) const noexcept; /** * Deletes the key and corresponding value from storage. diff --git a/src/license/license.cpp b/src/license/license.cpp index ff5306dab..653e66222 100644 --- a/src/license/license.cpp +++ b/src/license/license.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -92,7 +92,7 @@ void RegisterLicenseSettings(LicenseChecker &license_checker, utils::Settings &s // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) LicenseChecker global_license_checker; -LicenseChecker::~LicenseChecker() { scheduler_.Stop(); } +LicenseChecker::~LicenseChecker() { Finalize(); } std::pair<std::string, std::string> LicenseChecker::ExtractLicenseInfo(const utils::Settings &settings) const { if (license_info_override_) { diff --git a/src/license/license.hpp b/src/license/license.hpp index da47ae0ff..b2e7dcce4 100644 --- a/src/license/license.hpp +++ b/src/license/license.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -89,6 +89,8 @@ struct LicenseChecker { utils::Synchronized<std::optional<LicenseInfo>, utils::SpinLock> &GetLicenseInfo(); + void Finalize() { scheduler_.Stop(); } + private: std::pair<std::string, std::string> ExtractLicenseInfo(const utils::Settings &settings) const; void RevalidateLicense(const utils::Settings &settings); diff --git a/src/memgraph.cpp b/src/memgraph.cpp index 057b30982..cbd63490e 100644 --- a/src/memgraph.cpp +++ b/src/memgraph.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -11,13 +11,11 @@ #include <cstdint> #include "audit/log.hpp" -#include "communication/metrics.hpp" #include "communication/websocket/auth.hpp" #include "communication/websocket/server.hpp" #include "dbms/constants.hpp" #include "dbms/inmemory/replication_handlers.hpp" #include "flags/all.hpp" -#include "flags/run_time_configurable.hpp" #include "glue/MonitoringServerT.hpp" #include "glue/ServerT.hpp" #include "glue/auth_checker.hpp" @@ -33,9 +31,9 @@ #include "query/procedure/module.hpp" #include "query/procedure/py_module.hpp" #include "requests/requests.hpp" +#include "storage/v2/durability/durability.hpp" #include "telemetry/telemetry.hpp" #include "utils/signals.hpp" -#include "utils/skip_list.hpp" #include "utils/sysinfo/memory.hpp" #include "utils/system_info.hpp" #include "utils/terminate_handler.hpp" @@ -73,7 +71,7 @@ void InitFromCypherlFile(memgraph::query::InterpreterContext &ctx, memgraph::dbm spdlog::warn("{} The rest of the init-file will be run.", e.what()); } if (audit_log) { - audit_log->Record("", "", line, {}, memgraph::dbms::kDefaultDB); + audit_log->Record("", "", line, {}, std::string{memgraph::dbms::kDefaultDB}); } } } @@ -163,7 +161,7 @@ int main(int argc, char **argv) { // libstd. auto gil = memgraph::py::EnsureGIL(); // NOLINTNEXTLINE(hicpp-signed-bitwise) - auto *flag = PyLong_FromLong(RTLD_NOW | RTLD_DEEPBIND); + auto *flag = PyLong_FromLong(RTLD_NOW); auto *setdl = PySys_GetObject("setdlopenflags"); MG_ASSERT(setdl); auto *arg = PyTuple_New(1); @@ -184,6 +182,10 @@ int main(int argc, char **argv) { "https://memgr.ph/python")); } + memgraph::utils::Scheduler python_gc_scheduler; + python_gc_scheduler.Run("Python GC", std::chrono::seconds(FLAGS_storage_python_gc_cycle_sec), + [] { memgraph::query::procedure::PyCollectGarbage(); }); + // Initialize the communication library. memgraph::communication::SSLInit sslInit; @@ -253,6 +255,8 @@ int main(int argc, char **argv) { // register all runtime settings memgraph::license::RegisterLicenseSettings(memgraph::license::global_license_checker, memgraph::utils::global_settings); + memgraph::utils::OnScopeExit global_license_finalizer([] { memgraph::license::global_license_checker.Finalize(); }); + memgraph::flags::run_time::Initialize(); memgraph::license::global_license_checker.CheckEnvLicense(); @@ -294,8 +298,7 @@ int main(int argc, char **argv) { memgraph::storage::Config db_config{ .gc = {.type = memgraph::storage::Config::Gc::Type::PERIODIC, .interval = std::chrono::seconds(FLAGS_storage_gc_cycle_sec)}, - .items = {.properties_on_edges = FLAGS_storage_properties_on_edges, - .enable_schema_metadata = FLAGS_storage_enable_schema_metadata}, + .durability = {.storage_directory = FLAGS_data_directory, .recover_on_startup = FLAGS_storage_recover_on_startup || FLAGS_data_recovery_on_startup, .snapshot_retention_count = FLAGS_storage_snapshot_retention_count, @@ -317,7 +320,14 @@ int main(int argc, char **argv) { .id_name_mapper_directory = FLAGS_data_directory + "/rocksdb_id_name_mapper", .durability_directory = FLAGS_data_directory + "/rocksdb_durability", .wal_directory = FLAGS_data_directory + "/rocksdb_wal"}, - .storage_mode = memgraph::flags::ParseStorageMode()}; + .salient.items = {.properties_on_edges = FLAGS_storage_properties_on_edges, + .enable_schema_metadata = FLAGS_storage_enable_schema_metadata}, + .salient.storage_mode = memgraph::flags::ParseStorageMode()}; + + memgraph::utils::Scheduler jemalloc_purge_scheduler; + jemalloc_purge_scheduler.Run("Jemalloc purge", std::chrono::seconds(FLAGS_storage_gc_cycle_sec), + [] { memgraph::memory::PurgeUnusedMemory(); }); + if (FLAGS_storage_snapshot_interval_sec == 0) { if (FLAGS_storage_wal_enabled) { LOG_FATAL( @@ -347,11 +357,10 @@ int main(int argc, char **argv) { .stream_transaction_retry_interval = std::chrono::milliseconds(FLAGS_stream_transaction_retry_interval)}; auto auth_glue = - [flag = FLAGS_auth_user_or_role_name_regex]( - memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth, - std::unique_ptr<memgraph::query::AuthQueryHandler> &ah, std::unique_ptr<memgraph::query::AuthChecker> &ac) { + [](memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth, + std::unique_ptr<memgraph::query::AuthQueryHandler> &ah, std::unique_ptr<memgraph::query::AuthChecker> &ac) { // Glue high level auth implementations to the query side - ah = std::make_unique<memgraph::glue::AuthQueryHandler>(auth, flag); + ah = std::make_unique<memgraph::glue::AuthQueryHandler>(auth); ac = std::make_unique<memgraph::glue::AuthChecker>(auth); // Handle users passed via arguments auto *maybe_username = std::getenv(kMgUser); @@ -367,9 +376,10 @@ int main(int argc, char **argv) { } }; - // WIP - memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> auth_{data_directory / - "auth"}; + memgraph::auth::Auth::Config auth_config{FLAGS_auth_user_or_role_name_regex, FLAGS_auth_password_strength_regex, + FLAGS_auth_password_permit_null}; + memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> auth_{ + data_directory / "auth", auth_config}; std::unique_ptr<memgraph::query::AuthQueryHandler> auth_handler; std::unique_ptr<memgraph::query::AuthChecker> auth_checker; auth_glue(&auth_, auth_handler, auth_checker); @@ -377,7 +387,7 @@ int main(int argc, char **argv) { memgraph::dbms::DbmsHandler dbms_handler(db_config #ifdef MG_ENTERPRISE , - &auth_, FLAGS_data_recovery_on_startup, FLAGS_storage_delete_on_drop + &auth_, FLAGS_data_recovery_on_startup #endif ); auto db_acc = dbms_handler.Get(); @@ -537,6 +547,7 @@ int main(int argc, char **argv) { memgraph::query::procedure::gModuleRegistry.UnloadAllModules(); + python_gc_scheduler.Stop(); Py_END_ALLOW_THREADS; // Shutdown Python Py_Finalize(); diff --git a/src/memory/global_memory_control.cpp b/src/memory/global_memory_control.cpp index 57d97bcaa..ab75435ca 100644 --- a/src/memory/global_memory_control.cpp +++ b/src/memory/global_memory_control.cpp @@ -119,7 +119,7 @@ static bool my_commit(extent_hooks_t *extent_hooks, void *addr, size_t size, siz memgraph::utils::total_memory_tracker.Alloc(static_cast<int64_t>(length)); if (GetQueriesMemoryControl().IsThreadTracked()) [[unlikely]] { - GetQueriesMemoryControl().TrackFreeOnCurrentThread(size); + GetQueriesMemoryControl().TrackAllocOnCurrentThread(size); } return false; diff --git a/src/memory/query_memory_control.cpp b/src/memory/query_memory_control.cpp index 91730c900..5e569bd13 100644 --- a/src/memory/query_memory_control.cpp +++ b/src/memory/query_memory_control.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -36,14 +36,22 @@ namespace memgraph::memory { void QueriesMemoryControl::UpdateThreadToTransactionId(const std::thread::id &thread_id, uint64_t transaction_id) { auto accessor = thread_id_to_transaction_id.access(); - accessor.insert({thread_id, transaction_id}); + auto elem = accessor.find(thread_id); + if (elem == accessor.end()) { + accessor.insert({thread_id, {transaction_id, 1}}); + } else { + elem->transaction_id.cnt++; + } } void QueriesMemoryControl::EraseThreadToTransactionId(const std::thread::id &thread_id, uint64_t transaction_id) { auto accessor = thread_id_to_transaction_id.access(); auto elem = accessor.find(thread_id); MG_ASSERT(elem != accessor.end() && elem->transaction_id == transaction_id); - accessor.remove(thread_id); + elem->transaction_id.cnt--; + if (elem->transaction_id.cnt == 0) { + accessor.remove(thread_id); + } } void QueriesMemoryControl::TrackAllocOnCurrentThread(size_t size) { diff --git a/src/memory/query_memory_control.hpp b/src/memory/query_memory_control.hpp index 901917757..3852027a5 100644 --- a/src/memory/query_memory_control.hpp +++ b/src/memory/query_memory_control.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -78,9 +78,20 @@ class QueriesMemoryControl { bool IsThreadTracked(); private: + struct TransactionId { + uint64_t id; + uint64_t cnt; + + bool operator<(const TransactionId &other) const { return id < other.id; } + bool operator==(const TransactionId &other) const { return id == other.id; } + + bool operator<(uint64_t other) const { return id < other; } + bool operator==(uint64_t other) const { return id == other; } + }; + struct ThreadIdToTransactionId { std::thread::id thread_id; - uint64_t transaction_id; + TransactionId transaction_id; bool operator<(const ThreadIdToTransactionId &other) const { return thread_id < other.thread_id; } bool operator==(const ThreadIdToTransactionId &other) const { return thread_id == other.thread_id; } @@ -98,6 +109,9 @@ class QueriesMemoryControl { bool operator<(uint64_t other) const { return transaction_id < other; } bool operator==(uint64_t other) const { return transaction_id == other; } + + bool operator<(TransactionId other) const { return transaction_id < other.id; } + bool operator==(TransactionId other) const { return transaction_id == other.id; } }; utils::SkipList<ThreadIdToTransactionId> thread_id_to_transaction_id; diff --git a/src/mg_import_csv.cpp b/src/mg_import_csv.cpp index e0a82584b..cbfb905aa 100644 --- a/src/mg_import_csv.cpp +++ b/src/mg_import_csv.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -32,6 +32,8 @@ #include "utils/timer.hpp" #include "version.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; + bool ValidateControlCharacter(const char *flagname, const std::string &value) { if (value.empty()) { printf("The argument '%s' cannot be empty\n", flagname); @@ -425,7 +427,7 @@ void ProcessNodeRow(memgraph::storage::Storage *store, const std::vector<std::st const std::vector<Field> &fields, const std::vector<std::string> &additional_labels, std::unordered_map<NodeId, memgraph::storage::Gid> *node_id_map) { std::optional<NodeId> id; - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto node = acc->CreateVertex(); for (size_t i = 0; i < row.size(); ++i) { const auto &field = fields[i]; @@ -571,7 +573,7 @@ void ProcessRelationshipsRow(memgraph::storage::Storage *store, const std::vecto if (!end_id) throw LoadException("END_ID must be set"); if (!relationship_type) throw LoadException("Relationship TYPE must be set"); - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto from_node = acc->FindVertex(*start_id, memgraph::storage::View::NEW); if (!from_node) throw LoadException("From node must be in the storage"); auto to_node = acc->FindVertex(*end_id, memgraph::storage::View::NEW); @@ -705,12 +707,11 @@ int main(int argc, char *argv[]) { std::unordered_map<NodeId, memgraph::storage::Gid> node_id_map; memgraph::storage::Config config{ - - .items = {.properties_on_edges = FLAGS_storage_properties_on_edges}, .durability = {.storage_directory = FLAGS_data_directory, .recover_on_startup = false, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::DISABLED, .snapshot_on_exit = true}, + .salient = {.items = {.properties_on_edges = FLAGS_storage_properties_on_edges}}, }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; auto store = memgraph::dbms::CreateInMemoryStorage(config, repl_state); diff --git a/src/query/auth_query_handler.hpp b/src/query/auth_query_handler.hpp index 908dd3ebc..693103354 100644 --- a/src/query/auth_query_handler.hpp +++ b/src/query/auth_query_handler.hpp @@ -57,7 +57,7 @@ class AuthQueryHandler { /// Return true if main database set successfully /// @throw QueryRuntimeException if an error ocurred. - virtual bool SetMainDatabase(const std::string &db, const std::string &username) = 0; + virtual bool SetMainDatabase(std::string_view db, const std::string &username) = 0; /// Delete database from all users /// @throw QueryRuntimeException if an error ocurred. diff --git a/src/query/common.hpp b/src/query/common.hpp index 6f45760fe..054714164 100644 --- a/src/query/common.hpp +++ b/src/query/common.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -72,8 +72,9 @@ class TypedValueVectorCompare final { /// Raise QueryRuntimeException if the value for symbol isn't of expected type. inline void ExpectType(const Symbol &symbol, const TypedValue &value, TypedValue::Type expected) { - if (value.type() != expected) + if (value.type() != expected) [[unlikely]] { throw QueryRuntimeException("Expected a {} for '{}', but got {}.", expected, symbol.name(), value.type()); + } } inline void ProcessError(const storage::Error error) { diff --git a/src/query/config.hpp b/src/query/config.hpp index 64e2da5bb..88c3dd00e 100644 --- a/src/query/config.hpp +++ b/src/query/config.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source diff --git a/src/query/context.hpp b/src/query/context.hpp index 3040d6e10..f1522053c 100644 --- a/src/query/context.hpp +++ b/src/query/context.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source diff --git a/src/query/cypher_query_interpreter.cpp b/src/query/cypher_query_interpreter.cpp index d3ddc22c4..30966119b 100644 --- a/src/query/cypher_query_interpreter.cpp +++ b/src/query/cypher_query_interpreter.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -12,7 +12,6 @@ #include "query/cypher_query_interpreter.hpp" #include "query/frontend/ast/cypher_main_visitor.hpp" #include "query/frontend/opencypher/parser.hpp" -#include "utils/synchronized.hpp" // NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables) DEFINE_bool(query_cost_planner, true, "Use the cost-estimating query planner."); @@ -80,7 +79,7 @@ ParsedQuery ParseQuery(const std::string &query_string, const std::map<std::stri // Convert the ANTLR4 parse tree into an AST. AstStorage ast_storage; frontend::ParsingContext context{.is_query_cached = true}; - frontend::CypherMainVisitor visitor(context, &ast_storage); + frontend::CypherMainVisitor visitor(context, &ast_storage, ¶meters); visitor.visit(parser->tree()); diff --git a/src/query/db_accessor.hpp b/src/query/db_accessor.hpp index ed7dde409..71b997d9e 100644 --- a/src/query/db_accessor.hpp +++ b/src/query/db_accessor.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -541,7 +541,10 @@ class DbAccessor final { void AdvanceCommand() { accessor_->AdvanceCommand(); } - utils::BasicResult<storage::StorageManipulationError, void> Commit() { return accessor_->Commit(); } + utils::BasicResult<storage::StorageManipulationError, void> Commit(storage::CommitReplArgs reparg = {}, + storage::DatabaseAccessProtector db_acc = {}) { + return accessor_->Commit(std::move(reparg), std::move(db_acc)); + } void Abort() { accessor_->Abort(); } diff --git a/src/query/dump.cpp b/src/query/dump.cpp index a1421cbf9..2925023fb 100644 --- a/src/query/dump.cpp +++ b/src/query/dump.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -11,6 +11,7 @@ #include "query/dump.hpp" +#include <algorithm> #include <iomanip> #include <limits> #include <map> @@ -21,9 +22,11 @@ #include <fmt/format.h> +#include "dbms/database.hpp" #include "query/db_accessor.hpp" #include "query/exceptions.hpp" #include "query/stream.hpp" +#include "query/trigger_context.hpp" #include "query/typed_value.hpp" #include "storage/v2/property_value.hpp" #include "storage/v2/storage.hpp" @@ -260,10 +263,20 @@ void DumpUniqueConstraint(std::ostream *os, query::DbAccessor *dba, storage::Lab *os << " IS UNIQUE;"; } +const char *triggerPhaseToString(TriggerPhase phase) { + switch (phase) { + case TriggerPhase::BEFORE_COMMIT: + return "BEFORE COMMIT EXECUTE"; + case TriggerPhase::AFTER_COMMIT: + return "AFTER COMMIT EXECUTE"; + } +} + } // namespace -PullPlanDump::PullPlanDump(DbAccessor *dba) +PullPlanDump::PullPlanDump(DbAccessor *dba, dbms::DatabaseAccess db_acc) : dba_(dba), + db_acc_(db_acc), vertices_iterable_(dba->Vertices(storage::View::OLD)), pull_chunks_{// Dump all label indices CreateLabelIndicesPullChunk(), @@ -282,7 +295,9 @@ PullPlanDump::PullPlanDump(DbAccessor *dba) // Drop the internal index CreateDropInternalIndexPullChunk(), // Internal index cleanup - CreateInternalIndexCleanupPullChunk()} {} + CreateInternalIndexCleanupPullChunk(), + // Dump all triggers + CreateTriggersPullChunk()} {} bool PullPlanDump::Pull(AnyStream *stream, std::optional<int> n) { // Iterate all functions that stream some results. @@ -536,6 +551,23 @@ PullPlanDump::PullChunk PullPlanDump::CreateInternalIndexCleanupPullChunk() { }; } -void DumpDatabaseToCypherQueries(query::DbAccessor *dba, AnyStream *stream) { PullPlanDump(dba).Pull(stream, {}); } +PullPlanDump::PullChunk PullPlanDump::CreateTriggersPullChunk() { + return [this](AnyStream *stream, std::optional<int>) { + auto triggers = db_acc_->trigger_store()->GetTriggerInfo(); + for (const auto &trigger : triggers) { + std::ostringstream os; + auto trigger_statement_copy = trigger.statement; + std::replace(trigger_statement_copy.begin(), trigger_statement_copy.end(), '\n', ' '); + os << "CREATE TRIGGER " << trigger.name << " ON " << memgraph::query::TriggerEventTypeToString(trigger.event_type) + << " " << triggerPhaseToString(trigger.phase) << " " << trigger_statement_copy << ";"; + stream->Result({TypedValue(os.str())}); + } + return 0; + }; +} + +void DumpDatabaseToCypherQueries(query::DbAccessor *dba, AnyStream *stream, dbms::DatabaseAccess db_acc) { + PullPlanDump(dba, db_acc).Pull(stream, {}); +} } // namespace memgraph::query diff --git a/src/query/dump.hpp b/src/query/dump.hpp index b15a8d9e9..a9d68d45c 100644 --- a/src/query/dump.hpp +++ b/src/query/dump.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -11,18 +11,17 @@ #pragma once -#include <ostream> - +#include "dbms/database.hpp" #include "query/db_accessor.hpp" #include "query/stream.hpp" #include "storage/v2/storage.hpp" namespace memgraph::query { -void DumpDatabaseToCypherQueries(query::DbAccessor *dba, AnyStream *stream); +void DumpDatabaseToCypherQueries(query::DbAccessor *dba, AnyStream *stream, dbms::DatabaseAccess db_acc); struct PullPlanDump { - explicit PullPlanDump(query::DbAccessor *dba); + explicit PullPlanDump(query::DbAccessor *dba, dbms::DatabaseAccess db_acc); /// Pull the dump results lazily /// @return true if all results were returned, false otherwise @@ -30,6 +29,7 @@ struct PullPlanDump { private: query::DbAccessor *dba_ = nullptr; + dbms::DatabaseAccess db_acc_; std::optional<storage::IndicesInfo> indices_info_ = std::nullopt; std::optional<storage::ConstraintsInfo> constraints_info_ = std::nullopt; @@ -62,5 +62,6 @@ struct PullPlanDump { PullChunk CreateEdgePullChunk(); PullChunk CreateDropInternalIndexPullChunk(); PullChunk CreateInternalIndexCleanupPullChunk(); + PullChunk CreateTriggersPullChunk(); }; } // namespace memgraph::query diff --git a/src/query/exceptions.hpp b/src/query/exceptions.hpp index ac8cc8fe8..147dc8710 100644 --- a/src/query/exceptions.hpp +++ b/src/query/exceptions.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -195,6 +195,12 @@ class DatabaseContextRequiredException : public QueryRuntimeException { SPECIALIZE_GET_EXCEPTION_NAME(DatabaseContextRequiredException) }; +class ConcurrentSystemQueriesException : public QueryRuntimeException { + public: + using QueryRuntimeException::QueryRuntimeException; + SPECIALIZE_GET_EXCEPTION_NAME(ConcurrentSystemQueriesException) +}; + class WriteVertexOperationInEdgeImportModeException : public QueryException { public: WriteVertexOperationInEdgeImportModeException() @@ -253,6 +259,13 @@ class ReplicationModificationInMulticommandTxException : public QueryException { SPECIALIZE_GET_EXCEPTION_NAME(ReplicationModificationInMulticommandTxException) }; +class CoordinatorModificationInMulticommandTxException : public QueryException { + public: + CoordinatorModificationInMulticommandTxException() + : QueryException("Coordinator clause not allowed in multicommand transactions.") {} + SPECIALIZE_GET_EXCEPTION_NAME(CoordinatorModificationInMulticommandTxException) +}; + class ReplicationDisabledOnDiskStorage : public QueryException { public: ReplicationDisabledOnDiskStorage() : QueryException("Replication is not supported while in on-disk storage mode.") {} diff --git a/src/query/frontend/ast/ast.cpp b/src/query/frontend/ast/ast.cpp index c5e4c84c4..57d5398ab 100644 --- a/src/query/frontend/ast/ast.cpp +++ b/src/query/frontend/ast/ast.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -239,6 +239,9 @@ constexpr utils::TypeInfo query::DumpQuery::kType{utils::TypeId::AST_DUMP_QUERY, constexpr utils::TypeInfo query::ReplicationQuery::kType{utils::TypeId::AST_REPLICATION_QUERY, "ReplicationQuery", &query::Query::kType}; +constexpr utils::TypeInfo query::CoordinatorQuery::kType{utils::TypeId::AST_COORDINATOR_QUERY, "CoordinatorQuery", + &query::Query::kType}; + constexpr utils::TypeInfo query::LockPathQuery::kType{utils::TypeId::AST_LOCK_PATH_QUERY, "LockPathQuery", &query::Query::kType}; @@ -293,4 +296,7 @@ constexpr utils::TypeInfo query::ShowDatabasesQuery::kType{utils::TypeId::AST_SH constexpr utils::TypeInfo query::EdgeImportModeQuery::kType{utils::TypeId::AST_EDGE_IMPORT_MODE_QUERY, "EdgeImportModeQuery", &query::Query::kType}; +constexpr utils::TypeInfo query::PatternComprehension::kType{utils::TypeId::AST_PATTERN_COMPREHENSION, + "PatternComprehension", &query::Expression::kType}; + } // namespace memgraph diff --git a/src/query/frontend/ast/ast.hpp b/src/query/frontend/ast/ast.hpp index 59860d5b0..0cbb790d0 100644 --- a/src/query/frontend/ast/ast.hpp +++ b/src/query/frontend/ast/ast.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -1209,7 +1209,8 @@ class PropertyLookup : public memgraph::query::Expression { } protected: - PropertyLookup(Expression *expression, PropertyIx property) : expression_(expression), property_(property) {} + PropertyLookup(Expression *expression, PropertyIx property) + : expression_(expression), property_(std::move(property)) {} private: friend class AstStorage; @@ -1805,9 +1806,9 @@ class EdgeAtom : public memgraph::query::PatternAtom { static const utils::TypeInfo kType; const utils::TypeInfo &GetTypeInfo() const override { return kType; } - enum class Type { SINGLE, DEPTH_FIRST, BREADTH_FIRST, WEIGHTED_SHORTEST_PATH, ALL_SHORTEST_PATHS }; + enum class Type : uint8_t { SINGLE, DEPTH_FIRST, BREADTH_FIRST, WEIGHTED_SHORTEST_PATH, ALL_SHORTEST_PATHS }; - enum class Direction { IN, OUT, BOTH }; + enum class Direction : uint8_t { IN, OUT, BOTH }; /// Lambda for use in filtering or weight calculation during variable expand. struct Lambda { @@ -2216,7 +2217,7 @@ class IndexQuery : public memgraph::query::Query { protected: IndexQuery(Action action, LabelIx label, std::vector<PropertyIx> properties) - : action_(action), label_(label), properties_(properties) {} + : action_(action), label_(std::move(label)), properties_(std::move(properties)) {} private: friend class AstStorage; @@ -2849,6 +2850,7 @@ class AuthQuery : public memgraph::query::Query { TRANSACTION_MANAGEMENT, MULTI_DATABASE_EDIT, MULTI_DATABASE_USE, + COORDINATOR }; enum class FineGrainedPrivilege { NOTHING, READ, UPDATE, CREATE_DELETE }; @@ -2927,7 +2929,8 @@ const std::vector<AuthQuery::Privilege> kPrivilegesAll = {AuthQuery::Privilege:: AuthQuery::Privilege::TRANSACTION_MANAGEMENT, AuthQuery::Privilege::STORAGE_MODE, AuthQuery::Privilege::MULTI_DATABASE_EDIT, - AuthQuery::Privilege::MULTI_DATABASE_USE}; + AuthQuery::Privilege::MULTI_DATABASE_USE, + AuthQuery::Privilege::COORDINATOR}; class DatabaseInfoQuery : public memgraph::query::Query { public: @@ -3039,8 +3042,9 @@ class ReplicationQuery : public memgraph::query::Query { memgraph::query::ReplicationQuery::Action action_; memgraph::query::ReplicationQuery::ReplicationRole role_; - std::string replica_name_; + std::string instance_name_; memgraph::query::Expression *socket_address_{nullptr}; + memgraph::query::Expression *coordinator_socket_address_{nullptr}; memgraph::query::Expression *port_{nullptr}; memgraph::query::ReplicationQuery::SyncMode sync_mode_; @@ -3048,10 +3052,53 @@ class ReplicationQuery : public memgraph::query::Query { ReplicationQuery *object = storage->Create<ReplicationQuery>(); object->action_ = action_; object->role_ = role_; - object->replica_name_ = replica_name_; + object->instance_name_ = instance_name_; object->socket_address_ = socket_address_ ? socket_address_->Clone(storage) : nullptr; object->port_ = port_ ? port_->Clone(storage) : nullptr; object->sync_mode_ = sync_mode_; + object->coordinator_socket_address_ = + coordinator_socket_address_ ? coordinator_socket_address_->Clone(storage) : nullptr; + + return object; + } + + private: + friend class AstStorage; +}; + +class CoordinatorQuery : public memgraph::query::Query { + public: + static const utils::TypeInfo kType; + const utils::TypeInfo &GetTypeInfo() const override { return kType; } + + enum class Action { + REGISTER_INSTANCE, + SET_INSTANCE_TO_MAIN, + SHOW_REPLICATION_CLUSTER, + }; + + enum class SyncMode { SYNC, ASYNC }; + + CoordinatorQuery() = default; + + DEFVISITABLE(QueryVisitor<void>); + + memgraph::query::CoordinatorQuery::Action action_; + std::string instance_name_; + memgraph::query::Expression *replication_socket_address_{nullptr}; + memgraph::query::Expression *coordinator_socket_address_{nullptr}; + memgraph::query::CoordinatorQuery::SyncMode sync_mode_; + + CoordinatorQuery *Clone(AstStorage *storage) const override { + auto *object = storage->Create<CoordinatorQuery>(); + object->action_ = action_; + object->instance_name_ = instance_name_; + object->replication_socket_address_ = + replication_socket_address_ ? replication_socket_address_->Clone(storage) : nullptr; + object->sync_mode_ = sync_mode_; + object->coordinator_socket_address_ = + coordinator_socket_address_ ? coordinator_socket_address_->Clone(storage) : nullptr; + return object; } @@ -3520,6 +3567,65 @@ class Exists : public memgraph::query::Expression { friend class AstStorage; }; +class PatternComprehension : public memgraph::query::Expression { + public: + static const utils::TypeInfo kType; + const utils::TypeInfo &GetTypeInfo() const override { return kType; } + + PatternComprehension() = default; + + DEFVISITABLE(ExpressionVisitor<TypedValue>); + DEFVISITABLE(ExpressionVisitor<TypedValue *>); + DEFVISITABLE(ExpressionVisitor<void>); + + bool Accept(HierarchicalTreeVisitor &visitor) override { + if (visitor.PreVisit(*this)) { + if (variable_) { + variable_->Accept(visitor); + } + pattern_->Accept(visitor); + if (filter_) { + filter_->Accept(visitor); + } + resultExpr_->Accept(visitor); + } + return visitor.PostVisit(*this); + } + + PatternComprehension *MapTo(const Symbol &symbol) { + symbol_pos_ = symbol.position(); + return this; + } + + // The variable name. + Identifier *variable_{nullptr}; + // The pattern to match. + Pattern *pattern_{nullptr}; + // Optional WHERE clause for filtering. + Where *filter_{nullptr}; + // The projection expression. + Expression *resultExpr_{nullptr}; + + /// Symbol table position of the symbol this Aggregation is mapped to. + int32_t symbol_pos_{-1}; + + PatternComprehension *Clone(AstStorage *storage) const override { + PatternComprehension *object = storage->Create<PatternComprehension>(); + object->pattern_ = pattern_ ? pattern_->Clone(storage) : nullptr; + object->filter_ = filter_ ? filter_->Clone(storage) : nullptr; + object->resultExpr_ = resultExpr_ ? resultExpr_->Clone(storage) : nullptr; + + object->symbol_pos_ = symbol_pos_; + return object; + } + + protected: + PatternComprehension(Identifier *variable, Pattern *pattern) : variable_(variable), pattern_(pattern) {} + + private: + friend class AstStorage; +}; + class CallSubquery : public memgraph::query::Clause { public: static const utils::TypeInfo kType; @@ -3553,7 +3659,7 @@ class MultiDatabaseQuery : public memgraph::query::Query { DEFVISITABLE(QueryVisitor<void>); - enum class Action { CREATE, USE, DROP }; + enum class Action { CREATE, USE, DROP, SHOW }; memgraph::query::MultiDatabaseQuery::Action action_; std::string db_name_; diff --git a/src/query/frontend/ast/ast_visitor.hpp b/src/query/frontend/ast/ast_visitor.hpp index 793c15a95..5d463d3ee 100644 --- a/src/query/frontend/ast/ast_visitor.hpp +++ b/src/query/frontend/ast/ast_visitor.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -107,6 +107,8 @@ class Exists; class MultiDatabaseQuery; class ShowDatabasesQuery; class EdgeImportModeQuery; +class PatternComprehension; +class CoordinatorQuery; using TreeCompositeVisitor = utils::CompositeVisitor< SingleQuery, CypherUnion, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator, AdditionOperator, @@ -116,7 +118,7 @@ using TreeCompositeVisitor = utils::CompositeVisitor< MapProjectionLiteral, PropertyLookup, AllPropertiesLookup, LabelsTest, Aggregation, Function, Reduce, Coalesce, Extract, All, Single, Any, None, CallProcedure, Create, Match, Return, With, Pattern, NodeAtom, EdgeAtom, Delete, Where, SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels, Merge, Unwind, RegexMatch, LoadCsv, - Foreach, Exists, CallSubquery, CypherQuery>; + Foreach, Exists, CallSubquery, CypherQuery, PatternComprehension>; using TreeLeafVisitor = utils::LeafVisitor<Identifier, PrimitiveLiteral, ParameterLookup>; @@ -137,7 +139,7 @@ class ExpressionVisitor ListSlicingOperator, IfOperator, UnaryPlusOperator, UnaryMinusOperator, IsNullOperator, ListLiteral, MapLiteral, MapProjectionLiteral, PropertyLookup, AllPropertiesLookup, LabelsTest, Aggregation, Function, Reduce, Coalesce, Extract, All, Single, Any, None, - ParameterLookup, Identifier, PrimitiveLiteral, RegexMatch, Exists> {}; + ParameterLookup, Identifier, PrimitiveLiteral, RegexMatch, Exists, PatternComprehension> {}; template <class TResult> class QueryVisitor @@ -145,6 +147,7 @@ class QueryVisitor SystemInfoQuery, ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery, FreeMemoryQuery, TriggerQuery, IsolationLevelQuery, CreateSnapshotQuery, StreamQuery, SettingQuery, VersionQuery, ShowConfigQuery, TransactionQueueQuery, StorageModeQuery, - AnalyzeGraphQuery, MultiDatabaseQuery, ShowDatabasesQuery, EdgeImportModeQuery> {}; + AnalyzeGraphQuery, MultiDatabaseQuery, ShowDatabasesQuery, EdgeImportModeQuery, + CoordinatorQuery> {}; } // namespace memgraph::query diff --git a/src/query/frontend/ast/cypher_main_visitor.cpp b/src/query/frontend/ast/cypher_main_visitor.cpp index 7002ee4b9..5735326ac 100644 --- a/src/query/frontend/ast/cypher_main_visitor.cpp +++ b/src/query/frontend/ast/cypher_main_visitor.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -302,6 +302,13 @@ antlrcpp::Any CypherMainVisitor::visitReplicationQuery(MemgraphCypher::Replicati return replication_query; } +antlrcpp::Any CypherMainVisitor::visitCoordinatorQuery(MemgraphCypher::CoordinatorQueryContext *ctx) { + MG_ASSERT(ctx->children.size() == 1, "CoordinatorQuery should have exactly one child!"); + auto *coordinator_query = std::any_cast<CoordinatorQuery *>(ctx->children[0]->accept(this)); + query_ = coordinator_query; + return coordinator_query; +} + antlrcpp::Any CypherMainVisitor::visitEdgeImportModeQuery(MemgraphCypher::EdgeImportModeQueryContext *ctx) { auto *edge_import_mode_query = storage_->Create<EdgeImportModeQuery>(); if (ctx->ACTIVE()) { @@ -316,24 +323,34 @@ antlrcpp::Any CypherMainVisitor::visitEdgeImportModeQuery(MemgraphCypher::EdgeIm antlrcpp::Any CypherMainVisitor::visitSetReplicationRole(MemgraphCypher::SetReplicationRoleContext *ctx) { auto *replication_query = storage_->Create<ReplicationQuery>(); replication_query->action_ = ReplicationQuery::Action::SET_REPLICATION_ROLE; + + auto set_replication_port = [replication_query, ctx, this]() -> void { + if (ctx->port->numberLiteral() && ctx->port->numberLiteral()->integerLiteral()) { + replication_query->port_ = std::any_cast<Expression *>(ctx->port->accept(this)); + } else { + throw SyntaxException("Port must be an integer literal!"); + } + }; + if (ctx->MAIN()) { + replication_query->role_ = ReplicationQuery::ReplicationRole::MAIN; if (ctx->WITH() || ctx->PORT()) { throw SemanticException("Main can't set a port!"); } - replication_query->role_ = ReplicationQuery::ReplicationRole::MAIN; + } else if (ctx->REPLICA()) { replication_query->role_ = ReplicationQuery::ReplicationRole::REPLICA; if (ctx->WITH() && ctx->PORT()) { - if (ctx->port->numberLiteral() && ctx->port->numberLiteral()->integerLiteral()) { - replication_query->port_ = std::any_cast<Expression *>(ctx->port->accept(this)); - } else { - throw SyntaxException("Port must be an integer literal!"); - } + set_replication_port(); + } else { + throw SemanticException("Replica must set a port!"); } } + return replication_query; } -antlrcpp::Any CypherMainVisitor::visitShowReplicationRole(MemgraphCypher::ShowReplicationRoleContext *ctx) { + +antlrcpp::Any CypherMainVisitor::visitShowReplicationRole(MemgraphCypher::ShowReplicationRoleContext * /*ctx*/) { auto *replication_query = storage_->Create<ReplicationQuery>(); replication_query->action_ = ReplicationQuery::Action::SHOW_REPLICATION_ROLE; return replication_query; @@ -342,7 +359,7 @@ antlrcpp::Any CypherMainVisitor::visitShowReplicationRole(MemgraphCypher::ShowRe antlrcpp::Any CypherMainVisitor::visitRegisterReplica(MemgraphCypher::RegisterReplicaContext *ctx) { auto *replication_query = storage_->Create<ReplicationQuery>(); replication_query->action_ = ReplicationQuery::Action::REGISTER_REPLICA; - replication_query->replica_name_ = std::any_cast<std::string>(ctx->replicaName()->symbolicName()->accept(this)); + replication_query->instance_name_ = std::any_cast<std::string>(ctx->instanceName()->symbolicName()->accept(this)); if (ctx->SYNC()) { replication_query->sync_mode_ = memgraph::query::ReplicationQuery::SyncMode::SYNC; } else if (ctx->ASYNC()) { @@ -351,26 +368,67 @@ antlrcpp::Any CypherMainVisitor::visitRegisterReplica(MemgraphCypher::RegisterRe if (!ctx->socketAddress()->literal()->StringLiteral()) { throw SemanticException("Socket address should be a string literal!"); - } else { - replication_query->socket_address_ = std::any_cast<Expression *>(ctx->socketAddress()->accept(this)); } + replication_query->socket_address_ = std::any_cast<Expression *>(ctx->socketAddress()->accept(this)); return replication_query; } +// License check is done in the interpreter. +antlrcpp::Any CypherMainVisitor::visitRegisterInstanceOnCoordinator( + MemgraphCypher::RegisterInstanceOnCoordinatorContext *ctx) { + auto *coordinator_query = storage_->Create<CoordinatorQuery>(); + if (!ctx->replicationSocketAddress()->literal()->StringLiteral()) { + throw SemanticException("Replication socket address should be a string literal!"); + } + + if (!ctx->coordinatorSocketAddress()->literal()->StringLiteral()) { + throw SemanticException("Coordinator socket address should be a string literal!"); + } + coordinator_query->action_ = CoordinatorQuery::Action::REGISTER_INSTANCE; + coordinator_query->replication_socket_address_ = + std::any_cast<Expression *>(ctx->replicationSocketAddress()->accept(this)); + coordinator_query->coordinator_socket_address_ = + std::any_cast<Expression *>(ctx->coordinatorSocketAddress()->accept(this)); + coordinator_query->instance_name_ = std::any_cast<std::string>(ctx->instanceName()->symbolicName()->accept(this)); + if (ctx->ASYNC()) { + coordinator_query->sync_mode_ = memgraph::query::CoordinatorQuery::SyncMode::ASYNC; + } else { + coordinator_query->sync_mode_ = memgraph::query::CoordinatorQuery::SyncMode::SYNC; + } + + return coordinator_query; +} + +// License check is done in the interpreter +antlrcpp::Any CypherMainVisitor::visitShowReplicationCluster(MemgraphCypher::ShowReplicationClusterContext * /*ctx*/) { + auto *coordinator_query = storage_->Create<CoordinatorQuery>(); + coordinator_query->action_ = CoordinatorQuery::Action::SHOW_REPLICATION_CLUSTER; + return coordinator_query; +} + antlrcpp::Any CypherMainVisitor::visitDropReplica(MemgraphCypher::DropReplicaContext *ctx) { auto *replication_query = storage_->Create<ReplicationQuery>(); replication_query->action_ = ReplicationQuery::Action::DROP_REPLICA; - replication_query->replica_name_ = std::any_cast<std::string>(ctx->replicaName()->symbolicName()->accept(this)); + replication_query->instance_name_ = std::any_cast<std::string>(ctx->instanceName()->symbolicName()->accept(this)); return replication_query; } -antlrcpp::Any CypherMainVisitor::visitShowReplicas(MemgraphCypher::ShowReplicasContext *ctx) { +antlrcpp::Any CypherMainVisitor::visitShowReplicas(MemgraphCypher::ShowReplicasContext * /*ctx*/) { auto *replication_query = storage_->Create<ReplicationQuery>(); replication_query->action_ = ReplicationQuery::Action::SHOW_REPLICAS; return replication_query; } +// License check is done in the interpreter +antlrcpp::Any CypherMainVisitor::visitSetInstanceToMain(MemgraphCypher::SetInstanceToMainContext *ctx) { + auto *coordinator_query = storage_->Create<CoordinatorQuery>(); + coordinator_query->action_ = CoordinatorQuery::Action::SET_INSTANCE_TO_MAIN; + coordinator_query->instance_name_ = std::any_cast<std::string>(ctx->instanceName()->symbolicName()->accept(this)); + query_ = coordinator_query; + return coordinator_query; +} + antlrcpp::Any CypherMainVisitor::visitLockPathQuery(MemgraphCypher::LockPathQueryContext *ctx) { auto *lock_query = storage_->Create<LockPathQuery>(); if (ctx->STATUS()) { @@ -1638,6 +1696,7 @@ antlrcpp::Any CypherMainVisitor::visitPrivilege(MemgraphCypher::PrivilegeContext if (ctx->STORAGE_MODE()) return AuthQuery::Privilege::STORAGE_MODE; if (ctx->MULTI_DATABASE_EDIT()) return AuthQuery::Privilege::MULTI_DATABASE_EDIT; if (ctx->MULTI_DATABASE_USE()) return AuthQuery::Privilege::MULTI_DATABASE_USE; + if (ctx->COORDINATOR()) return AuthQuery::Privilege::COORDINATOR; LOG_FATAL("Should not get here - unknown privilege!"); } @@ -1752,7 +1811,11 @@ antlrcpp::Any CypherMainVisitor::visitReturnBody(MemgraphCypher::ReturnBodyConte body.skip = static_cast<Expression *>(std::any_cast<Expression *>(ctx->skip()->accept(this))); } if (ctx->limit()) { - body.limit = static_cast<Expression *>(std::any_cast<Expression *>(ctx->limit()->accept(this))); + if (ctx->limit()->expression()) { + body.limit = std::any_cast<Expression *>(ctx->limit()->accept(this)); + } else { + body.limit = std::any_cast<ParameterLookup *>(ctx->limit()->accept(this)); + } } std::tie(body.all_identifiers, body.named_expressions) = std::any_cast<std::pair<bool, std::vector<NamedExpression *>>>(ctx->returnItems()->accept(this)); @@ -1825,7 +1888,15 @@ antlrcpp::Any CypherMainVisitor::visitNodePattern(MemgraphCypher::NodePatternCon antlrcpp::Any CypherMainVisitor::visitNodeLabels(MemgraphCypher::NodeLabelsContext *ctx) { std::vector<LabelIx> labels; for (auto *node_label : ctx->nodeLabel()) { - labels.push_back(AddLabel(std::any_cast<std::string>(node_label->accept(this)))); + if (node_label->labelName()->symbolicName()) { + labels.emplace_back(AddLabel(std::any_cast<std::string>(node_label->accept(this)))); + } else { + // If we have a parameter, we have to resolve it. + const auto *param_lookup = std::any_cast<ParameterLookup *>(node_label->accept(this)); + const auto label_name = parameters_->AtTokenPosition(param_lookup->token_position_).ValueString(); + labels.emplace_back(storage_->GetLabelIx(label_name)); + query_info_.is_cacheable = false; // We can't cache queries with label parameters. + } } return labels; } @@ -1961,6 +2032,18 @@ antlrcpp::Any CypherMainVisitor::visitPatternElement(MemgraphCypher::PatternElem return pattern; } +antlrcpp::Any CypherMainVisitor::visitRelationshipsPattern(MemgraphCypher::RelationshipsPatternContext *ctx) { + auto *pattern = storage_->Create<Pattern>(); + pattern->atoms_.push_back(std::any_cast<NodeAtom *>(ctx->nodePattern()->accept(this))); + for (auto *pattern_element_chain : ctx->patternElementChain()) { + auto element = std::any_cast<std::pair<PatternAtom *, PatternAtom *>>(pattern_element_chain->accept(this)); + pattern->atoms_.push_back(element.first); + pattern->atoms_.push_back(element.second); + } + anonymous_identifiers.push_back(&pattern->identifier_); + return pattern; +} + antlrcpp::Any CypherMainVisitor::visitPatternElementChain(MemgraphCypher::PatternElementChainContext *ctx) { return std::pair<PatternAtom *, PatternAtom *>(std::any_cast<EdgeAtom *>(ctx->relationshipPattern()->accept(this)), std::any_cast<NodeAtom *>(ctx->nodePattern()->accept(this))); @@ -2455,6 +2538,8 @@ antlrcpp::Any CypherMainVisitor::visitAtom(MemgraphCypher::AtomContext *ctx) { return static_cast<Expression *>(storage_->Create<Extract>(ident, list, expr)); } else if (ctx->existsExpression()) { return std::any_cast<Expression *>(ctx->existsExpression()->accept(this)); + } else if (ctx->patternComprehension()) { + return std::any_cast<Expression *>(ctx->patternComprehension()->accept(this)); } // TODO: Implement this. We don't support comprehensions, filtering... at @@ -2515,6 +2600,19 @@ antlrcpp::Any CypherMainVisitor::visitExistsExpression(MemgraphCypher::ExistsExp return static_cast<Expression *>(exists); } +antlrcpp::Any CypherMainVisitor::visitPatternComprehension(MemgraphCypher::PatternComprehensionContext *ctx) { + auto *comprehension = storage_->Create<PatternComprehension>(); + if (ctx->variable()) { + comprehension->variable_ = storage_->Create<Identifier>(std::any_cast<std::string>(ctx->variable()->accept(this))); + } + comprehension->pattern_ = std::any_cast<Pattern *>(ctx->relationshipsPattern()->accept(this)); + if (ctx->where()) { + comprehension->filter_ = std::any_cast<Where *>(ctx->where()->accept(this)); + } + comprehension->resultExpr_ = std::any_cast<Expression *>(ctx->expression()->accept(this)); + return static_cast<Expression *>(comprehension); +} + antlrcpp::Any CypherMainVisitor::visitParenthesizedExpression(MemgraphCypher::ParenthesizedExpressionContext *ctx) { return std::any_cast<Expression *>(ctx->expression()->accept(this)); } @@ -2853,6 +2951,14 @@ antlrcpp::Any CypherMainVisitor::visitDropDatabase(MemgraphCypher::DropDatabaseC return mdb_query; } +antlrcpp::Any CypherMainVisitor::visitShowDatabase(MemgraphCypher::ShowDatabaseContext * /*ctx*/) { + auto *mdb_query = storage_->Create<MultiDatabaseQuery>(); + mdb_query->db_name_ = ""; + mdb_query->action_ = MultiDatabaseQuery::Action::SHOW; + query_ = mdb_query; + return mdb_query; +} + antlrcpp::Any CypherMainVisitor::visitShowDatabases(MemgraphCypher::ShowDatabasesContext * /*ctx*/) { query_ = storage_->Create<ShowDatabasesQuery>(); return query_; diff --git a/src/query/frontend/ast/cypher_main_visitor.hpp b/src/query/frontend/ast/cypher_main_visitor.hpp index 9e828674f..e9da98f71 100644 --- a/src/query/frontend/ast/cypher_main_visitor.hpp +++ b/src/query/frontend/ast/cypher_main_visitor.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -17,6 +17,7 @@ #include "query/frontend/ast/ast.hpp" #include "query/frontend/opencypher/generated/MemgraphCypherBaseVisitor.h" +#include "query/parameters.hpp" #include "utils/exceptions.hpp" #include "utils/logging.hpp" @@ -30,7 +31,8 @@ struct ParsingContext { class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor { public: - explicit CypherMainVisitor(ParsingContext context, AstStorage *storage) : context_(context), storage_(storage) {} + explicit CypherMainVisitor(ParsingContext context, AstStorage *storage, Parameters *parameters) + : context_(context), storage_(storage), parameters_(parameters) {} private: Expression *CreateBinaryOperatorByToken(size_t token, Expression *e1, Expression *e2) { @@ -231,6 +233,26 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor { */ antlrcpp::Any visitShowReplicas(MemgraphCypher::ShowReplicasContext *ctx) override; + /** + * @return CoordinatorQuery* + */ + antlrcpp::Any visitCoordinatorQuery(MemgraphCypher::CoordinatorQueryContext *ctx) override; + + /** + * @return CoordinatorQuery* + */ + antlrcpp::Any visitRegisterInstanceOnCoordinator(MemgraphCypher::RegisterInstanceOnCoordinatorContext *ctx) override; + + /** + * @return CoordinatorQuery* + */ + antlrcpp::Any visitSetInstanceToMain(MemgraphCypher::SetInstanceToMainContext *ctx) override; + + /** + * @return CoordinatorQuery* + */ + antlrcpp::Any visitShowReplicationCluster(MemgraphCypher::ShowReplicationClusterContext *ctx) override; + /** * @return LockPathQuery* */ @@ -676,6 +698,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor { */ antlrcpp::Any visitPatternElement(MemgraphCypher::PatternElementContext *ctx) override; + /** + * @return Pattern* + */ + antlrcpp::Any visitRelationshipsPattern(MemgraphCypher::RelationshipsPatternContext *ctx) override; + /** * @return vector<pair<EdgeAtom*, NodeAtom*>> */ @@ -841,6 +868,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor { */ antlrcpp::Any visitExistsExpression(MemgraphCypher::ExistsExpressionContext *ctx) override; + /** + * @return pattern comprehension (Expression) + */ + antlrcpp::Any visitPatternComprehension(MemgraphCypher::PatternComprehensionContext *ctx) override; + /** * @return Expression* */ @@ -985,6 +1017,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor { */ antlrcpp::Any visitDropDatabase(MemgraphCypher::DropDatabaseContext *ctx) override; + /** + * @return MultiDatabaseQuery* + */ + antlrcpp::Any visitShowDatabase(MemgraphCypher::ShowDatabaseContext *ctx) override; + /** * @return ShowDatabasesQuery* */ @@ -1022,6 +1059,8 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor { // return. bool in_with_ = false; + Parameters *parameters_; + QueryInfo query_info_; }; } // namespace memgraph::query::frontend diff --git a/src/query/frontend/ast/pretty_print.cpp b/src/query/frontend/ast/pretty_print.cpp index ef45afd7d..61bd23797 100644 --- a/src/query/frontend/ast/pretty_print.cpp +++ b/src/query/frontend/ast/pretty_print.cpp @@ -73,6 +73,7 @@ class ExpressionPrettyPrinter : public ExpressionVisitor<void> { void Visit(ParameterLookup &op) override; void Visit(NamedExpression &op) override; void Visit(RegexMatch &op) override; + void Visit(PatternComprehension &op) override; private: std::ostream *out_; @@ -323,6 +324,10 @@ void ExpressionPrettyPrinter::Visit(NamedExpression &op) { void ExpressionPrettyPrinter::Visit(RegexMatch &op) { PrintOperator(out_, "=~", op.string_expr_, op.regex_); } +void ExpressionPrettyPrinter::Visit(PatternComprehension &op) { + PrintOperator(out_, "Pattern Comprehension", op.variable_, op.pattern_, op.filter_, op.resultExpr_); +} + } // namespace void PrintExpression(Expression *expr, std::ostream *out) { diff --git a/src/query/frontend/opencypher/grammar/Cypher.g4 b/src/query/frontend/opencypher/grammar/Cypher.g4 index d387002d8..55cb53ef3 100644 --- a/src/query/frontend/opencypher/grammar/Cypher.g4 +++ b/src/query/frontend/opencypher/grammar/Cypher.g4 @@ -143,7 +143,7 @@ order : ORDER BY sortItem ( ',' sortItem )* ; skip : L_SKIP expression ; -limit : LIMIT expression ; +limit : LIMIT ( expression | parameter ) ; sortItem : expression ( ASCENDING | ASC | DESCENDING | DESC )? ; @@ -193,7 +193,7 @@ nodeLabels : nodeLabel ( nodeLabel )* ; nodeLabel : ':' labelName ; -labelName : symbolicName ; +labelName : symbolicName | parameter; relTypeName : symbolicName ; @@ -296,7 +296,7 @@ functionName : symbolicName ( '.' symbolicName )* ; listComprehension : '[' filterExpression ( '|' expression )? ']' ; -patternComprehension : '[' ( variable '=' )? relationshipsPattern ( WHERE expression )? '|' expression ']' ; +patternComprehension : '[' ( variable '=' )? relationshipsPattern ( where )? '|' resultExpr=expression ']' ; propertyLookup : '.' ( propertyKeyName ) ; diff --git a/src/query/frontend/opencypher/grammar/CypherLexer.g4 b/src/query/frontend/opencypher/grammar/CypherLexer.g4 index 3428a2191..3e3c640d6 100644 --- a/src/query/frontend/opencypher/grammar/CypherLexer.g4 +++ b/src/query/frontend/opencypher/grammar/CypherLexer.g4 @@ -102,6 +102,7 @@ FILTER : F I L T E R ; IN : I N ; INDEX : I N D E X ; INFO : I N F O ; +INSTANCE : I N S T A N C E ; IS : I S ; KB : K B ; KEY : K E Y ; @@ -122,6 +123,7 @@ PROCEDURE : P R O C E D U R E ; PROFILE : P R O F I L E ; QUERY : Q U E R Y ; REDUCE : R E D U C E ; +REGISTER : R E G I S T E R; REMOVE : R E M O V E ; RETURN : R E T U R N ; SET : S E T ; diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 index d585acbb1..e41184468 100644 --- a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 +++ b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 @@ -48,10 +48,12 @@ memgraphCypherKeyword : cypherKeyword | DATABASE | DENY | DROP + | DO | DUMP | EDGE | EDGE_TYPES | EXECUTE + | FAILOVER | FOR | FOREACH | FREE @@ -61,6 +63,7 @@ memgraphCypherKeyword : cypherKeyword | GRANT | HEADER | IDENTIFIED + | INSTANCE | NODE_LABELS | NULLIF | IMPORT @@ -151,6 +154,7 @@ query : cypherQuery | multiDatabaseQuery | showDatabases | edgeImportModeQuery + | coordinatorQuery ; cypherQuery : ( indexHints )? singleQuery ( cypherUnion )* ( queryMemoryLimit )? ; @@ -183,6 +187,11 @@ replicationQuery : setReplicationRole | showReplicas ; +coordinatorQuery : registerInstanceOnCoordinator + | setInstanceToMain + | showReplicationCluster + ; + triggerQuery : createTrigger | dropTrigger | showTriggers @@ -323,6 +332,7 @@ privilege : CREATE | STORAGE_MODE | MULTI_DATABASE_EDIT | MULTI_DATABASE_USE + | COORDINATOR ; granularPrivilege : NOTHING | READ | UPDATE | CREATE_DELETE ; @@ -364,14 +374,23 @@ setReplicationRole : SET REPLICATION ROLE TO ( MAIN | REPLICA ) showReplicationRole : SHOW REPLICATION ROLE ; -replicaName : symbolicName ; +showReplicationCluster : SHOW REPLICATION CLUSTER ; + +instanceName : symbolicName ; socketAddress : literal ; -registerReplica : REGISTER REPLICA replicaName ( SYNC | ASYNC ) +coordinatorSocketAddress : literal ; +replicationSocketAddress : literal ; + +registerReplica : REGISTER REPLICA instanceName ( SYNC | ASYNC ) TO socketAddress ; -dropReplica : DROP REPLICA replicaName ; +registerInstanceOnCoordinator : REGISTER INSTANCE instanceName ON coordinatorSocketAddress ( AS ASYNC ) ? WITH replicationSocketAddress ; + +setInstanceToMain : SET INSTANCE instanceName TO MAIN ; + +dropReplica : DROP REPLICA instanceName ; showReplicas : SHOW REPLICAS ; @@ -480,6 +499,7 @@ transactionId : literal ; multiDatabaseQuery : createDatabase | useDatabase | dropDatabase + | showDatabase ; createDatabase : CREATE DATABASE databaseName ; @@ -488,6 +508,8 @@ useDatabase : USE DATABASE databaseName ; dropDatabase : DROP DATABASE databaseName ; +showDatabase : SHOW DATABASE ; + showDatabases : SHOW DATABASES ; edgeImportModeQuery : EDGE IMPORT MODE ( ACTIVE | INACTIVE ) ; diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 index 1b44a6e79..b0febc4af 100644 --- a/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 +++ b/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 @@ -39,15 +39,18 @@ BOOTSTRAP_SERVERS : B O O T S T R A P UNDERSCORE S E R V E R S ; CALL : C A L L ; CHECK : C H E C K ; CLEAR : C L E A R ; +CLUSTER : C L U S T E R ; COMMIT : C O M M I T ; COMMITTED : C O M M I T T E D ; CONFIG : C O N F I G ; CONFIGS : C O N F I G S; CONSUMER_GROUP : C O N S U M E R UNDERSCORE G R O U P ; +COORDINATOR : C O O R D I N A T O R ; CREATE_DELETE : C R E A T E UNDERSCORE D E L E T E ; CREDENTIALS : C R E D E N T I A L S ; CSV : C S V ; DATA : D A T A ; +DO : D O ; DELIMITER : D E L I M I T E R ; DATABASE : D A T A B A S E ; DATABASES : D A T A B A S E S ; @@ -59,6 +62,7 @@ DURABILITY : D U R A B I L I T Y ; EDGE : E D G E ; EDGE_TYPES : E D G E UNDERSCORE T Y P E S ; EXECUTE : E X E C U T E ; +FAILOVER : F A I L O V E R ; FOR : F O R ; FOREACH : F O R E A C H; FREE : F R E E ; @@ -75,6 +79,7 @@ IMPORT : I M P O R T ; INACTIVE : I N A C T I V E ; IN_MEMORY_ANALYTICAL : I N UNDERSCORE M E M O R Y UNDERSCORE A N A L Y T I C A L ; IN_MEMORY_TRANSACTIONAL : I N UNDERSCORE M E M O R Y UNDERSCORE T R A N S A C T I O N A L ; +INSTANCE : I N S T A N C E ; ISOLATION : I S O L A T I O N ; KAFKA : K A F K A ; LABELS : L A B E L S ; @@ -107,6 +112,7 @@ REVOKE : R E V O K E ; ROLE : R O L E ; ROLES : R O L E S ; QUOTE : Q U O T E ; +SERVER : S E R V E R ; SERVICE_URL : S E R V I C E UNDERSCORE U R L ; SESSION : S E S S I O N ; SETTING : S E T T I N G ; diff --git a/src/query/frontend/semantic/required_privileges.cpp b/src/query/frontend/semantic/required_privileges.cpp index 04772cded..ef66a75ac 100644 --- a/src/query/frontend/semantic/required_privileges.cpp +++ b/src/query/frontend/semantic/required_privileges.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -106,6 +106,7 @@ class PrivilegeExtractor : public QueryVisitor<void>, public HierarchicalTreeVis AddPrivilege(AuthQuery::Privilege::MULTI_DATABASE_EDIT); break; case MultiDatabaseQuery::Action::USE: + case MultiDatabaseQuery::Action::SHOW: AddPrivilege(AuthQuery::Privilege::MULTI_DATABASE_USE); break; } @@ -115,6 +116,8 @@ class PrivilegeExtractor : public QueryVisitor<void>, public HierarchicalTreeVis AddPrivilege(AuthQuery::Privilege::MULTI_DATABASE_USE); /* OR EDIT */ } + void Visit(CoordinatorQuery & /*coordinator_query*/) override { AddPrivilege(AuthQuery::Privilege::COORDINATOR); } + bool PreVisit(Create & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::CREATE); return false; diff --git a/src/query/frontend/semantic/symbol_generator.hpp b/src/query/frontend/semantic/symbol_generator.hpp index 207bbddbd..f9e6468f6 100644 --- a/src/query/frontend/semantic/symbol_generator.hpp +++ b/src/query/frontend/semantic/symbol_generator.hpp @@ -249,6 +249,7 @@ class PropertyLookupEvaluationModeVisitor : public ExpressionVisitor<void> { void Visit(ParameterLookup &op) override{}; void Visit(NamedExpression &op) override { op.expression_->Accept(*this); }; void Visit(RegexMatch &op) override{}; + void Visit(PatternComprehension &op) override{}; void Visit(PropertyLookup & /*property_lookup*/) override; diff --git a/src/query/frontend/semantic/symbol_table.hpp b/src/query/frontend/semantic/symbol_table.hpp index 0b521356c..cf462c437 100644 --- a/src/query/frontend/semantic/symbol_table.hpp +++ b/src/query/frontend/semantic/symbol_table.hpp @@ -52,6 +52,9 @@ class SymbolTable final { const Symbol &at(const NamedExpression &nexpr) const { return table_.at(nexpr.symbol_pos_); } const Symbol &at(const Aggregation &aggr) const { return table_.at(aggr.symbol_pos_); } const Symbol &at(const Exists &exists) const { return table_.at(exists.symbol_pos_); } + const Symbol &at(const PatternComprehension &pattern_comprehension) const { + return table_.at(pattern_comprehension.symbol_pos_); + } // TODO: Remove these since members are public int32_t max_position() const { return static_cast<int32_t>(table_.size()); } diff --git a/src/query/frontend/stripped_lexer_constants.hpp b/src/query/frontend/stripped_lexer_constants.hpp index 21a14ae83..bd6ab7971 100644 --- a/src/query/frontend/stripped_lexer_constants.hpp +++ b/src/query/frontend/stripped_lexer_constants.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -218,7 +218,8 @@ const trie::Trie kKeywords = {"union", "directory", "lock", "unlock", - "build"}; + "build", + "instance"}; // Unicode codepoints that are allowed at the start of the unescaped name. const std::bitset<kBitsetSize> kUnescapedNameAllowedStarts( diff --git a/src/query/interpret/awesome_memgraph_functions.cpp b/src/query/interpret/awesome_memgraph_functions.cpp index 6f49ee99f..ece0aec78 100644 --- a/src/query/interpret/awesome_memgraph_functions.cpp +++ b/src/query/interpret/awesome_memgraph_functions.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -504,8 +504,8 @@ TypedValue ToBoolean(const TypedValue *args, int64_t nargs, const FunctionContex return TypedValue(value.ValueInt() != 0L, ctx.memory); } else { auto s = utils::ToUpperCase(utils::Trim(value.ValueString())); - if (s == "TRUE") return TypedValue(true, ctx.memory); - if (s == "FALSE") return TypedValue(false, ctx.memory); + if (s == "TRUE" || s == "T") return TypedValue(true, ctx.memory); + if (s == "FALSE" || s == "F") return TypedValue(false, ctx.memory); // I think this is just stupid and that exception should be thrown, but // neo4j does it this way... return TypedValue(ctx.memory); @@ -957,7 +957,7 @@ TypedValue ToString(const TypedValue *args, int64_t nargs, const FunctionContext return TypedValue(std::to_string(arg.ValueInt()), ctx.memory); } if (arg.IsDouble()) { - return TypedValue(std::to_string(arg.ValueDouble()), ctx.memory); + return TypedValue(memgraph::utils::DoubleToString(arg.ValueDouble()), ctx.memory); } if (arg.IsDate()) { return TypedValue(arg.ValueDate().ToString(), ctx.memory); diff --git a/src/query/interpret/eval.hpp b/src/query/interpret/eval.hpp index f4f3126cd..fe47a3fcd 100644 --- a/src/query/interpret/eval.hpp +++ b/src/query/interpret/eval.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -101,6 +101,7 @@ class ReferenceExpressionEvaluator : public ExpressionVisitor<TypedValue *> { UNSUCCESSFUL_VISIT(ParameterLookup); UNSUCCESSFUL_VISIT(RegexMatch); UNSUCCESSFUL_VISIT(Exists); + UNSUCCESSFUL_VISIT(PatternComprehension); #undef UNSUCCESSFUL_VISIT @@ -170,6 +171,7 @@ class PrimitiveLiteralExpressionEvaluator : public ExpressionVisitor<TypedValue> INVALID_VISIT(Identifier) INVALID_VISIT(RegexMatch) INVALID_VISIT(Exists) + INVALID_VISIT(PatternComprehension) #undef INVALID_VISIT private: @@ -1090,6 +1092,10 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> { } } + TypedValue Visit(PatternComprehension & /*pattern_comprehension*/) override { + throw utils::NotYetImplemented("Expression evaluator can not handle pattern comprehension."); + } + private: template <class TRecordAccessor> std::map<storage::PropertyId, storage::PropertyValue> GetAllProperties(const TRecordAccessor &record_accessor) { @@ -1115,11 +1121,11 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> { throw QueryRuntimeException("Unexpected error when getting properties."); } } - return *maybe_props; + return *std::move(maybe_props); } template <class TRecordAccessor> - storage::PropertyValue GetProperty(const TRecordAccessor &record_accessor, PropertyIx prop) { + storage::PropertyValue GetProperty(const TRecordAccessor &record_accessor, const PropertyIx &prop) { auto maybe_prop = record_accessor.GetProperty(view_, ctx_->properties[prop.ix]); if (maybe_prop.HasError() && maybe_prop.GetError() == storage::Error::NONEXISTENT_OBJECT) { // This is a very nasty and temporary hack in order to make MERGE work. @@ -1142,7 +1148,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> { throw QueryRuntimeException("Unexpected error when getting a property."); } } - return *maybe_prop; + return *std::move(maybe_prop); } template <class TRecordAccessor> @@ -1172,7 +1178,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> { return *maybe_prop; } - storage::LabelId GetLabel(LabelIx label) { return ctx_->labels[label.ix]; } + storage::LabelId GetLabel(const LabelIx &label) { return ctx_->labels[label.ix]; } Frame *frame_; const SymbolTable *symbol_table_; diff --git a/src/query/interpreter.cpp b/src/query/interpreter.cpp index 7292c4591..1576df7c4 100644 --- a/src/query/interpreter.cpp +++ b/src/query/interpreter.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -35,9 +35,9 @@ #include "auth/models.hpp" #include "csv/parsing.hpp" #include "dbms/database.hpp" -#include "dbms/dbms_handler.hpp" #include "dbms/global.hpp" #include "dbms/inmemory/storage_helper.hpp" +#include "flags/replication.hpp" #include "flags/run_time_configurable.hpp" #include "glue/communication.hpp" #include "license/license.hpp" @@ -101,12 +101,17 @@ #include "utils/typeinfo.hpp" #include "utils/variant_helpers.hpp" +#include "dbms/coordinator_handler.hpp" #include "dbms/dbms_handler.hpp" #include "dbms/replication_handler.hpp" #include "query/auth_query_handler.hpp" #include "query/interpreter_context.hpp" #include "replication/state.hpp" +#ifdef MG_ENTERPRISE +#include "coordination/constants.hpp" +#endif + namespace memgraph::metrics { extern Event ReadQuery; extern Event WriteQuery; @@ -121,6 +126,7 @@ extern const Event CommitedTransactions; extern const Event RollbackedTransactions; extern const Event ActiveTransactions; } // namespace memgraph::metrics + void memgraph::query::CurrentDB::SetupDatabaseTransaction( std::optional<storage::IsolationLevel> override_isolation_level, bool could_commit, bool unique) { auto &db_acc = *db_acc_; @@ -148,6 +154,7 @@ void memgraph::query::CurrentDB::CleanupDBTransaction(bool abort) { namespace memgraph::query { constexpr std::string_view kSchemaAssert = "SCHEMA.ASSERT"; +constexpr int kSystemTxTryMS = 100; //!< Duration of the unique try_lock_for template <typename> constexpr auto kAlwaysFalse = false; @@ -259,33 +266,61 @@ bool IsAllShortestPathsQuery(const std::vector<memgraph::query::Clause *> &claus return false; } -inline auto convertToReplicationMode(const ReplicationQuery::SyncMode &sync_mode) -> replication::ReplicationMode { +inline auto convertFromCoordinatorToReplicationMode(const CoordinatorQuery::SyncMode &sync_mode) + -> replication_coordination_glue::ReplicationMode { switch (sync_mode) { - case ReplicationQuery::SyncMode::ASYNC: { - return replication::ReplicationMode::ASYNC; + case CoordinatorQuery::SyncMode::ASYNC: { + return replication_coordination_glue::ReplicationMode::ASYNC; } - case ReplicationQuery::SyncMode::SYNC: { - return replication::ReplicationMode::SYNC; + case CoordinatorQuery::SyncMode::SYNC: { + return replication_coordination_glue::ReplicationMode::SYNC; } } // TODO: C++23 std::unreachable() - return replication::ReplicationMode::ASYNC; + return replication_coordination_glue::ReplicationMode::ASYNC; } -class ReplQueryHandler final : public query::ReplicationQueryHandler { +inline auto convertToReplicationMode(const ReplicationQuery::SyncMode &sync_mode) + -> replication_coordination_glue::ReplicationMode { + switch (sync_mode) { + case ReplicationQuery::SyncMode::ASYNC: { + return replication_coordination_glue::ReplicationMode::ASYNC; + } + case ReplicationQuery::SyncMode::SYNC: { + return replication_coordination_glue::ReplicationMode::SYNC; + } + } + // TODO: C++23 std::unreachable() + return replication_coordination_glue::ReplicationMode::ASYNC; +} + +class ReplQueryHandler { public: - explicit ReplQueryHandler(dbms::DbmsHandler *dbms_handler) : dbms_handler_(dbms_handler), handler_{*dbms_handler} {} + struct ReplicaInfo { + std::string name; + std::string socket_address; + ReplicationQuery::SyncMode sync_mode; + std::optional<double> timeout; + uint64_t current_timestamp_of_replica; + uint64_t current_number_of_timestamp_behind_master; + ReplicationQuery::ReplicaState state; + }; + + explicit ReplQueryHandler(dbms::DbmsHandler *dbms_handler) : handler_{*dbms_handler} {} /// @throw QueryRuntimeException if an error ocurred. - void SetReplicationRole(ReplicationQuery::ReplicationRole replication_role, std::optional<int64_t> port) override { - if (replication_role == ReplicationQuery::ReplicationRole::MAIN) { - if (!handler_.SetReplicationRoleMain()) { - throw QueryRuntimeException("Couldn't set role to main!"); - } - } else { - if (!port || *port < 0 || *port > std::numeric_limits<uint16_t>::max()) { + void SetReplicationRole(ReplicationQuery::ReplicationRole replication_role, std::optional<int64_t> port) { + auto ValidatePort = [](std::optional<int64_t> port) -> void { + if (*port < 0 || *port > std::numeric_limits<uint16_t>::max()) { throw QueryRuntimeException("Port number invalid!"); } + }; + if (replication_role == ReplicationQuery::ReplicationRole::MAIN) { + if (!handler_.SetReplicationRoleMain()) { + throw QueryRuntimeException("Couldn't set replication role to main!"); + } + } else { + ValidatePort(port); auto const config = memgraph::replication::ReplicationServerConfig{ .ip_address = memgraph::replication::kDefaultReplicationServerIp, @@ -299,11 +334,11 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler { } /// @throw QueryRuntimeException if an error ocurred. - ReplicationQuery::ReplicationRole ShowReplicationRole() const override { + ReplicationQuery::ReplicationRole ShowReplicationRole() const { switch (handler_.GetRole()) { - case memgraph::replication::ReplicationRole::MAIN: + case memgraph::replication_coordination_glue::ReplicationRole::MAIN: return ReplicationQuery::ReplicationRole::MAIN; - case memgraph::replication::ReplicationRole::REPLICA: + case memgraph::replication_coordination_glue::ReplicationRole::REPLICA: return ReplicationQuery::ReplicationRole::REPLICA; } throw QueryRuntimeException("Couldn't show replication role - invalid role set!"); @@ -311,36 +346,41 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler { /// @throw QueryRuntimeException if an error ocurred. void RegisterReplica(const std::string &name, const std::string &socket_address, - const ReplicationQuery::SyncMode sync_mode, - const std::chrono::seconds replica_check_frequency) override { + const ReplicationQuery::SyncMode sync_mode, const std::chrono::seconds replica_check_frequency) { + // Coordinator is main by default so this check is OK although it should actually be nothing (neither main nor + // replica) if (handler_.IsReplica()) { // replica can't register another replica throw QueryRuntimeException("Replica can't register another replica!"); } - auto repl_mode = convertToReplicationMode(sync_mode); + const auto repl_mode = convertToReplicationMode(sync_mode); - auto maybe_ip_and_port = + const auto maybe_ip_and_port = io::network::Endpoint::ParseSocketOrAddress(socket_address, memgraph::replication::kDefaultReplicationPort); if (maybe_ip_and_port) { - auto [ip, port] = *maybe_ip_and_port; - auto config = replication::ReplicationClientConfig{.name = name, - .mode = repl_mode, - .ip_address = ip, - .port = port, - .replica_check_frequency = replica_check_frequency, - .ssl = std::nullopt}; - auto ret = handler_.RegisterReplica(config); - if (ret.HasError()) { + const auto [ip, port] = *maybe_ip_and_port; + const auto replication_config = + replication::ReplicationClientConfig{.name = name, + .mode = repl_mode, + .ip_address = ip, + .port = port, + .replica_check_frequency = replica_check_frequency, + .ssl = std::nullopt}; + + const auto error = handler_.RegisterReplica(replication_config).HasError(); + + if (error) { throw QueryRuntimeException(fmt::format("Couldn't register replica '{}'!", name)); } + } else { throw QueryRuntimeException("Invalid socket address!"); } } /// @throw QueryRuntimeException if an error occurred. - void DropReplica(std::string_view replica_name) override { + void DropReplica(std::string_view replica_name) { auto const result = handler_.UnregisterReplica(replica_name); switch (result) { using enum memgraph::dbms::UnregisterReplicaResult; @@ -355,8 +395,7 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler { } } - using Replica = ReplicationQueryHandler::Replica; - std::vector<Replica> ShowReplicas() const override { + std::vector<ReplicaInfo> ShowReplicas(const dbms::Database &db) const { if (handler_.IsReplica()) { // replica can't show registered replicas (it shouldn't have any) throw QueryRuntimeException("Replica can't show registered replicas (it shouldn't have any)!"); @@ -364,27 +403,19 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler { // TODO: Combine results? Have a single place with clients??? // Also authentication checks (replica + database visibility) - std::vector<storage::ReplicaInfo> repl_infos{}; - dbms_handler_->ForOne([&repl_infos](dbms::Database *db) -> bool { - auto infos = db->storage()->ReplicasInfo(); - if (!infos.empty()) { - repl_infos = std::move(infos); - return true; - } - return false; - }); - std::vector<Replica> replicas; + const auto repl_infos = db.storage()->ReplicasInfo(); + std::vector<ReplicaInfo> replicas; replicas.reserve(repl_infos.size()); - const auto from_info = [](const auto &repl_info) -> Replica { - Replica replica; + const auto from_info = [](const auto &repl_info) -> ReplicaInfo { + ReplicaInfo replica; replica.name = repl_info.name; replica.socket_address = repl_info.endpoint.SocketAddress(); switch (repl_info.mode) { - case memgraph::replication::ReplicationMode::SYNC: + case replication_coordination_glue::ReplicationMode::SYNC: replica.sync_mode = ReplicationQuery::SyncMode::SYNC; break; - case memgraph::replication::ReplicationMode::ASYNC: + case replication_coordination_glue::ReplicationMode::ASYNC: replica.sync_mode = ReplicationQuery::SyncMode::ASYNC; break; } @@ -416,10 +447,104 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler { } private: - dbms::DbmsHandler *dbms_handler_; dbms::ReplicationHandler handler_; }; +class CoordQueryHandler final : public query::CoordinatorQueryHandler { + public: + explicit CoordQueryHandler(dbms::DbmsHandler *dbms_handler) : handler_ { *dbms_handler } +#ifdef MG_ENTERPRISE + , coordinator_handler_(*dbms_handler) +#endif + { + } + +#ifdef MG_ENTERPRISE + /// @throw QueryRuntimeException if an error ocurred. + void RegisterInstance(const std::string &coordinator_socket_address, const std::string &replication_socket_address, + const std::chrono::seconds instance_check_frequency, const std::string &instance_name, + CoordinatorQuery::SyncMode sync_mode) override { + const auto maybe_replication_ip_port = + io::network::Endpoint::ParseSocketOrAddress(replication_socket_address, std::nullopt); + if (!maybe_replication_ip_port) { + throw QueryRuntimeException("Invalid replication socket address!"); + } + + const auto maybe_coordinator_ip_port = + io::network::Endpoint::ParseSocketOrAddress(coordinator_socket_address, std::nullopt); + if (!maybe_replication_ip_port) { + throw QueryRuntimeException("Invalid replication socket address!"); + } + + const auto [replication_ip, replication_port] = *maybe_replication_ip_port; + const auto [coordinator_server_ip, coordinator_server_port] = *maybe_coordinator_ip_port; + const auto repl_config = coordination::CoordinatorClientConfig::ReplicationClientInfo{ + .instance_name = instance_name, + .replication_mode = convertFromCoordinatorToReplicationMode(sync_mode), + .replication_ip_address = replication_ip, + .replication_port = replication_port}; + + auto coordinator_client_config = + coordination::CoordinatorClientConfig{.instance_name = instance_name, + .ip_address = coordinator_server_ip, + .port = coordinator_server_port, + .health_check_frequency_sec = instance_check_frequency, + .replication_client_info = repl_config, + .ssl = std::nullopt}; + + auto status = coordinator_handler_.RegisterInstance(coordinator_client_config); + switch (status) { + using enum memgraph::coordination::RegisterInstanceCoordinatorStatus; + case NAME_EXISTS: + throw QueryRuntimeException("Couldn't register replica instance since instance with such name already exists!"); + case END_POINT_EXISTS: + throw QueryRuntimeException( + "Couldn't register replica instance since instance with such endpoint already exists!"); + case COULD_NOT_BE_PERSISTED: + throw QueryRuntimeException("Couldn't register replica instance since it couldn't be persisted!"); + case NOT_COORDINATOR: + throw QueryRuntimeException("Couldn't register replica instance since this instance is not a coordinator!"); + case RPC_FAILED: + throw QueryRuntimeException( + "Couldn't register replica because promotion on replica failed! Check logs on replica to find out more " + "info!"); + case SUCCESS: + break; + } + } + + void SetInstanceToMain(const std::string &instance_name) override { + auto status = coordinator_handler_.SetInstanceToMain(instance_name); + switch (status) { + using enum memgraph::coordination::SetInstanceToMainCoordinatorStatus; + case NO_INSTANCE_WITH_NAME: + throw QueryRuntimeException("No instance with such name!"); + case NOT_COORDINATOR: + throw QueryRuntimeException("Couldn't set replica instance to main since this instance is not a coordinator!"); + case COULD_NOT_PROMOTE_TO_MAIN: + throw QueryRuntimeException( + "Couldn't set replica instance to main. Check coordinator and replica for more logs"); + case SUCCESS: + break; + } + } + +#endif + +#ifdef MG_ENTERPRISE + std::vector<coordination::CoordinatorInstanceStatus> ShowInstances() const override { + return coordinator_handler_.ShowInstances(); + } + +#endif + + private: + dbms::ReplicationHandler handler_; +#ifdef MG_ENTERPRISE + dbms::CoordinatorHandler coordinator_handler_; +#endif +}; + /// returns false if the replication role can't be set /// @throw QueryRuntimeException if an error ocurred. @@ -711,8 +836,8 @@ Callback HandleAuthQuery(AuthQuery *auth_query, InterpreterContext *interpreter_ } // namespace Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters ¶meters, - dbms::DbmsHandler *dbms_handler, const query::InterpreterConfig &config, - std::vector<Notification> *notifications) { + dbms::DbmsHandler *dbms_handler, CurrentDB ¤t_db, + const query::InterpreterConfig &config, std::vector<Notification> *notifications) { // TODO: MemoryResource for EvaluationContext, it should probably be passed as // the argument to Callback. EvaluationContext evaluation_context; @@ -723,6 +848,15 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters & Callback callback; switch (repl_query->action_) { case ReplicationQuery::Action::SET_REPLICATION_ROLE: { +#ifdef MG_ENTERPRISE + if (FLAGS_coordinator) { + throw QueryRuntimeException("Coordinator can't set roles!"); + } + if (FLAGS_coordinator_server_port) { + throw QueryRuntimeException("Can't set role manually on instance with coordinator server port."); + } +#endif + auto port = EvaluateOptionalExpression(repl_query->port_, evaluator); std::optional<int64_t> maybe_port; if (port.IsInt()) { @@ -743,6 +877,12 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters & return callback; } case ReplicationQuery::Action::SHOW_REPLICATION_ROLE: { +#ifdef MG_ENTERPRISE + if (FLAGS_coordinator) { + throw QueryRuntimeException("Coordinator doesn't have a replication role!"); + } +#endif + callback.header = {"replication role"}; callback.fn = [handler = ReplQueryHandler{dbms_handler}] { auto mode = handler.ShowReplicationRole(); @@ -758,7 +898,12 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters & return callback; } case ReplicationQuery::Action::REGISTER_REPLICA: { - const auto &name = repl_query->replica_name_; +#ifdef MG_ENTERPRISE + if (FLAGS_coordinator_server_port) { + throw QueryRuntimeException("Can't register replica manually on instance with coordinator server port."); + } +#endif + const auto &name = repl_query->instance_name_; const auto &sync_mode = repl_query->sync_mode_; auto socket_address = repl_query->socket_address_->Accept(evaluator); const auto replica_check_frequency = config.replication_replica_check_frequency; @@ -769,25 +914,38 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters & return std::vector<std::vector<TypedValue>>(); }; notifications->emplace_back(SeverityLevel::INFO, NotificationCode::REGISTER_REPLICA, - fmt::format("Replica {} is registered.", repl_query->replica_name_)); + fmt::format("Replica {} is registered.", repl_query->instance_name_)); return callback; } + case ReplicationQuery::Action::DROP_REPLICA: { - const auto &name = repl_query->replica_name_; +#ifdef MG_ENTERPRISE + if (FLAGS_coordinator_server_port) { + throw QueryRuntimeException("Can't drop replica manually on instance with coordinator server port."); + } +#endif + const auto &name = repl_query->instance_name_; callback.fn = [handler = ReplQueryHandler{dbms_handler}, name]() mutable { handler.DropReplica(name); return std::vector<std::vector<TypedValue>>(); }; notifications->emplace_back(SeverityLevel::INFO, NotificationCode::DROP_REPLICA, - fmt::format("Replica {} is dropped.", repl_query->replica_name_)); + fmt::format("Replica {} is dropped.", repl_query->instance_name_)); return callback; } case ReplicationQuery::Action::SHOW_REPLICAS: { +#ifdef MG_ENTERPRISE + if (FLAGS_coordinator) { + throw QueryRuntimeException("Coordinator cannot call SHOW REPLICAS! Use SHOW REPLICATION CLUSTER instead."); + } +#endif + callback.header = { "name", "socket_address", "sync_mode", "current_timestamp_of_replica", "number_of_timestamp_behind_master", "state"}; - callback.fn = [handler = ReplQueryHandler{dbms_handler}, replica_nfields = callback.header.size()] { - const auto &replicas = handler.ShowReplicas(); + callback.fn = [handler = ReplQueryHandler{dbms_handler}, replica_nfields = callback.header.size(), + db_acc = current_db.db_acc_] { + const auto &replicas = handler.ShowReplicas(*db_acc->get()); auto typed_replicas = std::vector<std::vector<TypedValue>>{}; typed_replicas.reserve(replicas.size()); for (const auto &replica : replicas) { @@ -833,6 +991,110 @@ Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters & } } +Callback HandleCoordinatorQuery(CoordinatorQuery *coordinator_query, const Parameters ¶meters, + dbms::DbmsHandler *dbms_handler, const query::InterpreterConfig &config, + std::vector<Notification> *notifications) { + Callback callback; + switch (coordinator_query->action_) { + case CoordinatorQuery::Action::REGISTER_INSTANCE: { + if (!license::global_license_checker.IsEnterpriseValidFast()) { + throw QueryException("Trying to use enterprise feature without a valid license."); + } +#ifdef MG_ENTERPRISE + if constexpr (!coordination::allow_ha) { + throw QueryRuntimeException( + "High availability is experimental feature. Please set MG_EXPERIMENTAL_HIGH_AVAILABILITY compile flag to " + "be able to use this functionality."); + } + if (!FLAGS_coordinator) { + throw QueryRuntimeException("Only coordinator can register coordinator server!"); + } + // TODO: MemoryResource for EvaluationContext, it should probably be passed as + // the argument to Callback. + EvaluationContext evaluation_context{.timestamp = QueryTimestamp(), .parameters = parameters}; + auto evaluator = PrimitiveLiteralExpressionEvaluator{evaluation_context}; + + auto coordinator_socket_address_tv = coordinator_query->coordinator_socket_address_->Accept(evaluator); + auto replication_socket_address_tv = coordinator_query->replication_socket_address_->Accept(evaluator); + callback.fn = [handler = CoordQueryHandler{dbms_handler}, coordinator_socket_address_tv, + replication_socket_address_tv, main_check_frequency = config.replication_replica_check_frequency, + instance_name = coordinator_query->instance_name_, + sync_mode = coordinator_query->sync_mode_]() mutable { + handler.RegisterInstance(std::string(coordinator_socket_address_tv.ValueString()), + std::string(replication_socket_address_tv.ValueString()), main_check_frequency, + instance_name, sync_mode); + return std::vector<std::vector<TypedValue>>(); + }; + + notifications->emplace_back( + SeverityLevel::INFO, NotificationCode::REGISTER_COORDINATOR_SERVER, + fmt::format("Coordinator has registered coordinator server on {} for instance {}.", + coordinator_socket_address_tv.ValueString(), coordinator_query->instance_name_)); + return callback; +#endif + } + case CoordinatorQuery::Action::SET_INSTANCE_TO_MAIN: { + if (!license::global_license_checker.IsEnterpriseValidFast()) { + throw QueryException("Trying to use enterprise feature without a valid license."); + } +#ifdef MG_ENTERPRISE + if constexpr (!coordination::allow_ha) { + throw QueryRuntimeException( + "High availability is experimental feature. Please set MG_EXPERIMENTAL_HIGH_AVAILABILITY compile flag to " + "be able to use this functionality."); + } + if (!FLAGS_coordinator) { + throw QueryRuntimeException("Only coordinator can register coordinator server!"); + } + // TODO: MemoryResource for EvaluationContext, it should probably be passed as + // the argument to Callback. + EvaluationContext evaluation_context{.timestamp = QueryTimestamp(), .parameters = parameters}; + auto evaluator = PrimitiveLiteralExpressionEvaluator{evaluation_context}; + + callback.fn = [handler = CoordQueryHandler{dbms_handler}, + instance_name = coordinator_query->instance_name_]() mutable { + handler.SetInstanceToMain(instance_name); + return std::vector<std::vector<TypedValue>>(); + }; + + return callback; +#endif + } + case CoordinatorQuery::Action::SHOW_REPLICATION_CLUSTER: { + if (!license::global_license_checker.IsEnterpriseValidFast()) { + throw QueryException("Trying to use enterprise feature without a valid license."); + } +#ifdef MG_ENTERPRISE + if constexpr (!coordination::allow_ha) { + throw QueryRuntimeException( + "High availability is experimental feature. Please set MG_EXPERIMENTAL_HIGH_AVAILABILITY compile flag to " + "be able to use this functionality."); + } + if (!FLAGS_coordinator) { + throw QueryRuntimeException("Only coordinator can run SHOW REPLICATION CLUSTER."); + } + + callback.header = {"name", "socket_address", "alive", "role"}; + callback.fn = [handler = CoordQueryHandler{dbms_handler}, replica_nfields = callback.header.size()]() mutable { + auto const instances = handler.ShowInstances(); + std::vector<std::vector<TypedValue>> result{}; + result.reserve(result.size()); + + std::ranges::transform(instances, std::back_inserter(result), + [](const auto &status) -> std::vector<TypedValue> { + return {TypedValue{status.instance_name}, TypedValue{status.socket_address}, + TypedValue{status.is_alive}, TypedValue{status.replication_role}}; + }); + + return result; + }; + return callback; +#endif + } + return callback; + } +} + stream::CommonStreamInfo GetCommonStreamInfo(StreamQuery *stream_query, ExpressionVisitor<TypedValue> &evaluator) { return { .batch_interval = GetOptionalValue<std::chrono::milliseconds>(stream_query->batch_interval_, evaluator) @@ -963,12 +1225,19 @@ Callback HandleStreamQuery(StreamQuery *stream_query, const Parameters ¶mete throw utils::BasicException("Parameter BATCH_LIMIT cannot hold negative value"); } - callback.fn = [streams = db_acc->streams(), stream_name = stream_query->stream_name_, batch_limit, timeout]() { + callback.fn = [db_acc, streams = db_acc->streams(), stream_name = stream_query->stream_name_, batch_limit, + timeout]() { + if (db_acc.is_deleting()) { + throw QueryException("Can not start stream while database is being dropped."); + } streams->StartWithLimit(stream_name, static_cast<uint64_t>(batch_limit.value()), timeout); return std::vector<std::vector<TypedValue>>{}; }; } else { - callback.fn = [streams = db_acc->streams(), stream_name = stream_query->stream_name_]() { + callback.fn = [db_acc, streams = db_acc->streams(), stream_name = stream_query->stream_name_]() { + if (db_acc.is_deleting()) { + throw QueryException("Can not start stream while database is being dropped."); + } streams->Start(stream_name); return std::vector<std::vector<TypedValue>>{}; }; @@ -1457,8 +1726,7 @@ PreparedQuery Interpreter::PrepareTransactionQuery(std::string_view query_upper, std::function<void()> handler; if (query_upper == "BEGIN") { - query_executions_.clear(); - transaction_queries_->clear(); + ResetInterpreter(); // TODO: Evaluate doing move(extras). Currently the extras is very small, but this will be important if it ever // becomes large. handler = [this, extras = extras] { @@ -1818,7 +2086,7 @@ PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_tra PreparedQuery PrepareDumpQuery(ParsedQuery parsed_query, CurrentDB ¤t_db) { MG_ASSERT(current_db.execution_db_accessor_, "Dump query expects a current DB transaction"); auto *dba = &*current_db.execution_db_accessor_; - auto plan = std::make_shared<PullPlanDump>(dba); + auto plan = std::make_shared<PullPlanDump>(dba, *current_db.db_acc_); return PreparedQuery{ {"QUERY"}, std::move(parsed_query.required_privileges), @@ -2260,14 +2528,14 @@ PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transa PreparedQuery PrepareReplicationQuery(ParsedQuery parsed_query, bool in_explicit_transaction, std::vector<Notification> *notifications, dbms::DbmsHandler &dbms_handler, - const InterpreterConfig &config) { + CurrentDB ¤t_db, const InterpreterConfig &config) { if (in_explicit_transaction) { throw ReplicationModificationInMulticommandTxException(); } auto *replication_query = utils::Downcast<ReplicationQuery>(parsed_query.query); - auto callback = - HandleReplicationQuery(replication_query, parsed_query.parameters, &dbms_handler, config, notifications); + auto callback = HandleReplicationQuery(replication_query, parsed_query.parameters, &dbms_handler, current_db, config, + notifications); return PreparedQuery{callback.header, std::move(parsed_query.required_privileges), [callback_fn = std::move(callback.fn), pull_plan = std::shared_ptr<PullPlanVector>{nullptr}]( @@ -2286,6 +2554,34 @@ PreparedQuery PrepareReplicationQuery(ParsedQuery parsed_query, bool in_explicit // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) } +PreparedQuery PrepareCoordinatorQuery(ParsedQuery parsed_query, bool in_explicit_transaction, + std::vector<Notification> *notifications, dbms::DbmsHandler &dbms_handler, + const InterpreterConfig &config) { + if (in_explicit_transaction) { + throw CoordinatorModificationInMulticommandTxException(); + } + + auto *coordinator_query = utils::Downcast<CoordinatorQuery>(parsed_query.query); + auto callback = + HandleCoordinatorQuery(coordinator_query, parsed_query.parameters, &dbms_handler, config, notifications); + + return PreparedQuery{callback.header, std::move(parsed_query.required_privileges), + [callback_fn = std::move(callback.fn), pull_plan = std::shared_ptr<PullPlanVector>{nullptr}]( + AnyStream *stream, std::optional<int> n) mutable -> std::optional<QueryHandlerResult> { + if (UNLIKELY(!pull_plan)) { + pull_plan = std::make_shared<PullPlanVector>(callback_fn()); + } + + if (pull_plan->Pull(stream, n)) [[likely]] { + return QueryHandlerResult::COMMIT; + } + return std::nullopt; + }, + RWType::NONE}; + // False positive report for the std::make_shared above + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) +} + PreparedQuery PrepareLockPathQuery(ParsedQuery parsed_query, bool in_explicit_transaction, CurrentDB ¤t_db) { if (in_explicit_transaction) { throw LockPathModificationInMulticommandTxException(); @@ -2786,8 +3082,8 @@ PreparedQuery PrepareEdgeImportModeQuery(ParsedQuery parsed_query, CurrentDB &cu RWType::NONE}; } -PreparedQuery PrepareCreateSnapshotQuery(ParsedQuery parsed_query, bool in_explicit_transaction, - CurrentDB ¤t_db) { +PreparedQuery PrepareCreateSnapshotQuery(ParsedQuery parsed_query, bool in_explicit_transaction, CurrentDB ¤t_db, + replication_coordination_glue::ReplicationRole replication_role) { if (in_explicit_transaction) { throw CreateSnapshotInMulticommandTxException(); } @@ -2802,9 +3098,10 @@ PreparedQuery PrepareCreateSnapshotQuery(ParsedQuery parsed_query, bool in_expli return PreparedQuery{ {}, std::move(parsed_query.required_privileges), - [storage](AnyStream * /*stream*/, std::optional<int> /*n*/) -> std::optional<QueryHandlerResult> { + [storage, replication_role](AnyStream * /*stream*/, + std::optional<int> /*n*/) -> std::optional<QueryHandlerResult> { auto *mem_storage = static_cast<storage::InMemoryStorage *>(storage); - if (auto maybe_error = mem_storage->CreateSnapshot(); maybe_error.HasError()) { + if (auto maybe_error = mem_storage->CreateSnapshot(replication_role); maybe_error.HasError()) { switch (maybe_error.GetError()) { case storage::InMemoryStorage::CreateSnapshotError::DisabledForReplica: throw utils::BasicException( @@ -2864,7 +3161,7 @@ auto ShowTransactions(const std::unordered_set<Interpreter *> &interpreters, con auto get_interpreter_db_name = [&]() -> std::string const & { static std::string all; - return interpreter->current_db_.db_acc_ ? interpreter->current_db_.db_acc_->get()->id() : all; + return interpreter->current_db_.db_acc_ ? interpreter->current_db_.db_acc_->get()->name() : all; }; if (transaction_id.has_value() && (interpreter->username_ == username || privilege_checker(get_interpreter_db_name()))) { @@ -2977,15 +3274,16 @@ PreparedQuery PrepareDatabaseInfoQuery(ParsedQuery parsed_query, bool in_explici auto *info_query = utils::Downcast<DatabaseInfoQuery>(parsed_query.query); std::vector<std::string> header; std::function<std::pair<std::vector<std::vector<TypedValue>>, QueryHandlerResult>()> handler; - + auto *database = current_db.db_acc_->get(); switch (info_query->info_type_) { case DatabaseInfoQuery::InfoType::INDEX: { header = {"index type", "label", "property", "count"}; - handler = [storage = current_db.db_acc_->get()->storage(), dba] { + handler = [database, dba] { + auto *storage = database->storage(); const std::string_view label_index_mark{"label"}; const std::string_view label_property_index_mark{"label+property"}; auto info = dba->ListAllIndices(); - auto storage_acc = storage->Access(); + auto storage_acc = database->Access(); std::vector<std::vector<TypedValue>> results; results.reserve(info.label.size() + info.label_property.size()); for (const auto &item : info.label) { @@ -3045,7 +3343,7 @@ PreparedQuery PrepareDatabaseInfoQuery(ParsedQuery parsed_query, bool in_explici case DatabaseInfoQuery::InfoType::EDGE_TYPES: { header = {"edge types"}; handler = [storage = current_db.db_acc_->get()->storage(), dba] { - if (!storage->config_.items.enable_schema_metadata) { + if (!storage->config_.salient.items.enable_schema_metadata) { throw QueryRuntimeException( "The metadata collection for edge-types is disabled. To enable it, restart your instance and set the " "storage-enable-schema-metadata flag to True."); @@ -3065,7 +3363,7 @@ PreparedQuery PrepareDatabaseInfoQuery(ParsedQuery parsed_query, bool in_explici case DatabaseInfoQuery::InfoType::NODE_LABELS: { header = {"node labels"}; handler = [storage = current_db.db_acc_->get()->storage(), dba] { - if (!storage->config_.items.enable_schema_metadata) { + if (!storage->config_.salient.items.enable_schema_metadata) { throw QueryRuntimeException( "The metadata collection for node-labels is disabled. To enable it, restart your instance and set the " "storage-enable-schema-metadata flag to True."); @@ -3124,7 +3422,7 @@ PreparedQuery PrepareSystemInfoQuery(ParsedQuery parsed_query, bool in_explicit_ const int64_t vm_max_map_count_storage_info = vm_max_map_count.has_value() ? vm_max_map_count.value() : memgraph::utils::VM_MAX_MAP_COUNT_DEFAULT; std::vector<std::vector<TypedValue>> results{ - {TypedValue("name"), TypedValue(storage->id())}, + {TypedValue("name"), TypedValue(storage->name())}, {TypedValue("vertex_count"), TypedValue(static_cast<int64_t>(info.vertex_count))}, {TypedValue("edge_count"), TypedValue(static_cast<int64_t>(info.edge_count))}, {TypedValue("average_degree"), TypedValue(info.average_degree)}, @@ -3390,8 +3688,6 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur if (!license::global_license_checker.IsEnterpriseValidFast()) { throw QueryException("Trying to use enterprise feature without a valid license."); } - // TODO: Remove once replicas support multi-tenant replication - if (!current_db.db_acc_) throw DatabaseContextRequiredException("Multi database queries require a defined database."); auto *query = utils::Downcast<MultiDatabaseQuery>(parsed_query.query); auto *db_handler = interpreter_context->dbms_handler; @@ -3399,7 +3695,7 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur const bool is_replica = interpreter_context->repl_state->IsReplica(); switch (query->action_) { - case MultiDatabaseQuery::Action::CREATE: + case MultiDatabaseQuery::Action::CREATE: { if (is_replica) { throw QueryException("Query forbidden on the replica!"); } @@ -3440,8 +3736,8 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur RWType::W, "" // No target DB possible }; - - case MultiDatabaseQuery::Action::USE: + } + case MultiDatabaseQuery::Action::USE: { if (current_db.in_explicit_db_) { throw QueryException("Database switching is prohibited if session explicitly defines the used database"); } @@ -3456,7 +3752,7 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur std::string res; try { - if (current_db.db_acc_ && db_name == current_db.db_acc_->get()->id()) { + if (current_db.db_acc_ && db_name == current_db.db_acc_->get()->name()) { res = "Already using " + db_name; } else { auto tmp = db_handler->Get(db_name); @@ -3477,11 +3773,12 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur }, RWType::NONE, query->db_name_}; - - case MultiDatabaseQuery::Action::DROP: + } + case MultiDatabaseQuery::Action::DROP: { if (is_replica) { throw QueryException("Query forbidden on the replica!"); } + return PreparedQuery{ {"STATUS"}, std::move(parsed_query.required_privileges), @@ -3491,10 +3788,10 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur try { // Remove database - auto success = db_handler->Delete(db_name); + auto success = db_handler->TryDelete(db_name); if (!success.HasError()) { // Remove from auth - auth->DeleteDatabase(db_name); + if (auth) auth->DeleteDatabase(db_name); } else { switch (success.GetError()) { case dbms::DeleteError::DEFAULT_DB: @@ -3522,48 +3819,56 @@ PreparedQuery PrepareMultiDatabaseQuery(ParsedQuery parsed_query, CurrentDB &cur }, RWType::W, query->db_name_}; - } + } + case MultiDatabaseQuery::Action::SHOW: { + return PreparedQuery{ + {"Current"}, + std::move(parsed_query.required_privileges), + [db_acc = current_db.db_acc_, pull_plan = std::shared_ptr<PullPlanVector>(nullptr)]( + AnyStream *stream, std::optional<int> n) mutable -> std::optional<QueryHandlerResult> { + if (!pull_plan) { + std::vector<std::vector<TypedValue>> results; + auto db_name = db_acc ? TypedValue{db_acc->get()->storage()->name()} : TypedValue{}; + results.push_back({std::move(db_name)}); + pull_plan = std::make_shared<PullPlanVector>(std::move(results)); + } + + if (pull_plan->Pull(stream, n)) { + return QueryHandlerResult::NOTHING; + } + return std::nullopt; + }, + RWType::NONE, + "" // No target DB + }; + } + }; #else throw QueryException("Query not supported."); #endif } -PreparedQuery PrepareShowDatabasesQuery(ParsedQuery parsed_query, CurrentDB ¤t_db, - InterpreterContext *interpreter_context, +PreparedQuery PrepareShowDatabasesQuery(ParsedQuery parsed_query, InterpreterContext *interpreter_context, const std::optional<std::string> &username) { #ifdef MG_ENTERPRISE - - // TODO: split query into two, Databases (no need for current_db), & Current database (uses current_db) - MG_ASSERT(current_db.db_acc_, "Show Database Level query expects a current DB"); - storage::Storage *storage = current_db.db_acc_->get()->storage(); - if (!license::global_license_checker.IsEnterpriseValidFast()) { throw QueryException("Trying to use enterprise feature without a valid license."); } - // TODO pick directly from ic auto *db_handler = interpreter_context->dbms_handler; AuthQueryHandler *auth = interpreter_context->auth; Callback callback; - callback.header = {"Name", "Current"}; - callback.fn = [auth, storage, db_handler, username]() mutable -> std::vector<std::vector<TypedValue>> { + callback.header = {"Name"}; + callback.fn = [auth, db_handler, username]() mutable -> std::vector<std::vector<TypedValue>> { std::vector<std::vector<TypedValue>> status; - const auto &in_use = storage->id(); - bool found_current = false; - auto gen_status = [&]<typename T, typename K>(T all, K denied) { Sort(all); Sort(denied); status.reserve(all.size()); for (const auto &name : all) { - TypedValue use(""); - if (!found_current && Same(name, in_use)) { - use = TypedValue("*"); - found_current = true; - } - status.push_back({TypedValue(name), std::move(use)}); + status.push_back({TypedValue(name)}); } // No denied databases (no need to filter them out) @@ -3593,7 +3898,6 @@ PreparedQuery PrepareShowDatabasesQuery(ParsedQuery parsed_query, CurrentDB &cur } } - if (!found_current) throw QueryRuntimeException("Missing current database!"); return status; }; @@ -3629,15 +3933,13 @@ void Interpreter::BeginTransaction(QueryExtras const &extras) { void Interpreter::CommitTransaction() { const auto prepared_query = PrepareTransactionQuery("COMMIT"); prepared_query.query_handler(nullptr, {}); - query_executions_.clear(); - transaction_queries_->clear(); + ResetInterpreter(); } void Interpreter::RollbackTransaction() { const auto prepared_query = PrepareTransactionQuery("ROLLBACK"); prepared_query.query_handler(nullptr, {}); - query_executions_.clear(); - transaction_queries_->clear(); + ResetInterpreter(); } #if MG_ENTERPRISE @@ -3653,11 +3955,6 @@ void Interpreter::SetCurrentDB(std::string_view db_name, bool in_explicit_db) { Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string, const std::map<std::string, storage::PropertyValue> ¶ms, QueryExtras const &extras) { - // TODO: Remove once the interpreter is storage/tx independent and could run without an associated database - if (!current_db_.db_acc_) { - throw DatabaseContextRequiredException("Database required for the query."); - } - // Handle transaction control queries. const auto upper_case_query = utils::ToUpperCase(query_string); const auto trimmed_query = utils::Trim(upper_case_query); @@ -3671,18 +3968,16 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string, return {query_execution->prepared_query->header, query_execution->prepared_query->privileges, qid, {}}; } - if (!in_explicit_transaction_) { - transaction_queries_->clear(); - } - // Don't save BEGIN, COMMIT or ROLLBACK - transaction_queries_->push_back(query_string); + // NOTE: query_string is not BEGIN, COMMIT or ROLLBACK // All queries other than transaction control queries advance the command in // an explicit transaction block. if (in_explicit_transaction_) { + transaction_queries_->push_back(query_string); AdvanceCommand(); } else { - query_executions_.clear(); + ResetInterpreter(); + transaction_queries_->push_back(query_string); if (current_db_.db_transactional_accessor_ /* && !in_explicit_transaction_*/) { // If we're not in an explicit transaction block and we have an open // transaction, abort it since we're about to prepare a new query. @@ -3740,6 +4035,37 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string, // field with an improved estimate. query_execution->summary["cost_estimate"] = 0.0; + // System queries require strict ordering; since there is no MVCC-like thing, we allow single queries + bool system_queries = utils::Downcast<AuthQuery>(parsed_query.query) || + utils::Downcast<MultiDatabaseQuery>(parsed_query.query) || + utils::Downcast<ShowDatabasesQuery>(parsed_query.query) || + utils::Downcast<ReplicationQuery>(parsed_query.query); + + // TODO Split SHOW REPLICAS (which needs the db) and other replication queries + auto system_transaction_guard = std::invoke([&]() -> std::optional<SystemTransactionGuard> { + if (system_queries) { + // TODO: Ordering between system and data queries + // Start a system transaction + auto system_unique = std::unique_lock{interpreter_context_->dbms_handler->system_lock_, std::defer_lock}; + if (!system_unique.try_lock_for(std::chrono::milliseconds(kSystemTxTryMS))) { + throw ConcurrentSystemQueriesException("Multiple concurrent system queries are not supported."); + } + return std::optional<SystemTransactionGuard>{std::in_place, std::move(system_unique), + *interpreter_context_->dbms_handler}; + } + return std::nullopt; + }); + + // Some queries do not require a database to be executed (current_db_ won't be passed on to the Prepare*; special + // case for use database which overwrites the current database) + bool no_db_required = system_queries || utils::Downcast<ShowConfigQuery>(parsed_query.query) || + utils::Downcast<SettingQuery>(parsed_query.query) || + utils::Downcast<VersionQuery>(parsed_query.query) || + utils::Downcast<TransactionQueueQuery>(parsed_query.query); + if (!no_db_required && !current_db_.db_acc_) { + throw DatabaseContextRequiredException("Database required for the query."); + } + // Some queries require an active transaction in order to be prepared. // TODO: make a better analysis visitor over the `parsed_query.query` bool requires_db_transaction = @@ -3758,10 +4084,12 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string, SetupDatabaseTransaction(could_commit, unique); } - // TODO: none database transaction (assuming mutually exclusive from DB transactions) - // if (!requires_db_transaction) { - // /* something */ - // } +#ifdef MG_ENTERPRISE + if (FLAGS_coordinator && !utils::Downcast<CoordinatorQuery>(parsed_query.query) && + !utils::Downcast<SettingQuery>(parsed_query.query)) { + throw QueryRuntimeException("Coordinator can run only coordinator queries!"); + } +#endif utils::Timer planning_timer; PreparedQuery prepared_query; @@ -3804,6 +4132,10 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string, /// TODO: make replication DB agnostic prepared_query = PrepareReplicationQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->notifications, + *interpreter_context_->dbms_handler, current_db_, interpreter_context_->config); + } else if (utils::Downcast<CoordinatorQuery>(parsed_query.query)) { + prepared_query = + PrepareCoordinatorQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->notifications, *interpreter_context_->dbms_handler, interpreter_context_->config); } else if (utils::Downcast<LockPathQuery>(parsed_query.query)) { prepared_query = PrepareLockPathQuery(std::move(parsed_query), in_explicit_transaction_, current_db_); @@ -3823,7 +4155,9 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string, } else if (utils::Downcast<IsolationLevelQuery>(parsed_query.query)) { prepared_query = PrepareIsolationLevelQuery(std::move(parsed_query), in_explicit_transaction_, current_db_, this); } else if (utils::Downcast<CreateSnapshotQuery>(parsed_query.query)) { - prepared_query = PrepareCreateSnapshotQuery(std::move(parsed_query), in_explicit_transaction_, current_db_); + auto const replication_role = interpreter_context_->repl_state->GetRole(); + prepared_query = + PrepareCreateSnapshotQuery(std::move(parsed_query), in_explicit_transaction_, current_db_, replication_role); } else if (utils::Downcast<SettingQuery>(parsed_query.query)) { /// SYSTEM PURE prepared_query = PrepareSettingQuery(std::move(parsed_query), in_explicit_transaction_); @@ -3844,12 +4178,11 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string, throw MultiDatabaseQueryInMulticommandTxException(); } /// SYSTEM (Replication) + INTERPRETER - prepared_query = - PrepareMultiDatabaseQuery(std::move(parsed_query), current_db_, interpreter_context_, on_change_); + // DMG_ASSERT(system_guard); + prepared_query = PrepareMultiDatabaseQuery(std::move(parsed_query), current_db_, interpreter_context_, on_change_ + /*, *system_guard*/); } else if (utils::Downcast<ShowDatabasesQuery>(parsed_query.query)) { - /// SYSTEM PURE ("SHOW DATABASES") - /// INTERPRETER (TODO: "SHOW DATABASE") - prepared_query = PrepareShowDatabasesQuery(std::move(parsed_query), current_db_, interpreter_context_, username_); + prepared_query = PrepareShowDatabasesQuery(std::move(parsed_query), interpreter_context_, username_); } else if (utils::Downcast<EdgeImportModeQuery>(parsed_query.query)) { if (in_explicit_transaction_) { throw EdgeImportModeModificationInMulticommandTxException(); @@ -3874,10 +4207,12 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string, // Set the target db to the current db (some queries have different target from the current db) if (!query_execution->prepared_query->db) { - query_execution->prepared_query->db = current_db_.db_acc_->get()->id(); + query_execution->prepared_query->db = current_db_.db_acc_->get()->name(); } query_execution->summary["db"] = *query_execution->prepared_query->db; + // prepare is done, move system txn guard to be owned by interpreter + system_transaction_guard_ = std::move(system_transaction_guard); return {query_execution->prepared_query->header, query_execution->prepared_query->privileges, qid, query_execution->prepared_query->db}; } catch (const utils::BasicException &) { @@ -3889,9 +4224,11 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string, throw; } } + void Interpreter::SetupDatabaseTransaction(bool couldCommit, bool unique) { current_db_.SetupDatabaseTransaction(GetIsolationLevelOverride(), couldCommit, unique); } + void Interpreter::SetupInterpreterTransaction(const QueryExtras &extras) { metrics::IncrementCounter(metrics::ActiveTransactions); transaction_status_.store(TransactionStatus::ACTIVE, std::memory_order_release); @@ -3970,7 +4307,9 @@ void RunTriggersAfterCommit(dbms::DatabaseAccess db_acc, InterpreterContext *int continue; } - auto maybe_commit_error = db_accessor.Commit(); + bool is_main = interpreter_context->repl_state->IsMain(); + auto maybe_commit_error = db_accessor.Commit({.is_main = is_main}, db_acc); + if (maybe_commit_error.HasError()) { const auto &error = maybe_commit_error.GetError(); @@ -4021,10 +4360,35 @@ void Interpreter::Commit() { // We should document clearly that all results should be pulled to complete // a query. current_transaction_.reset(); - if (!current_db_.db_transactional_accessor_) return; + if (!current_db_.db_transactional_accessor_ || !current_db_.db_acc_) { + // No database nor db transaction; check for system transaction + if (!system_transaction_guard_) return; - // TODO: Better (or removed) check - if (!current_db_.db_acc_) return; + // TODO Distinguish between data and system transaction state + // Think about updating the status to a struct with bitfield + // Clean transaction status on exit + utils::OnScopeExit clean_status([this]() { + system_transaction_guard_.reset(); + // System transactions are not terminable + // Durability has happened at time of PULL + // Commit is doing replication and timestamp update + // The DBMS does not support MVCC, so doing durability here doesn't change the overall logic; we cannot abort! + // What we are trying to do is set the transaction back to IDLE + // We cannot simply put it to IDLE, since the status is used as a syncronization method and we have to follow + // its logic. There are 2 states when we could update to IDLE (ACTIVE and TERMINATED). + auto expected = TransactionStatus::ACTIVE; + while (!transaction_status_.compare_exchange_weak(expected, TransactionStatus::IDLE)) { + if (expected == TransactionStatus::TERMINATED) { + continue; + } + expected = TransactionStatus::ACTIVE; + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + }); + + system_transaction_guard_->Commit(); + return; + } auto *db = current_db_.db_acc_->get(); /* @@ -4094,19 +4458,19 @@ void Interpreter::Commit() { }; utils::OnScopeExit members_reseter(reset_necessary_members); - auto commit_confirmed_by_all_sync_repplicas = true; + auto commit_confirmed_by_all_sync_replicas = true; - auto maybe_commit_error = - current_db_.db_transactional_accessor_->Commit(std::nullopt, interpreter_context_->repl_state->IsMain()); + bool is_main = interpreter_context_->repl_state->IsMain(); + auto maybe_commit_error = current_db_.db_transactional_accessor_->Commit({.is_main = is_main}, current_db_.db_acc_); if (maybe_commit_error.HasError()) { const auto &error = maybe_commit_error.GetError(); std::visit( [&execution_db_accessor = current_db_.execution_db_accessor_, - &commit_confirmed_by_all_sync_repplicas]<typename T>(T &&arg) { + &commit_confirmed_by_all_sync_replicas]<typename T>(const T &arg) { using ErrorType = std::remove_cvref_t<T>; if constexpr (std::is_same_v<ErrorType, storage::ReplicationError>) { - commit_confirmed_by_all_sync_repplicas = false; + commit_confirmed_by_all_sync_replicas = false; } else if constexpr (std::is_same_v<ErrorType, storage::ConstraintViolation>) { const auto &constraint_violation = arg; auto &label_name = execution_db_accessor->LabelToName(constraint_violation.label); @@ -4146,7 +4510,6 @@ void Interpreter::Commit() { if (trigger_context && db->trigger_store()->AfterCommitTriggers().size() > 0) { db->AddTask([this, trigger_context = std::move(*trigger_context), user_transaction = std::shared_ptr(std::move(current_db_.db_transactional_accessor_))]() mutable { - // TODO: Should this take the db_ and not Access()? RunTriggersAfterCommit(*current_db_.db_acc_, interpreter_context_, std::move(trigger_context), &this->transaction_status_); user_transaction->FinalizeTransaction(); @@ -4155,7 +4518,7 @@ void Interpreter::Commit() { } SPDLOG_DEBUG("Finished committing the transaction"); - if (!commit_confirmed_by_all_sync_repplicas) { + if (!commit_confirmed_by_all_sync_replicas) { throw ReplicationException("At least one SYNC replica has not confirmed committing last transaction."); } } diff --git a/src/query/interpreter.hpp b/src/query/interpreter.hpp index 5cb73cb07..42100059c 100644 --- a/src/query/interpreter.hpp +++ b/src/query/interpreter.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -16,6 +16,7 @@ #include <gflags/gflags.h> #include "dbms/database.hpp" +#include "dbms/dbms_handler.hpp" #include "memory/query_memory_control.hpp" #include "query/auth_checker.hpp" #include "query/auth_query_handler.hpp" @@ -51,6 +52,10 @@ #include "utils/timer.hpp" #include "utils/tsc.hpp" +#ifdef MG_ENTERPRISE +#include "coordination/coordinator_instance_status.hpp" +#endif + namespace memgraph::metrics { extern const Event FailedQuery; extern const Event FailedPrepare; @@ -67,16 +72,16 @@ inline constexpr size_t kExecutionPoolMaxBlockSize = 1024UL; // 2 ^ 10 enum class QueryHandlerResult { COMMIT, ABORT, NOTHING }; -class ReplicationQueryHandler { +class CoordinatorQueryHandler { public: - ReplicationQueryHandler() = default; - virtual ~ReplicationQueryHandler() = default; + CoordinatorQueryHandler() = default; + virtual ~CoordinatorQueryHandler() = default; - ReplicationQueryHandler(const ReplicationQueryHandler &) = default; - ReplicationQueryHandler &operator=(const ReplicationQueryHandler &) = default; + CoordinatorQueryHandler(const CoordinatorQueryHandler &) = default; + CoordinatorQueryHandler &operator=(const CoordinatorQueryHandler &) = default; - ReplicationQueryHandler(ReplicationQueryHandler &&) = default; - ReplicationQueryHandler &operator=(ReplicationQueryHandler &&) = default; + CoordinatorQueryHandler(CoordinatorQueryHandler &&) = default; + CoordinatorQueryHandler &operator=(CoordinatorQueryHandler &&) = default; struct Replica { std::string name; @@ -88,22 +93,32 @@ class ReplicationQueryHandler { ReplicationQuery::ReplicaState state; }; +#ifdef MG_ENTERPRISE + struct MainReplicaStatus { + std::string_view name; + std::string_view socket_address; + bool alive; + bool is_main; + + MainReplicaStatus(std::string_view name, std::string_view socket_address, bool alive, bool is_main) + : name{name}, socket_address{socket_address}, alive{alive}, is_main{is_main} {} + }; +#endif + +#ifdef MG_ENTERPRISE /// @throw QueryRuntimeException if an error ocurred. - virtual void SetReplicationRole(ReplicationQuery::ReplicationRole replication_role, std::optional<int64_t> port) = 0; + virtual void RegisterInstance(const std::string &coordinator_socket_address, + const std::string &replication_socket_address, + const std::chrono::seconds instance_check_frequency, const std::string &instance_name, + CoordinatorQuery::SyncMode sync_mode) = 0; /// @throw QueryRuntimeException if an error ocurred. - virtual ReplicationQuery::ReplicationRole ShowReplicationRole() const = 0; + virtual void SetInstanceToMain(const std::string &instance_name) = 0; /// @throw QueryRuntimeException if an error ocurred. - virtual void RegisterReplica(const std::string &name, const std::string &socket_address, - ReplicationQuery::SyncMode sync_mode, - const std::chrono::seconds replica_check_frequency) = 0; + virtual std::vector<coordination::CoordinatorInstanceStatus> ShowInstances() const = 0; - /// @throw QueryRuntimeException if an error ocurred. - virtual void DropReplica(std::string_view replica_name) = 0; - - /// @throw QueryRuntimeException if an error ocurred. - virtual std::vector<Replica> ShowReplicas() const = 0; +#endif }; class AnalyzeGraphQueryHandler { @@ -281,7 +296,38 @@ class Interpreter final { void SetUser(std::string_view username); + struct SystemTransactionGuard { + explicit SystemTransactionGuard(std::unique_lock<utils::ResourceLock> guard, dbms::DbmsHandler &dbms_handler) + : system_guard_(std::move(guard)), dbms_handler_{&dbms_handler} { + dbms_handler_->NewSystemTransaction(); + } + SystemTransactionGuard &operator=(SystemTransactionGuard &&) = default; + SystemTransactionGuard(SystemTransactionGuard &&) = default; + + ~SystemTransactionGuard() { + if (system_guard_.owns_lock()) dbms_handler_->ResetSystemTransaction(); + } + + dbms::AllSyncReplicaStatus Commit() { return dbms_handler_->Commit(); } + + private: + std::unique_lock<utils::ResourceLock> system_guard_; + dbms::DbmsHandler *dbms_handler_; + }; + + std::optional<SystemTransactionGuard> system_transaction_guard_{}; + private: + void ResetInterpreter() { + query_executions_.clear(); + system_guard.reset(); + system_transaction_guard_.reset(); + transaction_queries_->clear(); + if (current_db_.db_acc_ && current_db_.db_acc_->is_deleting()) { + current_db_.db_acc_.reset(); + } + } + struct QueryExecution { std::variant<utils::MonotonicBufferResource, utils::PoolResource> execution_memory; utils::ResourceWithOutOfMemoryException execution_memory_with_exception; @@ -340,6 +386,9 @@ class Interpreter final { // TODO Figure out how this would work for multi-database // Exists only during a single transaction (for now should be okay as is) std::vector<std::unique_ptr<QueryExecution>> query_executions_; + // TODO: our upgradable lock guard for system + std::optional<utils::ResourceLockGuard> system_guard; + // all queries that are run as part of the current transaction utils::Synchronized<std::vector<std::string>, utils::SpinLock> transaction_queries_; @@ -435,8 +484,7 @@ std::map<std::string, TypedValue> Interpreter::Pull(TStream *result_stream, std: // NOTE: we cannot clear query_execution inside the Abort and Commit // methods as we will delete summary contained in them which we need // after our query finished executing. - query_executions_.clear(); - transaction_queries_->clear(); + ResetInterpreter(); } else { // We can only clear this execution as some of the queries // in the transaction can be in unfinished state diff --git a/src/query/interpreter_context.cpp b/src/query/interpreter_context.cpp index 75d734645..cace25ec6 100644 --- a/src/query/interpreter_context.cpp +++ b/src/query/interpreter_context.cpp @@ -56,7 +56,7 @@ std::vector<std::vector<TypedValue>> InterpreterContext::TerminateTransactions( std::iter_swap(it, not_found_midpoint); auto get_interpreter_db_name = [&]() -> std::string const & { static std::string all; - return interpreter->current_db_.db_acc_ ? interpreter->current_db_.db_acc_->get()->id() : all; + return interpreter->current_db_.db_acc_ ? interpreter->current_db_.db_acc_->get()->name() : all; }; if (interpreter->username_ == username || privilege_checker(get_interpreter_db_name())) { killed = true; // Note: this is used by the above `clean_status` (OnScopeExit) diff --git a/src/query/interpreter_context.hpp b/src/query/interpreter_context.hpp index af8648376..9b54dbd3a 100644 --- a/src/query/interpreter_context.hpp +++ b/src/query/interpreter_context.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -22,6 +22,8 @@ #include "query/cypher_query_interpreter.hpp" #include "query/typed_value.hpp" #include "replication/state.hpp" +#include "storage/v2/config.hpp" +#include "storage/v2/transaction.hpp" #include "utils/gatekeeper.hpp" #include "utils/skip_list.hpp" #include "utils/spin_lock.hpp" @@ -57,6 +59,7 @@ struct InterpreterContext { // GLOBAL memgraph::replication::ReplicationState *repl_state; + AuthQueryHandler *auth; AuthChecker *auth_checker; diff --git a/src/query/metadata.cpp b/src/query/metadata.cpp index ade17eb5c..56ef57431 100644 --- a/src/query/metadata.cpp +++ b/src/query/metadata.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -66,6 +66,10 @@ constexpr std::string_view GetCodeString(const NotificationCode code) { return "PlanHinting"sv; case NotificationCode::REGISTER_REPLICA: return "RegisterReplica"sv; +#ifdef MG_ENTERPRISE + case NotificationCode::REGISTER_COORDINATOR_SERVER: + return "RegisterCoordinatorServer"sv; +#endif case NotificationCode::REPLICA_PORT_WARNING: return "ReplicaPortWarning"sv; case NotificationCode::SET_REPLICA: diff --git a/src/query/metadata.hpp b/src/query/metadata.hpp index ca3914047..8e82ad1e3 100644 --- a/src/query/metadata.hpp +++ b/src/query/metadata.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -42,6 +42,9 @@ enum class NotificationCode : uint8_t { PLAN_HINTING, REPLICA_PORT_WARNING, REGISTER_REPLICA, +#ifdef MG_ENTERPRISE + REGISTER_COORDINATOR_SERVER, +#endif SET_REPLICA, START_STREAM, START_ALL_STREAMS, diff --git a/src/query/plan/cost_estimator.hpp b/src/query/plan/cost_estimator.hpp index 47da0a23b..ede4a89fc 100644 --- a/src/query/plan/cost_estimator.hpp +++ b/src/query/plan/cost_estimator.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -14,6 +14,7 @@ #include "query/frontend/ast/ast.hpp" #include "query/parameters.hpp" #include "query/plan/operator.hpp" +#include "query/plan/rewrite/index_lookup.hpp" #include "query/typed_value.hpp" #include "utils/algorithm.hpp" #include "utils/math.hpp" @@ -46,6 +47,11 @@ struct CostEstimation { double cardinality; }; +struct PlanCost { + double cost; + bool use_index_hints; +}; + /** * Query plan execution time cost estimator, for comparing and choosing optimal * execution plans. @@ -109,11 +115,13 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor { using HierarchicalLogicalOperatorVisitor::PostVisit; using HierarchicalLogicalOperatorVisitor::PreVisit; - CostEstimator(TDbAccessor *db_accessor, const SymbolTable &table, const Parameters ¶meters) - : db_accessor_(db_accessor), table_(table), parameters(parameters), scopes_{Scope()} {} + CostEstimator(TDbAccessor *db_accessor, const SymbolTable &table, const Parameters ¶meters, + const IndexHints &index_hints) + : db_accessor_(db_accessor), table_(table), parameters(parameters), scopes_{Scope()}, index_hints_(index_hints) {} - CostEstimator(TDbAccessor *db_accessor, const SymbolTable &table, const Parameters ¶meters, Scope scope) - : db_accessor_(db_accessor), table_(table), parameters(parameters), scopes_{scope} {} + CostEstimator(TDbAccessor *db_accessor, const SymbolTable &table, const Parameters ¶meters, Scope scope, + const IndexHints &index_hints) + : db_accessor_(db_accessor), table_(table), parameters(parameters), scopes_{scope}, index_hints_(index_hints) {} bool PostVisit(ScanAll &) override { cardinality_ *= db_accessor_->VerticesCount(); @@ -129,7 +137,10 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor { } cardinality_ *= db_accessor_->VerticesCount(scan_all_by_label.label_); - // ScanAll performs some work for every element that is produced + if (index_hints_.HasLabelIndex(db_accessor_, scan_all_by_label.label_)) { + use_index_hints_ = true; + } + IncrementCost(CostParam::kScanAllByLabel); return true; } @@ -154,6 +165,10 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor { cardinality_ *= factor; + if (index_hints_.HasLabelPropertyIndex(db_accessor_, logical_op.label_, logical_op.property_)) { + use_index_hints_ = true; + } + // ScanAll performs some work for every element that is produced IncrementCost(CostParam::MakeScanAllByLabelPropertyValue); return true; @@ -184,6 +199,10 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor { cardinality_ *= factor; + if (index_hints_.HasLabelPropertyIndex(db_accessor_, logical_op.label_, logical_op.property_)) { + use_index_hints_ = true; + } + // ScanAll performs some work for every element that is produced IncrementCost(CostParam::MakeScanAllByLabelPropertyRange); return true; @@ -197,6 +216,10 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor { const auto factor = db_accessor_->VerticesCount(logical_op.label_, logical_op.property_); cardinality_ *= factor; + if (index_hints_.HasLabelPropertyIndex(db_accessor_, logical_op.label_, logical_op.property_)) { + use_index_hints_ = true; + } + IncrementCost(CostParam::MakeScanAllByLabelProperty); return true; } @@ -375,6 +398,7 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor { auto cost() const { return cost_; } auto cardinality() const { return cardinality_; } + auto use_index_hints() const { return use_index_hints_; } private: // cost estimation that gets accumulated as the visitor @@ -390,17 +414,19 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor { const SymbolTable &table_; const Parameters ¶meters; std::vector<Scope> scopes_; + IndexHints index_hints_; + bool use_index_hints_{false}; void IncrementCost(double param) { cost_ += param * cardinality_; } CostEstimation EstimateCostOnBranch(std::shared_ptr<LogicalOperator> *branch) { - CostEstimator<TDbAccessor> cost_estimator(db_accessor_, table_, parameters); + CostEstimator<TDbAccessor> cost_estimator(db_accessor_, table_, parameters, index_hints_); (*branch)->Accept(cost_estimator); return CostEstimation{.cost = cost_estimator.cost(), .cardinality = cost_estimator.cardinality()}; } CostEstimation EstimateCostOnBranch(std::shared_ptr<LogicalOperator> *branch, Scope scope) { - CostEstimator<TDbAccessor> cost_estimator(db_accessor_, table_, parameters, scope); + CostEstimator<TDbAccessor> cost_estimator(db_accessor_, table_, parameters, scope, index_hints_); (*branch)->Accept(cost_estimator); return CostEstimation{.cost = cost_estimator.cost(), .cardinality = cost_estimator.cardinality()}; } @@ -450,11 +476,11 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor { /** Returns the estimated cost of the given plan. */ template <class TDbAccessor> -double EstimatePlanCost(TDbAccessor *db, const SymbolTable &table, const Parameters ¶meters, - LogicalOperator &plan) { - CostEstimator<TDbAccessor> estimator(db, table, parameters); +PlanCost EstimatePlanCost(TDbAccessor *db, const SymbolTable &table, const Parameters ¶meters, + LogicalOperator &plan, const IndexHints &index_hints) { + CostEstimator<TDbAccessor> estimator(db, table, parameters, index_hints); plan.Accept(estimator); - return estimator.cost(); + return PlanCost{.cost = estimator.cost(), .use_index_hints = estimator.use_index_hints()}; } } // namespace memgraph::query::plan diff --git a/src/query/plan/operator.hpp b/src/query/plan/operator.hpp index 8fa3d3a7c..516ef2e38 100644 --- a/src/query/plan/operator.hpp +++ b/src/query/plan/operator.hpp @@ -916,11 +916,11 @@ struct ExpansionLambda { /// Currently expanded node symbol. Symbol inner_node_symbol; /// Expression used in lambda during expansion. - Expression *expression; + Expression *expression = nullptr; /// Currently expanded accumulated path symbol. - std::optional<Symbol> accumulated_path_symbol; + std::optional<Symbol> accumulated_path_symbol = std::nullopt; /// Currently expanded accumulated weight symbol. - std::optional<Symbol> accumulated_weight_symbol; + std::optional<Symbol> accumulated_weight_symbol = std::nullopt; ExpansionLambda Clone(AstStorage *storage) const { ExpansionLambda object; diff --git a/src/query/plan/planner.hpp b/src/query/plan/planner.hpp index 10318e6b9..e8ca80e39 100644 --- a/src/query/plan/planner.hpp +++ b/src/query/plan/planner.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -59,9 +59,9 @@ class PostProcessor final { } template <class TVertexCounts> - double EstimatePlanCost(const std::unique_ptr<LogicalOperator> &plan, TVertexCounts *vertex_counts, - const SymbolTable &table) { - return query::plan::EstimatePlanCost(vertex_counts, table, parameters_, *plan); + PlanCost EstimatePlanCost(const std::unique_ptr<LogicalOperator> &plan, TVertexCounts *vertex_counts, + const SymbolTable &table) { + return query::plan::EstimatePlanCost(vertex_counts, table, parameters_, *plan, index_hints_); } }; @@ -99,6 +99,7 @@ auto MakeLogicalPlan(TPlanningContext *context, TPlanPostProcess *post_process, auto query_parts = CollectQueryParts(*context->symbol_table, *context->ast_storage, context->query); auto &vertex_counts = *context->db; double total_cost = std::numeric_limits<double>::max(); + bool curr_uses_index_hint = false; using ProcessedPlan = typename TPlanPostProcess::ProcessedPlan; ProcessedPlan plan_with_least_cost; @@ -110,16 +111,28 @@ auto MakeLogicalPlan(TPlanningContext *context, TPlanPostProcess *post_process, // Plans are generated lazily and the current plan will disappear, so // it's ok to move it. auto rewritten_plan = post_process->Rewrite(std::move(plan), context); - double cost = post_process->EstimatePlanCost(rewritten_plan, &vertex_counts, *context->symbol_table); - if (!curr_plan || cost < total_cost) { + auto plan_cost = post_process->EstimatePlanCost(rewritten_plan, &vertex_counts, *context->symbol_table); + // if we have a plan that uses index hints, we reject all the plans that don't use index hinting because we want + // to force the plan using the index hints to be executed + if (curr_uses_index_hint && !plan_cost.use_index_hints) continue; + // if a plan uses index hints, and there is currently not yet a plan that utilizes it, we will take it regardless + if (plan_cost.use_index_hints && !curr_uses_index_hint) { + curr_uses_index_hint = plan_cost.use_index_hints; curr_plan.emplace(std::move(rewritten_plan)); - total_cost = cost; + total_cost = plan_cost.cost; + continue; + } + // if both plans either use or don't use index hints, we want to use the one with the least cost + if (!curr_plan || plan_cost.cost < total_cost) { + curr_uses_index_hint = plan_cost.use_index_hints; + curr_plan.emplace(std::move(rewritten_plan)); + total_cost = plan_cost.cost; } } } else { auto plan = MakeLogicalPlanForSingleQuery<RuleBasedPlanner>(query_parts, context); auto rewritten_plan = post_process->Rewrite(std::move(plan), context); - total_cost = post_process->EstimatePlanCost(rewritten_plan, &vertex_counts, *context->symbol_table); + total_cost = post_process->EstimatePlanCost(rewritten_plan, &vertex_counts, *context->symbol_table).cost; curr_plan.emplace(std::move(rewritten_plan)); } diff --git a/src/query/plan/preprocess.cpp b/src/query/plan/preprocess.cpp index 22899cbc0..cf8ad9c97 100644 --- a/src/query/plan/preprocess.cpp +++ b/src/query/plan/preprocess.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -257,7 +257,7 @@ void Filters::EraseFilter(const FilterInfo &filter) { all_filters_.end()); } -void Filters::EraseLabelFilter(const Symbol &symbol, LabelIx label, std::vector<Expression *> *removed_filters) { +void Filters::EraseLabelFilter(const Symbol &symbol, const LabelIx &label, std::vector<Expression *> *removed_filters) { for (auto filter_it = all_filters_.begin(); filter_it != all_filters_.end();) { if (filter_it->type != FilterInfo::Type::Label) { ++filter_it; diff --git a/src/query/plan/preprocess.hpp b/src/query/plan/preprocess.hpp index 8e1955907..2b53fb7b0 100644 --- a/src/query/plan/preprocess.hpp +++ b/src/query/plan/preprocess.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -230,6 +230,7 @@ class PatternFilterVisitor : public ExpressionVisitor<void> { void Visit(ParameterLookup &op) override{}; void Visit(NamedExpression &op) override{}; void Visit(RegexMatch &op) override{}; + void Visit(PatternComprehension &op) override{}; std::vector<FilterMatching> getMatchings() { return matchings_; } @@ -365,7 +366,8 @@ class Filters final { /// Remove a label filter for symbol; may invalidate iterators. /// If removed_filters is not nullptr, fills the vector with original /// `Expression *` which are now completely removed. - void EraseLabelFilter(const Symbol &, LabelIx, std::vector<Expression *> *removed_filters = nullptr); + void EraseLabelFilter(const Symbol &symbol, const LabelIx &label, + std::vector<Expression *> *removed_filters = nullptr); /// Returns a vector of FilterInfo for properties. auto PropertyFilters(const Symbol &symbol) const { diff --git a/src/query/plan/rewrite/index_lookup.hpp b/src/query/plan/rewrite/index_lookup.hpp index 590bad5f4..09c6e2014 100644 --- a/src/query/plan/rewrite/index_lookup.hpp +++ b/src/query/plan/rewrite/index_lookup.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -28,6 +28,7 @@ #include "query/plan/operator.hpp" #include "query/plan/preprocess.hpp" +#include "storage/v2/id_types.hpp" DECLARE_int64(query_vertex_count_to_expand_existing); @@ -59,6 +60,29 @@ struct IndexHints { } } + template <class TDbAccessor> + bool HasLabelIndex(TDbAccessor *db, storage::LabelId label) const { + for (const auto &[index_type, label_hint, _] : label_index_hints_) { + auto label_id = db->NameToLabel(label_hint.name); + if (label_id == label) { + return true; + } + } + return false; + } + + template <class TDbAccessor> + bool HasLabelPropertyIndex(TDbAccessor *db, storage::LabelId label, storage::PropertyId property) const { + for (const auto &[index_type, label_hint, property_hint] : label_property_index_hints_) { + auto label_id = db->NameToLabel(label_hint.name); + auto property_id = db->NameToProperty(property_hint->name); + if (label_id == label && property_id == property) { + return true; + } + } + return false; + } + std::vector<IndexHint> label_index_hints_{}; std::vector<IndexHint> label_property_index_hints_{}; }; @@ -631,9 +655,9 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor { } } - storage::LabelId GetLabel(LabelIx label) { return db_->NameToLabel(label.name); } + storage::LabelId GetLabel(const LabelIx &label) { return db_->NameToLabel(label.name); } - storage::PropertyId GetProperty(PropertyIx prop) { return db_->NameToProperty(prop.name); } + storage::PropertyId GetProperty(const PropertyIx &prop) { return db_->NameToProperty(prop.name); } std::optional<LabelIx> FindBestLabelIndex(const std::unordered_set<LabelIx> &labels) { MG_ASSERT(!labels.empty(), "Trying to find the best label without any labels."); diff --git a/src/query/plan/rule_based_planner.cpp b/src/query/plan/rule_based_planner.cpp index f3d0c1487..bf5e66158 100644 --- a/src/query/plan/rule_based_planner.cpp +++ b/src/query/plan/rule_based_planner.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -373,12 +373,12 @@ class ReturnBodyContext : public HierarchicalTreeVisitor { return true; } - bool Visit(ParameterLookup &) override { + bool Visit(ParameterLookup & /*unused*/) override { has_aggregation_.emplace_back(false); return true; } - bool PostVisit(RegexMatch ®ex_match) override { + bool PostVisit(RegexMatch & /*unused*/) override { MG_ASSERT(has_aggregation_.size() >= 2U, "Expected 2 has_aggregation_ flags for RegexMatch arguments"); bool has_aggr = has_aggregation_.back(); has_aggregation_.pop_back(); @@ -386,6 +386,10 @@ class ReturnBodyContext : public HierarchicalTreeVisitor { return true; } + bool PostVisit(PatternComprehension & /*unused*/) override { + throw utils::NotYetImplemented("Planner can not handle pattern comprehension."); + } + // Creates NamedExpression with an Identifier for each user declared symbol. // This should be used when body.all_identifiers is true, to generate // expressions for Produce operator. diff --git a/src/query/plan/rule_based_planner.hpp b/src/query/plan/rule_based_planner.hpp index 074bd1c88..092710628 100644 --- a/src/query/plan/rule_based_planner.hpp +++ b/src/query/plan/rule_based_planner.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -271,9 +271,9 @@ class RuleBasedPlanner { private: TPlanningContext *context_; - storage::LabelId GetLabel(LabelIx label) { return context_->db->NameToLabel(label.name); } + storage::LabelId GetLabel(const LabelIx &label) { return context_->db->NameToLabel(label.name); } - storage::PropertyId GetProperty(PropertyIx prop) { return context_->db->NameToProperty(prop.name); } + storage::PropertyId GetProperty(const PropertyIx &prop) { return context_->db->NameToProperty(prop.name); } storage::EdgeTypeId GetEdgeType(EdgeTypeIx edge_type) { return context_->db->NameToEdgeType(edge_type.name); } @@ -380,6 +380,7 @@ class RuleBasedPlanner { if (pattern.identifier_->user_declared_) { std::vector<Symbol> path_elements; for (const PatternAtom *atom : pattern.atoms_) path_elements.emplace_back(symbol_table.at(*atom->identifier_)); + bound_symbols.insert(symbol_table.at(*pattern.identifier_)); last_op = std::make_unique<ConstructNamedPath>(std::move(last_op), symbol_table.at(*pattern.identifier_), path_elements); } diff --git a/src/query/procedure/module.hpp b/src/query/procedure/module.hpp index 8963173e7..41cda0ca6 100644 --- a/src/query/procedure/module.hpp +++ b/src/query/procedure/module.hpp @@ -169,7 +169,7 @@ class ModuleRegistry final { // mentioned library will be first performed in the already existing binded // libraries and then the global namespace. // RTLD_DEEPBIND => https://linux.die.net/man/3/dlopen - SharedLibraryHandle libstd_handle{"libstdc++.so.6", RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND, kLibstdcppWarning}; + SharedLibraryHandle libstd_handle{"libstdc++.so.6", RTLD_NOW | RTLD_LOCAL, kLibstdcppWarning}; #endif std::vector<std::filesystem::path> modules_dirs_; std::filesystem::path internal_module_dir_; diff --git a/src/query/procedure/py_module.cpp b/src/query/procedure/py_module.cpp index 3fd09b0ab..19393c4d0 100644 --- a/src/query/procedure/py_module.cpp +++ b/src/query/procedure/py_module.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -13,6 +13,7 @@ #include <datetime.h> #include <methodobject.h> +#include <objimpl.h> #include <pyerrors.h> #include <array> #include <optional> @@ -57,7 +58,6 @@ PyObject *gMgpValueConversionError{nullptr}; // NOLINT(cppcoreguidelines-avo PyObject *gMgpSerializationError{nullptr}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) PyObject *gMgpAuthorizationError{nullptr}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -constexpr bool kStartGarbageCollection{true}; constexpr auto kMicrosecondsInMillisecond{1000}; constexpr auto kMicrosecondsInSecond{1000000}; @@ -867,6 +867,27 @@ py::Object MgpListToPyTuple(mgp_list *list, PyObject *py_graph) { return MgpListToPyTuple(list, reinterpret_cast<PyGraph *>(py_graph)); } +void PyCollectGarbage() { + // NOTE: No need to call _Py_IsFinalizing(), we ensure + // Python GC thread is stopped before Py_Finalize() is called + // in memgraph.cpp + if (!Py_IsInitialized()) { + // Calling EnsureGIL will crash the program if this is true. + return; + } + + auto gil = py::EnsureGIL(); + + py::Object gc(PyImport_ImportModule("gc")); + if (!gc) { + LOG_FATAL(py::FetchError().value()); + } + + if (!gc.CallMethod("collect")) { + LOG_FATAL(py::FetchError().value()); + } +} + namespace { struct RecordFieldCache { PyObject *key; @@ -1027,24 +1048,8 @@ std::optional<py::ExceptionInfo> AddMultipleBatchRecordsFromPython(mgp_result *r return std::nullopt; } -std::function<void()> PyObjectCleanup(py::Object &py_object, bool start_gc) { - return [py_object, start_gc]() { - if (start_gc) { - // Run `gc.collect` (reference cycle-detection) explicitly, so that we are - // sure the procedure cleaned up everything it held references to. If the - // user stored a reference to one of our `_mgp` instances then the - // internally used `mgp_*` structs will stay unfreed and a memory leak - // will be reported at the end of the query execution. - py::Object gc(PyImport_ImportModule("gc")); - if (!gc) { - LOG_FATAL(py::FetchError().value()); - } - - if (!gc.CallMethod("collect")) { - LOG_FATAL(py::FetchError().value()); - } - } - +std::function<void()> PyObjectCleanup(py::Object &py_object) { + return [py_object]() { // After making sure all references from our side have been cleared, // invalidate the `_mgp.Graph` object. If the user kept a reference to one // of our `_mgp` instances then this will prevent them from using those @@ -1095,7 +1100,7 @@ void CallPythonProcedure(const py::Object &py_cb, mgp_list *args, mgp_graph *gra std::optional<std::string> maybe_msg; { py::Object py_graph(MakePyGraph(graph, memory)); - utils::OnScopeExit clean_up(PyObjectCleanup(py_graph, !is_batched)); + utils::OnScopeExit clean_up(PyObjectCleanup(py_graph)); if (py_graph) { maybe_msg = error_to_msg(call(py_graph)); } else { @@ -1110,22 +1115,11 @@ void CallPythonProcedure(const py::Object &py_cb, mgp_list *args, mgp_graph *gra void CallPythonCleanup(const py::Object &py_cleanup) { auto gil = py::EnsureGIL(); - auto py_res = py_cleanup.Call(); - - py::Object gc(PyImport_ImportModule("gc")); - if (!gc) { - LOG_FATAL(py::FetchError().value()); - } - - if (!gc.CallMethod("collect")) { - LOG_FATAL(py::FetchError().value()); - } } void CallPythonInitializer(const py::Object &py_initializer, mgp_list *args, mgp_graph *graph, mgp_memory *memory) { auto gil = py::EnsureGIL(); - auto error_to_msg = [](const std::optional<py::ExceptionInfo> &exc_info) -> std::optional<std::string> { if (!exc_info) return std::nullopt; // Here we tell the traceback formatter to skip the first line of the @@ -1146,7 +1140,7 @@ void CallPythonInitializer(const py::Object &py_initializer, mgp_list *args, mgp std::optional<std::string> maybe_msg; { py::Object py_graph(MakePyGraph(graph, memory)); - utils::OnScopeExit clean_up_graph(PyObjectCleanup(py_graph, !kStartGarbageCollection)); + utils::OnScopeExit clean_up_graph(PyObjectCleanup(py_graph)); if (py_graph) { maybe_msg = error_to_msg(call(py_graph)); } else { @@ -1194,8 +1188,8 @@ void CallPythonTransformation(const py::Object &py_cb, mgp_messages *msgs, mgp_g py::Object py_graph(MakePyGraph(graph, memory)); py::Object py_messages(MakePyMessages(msgs, memory)); - utils::OnScopeExit clean_up_graph(PyObjectCleanup(py_graph, kStartGarbageCollection)); - utils::OnScopeExit clean_up_messages(PyObjectCleanup(py_messages, kStartGarbageCollection)); + utils::OnScopeExit clean_up_graph(PyObjectCleanup(py_graph)); + utils::OnScopeExit clean_up_messages(PyObjectCleanup(py_messages)); if (py_graph && py_messages) { maybe_msg = error_to_msg(call(py_graph, py_messages)); @@ -1264,7 +1258,7 @@ void CallPythonFunction(const py::Object &py_cb, mgp_list *args, mgp_graph *grap std::optional<std::string> maybe_msg; { py::Object py_graph(MakePyGraph(graph, memory)); - utils::OnScopeExit clean_up(PyObjectCleanup(py_graph, kStartGarbageCollection)); + utils::OnScopeExit clean_up(PyObjectCleanup(py_graph)); if (py_graph) { auto maybe_result = call(py_graph); if (!maybe_result.HasError()) { diff --git a/src/query/procedure/py_module.hpp b/src/query/procedure/py_module.hpp index 85ad82b3e..9cb22fe2c 100644 --- a/src/query/procedure/py_module.hpp +++ b/src/query/procedure/py_module.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -79,4 +79,7 @@ py::Object ImportPyModule(const char *, mgp_module *); /// Return nullptr and set appropriate Python exception on failure. py::Object ReloadPyModule(PyObject *, mgp_module *); +/// Call full python circular reference garbage collection (all generations) +void PyCollectGarbage(); + } // namespace memgraph::query::procedure diff --git a/src/query/stream/streams.cpp b/src/query/stream/streams.cpp index 896607e94..101ca592c 100644 --- a/src/query/stream/streams.cpp +++ b/src/query/stream/streams.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -644,6 +644,25 @@ void Streams::Drop(const std::string &stream_name) { // TODO(antaljanosbenjamin) Release the transformation } +void Streams::DropAll() { + streams_.WithLock([this](StreamsMap &streams) { + bool durability_ok = true; + for (auto &[name, stream] : streams) { + // streams_ is write locked, which means there is no access to it outside of this function, thus only the Test + // function can be executing with the consumer, nothing else. + // By acquiring the write lock here for the consumer, we make sure there is + // no running Test function for this consumer, therefore it can be erased. + std::visit([&](const auto &stream_data) { stream_data.stream_source->Lock(); }, stream); + if (!storage_.Delete(name)) { + durability_ok = false; + } + } + + streams.clear(); + return durability_ok; // TODO: do we need special case for this cleanup if false + }); +} + void Streams::Start(const std::string &stream_name) { auto locked_streams = streams_.Lock(); auto it = GetStream(*locked_streams, stream_name); diff --git a/src/query/stream/streams.hpp b/src/query/stream/streams.hpp index 2c89341d1..bad1f8c98 100644 --- a/src/query/stream/streams.hpp +++ b/src/query/stream/streams.hpp @@ -110,6 +110,11 @@ class Streams final { /// @throws StreamsException if the stream doesn't exist or if the persisted metadata can't be deleted. void Drop(const std::string &stream_name); + /// Deletes all existing streams and all the data that was persisted. + /// + /// @throws StreamsException if the persisted metadata can't be deleted. + void DropAll(); + /// Start consuming from a stream. /// /// @param stream_name name of the stream that needs to be started diff --git a/src/query/typed_value.cpp b/src/query/typed_value.cpp index ea883e428..4cb79508e 100644 --- a/src/query/typed_value.cpp +++ b/src/query/typed_value.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -125,9 +125,7 @@ TypedValue::TypedValue(storage::PropertyValue &&other, utils::MemoryResource *me case storage::PropertyValue::Type::List: { type_ = Type::List; auto &vec = other.ValueList(); - new (&list_v) TVector(memory_); - list_v.reserve(vec.size()); - for (auto &v : vec) list_v.emplace_back(std::move(v)); + new (&list_v) TVector(std::make_move_iterator(vec.begin()), std::make_move_iterator(vec.end()), memory_); break; } case storage::PropertyValue::Type::Map: { @@ -324,13 +322,13 @@ TypedValue::operator storage::PropertyValue() const { #define DEFINE_VALUE_AND_TYPE_GETTERS(type_param, type_enum, field) \ type_param &TypedValue::Value##type_enum() { \ - if (type_ != Type::type_enum) \ + if (type_ != Type::type_enum) [[unlikely]] \ throw TypedValueException("TypedValue is of type '{}', not '{}'", type_, Type::type_enum); \ return field; \ } \ \ const type_param &TypedValue::Value##type_enum() const { \ - if (type_ != Type::type_enum) \ + if (type_ != Type::type_enum) [[unlikely]] \ throw TypedValueException("TypedValue is of type '{}', not '{}'", type_, Type::type_enum); \ return field; \ } \ diff --git a/src/replication/CMakeLists.txt b/src/replication/CMakeLists.txt index 597ed096a..e19ba7061 100644 --- a/src/replication/CMakeLists.txt +++ b/src/replication/CMakeLists.txt @@ -5,10 +5,8 @@ target_sources(mg-replication include/replication/state.hpp include/replication/epoch.hpp include/replication/config.hpp - include/replication/mode.hpp - include/replication/messages.hpp - include/replication/role.hpp include/replication/status.hpp + include/replication/messages.hpp include/replication/replication_client.hpp include/replication/replication_server.hpp @@ -25,6 +23,6 @@ target_include_directories(mg-replication PUBLIC include) find_package(fmt REQUIRED) target_link_libraries(mg-replication - PUBLIC mg::utils mg::kvstore lib::json mg::rpc mg::slk + PUBLIC mg::utils mg::kvstore lib::json mg::rpc mg::slk mg::io mg::repl_coord_glue PRIVATE fmt::fmt ) diff --git a/src/replication/include/replication/config.hpp b/src/replication/include/replication/config.hpp index f98069955..822e09f72 100644 --- a/src/replication/include/replication/config.hpp +++ b/src/replication/include/replication/config.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -15,7 +15,7 @@ #include <cstdint> #include <optional> #include <string> -#include "replication/mode.hpp" +#include "replication_coordination_glue/mode.hpp" namespace memgraph::replication { @@ -24,7 +24,7 @@ inline constexpr auto *kDefaultReplicationServerIp = "0.0.0.0"; struct ReplicationClientConfig { std::string name; - ReplicationMode mode{}; + replication_coordination_glue::ReplicationMode mode{}; std::string ip_address; uint16_t port{}; @@ -40,7 +40,7 @@ struct ReplicationClientConfig { friend bool operator==(const SSL &, const SSL &) = default; }; - std::optional<SSL> ssl; + std::optional<SSL> ssl{}; friend bool operator==(ReplicationClientConfig const &, ReplicationClientConfig const &) = default; }; diff --git a/src/replication/include/replication/messages.hpp b/src/replication/include/replication/messages.hpp index 57cf29351..b4e0b51c7 100644 --- a/src/replication/include/replication/messages.hpp +++ b/src/replication/include/replication/messages.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -15,30 +15,33 @@ #include "slk/serialization.hpp" namespace memgraph::replication { - -struct FrequentHeartbeatReq { - static const utils::TypeInfo kType; // TODO: make constexpr? - static const utils::TypeInfo &GetTypeInfo() { return kType; } // WHAT? - - static void Load(FrequentHeartbeatReq *self, memgraph::slk::Reader *reader); - static void Save(const FrequentHeartbeatReq &self, memgraph::slk::Builder *builder); - FrequentHeartbeatReq() = default; -}; - -struct FrequentHeartbeatRes { +struct SystemHeartbeatReq { static const utils::TypeInfo kType; static const utils::TypeInfo &GetTypeInfo() { return kType; } - static void Load(FrequentHeartbeatRes *self, memgraph::slk::Reader *reader); - static void Save(const FrequentHeartbeatRes &self, memgraph::slk::Builder *builder); - FrequentHeartbeatRes() = default; - explicit FrequentHeartbeatRes(bool success) : success(success) {} - - bool success; + static void Load(SystemHeartbeatReq *self, memgraph::slk::Reader *reader); + static void Save(const SystemHeartbeatReq &self, memgraph::slk::Builder *builder); + SystemHeartbeatReq() = default; }; -using FrequentHeartbeatRpc = rpc::RequestResponse<FrequentHeartbeatReq, FrequentHeartbeatRes>; +struct SystemHeartbeatRes { + static const utils::TypeInfo kType; + static const utils::TypeInfo &GetTypeInfo() { return kType; } -void FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder); + static void Load(SystemHeartbeatRes *self, memgraph::slk::Reader *reader); + static void Save(const SystemHeartbeatRes &self, memgraph::slk::Builder *builder); + SystemHeartbeatRes() = default; + explicit SystemHeartbeatRes(uint64_t system_timestamp) : system_timestamp(system_timestamp) {} + uint64_t system_timestamp; +}; + +using SystemHeartbeatRpc = rpc::RequestResponse<SystemHeartbeatReq, SystemHeartbeatRes>; } // namespace memgraph::replication + +namespace memgraph::slk { +void Save(const memgraph::replication::SystemHeartbeatRes &self, memgraph::slk::Builder *builder); +void Load(memgraph::replication::SystemHeartbeatRes *self, memgraph::slk::Reader *reader); +void Save(const memgraph::replication::SystemHeartbeatReq & /*self*/, memgraph::slk::Builder * /*builder*/); +void Load(memgraph::replication::SystemHeartbeatReq * /*self*/, memgraph::slk::Reader * /*reader*/); +} // namespace memgraph::slk diff --git a/src/replication/include/replication/replication_client.hpp b/src/replication/include/replication/replication_client.hpp index 16e1010bf..0c64ae625 100644 --- a/src/replication/include/replication/replication_client.hpp +++ b/src/replication/include/replication/replication_client.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -12,9 +12,10 @@ #pragma once #include "replication/config.hpp" -#include "replication/messages.hpp" +#include "replication_coordination_glue/messages.hpp" #include "rpc/client.hpp" #include "utils/scheduler.hpp" +#include "utils/synchronized.hpp" #include "utils/thread_pool.hpp" #include <concepts> @@ -22,8 +23,10 @@ namespace memgraph::replication { +struct ReplicationClient; + template <typename F> -concept InvocableWithStringView = std::invocable<F, std::string_view>; +concept FrequentCheckCB = std::invocable<F, bool, ReplicationClient &>; struct ReplicationClient { explicit ReplicationClient(const memgraph::replication::ReplicationClientConfig &config); @@ -34,24 +37,27 @@ struct ReplicationClient { ReplicationClient(ReplicationClient &&) noexcept = delete; ReplicationClient &operator=(ReplicationClient &&) noexcept = delete; - template <InvocableWithStringView F> + template <FrequentCheckCB F> void StartFrequentCheck(F &&callback) { // Help the user to get the most accurate replica state possible. if (replica_check_frequency_ > std::chrono::seconds(0)) { - replica_checker_.Run("Replica Checker", replica_check_frequency_, [this, cb = std::forward<F>(callback)] { - try { - bool success = false; - { - auto stream{rpc_client_.Stream<memgraph::replication::FrequentHeartbeatRpc>()}; - success = stream.AwaitResponse().success; - } - if (success) { - cb(name_); - } - } catch (const rpc::RpcFailedException &) { - // Nothing to do...wait for a reconnect - } - }); + replica_checker_.Run("Replica Checker", replica_check_frequency_, + [this, cb = std::forward<F>(callback), reconnect = false]() mutable { + try { + { + auto stream{rpc_client_.Stream<memgraph::replication_coordination_glue::FrequentHeartbeatRpc>()}; + stream.AwaitResponse(); + } + cb(reconnect, *this); + reconnect = false; + } catch (const rpc::RpcFailedException &) { + // Nothing to do...wait for a reconnect + // NOTE: Here we are communicating with the instance connection. + // We don't have access to the undelying client; so the only thing we can do it + // tell the callback that this is a reconnection and to check the state + reconnect = true; + } + }); } } @@ -60,7 +66,14 @@ struct ReplicationClient { rpc::Client rpc_client_; std::chrono::seconds replica_check_frequency_; - memgraph::replication::ReplicationMode mode_{memgraph::replication::ReplicationMode::SYNC}; + // TODO: Better, this was the easiest place to put this + enum class State { + BEHIND, + READY, + }; + utils::Synchronized<State> state_{State::BEHIND}; + + replication_coordination_glue::ReplicationMode mode_{replication_coordination_glue::ReplicationMode::SYNC}; // This thread pool is used for background tasks so we don't // block the main storage thread // We use only 1 thread for 2 reasons: diff --git a/src/replication/include/replication/state.hpp b/src/replication/include/replication/state.hpp index 76aec1053..a53885aff 100644 --- a/src/replication/include/replication/state.hpp +++ b/src/replication/include/replication/state.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -14,9 +14,9 @@ #include "kvstore/kvstore.hpp" #include "replication/config.hpp" #include "replication/epoch.hpp" -#include "replication/mode.hpp" #include "replication/replication_client.hpp" -#include "replication/role.hpp" +#include "replication_coordination_glue/mode.hpp" +#include "replication_coordination_glue/role.hpp" #include "replication_server.hpp" #include "status.hpp" #include "utils/result.hpp" @@ -32,7 +32,8 @@ namespace memgraph::replication { enum class RolePersisted : uint8_t { UNKNOWN_OR_NO, YES }; -enum class RegisterReplicaError : uint8_t { NAME_EXISTS, END_POINT_EXISTS, COULD_NOT_BE_PERSISTED, NOT_MAIN, SUCCESS }; +// TODO: (andi) Rename Error to Status +enum class RegisterReplicaError : uint8_t { NAME_EXISTS, ENDPOINT_EXISTS, COULD_NOT_BE_PERSISTED, NOT_MAIN, SUCCESS }; struct RoleMainData { RoleMainData() = default; @@ -45,7 +46,7 @@ struct RoleMainData { RoleMainData &operator=(RoleMainData &&) = default; ReplicationEpoch epoch_; - std::list<ReplicationClient> registered_replicas_{}; + std::list<ReplicationClient> registered_replicas_{}; // TODO: data race issues }; struct RoleReplicaData { @@ -72,14 +73,16 @@ struct ReplicationState { using FetchReplicationResult_t = utils::BasicResult<FetchReplicationError, ReplicationData_t>; auto FetchReplicationData() -> FetchReplicationResult_t; - auto GetRole() const -> ReplicationRole { - return std::holds_alternative<RoleReplicaData>(replication_data_) ? ReplicationRole::REPLICA - : ReplicationRole::MAIN; + auto GetRole() const -> replication_coordination_glue::ReplicationRole { + return std::holds_alternative<RoleReplicaData>(replication_data_) + ? replication_coordination_glue::ReplicationRole::REPLICA + : replication_coordination_glue::ReplicationRole::MAIN; } - bool IsMain() const { return GetRole() == ReplicationRole::MAIN; } - bool IsReplica() const { return GetRole() == ReplicationRole::REPLICA; } + bool IsMain() const { return GetRole() == replication_coordination_glue::ReplicationRole::MAIN; } + bool IsReplica() const { return GetRole() == replication_coordination_glue::ReplicationRole::REPLICA; } + + bool HasDurability() const { return nullptr != durability_; } - bool ShouldPersist() const { return nullptr != durability_; } bool TryPersistRoleMain(std::string new_epoch); bool TryPersistRoleReplica(const ReplicationServerConfig &config); bool TryPersistUnregisterReplica(std::string_view name); @@ -91,7 +94,6 @@ struct ReplicationState { utils::BasicResult<RegisterReplicaError, ReplicationClient *> RegisterReplica(const ReplicationClientConfig &config); bool SetReplicationRoleMain(); - bool SetReplicationRoleReplica(const ReplicationServerConfig &config); private: diff --git a/src/replication/include/replication/status.hpp b/src/replication/include/replication/status.hpp index 943db423a..4dfba6aaa 100644 --- a/src/replication/include/replication/status.hpp +++ b/src/replication/include/replication/status.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -21,7 +21,7 @@ #include "replication/config.hpp" #include "replication/epoch.hpp" -#include "replication/role.hpp" +#include "replication_coordination_glue/role.hpp" namespace memgraph::replication::durability { @@ -42,7 +42,7 @@ struct MainRole { // fragment of key: "__replication_role" struct ReplicaRole { - ReplicationServerConfig config; + ReplicationServerConfig config{}; friend bool operator==(ReplicaRole const &, ReplicaRole const &) = default; }; diff --git a/src/replication/messages.cpp b/src/replication/messages.cpp index 4503e9df2..b2dca374e 100644 --- a/src/replication/messages.cpp +++ b/src/replication/messages.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -8,58 +8,45 @@ // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. - #include "replication/messages.hpp" -#include "rpc/messages.hpp" -#include "slk/serialization.hpp" -#include "slk/streams.hpp" - -namespace memgraph::slk { -// Serialize code for FrequentHeartbeatRes -void Save(const memgraph::replication::FrequentHeartbeatRes &self, memgraph::slk::Builder *builder) { - memgraph::slk::Save(self.success, builder); -} -void Load(memgraph::replication::FrequentHeartbeatRes *self, memgraph::slk::Reader *reader) { - memgraph::slk::Load(&self->success, reader); -} - -// Serialize code for FrequentHeartbeatReq -void Save(const memgraph::replication::FrequentHeartbeatReq & /*self*/, memgraph::slk::Builder * /*builder*/) { - /* Nothing to serialize */ -} -void Load(memgraph::replication::FrequentHeartbeatReq * /*self*/, memgraph::slk::Reader * /*reader*/) { - /* Nothing to serialize */ -} - -} // namespace memgraph::slk namespace memgraph::replication { -constexpr utils::TypeInfo FrequentHeartbeatReq::kType{utils::TypeId::REP_FREQUENT_HEARTBEAT_REQ, "FrequentHeartbeatReq", - nullptr}; +constexpr utils::TypeInfo SystemHeartbeatReq::kType{utils::TypeId::REP_SYSTEM_HEARTBEAT_REQ, "SystemHeartbeatReq", + nullptr}; -constexpr utils::TypeInfo FrequentHeartbeatRes::kType{utils::TypeId::REP_FREQUENT_HEARTBEAT_RES, "FrequentHeartbeatRes", - nullptr}; +constexpr utils::TypeInfo SystemHeartbeatRes::kType{utils::TypeId::REP_SYSTEM_HEARTBEAT_RES, "SystemHeartbeatRes", + nullptr}; -void FrequentHeartbeatReq::Save(const FrequentHeartbeatReq &self, memgraph::slk::Builder *builder) { +void SystemHeartbeatReq::Save(const SystemHeartbeatReq &self, memgraph::slk::Builder *builder) { memgraph::slk::Save(self, builder); } -void FrequentHeartbeatReq::Load(FrequentHeartbeatReq *self, memgraph::slk::Reader *reader) { +void SystemHeartbeatReq::Load(SystemHeartbeatReq *self, memgraph::slk::Reader *reader) { memgraph::slk::Load(self, reader); } -void FrequentHeartbeatRes::Save(const FrequentHeartbeatRes &self, memgraph::slk::Builder *builder) { +void SystemHeartbeatRes::Save(const SystemHeartbeatRes &self, memgraph::slk::Builder *builder) { memgraph::slk::Save(self, builder); } -void FrequentHeartbeatRes::Load(FrequentHeartbeatRes *self, memgraph::slk::Reader *reader) { +void SystemHeartbeatRes::Load(SystemHeartbeatRes *self, memgraph::slk::Reader *reader) { memgraph::slk::Load(self, reader); } -void FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) { - FrequentHeartbeatReq req; - FrequentHeartbeatReq::Load(&req, req_reader); - memgraph::slk::Load(&req, req_reader); - FrequentHeartbeatRes res{true}; - memgraph::slk::Save(res, res_builder); -} - } // namespace memgraph::replication + +namespace memgraph::slk { +// Serialize code for SystemHeartbeatRes +void Save(const memgraph::replication::SystemHeartbeatRes &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self.system_timestamp, builder); +} +void Load(memgraph::replication::SystemHeartbeatRes *self, memgraph::slk::Reader *reader) { + memgraph::slk::Load(&self->system_timestamp, reader); +} + +// Serialize code for SystemHeartbeatReq +void Save(const memgraph::replication::SystemHeartbeatReq & /*self*/, memgraph::slk::Builder * /*builder*/) { + /* Nothing to serialize */ +} +void Load(memgraph::replication::SystemHeartbeatReq * /*self*/, memgraph::slk::Reader * /*reader*/) { + /* Nothing to serialize */ +} +} // namespace memgraph::slk diff --git a/src/replication/replication_client.cpp b/src/replication/replication_client.cpp index d14250c2a..ed46ea471 100644 --- a/src/replication/replication_client.cpp +++ b/src/replication/replication_client.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -28,8 +28,8 @@ ReplicationClient::ReplicationClient(const memgraph::replication::ReplicationCli mode_{config.mode} {} ReplicationClient::~ReplicationClient() { - auto endpoint = rpc_client_.Endpoint(); try { + auto const &endpoint = rpc_client_.Endpoint(); spdlog::trace("Closing replication client on {}:{}", endpoint.address, endpoint.port); } catch (...) { // Logging can throw. Not a big deal, just ignore. diff --git a/src/replication/replication_server.cpp b/src/replication/replication_server.cpp index f79ea2add..03c48d298 100644 --- a/src/replication/replication_server.cpp +++ b/src/replication/replication_server.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -10,7 +10,7 @@ // licenses/APL.txt. #include "replication/replication_server.hpp" -#include "replication/messages.hpp" +#include "replication_coordination_glue/messages.hpp" namespace memgraph::replication { namespace { @@ -32,9 +32,9 @@ ReplicationServer::ReplicationServer(const memgraph::replication::ReplicationSer : rpc_server_context_{CreateServerContext(config)}, rpc_server_{io::network::Endpoint{config.ip_address, config.port}, &rpc_server_context_, kReplicationServerThreads} { - rpc_server_.Register<FrequentHeartbeatRpc>([](auto *req_reader, auto *res_builder) { + rpc_server_.Register<replication_coordination_glue::FrequentHeartbeatRpc>([](auto *req_reader, auto *res_builder) { spdlog::debug("Received FrequentHeartbeatRpc"); - FrequentHeartbeatHandler(req_reader, res_builder); + replication_coordination_glue::FrequentHeartbeatHandler(req_reader, res_builder); }); } diff --git a/src/replication/state.cpp b/src/replication/state.cpp index 60c390e17..d04a3d245 100644 --- a/src/replication/state.cpp +++ b/src/replication/state.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -34,6 +34,7 @@ ReplicationState::ReplicationState(std::optional<std::filesystem::path> durabili repl_dir /= kReplicationDirectory; utils::EnsureDirOrDie(repl_dir); durability_ = std::make_unique<kvstore::KVStore>(std::move(repl_dir)); + spdlog::info("Replication configuration will be stored and will be automatically restored in case of a crash."); auto replicationData = FetchReplicationData(); if (replicationData.HasError()) { @@ -54,7 +55,7 @@ ReplicationState::ReplicationState(std::optional<std::filesystem::path> durabili } bool ReplicationState::TryPersistRoleReplica(const ReplicationServerConfig &config) { - if (!ShouldPersist()) return true; + if (!HasDurability()) return true; auto data = durability::ReplicationRoleEntry{.role = durability::ReplicaRole{ .config = config, @@ -78,7 +79,7 @@ bool ReplicationState::TryPersistRoleReplica(const ReplicationServerConfig &conf } bool ReplicationState::TryPersistRoleMain(std::string new_epoch) { - if (!ShouldPersist()) return true; + if (!HasDurability()) return true; auto data = durability::ReplicationRoleEntry{.role = durability::MainRole{.epoch = ReplicationEpoch{std::move(new_epoch)}}}; @@ -92,7 +93,7 @@ bool ReplicationState::TryPersistRoleMain(std::string new_epoch) { } bool ReplicationState::TryPersistUnregisterReplica(std::string_view name) { - if (!ShouldPersist()) return true; + if (!HasDurability()) return true; auto key = BuildReplicaKey(name); @@ -104,7 +105,7 @@ bool ReplicationState::TryPersistUnregisterReplica(std::string_view name) { // TODO: FetchEpochData (agnostic of FetchReplicationData, but should be done before) auto ReplicationState::FetchReplicationData() -> FetchReplicationResult_t { - if (!ShouldPersist()) return FetchReplicationError::NOTHING_FETCHED; + if (!HasDurability()) return FetchReplicationError::NOTHING_FETCHED; const auto replication_data = durability_->Get(durability::kReplicationRoleName); if (!replication_data.has_value()) { return FetchReplicationError::NOTHING_FETCHED; @@ -199,7 +200,7 @@ bool ReplicationState::HandleVersionMigration(durability::ReplicationRoleEntry & } bool ReplicationState::TryPersistRegisteredReplica(const ReplicationClientConfig &config) { - if (!ShouldPersist()) return true; + if (!HasDurability()) return true; // If any replicas are persisted then Role must be persisted if (role_persisted != RolePersisted::YES) { @@ -218,10 +219,12 @@ bool ReplicationState::TryPersistRegisteredReplica(const ReplicationClientConfig bool ReplicationState::SetReplicationRoleMain() { auto new_epoch = utils::GenerateUUID(); + if (!TryPersistRoleMain(new_epoch)) { return false; } replication_data_ = RoleMainData{ReplicationEpoch{new_epoch}}; + return true; } @@ -236,6 +239,7 @@ bool ReplicationState::SetReplicationRoleReplica(const ReplicationServerConfig & utils::BasicResult<RegisterReplicaError, ReplicationClient *> ReplicationState::RegisterReplica( const ReplicationClientConfig &config) { auto const replica_handler = [](RoleReplicaData const &) { return RegisterReplicaError::NOT_MAIN; }; + ReplicationClient *client{nullptr}; auto const main_handler = [&client, &config, this](RoleMainData &mainData) -> RegisterReplicaError { // name check @@ -256,7 +260,7 @@ utils::BasicResult<RegisterReplicaError, ReplicationClient *> ReplicationState:: return std::any_of(replicas.begin(), replicas.end(), endpoint_matches); }; if (endpoint_check(mainData.registered_replicas_)) { - return RegisterReplicaError::END_POINT_EXISTS; + return RegisterReplicaError::ENDPOINT_EXISTS; } // Durability @@ -275,4 +279,5 @@ utils::BasicResult<RegisterReplicaError, ReplicationClient *> ReplicationState:: } return res; } + } // namespace memgraph::replication diff --git a/src/replication/status.cpp b/src/replication/status.cpp index 06d67cc66..de1af9589 100644 --- a/src/replication/status.cpp +++ b/src/replication/status.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -29,12 +29,14 @@ constexpr auto *kVersion = "durability_version"; void to_json(nlohmann::json &j, const ReplicationRoleEntry &p) { auto processMAIN = [&](MainRole const &main) { - j = nlohmann::json{{kVersion, p.version}, {kReplicationRole, ReplicationRole::MAIN}, {kEpoch, main.epoch.id()}}; + j = nlohmann::json{{kVersion, p.version}, + {kReplicationRole, replication_coordination_glue::ReplicationRole::MAIN}, + {kEpoch, main.epoch.id()}}; }; auto processREPLICA = [&](ReplicaRole const &replica) { j = nlohmann::json{ {kVersion, p.version}, - {kReplicationRole, ReplicationRole::REPLICA}, + {kReplicationRole, replication_coordination_glue::ReplicationRole::REPLICA}, {kIpAddress, replica.config.ip_address}, {kPort, replica.config.port} // TODO: SSL @@ -47,17 +49,17 @@ void from_json(const nlohmann::json &j, ReplicationRoleEntry &p) { // This value did not exist in V1, hence default DurabilityVersion::V1 DurabilityVersion version = j.value(kVersion, DurabilityVersion::V1); // NOLINTNEXTLINE(cppcoreguidelines-init-variables) - ReplicationRole role; + replication_coordination_glue::ReplicationRole role; j.at(kReplicationRole).get_to(role); switch (role) { - case ReplicationRole::MAIN: { + case replication_coordination_glue::ReplicationRole::MAIN: { auto json_epoch = j.value(kEpoch, std::string{}); auto epoch = ReplicationEpoch{}; if (!json_epoch.empty()) epoch.SetEpoch(json_epoch); p = ReplicationRoleEntry{.version = version, .role = MainRole{.epoch = std::move(epoch)}}; break; } - case ReplicationRole::REPLICA: { + case memgraph::replication_coordination_glue::ReplicationRole::REPLICA: { std::string ip_address; // NOLINTNEXTLINE(cppcoreguidelines-init-variables) uint16_t port; @@ -95,7 +97,7 @@ void from_json(const nlohmann::json &j, ReplicationReplicaEntry &p) { auto seconds = j.at(kCheckFrequency).get<std::chrono::seconds::rep>(); auto config = ReplicationClientConfig{ .name = j.at(kReplicaName).get<std::string>(), - .mode = j.at(kSyncMode).get<ReplicationMode>(), + .mode = j.at(kSyncMode).get<replication_coordination_glue::ReplicationMode>(), .ip_address = j.at(kIpAddress).get<std::string>(), .port = j.at(kPort).get<uint16_t>(), .replica_check_frequency = std::chrono::seconds{seconds}, diff --git a/src/replication_coordination_glue/CMakeLists.txt b/src/replication_coordination_glue/CMakeLists.txt new file mode 100644 index 000000000..010a7b596 --- /dev/null +++ b/src/replication_coordination_glue/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(mg-repl_coord_glue STATIC ) +add_library(mg::repl_coord_glue ALIAS mg-repl_coord_glue) + +target_sources(mg-repl_coord_glue + PUBLIC + messages.hpp + mode.hpp + role.hpp + + PRIVATE + messages.cpp +) + +target_link_libraries(mg-repl_coord_glue mg-rpc mg-slk) diff --git a/src/replication_coordination_glue/messages.cpp b/src/replication_coordination_glue/messages.cpp new file mode 100644 index 000000000..c7cf0b15c --- /dev/null +++ b/src/replication_coordination_glue/messages.cpp @@ -0,0 +1,63 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include "replication_coordination_glue/messages.hpp" +#include "rpc/messages.hpp" +#include "slk/serialization.hpp" +#include "slk/streams.hpp" + +namespace memgraph::slk { +// Serialize code for FrequentHeartbeatRes +void Save(const memgraph::replication_coordination_glue::FrequentHeartbeatRes &self, memgraph::slk::Builder *builder) {} +void Load(memgraph::replication_coordination_glue::FrequentHeartbeatRes *self, memgraph::slk::Reader *reader) {} + +// Serialize code for FrequentHeartbeatReq +void Save(const memgraph::replication_coordination_glue::FrequentHeartbeatReq & /*self*/, + memgraph::slk::Builder * /*builder*/) { + /* Nothing to serialize */ +} +void Load(memgraph::replication_coordination_glue::FrequentHeartbeatReq * /*self*/, + memgraph::slk::Reader * /*reader*/) { + /* Nothing to serialize */ +} + +} // namespace memgraph::slk + +namespace memgraph::replication_coordination_glue { + +constexpr utils::TypeInfo FrequentHeartbeatReq::kType{utils::TypeId::REP_FREQUENT_HEARTBEAT_REQ, "FrequentHeartbeatReq", + nullptr}; + +constexpr utils::TypeInfo FrequentHeartbeatRes::kType{utils::TypeId::REP_FREQUENT_HEARTBEAT_RES, "FrequentHeartbeatRes", + nullptr}; + +void FrequentHeartbeatReq::Save(const FrequentHeartbeatReq &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self, builder); +} +void FrequentHeartbeatReq::Load(FrequentHeartbeatReq *self, memgraph::slk::Reader *reader) { + memgraph::slk::Load(self, reader); +} +void FrequentHeartbeatRes::Save(const FrequentHeartbeatRes &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self, builder); +} +void FrequentHeartbeatRes::Load(FrequentHeartbeatRes *self, memgraph::slk::Reader *reader) { + memgraph::slk::Load(self, reader); +} + +void FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) { + FrequentHeartbeatReq req; + FrequentHeartbeatReq::Load(&req, req_reader); + memgraph::slk::Load(&req, req_reader); + FrequentHeartbeatRes res{}; + memgraph::slk::Save(res, res_builder); +} + +} // namespace memgraph::replication_coordination_glue diff --git a/src/replication_coordination_glue/messages.hpp b/src/replication_coordination_glue/messages.hpp new file mode 100644 index 000000000..5e2ef0fdf --- /dev/null +++ b/src/replication_coordination_glue/messages.hpp @@ -0,0 +1,49 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include "rpc/messages.hpp" +#include "slk/serialization.hpp" + +namespace memgraph::replication_coordination_glue { + +struct FrequentHeartbeatReq { + static const utils::TypeInfo kType; // TODO: make constexpr? + static const utils::TypeInfo &GetTypeInfo() { return kType; } + + static void Load(FrequentHeartbeatReq *self, memgraph::slk::Reader *reader); + static void Save(const FrequentHeartbeatReq &self, memgraph::slk::Builder *builder); + FrequentHeartbeatReq() = default; +}; + +struct FrequentHeartbeatRes { + static const utils::TypeInfo kType; + static const utils::TypeInfo &GetTypeInfo() { return kType; } + + static void Load(FrequentHeartbeatRes *self, memgraph::slk::Reader *reader); + static void Save(const FrequentHeartbeatRes &self, memgraph::slk::Builder *builder); + FrequentHeartbeatRes() = default; +}; + +using FrequentHeartbeatRpc = rpc::RequestResponse<FrequentHeartbeatReq, FrequentHeartbeatRes>; + +void FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder); + +} // namespace memgraph::replication_coordination_glue + +namespace memgraph::slk { +void Save(const memgraph::replication_coordination_glue::FrequentHeartbeatRes &self, memgraph::slk::Builder *builder); +void Load(memgraph::replication_coordination_glue::FrequentHeartbeatRes *self, memgraph::slk::Reader *reader); +void Save(const memgraph::replication_coordination_glue::FrequentHeartbeatReq & /*self*/, + memgraph::slk::Builder * /*builder*/); +void Load(memgraph::replication_coordination_glue::FrequentHeartbeatReq * /*self*/, memgraph::slk::Reader * /*reader*/); +} // namespace memgraph::slk diff --git a/src/replication/include/replication/mode.hpp b/src/replication_coordination_glue/mode.hpp similarity index 80% rename from src/replication/include/replication/mode.hpp rename to src/replication_coordination_glue/mode.hpp index c1afe2b1f..d0b415733 100644 --- a/src/replication/include/replication/mode.hpp +++ b/src/replication_coordination_glue/mode.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -10,7 +10,9 @@ // licenses/APL.txt. #pragma once + #include <cstdint> -namespace memgraph::replication { + +namespace memgraph::replication_coordination_glue { enum class ReplicationMode : std::uint8_t { SYNC, ASYNC }; -} +} // namespace memgraph::replication_coordination_glue diff --git a/src/replication_coordination_glue/role.hpp b/src/replication_coordination_glue/role.hpp new file mode 100644 index 000000000..d472cb454 --- /dev/null +++ b/src/replication_coordination_glue/role.hpp @@ -0,0 +1,19 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include <cstdint> +namespace memgraph::replication_coordination_glue { + +// TODO: figure out a way of ensuring that usage of this type is never uninitialed/defaulted incorrectly to MAIN +enum class ReplicationRole : uint8_t { MAIN, REPLICA }; +} // namespace memgraph::replication_coordination_glue diff --git a/src/rpc/client.hpp b/src/rpc/client.hpp index 1fd3fff8d..a9ae7202d 100644 --- a/src/rpc/client.hpp +++ b/src/rpc/client.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -105,11 +105,15 @@ class Client { utils::OnScopeExit res_cleanup([&, response_data_size] { self_->client_->ShiftData(response_data_size); }); utils::TypeId res_id{utils::TypeId::UNKNOWN}; - slk::Load(&res_id, &res_reader); - // NOLINTNEXTLINE(cppcoreguidelines-init-variables) rpc::Version version; - slk::Load(&version, &res_reader); + + try { + slk::Load(&res_id, &res_reader); + slk::Load(&version, &res_reader); + } catch (const slk::SlkReaderException &) { + throw SlkRpcFailedException(); + } if (version != rpc::current_version) { // V1 we introduced versioning with, absolutely no backwards compatibility, diff --git a/src/rpc/exceptions.hpp b/src/rpc/exceptions.hpp index 346c53a9a..f278b2414 100644 --- a/src/rpc/exceptions.hpp +++ b/src/rpc/exceptions.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -45,4 +45,12 @@ class GenericRpcFailedException : public RpcFailedException { SPECIALIZE_GET_EXCEPTION_NAME(GenericRpcFailedException); }; +class SlkRpcFailedException : public RpcFailedException { + public: + SlkRpcFailedException() + : RpcFailedException("Received malformed message from cluster. Please raise an issue on Memgraph GitHub issues.") {} + + SPECIALIZE_GET_EXCEPTION_NAME(SlkRpcFailedException); +}; + } // namespace memgraph::rpc diff --git a/src/rpc/protocol.cpp b/src/rpc/protocol.cpp index 8bc77579b..2a9c8ea72 100644 --- a/src/rpc/protocol.cpp +++ b/src/rpc/protocol.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -13,7 +13,7 @@ #include <utility> -#include "rpc/messages.hpp" +#include "rpc/exceptions.hpp" #include "rpc/server.hpp" #include "rpc/version.hpp" #include "slk/serialization.hpp" @@ -46,10 +46,14 @@ void Session::Execute() { // Load the request ID. utils::TypeId req_id{utils::TypeId::UNKNOWN}; - slk::Load(&req_id, &req_reader); // NOLINTNEXTLINE(cppcoreguidelines-init-variables) rpc::Version version; - slk::Load(&version, &req_reader); + try { + slk::Load(&req_id, &req_reader); + slk::Load(&version, &req_reader); + } catch (const slk::SlkReaderException &) { + throw rpc::SlkRpcFailedException(); + } if (version != rpc::current_version) { // V1 we introduced versioning with, absolutely no backwards compatibility, @@ -76,12 +80,20 @@ void Session::Execute() { SPDLOG_TRACE("[RpcServer] received {}", extended_it->second.req_type.name); slk::Save(extended_it->second.res_type.id, &res_builder); slk::Save(rpc::current_version, &res_builder); - extended_it->second.callback(endpoint_, &req_reader, &res_builder); + try { + extended_it->second.callback(endpoint_, &req_reader, &res_builder); + } catch (const slk::SlkReaderException &) { + throw rpc::SlkRpcFailedException(); + } } else { SPDLOG_TRACE("[RpcServer] received {}", it->second.req_type.name); slk::Save(it->second.res_type.id, &res_builder); slk::Save(rpc::current_version, &res_builder); - it->second.callback(&req_reader, &res_builder); + try { + it->second.callback(&req_reader, &res_builder); + } catch (const slk::SlkReaderException &) { + throw rpc::SlkRpcFailedException(); + } } // Finalize the SLK streams. diff --git a/src/rpc/version.hpp b/src/rpc/version.hpp index 29e7f8d3a..b234a3ccc 100644 --- a/src/rpc/version.hpp +++ b/src/rpc/version.hpp @@ -22,6 +22,12 @@ using Version = uint64_t; // probability of accidental match/conformance with pre 2.13 versions constexpr auto v1 = Version{2023'10'30'0'2'13}; -constexpr auto current_version = v1; +// TypeId has been changed, they were not stable +// Added stable numbering for replication types to be in +// 2000-2999 range. We shouldn't need to version bump again +// for any TypeIds that get added. +constexpr auto v2 = Version{2023'12'07'0'2'14}; + +constexpr auto current_version = v2; } // namespace memgraph::rpc diff --git a/src/slk/serialization.hpp b/src/slk/serialization.hpp index 9ca99527d..41c3d8539 100644 --- a/src/slk/serialization.hpp +++ b/src/slk/serialization.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -55,11 +55,22 @@ class SlkDecodeException : public utils::BasicException { // here because C++ doesn't know how to resolve the function call if it isn't in // the global namespace. +template <typename T> +inline void Save(const std::vector<T> &obj, Builder *builder, + std::function<void(const T &, Builder *)> item_save_function); +template <typename T> +inline void Load(std::vector<T> *obj, Reader *reader, std::function<void(T *, Reader *)> item_load_function); + template <typename T> void Save(const std::vector<T> &obj, Builder *builder); template <typename T> void Load(std::vector<T> *obj, Reader *reader); +template <typename T, size_t N> +void Save(const std::array<T, N> &obj, Builder *builder); +template <typename T, size_t N> +void Load(std::array<T, N> *obj, Reader *reader); + template <typename T, typename Cmp> void Save(const std::set<T, Cmp> &obj, Builder *builder); template <typename T, typename Cmp> @@ -201,6 +212,24 @@ inline void Load(std::vector<T> *obj, Reader *reader) { } } +template <typename T, size_t N> +inline void Save(const std::array<T, N> &obj, Builder *builder) { + uint64_t size = obj.size(); + Save(size, builder); + for (const auto &item : obj) { + Save(item, builder); + } +} + +template <typename T, size_t N> +inline void Load(std::array<T, N> *obj, Reader *reader) { + uint64_t size = 0; + Load(&size, reader); + for (uint64_t i = 0; i < size; ++i) { + Load(&(*obj)[i], reader); + } +} + template <typename T, typename Cmp> inline void Save(const std::set<T, Cmp> &obj, Builder *builder) { uint64_t size = obj.size(); @@ -486,4 +515,17 @@ inline void Load(utils::TypeId *obj, Reader *reader) { *obj = utils::TypeId(utils::MemcpyCast<enum_type>(obj_encoded)); } +template <utils::Enum T> +void Save(const T &enum_value, slk::Builder *builder) { + slk::Save(utils::UnderlyingCast(enum_value), builder); +} + +template <utils::Enum T> +void Load(T *enum_value, slk::Reader *reader) { + using UnderlyingType = std::underlying_type_t<T>; + UnderlyingType value; + slk::Load(&value, reader); + *enum_value = static_cast<T>(value); +} + } // namespace memgraph::slk diff --git a/src/storage/v2/config.hpp b/src/storage/v2/config.hpp index dee2afe87..3533594ce 100644 --- a/src/storage/v2/config.hpp +++ b/src/storage/v2/config.hpp @@ -14,10 +14,12 @@ #include <chrono> #include <cstdint> #include <filesystem> + #include "storage/v2/isolation_level.hpp" #include "storage/v2/storage_mode.hpp" #include "utils/exceptions.hpp" #include "utils/logging.hpp" +#include "utils/uuid.hpp" namespace memgraph::storage { @@ -27,6 +29,41 @@ class StorageConfigException : public utils::BasicException { SPECIALIZE_GET_EXCEPTION_NAME(StorageConfigException) }; +struct SalientConfig { + std::string name; + utils::UUID uuid; + StorageMode storage_mode{StorageMode::IN_MEMORY_TRANSACTIONAL}; + struct Items { + bool properties_on_edges{true}; + bool enable_schema_metadata{false}; + friend bool operator==(const Items &lrh, const Items &rhs) = default; + } items; + + friend bool operator==(const SalientConfig &, const SalientConfig &) = default; +}; + +inline void to_json(nlohmann::json &data, SalientConfig::Items const &items) { + data = nlohmann::json{{"properties_on_edges", items.properties_on_edges}, + {"enable_schema_metadata", items.enable_schema_metadata}}; +} + +inline void from_json(const nlohmann::json &data, SalientConfig::Items &items) { + data.at("properties_on_edges").get_to(items.properties_on_edges); + data.at("enable_schema_metadata").get_to(items.enable_schema_metadata); +} + +inline void to_json(nlohmann::json &data, SalientConfig const &config) { + data = nlohmann::json{ + {"items", config.items}, {"name", config.name}, {"uuid", config.uuid}, {"storage_mode", config.storage_mode}}; +} + +inline void from_json(const nlohmann::json &data, SalientConfig &config) { + data.at("items").get_to(config.items); + data.at("name").get_to(config.name); + data.at("uuid").get_to(config.uuid); + data.at("storage_mode").get_to(config.storage_mode); +} + /// Pass this class to the \ref Storage constructor to change the behavior of /// the storage. This class also defines the default behavior. struct Config { @@ -36,46 +73,40 @@ struct Config { Type type{Type::PERIODIC}; std::chrono::milliseconds interval{std::chrono::milliseconds(1000)}; friend bool operator==(const Gc &lrh, const Gc &rhs) = default; - } gc; - - struct Items { - bool properties_on_edges{true}; - bool enable_schema_metadata{false}; - friend bool operator==(const Items &lrh, const Items &rhs) = default; - } items; + } gc; // SYSTEM FLAG struct Durability { enum class SnapshotWalMode { DISABLED, PERIODIC_SNAPSHOT, PERIODIC_SNAPSHOT_WITH_WAL }; - std::filesystem::path storage_directory{"storage"}; + std::filesystem::path storage_directory{"storage"}; // PER INSTANCE SYSTEM FLAG-> root folder...ish - bool recover_on_startup{false}; + bool recover_on_startup{false}; // PER INSTANCE SYSTEM FLAG - SnapshotWalMode snapshot_wal_mode{SnapshotWalMode::DISABLED}; + SnapshotWalMode snapshot_wal_mode{SnapshotWalMode::DISABLED}; // PER DATABASE - std::chrono::milliseconds snapshot_interval{std::chrono::minutes(2)}; - uint64_t snapshot_retention_count{3}; + std::chrono::milliseconds snapshot_interval{std::chrono::minutes(2)}; // PER DATABASE + uint64_t snapshot_retention_count{3}; // PER DATABASE - uint64_t wal_file_size_kibibytes{20 * 1024}; - uint64_t wal_file_flush_every_n_tx{100000}; + uint64_t wal_file_size_kibibytes{20 * 1024}; // PER DATABASE + uint64_t wal_file_flush_every_n_tx{100000}; // PER DATABASE - bool snapshot_on_exit{false}; - bool restore_replication_state_on_startup{false}; + bool snapshot_on_exit{false}; // PER DATABASE + bool restore_replication_state_on_startup{false}; // PER INSTANCE - uint64_t items_per_batch{1'000'000}; - uint64_t recovery_thread_count{8}; + uint64_t items_per_batch{1'000'000}; // PER DATABASE + uint64_t recovery_thread_count{8}; // PER INSTANCE SYSTEM FLAG // deprecated - bool allow_parallel_index_creation{false}; + bool allow_parallel_index_creation{false}; // KILL - bool allow_parallel_schema_creation{false}; + bool allow_parallel_schema_creation{false}; // PER DATABASE friend bool operator==(const Durability &lrh, const Durability &rhs) = default; } durability; struct Transaction { IsolationLevel isolation_level{IsolationLevel::SNAPSHOT_ISOLATION}; friend bool operator==(const Transaction &lrh, const Transaction &rhs) = default; - } transaction; + } transaction; // PER DATABASE struct DiskConfig { std::filesystem::path main_storage_directory{"storage/rocksdb_main_storage"}; @@ -89,9 +120,9 @@ struct Config { friend bool operator==(const DiskConfig &lrh, const DiskConfig &rhs) = default; } disk; - std::string name; - bool force_on_disk{false}; - StorageMode storage_mode{StorageMode::IN_MEMORY_TRANSACTIONAL}; + SalientConfig salient; + + bool force_on_disk{false}; // TODO: cleanup.... remove + make the default storage_mode ON_DISK_TRANSACTIONAL if true friend bool operator==(const Config &lrh, const Config &rhs) = default; }; diff --git a/src/storage/v2/database_access.hpp b/src/storage/v2/database_access.hpp new file mode 100644 index 000000000..de7a1d7d4 --- /dev/null +++ b/src/storage/v2/database_access.hpp @@ -0,0 +1,25 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include <any> + +namespace memgraph::storage { + +/** + * @brief We need to protect the database using a DatabaseAccess, and we need to keep the replication/storage/dbms + * untied. To achieve that we are using std::any, but beware to pass in the correct type using DatabaseAccess = + * memgraph::utils::Gatekeeper<memgraph::dbms::Database>::Accessor; + */ +using DatabaseAccessProtector = std::any; + +} // namespace memgraph::storage diff --git a/src/storage/v2/disk/storage.cpp b/src/storage/v2/disk/storage.cpp index 3f0ab9572..f3c3aa0f4 100644 --- a/src/storage/v2/disk/storage.cpp +++ b/src/storage/v2/disk/storage.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -288,7 +288,8 @@ DiskStorage::~DiskStorage() { DiskStorage::DiskAccessor::DiskAccessor(auto tag, DiskStorage *storage, IsolationLevel isolation_level, StorageMode storage_mode) - : Accessor(tag, storage, isolation_level, storage_mode) { + : Accessor(tag, storage, isolation_level, storage_mode, + memgraph::replication_coordination_glue::ReplicationRole::MAIN) { rocksdb::WriteOptions write_options; auto txOptions = rocksdb::TransactionOptions{.set_snapshot = true}; transaction_.disk_transaction_ = storage->kvstore_->db_->BeginTransaction(write_options, txOptions); @@ -837,10 +838,11 @@ StorageInfo DiskStorage::GetBaseInfo(bool /* unused */) { return info; } -StorageInfo DiskStorage::GetInfo(bool force_dir) { +StorageInfo DiskStorage::GetInfo(bool force_dir, + memgraph::replication_coordination_glue::ReplicationRole replication_role) { StorageInfo info = GetBaseInfo(force_dir); { - auto access = Access(std::nullopt); + auto access = Access(replication_role); const auto &lbl = access->ListAllIndices(); info.label_indices = lbl.label.size(); info.label_property_indices = lbl.label_property.size(); @@ -951,7 +953,7 @@ Result<EdgeAccessor> DiskStorage::DiskAccessor::CreateEdge(VertexAccessor *from, EdgeRef edge(gid); bool edge_import_mode_active = disk_storage->edge_import_status_ == EdgeImportMode::ACTIVE; - if (storage_->config_.items.properties_on_edges) { + if (storage_->config_.salient.items.properties_on_edges) { auto acc = edge_import_mode_active ? disk_storage->edge_import_mode_cache_->AccessToEdges() : transaction_.edges_->access(); auto *delta = CreateDeleteObjectDelta(&transaction_); @@ -975,7 +977,7 @@ Result<EdgeAccessor> DiskStorage::DiskAccessor::CreateEdge(VertexAccessor *from, transaction_.manyDeltasCache.Invalidate(from_vertex, edge_type, EdgeDirection::OUT); transaction_.manyDeltasCache.Invalidate(to_vertex, edge_type, EdgeDirection::IN); - if (storage_->config_.items.enable_schema_metadata) { + if (storage_->config_.salient.items.enable_schema_metadata) { storage_->stored_edge_types_.try_insert(edge_type); } storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel); @@ -1283,7 +1285,7 @@ bool DiskStorage::DeleteEdgeFromConnectivityIndex(Transaction *transaction, cons const auto src_vertex_gid = modified_edge.second.src_vertex_gid.ToString(); const auto dst_vertex_gid = modified_edge.second.dest_vertex_gid.ToString(); - if (!config_.items.properties_on_edges) { + if (!config_.salient.items.properties_on_edges) { /// If the object was created then flush it, otherwise since properties on edges are false /// edge wasn't modified for sure. if (root_action == Delta::Action::DELETE_OBJECT && @@ -1400,7 +1402,7 @@ std::optional<EdgeAccessor> DiskStorage::CreateEdgeFromDisk(const VertexAccessor } EdgeRef edge(gid); - if (config_.items.properties_on_edges) { + if (config_.salient.items.properties_on_edges) { auto acc = edge_import_mode_active ? edge_import_mode_cache_->AccessToEdges() : transaction->edges_->access(); auto *delta = CreateDeleteDeserializedObjectDelta(transaction, old_disk_key, std::move(read_ts)); auto [it, inserted] = acc.insert(Edge(gid, delta)); @@ -1458,7 +1460,8 @@ std::vector<EdgeAccessor> DiskStorage::OutEdges(const VertexAccessor *src_vertex if (!edge_types.empty() && !utils::Contains(edge_types, edge_type_id)) continue; auto edge_gid = Gid::FromString(edge_gid_str); - auto properties_str = config_.items.properties_on_edges ? utils::GetPropertiesFromEdgeValue(edge_val_str) : ""; + auto properties_str = + config_.salient.items.properties_on_edges ? utils::GetPropertiesFromEdgeValue(edge_val_str) : ""; const auto edge = std::invoke([this, destination, &edge_val_str, transaction, view, src_vertex, edge_type_id, edge_gid, &properties_str, &edge_gid_str]() { @@ -1599,7 +1602,7 @@ DiskStorage::CheckExistingVerticesBeforeCreatingUniqueConstraint(LabelId label, // NOLINTNEXTLINE(google-default-arguments) utils::BasicResult<StorageManipulationError, void> DiskStorage::DiskAccessor::Commit( - const std::optional<uint64_t> desired_commit_timestamp, bool /*is_main*/) { + CommitReplArgs reparg, DatabaseAccessProtector /*db_acc*/) { MG_ASSERT(is_transaction_active_, "The transaction is already terminated!"); MG_ASSERT(!transaction_.must_abort, "The transaction can't be committed!"); @@ -1610,7 +1613,7 @@ utils::BasicResult<StorageManipulationError, void> DiskStorage::DiskAccessor::Co // This is usually done by the MVCC, but it does not handle the metadata deltas transaction_.EnsureCommitTimestampExists(); std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_); - commit_timestamp_.emplace(disk_storage->CommitTimestamp(desired_commit_timestamp)); + commit_timestamp_.emplace(disk_storage->CommitTimestamp(reparg.desired_commit_timestamp)); transaction_.commit_timestamp->store(*commit_timestamp_, std::memory_order_release); for (const auto &md_delta : transaction_.md_deltas) { @@ -1686,7 +1689,7 @@ utils::BasicResult<StorageManipulationError, void> DiskStorage::DiskAccessor::Co }))) { } else { std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_); - commit_timestamp_.emplace(disk_storage->CommitTimestamp(desired_commit_timestamp)); + commit_timestamp_.emplace(disk_storage->CommitTimestamp(reparg.desired_commit_timestamp)); transaction_.commit_timestamp->store(*commit_timestamp_, std::memory_order_release); if (edge_import_mode_active) { @@ -2005,7 +2008,8 @@ UniqueConstraints::DeletionStatus DiskStorage::DiskAccessor::DropUniqueConstrain return UniqueConstraints::DeletionStatus::SUCCESS; } -Transaction DiskStorage::CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, bool /*is_main*/) { +Transaction DiskStorage::CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, + memgraph::replication_coordination_glue::ReplicationRole /*is_main*/) { /// We acquire the transaction engine lock here because we access (and /// modify) the transaction engine variables (`transaction_id` and /// `timestamp`) below. @@ -2030,8 +2034,9 @@ uint64_t DiskStorage::CommitTimestamp(const std::optional<uint64_t> desired_comm return *desired_commit_timestamp; } -std::unique_ptr<Storage::Accessor> DiskStorage::Access(std::optional<IsolationLevel> override_isolation_level, - bool /*is_main*/) { +std::unique_ptr<Storage::Accessor> DiskStorage::Access( + memgraph::replication_coordination_glue::ReplicationRole /*replication_role*/, + std::optional<IsolationLevel> override_isolation_level) { auto isolation_level = override_isolation_level.value_or(isolation_level_); if (isolation_level != IsolationLevel::SNAPSHOT_ISOLATION) { throw utils::NotYetImplemented("Disk storage supports only SNAPSHOT isolation level."); @@ -2039,8 +2044,9 @@ std::unique_ptr<Storage::Accessor> DiskStorage::Access(std::optional<IsolationLe return std::unique_ptr<DiskAccessor>( new DiskAccessor{Storage::Accessor::shared_access, this, isolation_level, storage_mode_}); } -std::unique_ptr<Storage::Accessor> DiskStorage::UniqueAccess(std::optional<IsolationLevel> override_isolation_level, - bool /*is_main*/) { +std::unique_ptr<Storage::Accessor> DiskStorage::UniqueAccess( + memgraph::replication_coordination_glue::ReplicationRole /*replication_role*/, + std::optional<IsolationLevel> override_isolation_level) { auto isolation_level = override_isolation_level.value_or(isolation_level_); if (isolation_level != IsolationLevel::SNAPSHOT_ISOLATION) { throw utils::NotYetImplemented("Disk storage supports only SNAPSHOT isolation level."); diff --git a/src/storage/v2/disk/storage.hpp b/src/storage/v2/disk/storage.hpp index 8640462de..293e102b1 100644 --- a/src/storage/v2/disk/storage.hpp +++ b/src/storage/v2/disk/storage.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -145,8 +145,8 @@ class DiskStorage final : public Storage { ConstraintsInfo ListAllConstraints() const override; // NOLINTNEXTLINE(google-default-arguments) - utils::BasicResult<StorageManipulationError, void> Commit(std::optional<uint64_t> desired_commit_timestamp = {}, - bool is_main = true) override; + utils::BasicResult<StorageManipulationError, void> Commit(CommitReplArgs reparg = {}, + DatabaseAccessProtector db_acc = {}) override; void UpdateObjectsCountOnAbort(); @@ -176,12 +176,12 @@ class DiskStorage final : public Storage { }; using Storage::Access; - std::unique_ptr<Storage::Accessor> Access(std::optional<IsolationLevel> override_isolation_level, - bool is_main) override; + std::unique_ptr<Accessor> Access(memgraph::replication_coordination_glue::ReplicationRole replication_role, + std::optional<IsolationLevel> override_isolation_level) override; using Storage::UniqueAccess; - std::unique_ptr<Storage::Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level, - bool is_main) override; + std::unique_ptr<Accessor> UniqueAccess(memgraph::replication_coordination_glue::ReplicationRole replication_role, + std::optional<IsolationLevel> override_isolation_level) override; /// Flushing methods [[nodiscard]] utils::BasicResult<StorageManipulationError, void> FlushIndexCache(Transaction *transaction); @@ -284,8 +284,8 @@ class DiskStorage final : public Storage { RocksDBStorage *GetRocksDBStorage() const { return kvstore_.get(); } - using Storage::CreateTransaction; - Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, bool is_main) override; + Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, + memgraph::replication_coordination_glue::ReplicationRole replication_role) override; void SetEdgeImportMode(EdgeImportMode edge_import_status); @@ -308,7 +308,8 @@ class DiskStorage final : public Storage { PropertyId property); StorageInfo GetBaseInfo(bool force_directory) override; - StorageInfo GetInfo(bool force_directory) override; + StorageInfo GetInfo(bool force_directory, + memgraph::replication_coordination_glue::ReplicationRole replication_role) override; void FreeMemory(std::unique_lock<utils::ResourceLock> /*lock*/) override {} diff --git a/src/storage/v2/durability/durability.cpp b/src/storage/v2/durability/durability.cpp index 6a89b7b5a..92c4d11e8 100644 --- a/src/storage/v2/durability/durability.cpp +++ b/src/storage/v2/durability/durability.cpp @@ -428,7 +428,7 @@ std::optional<RecoveryInfo> Recovery::RecoverData(std::string *uuid, Replication } try { auto info = LoadWal(wal_file.path, &indices_constraints, last_loaded_timestamp, vertices, edges, name_id_mapper, - edge_count, config.items); + edge_count, config.salient.items); recovery_info.next_vertex_id = std::max(recovery_info.next_vertex_id, info.next_vertex_id); recovery_info.next_edge_id = std::max(recovery_info.next_edge_id, info.next_edge_id); recovery_info.next_timestamp = std::max(recovery_info.next_timestamp, info.next_timestamp); diff --git a/src/storage/v2/durability/snapshot.cpp b/src/storage/v2/durability/snapshot.cpp index 52872222b..0d434fadf 100644 --- a/src/storage/v2/durability/snapshot.cpp +++ b/src/storage/v2/durability/snapshot.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -222,7 +222,7 @@ std::vector<BatchInfo> ReadBatchInfos(Decoder &snapshot) { template <typename TFunc> void LoadPartialEdges(const std::filesystem::path &path, utils::SkipList<Edge> &edges, const uint64_t from_offset, - const uint64_t edges_count, const Config::Items items, TFunc get_property_from_id) { + const uint64_t edges_count, const SalientConfig::Items items, TFunc get_property_from_id) { Decoder snapshot; snapshot.Initialize(path, kSnapshotMagic); @@ -420,7 +420,7 @@ template <typename TEdgeTypeFromIdFunc> LoadPartialConnectivityResult LoadPartialConnectivity(const std::filesystem::path &path, utils::SkipList<Vertex> &vertices, utils::SkipList<Edge> &edges, const uint64_t from_offset, const uint64_t vertices_count, - const Config::Items items, const bool snapshot_has_edges, + const SalientConfig::Items items, const bool snapshot_has_edges, TEdgeTypeFromIdFunc get_edge_type_from_id) { Decoder snapshot; snapshot.Initialize(path, kSnapshotMagic); @@ -621,7 +621,7 @@ RecoveredSnapshot LoadSnapshotVersion14(const std::filesystem::path &path, utils utils::SkipList<Edge> *edges, std::deque<std::pair<std::string, uint64_t>> *epoch_history, NameIdMapper *name_id_mapper, std::atomic<uint64_t> *edge_count, - Config::Items items) { + SalientConfig::Items items) { RecoveryInfo ret; RecoveredIndicesAndConstraints indices_constraints; @@ -1177,8 +1177,8 @@ RecoveredSnapshot LoadSnapshotVersion15(const std::filesystem::path &path, utils RecoverOnMultipleThreads( config.durability.recovery_thread_count, - [path, edges, items = config.items, &get_property_from_id](const size_t /*batch_index*/, - const BatchInfo &batch) { + [path, edges, items = config.salient.items, &get_property_from_id](const size_t /*batch_index*/, + const BatchInfo &batch) { LoadPartialEdges(path, *edges, batch.offset, batch.count, items, get_property_from_id); }, edge_batches); @@ -1218,7 +1218,7 @@ RecoveredSnapshot LoadSnapshotVersion15(const std::filesystem::path &path, utils RecoverOnMultipleThreads( config.durability.recovery_thread_count, - [path, vertices, edges, edge_count, items = config.items, snapshot_has_edges, &get_edge_type_from_id, + [path, vertices, edges, edge_count, items = config.salient.items, snapshot_has_edges, &get_edge_type_from_id, &highest_edge_gid, &recovery_info](const size_t batch_index, const BatchInfo &batch) { const auto result = LoadPartialConnectivity(path, *vertices, *edges, batch.offset, batch.count, items, snapshot_has_edges, get_edge_type_from_id); @@ -1391,7 +1391,8 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis if (!IsVersionSupported(*version)) throw RecoveryFailure(fmt::format("Invalid snapshot version {}", *version)); if (*version == 14U) { - return LoadSnapshotVersion14(path, vertices, edges, epoch_history, name_id_mapper, edge_count, config.items); + return LoadSnapshotVersion14(path, vertices, edges, epoch_history, name_id_mapper, edge_count, + config.salient.items); } if (*version == 15U) { return LoadSnapshotVersion15(path, vertices, edges, epoch_history, name_id_mapper, edge_count, config); @@ -1470,8 +1471,8 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis RecoverOnMultipleThreads( config.durability.recovery_thread_count, - [path, edges, items = config.items, &get_property_from_id](const size_t /*batch_index*/, - const BatchInfo &batch) { + [path, edges, items = config.salient.items, &get_property_from_id](const size_t /*batch_index*/, + const BatchInfo &batch) { LoadPartialEdges(path, *edges, batch.offset, batch.count, items, get_property_from_id); }, edge_batches); @@ -1511,7 +1512,7 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis RecoverOnMultipleThreads( config.durability.recovery_thread_count, - [path, vertices, edges, edge_count, items = config.items, snapshot_has_edges, &get_edge_type_from_id, + [path, vertices, edges, edge_count, items = config.salient.items, snapshot_has_edges, &get_edge_type_from_id, &highest_edge_gid, &recovery_info](const size_t batch_index, const BatchInfo &batch) { const auto result = LoadPartialConnectivity(path, *vertices, *edges, batch.offset, batch.count, items, snapshot_has_edges, get_edge_type_from_id); @@ -1868,7 +1869,7 @@ void CreateSnapshot(Storage *storage, Transaction *transaction, const std::files auto items_in_current_batch{0UL}; auto batch_start_offset{0UL}; // Store all edges. - if (storage->config_.items.properties_on_edges) { + if (storage->config_.salient.items.properties_on_edges) { offset_edges = snapshot.GetPosition(); batch_start_offset = offset_edges; auto acc = edges->access(); @@ -1982,18 +1983,34 @@ void CreateSnapshot(Storage *storage, Transaction *transaction, const std::files snapshot.WritePropertyValue(item.second); } const auto &in_edges = maybe_in_edges.GetValue().edges; - snapshot.WriteUint(in_edges.size()); - for (const auto &item : in_edges) { - snapshot.WriteUint(item.Gid().AsUint()); - snapshot.WriteUint(item.FromVertex().Gid().AsUint()); - write_mapping(item.EdgeType()); - } const auto &out_edges = maybe_out_edges.GetValue().edges; - snapshot.WriteUint(out_edges.size()); - for (const auto &item : out_edges) { - snapshot.WriteUint(item.Gid().AsUint()); - snapshot.WriteUint(item.ToVertex().Gid().AsUint()); - write_mapping(item.EdgeType()); + + if (storage->config_.salient.items.properties_on_edges) { + snapshot.WriteUint(in_edges.size()); + for (const auto &item : in_edges) { + snapshot.WriteUint(item.GidPropertiesOnEdges().AsUint()); + snapshot.WriteUint(item.FromVertex().Gid().AsUint()); + write_mapping(item.EdgeType()); + } + snapshot.WriteUint(out_edges.size()); + for (const auto &item : out_edges) { + snapshot.WriteUint(item.GidPropertiesOnEdges().AsUint()); + snapshot.WriteUint(item.ToVertex().Gid().AsUint()); + write_mapping(item.EdgeType()); + } + } else { + snapshot.WriteUint(in_edges.size()); + for (const auto &item : in_edges) { + snapshot.WriteUint(item.GidNoPropertiesOnEdges().AsUint()); + snapshot.WriteUint(item.FromVertex().Gid().AsUint()); + write_mapping(item.EdgeType()); + } + snapshot.WriteUint(out_edges.size()); + for (const auto &item : out_edges) { + snapshot.WriteUint(item.GidNoPropertiesOnEdges().AsUint()); + snapshot.WriteUint(item.ToVertex().Gid().AsUint()); + write_mapping(item.EdgeType()); + } } } diff --git a/src/storage/v2/durability/wal.cpp b/src/storage/v2/durability/wal.cpp index 8c28a6b6d..e808f01a3 100644 --- a/src/storage/v2/durability/wal.cpp +++ b/src/storage/v2/durability/wal.cpp @@ -548,7 +548,7 @@ WalDeltaData::Type SkipWalDeltaData(BaseDecoder *decoder) { return delta.type; } -void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Config::Items items, const Delta &delta, +void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, SalientConfig::Items items, const Delta &delta, const Vertex &vertex, uint64_t timestamp) { // When converting a Delta to a WAL delta the logic is inverted. That is // because the Delta's represent undo actions and we want to store redo @@ -709,7 +709,7 @@ void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Storage RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConstraints *indices_constraints, const std::optional<uint64_t> last_loaded_timestamp, utils::SkipList<Vertex> *vertices, utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper, std::atomic<uint64_t> *edge_count, - Config::Items items) { + SalientConfig::Items items) { spdlog::info("Trying to load WAL file {}.", path); RecoveryInfo ret; @@ -983,8 +983,8 @@ RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConst } WalFile::WalFile(const std::filesystem::path &wal_directory, const std::string_view uuid, - const std::string_view epoch_id, Config::Items items, NameIdMapper *name_id_mapper, uint64_t seq_num, - utils::FileRetainer *file_retainer) + const std::string_view epoch_id, SalientConfig::Items items, NameIdMapper *name_id_mapper, + uint64_t seq_num, utils::FileRetainer *file_retainer) : items_(items), name_id_mapper_(name_id_mapper), path_(wal_directory / MakeWalName()), @@ -1026,7 +1026,7 @@ WalFile::WalFile(const std::filesystem::path &wal_directory, const std::string_v wal_.Sync(); } -WalFile::WalFile(std::filesystem::path current_wal_path, Config::Items items, NameIdMapper *name_id_mapper, +WalFile::WalFile(std::filesystem::path current_wal_path, SalientConfig::Items items, NameIdMapper *name_id_mapper, uint64_t seq_num, uint64_t from_timestamp, uint64_t to_timestamp, uint64_t count, utils::FileRetainer *file_retainer) : items_(items), diff --git a/src/storage/v2/durability/wal.hpp b/src/storage/v2/durability/wal.hpp index 8f6492ac7..20d88b040 100644 --- a/src/storage/v2/durability/wal.hpp +++ b/src/storage/v2/durability/wal.hpp @@ -193,7 +193,7 @@ WalDeltaData ReadWalDeltaData(BaseDecoder *decoder); WalDeltaData::Type SkipWalDeltaData(BaseDecoder *decoder); /// Function used to encode a `Delta` that originated from a `Vertex`. -void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Config::Items items, const Delta &delta, +void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, SalientConfig::Items items, const Delta &delta, const Vertex &vertex, uint64_t timestamp); /// Function used to encode a `Delta` that originated from an `Edge`. @@ -213,15 +213,17 @@ void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Storage RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConstraints *indices_constraints, std::optional<uint64_t> last_loaded_timestamp, utils::SkipList<Vertex> *vertices, utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper, std::atomic<uint64_t> *edge_count, - Config::Items items); + SalientConfig::Items items); /// WalFile class used to append deltas and operations to the WAL file. class WalFile { public: WalFile(const std::filesystem::path &wal_directory, std::string_view uuid, std::string_view epoch_id, - Config::Items items, NameIdMapper *name_id_mapper, uint64_t seq_num, utils::FileRetainer *file_retainer); - WalFile(std::filesystem::path current_wal_path, Config::Items items, NameIdMapper *name_id_mapper, uint64_t seq_num, - uint64_t from_timestamp, uint64_t to_timestamp, uint64_t count, utils::FileRetainer *file_retainer); + SalientConfig::Items items, NameIdMapper *name_id_mapper, uint64_t seq_num, + utils::FileRetainer *file_retainer); + WalFile(std::filesystem::path current_wal_path, SalientConfig::Items items, NameIdMapper *name_id_mapper, + uint64_t seq_num, uint64_t from_timestamp, uint64_t to_timestamp, uint64_t count, + utils::FileRetainer *file_retainer); WalFile(const WalFile &) = delete; WalFile(WalFile &&) = delete; @@ -268,7 +270,7 @@ class WalFile { private: void UpdateStats(uint64_t timestamp); - Config::Items items_; + SalientConfig::Items items_; NameIdMapper *name_id_mapper_; Encoder wal_; std::filesystem::path path_; diff --git a/src/storage/v2/edge_accessor.cpp b/src/storage/v2/edge_accessor.cpp index 7e7166117..3ab2e3d79 100644 --- a/src/storage/v2/edge_accessor.cpp +++ b/src/storage/v2/edge_accessor.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -21,12 +21,13 @@ #include "storage/v2/result.hpp" #include "storage/v2/storage.hpp" #include "storage/v2/vertex_accessor.hpp" +#include "utils/atomic_memory_block.hpp" #include "utils/memory_tracker.hpp" namespace memgraph::storage { bool EdgeAccessor::IsDeleted() const { - if (!storage_->config_.items.properties_on_edges) { + if (!storage_->config_.salient.items.properties_on_edges) { return false; } return edge_.ptr->deleted; @@ -37,7 +38,7 @@ bool EdgeAccessor::IsVisible(const View view) const { bool deleted = true; // When edges don't have properties, their isolation level is still dictated by MVCC -> // iterate over the deltas of the from_vertex_ and see which deltas can be applied on edges. - if (!storage_->config_.items.properties_on_edges) { + if (!storage_->config_.salient.items.properties_on_edges) { Delta *delta = nullptr; { auto guard = std::shared_lock{from_vertex_->lock}; @@ -119,36 +120,40 @@ VertexAccessor EdgeAccessor::DeletedEdgeToVertex() const { Result<storage::PropertyValue> EdgeAccessor::SetProperty(PropertyId property, const PropertyValue &value) { utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; - if (!storage_->config_.items.properties_on_edges) return Error::PROPERTIES_DISABLED; + if (!storage_->config_.salient.items.properties_on_edges) return Error::PROPERTIES_DISABLED; auto guard = std::unique_lock{edge_.ptr->lock}; if (!PrepareForWrite(transaction_, edge_.ptr)) return Error::SERIALIZATION_ERROR; if (edge_.ptr->deleted) return Error::DELETED_OBJECT; - - auto current_value = edge_.ptr->properties.GetProperty(property); - // We could skip setting the value if the previous one is the same to the new - // one. This would save some memory as a delta would not be created as well as - // avoid copying the value. The reason we are not doing that is because the - // current code always follows the logical pattern of "create a delta" and - // "modify in-place". Additionally, the created delta will make other - // transactions get a SERIALIZATION_ERROR. - - CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property, current_value); - edge_.ptr->properties.SetProperty(property, value); + using ReturnType = decltype(edge_.ptr->properties.GetProperty(property)); + std::optional<ReturnType> current_value; + utils::AtomicMemoryBlock atomic_memory_block{ + [¤t_value, &property, &value, transaction = transaction_, edge = edge_]() { + current_value.emplace(edge.ptr->properties.GetProperty(property)); + // We could skip setting the value if the previous one is the same to the new + // one. This would save some memory as a delta would not be created as well as + // avoid copying the value. The reason we are not doing that is because the + // current code always follows the logical pattern of "create a delta" and + // "modify in-place". Additionally, the created delta will make other + // transactions get a SERIALIZATION_ERROR. + CreateAndLinkDelta(transaction, edge.ptr, Delta::SetPropertyTag(), property, *current_value); + edge.ptr->properties.SetProperty(property, value); + }}; + std::invoke(atomic_memory_block); if (transaction_->IsDiskStorage()) { ModifiedEdgeInfo modified_edge(Delta::Action::SET_PROPERTY, from_vertex_->gid, to_vertex_->gid, edge_type_, edge_); transaction_->AddModifiedEdge(Gid(), modified_edge); } - return std::move(current_value); + return std::move(*current_value); } Result<bool> EdgeAccessor::InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties) { utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; - if (!storage_->config_.items.properties_on_edges) return Error::PROPERTIES_DISABLED; + if (!storage_->config_.salient.items.properties_on_edges) return Error::PROPERTIES_DISABLED; auto guard = std::unique_lock{edge_.ptr->lock}; @@ -157,9 +162,12 @@ Result<bool> EdgeAccessor::InitProperties(const std::map<storage::PropertyId, st if (edge_.ptr->deleted) return Error::DELETED_OBJECT; if (!edge_.ptr->properties.InitProperties(properties)) return false; - for (const auto &[property, _] : properties) { - CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property, PropertyValue()); - } + utils::AtomicMemoryBlock atomic_memory_block{[&properties, transaction_ = transaction_, edge_ = edge_]() { + for (const auto &[property, _] : properties) { + CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property, PropertyValue()); + } + }}; + std::invoke(atomic_memory_block); return true; } @@ -167,7 +175,7 @@ Result<bool> EdgeAccessor::InitProperties(const std::map<storage::PropertyId, st Result<std::vector<std::tuple<PropertyId, PropertyValue, PropertyValue>>> EdgeAccessor::UpdateProperties( std::map<storage::PropertyId, storage::PropertyValue> &properties) const { utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; - if (!storage_->config_.items.properties_on_edges) return Error::PROPERTIES_DISABLED; + if (!storage_->config_.salient.items.properties_on_edges) return Error::PROPERTIES_DISABLED; auto guard = std::unique_lock{edge_.ptr->lock}; @@ -175,17 +183,22 @@ Result<std::vector<std::tuple<PropertyId, PropertyValue, PropertyValue>>> EdgeAc if (edge_.ptr->deleted) return Error::DELETED_OBJECT; - auto id_old_new_change = edge_.ptr->properties.UpdateProperties(properties); + using ReturnType = decltype(edge_.ptr->properties.UpdateProperties(properties)); + std::optional<ReturnType> id_old_new_change; + utils::AtomicMemoryBlock atomic_memory_block{ + [transaction_ = transaction_, edge_ = edge_, &properties, &id_old_new_change]() { + id_old_new_change.emplace(edge_.ptr->properties.UpdateProperties(properties)); + for (auto &[property, old_value, new_value] : *id_old_new_change) { + CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property, std::move(old_value)); + } + }}; + std::invoke(atomic_memory_block); - for (auto &[property, old_value, new_value] : id_old_new_change) { - CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property, std::move(old_value)); - } - - return id_old_new_change; + return id_old_new_change.has_value() ? std::move(id_old_new_change.value()) : ReturnType{}; } Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::ClearProperties() { - if (!storage_->config_.items.properties_on_edges) return Error::PROPERTIES_DISABLED; + if (!storage_->config_.salient.items.properties_on_edges) return Error::PROPERTIES_DISABLED; auto guard = std::unique_lock{edge_.ptr->lock}; @@ -193,33 +206,38 @@ Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::ClearProperties() { if (edge_.ptr->deleted) return Error::DELETED_OBJECT; - auto properties = edge_.ptr->properties.Properties(); - for (const auto &property : properties) { - CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property.first, property.second); - } + using ReturnType = decltype(edge_.ptr->properties.Properties()); + std::optional<ReturnType> properties; + utils::AtomicMemoryBlock atomic_memory_block{[&properties, transaction_ = transaction_, edge_ = edge_]() { + properties.emplace(edge_.ptr->properties.Properties()); + for (const auto &property : *properties) { + CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property.first, property.second); + } - edge_.ptr->properties.ClearProperties(); + edge_.ptr->properties.ClearProperties(); + }}; + std::invoke(atomic_memory_block); - return std::move(properties); + return properties.has_value() ? std::move(properties.value()) : ReturnType{}; } Result<PropertyValue> EdgeAccessor::GetProperty(PropertyId property, View view) const { - if (!storage_->config_.items.properties_on_edges) return PropertyValue(); + if (!storage_->config_.salient.items.properties_on_edges) return PropertyValue(); bool exists = true; bool deleted = false; - PropertyValue value; + std::optional<PropertyValue> value; Delta *delta = nullptr; { auto guard = std::shared_lock{edge_.ptr->lock}; deleted = edge_.ptr->deleted; - value = edge_.ptr->properties.GetProperty(property); + value.emplace(edge_.ptr->properties.GetProperty(property)); delta = edge_.ptr->delta; } ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &value, property](const Delta &delta) { switch (delta.action) { case Delta::Action::SET_PROPERTY: { if (delta.property.key == property) { - value = delta.property.value; + *value = delta.property.value; } break; } @@ -243,11 +261,11 @@ Result<PropertyValue> EdgeAccessor::GetProperty(PropertyId property, View view) }); if (!exists) return Error::NONEXISTENT_OBJECT; if (!for_deleted_ && deleted) return Error::DELETED_OBJECT; - return std::move(value); + return *std::move(value); } Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::Properties(View view) const { - if (!storage_->config_.items.properties_on_edges) return std::map<PropertyId, PropertyValue>{}; + if (!storage_->config_.salient.items.properties_on_edges) return std::map<PropertyId, PropertyValue>{}; bool exists = true; bool deleted = false; std::map<PropertyId, PropertyValue> properties; @@ -299,7 +317,7 @@ Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::Properties(View view) } Gid EdgeAccessor::Gid() const noexcept { - if (storage_->config_.items.properties_on_edges) { + if (storage_->config_.salient.items.properties_on_edges) { return edge_.ptr->gid; } return edge_.gid; diff --git a/src/storage/v2/edge_accessor.hpp b/src/storage/v2/edge_accessor.hpp index a1c52d0a5..83a3e549d 100644 --- a/src/storage/v2/edge_accessor.hpp +++ b/src/storage/v2/edge_accessor.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -85,6 +85,8 @@ class EdgeAccessor final { /// @throw std::bad_alloc Result<std::map<PropertyId, PropertyValue>> Properties(View view) const; + auto GidPropertiesOnEdges() const -> Gid { return edge_.ptr->gid; } + auto GidNoPropertiesOnEdges() const -> Gid { return edge_.gid; } Gid Gid() const noexcept; bool IsCycle() const { return from_vertex_ == to_vertex_; } diff --git a/src/storage/v2/indices/indices.cpp b/src/storage/v2/indices/indices.cpp index e0b194ad4..c86ec8442 100644 --- a/src/storage/v2/indices/indices.cpp +++ b/src/storage/v2/indices/indices.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -32,10 +32,10 @@ void Indices::AbortEntries(LabelId label, std::span<std::pair<PropertyValue, Ver ->AbortEntries(label, vertices, exact_start_timestamp); } -void Indices::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) const { - static_cast<InMemoryLabelIndex *>(label_index_.get())->RemoveObsoleteEntries(oldest_active_start_timestamp); +void Indices::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std::stop_token token) const { + static_cast<InMemoryLabelIndex *>(label_index_.get())->RemoveObsoleteEntries(oldest_active_start_timestamp, token); static_cast<InMemoryLabelPropertyIndex *>(label_property_index_.get()) - ->RemoveObsoleteEntries(oldest_active_start_timestamp); + ->RemoveObsoleteEntries(oldest_active_start_timestamp, std::move(token)); } void Indices::UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx) const { diff --git a/src/storage/v2/indices/indices.hpp b/src/storage/v2/indices/indices.hpp index 33bd429e6..d95187bbb 100644 --- a/src/storage/v2/indices/indices.hpp +++ b/src/storage/v2/indices/indices.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -33,7 +33,7 @@ struct Indices { /// This function should be called from garbage collection to clean-up the /// index. /// TODO: unused in disk indices - void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) const; + void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std::stop_token token) const; /// Surgical removal of entries that was inserted this transaction /// TODO: unused in disk indices diff --git a/src/storage/v2/indices/indices_utils.hpp b/src/storage/v2/indices/indices_utils.hpp index 59b492ba3..054609188 100644 --- a/src/storage/v2/indices/indices_utils.hpp +++ b/src/storage/v2/indices/indices_utils.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -21,11 +21,18 @@ namespace memgraph::storage { +namespace { + +template <Delta::Action... actions> +struct ActionSet { + constexpr bool contains(Delta::Action action) const { return ((action == actions) || ...); } +}; + /// Traverses deltas visible from transaction with start timestamp greater than /// the provided timestamp, and calls the provided callback function for each /// delta. If the callback ever returns true, traversal is stopped and the /// function returns true. Otherwise, the function returns false. -template <typename TCallback> +template <ActionSet interesting, typename TCallback> inline bool AnyVersionSatisfiesPredicate(uint64_t timestamp, const Delta *delta, const TCallback &predicate) { while (delta != nullptr) { const auto ts = delta->timestamp->load(std::memory_order_acquire); @@ -33,7 +40,7 @@ inline bool AnyVersionSatisfiesPredicate(uint64_t timestamp, const Delta *delta, if (ts < timestamp) { break; } - if (predicate(*delta)) { + if (interesting.contains(delta->action) && predicate(*delta)) { return true; } // Move to the next delta. @@ -42,6 +49,8 @@ inline bool AnyVersionSatisfiesPredicate(uint64_t timestamp, const Delta *delta, return false; } +} // namespace + /// Helper function for label index garbage collection. Returns true if there's /// a reachable version of the vertex that has the given label. inline bool AnyVersionHasLabel(const Vertex &vertex, LabelId label, uint64_t timestamp) { @@ -57,7 +66,10 @@ inline bool AnyVersionHasLabel(const Vertex &vertex, LabelId label, uint64_t tim if (!deleted && has_label) { return true; } - return AnyVersionSatisfiesPredicate(timestamp, delta, [&has_label, &deleted, label](const Delta &delta) { + constexpr auto interesting = + ActionSet<Delta::Action::ADD_LABEL, Delta::Action::REMOVE_LABEL, Delta::Action::RECREATE_OBJECT, + Delta::Action::DELETE_DESERIALIZED_OBJECT, Delta::Action::DELETE_OBJECT>{}; + return AnyVersionSatisfiesPredicate<interesting>(timestamp, delta, [&has_label, &deleted, label](const Delta &delta) { switch (delta.action) { case Delta::Action::ADD_LABEL: if (delta.label == label) { @@ -98,10 +110,10 @@ inline bool AnyVersionHasLabel(const Vertex &vertex, LabelId label, uint64_t tim /// property value. inline bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label, PropertyId key, const PropertyValue &value, uint64_t timestamp) { - bool has_label{false}; - bool current_value_equal_to_value{value.IsNull()}; - bool deleted{false}; - const Delta *delta = nullptr; + Delta const *delta; + bool deleted; + bool has_label; + bool current_value_equal_to_value; { auto guard = std::shared_lock{vertex.lock}; delta = vertex.delta; @@ -116,7 +128,10 @@ inline bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label, Prop return true; } - return AnyVersionSatisfiesPredicate( + constexpr auto interesting = ActionSet<Delta::Action::ADD_LABEL, Delta::Action::REMOVE_LABEL, + Delta::Action::SET_PROPERTY, Delta::Action::RECREATE_OBJECT, + Delta::Action::DELETE_DESERIALIZED_OBJECT, Delta::Action::DELETE_OBJECT>{}; + return AnyVersionSatisfiesPredicate<interesting>( timestamp, delta, [&has_label, ¤t_value_equal_to_value, &deleted, label, key, &value](const Delta &delta) { switch (delta.action) { case Delta::Action::ADD_LABEL: diff --git a/src/storage/v2/inmemory/label_index.cpp b/src/storage/v2/inmemory/label_index.cpp index b833c97ff..9ab027308 100644 --- a/src/storage/v2/inmemory/label_index.cpp +++ b/src/storage/v2/inmemory/label_index.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -16,6 +16,7 @@ #include "storage/v2/constraints/constraints.hpp" #include "storage/v2/indices/indices_utils.hpp" #include "storage/v2/inmemory/storage.hpp" +#include "utils/counter.hpp" namespace memgraph::storage { @@ -79,10 +80,18 @@ std::vector<LabelId> InMemoryLabelIndex::ListIndices() const { return ret; } -void InMemoryLabelIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) { +void InMemoryLabelIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std::stop_token token) { + auto maybe_stop = utils::ResettableCounter<2048>(); + for (auto &label_storage : index_) { + // before starting index, check if stop_requested + if (token.stop_requested()) return; + auto vertices_acc = label_storage.second.access(); for (auto it = vertices_acc.begin(); it != vertices_acc.end();) { + // Hot loop, don't check stop_requested every time + if (maybe_stop() && token.stop_requested()) return; + auto next_it = it; ++next_it; diff --git a/src/storage/v2/inmemory/label_index.hpp b/src/storage/v2/inmemory/label_index.hpp index 2411f0ba1..5ecac117b 100644 --- a/src/storage/v2/inmemory/label_index.hpp +++ b/src/storage/v2/inmemory/label_index.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -54,7 +54,7 @@ class InMemoryLabelIndex : public storage::LabelIndex { std::vector<LabelId> ListIndices() const override; - void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp); + void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std::stop_token token); /// Surgical removal of entries that was inserted this transaction void AbortEntries(LabelId labelId, std::span<Vertex *const> vertices, uint64_t exact_start_timestamp); diff --git a/src/storage/v2/inmemory/label_property_index.cpp b/src/storage/v2/inmemory/label_property_index.cpp index c8333fb95..59b12a779 100644 --- a/src/storage/v2/inmemory/label_property_index.cpp +++ b/src/storage/v2/inmemory/label_property_index.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -13,6 +13,7 @@ #include "storage/v2/constraints/constraints.hpp" #include "storage/v2/indices/indices_utils.hpp" #include "storage/v2/inmemory/storage.hpp" +#include "utils/counter.hpp" #include "utils/logging.hpp" namespace memgraph::storage { @@ -139,10 +140,18 @@ std::vector<std::pair<LabelId, PropertyId>> InMemoryLabelPropertyIndex::ListIndi return ret; } -void InMemoryLabelPropertyIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) { +void InMemoryLabelPropertyIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std::stop_token token) { + auto maybe_stop = utils::ResettableCounter<2048>(); + for (auto &[label_property, index] : index_) { + // before starting index, check if stop_requested + if (token.stop_requested()) return; + auto index_acc = index.access(); for (auto it = index_acc.begin(); it != index_acc.end();) { + // Hot loop, don't check stop_requested every time + if (maybe_stop() && token.stop_requested()) return; + auto next_it = it; ++next_it; diff --git a/src/storage/v2/inmemory/label_property_index.hpp b/src/storage/v2/inmemory/label_property_index.hpp index 8bc4148bb..6ca67e1c6 100644 --- a/src/storage/v2/inmemory/label_property_index.hpp +++ b/src/storage/v2/inmemory/label_property_index.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -60,7 +60,7 @@ class InMemoryLabelPropertyIndex : public storage::LabelPropertyIndex { std::vector<std::pair<LabelId, PropertyId>> ListIndices() const override; - void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp); + void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std::stop_token token); void AbortEntries(PropertyId property, std::span<std::pair<PropertyValue, Vertex *> const> vertices, uint64_t exact_start_timestamp); diff --git a/src/storage/v2/inmemory/replication/recovery.cpp b/src/storage/v2/inmemory/replication/recovery.cpp index 536c7c8fc..d6f2b464c 100644 --- a/src/storage/v2/inmemory/replication/recovery.cpp +++ b/src/storage/v2/inmemory/replication/recovery.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -44,7 +44,7 @@ class InMemoryCurrentWalHandler { ////// CurrentWalHandler ////// InMemoryCurrentWalHandler::InMemoryCurrentWalHandler(InMemoryStorage const *storage, rpc::Client &rpc_client) - : stream_(rpc_client.Stream<replication::CurrentWalRpc>(storage->id())) {} + : stream_(rpc_client.Stream<replication::CurrentWalRpc>(storage->uuid())) {} void InMemoryCurrentWalHandler::AppendFilename(const std::string &filename) { replication::Encoder encoder(stream_.GetBuilder()); @@ -69,10 +69,10 @@ void InMemoryCurrentWalHandler::AppendBufferData(const uint8_t *buffer, const si replication::CurrentWalRes InMemoryCurrentWalHandler::Finalize() { return stream_.AwaitResponse(); } ////// ReplicationClient Helpers ////// -replication::WalFilesRes TransferWalFiles(std::string db_name, rpc::Client &client, +replication::WalFilesRes TransferWalFiles(const utils::UUID &uuid, rpc::Client &client, const std::vector<std::filesystem::path> &wal_files) { MG_ASSERT(!wal_files.empty(), "Wal files list is empty!"); - auto stream = client.Stream<replication::WalFilesRpc>(std::move(db_name), wal_files.size()); + auto stream = client.Stream<replication::WalFilesRpc>(uuid, wal_files.size()); replication::Encoder encoder(stream.GetBuilder()); for (const auto &wal : wal_files) { spdlog::debug("Sending wal file: {}", wal); @@ -81,8 +81,9 @@ replication::WalFilesRes TransferWalFiles(std::string db_name, rpc::Client &clie return stream.AwaitResponse(); } -replication::SnapshotRes TransferSnapshot(std::string db_name, rpc::Client &client, const std::filesystem::path &path) { - auto stream = client.Stream<replication::SnapshotRpc>(std::move(db_name)); +replication::SnapshotRes TransferSnapshot(const utils::UUID &uuid, rpc::Client &client, + const std::filesystem::path &path) { + auto stream = client.Stream<replication::SnapshotRpc>(uuid); replication::Encoder encoder(stream.GetBuilder()); encoder.WriteFile(path); return stream.AwaitResponse(); diff --git a/src/storage/v2/inmemory/replication/recovery.hpp b/src/storage/v2/inmemory/replication/recovery.hpp index 2025800ab..730822a62 100644 --- a/src/storage/v2/inmemory/replication/recovery.hpp +++ b/src/storage/v2/inmemory/replication/recovery.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -19,10 +19,11 @@ class InMemoryStorage; ////// ReplicationClient Helpers ////// -replication::WalFilesRes TransferWalFiles(std::string db_name, rpc::Client &client, +replication::WalFilesRes TransferWalFiles(const utils::UUID &uuid, rpc::Client &client, const std::vector<std::filesystem::path> &wal_files); -replication::SnapshotRes TransferSnapshot(std::string db_name, rpc::Client &client, const std::filesystem::path &path); +replication::SnapshotRes TransferSnapshot(const utils::UUID &uuid, rpc::Client &client, + const std::filesystem::path &path); uint64_t ReplicateCurrentWal(const InMemoryStorage *storage, rpc::Client &client, durability::WalFile const &wal_file); diff --git a/src/storage/v2/inmemory/storage.cpp b/src/storage/v2/inmemory/storage.cpp index 08aa896bf..381a67d3f 100644 --- a/src/storage/v2/inmemory/storage.cpp +++ b/src/storage/v2/inmemory/storage.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -26,6 +26,7 @@ #include "storage/v2/inmemory/replication/recovery.hpp" #include "storage/v2/inmemory/unique_constraints.hpp" #include "storage/v2/property_value.hpp" +#include "utils/atomic_memory_block.hpp" #include "utils/resource_lock.hpp" #include "utils/stat.hpp" @@ -64,14 +65,14 @@ auto FindEdges(const View view, EdgeTypeId edge_type, const VertexAccessor *from using OOMExceptionEnabler = utils::MemoryTracker::OutOfMemoryExceptionEnabler; -InMemoryStorage::InMemoryStorage(Config config, StorageMode storage_mode) - : Storage(config, storage_mode), +InMemoryStorage::InMemoryStorage(Config config) + : Storage(config, config.salient.storage_mode), recovery_{config.durability.storage_directory / durability::kSnapshotDirectory, config.durability.storage_directory / durability::kWalDirectory}, lock_file_path_(config.durability.storage_directory / durability::kLockFile), uuid_(utils::GenerateUUID()), global_locker_(file_retainer_.AddLocker()) { - MG_ASSERT(storage_mode != StorageMode::ON_DISK_TRANSACTIONAL, + MG_ASSERT(config.salient.storage_mode != StorageMode::ON_DISK_TRANSACTIONAL, "Invalid storage mode sent to InMemoryStorage constructor!"); if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED || config_.durability.snapshot_on_exit || config_.durability.recover_on_startup) { @@ -144,7 +145,6 @@ InMemoryStorage::InMemoryStorage(Config config, StorageMode storage_mode) gc_runner_.Run("Storage GC", config_.gc.interval, [this] { this->FreeMemory(std::unique_lock<utils::ResourceLock>{main_lock_, std::defer_lock}); }); - gc_jemalloc_runner_.Run("Jemalloc GC", config_.gc.interval, [] { memory::PurgeUnusedMemory(); }); } if (timestamp_ == kTimestampInitialId) { commit_log_.emplace(); @@ -153,12 +153,11 @@ InMemoryStorage::InMemoryStorage(Config config, StorageMode storage_mode) } } -InMemoryStorage::InMemoryStorage(Config config) : InMemoryStorage(config, StorageMode::IN_MEMORY_TRANSACTIONAL) {} - InMemoryStorage::~InMemoryStorage() { + stop_source.request_stop(); + if (config_.gc.type == Config::Gc::Type::PERIODIC) { gc_runner_.Stop(); - gc_jemalloc_runner_.Stop(); } { // Stop replication (Stop all clients or stop the REPLICA server) @@ -178,8 +177,10 @@ InMemoryStorage::~InMemoryStorage() { } InMemoryStorage::InMemoryAccessor::InMemoryAccessor(auto tag, InMemoryStorage *storage, IsolationLevel isolation_level, - StorageMode storage_mode, bool is_main) - : Accessor(tag, storage, isolation_level, storage_mode, is_main), config_(storage->config_.items) {} + StorageMode storage_mode, + memgraph::replication_coordination_glue::ReplicationRole replication_role) + : Accessor(tag, storage, isolation_level, storage_mode, replication_role), + config_(storage->config_.salient.items) {} InMemoryStorage::InMemoryAccessor::InMemoryAccessor(InMemoryAccessor &&other) noexcept : Accessor(std::move(other)), config_(other.config_) {} @@ -321,7 +322,7 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::CreateEdge(VertexAccesso if (to_vertex->deleted) return Error::DELETED_OBJECT; } - if (storage_->config_.items.enable_schema_metadata) { + if (storage_->config_.salient.items.enable_schema_metadata) { storage_->stored_edge_types_.try_insert(edge_type); } auto *mem_storage = static_cast<InMemoryStorage *>(storage_); @@ -338,18 +339,22 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::CreateEdge(VertexAccesso delta->prev.Set(&*it); } } + utils::AtomicMemoryBlock atomic_memory_block{ + [this, edge, from_vertex = from_vertex, edge_type = edge_type, to_vertex = to_vertex]() { + CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge); + from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge); - CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge); - from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge); + CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge); + to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge); - CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge); - to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge); + transaction_.manyDeltasCache.Invalidate(from_vertex, edge_type, EdgeDirection::OUT); + transaction_.manyDeltasCache.Invalidate(to_vertex, edge_type, EdgeDirection::IN); - transaction_.manyDeltasCache.Invalidate(from_vertex, edge_type, EdgeDirection::OUT); - transaction_.manyDeltasCache.Invalidate(to_vertex, edge_type, EdgeDirection::IN); + // Increment edge count. + storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel); + }}; - // Increment edge count. - storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel); + std::invoke(atomic_memory_block); return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, storage_, &transaction_); } @@ -406,7 +411,7 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::CreateEdgeEx(VertexAcces if (to_vertex->deleted) return Error::DELETED_OBJECT; } - if (storage_->config_.items.enable_schema_metadata) { + if (storage_->config_.salient.items.enable_schema_metadata) { storage_->stored_edge_types_.try_insert(edge_type); } @@ -433,18 +438,22 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::CreateEdgeEx(VertexAcces delta->prev.Set(&*it); } } + utils::AtomicMemoryBlock atomic_memory_block{ + [this, edge, from_vertex = from_vertex, edge_type = edge_type, to_vertex = to_vertex]() { + CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge); + from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge); - CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge); - from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge); + CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge); + to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge); - CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge); - to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge); + transaction_.manyDeltasCache.Invalidate(from_vertex, edge_type, EdgeDirection::OUT); + transaction_.manyDeltasCache.Invalidate(to_vertex, edge_type, EdgeDirection::IN); - transaction_.manyDeltasCache.Invalidate(from_vertex, edge_type, EdgeDirection::OUT); - transaction_.manyDeltasCache.Invalidate(to_vertex, edge_type, EdgeDirection::IN); + // Increment edge count. + storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel); + }}; - // Increment edge count. - storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel); + std::invoke(atomic_memory_block); return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, storage_, &transaction_); } @@ -534,18 +543,22 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::EdgeSetFrom(EdgeAccessor return Error::DELETED_OBJECT; } } + utils::AtomicMemoryBlock atomic_memory_block{ + [this, edge_ref, old_from_vertex, new_from_vertex, edge_type, to_vertex]() { + CreateAndLinkDelta(&transaction_, old_from_vertex, Delta::AddOutEdgeTag(), edge_type, to_vertex, edge_ref); + CreateAndLinkDelta(&transaction_, to_vertex, Delta::AddInEdgeTag(), edge_type, old_from_vertex, edge_ref); - CreateAndLinkDelta(&transaction_, old_from_vertex, Delta::AddOutEdgeTag(), edge_type, to_vertex, edge_ref); - CreateAndLinkDelta(&transaction_, to_vertex, Delta::AddInEdgeTag(), edge_type, old_from_vertex, edge_ref); + CreateAndLinkDelta(&transaction_, new_from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge_ref); + new_from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge_ref); + CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, new_from_vertex, edge_ref); + to_vertex->in_edges.emplace_back(edge_type, new_from_vertex, edge_ref); - CreateAndLinkDelta(&transaction_, new_from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge_ref); - new_from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge_ref); - CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, new_from_vertex, edge_ref); - to_vertex->in_edges.emplace_back(edge_type, new_from_vertex, edge_ref); + transaction_.manyDeltasCache.Invalidate(new_from_vertex, edge_type, EdgeDirection::OUT); + transaction_.manyDeltasCache.Invalidate(old_from_vertex, edge_type, EdgeDirection::OUT); + transaction_.manyDeltasCache.Invalidate(to_vertex, edge_type, EdgeDirection::IN); + }}; - transaction_.manyDeltasCache.Invalidate(new_from_vertex, edge_type, EdgeDirection::OUT); - transaction_.manyDeltasCache.Invalidate(old_from_vertex, edge_type, EdgeDirection::OUT); - transaction_.manyDeltasCache.Invalidate(to_vertex, edge_type, EdgeDirection::IN); + std::invoke(atomic_memory_block); return EdgeAccessor(edge_ref, edge_type, new_from_vertex, to_vertex, storage_, &transaction_); } @@ -636,17 +649,22 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::EdgeSetTo(EdgeAccessor * } } - CreateAndLinkDelta(&transaction_, from_vertex, Delta::AddOutEdgeTag(), edge_type, old_to_vertex, edge_ref); - CreateAndLinkDelta(&transaction_, old_to_vertex, Delta::AddInEdgeTag(), edge_type, from_vertex, edge_ref); + utils::AtomicMemoryBlock atomic_memory_block{ + [this, edge_ref, old_to_vertex, from_vertex, edge_type, new_to_vertex]() { + CreateAndLinkDelta(&transaction_, from_vertex, Delta::AddOutEdgeTag(), edge_type, old_to_vertex, edge_ref); + CreateAndLinkDelta(&transaction_, old_to_vertex, Delta::AddInEdgeTag(), edge_type, from_vertex, edge_ref); - CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, new_to_vertex, edge_ref); - from_vertex->out_edges.emplace_back(edge_type, new_to_vertex, edge_ref); - CreateAndLinkDelta(&transaction_, new_to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge_ref); - new_to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge_ref); + CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, new_to_vertex, edge_ref); + from_vertex->out_edges.emplace_back(edge_type, new_to_vertex, edge_ref); + CreateAndLinkDelta(&transaction_, new_to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge_ref); + new_to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge_ref); - transaction_.manyDeltasCache.Invalidate(from_vertex, edge_type, EdgeDirection::OUT); - transaction_.manyDeltasCache.Invalidate(old_to_vertex, edge_type, EdgeDirection::IN); - transaction_.manyDeltasCache.Invalidate(new_to_vertex, edge_type, EdgeDirection::IN); + transaction_.manyDeltasCache.Invalidate(from_vertex, edge_type, EdgeDirection::OUT); + transaction_.manyDeltasCache.Invalidate(old_to_vertex, edge_type, EdgeDirection::IN); + transaction_.manyDeltasCache.Invalidate(new_to_vertex, edge_type, EdgeDirection::IN); + }}; + + std::invoke(atomic_memory_block); return EdgeAccessor(edge_ref, edge_type, from_vertex, new_to_vertex, storage_, &transaction_); } @@ -709,24 +727,28 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::EdgeChangeType(EdgeAcces MG_ASSERT((op1 && op2), "Invalid database state!"); - // "deleting" old edge - CreateAndLinkDelta(&transaction_, from_vertex, Delta::AddOutEdgeTag(), edge_type, to_vertex, edge_ref); - CreateAndLinkDelta(&transaction_, to_vertex, Delta::AddInEdgeTag(), edge_type, from_vertex, edge_ref); + utils::AtomicMemoryBlock atomic_memory_block{[this, to_vertex, new_edge_type, edge_ref, from_vertex, edge_type]() { + // "deleting" old edge + CreateAndLinkDelta(&transaction_, from_vertex, Delta::AddOutEdgeTag(), edge_type, to_vertex, edge_ref); + CreateAndLinkDelta(&transaction_, to_vertex, Delta::AddInEdgeTag(), edge_type, from_vertex, edge_ref); - // "adding" new edge - CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), new_edge_type, to_vertex, edge_ref); - CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), new_edge_type, from_vertex, edge_ref); + // "adding" new edge + CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), new_edge_type, to_vertex, edge_ref); + CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), new_edge_type, from_vertex, edge_ref); - // edge type is not used while invalidating cache so we can only call it once - transaction_.manyDeltasCache.Invalidate(from_vertex, new_edge_type, EdgeDirection::OUT); - transaction_.manyDeltasCache.Invalidate(to_vertex, new_edge_type, EdgeDirection::IN); + // edge type is not used while invalidating cache so we can only call it once + transaction_.manyDeltasCache.Invalidate(from_vertex, new_edge_type, EdgeDirection::OUT); + transaction_.manyDeltasCache.Invalidate(to_vertex, new_edge_type, EdgeDirection::IN); + }}; + + std::invoke(atomic_memory_block); return EdgeAccessor(edge_ref, new_edge_type, from_vertex, to_vertex, storage_, &transaction_); } // NOLINTNEXTLINE(google-default-arguments) utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAccessor::Commit( - const std::optional<uint64_t> desired_commit_timestamp, bool is_main) { + CommitReplArgs reparg, DatabaseAccessProtector db_acc) { MG_ASSERT(is_transaction_active_, "The transaction is already terminated!"); MG_ASSERT(!transaction_.must_abort, "The transaction can't be committed!"); @@ -735,47 +757,14 @@ utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAcce auto *mem_storage = static_cast<InMemoryStorage *>(storage_); // TODO: duplicated transaction finalisation in md_deltas and deltas processing cases - if (!transaction_.md_deltas.empty()) { - // This is usually done by the MVCC, but it does not handle the metadata deltas - transaction_.EnsureCommitTimestampExists(); - - // Save these so we can mark them used in the commit log. - uint64_t start_timestamp = transaction_.start_timestamp; - - std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_); - commit_timestamp_.emplace(mem_storage->CommitTimestamp(desired_commit_timestamp)); - - // Write transaction to WAL while holding the engine lock to make sure - // that committed transactions are sorted by the commit timestamp in the - // WAL files. We supply the new commit timestamp to the function so that - // it knows what will be the final commit timestamp. The WAL must be - // written before actually committing the transaction (before setting - // the commit timestamp) so that no other transaction can see the - // modifications before they are written to disk. - // Replica can log only the write transaction received from Main - // so the Wal files are consistent - if (is_main || desired_commit_timestamp.has_value()) { - could_replicate_all_sync_replicas = - mem_storage->AppendToWalDataDefinition(transaction_, *commit_timestamp_); // protected by engine_guard - // TODO: release lock, and update all deltas to have a local copy of the commit timestamp - transaction_.commit_timestamp->store(*commit_timestamp_, - std::memory_order_release); // protected by engine_guard - // Replica can only update the last commit timestamp with - // the commits received from main. - if (is_main || desired_commit_timestamp.has_value()) { - // Update the last commit timestamp - mem_storage->repl_storage_state_.last_commit_timestamp_.store(*commit_timestamp_); // protected by engine_guard - } - // Release engine lock because we don't have to hold it anymore - engine_guard.unlock(); - - mem_storage->commit_log_->MarkFinished(start_timestamp); - } - } else if (transaction_.deltas.use().empty()) { + if (transaction_.deltas.use().empty() && transaction_.md_deltas.empty()) { // We don't have to update the commit timestamp here because no one reads // it. mem_storage->commit_log_->MarkFinished(transaction_.start_timestamp); } else { + // This is usually done by the MVCC, but it does not handle the metadata deltas + transaction_.EnsureCommitTimestampExists(); + if (transaction_.constraint_verification_info.NeedsExistenceConstraintVerification()) { const auto vertices_to_update = transaction_.constraint_verification_info.GetVerticesForExistenceConstraintChecking(); @@ -803,7 +792,7 @@ utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAcce std::unique_lock<utils::SpinLock> engine_guard(storage_->engine_lock_); auto *mem_unique_constraints = static_cast<InMemoryUniqueConstraints *>(storage_->constraints_.unique_constraints_.get()); - commit_timestamp_.emplace(mem_storage->CommitTimestamp(desired_commit_timestamp)); + commit_timestamp_.emplace(mem_storage->CommitTimestamp(reparg.desired_commit_timestamp)); if (transaction_.constraint_verification_info.NeedsUniqueConstraintVerification()) { // Before committing and validating vertices against unique constraints, @@ -827,6 +816,16 @@ utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAcce } if (!unique_constraint_violation) { + [[maybe_unused]] bool const is_main_or_replica_write = + reparg.IsMain() || reparg.desired_commit_timestamp.has_value(); + + // TODO Figure out if we can assert this + // DMG_ASSERT(is_main_or_replica_write, "Should only get here on writes"); + // Currently there are queries that write to some subsystem that are allowed on a replica + // ex. analyze graph stats + // There are probably others. We not to check all of them and figure out if they are allowed and what are + // they even doing here... + // Write transaction to WAL while holding the engine lock to make sure // that committed transactions are sorted by the commit timestamp in the // WAL files. We supply the new commit timestamp to the function so that @@ -836,18 +835,16 @@ utils::BasicResult<StorageManipulationError, void> InMemoryStorage::InMemoryAcce // modifications before they are written to disk. // Replica can log only the write transaction received from Main // so the Wal files are consistent - if (is_main || desired_commit_timestamp.has_value()) { - could_replicate_all_sync_replicas = - mem_storage->AppendToWalDataManipulation(transaction_, *commit_timestamp_); // protected by engine_guard - } + if (is_main_or_replica_write) { + could_replicate_all_sync_replicas = mem_storage->AppendToWal(transaction_, *commit_timestamp_, + std::move(db_acc)); // protected by engine_guard - // TODO: release lock, and update all deltas to have a local copy of the commit timestamp - MG_ASSERT(transaction_.commit_timestamp != nullptr, "Invalid database state!"); - transaction_.commit_timestamp->store(*commit_timestamp_, - std::memory_order_release); // protected by engine_guard - // Replica can only update the last commit timestamp with - // the commits received from main. - if (is_main || desired_commit_timestamp.has_value()) { + // TODO: release lock, and update all deltas to have a local copy of the commit timestamp + MG_ASSERT(transaction_.commit_timestamp != nullptr, "Invalid database state!"); + transaction_.commit_timestamp->store(*commit_timestamp_, + std::memory_order_release); // protected by engine_guard + // Replica can only update the last commit timestamp with + // the commits received from main. // Update the last commit timestamp mem_storage->repl_storage_state_.last_commit_timestamp_.store( *commit_timestamp_); // protected by engine_guard @@ -1283,7 +1280,9 @@ VerticesIterable InMemoryStorage::InMemoryAccessor::Vertices( mem_label_property_index->Vertices(label, property, lower_bound, upper_bound, view, storage_, &transaction_)); } -Transaction InMemoryStorage::CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, bool is_main) { +Transaction InMemoryStorage::CreateTransaction( + IsolationLevel isolation_level, StorageMode storage_mode, + memgraph::replication_coordination_glue::ReplicationRole replication_role) { // We acquire the transaction engine lock here because we access (and // modify) the transaction engine variables (`transaction_id` and // `timestamp`) below. @@ -1298,7 +1297,7 @@ Transaction InMemoryStorage::CreateTransaction(IsolationLevel isolation_level, S // of any query on replica to the last commited transaction // which is timestamp_ as only commit of transaction with writes // can change the value of it. - if (is_main) { + if (replication_role == memgraph::replication_coordination_glue::ReplicationRole::MAIN) { start_timestamp = timestamp_++; } else { start_timestamp = timestamp_; @@ -1579,9 +1578,12 @@ void InMemoryStorage::CollectGarbage(std::unique_lock<utils::ResourceLock> main_ if (run_index_cleanup) { // This operation is very expensive as it traverses through all of the items // in every index every time. - indices_.RemoveObsoleteEntries(oldest_active_start_timestamp); - auto *mem_unique_constraints = static_cast<InMemoryUniqueConstraints *>(constraints_.unique_constraints_.get()); - mem_unique_constraints->RemoveObsoleteEntries(oldest_active_start_timestamp); + auto token = stop_source.get_token(); + if (!token.stop_requested()) { + indices_.RemoveObsoleteEntries(oldest_active_start_timestamp, token); + auto *mem_unique_constraints = static_cast<InMemoryUniqueConstraints *>(constraints_.unique_constraints_.get()); + mem_unique_constraints->RemoveObsoleteEntries(oldest_active_start_timestamp, std::move(token)); + } } { @@ -1672,7 +1674,7 @@ StorageInfo InMemoryStorage::GetBaseInfo(bool force_directory) { --it; if (it != end && *it != "databases") { // Default DB points to the root (for back-compatibility); update to the "database" dir - return dir / "databases" / dbms::kDefaultDB; + return dir / dbms::kMultiTenantDir / dbms::kDefaultDB; } } } @@ -1682,10 +1684,11 @@ StorageInfo InMemoryStorage::GetBaseInfo(bool force_directory) { return info; } -StorageInfo InMemoryStorage::GetInfo(bool force_directory) { +StorageInfo InMemoryStorage::GetInfo(bool force_directory, + memgraph::replication_coordination_glue::ReplicationRole replication_role) { StorageInfo info = GetBaseInfo(force_directory); { - auto access = Access(std::nullopt); + auto access = Access(replication_role); // TODO: override isolation level? const auto &lbl = access->ListAllIndices(); info.label_indices = lbl.label.size(); info.label_property_indices = lbl.label_property.size(); @@ -1707,8 +1710,8 @@ bool InMemoryStorage::InitializeWalFile(memgraph::replication::ReplicationEpoch if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL) return false; if (!wal_file_) { - wal_file_.emplace(recovery_.wal_directory_, uuid_, epoch.id(), config_.items, name_id_mapper_.get(), wal_seq_num_++, - &file_retainer_); + wal_file_.emplace(recovery_.wal_directory_, uuid_, epoch.id(), config_.salient.items, name_id_mapper_.get(), + wal_seq_num_++, &file_retainer_); } return true; } @@ -1732,7 +1735,8 @@ void InMemoryStorage::FinalizeWalFile() { } } -bool InMemoryStorage::AppendToWalDataManipulation(const Transaction &transaction, uint64_t final_commit_timestamp) { +bool InMemoryStorage::AppendToWal(const Transaction &transaction, uint64_t final_commit_timestamp, + DatabaseAccessProtector db_acc) { if (!InitializeWalFile(repl_storage_state_.epoch_)) { return true; } @@ -1740,7 +1744,7 @@ bool InMemoryStorage::AppendToWalDataManipulation(const Transaction &transaction // A single transaction will always be contained in a single WAL file. auto current_commit_timestamp = transaction.commit_timestamp->load(std::memory_order_acquire); - repl_storage_state_.InitializeTransaction(wal_file_->SequenceNumber(), this); + repl_storage_state_.InitializeTransaction(wal_file_->SequenceNumber(), this, db_acc); auto append_deltas = [&](auto callback) { // Helper lambda that traverses the delta chain on order to find the first @@ -1889,26 +1893,15 @@ bool InMemoryStorage::AppendToWalDataManipulation(const Transaction &transaction } }; - append_deltas([&](const Delta &delta, const auto &parent, uint64_t timestamp) { - wal_file_->AppendDelta(delta, parent, timestamp); - repl_storage_state_.AppendDelta(delta, parent, timestamp); - }); - - // Add a delta that indicates that the transaction is fully written to the WAL - // file.replication_clients_.WithLock - wal_file_->AppendTransactionEnd(final_commit_timestamp); - FinalizeWalFile(); - - return repl_storage_state_.FinalizeTransaction(final_commit_timestamp, this); -} - -bool InMemoryStorage::AppendToWalDataDefinition(const Transaction &transaction, uint64_t final_commit_timestamp) { - if (!InitializeWalFile(repl_storage_state_.epoch_)) { - return true; + // Handle MVCC deltas + if (!transaction.deltas.use().empty()) { + append_deltas([&](const Delta &delta, const auto &parent, uint64_t timestamp) { + wal_file_->AppendDelta(delta, parent, timestamp); + repl_storage_state_.AppendDelta(delta, parent, timestamp); + }); } - repl_storage_state_.InitializeTransaction(wal_file_->SequenceNumber(), this); - + // Handle metadata deltas for (const auto &md_delta : transaction.md_deltas) { switch (md_delta.action) { case MetadataDelta::Action::LABEL_INDEX_CREATE: { @@ -1978,7 +1971,7 @@ bool InMemoryStorage::AppendToWalDataDefinition(const Transaction &transaction, wal_file_->AppendTransactionEnd(final_commit_timestamp); FinalizeWalFile(); - return repl_storage_state_.FinalizeTransaction(final_commit_timestamp, this); + return repl_storage_state_.FinalizeTransaction(final_commit_timestamp, this, std::move(db_acc)); } void InMemoryStorage::AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label, @@ -2012,11 +2005,16 @@ void InMemoryStorage::AppendToWalDataDefinition(durability::StorageMetadataOpera return AppendToWalDataDefinition(operation, label, {}, {}, final_commit_timestamp); } -utils::BasicResult<InMemoryStorage::CreateSnapshotError> InMemoryStorage::CreateSnapshot() { +utils::BasicResult<InMemoryStorage::CreateSnapshotError> InMemoryStorage::CreateSnapshot( + memgraph::replication_coordination_glue::ReplicationRole replication_role) { + if (replication_role == memgraph::replication_coordination_glue::ReplicationRole::REPLICA) { + return InMemoryStorage::CreateSnapshotError::DisabledForReplica; + } auto const &epoch = repl_storage_state_.epoch_; auto snapshot_creator = [this, &epoch]() { utils::Timer timer; - auto transaction = CreateTransaction(IsolationLevel::SNAPSHOT_ISOLATION, storage_mode_); + auto transaction = CreateTransaction(IsolationLevel::SNAPSHOT_ISOLATION, storage_mode_, + memgraph::replication_coordination_glue::ReplicationRole::MAIN); durability::CreateSnapshot(this, &transaction, recovery_.snapshot_directory_, recovery_.wal_directory_, &vertices_, &edges_, uuid_, epoch, repl_storage_state_.history, &file_retainer_); // Finalize snapshot transaction. @@ -2104,17 +2102,19 @@ utils::FileRetainer::FileLockerAccessor::ret_type InMemoryStorage::UnlockPath() return true; } -std::unique_ptr<Storage::Accessor> InMemoryStorage::Access(std::optional<IsolationLevel> override_isolation_level, - bool is_main) { +std::unique_ptr<Storage::Accessor> InMemoryStorage::Access( + memgraph::replication_coordination_glue::ReplicationRole replication_role, + std::optional<IsolationLevel> override_isolation_level) { return std::unique_ptr<InMemoryAccessor>(new InMemoryAccessor{Storage::Accessor::shared_access, this, override_isolation_level.value_or(isolation_level_), - storage_mode_, is_main}); + storage_mode_, replication_role}); } -std::unique_ptr<Storage::Accessor> InMemoryStorage::UniqueAccess(std::optional<IsolationLevel> override_isolation_level, - bool is_main) { +std::unique_ptr<Storage::Accessor> InMemoryStorage::UniqueAccess( + memgraph::replication_coordination_glue::ReplicationRole replication_role, + std::optional<IsolationLevel> override_isolation_level) { return std::unique_ptr<InMemoryAccessor>(new InMemoryAccessor{Storage::Accessor::unique_access, this, override_isolation_level.value_or(isolation_level_), - storage_mode_, is_main}); + storage_mode_, replication_role}); } void InMemoryStorage::CreateSnapshotHandler( @@ -2134,8 +2134,11 @@ void InMemoryStorage::CreateSnapshotHandler( // Run the snapshot thread (if enabled) if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED) { - snapshot_runner_.Run("Snapshot", config_.durability.snapshot_interval, - [this]() { this->create_snapshot_handler(); }); + snapshot_runner_.Run("Snapshot", config_.durability.snapshot_interval, [this, token = stop_source.get_token()]() { + if (!token.stop_requested()) { + this->create_snapshot_handler(); + } + }); } } IndicesInfo InMemoryStorage::InMemoryAccessor::ListAllIndices() const { diff --git a/src/storage/v2/inmemory/storage.hpp b/src/storage/v2/inmemory/storage.hpp index 2d2837467..6f8806c26 100644 --- a/src/storage/v2/inmemory/storage.hpp +++ b/src/storage/v2/inmemory/storage.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -60,7 +60,6 @@ class InMemoryStorage final : public Storage { /// @throw std::system_error /// @throw std::bad_alloc explicit InMemoryStorage(Config config = Config()); - InMemoryStorage(Config config, StorageMode storage_mode); InMemoryStorage(const InMemoryStorage &) = delete; InMemoryStorage(InMemoryStorage &&) = delete; @@ -74,7 +73,8 @@ class InMemoryStorage final : public Storage { friend class InMemoryStorage; explicit InMemoryAccessor(auto tag, InMemoryStorage *storage, IsolationLevel isolation_level, - StorageMode storage_mode, bool is_main = true); + StorageMode storage_mode, + memgraph::replication_coordination_glue::ReplicationRole replication_role); public: InMemoryAccessor(const InMemoryAccessor &) = delete; @@ -215,8 +215,8 @@ class InMemoryStorage final : public Storage { /// case the transaction is automatically aborted. /// @throw std::bad_alloc // NOLINTNEXTLINE(google-default-arguments) - utils::BasicResult<StorageManipulationError, void> Commit(std::optional<uint64_t> desired_commit_timestamp = {}, - bool is_main = true) override; + utils::BasicResult<StorageManipulationError, void> Commit(CommitReplArgs reparg = {}, + DatabaseAccessProtector db_acc = {}) override; /// @throw std::bad_alloc void Abort() override; @@ -302,7 +302,7 @@ class InMemoryStorage final : public Storage { /// @throw std::bad_alloc Result<EdgeAccessor> CreateEdgeEx(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type, storage::Gid gid); - Config::Items config_; + SalientConfig::Items config_; }; class ReplicationAccessor final : public InMemoryAccessor { @@ -323,12 +323,11 @@ class InMemoryStorage final : public Storage { }; using Storage::Access; - std::unique_ptr<Storage::Accessor> Access(std::optional<IsolationLevel> override_isolation_level, - bool is_main) override; - + std::unique_ptr<Accessor> Access(memgraph::replication_coordination_glue::ReplicationRole replication_role, + std::optional<IsolationLevel> override_isolation_level) override; using Storage::UniqueAccess; - std::unique_ptr<Storage::Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level, - bool is_main) override; + std::unique_ptr<Accessor> UniqueAccess(memgraph::replication_coordination_glue::ReplicationRole replication_role, + std::optional<IsolationLevel> override_isolation_level) override; void FreeMemory(std::unique_lock<utils::ResourceLock> main_guard) override; @@ -336,12 +335,13 @@ class InMemoryStorage final : public Storage { utils::FileRetainer::FileLockerAccessor::ret_type LockPath(); utils::FileRetainer::FileLockerAccessor::ret_type UnlockPath(); - utils::BasicResult<InMemoryStorage::CreateSnapshotError> CreateSnapshot(); + utils::BasicResult<InMemoryStorage::CreateSnapshotError> CreateSnapshot( + memgraph::replication_coordination_glue::ReplicationRole replication_role); void CreateSnapshotHandler(std::function<utils::BasicResult<InMemoryStorage::CreateSnapshotError>()> cb); - using Storage::CreateTransaction; - Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, bool is_main) override; + Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, + memgraph::replication_coordination_glue::ReplicationRole replication_role) override; void SetStorageMode(StorageMode storage_mode); @@ -366,12 +366,12 @@ class InMemoryStorage final : public Storage { void FinalizeWalFile(); StorageInfo GetBaseInfo(bool force_directory) override; - StorageInfo GetInfo(bool force_directory) override; + StorageInfo GetInfo(bool force_directory, + memgraph::replication_coordination_glue::ReplicationRole replication_role) override; /// Return true in all cases excepted if any sync replicas have not sent confirmation. - [[nodiscard]] bool AppendToWalDataManipulation(const Transaction &transaction, uint64_t final_commit_timestamp); - /// Return true in all cases excepted if any sync replicas have not sent confirmation. - [[nodiscard]] bool AppendToWalDataDefinition(const Transaction &transaction, uint64_t final_commit_timestamp); + [[nodiscard]] bool AppendToWal(const Transaction &transaction, uint64_t final_commit_timestamp, + DatabaseAccessProtector db_acc); /// Return true in all cases excepted if any sync replicas have not sent confirmation. void AppendToWalDataDefinition(durability::StorageMetadataOperation operation, LabelId label, uint64_t final_commit_timestamp); @@ -427,7 +427,6 @@ class InMemoryStorage final : public Storage { std::optional<CommitLog> commit_log_; utils::Scheduler gc_runner_; - utils::Scheduler gc_jemalloc_runner_; std::mutex gc_lock_; using BondPmrLd = Bond<utils::pmr::list<Delta>>; @@ -463,6 +462,9 @@ class InMemoryStorage final : public Storage { // Moved the create snapshot to a user defined handler so we can remove the global replication state from the storage std::function<void()> create_snapshot_handler{}; + + // A way to tell async operation to stop + std::stop_source stop_source; }; } // namespace memgraph::storage diff --git a/src/storage/v2/inmemory/unique_constraints.cpp b/src/storage/v2/inmemory/unique_constraints.cpp index 76cda1730..667d0229f 100644 --- a/src/storage/v2/inmemory/unique_constraints.cpp +++ b/src/storage/v2/inmemory/unique_constraints.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -15,6 +15,7 @@ #include "storage/v2/constraints/utils.hpp" #include "storage/v2/durability/recovery_type.hpp" #include "storage/v2/id_types.hpp" +#include "utils/counter.hpp" #include "utils/logging.hpp" #include "utils/skip_list.hpp" namespace memgraph::storage { @@ -487,10 +488,18 @@ std::vector<std::pair<LabelId, std::set<PropertyId>>> InMemoryUniqueConstraints: return ret; } -void InMemoryUniqueConstraints::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) { +void InMemoryUniqueConstraints::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std::stop_token token) { + auto maybe_stop = utils::ResettableCounter<2048>(); + for (auto &[label_props, storage] : constraints_) { + // before starting constraint, check if stop_requested + if (token.stop_requested()) return; + auto acc = storage.access(); for (auto it = acc.begin(); it != acc.end();) { + // Hot loop, don't check stop_requested every time + if (maybe_stop() && token.stop_requested()) return; + auto next_it = it; ++next_it; diff --git a/src/storage/v2/inmemory/unique_constraints.hpp b/src/storage/v2/inmemory/unique_constraints.hpp index 15107f131..27fae1b30 100644 --- a/src/storage/v2/inmemory/unique_constraints.hpp +++ b/src/storage/v2/inmemory/unique_constraints.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -122,7 +122,7 @@ class InMemoryUniqueConstraints : public UniqueConstraints { std::vector<std::pair<LabelId, std::set<PropertyId>>> ListConstraints() const override; /// GC method that removes outdated entries from constraints' storages. - void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp); + void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp, std::stop_token token); void Clear() override; diff --git a/src/storage/v2/property_store.cpp b/src/storage/v2/property_store.cpp index 530d5f5f6..427998fbe 100644 --- a/src/storage/v2/property_store.cpp +++ b/src/storage/v2/property_store.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -1050,14 +1050,11 @@ bool PropertyStore::HasProperty(PropertyId property) const { return ExistsSpecificProperty(&reader, property) == ExpectedPropertyStatus::EQUAL; } -/// TODO: andi write a unit test for it bool PropertyStore::HasAllProperties(const std::set<PropertyId> &properties) const { return std::all_of(properties.begin(), properties.end(), [this](const auto &prop) { return HasProperty(prop); }); } -/// TODO: andi write a unit test for it bool PropertyStore::HasAllPropertyValues(const std::vector<PropertyValue> &property_values) const { - /// TODO: andi extract this into a private method auto property_map = Properties(); std::vector<PropertyValue> all_property_values; transform(property_map.begin(), property_map.end(), back_inserter(all_property_values), diff --git a/src/storage/v2/property_store.hpp b/src/storage/v2/property_store.hpp index 6a458641b..c217cbd81 100644 --- a/src/storage/v2/property_store.hpp +++ b/src/storage/v2/property_store.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -55,7 +55,6 @@ class PropertyStore { /// Checks whether all property values in the vector `property_values` exist in the store. The time /// complexity of this function is O(n^2). - /// TODO: andi Not so sure it is quadratic complexity bool HasAllPropertyValues(const std::vector<PropertyValue> &property_values) const; /// Extracts property values for all property ids in the set `properties`. The time diff --git a/src/storage/v2/property_value.hpp b/src/storage/v2/property_value.hpp index 05ab1d3db..727c75377 100644 --- a/src/storage/v2/property_value.hpp +++ b/src/storage/v2/property_value.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -123,21 +123,21 @@ class PropertyValue { // value getters for primitive types /// @throw PropertyValueException if value isn't of correct type. bool ValueBool() const { - if (type_ != Type::Bool) { + if (type_ != Type::Bool) [[unlikely]] { throw PropertyValueException("The value isn't a bool!"); } return bool_v; } /// @throw PropertyValueException if value isn't of correct type. int64_t ValueInt() const { - if (type_ != Type::Int) { + if (type_ != Type::Int) [[unlikely]] { throw PropertyValueException("The value isn't an int!"); } return int_v; } /// @throw PropertyValueException if value isn't of correct type. double ValueDouble() const { - if (type_ != Type::Double) { + if (type_ != Type::Double) [[unlikely]] { throw PropertyValueException("The value isn't a double!"); } return double_v; @@ -145,7 +145,7 @@ class PropertyValue { /// @throw PropertyValueException if value isn't of correct type. TemporalData ValueTemporalData() const { - if (type_ != Type::TemporalData) { + if (type_ != Type::TemporalData) [[unlikely]] { throw PropertyValueException("The value isn't a temporal data!"); } @@ -155,7 +155,7 @@ class PropertyValue { // const value getters for non-primitive types /// @throw PropertyValueException if value isn't of correct type. const std::string &ValueString() const { - if (type_ != Type::String) { + if (type_ != Type::String) [[unlikely]] { throw PropertyValueException("The value isn't a string!"); } return string_v; @@ -163,7 +163,7 @@ class PropertyValue { /// @throw PropertyValueException if value isn't of correct type. const std::vector<PropertyValue> &ValueList() const { - if (type_ != Type::List) { + if (type_ != Type::List) [[unlikely]] { throw PropertyValueException("The value isn't a list!"); } return list_v; @@ -171,7 +171,7 @@ class PropertyValue { /// @throw PropertyValueException if value isn't of correct type. const std::map<std::string, PropertyValue> &ValueMap() const { - if (type_ != Type::Map) { + if (type_ != Type::Map) [[unlikely]] { throw PropertyValueException("The value isn't a map!"); } return map_v; @@ -180,7 +180,7 @@ class PropertyValue { // reference value getters for non-primitive types /// @throw PropertyValueException if value isn't of correct type. std::string &ValueString() { - if (type_ != Type::String) { + if (type_ != Type::String) [[unlikely]] { throw PropertyValueException("The value isn't a string!"); } return string_v; @@ -188,7 +188,7 @@ class PropertyValue { /// @throw PropertyValueException if value isn't of correct type. std::vector<PropertyValue> &ValueList() { - if (type_ != Type::List) { + if (type_ != Type::List) [[unlikely]] { throw PropertyValueException("The value isn't a list!"); } return list_v; @@ -196,7 +196,7 @@ class PropertyValue { /// @throw PropertyValueException if value isn't of correct type. std::map<std::string, PropertyValue> &ValueMap() { - if (type_ != Type::Map) { + if (type_ != Type::Map) [[unlikely]] { throw PropertyValueException("The value isn't a map!"); } return map_v; @@ -279,7 +279,7 @@ inline bool operator==(const PropertyValue &first, const PropertyValue &second) case PropertyValue::Type::Bool: return first.ValueBool() == second.ValueBool(); case PropertyValue::Type::Int: - if (second.type() == PropertyValue::Type::Double) { + if (second.type() == PropertyValue::Type::Double) [[unlikely]] { return first.ValueInt() == second.ValueDouble(); } else { return first.ValueInt() == second.ValueInt(); @@ -310,7 +310,7 @@ inline bool operator<(const PropertyValue &first, const PropertyValue &second) n case PropertyValue::Type::Bool: return first.ValueBool() < second.ValueBool(); case PropertyValue::Type::Int: - if (second.type() == PropertyValue::Type::Double) { + if (second.type() == PropertyValue::Type::Double) [[unlikely]] { return first.ValueInt() < second.ValueDouble(); } else { return first.ValueInt() < second.ValueInt(); @@ -363,36 +363,35 @@ inline PropertyValue::PropertyValue(const PropertyValue &other) : type_(other.ty } } -inline PropertyValue::PropertyValue(PropertyValue &&other) noexcept : type_(other.type_) { - switch (other.type_) { +inline PropertyValue::PropertyValue(PropertyValue &&other) noexcept : type_(std::exchange(other.type_, Type::Null)) { + switch (type_) { case Type::Null: break; case Type::Bool: - this->bool_v = other.bool_v; + bool_v = other.bool_v; break; case Type::Int: - this->int_v = other.int_v; + int_v = other.int_v; break; case Type::Double: - this->double_v = other.double_v; + double_v = other.double_v; break; case Type::String: - new (&string_v) std::string(std::move(other.string_v)); + std::construct_at(&string_v, std::move(other.string_v)); + std::destroy_at(&other.string_v); break; case Type::List: - new (&list_v) std::vector<PropertyValue>(std::move(other.list_v)); + std::construct_at(&list_v, std::move(other.list_v)); + std::destroy_at(&other.list_v); break; case Type::Map: - new (&map_v) std::map<std::string, PropertyValue>(std::move(other.map_v)); + std::construct_at(&map_v, std::move(other.map_v)); + std::destroy_at(&other.map_v); break; case Type::TemporalData: - this->temporal_data_v = other.temporal_data_v; + temporal_data_v = other.temporal_data_v; break; } - - // reset the type of other - other.DestroyValue(); - other.type_ = Type::Null; } inline PropertyValue &PropertyValue::operator=(const PropertyValue &other) { @@ -431,46 +430,48 @@ inline PropertyValue &PropertyValue::operator=(const PropertyValue &other) { } inline PropertyValue &PropertyValue::operator=(PropertyValue &&other) noexcept { - if (this == &other) return *this; + if (type_ == other.type_) { + // maybe the same object, check if no work is required + if (this == &other) return *this; - DestroyValue(); - type_ = other.type_; - - switch (other.type_) { - case Type::Null: - break; - case Type::Bool: - this->bool_v = other.bool_v; - break; - case Type::Int: - this->int_v = other.int_v; - break; - case Type::Double: - this->double_v = other.double_v; - break; - case Type::String: - new (&string_v) std::string(std::move(other.string_v)); - break; - case Type::List: - new (&list_v) std::vector<PropertyValue>(std::move(other.list_v)); - break; - case Type::Map: - new (&map_v) std::map<std::string, PropertyValue>(std::move(other.map_v)); - break; - case Type::TemporalData: - this->temporal_data_v = other.temporal_data_v; - break; + switch (type_) { + case Type::Null: + break; + case Type::Bool: + bool_v = other.bool_v; + break; + case Type::Int: + int_v = other.int_v; + break; + case Type::Double: + double_v = other.double_v; + break; + case Type::String: + string_v = std::move(other.string_v); + std::destroy_at(&other.string_v); + break; + case Type::List: + list_v = std::move(other.list_v); + std::destroy_at(&other.list_v); + break; + case Type::Map: + map_v = std::move(other.map_v); + std::destroy_at(&other.map_v); + break; + case Type::TemporalData: + temporal_data_v = other.temporal_data_v; + break; + } + other.type_ = Type::Null; + return *this; + } else { + std::destroy_at(this); + return *std::construct_at(std::launder(this), std::move(other)); } - - // reset the type of other - other.DestroyValue(); - other.type_ = Type::Null; - - return *this; } inline void PropertyValue::DestroyValue() noexcept { - switch (type_) { + switch (std::exchange(type_, Type::Null)) { // destructor for primitive types does nothing case Type::Null: case Type::Bool: diff --git a/src/storage/v2/replication/global.hpp b/src/storage/v2/replication/global.hpp index 7892fb990..ebcec1206 100644 --- a/src/storage/v2/replication/global.hpp +++ b/src/storage/v2/replication/global.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -31,7 +31,7 @@ struct TimestampInfo { struct ReplicaInfo { std::string name; - memgraph::replication::ReplicationMode mode; + replication_coordination_glue::ReplicationMode mode; io::network::Endpoint endpoint; replication::ReplicaState state; TimestampInfo timestamp_info; diff --git a/src/storage/v2/replication/replication_client.cpp b/src/storage/v2/replication/replication_client.cpp index 3bc1b3d32..b68618e04 100644 --- a/src/storage/v2/replication/replication_client.cpp +++ b/src/storage/v2/replication/replication_client.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -10,14 +10,13 @@ // licenses/APL.txt. #include "replication/replication_client.hpp" -#include "storage/v2/durability/durability.hpp" #include "storage/v2/inmemory/storage.hpp" #include "storage/v2/storage.hpp" #include "utils/exceptions.hpp" +#include "utils/on_scope_exit.hpp" #include "utils/variant_helpers.hpp" #include <algorithm> -#include <type_traits> namespace { template <typename> @@ -29,14 +28,26 @@ namespace memgraph::storage { ReplicationStorageClient::ReplicationStorageClient(::memgraph::replication::ReplicationClient &client) : client_{client} {} -void ReplicationStorageClient::CheckReplicaState(Storage *storage) { +void ReplicationStorageClient::UpdateReplicaState(Storage *storage, DatabaseAccessProtector db_acc) { uint64_t current_commit_timestamp{kTimestampInitialId}; auto &replStorageState = storage->repl_storage_state_; - auto stream{client_.rpc_client_.Stream<replication::HeartbeatRpc>( - storage->id(), replStorageState.last_commit_timestamp_, std::string{replStorageState.epoch_.id()})}; - const auto replica = stream.AwaitResponse(); + auto hb_stream{client_.rpc_client_.Stream<replication::HeartbeatRpc>( + storage->uuid(), replStorageState.last_commit_timestamp_, std::string{replStorageState.epoch_.id()})}; + const auto replica = hb_stream.AwaitResponse(); + +#ifdef MG_ENTERPRISE // Multi-tenancy is only supported in enterprise + if (!replica.success) { // Replica is missing the current database + client_.state_.WithLock([&](auto &state) { + spdlog::debug("Replica '{}' missing database '{}' - '{}'", client_.name_, storage->name(), + std::string{storage->uuid()}); + state = memgraph::replication::ReplicationClient::State::BEHIND; + }); + return; + } +#endif + std::optional<uint64_t> branching_point; if (replica.epoch_id != replStorageState.epoch_.id() && replica.current_commit_timestamp != kTimestampInitialId) { auto const &history = replStorageState.history; @@ -56,6 +67,7 @@ void ReplicationStorageClient::CheckReplicaState(Storage *storage) { "now hold unique data. Please resolve data conflicts and start the " "replication on a clean instance.", client_.name_, client_.name_, client_.name_); + // TODO: (andi) Talk about renaming MAYBE_BEHIND to branching // State not updated, hence in MAYBE_BEHIND state return; } @@ -70,8 +82,9 @@ void ReplicationStorageClient::CheckReplicaState(Storage *storage) { } else { spdlog::debug("Replica '{}' is behind", client_.name_); state = replication::ReplicaState::RECOVERY; - client_.thread_pool_.AddTask( - [storage, current_commit_timestamp, this] { this->RecoverReplica(current_commit_timestamp, storage); }); + client_.thread_pool_.AddTask([storage, current_commit_timestamp, gk = std::move(db_acc), this] { + this->RecoverReplica(current_commit_timestamp, storage); + }); } }); } @@ -82,16 +95,18 @@ TimestampInfo ReplicationStorageClient::GetTimestampInfo(Storage const *storage) info.current_number_of_timestamp_behind_master = 0; try { - auto stream{client_.rpc_client_.Stream<replication::TimestampRpc>(storage->id())}; + auto stream{client_.rpc_client_.Stream<replication::TimestampRpc>(storage->uuid())}; const auto response = stream.AwaitResponse(); const auto is_success = response.success; - if (!is_success) { - replica_state_.WithLock([](auto &val) { val = replication::ReplicaState::MAYBE_BEHIND; }); - LogRpcFailure(); - } + auto main_time_stamp = storage->repl_storage_state_.last_commit_timestamp_.load(); info.current_timestamp_of_replica = response.current_commit_timestamp; info.current_number_of_timestamp_behind_master = response.current_commit_timestamp - main_time_stamp; + + if (!is_success || info.current_number_of_timestamp_behind_master != 0) { + replica_state_.WithLock([](auto &val) { val = replication::ReplicaState::MAYBE_BEHIND; }); + LogRpcFailure(); + } } catch (const rpc::RpcFailedException &) { replica_state_.WithLock([](auto &val) { val = replication::ReplicaState::MAYBE_BEHIND; }); LogRpcFailure(); // mutex already unlocked, if the new enqueued task dispatches immediately it probably @@ -106,13 +121,15 @@ void ReplicationStorageClient::LogRpcFailure() { utils::MessageWithLink("Couldn't replicate data to {}.", client_.name_, "https://memgr.ph/replication")); } -void ReplicationStorageClient::TryCheckReplicaStateAsync(Storage *storage) { - client_.thread_pool_.AddTask([storage, this] { this->TryCheckReplicaStateSync(storage); }); +void ReplicationStorageClient::TryCheckReplicaStateAsync(Storage *storage, DatabaseAccessProtector db_acc) { + client_.thread_pool_.AddTask([storage, db_acc = std::move(db_acc), this]() mutable { + this->TryCheckReplicaStateSync(storage, std::move(db_acc)); + }); } -void ReplicationStorageClient::TryCheckReplicaStateSync(Storage *storage) { +void ReplicationStorageClient::TryCheckReplicaStateSync(Storage *storage, DatabaseAccessProtector db_acc) { try { - CheckReplicaState(storage); + UpdateReplicaState(storage, std::move(db_acc)); } catch (const rpc::VersionMismatchRpcFailedException &) { replica_state_.WithLock([](auto &val) { val = replication::ReplicaState::MAYBE_BEHIND; }); spdlog::error( @@ -126,7 +143,8 @@ void ReplicationStorageClient::TryCheckReplicaStateSync(Storage *storage) { } } -void ReplicationStorageClient::StartTransactionReplication(const uint64_t current_wal_seq_num, Storage *storage) { +void ReplicationStorageClient::StartTransactionReplication(const uint64_t current_wal_seq_num, Storage *storage, + DatabaseAccessProtector db_acc) { auto locked_state = replica_state_.Lock(); switch (*locked_state) { using enum replication::ReplicaState; @@ -150,7 +168,7 @@ void ReplicationStorageClient::StartTransactionReplication(const uint64_t curren case MAYBE_BEHIND: spdlog::error( utils::MessageWithLink("Couldn't replicate data to {}.", client_.name_, "https://memgr.ph/replication")); - TryCheckReplicaStateAsync(storage); + TryCheckReplicaStateAsync(storage, std::move(db_acc)); return; case READY: MG_ASSERT(!replica_stream_); @@ -165,7 +183,7 @@ void ReplicationStorageClient::StartTransactionReplication(const uint64_t curren } } -bool ReplicationStorageClient::FinalizeTransactionReplication(Storage *storage) { +bool ReplicationStorageClient::FinalizeTransactionReplication(Storage *storage, DatabaseAccessProtector db_acc) { // We can only check the state because it guarantees to be only // valid during a single transaction replication (if the assumption // that this and other transaction replication functions can only be @@ -174,18 +192,26 @@ bool ReplicationStorageClient::FinalizeTransactionReplication(Storage *storage) return false; } - if (replica_stream_->IsDefunct()) return false; + if (!replica_stream_ || replica_stream_->IsDefunct()) { + replica_state_.WithLock([this](auto &state) { + replica_stream_.reset(); + state = replication::ReplicaState::MAYBE_BEHIND; + }); + LogRpcFailure(); + return false; + } - auto task = [storage, this]() { + auto task = [storage, db_acc = std::move(db_acc), this]() mutable { MG_ASSERT(replica_stream_, "Missing stream for transaction deltas"); try { auto response = replica_stream_->Finalize(); - return replica_state_.WithLock([storage, &response, this](auto &state) { + return replica_state_.WithLock([storage, &response, db_acc = std::move(db_acc), this](auto &state) mutable { replica_stream_.reset(); if (!response.success || state == replication::ReplicaState::RECOVERY) { state = replication::ReplicaState::RECOVERY; - client_.thread_pool_.AddTask( - [storage, &response, this] { this->RecoverReplica(response.current_commit_timestamp, storage); }); + client_.thread_pool_.AddTask([storage, &response, db_acc = std::move(db_acc), this] { + this->RecoverReplica(response.current_commit_timestamp, storage); + }); return false; } state = replication::ReplicaState::READY; @@ -201,17 +227,17 @@ bool ReplicationStorageClient::FinalizeTransactionReplication(Storage *storage) } }; - if (client_.mode_ == memgraph::replication::ReplicationMode::ASYNC) { - client_.thread_pool_.AddTask([task = std::move(task)] { (void)task(); }); + if (client_.mode_ == replication_coordination_glue::ReplicationMode::ASYNC) { + client_.thread_pool_.AddTask([task = std::move(task)]() mutable { (void)task(); }); return true; } return task(); } -void ReplicationStorageClient::Start(Storage *storage) { - spdlog::trace("Replication client started for database \"{}\"", storage->id()); - TryCheckReplicaStateSync(storage); +void ReplicationStorageClient::Start(Storage *storage, DatabaseAccessProtector db_acc) { + spdlog::trace("Replication client started for database \"{}\"", storage->name()); + TryCheckReplicaStateSync(storage, std::move(db_acc)); } void ReplicationStorageClient::RecoverReplica(uint64_t replica_commit, memgraph::storage::Storage *storage) { @@ -233,12 +259,12 @@ void ReplicationStorageClient::RecoverReplica(uint64_t replica_commit, memgraph: std::visit(utils::Overloaded{ [&replica_commit, mem_storage, &rpcClient](RecoverySnapshot const &snapshot) { spdlog::debug("Sending the latest snapshot file: {}", snapshot); - auto response = TransferSnapshot(mem_storage->id(), rpcClient, snapshot); + auto response = TransferSnapshot(mem_storage->uuid(), rpcClient, snapshot); replica_commit = response.current_commit_timestamp; }, [&replica_commit, mem_storage, &rpcClient](RecoveryWals const &wals) { spdlog::debug("Sending the latest wal files"); - auto response = TransferWalFiles(mem_storage->id(), rpcClient, wals); + auto response = TransferWalFiles(mem_storage->uuid(), rpcClient, wals); replica_commit = response.current_commit_timestamp; spdlog::debug("Wal files successfully transferred."); }, @@ -246,11 +272,11 @@ void ReplicationStorageClient::RecoverReplica(uint64_t replica_commit, memgraph: std::unique_lock transaction_guard(mem_storage->engine_lock_); if (mem_storage->wal_file_ && mem_storage->wal_file_->SequenceNumber() == current_wal.current_wal_seq_num) { + utils::OnScopeExit on_exit([mem_storage]() { mem_storage->wal_file_->EnableFlushing(); }); mem_storage->wal_file_->DisableFlushing(); transaction_guard.unlock(); spdlog::debug("Sending current wal file"); replica_commit = ReplicateCurrentWal(mem_storage, rpcClient, *mem_storage->wal_file_); - mem_storage->wal_file_->EnableFlushing(); } else { spdlog::debug("Cannot recover using current wal file"); } @@ -291,14 +317,14 @@ void ReplicationStorageClient::RecoverReplica(uint64_t replica_commit, memgraph: ReplicaStream::ReplicaStream(Storage *storage, rpc::Client &rpc_client, const uint64_t current_seq_num) : storage_{storage}, stream_(rpc_client.Stream<replication::AppendDeltasRpc>( - storage->id(), storage->repl_storage_state_.last_commit_timestamp_.load(), current_seq_num)) { + storage->uuid(), storage->repl_storage_state_.last_commit_timestamp_.load(), current_seq_num)) { replication::Encoder encoder{stream_.GetBuilder()}; encoder.WriteString(storage->repl_storage_state_.epoch_.id()); } void ReplicaStream::AppendDelta(const Delta &delta, const Vertex &vertex, uint64_t final_commit_timestamp) { replication::Encoder encoder(stream_.GetBuilder()); - EncodeDelta(&encoder, storage_->name_id_mapper_.get(), storage_->config_.items, delta, vertex, + EncodeDelta(&encoder, storage_->name_id_mapper_.get(), storage_->config_.salient.items, delta, vertex, final_commit_timestamp); } diff --git a/src/storage/v2/replication/replication_client.hpp b/src/storage/v2/replication/replication_client.hpp index 3d2c019e9..fbcffe422 100644 --- a/src/storage/v2/replication/replication_client.hpp +++ b/src/storage/v2/replication/replication_client.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -13,9 +13,10 @@ #include "replication/config.hpp" #include "replication/epoch.hpp" -#include "replication/messages.hpp" #include "replication/replication_client.hpp" +#include "replication_coordination_glue/messages.hpp" #include "rpc/client.hpp" +#include "storage/v2/database_access.hpp" #include "storage/v2/durability/storage_global_operation.hpp" #include "storage/v2/id_types.hpp" #include "storage/v2/indices/label_index_stats.hpp" @@ -93,20 +94,34 @@ class ReplicationStorageClient { ~ReplicationStorageClient() = default; // TODO Remove the client related functions - auto Mode() const -> memgraph::replication::ReplicationMode { return client_.mode_; } + auto Mode() const -> memgraph::replication_coordination_glue::ReplicationMode { return client_.mode_; } auto Name() const -> std::string const & { return client_.name_; } auto Endpoint() const -> io::network::Endpoint const & { return client_.rpc_client_.Endpoint(); } auto State() const -> replication::ReplicaState { return replica_state_.WithLock(std::identity()); } auto GetTimestampInfo(Storage const *storage) -> TimestampInfo; - void Start(Storage *storage); - void StartTransactionReplication(uint64_t current_wal_seq_num, Storage *storage); + /** + * @brief Check the replica state + * + * @param storage pointer to the storage associated with the client + * @param gk gatekeeper access that protects the database; std::any to have separation between dbms and storage + */ + void Start(Storage *storage, DatabaseAccessProtector db_acc); + + /** + * @brief Start a new transaction replication (open up a stream) + * + * @param current_wal_seq_num + * @param storage pointer to the storage associated with the client + * @param gk gatekeeper access that protects the database; std::any to have separation between dbms and storage + */ + void StartTransactionReplication(uint64_t current_wal_seq_num, Storage *storage, DatabaseAccessProtector db_acc); // Replication clients can be removed at any point // so to avoid any complexity of checking if the client was removed whenever // we want to send part of transaction and to avoid adding some GC logic this - // function will run a callback if, after previously callling + // function will run a callback if, after previously calling // StartTransactionReplication, stream is created. template <InvocableWithStream F> void IfStreamingTransaction(F &&callback) { @@ -117,26 +132,69 @@ class ReplicationStorageClient { if (State() != replication::ReplicaState::REPLICATING) { return; } - if (replica_stream_->IsDefunct()) return; + if (!replica_stream_ || replica_stream_->IsDefunct()) { + replica_state_.WithLock([this](auto &state) { + replica_stream_.reset(); + state = replication::ReplicaState::MAYBE_BEHIND; + }); + LogRpcFailure(); + return; + } try { callback(*replica_stream_); // failure state what if not streaming (std::nullopt) } catch (const rpc::RpcFailedException &) { - return replica_state_.WithLock([](auto &state) { state = replication::ReplicaState::MAYBE_BEHIND; }); + replica_state_.WithLock([](auto &state) { state = replication::ReplicaState::MAYBE_BEHIND; }); LogRpcFailure(); + return; } } - // Return whether the transaction could be finalized on the replication client or not. - [[nodiscard]] bool FinalizeTransactionReplication(Storage *storage); + /** + * @brief Return whether the transaction could be finalized on the replication client or not. + * + * @param storage pointer to the storage associated with the client + * @param gk gatekeeper access that protects the database; std::any to have separation between dbms and storage + * @return true + * @return false + */ + [[nodiscard]] bool FinalizeTransactionReplication(Storage *storage, DatabaseAccessProtector db_acc); + + /** + * @brief Asynchronously try to check the replica state and start a recovery thread if necessary + * + * @param storage pointer to the storage associated with the client + * @param gk gatekeeper access that protects the database; std::any to have separation between dbms and storage + */ + void TryCheckReplicaStateAsync(Storage *storage, DatabaseAccessProtector db_acc); // TODO Move back to private + + auto &Client() { return client_; } - void TryCheckReplicaStateAsync(Storage *storage); // TODO Move back to private private: + /** + * @brief Get necessary recovery steps and execute them. + * + * @param replica_commit the commit up to which we should recover to + * @param gk gatekeeper access that protects the database; std::any to have separation between dbms and storage + */ void RecoverReplica(uint64_t replica_commit, memgraph::storage::Storage *storage); - void CheckReplicaState(Storage *storage); + /** + * @brief Check replica state + * + * @param storage pointer to the storage associated with the client + * @param gk gatekeeper access that protects the database; std::any to have separation between dbms and storage + */ + void UpdateReplicaState(Storage *storage, DatabaseAccessProtector db_acc); + void LogRpcFailure(); - void TryCheckReplicaStateSync(Storage *storage); - void FrequentCheck(Storage *storage); + + /** + * @brief Synchronously try to check the replica state and start a recovery thread if necessary + * + * @param storage pointer to the storage associated with the client + * @param gk gatekeeper access that protects the database; std::any to have separation between dbms and storage + */ + void TryCheckReplicaStateSync(Storage *storage, DatabaseAccessProtector db_acc); ::memgraph::replication::ReplicationClient &client_; // TODO Do not store the stream, make is a local variable diff --git a/src/storage/v2/replication/replication_storage_state.cpp b/src/storage/v2/replication/replication_storage_state.cpp index a443c7171..25cf484c9 100644 --- a/src/storage/v2/replication/replication_storage_state.cpp +++ b/src/storage/v2/replication/replication_storage_state.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -16,10 +16,11 @@ namespace memgraph::storage { -void ReplicationStorageState::InitializeTransaction(uint64_t seq_num, Storage *storage) { - replication_clients_.WithLock([=](auto &clients) { +void ReplicationStorageState::InitializeTransaction(uint64_t seq_num, Storage *storage, + DatabaseAccessProtector db_acc) { + replication_clients_.WithLock([=, db_acc = std::move(db_acc)](auto &clients) mutable { for (auto &client : clients) { - client->StartTransactionReplication(seq_num, storage); + client->StartTransactionReplication(seq_num, storage, std::move(db_acc)); } }); } @@ -52,14 +53,18 @@ void ReplicationStorageState::AppendOperation(durability::StorageMetadataOperati }); } -bool ReplicationStorageState::FinalizeTransaction(uint64_t timestamp, Storage *storage) { - return replication_clients_.WithLock([=](auto &clients) { +bool ReplicationStorageState::FinalizeTransaction(uint64_t timestamp, Storage *storage, + DatabaseAccessProtector db_acc) { + return replication_clients_.WithLock([=, db_acc = std::move(db_acc)](auto &clients) mutable { bool finalized_on_all_replicas = true; + MG_ASSERT(clients.empty() || db_acc.has_value(), + "Any clients assumes we are MAIN, we should have gatekeeper_access_wrapper so we can correctly " + "handle ASYNC tasks"); for (ReplicationClientPtr &client : clients) { client->IfStreamingTransaction([&](auto &stream) { stream.AppendTransactionEnd(timestamp); }); - const auto finalized = client->FinalizeTransactionReplication(storage); + const auto finalized = client->FinalizeTransactionReplication(storage, std::move(db_acc)); - if (client->Mode() == memgraph::replication::ReplicationMode::SYNC) { + if (client->Mode() == replication_coordination_glue::ReplicationMode::SYNC) { finalized_on_all_replicas = finalized && finalized_on_all_replicas; } } @@ -83,7 +88,8 @@ std::vector<ReplicaInfo> ReplicationStorageState::ReplicasInfo(const Storage *st std::vector<ReplicaInfo> replica_infos; replica_infos.reserve(clients.size()); auto const asReplicaInfo = [storage](ReplicationClientPtr const &client) -> ReplicaInfo { - return {client->Name(), client->Mode(), client->Endpoint(), client->State(), client->GetTimestampInfo(storage)}; + const auto ts = client->GetTimestampInfo(storage); + return {client->Name(), client->Mode(), client->Endpoint(), client->State(), ts}; }; std::transform(clients.begin(), clients.end(), std::back_inserter(replica_infos), asReplicaInfo); return replica_infos; diff --git a/src/storage/v2/replication/replication_storage_state.hpp b/src/storage/v2/replication/replication_storage_state.hpp index e3d6b94a0..adbf87aa9 100644 --- a/src/storage/v2/replication/replication_storage_state.hpp +++ b/src/storage/v2/replication/replication_storage_state.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -25,6 +25,7 @@ #include "replication/config.hpp" #include "replication/epoch.hpp" #include "replication/state.hpp" +#include "storage/v2/database_access.hpp" #include "storage/v2/replication/enums.hpp" #include "storage/v2/replication/global.hpp" #include "storage/v2/replication/rpc.hpp" @@ -39,13 +40,13 @@ class ReplicationStorageClient; struct ReplicationStorageState { // Only MAIN can send - void InitializeTransaction(uint64_t seq_num, Storage *storage); + void InitializeTransaction(uint64_t seq_num, Storage *storage, DatabaseAccessProtector db_acc); void AppendDelta(const Delta &delta, const Vertex &vertex, uint64_t timestamp); void AppendDelta(const Delta &delta, const Edge &edge, uint64_t timestamp); void AppendOperation(durability::StorageMetadataOperation operation, LabelId label, const std::set<PropertyId> &properties, const LabelIndexStats &stats, const LabelPropertyIndexStats &property_stats, uint64_t final_commit_timestamp); - bool FinalizeTransaction(uint64_t timestamp, Storage *storage); + bool FinalizeTransaction(uint64_t timestamp, Storage *storage, DatabaseAccessProtector db_acc); // Getters auto GetReplicaState(std::string_view name) const -> std::optional<replication::ReplicaState>; diff --git a/src/storage/v2/replication/rpc.cpp b/src/storage/v2/replication/rpc.cpp index b722dfebf..27fc1a0d6 100644 --- a/src/storage/v2/replication/rpc.cpp +++ b/src/storage/v2/replication/rpc.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -10,6 +10,9 @@ // licenses/APL.txt. #include "storage/v2/replication/rpc.hpp" +#include <cstdint> +#include "slk/streams.hpp" +#include "utils/enum.hpp" #include "utils/typeinfo.hpp" namespace memgraph { @@ -56,6 +59,38 @@ void TimestampRes::Save(const TimestampRes &self, memgraph::slk::Builder *builde memgraph::slk::Save(self, builder); } void TimestampRes::Load(TimestampRes *self, memgraph::slk::Reader *reader) { memgraph::slk::Load(self, reader); } +void CreateDatabaseReq::Save(const CreateDatabaseReq &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self, builder); +} +void CreateDatabaseReq::Load(CreateDatabaseReq *self, memgraph::slk::Reader *reader) { + memgraph::slk::Load(self, reader); +} +void CreateDatabaseRes::Save(const CreateDatabaseRes &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self, builder); +} +void CreateDatabaseRes::Load(CreateDatabaseRes *self, memgraph::slk::Reader *reader) { + memgraph::slk::Load(self, reader); +} +void DropDatabaseReq::Save(const DropDatabaseReq &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self, builder); +} +void DropDatabaseReq::Load(DropDatabaseReq *self, memgraph::slk::Reader *reader) { memgraph::slk::Load(self, reader); } +void DropDatabaseRes::Save(const DropDatabaseRes &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self, builder); +} +void DropDatabaseRes::Load(DropDatabaseRes *self, memgraph::slk::Reader *reader) { memgraph::slk::Load(self, reader); } +void SystemRecoveryReq::Save(const SystemRecoveryReq &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self, builder); +} +void SystemRecoveryReq::Load(SystemRecoveryReq *self, memgraph::slk::Reader *reader) { + memgraph::slk::Load(self, reader); +} +void SystemRecoveryRes::Save(const SystemRecoveryRes &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self, builder); +} +void SystemRecoveryRes::Load(SystemRecoveryRes *self, memgraph::slk::Reader *reader) { + memgraph::slk::Load(self, reader); +} } // namespace storage::replication @@ -95,18 +130,34 @@ constexpr utils::TypeInfo storage::replication::TimestampReq::kType{utils::TypeI constexpr utils::TypeInfo storage::replication::TimestampRes::kType{utils::TypeId::REP_TIMESTAMP_RES, "TimestampRes", nullptr}; +constexpr utils::TypeInfo storage::replication::CreateDatabaseReq::kType{utils::TypeId::REP_CREATE_DATABASE_REQ, + "CreateDatabaseReq", nullptr}; + +constexpr utils::TypeInfo storage::replication::CreateDatabaseRes::kType{utils::TypeId::REP_CREATE_DATABASE_RES, + "CreateDatabaseRes", nullptr}; + +constexpr utils::TypeInfo storage::replication::DropDatabaseReq::kType{utils::TypeId::REP_DROP_DATABASE_REQ, + "DropDatabaseReq", nullptr}; + +constexpr utils::TypeInfo storage::replication::DropDatabaseRes::kType{utils::TypeId::REP_DROP_DATABASE_RES, + "DropDatabaseRes", nullptr}; + +constexpr utils::TypeInfo storage::replication::SystemRecoveryReq::kType{utils::TypeId::REP_SYSTEM_RECOVERY_REQ, + "SystemRecoveryReq", nullptr}; + +constexpr utils::TypeInfo storage::replication::SystemRecoveryRes::kType{utils::TypeId::REP_SYSTEM_RECOVERY_RES, + "SystemRecoveryRes", nullptr}; + // Autogenerated SLK serialization code namespace slk { // Serialize code for TimestampRes void Save(const memgraph::storage::replication::TimestampRes &self, memgraph::slk::Builder *builder) { - memgraph::slk::Save(self.db_name, builder); memgraph::slk::Save(self.success, builder); memgraph::slk::Save(self.current_commit_timestamp, builder); } void Load(memgraph::storage::replication::TimestampRes *self, memgraph::slk::Reader *reader) { - memgraph::slk::Load(&self->db_name, reader); memgraph::slk::Load(&self->success, reader); memgraph::slk::Load(&self->current_commit_timestamp, reader); } @@ -114,23 +165,21 @@ void Load(memgraph::storage::replication::TimestampRes *self, memgraph::slk::Rea // Serialize code for TimestampReq void Save(const memgraph::storage::replication::TimestampReq &self, memgraph::slk::Builder *builder) { - memgraph::slk::Save(self.db_name, builder); + memgraph::slk::Save(self.uuid, builder); } void Load(memgraph::storage::replication::TimestampReq *self, memgraph::slk::Reader *reader) { - memgraph::slk::Load(&self->db_name, reader); + memgraph::slk::Load(&self->uuid, reader); } // Serialize code for CurrentWalRes void Save(const memgraph::storage::replication::CurrentWalRes &self, memgraph::slk::Builder *builder) { - memgraph::slk::Save(self.db_name, builder); memgraph::slk::Save(self.success, builder); memgraph::slk::Save(self.current_commit_timestamp, builder); } void Load(memgraph::storage::replication::CurrentWalRes *self, memgraph::slk::Reader *reader) { - memgraph::slk::Load(&self->db_name, reader); memgraph::slk::Load(&self->success, reader); memgraph::slk::Load(&self->current_commit_timestamp, reader); } @@ -138,23 +187,21 @@ void Load(memgraph::storage::replication::CurrentWalRes *self, memgraph::slk::Re // Serialize code for CurrentWalReq void Save(const memgraph::storage::replication::CurrentWalReq &self, memgraph::slk::Builder *builder) { - memgraph::slk::Save(self.db_name, builder); + memgraph::slk::Save(self.uuid, builder); } void Load(memgraph::storage::replication::CurrentWalReq *self, memgraph::slk::Reader *reader) { - memgraph::slk::Load(&self->db_name, reader); + memgraph::slk::Load(&self->uuid, reader); } // Serialize code for WalFilesRes void Save(const memgraph::storage::replication::WalFilesRes &self, memgraph::slk::Builder *builder) { - memgraph::slk::Save(self.db_name, builder); memgraph::slk::Save(self.success, builder); memgraph::slk::Save(self.current_commit_timestamp, builder); } void Load(memgraph::storage::replication::WalFilesRes *self, memgraph::slk::Reader *reader) { - memgraph::slk::Load(&self->db_name, reader); memgraph::slk::Load(&self->success, reader); memgraph::slk::Load(&self->current_commit_timestamp, reader); } @@ -162,25 +209,23 @@ void Load(memgraph::storage::replication::WalFilesRes *self, memgraph::slk::Read // Serialize code for WalFilesReq void Save(const memgraph::storage::replication::WalFilesReq &self, memgraph::slk::Builder *builder) { - memgraph::slk::Save(self.db_name, builder); + memgraph::slk::Save(self.uuid, builder); memgraph::slk::Save(self.file_number, builder); } void Load(memgraph::storage::replication::WalFilesReq *self, memgraph::slk::Reader *reader) { - memgraph::slk::Load(&self->db_name, reader); + memgraph::slk::Load(&self->uuid, reader); memgraph::slk::Load(&self->file_number, reader); } // Serialize code for SnapshotRes void Save(const memgraph::storage::replication::SnapshotRes &self, memgraph::slk::Builder *builder) { - memgraph::slk::Save(self.db_name, builder); memgraph::slk::Save(self.success, builder); memgraph::slk::Save(self.current_commit_timestamp, builder); } void Load(memgraph::storage::replication::SnapshotRes *self, memgraph::slk::Reader *reader) { - memgraph::slk::Load(&self->db_name, reader); memgraph::slk::Load(&self->success, reader); memgraph::slk::Load(&self->current_commit_timestamp, reader); } @@ -188,24 +233,22 @@ void Load(memgraph::storage::replication::SnapshotRes *self, memgraph::slk::Read // Serialize code for SnapshotReq void Save(const memgraph::storage::replication::SnapshotReq &self, memgraph::slk::Builder *builder) { - memgraph::slk::Save(self.db_name, builder); + memgraph::slk::Save(self.uuid, builder); } void Load(memgraph::storage::replication::SnapshotReq *self, memgraph::slk::Reader *reader) { - memgraph::slk::Load(&self->db_name, reader); + memgraph::slk::Load(&self->uuid, reader); } // Serialize code for HeartbeatRes void Save(const memgraph::storage::replication::HeartbeatRes &self, memgraph::slk::Builder *builder) { - memgraph::slk::Save(self.db_name, builder); memgraph::slk::Save(self.success, builder); memgraph::slk::Save(self.current_commit_timestamp, builder); memgraph::slk::Save(self.epoch_id, builder); } void Load(memgraph::storage::replication::HeartbeatRes *self, memgraph::slk::Reader *reader) { - memgraph::slk::Load(&self->db_name, reader); memgraph::slk::Load(&self->success, reader); memgraph::slk::Load(&self->current_commit_timestamp, reader); memgraph::slk::Load(&self->epoch_id, reader); @@ -214,13 +257,13 @@ void Load(memgraph::storage::replication::HeartbeatRes *self, memgraph::slk::Rea // Serialize code for HeartbeatReq void Save(const memgraph::storage::replication::HeartbeatReq &self, memgraph::slk::Builder *builder) { - memgraph::slk::Save(self.db_name, builder); + memgraph::slk::Save(self.uuid, builder); memgraph::slk::Save(self.main_commit_timestamp, builder); memgraph::slk::Save(self.epoch_id, builder); } void Load(memgraph::storage::replication::HeartbeatReq *self, memgraph::slk::Reader *reader) { - memgraph::slk::Load(&self->db_name, reader); + memgraph::slk::Load(&self->uuid, reader); memgraph::slk::Load(&self->main_commit_timestamp, reader); memgraph::slk::Load(&self->epoch_id, reader); } @@ -228,13 +271,11 @@ void Load(memgraph::storage::replication::HeartbeatReq *self, memgraph::slk::Rea // Serialize code for AppendDeltasRes void Save(const memgraph::storage::replication::AppendDeltasRes &self, memgraph::slk::Builder *builder) { - memgraph::slk::Save(self.db_name, builder); memgraph::slk::Save(self.success, builder); memgraph::slk::Save(self.current_commit_timestamp, builder); } void Load(memgraph::storage::replication::AppendDeltasRes *self, memgraph::slk::Reader *reader) { - memgraph::slk::Load(&self->db_name, reader); memgraph::slk::Load(&self->success, reader); memgraph::slk::Load(&self->current_commit_timestamp, reader); } @@ -242,15 +283,124 @@ void Load(memgraph::storage::replication::AppendDeltasRes *self, memgraph::slk:: // Serialize code for AppendDeltasReq void Save(const memgraph::storage::replication::AppendDeltasReq &self, memgraph::slk::Builder *builder) { - memgraph::slk::Save(self.db_name, builder); + memgraph::slk::Save(self.uuid, builder); memgraph::slk::Save(self.previous_commit_timestamp, builder); memgraph::slk::Save(self.seq_num, builder); } void Load(memgraph::storage::replication::AppendDeltasReq *self, memgraph::slk::Reader *reader) { - memgraph::slk::Load(&self->db_name, reader); + memgraph::slk::Load(&self->uuid, reader); memgraph::slk::Load(&self->previous_commit_timestamp, reader); memgraph::slk::Load(&self->seq_num, reader); } + +// Serialize SalientConfig + +void Save(const memgraph::storage::SalientConfig &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self.name, builder); + memgraph::slk::Save(self.uuid, builder); + memgraph::slk::Save(utils::EnumToNum<3, uint8_t>(self.storage_mode), builder); + memgraph::slk::Save(self.items.properties_on_edges, builder); + memgraph::slk::Save(self.items.enable_schema_metadata, builder); +} + +void Load(memgraph::storage::SalientConfig *self, memgraph::slk::Reader *reader) { + memgraph::slk::Load(&self->name, reader); + memgraph::slk::Load(&self->uuid, reader); + uint8_t sm = 0; + memgraph::slk::Load(&sm, reader); + if (!utils::NumToEnum<3>(sm, self->storage_mode)) { + throw SlkReaderException("Unexpected result line:{}!", __LINE__); + } + memgraph::slk::Load(&self->items.properties_on_edges, reader); + memgraph::slk::Load(&self->items.enable_schema_metadata, reader); +} + +// Serialize code for CreateDatabaseReq + +void Save(const memgraph::storage::replication::CreateDatabaseReq &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self.epoch_id, builder); + memgraph::slk::Save(self.expected_group_timestamp, builder); + memgraph::slk::Save(self.new_group_timestamp, builder); + memgraph::slk::Save(self.config, builder); +} + +void Load(memgraph::storage::replication::CreateDatabaseReq *self, memgraph::slk::Reader *reader) { + memgraph::slk::Load(&self->epoch_id, reader); + memgraph::slk::Load(&self->expected_group_timestamp, reader); + memgraph::slk::Load(&self->new_group_timestamp, reader); + memgraph::slk::Load(&self->config, reader); +} + +// Serialize code for CreateDatabaseRes + +void Save(const memgraph::storage::replication::CreateDatabaseRes &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(utils::EnumToNum<uint8_t>(self.result), builder); +} + +void Load(memgraph::storage::replication::CreateDatabaseRes *self, memgraph::slk::Reader *reader) { + uint8_t res = 0; + memgraph::slk::Load(&res, reader); + if (!utils::NumToEnum(res, self->result)) { + throw SlkReaderException("Unexpected result line:{}!", __LINE__); + } +} + +// Serialize code for DropDatabaseReq + +void Save(const memgraph::storage::replication::DropDatabaseReq &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self.epoch_id, builder); + memgraph::slk::Save(self.expected_group_timestamp, builder); + memgraph::slk::Save(self.new_group_timestamp, builder); + memgraph::slk::Save(self.uuid, builder); +} + +void Load(memgraph::storage::replication::DropDatabaseReq *self, memgraph::slk::Reader *reader) { + memgraph::slk::Load(&self->epoch_id, reader); + memgraph::slk::Load(&self->expected_group_timestamp, reader); + memgraph::slk::Load(&self->new_group_timestamp, reader); + memgraph::slk::Load(&self->uuid, reader); +} + +// Serialize code for DropDatabaseRes + +void Save(const memgraph::storage::replication::DropDatabaseRes &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(utils::EnumToNum<uint8_t>(self.result), builder); +} + +void Load(memgraph::storage::replication::DropDatabaseRes *self, memgraph::slk::Reader *reader) { + uint8_t res = 0; + memgraph::slk::Load(&res, reader); + if (!utils::NumToEnum(res, self->result)) { + throw SlkReaderException("Unexpected result line:{}!", __LINE__); + } +} + +// Serialize code for SystemRecoveryReq + +void Save(const memgraph::storage::replication::SystemRecoveryReq &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(self.forced_group_timestamp, builder); + memgraph::slk::Save(self.database_configs, builder); +} + +void Load(memgraph::storage::replication::SystemRecoveryReq *self, memgraph::slk::Reader *reader) { + memgraph::slk::Load(&self->forced_group_timestamp, reader); + memgraph::slk::Load(&self->database_configs, reader); +} + +// Serialize code for SystemRecoveryRes + +void Save(const memgraph::storage::replication::SystemRecoveryRes &self, memgraph::slk::Builder *builder) { + memgraph::slk::Save(utils::EnumToNum<uint8_t>(self.result), builder); +} + +void Load(memgraph::storage::replication::SystemRecoveryRes *self, memgraph::slk::Reader *reader) { + uint8_t res = 0; + memgraph::slk::Load(&res, reader); + if (!utils::NumToEnum(res, self->result)) { + throw SlkReaderException("Unexpected result line:{}!", __LINE__); + } +} + } // namespace slk } // namespace memgraph diff --git a/src/storage/v2/replication/rpc.hpp b/src/storage/v2/replication/rpc.hpp index 9e2f0b35e..62f8b680c 100644 --- a/src/storage/v2/replication/rpc.hpp +++ b/src/storage/v2/replication/rpc.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -19,6 +19,9 @@ #include "rpc/messages.hpp" #include "slk/serialization.hpp" #include "slk/streams.hpp" +#include "storage/v2/config.hpp" +#include "utils/enum.hpp" +#include "utils/uuid.hpp" namespace memgraph::storage::replication { @@ -29,10 +32,10 @@ struct AppendDeltasReq { static void Load(AppendDeltasReq *self, memgraph::slk::Reader *reader); static void Save(const AppendDeltasReq &self, memgraph::slk::Builder *builder); AppendDeltasReq() = default; - AppendDeltasReq(std::string name, uint64_t previous_commit_timestamp, uint64_t seq_num) - : db_name(std::move(name)), previous_commit_timestamp(previous_commit_timestamp), seq_num(seq_num) {} + AppendDeltasReq(const utils::UUID &uuid, uint64_t previous_commit_timestamp, uint64_t seq_num) + : uuid{uuid}, previous_commit_timestamp(previous_commit_timestamp), seq_num(seq_num) {} - std::string db_name; + utils::UUID uuid; uint64_t previous_commit_timestamp; uint64_t seq_num; }; @@ -44,10 +47,9 @@ struct AppendDeltasRes { static void Load(AppendDeltasRes *self, memgraph::slk::Reader *reader); static void Save(const AppendDeltasRes &self, memgraph::slk::Builder *builder); AppendDeltasRes() = default; - AppendDeltasRes(std::string name, bool success, uint64_t current_commit_timestamp) - : db_name(std::move(name)), success(success), current_commit_timestamp(current_commit_timestamp) {} + AppendDeltasRes(bool success, uint64_t current_commit_timestamp) + : success(success), current_commit_timestamp(current_commit_timestamp) {} - std::string db_name; bool success; uint64_t current_commit_timestamp; }; @@ -61,10 +63,10 @@ struct HeartbeatReq { static void Load(HeartbeatReq *self, memgraph::slk::Reader *reader); static void Save(const HeartbeatReq &self, memgraph::slk::Builder *builder); HeartbeatReq() = default; - HeartbeatReq(std::string name, uint64_t main_commit_timestamp, std::string epoch_id) - : db_name(std::move(name)), main_commit_timestamp(main_commit_timestamp), epoch_id(std::move(epoch_id)) {} + HeartbeatReq(const utils::UUID &uuid, uint64_t main_commit_timestamp, std::string epoch_id) + : uuid{uuid}, main_commit_timestamp(main_commit_timestamp), epoch_id(std::move(epoch_id)) {} - std::string db_name; + utils::UUID uuid; uint64_t main_commit_timestamp; std::string epoch_id; }; @@ -76,13 +78,9 @@ struct HeartbeatRes { static void Load(HeartbeatRes *self, memgraph::slk::Reader *reader); static void Save(const HeartbeatRes &self, memgraph::slk::Builder *builder); HeartbeatRes() = default; - HeartbeatRes(std::string name, bool success, uint64_t current_commit_timestamp, std::string epoch_id) - : db_name(std::move(name)), - success(success), - current_commit_timestamp(current_commit_timestamp), - epoch_id(std::move(epoch_id)) {} + HeartbeatRes(bool success, uint64_t current_commit_timestamp, std::string epoch_id) + : success(success), current_commit_timestamp(current_commit_timestamp), epoch_id(std::move(epoch_id)) {} - std::string db_name; bool success; uint64_t current_commit_timestamp; std::string epoch_id; @@ -97,9 +95,9 @@ struct SnapshotReq { static void Load(SnapshotReq *self, memgraph::slk::Reader *reader); static void Save(const SnapshotReq &self, memgraph::slk::Builder *builder); SnapshotReq() = default; - explicit SnapshotReq(std::string name) : db_name(std::move(name)) {} + explicit SnapshotReq(const utils::UUID &uuid) : uuid{uuid} {} - std::string db_name; + utils::UUID uuid; }; struct SnapshotRes { @@ -109,10 +107,9 @@ struct SnapshotRes { static void Load(SnapshotRes *self, memgraph::slk::Reader *reader); static void Save(const SnapshotRes &self, memgraph::slk::Builder *builder); SnapshotRes() = default; - SnapshotRes(std::string name, bool success, uint64_t current_commit_timestamp) - : db_name(std::move(name)), success(success), current_commit_timestamp(current_commit_timestamp) {} + SnapshotRes(bool success, uint64_t current_commit_timestamp) + : success(success), current_commit_timestamp(current_commit_timestamp) {} - std::string db_name; bool success; uint64_t current_commit_timestamp; }; @@ -126,9 +123,9 @@ struct WalFilesReq { static void Load(WalFilesReq *self, memgraph::slk::Reader *reader); static void Save(const WalFilesReq &self, memgraph::slk::Builder *builder); WalFilesReq() = default; - explicit WalFilesReq(std::string name, uint64_t file_number) : db_name(std::move(name)), file_number(file_number) {} + explicit WalFilesReq(const utils::UUID &uuid, uint64_t file_number) : uuid{uuid}, file_number(file_number) {} - std::string db_name; + utils::UUID uuid; uint64_t file_number; }; @@ -139,10 +136,9 @@ struct WalFilesRes { static void Load(WalFilesRes *self, memgraph::slk::Reader *reader); static void Save(const WalFilesRes &self, memgraph::slk::Builder *builder); WalFilesRes() = default; - WalFilesRes(std::string name, bool success, uint64_t current_commit_timestamp) - : db_name(std::move(name)), success(success), current_commit_timestamp(current_commit_timestamp) {} + WalFilesRes(bool success, uint64_t current_commit_timestamp) + : success(success), current_commit_timestamp(current_commit_timestamp) {} - std::string db_name; bool success; uint64_t current_commit_timestamp; }; @@ -156,9 +152,9 @@ struct CurrentWalReq { static void Load(CurrentWalReq *self, memgraph::slk::Reader *reader); static void Save(const CurrentWalReq &self, memgraph::slk::Builder *builder); CurrentWalReq() = default; - explicit CurrentWalReq(std::string name) : db_name(std::move(name)) {} + explicit CurrentWalReq(const utils::UUID &uuid) : uuid{uuid} {} - std::string db_name; + utils::UUID uuid; }; struct CurrentWalRes { @@ -168,10 +164,9 @@ struct CurrentWalRes { static void Load(CurrentWalRes *self, memgraph::slk::Reader *reader); static void Save(const CurrentWalRes &self, memgraph::slk::Builder *builder); CurrentWalRes() = default; - CurrentWalRes(std::string name, bool success, uint64_t current_commit_timestamp) - : db_name(std::move(name)), success(success), current_commit_timestamp(current_commit_timestamp) {} + CurrentWalRes(bool success, uint64_t current_commit_timestamp) + : success(success), current_commit_timestamp(current_commit_timestamp) {} - std::string db_name; bool success; uint64_t current_commit_timestamp; }; @@ -185,9 +180,9 @@ struct TimestampReq { static void Load(TimestampReq *self, memgraph::slk::Reader *reader); static void Save(const TimestampReq &self, memgraph::slk::Builder *builder); TimestampReq() = default; - explicit TimestampReq(std::string name) : db_name(std::move(name)) {} + explicit TimestampReq(const utils::UUID &uuid) : uuid{uuid} {} - std::string db_name; + utils::UUID uuid; }; struct TimestampRes { @@ -197,15 +192,117 @@ struct TimestampRes { static void Load(TimestampRes *self, memgraph::slk::Reader *reader); static void Save(const TimestampRes &self, memgraph::slk::Builder *builder); TimestampRes() = default; - TimestampRes(std::string name, bool success, uint64_t current_commit_timestamp) - : db_name(std::move(name)), success(success), current_commit_timestamp(current_commit_timestamp) {} + TimestampRes(bool success, uint64_t current_commit_timestamp) + : success(success), current_commit_timestamp(current_commit_timestamp) {} - std::string db_name; bool success; uint64_t current_commit_timestamp; }; using TimestampRpc = rpc::RequestResponse<TimestampReq, TimestampRes>; + +struct CreateDatabaseReq { + static const utils::TypeInfo kType; + static const utils::TypeInfo &GetTypeInfo() { return kType; } + + static void Load(CreateDatabaseReq *self, memgraph::slk::Reader *reader); + static void Save(const CreateDatabaseReq &self, memgraph::slk::Builder *builder); + CreateDatabaseReq() = default; + CreateDatabaseReq(std::string epoch_id, uint64_t expected_group_timestamp, uint64_t new_group_timestamp, + storage::SalientConfig config) + : epoch_id(std::move(epoch_id)), + expected_group_timestamp{expected_group_timestamp}, + new_group_timestamp(new_group_timestamp), + config(std::move(config)) {} + + std::string epoch_id; + uint64_t expected_group_timestamp; + uint64_t new_group_timestamp; + storage::SalientConfig config; +}; + +struct CreateDatabaseRes { + static const utils::TypeInfo kType; + static const utils::TypeInfo &GetTypeInfo() { return kType; } + + enum class Result : uint8_t { SUCCESS, NO_NEED, FAILURE, /* Leave at end */ N }; + + static void Load(CreateDatabaseRes *self, memgraph::slk::Reader *reader); + static void Save(const CreateDatabaseRes &self, memgraph::slk::Builder *builder); + CreateDatabaseRes() = default; + explicit CreateDatabaseRes(Result res) : result(res) {} + + Result result; +}; + +using CreateDatabaseRpc = rpc::RequestResponse<CreateDatabaseReq, CreateDatabaseRes>; + +struct DropDatabaseReq { + static const utils::TypeInfo kType; + static const utils::TypeInfo &GetTypeInfo() { return kType; } + + static void Load(DropDatabaseReq *self, memgraph::slk::Reader *reader); + static void Save(const DropDatabaseReq &self, memgraph::slk::Builder *builder); + DropDatabaseReq() = default; + DropDatabaseReq(std::string epoch_id, uint64_t expected_group_timestamp, uint64_t new_group_timestamp, + const utils::UUID &uuid) + : epoch_id(std::move(epoch_id)), + expected_group_timestamp{expected_group_timestamp}, + new_group_timestamp(new_group_timestamp), + uuid(uuid) {} + + std::string epoch_id; + uint64_t expected_group_timestamp; + uint64_t new_group_timestamp; + utils::UUID uuid; +}; + +struct DropDatabaseRes { + static const utils::TypeInfo kType; + static const utils::TypeInfo &GetTypeInfo() { return kType; } + + enum class Result : uint8_t { SUCCESS, NO_NEED, FAILURE, /* Leave at end */ N }; + + static void Load(DropDatabaseRes *self, memgraph::slk::Reader *reader); + static void Save(const DropDatabaseRes &self, memgraph::slk::Builder *builder); + DropDatabaseRes() = default; + explicit DropDatabaseRes(Result res) : result(res) {} + + Result result; +}; + +using DropDatabaseRpc = rpc::RequestResponse<DropDatabaseReq, DropDatabaseRes>; + +struct SystemRecoveryReq { + static const utils::TypeInfo kType; + static const utils::TypeInfo &GetTypeInfo() { return kType; } + + static void Load(SystemRecoveryReq *self, memgraph::slk::Reader *reader); + static void Save(const SystemRecoveryReq &self, memgraph::slk::Builder *builder); + SystemRecoveryReq() = default; + SystemRecoveryReq(uint64_t forced_group_timestamp, std::vector<storage::SalientConfig> database_configs) + : forced_group_timestamp{forced_group_timestamp}, database_configs(std::move(database_configs)) {} + + uint64_t forced_group_timestamp; + std::vector<storage::SalientConfig> database_configs; +}; + +struct SystemRecoveryRes { + static const utils::TypeInfo kType; + static const utils::TypeInfo &GetTypeInfo() { return kType; } + + enum class Result : uint8_t { SUCCESS, NO_NEED, FAILURE, /* Leave at end */ N }; + + static void Load(SystemRecoveryRes *self, memgraph::slk::Reader *reader); + static void Save(const SystemRecoveryRes &self, memgraph::slk::Builder *builder); + SystemRecoveryRes() = default; + explicit SystemRecoveryRes(Result res) : result(res) {} + + Result result; +}; + +using SystemRecoveryRpc = rpc::RequestResponse<SystemRecoveryReq, SystemRecoveryRes>; + } // namespace memgraph::storage::replication // SLK serialization declarations @@ -259,4 +356,28 @@ void Save(const memgraph::storage::replication::AppendDeltasReq &self, memgraph: void Load(memgraph::storage::replication::AppendDeltasReq *self, memgraph::slk::Reader *reader); +void Save(const memgraph::storage::replication::CreateDatabaseReq &self, memgraph::slk::Builder *builder); + +void Load(memgraph::storage::replication::CreateDatabaseReq *self, memgraph::slk::Reader *reader); + +void Save(const memgraph::storage::replication::CreateDatabaseRes &self, memgraph::slk::Builder *builder); + +void Load(memgraph::storage::replication::CreateDatabaseRes *self, memgraph::slk::Reader *reader); + +void Save(const memgraph::storage::replication::DropDatabaseReq &self, memgraph::slk::Builder *builder); + +void Load(memgraph::storage::replication::DropDatabaseReq *self, memgraph::slk::Reader *reader); + +void Save(const memgraph::storage::replication::DropDatabaseRes &self, memgraph::slk::Builder *builder); + +void Load(memgraph::storage::replication::DropDatabaseRes *self, memgraph::slk::Reader *reader); + +void Save(const memgraph::storage::replication::SystemRecoveryReq &self, memgraph::slk::Builder *builder); + +void Load(memgraph::storage::replication::SystemRecoveryReq *self, memgraph::slk::Reader *reader); + +void Save(const memgraph::storage::replication::SystemRecoveryRes &self, memgraph::slk::Builder *builder); + +void Load(memgraph::storage::replication::SystemRecoveryRes *self, memgraph::slk::Reader *reader); + } // namespace memgraph::slk diff --git a/src/storage/v2/replication/slk.hpp b/src/storage/v2/replication/slk.hpp index a202e55af..1d1399cb8 100644 --- a/src/storage/v2/replication/slk.hpp +++ b/src/storage/v2/replication/slk.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -25,17 +25,4 @@ void Load(storage::Gid *gid, slk::Reader *reader); void Save(const storage::PropertyValue &value, slk::Builder *builder); void Load(storage::PropertyValue *value, slk::Reader *reader); -template <utils::Enum T> -void Save(const T &enum_value, slk::Builder *builder) { - slk::Save(utils::UnderlyingCast(enum_value), builder); -} - -template <utils::Enum T> -void Load(T *enum_value, slk::Reader *reader) { - using UnderlyingType = std::underlying_type_t<T>; - UnderlyingType value; - slk::Load(&value, reader); - *enum_value = static_cast<T>(value); -} - } // namespace memgraph::slk diff --git a/src/storage/v2/storage.cpp b/src/storage/v2/storage.cpp index 86cc02696..536a504a0 100644 --- a/src/storage/v2/storage.cpp +++ b/src/storage/v2/storage.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -17,6 +17,7 @@ #include "storage/v2/storage.hpp" #include "storage/v2/transaction.hpp" #include "storage/v2/vertex_accessor.hpp" +#include "utils/atomic_memory_block.hpp" #include "utils/event_counter.hpp" #include "utils/event_histogram.hpp" #include "utils/exceptions.hpp" @@ -43,32 +44,33 @@ Storage::Storage(Config config, StorageMode storage_mode) isolation_level_(config.transaction.isolation_level), storage_mode_(storage_mode), indices_(config, storage_mode), - constraints_(config, storage_mode), - id_(config.name) { + constraints_(config, storage_mode) { spdlog::info("Created database with {} storage mode.", StorageModeToString(storage_mode)); } Storage::Accessor::Accessor(SharedAccess /* tag */, Storage *storage, IsolationLevel isolation_level, - StorageMode storage_mode, bool is_main) + StorageMode storage_mode, + memgraph::replication_coordination_glue::ReplicationRole replication_role) : storage_(storage), // The lock must be acquired before creating the transaction object to // prevent freshly created transactions from dangling in an active state // during exclusive operations. storage_guard_(storage_->main_lock_), unique_guard_(storage_->main_lock_, std::defer_lock), - transaction_(storage->CreateTransaction(isolation_level, storage_mode, is_main)), + transaction_(storage->CreateTransaction(isolation_level, storage_mode, replication_role)), is_transaction_active_(true), creation_storage_mode_(storage_mode) {} Storage::Accessor::Accessor(UniqueAccess /* tag */, Storage *storage, IsolationLevel isolation_level, - StorageMode storage_mode, bool is_main) + StorageMode storage_mode, + memgraph::replication_coordination_glue::ReplicationRole replication_role) : storage_(storage), // The lock must be acquired before creating the transaction object to // prevent freshly created transactions from dangling in an active state // during exclusive operations. storage_guard_(storage_->main_lock_, std::defer_lock), unique_guard_(storage_->main_lock_), - transaction_(storage->CreateTransaction(isolation_level, storage_mode, is_main)), + transaction_(storage->CreateTransaction(isolation_level, storage_mode, replication_role)), is_transaction_active_(true), creation_storage_mode_(storage_mode) {} @@ -318,7 +320,7 @@ EdgeInfoForDeletion Storage::Accessor::PrepareDeletableEdges(const std::unordere const auto &[edge_type, opposing_vertex, edge] = item; if (!vertices.contains(opposing_vertex)) { partial_delete_vertices.insert(opposing_vertex); - auto const edge_gid = storage_->config_.items.properties_on_edges ? edge.ptr->gid : edge.gid; + auto const edge_gid = storage_->config_.salient.items.properties_on_edges ? edge.ptr->gid : edge.gid; edge_ids.insert(edge_gid); } }; @@ -380,7 +382,7 @@ Result<std::optional<std::vector<EdgeAccessor>>> Storage::Accessor::ClearEdgesOn /// TODO: (andi) Again here, no need to lock the edge if using on disk storage. std::unique_lock<utils::RWSpinLock> guard; - if (storage_->config_.items.properties_on_edges) { + if (storage_->config_.salient.items.properties_on_edges) { auto edge_ptr = edge_ref.ptr; guard = std::unique_lock{edge_ptr->lock}; @@ -390,22 +392,29 @@ Result<std::optional<std::vector<EdgeAccessor>>> Storage::Accessor::ClearEdgesOn if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; MG_ASSERT(!vertex_ptr->deleted, "Invalid database state!"); - attached_edges_to_vertex->pop_back(); - if (storage_->config_.items.properties_on_edges) { - auto *edge_ptr = edge_ref.ptr; - MarkEdgeAsDeleted(edge_ptr); - } + // MarkEdgeAsDeleted allocates additional memory + // and CreateAndLinkDelta needs memory + utils::AtomicMemoryBlock atomic_memory_block{[&attached_edges_to_vertex, &deleted_edge_ids, &reverse_vertex_order, + &vertex_ptr, &deleted_edges, deletion_delta = deletion_delta, + edge_type = edge_type, opposing_vertex = opposing_vertex, + edge_ref = edge_ref, this]() { + attached_edges_to_vertex->pop_back(); + if (this->storage_->config_.salient.items.properties_on_edges) { + auto *edge_ptr = edge_ref.ptr; + MarkEdgeAsDeleted(edge_ptr); + } - auto const edge_gid = storage_->config_.items.properties_on_edges ? edge_ref.ptr->gid : edge_ref.gid; - auto const [_, was_inserted] = deleted_edge_ids.insert(edge_gid); - bool const edge_cleared_from_both_directions = !was_inserted; - if (edge_cleared_from_both_directions) { - auto *from_vertex = reverse_vertex_order ? vertex_ptr : opposing_vertex; - auto *to_vertex = reverse_vertex_order ? opposing_vertex : vertex_ptr; - deleted_edges.emplace_back(edge_ref, edge_type, from_vertex, to_vertex, storage_, &transaction_, true); - } - - CreateAndLinkDelta(&transaction_, vertex_ptr, deletion_delta, edge_type, opposing_vertex, edge_ref); + auto const edge_gid = storage_->config_.salient.items.properties_on_edges ? edge_ref.ptr->gid : edge_ref.gid; + auto const [_, was_inserted] = deleted_edge_ids.insert(edge_gid); + bool const edge_cleared_from_both_directions = !was_inserted; + if (edge_cleared_from_both_directions) { + auto *from_vertex = reverse_vertex_order ? vertex_ptr : opposing_vertex; + auto *to_vertex = reverse_vertex_order ? opposing_vertex : vertex_ptr; + deleted_edges.emplace_back(edge_ref, edge_type, from_vertex, to_vertex, storage_, &transaction_, true); + } + CreateAndLinkDelta(&transaction_, vertex_ptr, deletion_delta, edge_type, opposing_vertex, edge_ref); + }}; + std::invoke(atomic_memory_block); } return std::make_optional<ReturnType>(); @@ -445,35 +454,41 @@ Result<std::optional<std::vector<EdgeAccessor>>> Storage::Accessor::DetachRemain auto mid = std::partition( edges_attached_to_vertex->begin(), edges_attached_to_vertex->end(), [this, &set_for_erasure](auto &edge) { auto const &[edge_type, opposing_vertex, edge_ref] = edge; - auto const edge_gid = storage_->config_.items.properties_on_edges ? edge_ref.ptr->gid : edge_ref.gid; + auto const edge_gid = storage_->config_.salient.items.properties_on_edges ? edge_ref.ptr->gid : edge_ref.gid; return !set_for_erasure.contains(edge_gid); }); - for (auto it = mid; it != edges_attached_to_vertex->end(); it++) { - auto const &[edge_type, opposing_vertex, edge_ref] = *it; - std::unique_lock<utils::RWSpinLock> guard; - if (storage_->config_.items.properties_on_edges) { - auto edge_ptr = edge_ref.ptr; - guard = std::unique_lock{edge_ptr->lock}; - // this can happen only if we marked edges for deletion with no nodes, - // so the method detaching nodes will not do anything - MarkEdgeAsDeleted(edge_ptr); + // Creating deltas and erasing edge only at the end -> we might have incomplete state as + // delta might cause OOM, so we don't remove edges from edges_attached_to_vertex + utils::AtomicMemoryBlock atomic_memory_block{[&mid, &edges_attached_to_vertex, &deleted_edges, + &partially_detached_edge_ids, this, vertex_ptr, deletion_delta, + reverse_vertex_order]() { + for (auto it = mid; it != edges_attached_to_vertex->end(); it++) { + auto const &[edge_type, opposing_vertex, edge_ref] = *it; + std::unique_lock<utils::RWSpinLock> guard; + if (storage_->config_.salient.items.properties_on_edges) { + auto edge_ptr = edge_ref.ptr; + guard = std::unique_lock{edge_ptr->lock}; + // this can happen only if we marked edges for deletion with no nodes, + // so the method detaching nodes will not do anything + MarkEdgeAsDeleted(edge_ptr); + } + + CreateAndLinkDelta(&transaction_, vertex_ptr, deletion_delta, edge_type, opposing_vertex, edge_ref); + + auto const edge_gid = storage_->config_.salient.items.properties_on_edges ? edge_ref.ptr->gid : edge_ref.gid; + auto const [_, was_inserted] = partially_detached_edge_ids.insert(edge_gid); + bool const edge_cleared_from_both_directions = !was_inserted; + if (edge_cleared_from_both_directions) { + auto *from_vertex = reverse_vertex_order ? opposing_vertex : vertex_ptr; + auto *to_vertex = reverse_vertex_order ? vertex_ptr : opposing_vertex; + deleted_edges.emplace_back(edge_ref, edge_type, from_vertex, to_vertex, storage_, &transaction_, true); + } } + edges_attached_to_vertex->erase(mid, edges_attached_to_vertex->end()); + }}; - CreateAndLinkDelta(&transaction_, vertex_ptr, deletion_delta, edge_type, opposing_vertex, edge_ref); - - auto const edge_gid = storage_->config_.items.properties_on_edges ? edge_ref.ptr->gid : edge_ref.gid; - auto const [_, was_inserted] = partially_detached_edge_ids.insert(edge_gid); - bool const edge_cleared_from_both_directions = !was_inserted; - if (edge_cleared_from_both_directions) { - auto *from_vertex = reverse_vertex_order ? opposing_vertex : vertex_ptr; - auto *to_vertex = reverse_vertex_order ? vertex_ptr : opposing_vertex; - deleted_edges.emplace_back(edge_ref, edge_type, from_vertex, to_vertex, storage_, &transaction_, true); - } - } - - edges_attached_to_vertex->erase(mid, edges_attached_to_vertex->end()); - + std::invoke(atomic_memory_block); return std::make_optional<ReturnType>(); }; diff --git a/src/storage/v2/storage.hpp b/src/storage/v2/storage.hpp index bf1973bcd..a096f27fd 100644 --- a/src/storage/v2/storage.hpp +++ b/src/storage/v2/storage.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -12,6 +12,8 @@ #pragma once #include <chrono> +#include <functional> +#include <optional> #include <semaphore> #include <span> #include <thread> @@ -24,6 +26,7 @@ #include "storage/v2/all_vertices_iterable.hpp" #include "storage/v2/commit_log.hpp" #include "storage/v2/config.hpp" +#include "storage/v2/database_access.hpp" #include "storage/v2/durability/paths.hpp" #include "storage/v2/durability/wal.hpp" #include "storage/v2/edge_accessor.hpp" @@ -52,7 +55,6 @@ extern const Event ActiveLabelPropertyIndices; } // namespace memgraph::metrics namespace memgraph::storage { - struct Transaction; class EdgeAccessor; @@ -108,6 +110,15 @@ struct EdgeInfoForDeletion { std::unordered_set<Vertex *> partial_dest_vertices{}; }; +struct CommitReplArgs { + // REPLICA on recipt of Deltas will have a desired commit timestamp + std::optional<uint64_t> desired_commit_timestamp = std::nullopt; + + bool is_main = true; + + bool IsMain() { return is_main; } +}; + class Storage { friend class ReplicationServer; friend class ReplicationStorageClient; @@ -122,7 +133,9 @@ class Storage { virtual ~Storage() = default; - const std::string &id() const { return id_; } + const std::string &name() const { return config_.salient.name; } + + const utils::UUID &uuid() const { return config_.salient.uuid; } class Accessor { public: @@ -132,9 +145,9 @@ class Storage { } unique_access; Accessor(SharedAccess /* tag */, Storage *storage, IsolationLevel isolation_level, StorageMode storage_mode, - bool is_main = true); + memgraph::replication_coordination_glue::ReplicationRole replication_role); Accessor(UniqueAccess /* tag */, Storage *storage, IsolationLevel isolation_level, StorageMode storage_mode, - bool is_main = true); + memgraph::replication_coordination_glue::ReplicationRole replication_role); Accessor(const Accessor &) = delete; Accessor &operator=(const Accessor &) = delete; Accessor &operator=(Accessor &&other) = delete; @@ -216,8 +229,8 @@ class Storage { virtual ConstraintsInfo ListAllConstraints() const = 0; // NOLINTNEXTLINE(google-default-arguments) - virtual utils::BasicResult<StorageManipulationError, void> Commit( - std::optional<uint64_t> desired_commit_timestamp = {}, bool is_main = true) = 0; + virtual utils::BasicResult<StorageManipulationError, void> Commit(CommitReplArgs reparg = {}, + DatabaseAccessProtector db_acc = {}) = 0; virtual void Abort() = 0; @@ -241,7 +254,7 @@ class Storage { StorageMode GetCreationStorageMode() const noexcept; - const std::string &id() const { return storage_->id(); } + const std::string &id() const { return storage_->name(); } std::vector<LabelId> ListAllPossiblyPresentVertexLabels() const; @@ -315,19 +328,18 @@ class Storage { void FreeMemory() { FreeMemory({}); } - virtual std::unique_ptr<Accessor> Access(std::optional<IsolationLevel> override_isolation_level, bool is_main) = 0; - std::unique_ptr<Accessor> Access(bool is_main = true) { return Access(std::optional<IsolationLevel>{}, is_main); } - std::unique_ptr<Accessor> Access(std::optional<IsolationLevel> override_isolation_level) { - return Access(std::move(override_isolation_level), true); + virtual std::unique_ptr<Accessor> Access(memgraph::replication_coordination_glue::ReplicationRole replication_role, + std::optional<IsolationLevel> override_isolation_level) = 0; + + std::unique_ptr<Accessor> Access(memgraph::replication_coordination_glue::ReplicationRole replication_role) { + return Access(replication_role, {}); } - virtual std::unique_ptr<Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level, - bool is_main) = 0; - std::unique_ptr<Accessor> UniqueAccess(bool is_main = true) { - return UniqueAccess(std::optional<IsolationLevel>{}, is_main); - } - std::unique_ptr<Accessor> UniqueAccess(std::optional<IsolationLevel> override_isolation_level) { - return UniqueAccess(std::move(override_isolation_level), true); + virtual std::unique_ptr<Accessor> UniqueAccess( + memgraph::replication_coordination_glue::ReplicationRole replication_role, + std::optional<IsolationLevel> override_isolation_level) = 0; + std::unique_ptr<Accessor> UniqueAccess(memgraph::replication_coordination_glue::ReplicationRole replication_role) { + return UniqueAccess(replication_role, {}); } enum class SetIsolationLevelError : uint8_t { DisabledForAnalyticalMode }; @@ -345,21 +357,11 @@ class Storage { return GetBaseInfo(force_dir); } - virtual StorageInfo GetInfo(bool force_directory) = 0; - StorageInfo GetInfo() { -#if MG_ENTERPRISE - const bool force_dir = false; -#else - const bool force_dir = true; //!< Use the configured directory (multi-tenancy reroutes to another dir) -#endif - return GetInfo(force_dir); - } + virtual StorageInfo GetInfo(bool force_directory, + memgraph::replication_coordination_glue::ReplicationRole replication_role) = 0; - Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode) { - return CreateTransaction(isolation_level, storage_mode, true); - } - - virtual Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, bool is_main) = 0; + virtual Transaction CreateTransaction(IsolationLevel isolation_level, StorageMode storage_mode, + memgraph::replication_coordination_glue::ReplicationRole replication_role) = 0; virtual void PrepareForNewEpoch() = 0; @@ -412,7 +414,6 @@ class Storage { std::atomic<uint64_t> vertex_id_{0}; std::atomic<uint64_t> edge_id_{0}; - const std::string id_; //!< High-level assigned ID }; } // namespace memgraph::storage diff --git a/src/storage/v2/storage_mode.hpp b/src/storage/v2/storage_mode.hpp index c02d3c177..f4a133f38 100644 --- a/src/storage/v2/storage_mode.hpp +++ b/src/storage/v2/storage_mode.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -11,14 +11,19 @@ #pragma once +#include <array> #include <cstdint> #include <string_view> - namespace memgraph::storage { enum class StorageMode : std::uint8_t { IN_MEMORY_ANALYTICAL, IN_MEMORY_TRANSACTIONAL, ON_DISK_TRANSACTIONAL }; -bool IsTransactional(const StorageMode storage_mode) noexcept; +inline constexpr std::array storage_mode_mappings{ + std::pair{std::string_view{"IN_MEMORY_TRANSACTIONAL"}, memgraph::storage::StorageMode::IN_MEMORY_TRANSACTIONAL}, + std::pair{std::string_view{"IN_MEMORY_ANALYTICAL"}, memgraph::storage::StorageMode::IN_MEMORY_ANALYTICAL}, + std::pair{std::string_view{"ON_DISK_TRANSACTIONAL"}, memgraph::storage::StorageMode::ON_DISK_TRANSACTIONAL}}; + +bool IsTransactional(StorageMode storage_mode) noexcept; std::string_view StorageModeToString(memgraph::storage::StorageMode storage_mode); diff --git a/src/storage/v2/vertex_accessor.cpp b/src/storage/v2/vertex_accessor.cpp index ff5881563..ff5062444 100644 --- a/src/storage/v2/vertex_accessor.cpp +++ b/src/storage/v2/vertex_accessor.cpp @@ -26,6 +26,7 @@ #include "storage/v2/storage.hpp" #include "storage/v2/vertex_info_cache.hpp" #include "storage/v2/vertex_info_helpers.hpp" +#include "utils/atomic_memory_block.hpp" #include "utils/logging.hpp" #include "utils/memory_tracker.hpp" #include "utils/variant_helpers.hpp" @@ -107,10 +108,13 @@ Result<bool> VertexAccessor::AddLabel(LabelId label) { if (vertex_->deleted) return Error::DELETED_OBJECT; if (std::find(vertex_->labels.begin(), vertex_->labels.end(), label) != vertex_->labels.end()) return false; - CreateAndLinkDelta(transaction_, vertex_, Delta::RemoveLabelTag(), label); - vertex_->labels.push_back(label); + utils::AtomicMemoryBlock atomic_memory_block{[transaction = transaction_, vertex = vertex_, &label]() { + CreateAndLinkDelta(transaction, vertex, Delta::RemoveLabelTag(), label); + vertex->labels.push_back(label); + }}; + std::invoke(atomic_memory_block); - if (storage_->config_.items.enable_schema_metadata) { + if (storage_->config_.salient.items.enable_schema_metadata) { storage_->stored_node_labels_.try_insert(label); } @@ -136,9 +140,12 @@ Result<bool> VertexAccessor::RemoveLabel(LabelId label) { auto it = std::find(vertex_->labels.begin(), vertex_->labels.end(), label); if (it == vertex_->labels.end()) return false; - CreateAndLinkDelta(transaction_, vertex_, Delta::AddLabelTag(), label); - *it = vertex_->labels.back(); - vertex_->labels.pop_back(); + utils::AtomicMemoryBlock atomic_memory_block{[transaction = transaction_, vertex = vertex_, &label, &it]() { + CreateAndLinkDelta(transaction, vertex, Delta::AddLabelTag(), label); + *it = vertex->labels.back(); + vertex->labels.pop_back(); + }}; + std::invoke(atomic_memory_block); /// TODO: some by pointers, some by reference => not good, make it better storage_->constraints_.unique_constraints_->UpdateOnRemoveLabel(label, *vertex_, transaction_->start_timestamp); @@ -262,8 +269,12 @@ Result<PropertyValue> VertexAccessor::SetProperty(PropertyId property, const Pro // "modify in-place". Additionally, the created delta will make other // transactions get a SERIALIZATION_ERROR. - CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property, current_value); - vertex_->properties.SetProperty(property, value); + utils::AtomicMemoryBlock atomic_memory_block{ + [transaction = transaction_, vertex = vertex_, &value, &property, ¤t_value]() { + CreateAndLinkDelta(transaction, vertex, Delta::SetPropertyTag(), property, current_value); + vertex->properties.SetProperty(property, value); + }}; + std::invoke(atomic_memory_block); if (!value.IsNull()) { transaction_->constraint_verification_info.AddedProperty(vertex_); @@ -287,20 +298,28 @@ Result<bool> VertexAccessor::InitProperties(const std::map<storage::PropertyId, if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR; if (vertex_->deleted) return Error::DELETED_OBJECT; + bool result{false}; + utils::AtomicMemoryBlock atomic_memory_block{ + [&result, &properties, storage = storage_, transaction = transaction_, vertex = vertex_]() { + if (!vertex->properties.InitProperties(properties)) { + result = false; + return; + } + for (const auto &[property, value] : properties) { + CreateAndLinkDelta(transaction, vertex, Delta::SetPropertyTag(), property, PropertyValue()); + storage->indices_.UpdateOnSetProperty(property, value, vertex, *transaction); + transaction->manyDeltasCache.Invalidate(vertex, property); + if (!value.IsNull()) { + transaction->constraint_verification_info.AddedProperty(vertex); + } else { + transaction->constraint_verification_info.RemovedProperty(vertex); + } + } + result = true; + }}; + std::invoke(atomic_memory_block); - if (!vertex_->properties.InitProperties(properties)) return false; - for (const auto &[property, value] : properties) { - CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property, PropertyValue()); - storage_->indices_.UpdateOnSetProperty(property, value, vertex_, *transaction_); - transaction_->manyDeltasCache.Invalidate(vertex_, property); - if (!value.IsNull()) { - transaction_->constraint_verification_info.AddedProperty(vertex_); - } else { - transaction_->constraint_verification_info.RemovedProperty(vertex_); - } - } - - return true; + return result; } Result<std::vector<std::tuple<PropertyId, PropertyValue, PropertyValue>>> VertexAccessor::UpdateProperties( @@ -316,20 +335,28 @@ Result<std::vector<std::tuple<PropertyId, PropertyValue, PropertyValue>>> Vertex if (vertex_->deleted) return Error::DELETED_OBJECT; - auto id_old_new_change = vertex_->properties.UpdateProperties(properties); + using ReturnType = decltype(vertex_->properties.UpdateProperties(properties)); + std::optional<ReturnType> id_old_new_change; + utils::AtomicMemoryBlock atomic_memory_block{ + [storage = storage_, transaction = transaction_, vertex = vertex_, &properties, &id_old_new_change]() { + id_old_new_change.emplace(vertex->properties.UpdateProperties(properties)); + if (!id_old_new_change.has_value()) { + return; + } + for (auto &[id, old_value, new_value] : *id_old_new_change) { + storage->indices_.UpdateOnSetProperty(id, new_value, vertex, *transaction); + CreateAndLinkDelta(transaction, vertex, Delta::SetPropertyTag(), id, std::move(old_value)); + transaction->manyDeltasCache.Invalidate(vertex, id); + if (!new_value.IsNull()) { + transaction->constraint_verification_info.AddedProperty(vertex); + } else { + transaction->constraint_verification_info.RemovedProperty(vertex); + } + } + }}; + std::invoke(atomic_memory_block); - for (auto &[id, old_value, new_value] : id_old_new_change) { - storage_->indices_.UpdateOnSetProperty(id, new_value, vertex_, *transaction_); - CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), id, std::move(old_value)); - transaction_->manyDeltasCache.Invalidate(vertex_, id); - if (!new_value.IsNull()) { - transaction_->constraint_verification_info.AddedProperty(vertex_); - } else { - transaction_->constraint_verification_info.RemovedProperty(vertex_); - } - } - - return id_old_new_change; + return id_old_new_change.has_value() ? std::move(id_old_new_change.value()) : ReturnType{}; } Result<std::map<PropertyId, PropertyValue>> VertexAccessor::ClearProperties() { @@ -342,17 +369,25 @@ Result<std::map<PropertyId, PropertyValue>> VertexAccessor::ClearProperties() { if (vertex_->deleted) return Error::DELETED_OBJECT; - auto properties = vertex_->properties.Properties(); - for (const auto &[property, value] : properties) { - CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property, value); - storage_->indices_.UpdateOnSetProperty(property, PropertyValue(), vertex_, *transaction_); - transaction_->constraint_verification_info.RemovedProperty(vertex_); - transaction_->manyDeltasCache.Invalidate(vertex_, property); - } + using ReturnType = decltype(vertex_->properties.Properties()); + std::optional<ReturnType> properties; + utils::AtomicMemoryBlock atomic_memory_block{ + [storage = storage_, transaction = transaction_, vertex = vertex_, &properties]() { + properties.emplace(vertex->properties.Properties()); + if (!properties.has_value()) { + return; + } + for (const auto &[property, value] : *properties) { + CreateAndLinkDelta(transaction, vertex, Delta::SetPropertyTag(), property, value); + storage->indices_.UpdateOnSetProperty(property, PropertyValue(), vertex, *transaction); + transaction->constraint_verification_info.RemovedProperty(vertex); + transaction->manyDeltasCache.Invalidate(vertex, property); + } + vertex->properties.ClearProperties(); + }}; + std::invoke(atomic_memory_block); - vertex_->properties.ClearProperties(); - - return std::move(properties); + return properties.has_value() ? std::move(properties.value()) : ReturnType{}; } Result<PropertyValue> VertexAccessor::GetProperty(PropertyId property, View view) const { diff --git a/src/telemetry/telemetry.cpp b/src/telemetry/telemetry.cpp index 1635dda99..e5e779f31 100644 --- a/src/telemetry/telemetry.cpp +++ b/src/telemetry/telemetry.cpp @@ -110,7 +110,13 @@ void Telemetry::CollectData(const std::string &event) { { std::lock_guard<std::mutex> guard(lock_); for (auto &collector : collectors_) { - data[collector.first] = collector.second(); + try { + data[collector.first] = collector.second(); + } catch (std::exception &e) { + spdlog::warn(fmt::format( + "Unknwon exception occured on in telemetry server {}, please contact support on https://memgr.ph/unknown ", + e.what())); + } } } if (event == "") { diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt index 276927725..bac3e78f3 100644 --- a/src/utils/CMakeLists.txt +++ b/src/utils/CMakeLists.txt @@ -24,8 +24,8 @@ find_package(Threads REQUIRED) add_library(mg-utils STATIC ${utils_src_files}) add_library(mg::utils ALIAS mg-utils) -target_link_libraries(mg-utils PUBLIC Boost::headers fmt::fmt spdlog::spdlog) -target_link_libraries(mg-utils PRIVATE librdtsc stdc++fs Threads::Threads gflags json uuid rt) +target_link_libraries(mg-utils PUBLIC Boost::headers fmt::fmt spdlog::spdlog json) +target_link_libraries(mg-utils PRIVATE librdtsc stdc++fs Threads::Threads gflags uuid rt) set(settings_src_files settings.cpp) diff --git a/src/utils/algorithm.hpp b/src/utils/algorithm.hpp index cac7ccc9b..fc34e9a4d 100644 --- a/src/utils/algorithm.hpp +++ b/src/utils/algorithm.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -68,7 +68,7 @@ inline std::string IterableToString(const TIterable &iterable, const std::string */ template <typename TIterable> inline std::string IterableToString(const TIterable &iterable, const std::string_view delim = ", ") { - return IterableToString(iterable, delim, [](const auto &item) { return item; }); + return IterableToString(iterable, delim, std::identity{}); } /** @@ -216,8 +216,7 @@ TCollection Reversed(const TCollection &collection, const TAllocator &alloc) { template <typename TIterator> class Iterable { public: - Iterable(TIterator &&begin, TIterator &&end) - : begin_(std::forward<TIterator>(begin)), end_(std::forward<TIterator>(end)) {} + Iterable(TIterator &&begin, TIterator &&end) : begin_(std::move(begin)), end_(std::move(end)) {} auto begin() { return begin_; }; auto end() { return end_; }; diff --git a/src/utils/atomic_memory_block.hpp b/src/utils/atomic_memory_block.hpp new file mode 100644 index 000000000..c15424549 --- /dev/null +++ b/src/utils/atomic_memory_block.hpp @@ -0,0 +1,44 @@ +// Copyright 2023 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include <functional> +#include "utils/memory_tracker.hpp" + +namespace memgraph::utils { + +// Calls a function with out of memory exception blocker, checks memory allocation after block execution. +// Use it in case you need block which will be executed atomically considering memory execution +// but will check after block is executed if OOM exceptions needs to be thrown +template <typename Callable> +class [[nodiscard]] AtomicMemoryBlock { + public: + explicit AtomicMemoryBlock(Callable &&function) : function_{std::forward<Callable>(function)} {} + AtomicMemoryBlock(AtomicMemoryBlock const &) = delete; + AtomicMemoryBlock(AtomicMemoryBlock &&) = delete; + AtomicMemoryBlock &operator=(AtomicMemoryBlock const &) = delete; + AtomicMemoryBlock &operator=(AtomicMemoryBlock &&) = delete; + ~AtomicMemoryBlock() = default; + + void operator()() { + { + utils::MemoryTracker::OutOfMemoryExceptionBlocker oom_blocker; + function_(); + } + total_memory_tracker.DoCheck(); + } + + private: + Callable function_; +}; + +} // namespace memgraph::utils diff --git a/src/utils/counter.hpp b/src/utils/counter.hpp new file mode 100644 index 000000000..0d9aabca8 --- /dev/null +++ b/src/utils/counter.hpp @@ -0,0 +1,29 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include <cstdint> + +namespace memgraph::utils { + +/// A resetable counter, every Nth call returns true +template <std::size_t N> +auto ResettableCounter() { + return [counter = N]() mutable { + --counter; + if (counter != 0) return false; + counter = N; + return true; + }; +} + +} // namespace memgraph::utils diff --git a/src/utils/enum.hpp b/src/utils/enum.hpp index 505802088..50a6d4621 100644 --- a/src/utils/enum.hpp +++ b/src/utils/enum.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -54,4 +54,46 @@ std::optional<Enum> StringToEnum(const auto &value, const auto &mappings) { return mapping_iter->second; } + +// Tries to convert a enum into string, which would then contain a value if the conversion +// has been successful. +template <typename Enum> +auto EnumToString(const auto &value, const auto &mappings) -> std::optional<std::string_view> { + const auto mapping_iter = + std::find_if(mappings.begin(), mappings.end(), [&](const auto &mapping) { return mapping.second == value; }); + if (mapping_iter == mappings.cend()) { + return std::nullopt; + } + return mapping_iter->first; +} + +template <typename T, typename Enum> +requires std::integral<T> +inline T EnumToNum(Enum res) { + static_assert(std::numeric_limits<T>::max() >= static_cast<size_t>(Enum::N)); + return static_cast<T>(res); +} + +template <typename T, typename Enum> +requires std::integral<T> +inline bool NumToEnum(T input, Enum &res) { + if (input >= EnumToNum<T>(Enum::N)) return false; + res = static_cast<Enum>(input); + return true; +} + +template <size_t Num, typename T, typename Enum> +requires std::integral<T> +inline T EnumToNum(Enum res) { + static_assert(std::numeric_limits<T>::max() >= Num); + return static_cast<T>(res); +} + +template <size_t Num, typename T, typename Enum> +requires std::integral<T> +inline bool NumToEnum(T input, Enum &res) { + if (input >= Num) return false; + res = static_cast<Enum>(input); + return true; +} } // namespace memgraph::utils diff --git a/src/utils/file.cpp b/src/utils/file.cpp index de6590620..73ea424ac 100644 --- a/src/utils/file.cpp +++ b/src/utils/file.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -504,7 +504,7 @@ void OutputFile::Close() noexcept { void OutputFile::FlushBuffer(bool force_flush) { MG_ASSERT(IsOpen(), "Flushing an unopend file."); - if (!force_flush && buffer_position_.load() < kFileBufferSize) return; + if (!force_flush && buffer_position_ < kFileBufferSize) return; std::unique_lock flush_guard(flush_lock_); FlushBufferInternal(); diff --git a/src/utils/gatekeeper.hpp b/src/utils/gatekeeper.hpp index 21dad2543..862cad982 100644 --- a/src/utils/gatekeeper.hpp +++ b/src/utils/gatekeeper.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -78,21 +78,33 @@ struct EvalResult { template <typename Func, typename T> EvalResult(run_t, Func &&, T &) -> EvalResult<std::invoke_result_t<Func, T &>>; +template <typename T> +struct GKInternals { + template <typename... Args> + explicit GKInternals(Args &&...args) : value_{std::in_place, std::forward<Args>(args)...} {} + + std::optional<T> value_; + uint64_t count_ = 0; + std::atomic_bool is_deleting = false; + std::mutex mutex_; // TODO change to something cheaper? + std::condition_variable cv_; +}; + template <typename T> struct Gatekeeper { template <typename... Args> - explicit Gatekeeper(Args &&...args) : value_{std::in_place, std::forward<Args>(args)...} {} + explicit Gatekeeper(Args &&...args) : pimpl_(std::make_unique<GKInternals<T>>(std::forward<Args>(args)...)) {} Gatekeeper(Gatekeeper const &) = delete; - Gatekeeper(Gatekeeper &&) noexcept = delete; + Gatekeeper(Gatekeeper &&) noexcept = default; Gatekeeper &operator=(Gatekeeper const &) = delete; - Gatekeeper &operator=(Gatekeeper &&) = delete; + Gatekeeper &operator=(Gatekeeper &&) noexcept = default; struct Accessor { friend Gatekeeper; private: - explicit Accessor(Gatekeeper *owner) : owner_{owner} { ++owner_->count_; } + explicit Accessor(Gatekeeper *owner) : owner_{owner->pimpl_.get()} { ++owner_->count_; } public: Accessor(Accessor const &other) : owner_{other.owner_} { @@ -139,6 +151,14 @@ struct Gatekeeper { return *this; } + [[nodiscard]] bool is_deleting() const { return owner_->is_deleting; } + + void prepare_for_deletion() { + if (owner_) { + owner_->is_deleting = true; + } + } + ~Accessor() { reset(); } auto get() -> T * { return std::addressof(*owner_->value_); } @@ -159,18 +179,26 @@ struct Gatekeeper { } // Completely invalidated the accessor if return true - [[nodiscard]] bool try_delete(std::chrono::milliseconds timeout = std::chrono::milliseconds(100)) { + template <typename Func = decltype([](T &) { return true; })> + [[nodiscard]] bool try_delete(std::chrono::milliseconds timeout = std::chrono::milliseconds(100), + Func &&predicate = {}) { // Prevent new access auto guard = std::unique_lock{owner_->mutex_}; if (!owner_->cv_.wait_for(guard, timeout, [this] { return owner_->count_ == 1; })) { return false; } - // Delete value + // Already deleted + if (owner_->value_ == std::nullopt) return true; + // Delete value if ok + if (!predicate(*owner_->value_)) return false; owner_->value_ = std::nullopt; return true; } - explicit operator bool() const { return owner_ != nullptr; } + explicit operator bool() const { + return owner_ != nullptr // we have access + && !owner_->is_deleting; // AND we are allowed to use it + } void reset() { if (owner_) { @@ -186,28 +214,27 @@ struct Gatekeeper { friend bool operator==(Accessor const &lhs, Accessor const &rhs) { return lhs.owner_ == rhs.owner_; } private: - Gatekeeper *owner_ = nullptr; + GKInternals<T> *owner_ = nullptr; }; std::optional<Accessor> access() { - auto guard = std::unique_lock{mutex_}; - if (value_) { + auto guard = std::unique_lock{pimpl_->mutex_}; + if (pimpl_->value_) { return Accessor{this}; } return std::nullopt; } ~Gatekeeper() { + if (!pimpl_) return; // Moved out, nothing to do + pimpl_->is_deleting = true; // wait for count to drain to 0 - auto lock = std::unique_lock{mutex_}; - cv_.wait(lock, [this] { return count_ == 0; }); + auto lock = std::unique_lock{pimpl_->mutex_}; + pimpl_->cv_.wait(lock, [this] { return pimpl_->count_ == 0; }); } private: - std::optional<T> value_; - uint64_t count_ = 0; - std::mutex mutex_; // TODO change to something cheaper? - std::condition_variable cv_; + std::unique_ptr<GKInternals<T>> pimpl_; }; } // namespace memgraph::utils diff --git a/src/utils/memory_tracker.cpp b/src/utils/memory_tracker.cpp index 029223b71..7dfd88416 100644 --- a/src/utils/memory_tracker.cpp +++ b/src/utils/memory_tracker.cpp @@ -124,6 +124,19 @@ void MemoryTracker::Alloc(const int64_t size) { UpdatePeak(will_be); } +void MemoryTracker::DoCheck() { + const auto current_hard_limit = hard_limit_.load(std::memory_order_relaxed); + const auto current_amount = amount_.load(std::memory_order_relaxed); + if (current_hard_limit && current_amount > current_hard_limit && MemoryTrackerCanThrow()) [[unlikely]] { + MemoryTracker::OutOfMemoryExceptionBlocker exception_blocker; + throw OutOfMemoryException( + fmt::format("Memory limit exceeded! Current " + "use is {}, while the maximum allowed size for allocation is set to {}.", + GetReadableSize(static_cast<double>(current_amount)), + GetReadableSize(static_cast<double>(current_hard_limit)))); + } +} + void MemoryTracker::Free(const int64_t size) { amount_.fetch_sub(size, std::memory_order_relaxed); } } // namespace memgraph::utils diff --git a/src/utils/memory_tracker.hpp b/src/utils/memory_tracker.hpp index 0da888161..a6d7221ff 100644 --- a/src/utils/memory_tracker.hpp +++ b/src/utils/memory_tracker.hpp @@ -49,6 +49,7 @@ class MemoryTracker final { void Alloc(int64_t size); void Free(int64_t size); + void DoCheck(); auto Amount() const { return amount_.load(std::memory_order_relaxed); } diff --git a/src/utils/on_scope_exit.hpp b/src/utils/on_scope_exit.hpp index 70aec52b6..1c0b34fef 100644 --- a/src/utils/on_scope_exit.hpp +++ b/src/utils/on_scope_exit.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -11,6 +11,7 @@ #pragma once +#include <concepts> #include <functional> namespace memgraph::utils { @@ -37,7 +38,9 @@ namespace memgraph::utils { template <typename Callable> class [[nodiscard]] OnScopeExit { public: - explicit OnScopeExit(Callable &&function) : function_{std::forward<Callable>(function)}, doCall_{true} {} + template <typename U> + requires std::constructible_from<Callable, U> + explicit OnScopeExit(U &&function) : function_{std::forward<U>(function)}, doCall_{true} {} OnScopeExit(OnScopeExit const &) = delete; OnScopeExit(OnScopeExit &&) = delete; OnScopeExit &operator=(OnScopeExit const &) = delete; diff --git a/src/utils/resource_lock.hpp b/src/utils/resource_lock.hpp index 7d6be2685..7a3ef9444 100644 --- a/src/utils/resource_lock.hpp +++ b/src/utils/resource_lock.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -48,6 +48,15 @@ struct ResourceLock { } return false; } + + template <class Rep, class Period> + bool try_lock_for(const std::chrono::duration<Rep, Period> &timeout_duration) { + auto lock = std::unique_lock{mtx}; + if (!cv.wait_for(lock, timeout_duration, [this] { return state == UNLOCKED; })) return false; + state = UNIQUE; + return true; + } + bool try_lock_shared() { auto lock = std::unique_lock{mtx}; if (state != UNIQUE) { @@ -71,6 +80,22 @@ struct ResourceLock { } } + void upgrade_to_unique() { + auto lock = std::unique_lock{mtx}; + cv.wait(lock, [this] { return count == 1; }); + state = UNIQUE; + count = 0; + } + + template <class Rep, class Period> + bool try_upgrade_to_unique(const std::chrono::duration<Rep, Period> &timeout_duration) { + auto lock = std::unique_lock{mtx}; + if (!cv.wait_for(lock, timeout_duration, [this] { return count == 1; })) return false; + state = UNIQUE; + count = 0; + return true; + } + private: std::mutex mtx; std::condition_variable cv; @@ -78,4 +103,46 @@ struct ResourceLock { uint64_t count = 0; }; +struct ResourceLockGuard { + private: + enum states { UNIQUE, SHARED }; + + public: + explicit ResourceLockGuard(ResourceLock &thing) + : ptr{&thing}, state{[this]() { + ptr->lock_shared(); + return SHARED; + }()} {} + + void upgrade_to_unique() { + if (state == SHARED) { + ptr->upgrade_to_unique(); + state = UNIQUE; + } + } + + template <class Rep, class Period> + bool try_upgrade_to_unique(const std::chrono::duration<Rep, Period> &timeout_duration) { + if (state != SHARED) return true; // already locked + if (!ptr->try_upgrade_to_unique(timeout_duration)) return false; // timeout + state = UNIQUE; + return true; + } + + ~ResourceLockGuard() { + switch (state) { + case UNIQUE: + ptr->unlock(); + break; + case SHARED: + ptr->unlock_shared(); + break; + } + } + + private: + ResourceLock *ptr; + states state; +}; + } // namespace memgraph::utils diff --git a/src/utils/scheduler.hpp b/src/utils/scheduler.hpp index d96178598..742271a95 100644 --- a/src/utils/scheduler.hpp +++ b/src/utils/scheduler.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -58,28 +58,40 @@ class Scheduler { // the start of the program. Since Server will log some messages on // the program start we let him log first and we make sure by first // waiting that funcion f will not log before it. + // Check for pause also. std::unique_lock<std::mutex> lk(mutex_); auto now = std::chrono::system_clock::now(); start_time += pause; if (start_time > now) { - condition_variable_.wait_until(lk, start_time, [&] { return is_working_.load() == false; }); + condition_variable_.wait_until(lk, start_time, [&] { return !is_working_.load(); }); } else { start_time = now; } + pause_cv_.wait(lk, [&] { return !is_paused_.load(); }); + if (!is_working_) break; f(); } }); } + void Resume() { + is_paused_.store(false); + pause_cv_.notify_one(); + } + + void Pause() { is_paused_.store(true); } + /** * @brief Stops the thread execution. This is a blocking call and may take as * much time as one call to the function given previously to Run takes. * @throw std::system_error */ void Stop() { + is_paused_.store(false); is_working_.store(false); + pause_cv_.notify_one(); condition_variable_.notify_one(); if (thread_.joinable()) thread_.join(); } @@ -97,6 +109,16 @@ class Scheduler { */ std::atomic<bool> is_working_{false}; + /** + * Variable is true when thread is paused. + */ + std::atomic<bool> is_paused_{false}; + + /* + * Wait until the thread is resumed. + */ + std::condition_variable pause_cv_; + /** * Mutex used to synchronize threads using condition variable. */ diff --git a/src/utils/settings.cpp b/src/utils/settings.cpp index 330b43f48..4768edc42 100644 --- a/src/utils/settings.cpp +++ b/src/utils/settings.cpp @@ -27,6 +27,7 @@ void Settings::Finalize() { std::lock_guard settings_guard{settings_lock_}; storage_.reset(); on_change_callbacks_.clear(); + validations_.clear(); } void Settings::RegisterSetting(std::string name, const std::string &default_value, OnChangeCallback callback, diff --git a/src/utils/skip_list.hpp b/src/utils/skip_list.hpp index de4892375..193d83b0b 100644 --- a/src/utils/skip_list.hpp +++ b/src/utils/skip_list.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -365,9 +365,7 @@ class SkipListGc final { leftover.Push(*item); } } - while ((item = leftover.Pop())) { - deleted_.Push(*item); - } + deleted_ = std::move(leftover); } MemoryResource *GetMemoryResource() const { return memory_; } @@ -384,11 +382,14 @@ class SkipListGc final { } // Delete all items that have to be garbage collected. - std::optional<TDeleted> item; - while ((item = deleted_.Pop())) { - size_t bytes = SkipListNodeSize(*item->second); - item->second->~TNode(); - memory_->Deallocate(item->second, bytes); + { + std::optional<TDeleted> item; + std::unique_lock guard(lock_); + while ((item = deleted_.Pop())) { + size_t bytes = SkipListNodeSize(*item->second); + item->second->~TNode(); + memory_->Deallocate(item->second, bytes); + } } // Reset all variables. @@ -591,13 +592,20 @@ class SkipList final : detail::SkipListNode_base { Iterator(TNode *node) : node_(node) {} public: - TObj &operator*() const { return node_->obj; } + using value_type = TObj; + using difference_type = std::ptrdiff_t; - TObj *operator->() const { return &node_->obj; } + Iterator() = default; + Iterator(Iterator const &) = default; + Iterator(Iterator &&) = default; + Iterator &operator=(Iterator const &) = default; + Iterator &operator=(Iterator &&) = default; - bool operator==(const Iterator &other) const { return node_ == other.node_; } + value_type &operator*() const { return node_->obj; } - bool operator!=(const Iterator &other) const { return node_ != other.node_; } + value_type *operator->() const { return &node_->obj; } + + friend bool operator==(Iterator const &lhs, Iterator const &rhs) { return lhs.node_ == rhs.node_; } Iterator &operator++() { while (true) { @@ -610,8 +618,14 @@ class SkipList final : detail::SkipListNode_base { } } + Iterator operator++(int) { + Iterator old = *this; + ++(*this); + return old; + } + private: - TNode *node_; + TNode *node_{nullptr}; }; class ConstIterator final { @@ -621,15 +635,22 @@ class SkipList final : detail::SkipListNode_base { ConstIterator(TNode *node) : node_(node) {} public: + using value_type = TObj const; + using difference_type = std::ptrdiff_t; + + ConstIterator() = default; + ConstIterator(ConstIterator const &) = default; + ConstIterator(ConstIterator &&) = default; + ConstIterator &operator=(ConstIterator const &) = default; + ConstIterator &operator=(ConstIterator &&) = default; + ConstIterator(const Iterator &it) : node_(it.node_) {} - const TObj &operator*() const { return node_->obj; } + value_type &operator*() const { return node_->obj; } - const TObj *operator->() const { return &node_->obj; } + value_type *operator->() const { return &node_->obj; } - bool operator==(const ConstIterator &other) const { return node_ == other.node_; } - - bool operator!=(const ConstIterator &other) const { return node_ != other.node_; } + friend bool operator==(ConstIterator const &lhs, ConstIterator const &rhs) { return lhs.node_ == rhs.node_; } ConstIterator &operator++() { while (true) { @@ -642,6 +663,12 @@ class SkipList final : detail::SkipListNode_base { } } + ConstIterator operator++(int) { + ConstIterator old = *this; + ++(*this); + return old; + } + private: TNode *node_; }; @@ -653,6 +680,10 @@ class SkipList final : detail::SkipListNode_base { explicit Accessor(SkipList *skiplist) : skiplist_(skiplist), id_(skiplist->gc_.AllocateId()) {} public: + using value_type = TObj; + using iterator = Iterator; + using const_iterator = ConstIterator; + ~Accessor() { if (skiplist_ != nullptr) skiplist_->gc_.ReleaseId(id_); } @@ -695,7 +726,7 @@ class SkipList final : detail::SkipListNode_base { /// @return bool indicating whether the item exists template <typename TKey> bool contains(const TKey &key) const { - return skiplist_->template contains(key); + return skiplist_->contains(key); } /// Finds the key in the list and returns an iterator to the item. @@ -703,8 +734,17 @@ class SkipList final : detail::SkipListNode_base { /// @return Iterator to the item in the list, will be equal to `end()` when /// the key isn't found template <typename TKey> - Iterator find(const TKey &key) const { - return skiplist_->template find(key); + Iterator find(const TKey &key) { + return skiplist_->find(key); + } + + /// Finds the key in the list and returns an iterator to the item. + /// + /// @return ConstIterator to the item in the list, will be equal to `cend()` when + /// the key isn't found + template <typename TKey> + ConstIterator find(const TKey &key) const { + return skiplist_->find(key); } /// Finds the key or the first larger key in the list and returns an @@ -713,8 +753,18 @@ class SkipList final : detail::SkipListNode_base { /// @return Iterator to the item in the list, will be equal to `end()` when /// no items match the search template <typename TKey> - Iterator find_equal_or_greater(const TKey &key) const { - return skiplist_->template find_equal_or_greater(key); + Iterator find_equal_or_greater(const TKey &key) { + return skiplist_->find_equal_or_greater(key); + } + + /// Finds the key or the first larger key in the list and returns an + /// iterator to the item. + /// + /// @return ConstIterator to the item in the list, will be equal to `end()` when + /// no items match the search + template <typename TKey> + ConstIterator find_equal_or_greater(const TKey &key) const { + return skiplist_->find_equal_or_greater(key); } /// Estimates the number of items that are contained in the list that are @@ -727,7 +777,7 @@ class SkipList final : detail::SkipListNode_base { /// @return uint64_t estimated count of identical items in the list template <typename TKey> uint64_t estimate_count(const TKey &key, int max_layer_for_estimation = kSkipListCountEstimateDefaultLayer) const { - return skiplist_->template estimate_count(key, max_layer_for_estimation); + return skiplist_->estimate_count(key, max_layer_for_estimation); } /// Estimates the number of items that are contained in the list that are @@ -742,7 +792,7 @@ class SkipList final : detail::SkipListNode_base { uint64_t estimate_range_count(const std::optional<utils::Bound<TKey>> &lower, const std::optional<utils::Bound<TKey>> &upper, int max_layer_for_estimation = kSkipListCountEstimateDefaultLayer) const { - return skiplist_->template estimate_range_count(lower, upper, max_layer_for_estimation); + return skiplist_->estimate_range_count(lower, upper, max_layer_for_estimation); } /// Estimates the average number of objects in the list that have the same @@ -759,7 +809,7 @@ class SkipList final : detail::SkipListNode_base { template <typename TCallable> uint64_t estimate_average_number_of_equals( const TCallable &equal_cmp, int max_layer_for_estimation = kSkipListCountEstimateDefaultLayer) const { - return skiplist_->template estimate_average_number_of_equals(equal_cmp, max_layer_for_estimation); + return skiplist_->estimate_average_number_of_equals(equal_cmp, max_layer_for_estimation); } /// Removes the key from the list. @@ -767,7 +817,7 @@ class SkipList final : detail::SkipListNode_base { /// @return bool indicating whether the removal was successful template <typename TKey> bool remove(const TKey &key) { - return skiplist_->template remove(key); + return skiplist_->remove(key); } /// Returns the number of items contained in the list. @@ -787,6 +837,10 @@ class SkipList final : detail::SkipListNode_base { explicit ConstAccessor(const SkipList *skiplist) : skiplist_(skiplist), id_(skiplist->gc_.AllocateId()) {} public: + using value_type = TObj; + using iterator = ConstIterator; + using const_iterator = ConstIterator; + ~ConstAccessor() { if (skiplist_ != nullptr) skiplist_->gc_.ReleaseId(id_); } @@ -812,35 +866,35 @@ class SkipList final : detail::SkipListNode_base { template <typename TKey> bool contains(const TKey &key) const { - return skiplist_->template contains(key); + return skiplist_->contains(key); } template <typename TKey> ConstIterator find(const TKey &key) const { - return skiplist_->template find(key); + return skiplist_->find(key); } template <typename TKey> ConstIterator find_equal_or_greater(const TKey &key) const { - return skiplist_->template find_equal_or_greater(key); + return skiplist_->find_equal_or_greater(key); } template <typename TKey> uint64_t estimate_count(const TKey &key, int max_layer_for_estimation = kSkipListCountEstimateDefaultLayer) const { - return skiplist_->template estimate_count(key, max_layer_for_estimation); + return skiplist_->estimate_count(key, max_layer_for_estimation); } template <typename TKey> uint64_t estimate_range_count(const std::optional<utils::Bound<TKey>> &lower, const std::optional<utils::Bound<TKey>> &upper, int max_layer_for_estimation = kSkipListCountEstimateDefaultLayer) const { - return skiplist_->template estimate_range_count(lower, upper, max_layer_for_estimation); + return skiplist_->estimate_range_count(lower, upper, max_layer_for_estimation); } template <typename TCallable> uint64_t estimate_average_number_of_equals( const TCallable &equal_cmp, int max_layer_for_estimation = kSkipListCountEstimateDefaultLayer) const { - return skiplist_->template estimate_average_number_of_equals(equal_cmp, max_layer_for_estimation); + return skiplist_->estimate_average_number_of_equals(equal_cmp, max_layer_for_estimation); } uint64_t size() const { return skiplist_->size(); } @@ -975,40 +1029,46 @@ class SkipList final : detail::SkipListNode_base { continue; } - std::unique_lock<SpinLock> guards[kSkipListMaxHeight]; - TNode *pred, *succ, *prev_pred = nullptr; - bool valid = true; - // The paper has a wrong condition here. In the paper it states that this - // loop should have `(layer <= top_layer)`, but that isn't correct. - for (int layer = 0; valid && (layer < top_layer); ++layer) { - pred = preds[layer]; - succ = succs[layer]; - if (pred != prev_pred) { - guards[layer] = std::unique_lock<SpinLock>(pred->lock); - prev_pred = pred; + TNode *new_node; + { + TNode *prev_pred = nullptr; + bool valid = true; + std::unique_lock<SpinLock> guards[kSkipListMaxHeight]; + // The paper has a wrong condition here. In the paper it states that this + // loop should have `(layer <= top_layer)`, but that isn't correct. + for (int layer = 0; valid && (layer < top_layer); ++layer) { + TNode *pred = preds[layer]; + TNode *succ = succs[layer]; + if (pred != prev_pred) { + guards[layer] = std::unique_lock<SpinLock>(pred->lock); + prev_pred = pred; + } + // Existence test is missing in the paper. + valid = !pred->marked.load(std::memory_order_acquire) && + pred->nexts[layer].load(std::memory_order_acquire) == succ && + (succ == nullptr || !succ->marked.load(std::memory_order_acquire)); } - // Existence test is missing in the paper. - valid = !pred->marked.load(std::memory_order_acquire) && - pred->nexts[layer].load(std::memory_order_acquire) == succ && - (succ == nullptr || !succ->marked.load(std::memory_order_acquire)); - } - if (!valid) continue; + if (!valid) continue; - size_t node_bytes = sizeof(TNode) + top_layer * sizeof(std::atomic<TNode *>); - void *ptr = GetMemoryResource()->Allocate(node_bytes); - // `calloc` would be faster, but the API has no such call. - memset(ptr, 0, node_bytes); - auto *new_node = static_cast<TNode *>(ptr); - // Construct through allocator so it propagates if needed. - Allocator<TNode> allocator(GetMemoryResource()); - allocator.construct(new_node, top_layer, std::forward<TObjUniv>(object)); + size_t node_bytes = sizeof(TNode) + top_layer * sizeof(std::atomic<TNode *>); - // The paper is also wrong here. It states that the loop should go up to - // `top_layer` which is wrong. - for (int layer = 0; layer < top_layer; ++layer) { - new_node->nexts[layer].store(succs[layer], std::memory_order_release); - preds[layer]->nexts[layer].store(new_node, std::memory_order_release); + MemoryResource *memoryResource = GetMemoryResource(); + void *ptr = memoryResource->Allocate(node_bytes); + // `calloc` would be faster, but the API has no such call. + memset(ptr, 0, node_bytes); + new_node = static_cast<TNode *>(ptr); + + // Construct through allocator so it propagates if needed. + Allocator<TNode> allocator(memoryResource); + allocator.construct(new_node, top_layer, std::forward<TObjUniv>(object)); + + // The paper is also wrong here. It states that the loop should go up to + // `top_layer` which is wrong. + for (int layer = 0; layer < top_layer; ++layer) { + new_node->nexts[layer].store(succs[layer], std::memory_order_release); + preds[layer]->nexts[layer].store(new_node, std::memory_order_release); + } } new_node->fully_linked.store(true, std::memory_order_release); @@ -1018,26 +1078,33 @@ class SkipList final : detail::SkipListNode_base { } template <typename TKey> - bool contains(const TKey &key) const { - TNode *preds[kSkipListMaxHeight], *succs[kSkipListMaxHeight]; - int layer_found = find_node(key, preds, succs); - return (layer_found != -1 && succs[layer_found]->fully_linked.load(std::memory_order_acquire) && - !succs[layer_found]->marked.load(std::memory_order_acquire)); - } - - template <typename TKey> - Iterator find(const TKey &key) const { + SkipListNode<TObj> *find_(const TKey &key) const { TNode *preds[kSkipListMaxHeight], *succs[kSkipListMaxHeight]; int layer_found = find_node(key, preds, succs); if (layer_found != -1 && succs[layer_found]->fully_linked.load(std::memory_order_acquire) && !succs[layer_found]->marked.load(std::memory_order_acquire)) { - return Iterator{succs[layer_found]}; + return succs[layer_found]; } - return Iterator{nullptr}; + return nullptr; } template <typename TKey> - Iterator find_equal_or_greater(const TKey &key) const { + bool contains(const TKey &key) const { + return find_(key) != nullptr; + } + + template <typename TKey> + Iterator find(const TKey &key) { + return {find_(key)}; + } + + template <typename TKey> + ConstIterator find(const TKey &key) const { + return {find_(key)}; + } + + template <typename TKey> + Iterator find_equal_or_greater_(const TKey &key) const { TNode *preds[kSkipListMaxHeight], *succs[kSkipListMaxHeight]; find_node(key, preds, succs); if (succs[0] && succs[0]->fully_linked.load(std::memory_order_acquire) && @@ -1047,6 +1114,16 @@ class SkipList final : detail::SkipListNode_base { return Iterator{nullptr}; } + template <typename TKey> + Iterator find_equal_or_greater(const TKey &key) { + return {find_equal_or_greater_(key)}; + } + + template <typename TKey> + ConstIterator find_equal_or_greater(const TKey &key) const { + return {find_equal_or_greater_(key)}; + } + template <typename TKey> uint64_t estimate_count(const TKey &key, int max_layer_for_estimation) const { MG_ASSERT(max_layer_for_estimation >= 1 && max_layer_for_estimation <= kSkipListMaxHeight, diff --git a/src/utils/stat.hpp b/src/utils/stat.hpp index 7e4fab29b..4c2eec6d6 100644 --- a/src/utils/stat.hpp +++ b/src/utils/stat.hpp @@ -25,17 +25,32 @@ namespace memgraph::utils { static constexpr int64_t VM_MAX_MAP_COUNT_DEFAULT{-1}; /// Returns the number of bytes a directory is using on disk. If the given path -/// isn't a directory, zero will be returned. +/// isn't a directory, zero will be returned. If there are some files with +/// wrong permission, it will be skipped template <bool IgnoreSymlink = true> inline uint64_t GetDirDiskUsage(const std::filesystem::path &path) { if (!std::filesystem::is_directory(path)) return 0; + if (!utils::HasReadAccess(path)) { + spdlog::warn( + "Skipping directory path on collecting directory disk usage '{}' because it is not readable, check file " + "ownership and read permissions!", + path); + return 0; + } uint64_t size = 0; - for (auto &p : std::filesystem::directory_iterator(path)) { + for (const auto &p : std::filesystem::directory_iterator(path)) { if (IgnoreSymlink && std::filesystem::is_symlink(p)) continue; if (std::filesystem::is_directory(p)) { size += GetDirDiskUsage(p); } else if (std::filesystem::is_regular_file(p)) { + if (!utils::HasReadAccess(p)) { + spdlog::warn( + "Skipping file path on collecting directory disk usage '{}' because it is not readable, check file " + "ownership and read permissions!", + p); + continue; + } size += std::filesystem::file_size(p); } } diff --git a/src/utils/string.hpp b/src/utils/string.hpp index 833c158c8..8593fc57f 100644 --- a/src/utils/string.hpp +++ b/src/utils/string.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -17,6 +17,7 @@ #include <charconv> #include <cstdint> #include <cstring> +#include <iomanip> #include <iostream> #include <iterator> #include <random> @@ -459,4 +460,30 @@ inline std::string_view Substr(const std::string_view string, size_t pos = 0, si return string.substr(pos, len); } +/** + * Convert a double value to a string representation. + * Precision of converted value is 16. + * Function also removes trailing zeros after the dot. + * + * @param value The double value to be converted. + * + * @return The string representation of the double value. + * + * @throws None + */ +inline std::string DoubleToString(const double value) { + static const int PRECISION = 15; + + std::stringstream ss; + ss << std::setprecision(PRECISION) << std::fixed << value; + auto sv = ss.view(); + + // Because of setprecision and fixed manipulator we are guaranteed to have the dot + sv = sv.substr(0, sv.find_last_not_of('0') + 1); + if (sv.ends_with('.')) { + sv = sv.substr(0, sv.size() - 1); + } + return std::string(sv); +} + } // namespace memgraph::utils diff --git a/src/utils/thread_pool.hpp b/src/utils/thread_pool.hpp index 8597a78a1..c6f0bb6ee 100644 --- a/src/utils/thread_pool.hpp +++ b/src/utils/thread_pool.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -23,6 +23,16 @@ namespace memgraph::utils { +template <typename Func> +struct CopyMovableFunctionWrapper { + CopyMovableFunctionWrapper(Func &&func) : func_{std::make_shared<Func>(std::move(func))} {} + + void operator()() { (*func_)(); } + + private: + std::shared_ptr<Func> func_; +}; + class ThreadPool { using TaskSignature = std::function<void()>; diff --git a/src/utils/typeinfo.hpp b/src/utils/typeinfo.hpp index 682b5ac55..fd0d1fdeb 100644 --- a/src/utils/typeinfo.hpp +++ b/src/utils/typeinfo.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -17,9 +17,10 @@ namespace memgraph::utils { enum class TypeId : uint64_t { + UNKNOWN = 0, + // Operators - UNKNOWN, - LOGICAL_OPERATOR, + LOGICAL_OPERATOR = 1000, ONCE, NODE_CREATION_INFO, CREATE_NODE, @@ -69,7 +70,8 @@ enum class TypeId : uint64_t { HASH_JOIN, // Replication - REP_APPEND_DELTAS_REQ, + // NOTE: these NEED to be stable in the 2000+ range (see rpc version) + REP_APPEND_DELTAS_REQ = 2000, REP_APPEND_DELTAS_RES, REP_HEARTBEAT_REQ, REP_HEARTBEAT_RES, @@ -83,9 +85,23 @@ enum class TypeId : uint64_t { REP_CURRENT_WAL_RES, REP_TIMESTAMP_REQ, REP_TIMESTAMP_RES, + REP_CREATE_DATABASE_REQ, + REP_CREATE_DATABASE_RES, + REP_DROP_DATABASE_REQ, + REP_DROP_DATABASE_RES, + REP_SYSTEM_HEARTBEAT_REQ, + REP_SYSTEM_HEARTBEAT_RES, + REP_SYSTEM_RECOVERY_REQ, + REP_SYSTEM_RECOVERY_RES, + + // Coordinator + COORD_FAILOVER_REQ, + COORD_FAILOVER_RES, + COORD_SET_REPL_MAIN_REQ, + COORD_SET_REPL_MAIN_RES, // AST - AST_LABELIX, + AST_LABELIX = 3000, AST_PROPERTYIX, AST_EDGETYPEIX, AST_TREE, @@ -190,8 +206,11 @@ enum class TypeId : uint64_t { AST_MULTI_DATABASE_QUERY, AST_SHOW_DATABASES, AST_EDGE_IMPORT_MODE_QUERY, + AST_PATTERN_COMPREHENSION, + AST_COORDINATOR_QUERY, + // Symbol - SYMBOL, + SYMBOL = 4000, }; /// Type information on a C++ type. diff --git a/src/utils/uuid.cpp b/src/utils/uuid.cpp index 9b13b8965..fbcf662de 100644 --- a/src/utils/uuid.cpp +++ b/src/utils/uuid.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -10,8 +10,8 @@ // licenses/APL.txt. #include "utils/uuid.hpp" - #include <uuid/uuid.h> +#include "slk/serialization.hpp" namespace memgraph::utils { @@ -24,3 +24,13 @@ std::string GenerateUUID() { } } // namespace memgraph::utils + +// Serialize UUID +namespace memgraph::slk { +void Save(const memgraph::utils::UUID &self, memgraph::slk::Builder *builder) { + const auto &arr = static_cast<utils::UUID::arr_t>(self); + memgraph::slk::Save(arr, builder); +} + +void Load(memgraph::utils::UUID *self, memgraph::slk::Reader *reader) { memgraph::slk::Load(&self->uuid, reader); } +} // namespace memgraph::slk diff --git a/src/utils/uuid.hpp b/src/utils/uuid.hpp index 8bbb1a1a1..bca55d73b 100644 --- a/src/utils/uuid.hpp +++ b/src/utils/uuid.hpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -11,8 +11,22 @@ #pragma once +#include <uuid/uuid.h> +#include <array> +#include <json/json.hpp> #include <string> +namespace memgraph::utils { +struct UUID; +} + +namespace memgraph::slk { +class Reader; +class Builder; +void Save(const ::memgraph::utils::UUID &self, Builder *builder); +void Load(::memgraph::utils::UUID *self, Reader *reader); +} // namespace memgraph::slk + namespace memgraph::utils { /** @@ -20,4 +34,35 @@ namespace memgraph::utils { */ std::string GenerateUUID(); +struct UUID { + using arr_t = std::array<unsigned char, 16>; + + UUID() { uuid_generate(uuid.data()); } + explicit operator std::string() const { + auto decoded = std::array<char, UUID_STR_LEN>{}; + uuid_unparse(uuid.data(), decoded.data()); + return std::string{decoded.data(), UUID_STR_LEN - 1}; + } + + explicit operator arr_t() const { return uuid; } + + friend bool operator==(UUID const &, UUID const &) = default; + + private: + friend void to_json(nlohmann::json &j, const UUID &uuid); + friend void from_json(const nlohmann::json &j, UUID &uuid); + friend void ::memgraph::slk::Load(UUID *self, slk::Reader *reader); + explicit UUID(arr_t const &arr) : uuid(arr) {} + + arr_t uuid; +}; + +inline void to_json(nlohmann::json &j, const UUID &uuid) { j = nlohmann::json(uuid.uuid); } + +inline void from_json(const nlohmann::json &j, UUID &uuid) { + auto arr = UUID::arr_t{}; + j.get_to(arr); + uuid = UUID(arr); +} + } // namespace memgraph::utils diff --git a/tests/benchmark/query/eval.cpp b/tests/benchmark/query/eval.cpp index 5e5acea7f..92ba67cd6 100644 --- a/tests/benchmark/query/eval.cpp +++ b/tests/benchmark/query/eval.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -17,6 +17,7 @@ #include "storage/v2/inmemory/storage.hpp" #include "storage/v2/storage.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; // The following classes are wrappers for memgraph::utils::MemoryResource, so that we can // use BENCHMARK_TEMPLATE @@ -40,7 +41,7 @@ static void MapLiteral(benchmark::State &state) { TMemory memory; memgraph::query::Frame frame(symbol_table.max_position(), memory.get()); std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); - auto storage_dba = db->Access(); + auto storage_dba = db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); std::unordered_map<memgraph::query::PropertyIx, memgraph::query::Expression *> elements; for (int64_t i = 0; i < state.range(0); ++i) { @@ -71,7 +72,7 @@ static void AdditionOperator(benchmark::State &state) { TMemory memory; memgraph::query::Frame frame(symbol_table.max_position(), memory.get()); std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); - auto storage_dba = db->Access(); + auto storage_dba = db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); memgraph::query::Expression *expr = ast.Create<memgraph::query::PrimitiveLiteral>(0); for (int64_t i = 0; i < state.range(0); ++i) { diff --git a/tests/benchmark/query/execution.cpp b/tests/benchmark/query/execution.cpp index 771f0ac1f..d49b14fc3 100644 --- a/tests/benchmark/query/execution.cpp +++ b/tests/benchmark/query/execution.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -23,6 +23,7 @@ // variable of the same name, EOF. // This hides the definition of the macro which causes // the compilation to fail. +#include "query/parameters.hpp" #include "query/plan/planner.hpp" ////////////////////////////////////////////////////// #include "communication/result_stream_faker.hpp" @@ -32,6 +33,8 @@ #include "query/interpreter.hpp" #include "storage/v2/inmemory/storage.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; + // The following classes are wrappers for memgraph::utils::MemoryResource, so that we can // use BENCHMARK_TEMPLATE @@ -61,7 +64,7 @@ class PoolResource final { }; static void AddVertices(memgraph::storage::Storage *db, int vertex_count) { - auto dba = db->Access(); + auto dba = db->Access(ReplicationRole::MAIN); for (int i = 0; i < vertex_count; i++) dba->CreateVertex(); MG_ASSERT(!dba->Commit().HasError()); } @@ -70,7 +73,7 @@ static const char *kStartLabel = "start"; static void AddStarGraph(memgraph::storage::Storage *db, int spoke_count, int depth) { { - auto dba = db->Access(); + auto dba = db->Access(ReplicationRole::MAIN); auto center_vertex = dba->CreateVertex(); MG_ASSERT(center_vertex.AddLabel(dba->NameToLabel(kStartLabel)).HasValue()); for (int i = 0; i < spoke_count; ++i) { @@ -84,14 +87,14 @@ static void AddStarGraph(memgraph::storage::Storage *db, int spoke_count, int de MG_ASSERT(!dba->Commit().HasError()); } { - auto unique_acc = db->UniqueAccess(); + auto unique_acc = db->UniqueAccess(ReplicationRole::MAIN); MG_ASSERT(!unique_acc->CreateIndex(db->NameToLabel(kStartLabel)).HasError()); } } static void AddTree(memgraph::storage::Storage *db, int vertex_count) { { - auto dba = db->Access(); + auto dba = db->Access(ReplicationRole::MAIN); std::vector<memgraph::storage::VertexAccessor> vertices; vertices.reserve(vertex_count); auto root = dba->CreateVertex(); @@ -109,7 +112,7 @@ static void AddTree(memgraph::storage::Storage *db, int vertex_count) { MG_ASSERT(!dba->Commit().HasError()); } { - auto unique_acc = db->UniqueAccess(); + auto unique_acc = db->UniqueAccess(ReplicationRole::MAIN); MG_ASSERT(!unique_acc->CreateIndex(db->NameToLabel(kStartLabel)).HasError()); } } @@ -117,10 +120,11 @@ static void AddTree(memgraph::storage::Storage *db, int vertex_count) { static memgraph::query::CypherQuery *ParseCypherQuery(const std::string &query_string, memgraph::query::AstStorage *ast) { memgraph::query::frontend::ParsingContext parsing_context; + memgraph::query::Parameters parameters; parsing_context.is_query_cached = false; memgraph::query::frontend::opencypher::Parser parser(query_string); // Convert antlr4 AST into Memgraph AST. - memgraph::query::frontend::CypherMainVisitor cypher_visitor(parsing_context, ast); + memgraph::query::frontend::CypherMainVisitor cypher_visitor(parsing_context, ast, ¶meters); cypher_visitor.visit(parser.tree()); return memgraph::utils::Downcast<memgraph::query::CypherQuery>(cypher_visitor.query()); }; @@ -132,7 +136,7 @@ static void Distinct(benchmark::State &state) { memgraph::query::Parameters parameters; std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); AddVertices(db.get(), state.range(0)); - auto storage_dba = db->Access(); + auto storage_dba = db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto query_string = "MATCH (s) RETURN DISTINCT s"; auto *cypher_query = ParseCypherQuery(query_string, &ast); @@ -185,7 +189,7 @@ static void ExpandVariable(benchmark::State &state) { AddStarGraph(db.get(), state.range(0), state.range(1)); memgraph::query::SymbolTable symbol_table; auto expand_variable = MakeExpandVariable(memgraph::query::EdgeAtom::Type::DEPTH_FIRST, &symbol_table); - auto storage_dba = db->Access(); + auto storage_dba = db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // We need to only set the memory for temporary (per pull) evaluations TMemory per_pull_memory; @@ -225,7 +229,7 @@ static void ExpandBfs(benchmark::State &state) { AddTree(db.get(), state.range(0)); memgraph::query::SymbolTable symbol_table; auto expand_variable = MakeExpandVariable(memgraph::query::EdgeAtom::Type::BREADTH_FIRST, &symbol_table); - auto storage_dba = db->Access(); + auto storage_dba = db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // We need to only set the memory for temporary (per pull) evaluations TMemory per_pull_memory; @@ -261,7 +265,7 @@ static void ExpandShortest(benchmark::State &state) { auto expand_variable = MakeExpandVariable(memgraph::query::EdgeAtom::Type::BREADTH_FIRST, &symbol_table); expand_variable.common_.existing_node = true; auto dest_symbol = expand_variable.common_.node_symbol; - auto storage_dba = db->Access(); + auto storage_dba = db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // We need to only set the memory for temporary (per pull) evaluations TMemory per_pull_memory; @@ -303,7 +307,7 @@ static void ExpandWeightedShortest(benchmark::State &state) { symbol_table.CreateSymbol("edge", false), symbol_table.CreateSymbol("vertex", false), ast.Create<memgraph::query::PrimitiveLiteral>(1)}; auto dest_symbol = expand_variable.common_.node_symbol; - auto storage_dba = db->Access(); + auto storage_dba = db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // We need to only set the memory for temporary (per pull) evaluations TMemory per_pull_memory; @@ -349,7 +353,7 @@ static void Accumulate(benchmark::State &state) { } memgraph::query::plan::Accumulate accumulate(scan_all, symbols, /* advance_command= */ false); - auto storage_dba = db->Access(); + auto storage_dba = db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // We need to only set the memory for temporary (per pull) evaluations TMemory per_pull_memory; @@ -399,7 +403,7 @@ static void Aggregate(benchmark::State &state) { symbol_table.CreateSymbol("out" + std::to_string(i), false)}); } memgraph::query::plan::Aggregate aggregate(scan_all, aggregations, group_by, symbols); - auto storage_dba = db->Access(); + auto storage_dba = db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // We need to only set the memory for temporary (per pull) evaluations TMemory per_pull_memory; @@ -450,7 +454,7 @@ static void OrderBy(benchmark::State &state) { sort_items.push_back({memgraph::query::Ordering::ASC, ast.Create<memgraph::query::PrimitiveLiteral>(rand_value)}); } memgraph::query::plan::OrderBy order_by(scan_all, sort_items, symbols); - auto storage_dba = db->Access(); + auto storage_dba = db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // We need to only set the memory for temporary (per pull) evaluations TMemory per_pull_memory; @@ -489,7 +493,7 @@ static void Unwind(benchmark::State &state) { auto *list_expr = ast.Create<memgraph::query::Identifier>("list")->MapTo(list_sym); auto out_sym = symbol_table.CreateSymbol("out", false); memgraph::query::plan::Unwind unwind(scan_all, list_expr, out_sym); - auto storage_dba = db->Access(); + auto storage_dba = db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // We need to only set the memory for temporary (per pull) evaluations TMemory per_pull_memory; @@ -527,7 +531,7 @@ static void Foreach(benchmark::State &state) { std::make_shared<memgraph::query::plan::CreateNode>(nullptr, memgraph::query::plan::NodeCreationInfo{}); auto foreach = std::make_shared<memgraph::query::plan::Foreach>(nullptr, std::move(create_node), list_expr, out_sym); - auto storage_dba = db->Access(); + auto storage_dba = db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); TMemory per_pull_memory; memgraph::query::EvaluationContext evaluation_context{per_pull_memory.get()}; diff --git a/tests/benchmark/query/planner.cpp b/tests/benchmark/query/planner.cpp index 8cc90fe05..c70de0869 100644 --- a/tests/benchmark/query/planner.cpp +++ b/tests/benchmark/query/planner.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -16,9 +16,12 @@ #include "query/frontend/semantic/symbol_generator.hpp" #include "query/plan/cost_estimator.hpp" #include "query/plan/planner.hpp" +#include "query/plan/rewrite/index_lookup.hpp" #include "query/plan/vertex_count_cache.hpp" #include "storage/v2/inmemory/storage.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; + // Add chained MATCH (node1) -- (node2), MATCH (node2) -- (node3) ... clauses. static memgraph::query::CypherQuery *AddChainedMatches(int num_matches, memgraph::query::AstStorage &storage) { auto *query = storage.Create<memgraph::query::CypherQuery>(); @@ -44,7 +47,7 @@ static memgraph::query::CypherQuery *AddChainedMatches(int num_matches, memgraph static void BM_PlanChainedMatches(benchmark::State &state) { std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); - auto storage_dba = db->Access(); + auto storage_dba = db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); while (state.KeepRunning()) { state.PauseTiming(); @@ -95,10 +98,10 @@ static auto CreateIndexedVertices(int index_count, int vertex_count, memgraph::s auto label = db->NameToLabel("label"); auto prop = db->NameToProperty("prop"); { - auto unique_acc = db->UniqueAccess(); + auto unique_acc = db->UniqueAccess(ReplicationRole::MAIN); [[maybe_unused]] auto _ = unique_acc->CreateIndex(label, prop); } - auto dba = db->Access(); + auto dba = db->Access(ReplicationRole::MAIN); for (int vi = 0; vi < vertex_count; ++vi) { for (int index = 0; index < index_count; ++index) { auto vertex = dba->CreateVertex(); @@ -117,7 +120,7 @@ static void BM_PlanAndEstimateIndexedMatching(benchmark::State &state) { int index_count = state.range(0); int vertex_count = state.range(1); std::tie(label, prop) = CreateIndexedVertices(index_count, vertex_count, db.get()); - auto storage_dba = db->Access(); + auto storage_dba = db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); memgraph::query::Parameters parameters; while (state.KeepRunning()) { @@ -134,7 +137,8 @@ static void BM_PlanAndEstimateIndexedMatching(benchmark::State &state) { auto plans = memgraph::query::plan::MakeLogicalPlanForSingleQuery<memgraph::query::plan::VariableStartPlanner>( query_parts, &ctx); for (auto plan : plans) { - memgraph::query::plan::EstimatePlanCost(&dba, symbol_table, parameters, *plan); + memgraph::query::plan::EstimatePlanCost(&dba, symbol_table, parameters, *plan, + memgraph::query::plan::IndexHints()); } } } @@ -146,7 +150,7 @@ static void BM_PlanAndEstimateIndexedMatchingWithCachedCounts(benchmark::State & int index_count = state.range(0); int vertex_count = state.range(1); std::tie(label, prop) = CreateIndexedVertices(index_count, vertex_count, db.get()); - auto storage_dba = db->Access(); + auto storage_dba = db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto vertex_counts = memgraph::query::plan::MakeVertexCountCache(&dba); memgraph::query::Parameters parameters; @@ -164,7 +168,8 @@ static void BM_PlanAndEstimateIndexedMatchingWithCachedCounts(benchmark::State & auto plans = memgraph::query::plan::MakeLogicalPlanForSingleQuery<memgraph::query::plan::VariableStartPlanner>( query_parts, &ctx); for (auto plan : plans) { - memgraph::query::plan::EstimatePlanCost(&vertex_counts, symbol_table, parameters, *plan); + memgraph::query::plan::EstimatePlanCost(&vertex_counts, symbol_table, parameters, *plan, + memgraph::query::plan::IndexHints()); } } } diff --git a/tests/benchmark/query/profile.cpp b/tests/benchmark/query/profile.cpp index 1c28fd21c..49b97fc83 100644 --- a/tests/benchmark/query/profile.cpp +++ b/tests/benchmark/query/profile.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -141,9 +141,9 @@ struct ScopedProfile { } } - Context *context; - ProfilingStats *stats; - unsigned long long start_time; + Context *context = nullptr; + ProfilingStats *stats = nullptr; + unsigned long long start_time{}; }; ////////////////////////////////////////////////////////////////////////////// diff --git a/tests/benchmark/storage_v2_gc.cpp b/tests/benchmark/storage_v2_gc.cpp index 3941fcbe1..6f0e5712d 100644 --- a/tests/benchmark/storage_v2_gc.cpp +++ b/tests/benchmark/storage_v2_gc.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -17,6 +17,8 @@ #include "storage/v2/storage.hpp" #include "utils/timer.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; + // This benchmark should be run for a fixed amount of time that is // large compared to GC interval to make the output relevant. @@ -42,7 +44,7 @@ void UpdateLabelFunc(int thread_id, memgraph::storage::Storage *storage, memgraph::utils::Timer timer; for (int iter = 0; iter < num_iterations; ++iter) { - auto acc = storage->Access(); + auto acc = storage->Access(ReplicationRole::MAIN); memgraph::storage::Gid gid = vertices.at(vertex_dist(gen)); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); MG_ASSERT(vertex.has_value(), "Vertex with GID {} doesn't exist", gid.AsUint()); @@ -61,7 +63,7 @@ int main(int argc, char *argv[]) { std::unique_ptr<memgraph::storage::Storage> storage(new memgraph::storage::InMemoryStorage(config.second)); std::vector<memgraph::storage::Gid> vertices; { - auto acc = storage->Access(); + auto acc = storage->Access(ReplicationRole::MAIN); for (int i = 0; i < FLAGS_num_vertices; ++i) { vertices.push_back(acc->CreateVertex().Gid()); } diff --git a/tests/benchmark/storage_v2_gc2.cpp b/tests/benchmark/storage_v2_gc2.cpp index a8c0a589e..f3986edd3 100644 --- a/tests/benchmark/storage_v2_gc2.cpp +++ b/tests/benchmark/storage_v2_gc2.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -17,6 +17,8 @@ #include "storage/v2/storage.hpp" #include "utils/timer.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; + // This benchmark should be run for a fixed amount of time that is // large compared to GC interval to make the output relevant. @@ -41,14 +43,14 @@ int main(int argc, char *argv[]) { std::array<memgraph::storage::Gid, 1> vertices; memgraph::storage::PropertyId pid; { - auto acc = storage->Access(); + auto acc = storage->Access(ReplicationRole::MAIN); vertices[0] = acc->CreateVertex().Gid(); pid = acc->NameToProperty("NEW_PROP"); MG_ASSERT(!acc->Commit().HasError()); } for (int iter = 0; iter != FLAGS_num_iterations; ++iter) { - auto acc = storage->Access(); + auto acc = storage->Access(ReplicationRole::MAIN); auto vertex1 = acc->FindVertex(vertices[0], memgraph::storage::View::OLD); for (auto i = 0; i != FLAGS_num_poperties; ++i) { MG_ASSERT(!vertex1.value().SetProperty(pid, memgraph::storage::PropertyValue{i}).HasError()); diff --git a/tests/concurrent/storage_indices.cpp b/tests/concurrent/storage_indices.cpp index a3c09d2e4..fc4d75a76 100644 --- a/tests/concurrent/storage_indices.cpp +++ b/tests/concurrent/storage_indices.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -19,6 +19,8 @@ #include "storage/v2/storage_error.hpp" #include "utils/thread.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; + const uint64_t kNumVerifiers = 5; const uint64_t kNumMutators = 1; @@ -31,7 +33,7 @@ TEST(Storage, LabelIndex) { auto label = store->NameToLabel("label"); { - auto unique_acc = store->UniqueAccess(); + auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateIndex(label).HasError()); } @@ -44,7 +46,7 @@ TEST(Storage, LabelIndex) { gids.reserve(kNumIterations * kVerifierBatchSize); for (uint64_t i = 0; i < kNumIterations; ++i) { for (uint64_t j = 0; j < kVerifierBatchSize; ++j) { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gids.emplace(vertex.Gid(), false); auto ret = vertex.AddLabel(label); @@ -53,7 +55,7 @@ TEST(Storage, LabelIndex) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertices = acc->Vertices(label, memgraph::storage::View::OLD); for (auto vertex : vertices) { auto it = gids.find(vertex.Gid()); @@ -81,7 +83,7 @@ TEST(Storage, LabelIndex) { gids.resize(kMutatorBatchSize); while (mutators_run.load(std::memory_order_acquire)) { for (uint64_t i = 0; i < kMutatorBatchSize; ++i) { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gids[i] = vertex.Gid(); auto ret = vertex.AddLabel(label); @@ -90,7 +92,7 @@ TEST(Storage, LabelIndex) { ASSERT_FALSE(acc->Commit().HasError()); } for (uint64_t i = 0; i < kMutatorBatchSize; ++i) { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD); ASSERT_TRUE(vertex); ASSERT_TRUE(acc->DeleteVertex(&*vertex).HasValue()); @@ -116,7 +118,7 @@ TEST(Storage, LabelPropertyIndex) { auto label = store->NameToLabel("label"); auto prop = store->NameToProperty("prop"); { - auto unique_acc = store->UniqueAccess(); + auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateIndex(label, prop).HasError()); } @@ -129,7 +131,7 @@ TEST(Storage, LabelPropertyIndex) { gids.reserve(kNumIterations * kVerifierBatchSize); for (uint64_t i = 0; i < kNumIterations; ++i) { for (uint64_t j = 0; j < kVerifierBatchSize; ++j) { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gids.emplace(vertex.Gid(), false); { @@ -145,7 +147,7 @@ TEST(Storage, LabelPropertyIndex) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertices = acc->Vertices(label, prop, memgraph::storage::View::OLD); for (auto vertex : vertices) { auto it = gids.find(vertex.Gid()); @@ -173,7 +175,7 @@ TEST(Storage, LabelPropertyIndex) { gids.resize(kMutatorBatchSize); while (mutators_run.load(std::memory_order_acquire)) { for (uint64_t i = 0; i < kMutatorBatchSize; ++i) { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gids[i] = vertex.Gid(); { @@ -189,7 +191,7 @@ TEST(Storage, LabelPropertyIndex) { ASSERT_FALSE(acc->Commit().HasError()); } for (uint64_t i = 0; i < kMutatorBatchSize; ++i) { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD); ASSERT_TRUE(vertex); ASSERT_TRUE(acc->DeleteVertex(&*vertex).HasValue()); diff --git a/tests/concurrent/storage_unique_constraints.cpp b/tests/concurrent/storage_unique_constraints.cpp index 694249779..dc3b30146 100644 --- a/tests/concurrent/storage_unique_constraints.cpp +++ b/tests/concurrent/storage_unique_constraints.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -16,6 +16,8 @@ #include "storage/v2/constraints/constraints.hpp" #include "storage/v2/inmemory/storage.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; + const int kNumThreads = 8; #define ASSERT_OK(x) ASSERT_FALSE((x).HasError()) @@ -34,7 +36,7 @@ class StorageUniqueConstraints : public ::testing::Test { void SetUp() override { // Create initial vertices. - auto acc = storage->Access(); + auto acc = storage->Access(ReplicationRole::MAIN); // NOLINTNEXTLINE(modernize-loop-convert) for (int i = 0; i < kNumThreads; ++i) { auto vertex = acc->CreateVertex(); @@ -55,7 +57,7 @@ void SetProperties(memgraph::storage::Storage *storage, memgraph::storage::Gid g const std::vector<PropertyId> &properties, const std::vector<PropertyValue> &values, bool *commit_status) { ASSERT_EQ(properties.size(), values.size()); - auto acc = storage->Access(); + auto acc = storage->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); int value = 0; @@ -71,7 +73,7 @@ void SetProperties(memgraph::storage::Storage *storage, memgraph::storage::Gid g } void AddLabel(memgraph::storage::Storage *storage, memgraph::storage::Gid gid, LabelId label, bool *commit_status) { - auto acc = storage->Access(); + auto acc = storage->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); for (int iter = 0; iter < 40000; ++iter) { @@ -84,7 +86,7 @@ void AddLabel(memgraph::storage::Storage *storage, memgraph::storage::Gid gid, L TEST_F(StorageUniqueConstraints, ChangeProperties) { { - auto unique_acc = storage->UniqueAccess(); + auto unique_acc = storage->UniqueAccess(ReplicationRole::MAIN); auto res = unique_acc->CreateUniqueConstraint(label, {prop1, prop2, prop3}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), memgraph::storage::UniqueConstraints::CreationStatus::SUCCESS); @@ -92,7 +94,7 @@ TEST_F(StorageUniqueConstraints, ChangeProperties) { } { - auto acc = storage->Access(); + auto acc = storage->Access(ReplicationRole::MAIN); // NOLINTNEXTLINE(modernize-loop-convert) for (int i = 0; i < kNumThreads; ++i) { auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD); @@ -168,7 +170,7 @@ TEST_F(StorageUniqueConstraints, ChangeProperties) { TEST_F(StorageUniqueConstraints, ChangeLabels) { { - auto unique_acc = storage->UniqueAccess(); + auto unique_acc = storage->UniqueAccess(ReplicationRole::MAIN); auto res = unique_acc->CreateUniqueConstraint(label, {prop1, prop2, prop3}); ASSERT_TRUE(res.HasValue()); ASSERT_EQ(res.GetValue(), memgraph::storage::UniqueConstraints::CreationStatus::SUCCESS); @@ -181,7 +183,7 @@ TEST_F(StorageUniqueConstraints, ChangeLabels) { // succeed, as the others should result with constraint violation. { - auto acc = storage->Access(); + auto acc = storage->Access(ReplicationRole::MAIN); // NOLINTNEXTLINE(modernize-loop-convert) for (int i = 0; i < kNumThreads; ++i) { auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD); @@ -196,7 +198,7 @@ TEST_F(StorageUniqueConstraints, ChangeLabels) { for (int iter = 0; iter < 20; ++iter) { // Clear labels. { - auto acc = storage->Access(); + auto acc = storage->Access(ReplicationRole::MAIN); // NOLINTNEXTLINE(modernize-loop-convert) for (int i = 0; i < kNumThreads; ++i) { auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD); @@ -227,7 +229,7 @@ TEST_F(StorageUniqueConstraints, ChangeLabels) { // should succeed. { - auto acc = storage->Access(); + auto acc = storage->Access(ReplicationRole::MAIN); // NOLINTNEXTLINE(modernize-loop-convert) for (int i = 0; i < kNumThreads; ++i) { auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD); @@ -242,7 +244,7 @@ TEST_F(StorageUniqueConstraints, ChangeLabels) { for (int iter = 0; iter < 20; ++iter) { // Clear labels. { - auto acc = storage->Access(); + auto acc = storage->Access(ReplicationRole::MAIN); // NOLINTNEXTLINE(modernize-loop-convert) for (int i = 0; i < kNumThreads; ++i) { auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD); diff --git a/tests/e2e/CMakeLists.txt b/tests/e2e/CMakeLists.txt index fcf7f45b6..a95297301 100644 --- a/tests/e2e/CMakeLists.txt +++ b/tests/e2e/CMakeLists.txt @@ -77,6 +77,15 @@ add_subdirectory(query_modules_storage_modes) add_subdirectory(garbage_collection) add_subdirectory(query_planning) +if (MG_EXPERIMENTAL_HIGH_AVAILABILITY) + add_subdirectory(high_availability_experimental) +endif () + + +if (MG_EXPERIMENTAL_REPLICATION_MULTITENANCY) + add_subdirectory(replication_experimental) +endif () + copy_e2e_python_files(pytest_runner pytest_runner.sh "") copy_e2e_python_files(x x.sh "") file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/memgraph-selfsigned.crt DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/tests/e2e/analytical_mode/CMakeLists.txt b/tests/e2e/analytical_mode/CMakeLists.txt index e22830770..1756c9980 100644 --- a/tests/e2e/analytical_mode/CMakeLists.txt +++ b/tests/e2e/analytical_mode/CMakeLists.txt @@ -4,3 +4,5 @@ endfunction() copy_analytical_mode_e2e_python_files(common.py) copy_analytical_mode_e2e_python_files(free_memory.py) + +copy_e2e_files(analytical_mode workloads.yaml) diff --git a/tests/e2e/analyze_graph/CMakeLists.txt b/tests/e2e/analyze_graph/CMakeLists.txt index 1b96eb960..0faa37caa 100644 --- a/tests/e2e/analyze_graph/CMakeLists.txt +++ b/tests/e2e/analyze_graph/CMakeLists.txt @@ -4,3 +4,5 @@ endfunction() copy_analyze_graph_e2e_python_files(common.py) copy_analyze_graph_e2e_python_files(optimize_indexes.py) + +copy_e2e_files(analyze_graph workloads.yaml) diff --git a/tests/e2e/batched_procedures/CMakeLists.txt b/tests/e2e/batched_procedures/CMakeLists.txt index 19aacb15c..28eb46bee 100644 --- a/tests/e2e/batched_procedures/CMakeLists.txt +++ b/tests/e2e/batched_procedures/CMakeLists.txt @@ -7,3 +7,5 @@ copy_batched_procedures_e2e_python_files(conftest.py) copy_batched_procedures_e2e_python_files(simple_read.py) add_subdirectory(procedures) + +copy_e2e_files(batched_procedures workloads.yaml) diff --git a/tests/e2e/concurrent_query_modules/CMakeLists.txt b/tests/e2e/concurrent_query_modules/CMakeLists.txt index 6c92387cb..9f3996585 100644 --- a/tests/e2e/concurrent_query_modules/CMakeLists.txt +++ b/tests/e2e/concurrent_query_modules/CMakeLists.txt @@ -6,3 +6,5 @@ copy_concurrent_query_modules_e2e_python_files(client.py) copy_concurrent_query_modules_e2e_python_files(con_query_modules.py) add_subdirectory(test_query_modules) + +copy_e2e_files(concurrent_query_modules workloads.yaml) diff --git a/tests/e2e/configuration/CMakeLists.txt b/tests/e2e/configuration/CMakeLists.txt index 0411c70e3..b02f69639 100644 --- a/tests/e2e/configuration/CMakeLists.txt +++ b/tests/e2e/configuration/CMakeLists.txt @@ -1,7 +1,9 @@ function(copy_configuration_check_e2e_python_files FILE_NAME) - copy_e2e_python_files(write_procedures ${FILE_NAME}) + copy_e2e_python_files(configuration ${FILE_NAME}) endfunction() copy_configuration_check_e2e_python_files(default_config.py) copy_configuration_check_e2e_python_files(configuration_check.py) copy_configuration_check_e2e_python_files(storage_info.py) + +copy_e2e_files(configuration workloads.yaml) diff --git a/tests/e2e/configuration/default_config.py b/tests/e2e/configuration/default_config.py index e1d42b443..915a14d14 100644 --- a/tests/e2e/configuration/default_config.py +++ b/tests/e2e/configuration/default_config.py @@ -66,6 +66,8 @@ startup_config_dict = { "Time in seconds after which inactive Bolt sessions will be closed.", ), "cartesian_product_enabled": ("true", "true", "Enable cartesian product expansion."), + "coordinator": ("false", "false", "Controls whether the instance is a replication coordinator."), + "coordinator_server_port": ("0", "0", "Port on which coordinator servers will be started."), "data_directory": ("mg_data", "mg_data", "Path to directory in which to save all permanent data."), "data_recovery_on_startup": ( "false", @@ -143,6 +145,7 @@ startup_config_dict = { "The time duration between two replica checks/pings. If < 1, replicas will NOT be checked at all. NOTE: The MAIN instance allocates a new thread for each REPLICA.", ), "storage_gc_cycle_sec": ("30", "30", "Storage garbage collector interval (in seconds)."), + "storage_python_gc_cycle_sec": ("180", "180", "Storage python full garbage collection interval (in seconds)."), "storage_items_per_batch": ( "1000000", "1000000", @@ -173,11 +176,6 @@ startup_config_dict = { "Default storage mode Memgraph uses. Allowed values: IN_MEMORY_TRANSACTIONAL, IN_MEMORY_ANALYTICAL, ON_DISK_TRANSACTIONAL", ), "storage_wal_file_size_kib": ("20480", "20480", "Minimum file size of each WAL file."), - "storage_delete_on_drop": ( - "true", - "true", - "If set to true the query 'DROP DATABASE x' will delete the underlying storage as well.", - ), "stream_transaction_conflict_retries": ( "30", "30", diff --git a/tests/e2e/configuration/storage_info.py b/tests/e2e/configuration/storage_info.py index 042a57b08..8af294eca 100644 --- a/tests/e2e/configuration/storage_info.py +++ b/tests/e2e/configuration/storage_info.py @@ -11,7 +11,6 @@ import sys -import default_config import mgclient import pytest diff --git a/tests/e2e/constraints/CMakeLists.txt b/tests/e2e/constraints/CMakeLists.txt index 0c4ff72d9..b099dab4d 100644 --- a/tests/e2e/constraints/CMakeLists.txt +++ b/tests/e2e/constraints/CMakeLists.txt @@ -4,3 +4,5 @@ endfunction() copy_constraint_validation_e2e_python_files(common.py) copy_constraint_validation_e2e_python_files(constraints_validation.py) + +copy_e2e_files(constraint_validation workloads.yaml) diff --git a/tests/e2e/disk_storage/CMakeLists.txt b/tests/e2e/disk_storage/CMakeLists.txt index 777277178..5e0822ef8 100644 --- a/tests/e2e/disk_storage/CMakeLists.txt +++ b/tests/e2e/disk_storage/CMakeLists.txt @@ -13,3 +13,5 @@ copy_disk_storage_e2e_python_files(snapshot_disabled.py) copy_disk_storage_e2e_python_files(lock_data_dir_disabled.py) copy_disk_storage_e2e_python_files(create_edge_from_indices.py) copy_disk_storage_e2e_python_files(storage_info.py) + +copy_e2e_files(disk_storage workloads.yaml) diff --git a/tests/e2e/fine_grained_access/CMakeLists.txt b/tests/e2e/fine_grained_access/CMakeLists.txt index 6b277694f..71a02cd4b 100644 --- a/tests/e2e/fine_grained_access/CMakeLists.txt +++ b/tests/e2e/fine_grained_access/CMakeLists.txt @@ -7,3 +7,5 @@ copy_fine_grained_access_e2e_python_files(create_delete_filtering_tests.py) copy_fine_grained_access_e2e_python_files(edge_type_filtering_tests.py) copy_fine_grained_access_e2e_python_files(path_filtering_tests.py) copy_fine_grained_access_e2e_python_files(show_db.py) + +copy_e2e_files(fine_grained_access workloads.yaml) diff --git a/tests/e2e/fine_grained_access/show_db.py b/tests/e2e/fine_grained_access/show_db.py index 546d4e24a..c5378dca6 100644 --- a/tests/e2e/fine_grained_access/show_db.py +++ b/tests/e2e/fine_grained_access/show_db.py @@ -23,13 +23,20 @@ def test_show_databases_w_user(): user3_connection = common.connect(username="user3", password="test") assert common.execute_and_fetch_all(admin_connection.cursor(), "SHOW DATABASES") == [ - ("db1", ""), - ("db2", ""), - ("memgraph", "*"), + ("db1",), + ("db2",), + ("memgraph",), ] - assert common.execute_and_fetch_all(user_connection.cursor(), "SHOW DATABASES") == [("db1", ""), ("memgraph", "*")] - assert common.execute_and_fetch_all(user2_connection.cursor(), "SHOW DATABASES") == [("db2", "*")] - assert common.execute_and_fetch_all(user3_connection.cursor(), "SHOW DATABASES") == [("db1", "*"), ("db2", "")] + assert common.execute_and_fetch_all(admin_connection.cursor(), "SHOW DATABASE") == [("memgraph",)] + + assert common.execute_and_fetch_all(user_connection.cursor(), "SHOW DATABASES") == [("db1",), ("memgraph",)] + assert common.execute_and_fetch_all(user_connection.cursor(), "SHOW DATABASE") == [("memgraph",)] + + assert common.execute_and_fetch_all(user2_connection.cursor(), "SHOW DATABASES") == [("db2",)] + assert common.execute_and_fetch_all(user2_connection.cursor(), "SHOW DATABASE") == [("db2",)] + + assert common.execute_and_fetch_all(user3_connection.cursor(), "SHOW DATABASES") == [("db1",), ("db2",)] + assert common.execute_and_fetch_all(user3_connection.cursor(), "SHOW DATABASE") == [("db1",)] if __name__ == "__main__": diff --git a/tests/e2e/garbage_collection/CMakeLists.txt b/tests/e2e/garbage_collection/CMakeLists.txt index 690edf344..e515247ec 100644 --- a/tests/e2e/garbage_collection/CMakeLists.txt +++ b/tests/e2e/garbage_collection/CMakeLists.txt @@ -5,3 +5,5 @@ endfunction() garbage_collection_e2e_python_files(common.py) garbage_collection_e2e_python_files(conftest.py) garbage_collection_e2e_python_files(gc_periodic.py) + +copy_e2e_files(garbage_collection workloads.yaml) diff --git a/tests/e2e/garbage_collection/gc_periodic.py b/tests/e2e/garbage_collection/gc_periodic.py index a93846a68..2f1c8850c 100644 --- a/tests/e2e/garbage_collection/gc_periodic.py +++ b/tests/e2e/garbage_collection/gc_periodic.py @@ -38,21 +38,21 @@ def get_memory(cursor): def test_gc_periodic(connection): - """ - This test checks that periodic gc works. - It does so by checking that the allocated memory is lowered by at least 1/4 of the memory allocated by creating nodes. - If we choose a number a high number the test will become flaky because the memory only gets fully cleared after a while - due to jemalloc holding some memory for a while. If we'd wait for jemalloc to fully release the memory the test would take too long. - """ cursor = connection.cursor() memory_pre_creation = get_memory(cursor) - execute_and_fetch_all(cursor, "UNWIND range(1, 1000) AS index CREATE (:Node);") + execute_and_fetch_all(cursor, "UNWIND range(1, 100000) AS index CREATE (:Node);") memory_after_creation = get_memory(cursor) - time.sleep(5) - memory_after_gc = get_memory(cursor) + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") - assert memory_after_gc < memory_pre_creation + (memory_after_creation - memory_pre_creation) / 4 * 3 + for i in range(5): + time.sleep(2) + memory_after_gc = get_memory(cursor) + + if memory_after_gc < memory_pre_creation + (memory_after_creation - memory_pre_creation) / 4: + return + + raise MemoryError("GC didn't clean the memory after 10 seconds") if __name__ == "__main__": diff --git a/tests/e2e/graphql/CMakeLists.txt b/tests/e2e/graphql/CMakeLists.txt index 7ad1624b6..384d534d8 100644 --- a/tests/e2e/graphql/CMakeLists.txt +++ b/tests/e2e/graphql/CMakeLists.txt @@ -8,3 +8,5 @@ copy_graphql_e2e_python_files(callable_alias_mapping.json) add_subdirectory(graphql_library_config) add_subdirectory(temporary_procedures) + +copy_e2e_files(graphql workloads.yaml) diff --git a/tests/e2e/high_availability_experimental/CMakeLists.txt b/tests/e2e/high_availability_experimental/CMakeLists.txt new file mode 100644 index 000000000..76e1a6956 --- /dev/null +++ b/tests/e2e/high_availability_experimental/CMakeLists.txt @@ -0,0 +1,12 @@ +find_package(gflags REQUIRED) + +copy_e2e_python_files(ha_experimental coordinator.py) +copy_e2e_python_files(ha_experimental automatic_failover.py) +copy_e2e_python_files(ha_experimental manual_setting_replicas.py) +copy_e2e_python_files(ha_experimental common.py) +copy_e2e_python_files(ha_experimental conftest.py) +copy_e2e_python_files(ha_experimental workloads.yaml) + +copy_e2e_python_files_from_parent_folder(ha_experimental ".." memgraph.py) +copy_e2e_python_files_from_parent_folder(ha_experimental ".." interactive_mg_runner.py) +copy_e2e_python_files_from_parent_folder(ha_experimental ".." mg_utils.py) diff --git a/tests/e2e/high_availability_experimental/automatic_failover.py b/tests/e2e/high_availability_experimental/automatic_failover.py new file mode 100644 index 000000000..f3ffadfe8 --- /dev/null +++ b/tests/e2e/high_availability_experimental/automatic_failover.py @@ -0,0 +1,223 @@ +# Copyright 2024 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import os +import sys + +import interactive_mg_runner +import pytest +from common import execute_and_fetch_all +from mg_utils import mg_sleep_and_assert + +interactive_mg_runner.SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) +interactive_mg_runner.PROJECT_DIR = os.path.normpath( + os.path.join(interactive_mg_runner.SCRIPT_DIR, "..", "..", "..", "..") +) +interactive_mg_runner.BUILD_DIR = os.path.normpath(os.path.join(interactive_mg_runner.PROJECT_DIR, "build")) +interactive_mg_runner.MEMGRAPH_BINARY = os.path.normpath(os.path.join(interactive_mg_runner.BUILD_DIR, "memgraph")) + +MEMGRAPH_INSTANCES_DESCRIPTION = { + "instance_1": { + "args": ["--bolt-port", "7688", "--log-level", "TRACE", "--coordinator-server-port", "10011"], + "log_file": "replica1.log", + "setup_queries": [], + }, + "instance_2": { + "args": ["--bolt-port", "7689", "--log-level", "TRACE", "--coordinator-server-port", "10012"], + "log_file": "replica2.log", + "setup_queries": [], + }, + "instance_3": { + "args": ["--bolt-port", "7687", "--log-level", "TRACE", "--coordinator-server-port", "10013"], + "log_file": "main.log", + "setup_queries": [], + }, + "coordinator": { + "args": ["--bolt-port", "7690", "--log-level=TRACE", "--coordinator"], + "log_file": "replica3.log", + "setup_queries": [ + "REGISTER INSTANCE instance_1 ON '127.0.0.1:10011' WITH '127.0.0.1:10001';", + "REGISTER INSTANCE instance_2 ON '127.0.0.1:10012' WITH '127.0.0.1:10002';", + "REGISTER INSTANCE instance_3 ON '127.0.0.1:10013' WITH '127.0.0.1:10003';", + "SET INSTANCE instance_3 TO MAIN", + ], + }, +} + + +def test_replication_works_on_failover(connection): + # Goal of this test is to check the replication works after failover command. + # 1. We start all replicas, main and coordinator manually: we want to be able to kill them ourselves without relying on external tooling to kill processes. + # 2. We check that main has correct state + # 3. We kill main + # 4. We check that coordinator and new main have correct state + # 5. We insert one vertex on new main + # 6. We check that vertex appears on new replica + + # 1 + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION) + + # 2 + main_cursor = connection(7687, "instance_3").cursor() + expected_data_on_main = [ + ("instance_1", "127.0.0.1:10001", "sync", 0, 0, "ready"), + ("instance_2", "127.0.0.1:10002", "sync", 0, 0, "ready"), + ] + actual_data_on_main = sorted(list(execute_and_fetch_all(main_cursor, "SHOW REPLICAS;"))) + assert actual_data_on_main == expected_data_on_main + + # 3 + interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, "instance_3") + + # 4 + coord_cursor = connection(7690, "coordinator").cursor() + + def retrieve_data_show_repl_cluster(): + return sorted(list(execute_and_fetch_all(coord_cursor, "SHOW REPLICATION CLUSTER;"))) + + expected_data_on_coord = [ + ("instance_1", "127.0.0.1:10011", True, "main"), + ("instance_2", "127.0.0.1:10012", True, "replica"), + ("instance_3", "127.0.0.1:10013", False, ""), + ] + mg_sleep_and_assert(expected_data_on_coord, retrieve_data_show_repl_cluster) + + new_main_cursor = connection(7688, "instance_1").cursor() + + def retrieve_data_show_replicas(): + return sorted(list(execute_and_fetch_all(new_main_cursor, "SHOW REPLICAS;"))) + + expected_data_on_new_main = [ + ("instance_2", "127.0.0.1:10002", "sync", 0, 0, "ready"), + ] + mg_sleep_and_assert(expected_data_on_new_main, retrieve_data_show_replicas) + + # 5 + execute_and_fetch_all(new_main_cursor, "CREATE ();") + + # 6 + alive_replica_cursror = connection(7689, "instance_2").cursor() + res = execute_and_fetch_all(alive_replica_cursror, "MATCH (n) RETURN count(n) as count;")[0][0] + assert res == 1, "Vertex should be replicated" + interactive_mg_runner.stop_all(MEMGRAPH_INSTANCES_DESCRIPTION) + + +def test_show_replication_cluster(connection): + # Goal of this test is to check the SHOW REPLICATION CLUSTER command. + # 1. We start all replicas, main and coordinator manually: we want to be able to kill them ourselves without relying on external tooling to kill processes. + # 2. We check that all replicas and main have the correct state: they should all be alive. + # 3. We kill one replica. It should not appear anymore in the SHOW REPLICATION CLUSTER command. + # 4. We kill main. It should not appear anymore in the SHOW REPLICATION CLUSTER command. + + # 1. + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION) + + cursor = connection(7690, "coordinator").cursor() + + # 2. + + # We leave some time for the coordinator to realise the replicas are down. + def retrieve_data(): + return sorted(list(execute_and_fetch_all(cursor, "SHOW REPLICATION CLUSTER;"))) + + expected_data = [ + ("instance_1", "127.0.0.1:10011", True, "replica"), + ("instance_2", "127.0.0.1:10012", True, "replica"), + ("instance_3", "127.0.0.1:10013", True, "main"), + ] + mg_sleep_and_assert(expected_data, retrieve_data) + + # 3. + interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, "instance_1") + + expected_data = [ + ("instance_1", "127.0.0.1:10011", False, ""), + ("instance_2", "127.0.0.1:10012", True, "replica"), + ("instance_3", "127.0.0.1:10013", True, "main"), + ] + mg_sleep_and_assert(expected_data, retrieve_data) + + # 4. + interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, "instance_2") + + expected_data = [ + ("instance_1", "127.0.0.1:10011", False, ""), + ("instance_2", "127.0.0.1:10012", False, ""), + ("instance_3", "127.0.0.1:10013", True, "main"), + ] + mg_sleep_and_assert(expected_data, retrieve_data) + + +def test_simple_automatic_failover(connection): + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION) + + main_cursor = connection(7687, "instance_3").cursor() + expected_data_on_main = [ + ("instance_1", "127.0.0.1:10001", "sync", 0, 0, "ready"), + ("instance_2", "127.0.0.1:10002", "sync", 0, 0, "ready"), + ] + actual_data_on_main = sorted(list(execute_and_fetch_all(main_cursor, "SHOW REPLICAS;"))) + assert actual_data_on_main == expected_data_on_main + + interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, "instance_3") + + coord_cursor = connection(7690, "coordinator").cursor() + + def retrieve_data_show_repl_cluster(): + return sorted(list(execute_and_fetch_all(coord_cursor, "SHOW REPLICATION CLUSTER;"))) + + expected_data_on_coord = [ + ("instance_1", "127.0.0.1:10011", True, "main"), + ("instance_2", "127.0.0.1:10012", True, "replica"), + ("instance_3", "127.0.0.1:10013", False, ""), + ] + mg_sleep_and_assert(expected_data_on_coord, retrieve_data_show_repl_cluster) + + new_main_cursor = connection(7688, "instance_1").cursor() + + def retrieve_data_show_replicas(): + return sorted(list(execute_and_fetch_all(new_main_cursor, "SHOW REPLICAS;"))) + + expected_data_on_new_main = [ + ("instance_2", "127.0.0.1:10002", "sync", 0, 0, "ready"), + ] + mg_sleep_and_assert(expected_data_on_new_main, retrieve_data_show_replicas) + + +def test_registering_replica_fails_name_exists(connection): + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION) + + coord_cursor = connection(7690, "coordinator").cursor() + with pytest.raises(Exception) as e: + execute_and_fetch_all( + coord_cursor, + "REGISTER INSTANCE instance_1 ON '127.0.0.1:10051' WITH '127.0.0.1:10111';", + ) + assert str(e.value) == "Couldn't register replica instance since instance with such name already exists!" + + +def test_registering_replica_fails_endpoint_exists(connection): + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION) + + coord_cursor = connection(7690, "coordinator").cursor() + with pytest.raises(Exception) as e: + execute_and_fetch_all( + coord_cursor, + "REGISTER INSTANCE instance_5 ON '127.0.0.1:10001' WITH '127.0.0.1:10013';", + ) + assert ( + str(e.value) + == "Couldn't register replica because promotion on replica failed! Check logs on replica to find out more info!" + ) + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/high_availability_experimental/common.py b/tests/e2e/high_availability_experimental/common.py new file mode 100644 index 000000000..dc104d628 --- /dev/null +++ b/tests/e2e/high_availability_experimental/common.py @@ -0,0 +1,25 @@ +# Copyright 2022 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import typing + +import mgclient + + +def execute_and_fetch_all(cursor: mgclient.Cursor, query: str, params: dict = {}) -> typing.List[tuple]: + cursor.execute(query, params) + return cursor.fetchall() + + +def connect(**kwargs) -> mgclient.Connection: + connection = mgclient.connect(**kwargs) + connection.autocommit = True + return connection diff --git a/tests/e2e/high_availability_experimental/conftest.py b/tests/e2e/high_availability_experimental/conftest.py new file mode 100644 index 000000000..9100a63cf --- /dev/null +++ b/tests/e2e/high_availability_experimental/conftest.py @@ -0,0 +1,43 @@ +# Copyright 2022 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import pytest +from common import connect, execute_and_fetch_all + + +# The fixture here is more complex because the connection has to be +# parameterized based on the test parameters (info has to be available on both +# sides). +# +# https://docs.pytest.org/en/latest/example/parametrize.html#indirect-parametrization +# is not an elegant/feasible solution here. +# +# The solution was independently developed and then I stumbled upon the same +# approach here https://stackoverflow.com/a/68286553/4888809 which I think is +# optimal. +@pytest.fixture(scope="function") +def connection(): + connection_holder = None + role_holder = None + + def inner_connection(port, role): + nonlocal connection_holder, role_holder + connection_holder = connect(host="localhost", port=port) + role_holder = role + return connection_holder + + yield inner_connection + + # Only main instance can be cleaned up because replicas do NOT accept + # writes. + if role_holder == "main": + cursor = connection_holder.cursor() + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") diff --git a/tests/e2e/high_availability_experimental/coordinator.py b/tests/e2e/high_availability_experimental/coordinator.py new file mode 100644 index 000000000..e34e9f069 --- /dev/null +++ b/tests/e2e/high_availability_experimental/coordinator.py @@ -0,0 +1,87 @@ +# Copyright 2024 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import sys + +import pytest +from common import execute_and_fetch_all +from mg_utils import mg_sleep_and_assert + + +def test_disable_cypher_queries(connection): + cursor = connection(7690, "coordinator").cursor() + with pytest.raises(Exception) as e: + execute_and_fetch_all(cursor, "CREATE (n:TestNode {prop: 'test'})") + assert str(e.value) == "Coordinator can run only coordinator queries!" + + +def test_coordinator_cannot_be_replica_role(connection): + cursor = connection(7690, "coordinator").cursor() + with pytest.raises(Exception) as e: + execute_and_fetch_all(cursor, "SET REPLICATION ROLE TO REPLICA WITH PORT 10001;") + assert str(e.value) == "Coordinator can run only coordinator queries!" + + +def test_coordinator_cannot_run_show_repl_role(connection): + cursor = connection(7690, "coordinator").cursor() + with pytest.raises(Exception) as e: + execute_and_fetch_all(cursor, "SHOW REPLICATION ROLE;") + assert str(e.value) == "Coordinator can run only coordinator queries!" + + +def test_coordinator_show_replication_cluster(connection): + cursor = connection(7690, "coordinator").cursor() + + def retrieve_data(): + return sorted(list(execute_and_fetch_all(cursor, "SHOW REPLICATION CLUSTER;"))) + + expected_data = [ + ("instance_1", "127.0.0.1:10011", True, "replica"), + ("instance_2", "127.0.0.1:10012", True, "replica"), + ("instance_3", "127.0.0.1:10013", True, "main"), + ] + mg_sleep_and_assert(expected_data, retrieve_data) + + +def test_coordinator_cannot_call_show_replicas(connection): + cursor = connection(7690, "coordinator").cursor() + with pytest.raises(Exception) as e: + execute_and_fetch_all(cursor, "SHOW REPLICAS;") + assert str(e.value) == "Coordinator can run only coordinator queries!" + + +@pytest.mark.parametrize( + "port, role", + [(7687, "main"), (7688, "replica"), (7689, "replica")], +) +def test_main_and_replicas_cannot_call_show_repl_cluster(port, role, connection): + cursor = connection(port, role).cursor() + with pytest.raises(Exception) as e: + execute_and_fetch_all(cursor, "SHOW REPLICATION CLUSTER;") + assert str(e.value) == "Only coordinator can run SHOW REPLICATION CLUSTER." + + +@pytest.mark.parametrize( + "port, role", + [(7687, "main"), (7688, "replica"), (7689, "replica")], +) +def test_main_and_replicas_cannot_register_coord_server(port, role, connection): + cursor = connection(port, role).cursor() + with pytest.raises(Exception) as e: + execute_and_fetch_all( + cursor, + "REGISTER INSTANCE instance_1 ON '127.0.0.1:10001' WITH '127.0.0.1:10011';", + ) + assert str(e.value) == "Only coordinator can register coordinator server!" + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/high_availability_experimental/manual_setting_replicas.py b/tests/e2e/high_availability_experimental/manual_setting_replicas.py new file mode 100644 index 000000000..f2d48ffd7 --- /dev/null +++ b/tests/e2e/high_availability_experimental/manual_setting_replicas.py @@ -0,0 +1,57 @@ +# Copyright 2024 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import os +import sys + +import interactive_mg_runner +import pytest +from common import execute_and_fetch_all +from mg_utils import mg_sleep_and_assert + +interactive_mg_runner.SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) +interactive_mg_runner.PROJECT_DIR = os.path.normpath( + os.path.join(interactive_mg_runner.SCRIPT_DIR, "..", "..", "..", "..") +) +interactive_mg_runner.BUILD_DIR = os.path.normpath(os.path.join(interactive_mg_runner.PROJECT_DIR, "build")) +interactive_mg_runner.MEMGRAPH_BINARY = os.path.normpath(os.path.join(interactive_mg_runner.BUILD_DIR, "memgraph")) + +MEMGRAPH_INSTANCES_DESCRIPTION = { + "instance_3": { + "args": ["--bolt-port", "7687", "--log-level", "TRACE", "--coordinator-server-port", "10013"], + "log_file": "main.log", + "setup_queries": [], + }, +} + + +def test_no_manual_setup_on_main(connection): + # Goal of this test is to check that all manual registration actions are disabled on instances with coordiantor server port + + # 1 + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION) + + any_main = connection(7687, "instance_3").cursor() + with pytest.raises(Exception) as e: + execute_and_fetch_all(any_main, "REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:10001';") + assert str(e.value) == "Can't register replica manually on instance with coordinator server port." + + with pytest.raises(Exception) as e: + execute_and_fetch_all(any_main, "DROP REPLICA replica_1;") + assert str(e.value) == "Can't drop replica manually on instance with coordinator server port." + + with pytest.raises(Exception) as e: + execute_and_fetch_all(any_main, "SET REPLICATION ROLE TO REPLICA WITH PORT 10002;") + assert str(e.value) == "Can't set role manually on instance with coordinator server port." + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/high_availability_experimental/workloads.yaml b/tests/e2e/high_availability_experimental/workloads.yaml new file mode 100644 index 000000000..1d692084a --- /dev/null +++ b/tests/e2e/high_availability_experimental/workloads.yaml @@ -0,0 +1,37 @@ +ha_cluster: &ha_cluster + cluster: + replica_1: + args: ["--bolt-port", "7688", "--log-level=TRACE", "--coordinator-server-port=10011"] + log_file: "replication-e2e-replica1.log" + setup_queries: [] + replica_2: + args: ["--bolt-port", "7689", "--log-level=TRACE", "--coordinator-server-port=10012"] + log_file: "replication-e2e-replica2.log" + setup_queries: [] + main: + args: ["--bolt-port", "7687", "--log-level=TRACE", "--coordinator-server-port=10013"] + log_file: "replication-e2e-main.log" + setup_queries: [] + coordinator: + args: ["--bolt-port", "7690", "--log-level=TRACE", "--coordinator"] + log_file: "replication-e2e-coordinator.log" + setup_queries: [ + "REGISTER INSTANCE instance_1 ON '127.0.0.1:10011' WITH '127.0.0.1:10001';", + "REGISTER INSTANCE instance_2 ON '127.0.0.1:10012' WITH '127.0.0.1:10002';", + "REGISTER INSTANCE instance_3 ON '127.0.0.1:10013' WITH '127.0.0.1:10003';", + "SET INSTANCE instance_3 TO MAIN;" + ] + +workloads: + - name: "Coordinator" + binary: "tests/e2e/pytest_runner.sh" + args: ["high_availability_experimental/coordinator.py"] + <<: *ha_cluster + + - name: "Automatic failover" + binary: "tests/e2e/pytest_runner.sh" + args: ["high_availability_experimental/automatic_failover.py"] + + - name: "Disabled manual setting of replication cluster" + binary: "tests/e2e/pytest_runner.sh" + args: ["high_availability_experimental/manual_setting_replicas.py"] diff --git a/tests/e2e/import_mode/CMakeLists.txt b/tests/e2e/import_mode/CMakeLists.txt index e316b7e82..b48b4f12d 100644 --- a/tests/e2e/import_mode/CMakeLists.txt +++ b/tests/e2e/import_mode/CMakeLists.txt @@ -4,3 +4,5 @@ endfunction() copy_import_mode_e2e_python_files(common.py) copy_import_mode_e2e_python_files(test_command.py) + +copy_e2e_files(import_mode workloads.yaml) diff --git a/tests/e2e/index_hints/CMakeLists.txt b/tests/e2e/index_hints/CMakeLists.txt index 5261baacc..38b3baef0 100644 --- a/tests/e2e/index_hints/CMakeLists.txt +++ b/tests/e2e/index_hints/CMakeLists.txt @@ -4,3 +4,5 @@ endfunction() copy_index_hints_e2e_python_files(common.py) copy_index_hints_e2e_python_files(index_hints.py) + +copy_e2e_files(index_hints workloads.yaml) diff --git a/tests/e2e/index_hints/index_hints.py b/tests/e2e/index_hints/index_hints.py index 70d3ce6b6..b59d60103 100644 --- a/tests/e2e/index_hints/index_hints.py +++ b/tests/e2e/index_hints/index_hints.py @@ -478,5 +478,44 @@ def test_nonexistent_label_property_index(memgraph): assert False +def test_index_hint_on_expand(memgraph): + # Prefer expanding from the node with the given hint even if estimator estimates higher cost for that plan + + memgraph.execute("FOREACH (i IN range(1, 1000) | CREATE (n:Label1 {id: i}));") + memgraph.execute("FOREACH (i IN range(1, 10) | CREATE (n:Label2 {id: i}));") + memgraph.execute("CREATE INDEX ON :Label1;") + memgraph.execute("CREATE INDEX ON :Label2;") + + expected_explain_without_hint = [ + " * Produce {n, m}", + " * Filter (n :Label1)", + " * Expand (m)<-[anon1:rel]-(n)", + " * ScanAllByLabel (m :Label2)", + " * Once", + ] + + expected_explain_with_hint = [ + " * Produce {n, m}", + " * Filter (m :Label2)", + " * Expand (n)-[anon1:rel]->(m)", + " * ScanAllByLabel (n :Label1)", + " * Once", + ] + + explain_without_hint = [ + row["QUERY PLAN"] + for row in memgraph.execute_and_fetch("EXPLAIN MATCH (n:Label1)-[:rel]->(m:Label2) RETURN n, m;") + ] + + explain_with_hint = [ + row["QUERY PLAN"] + for row in memgraph.execute_and_fetch( + "EXPLAIN USING INDEX :Label1 MATCH (n:Label1)-[:rel]->(m:Label2) RETURN n, m;" + ) + ] + + assert explain_without_hint == expected_explain_without_hint and explain_with_hint == expected_explain_with_hint + + if __name__ == "__main__": sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/init_file_flags/CMakeLists.txt b/tests/e2e/init_file_flags/CMakeLists.txt index 8d98898e8..50ac5e8e5 100644 --- a/tests/e2e/init_file_flags/CMakeLists.txt +++ b/tests/e2e/init_file_flags/CMakeLists.txt @@ -10,3 +10,5 @@ copy_init_file_flags_e2e_python_files(init_file_setup.py) copy_init_file_flags_e2e_python_files(init_data_file_setup.py) copy_init_file_flags_e2e_files(init_file.cypherl) + +copy_e2e_files(init_file_flags workloads.yaml) diff --git a/tests/e2e/inspect_query/CMakeLists.txt b/tests/e2e/inspect_query/CMakeLists.txt index f0dbdb7cc..4b9b3d82d 100644 --- a/tests/e2e/inspect_query/CMakeLists.txt +++ b/tests/e2e/inspect_query/CMakeLists.txt @@ -4,3 +4,5 @@ endfunction() copy_inspect_query_e2e_python_files(common.py) copy_inspect_query_e2e_python_files(inspect_query.py) + +copy_e2e_files(inspect_query workloads.yaml) diff --git a/tests/e2e/interactive_mg_runner.py b/tests/e2e/interactive_mg_runner.py old mode 100644 new mode 100755 index 13aa951db..f0e4e6da1 --- a/tests/e2e/interactive_mg_runner.py +++ b/tests/e2e/interactive_mg_runner.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Copyright 2022 Memgraph Ltd. # # Use of this software is governed by the Business Source License diff --git a/tests/e2e/isolation_levels/CMakeLists.txt b/tests/e2e/isolation_levels/CMakeLists.txt index a5f31a79d..1835d75df 100644 --- a/tests/e2e/isolation_levels/CMakeLists.txt +++ b/tests/e2e/isolation_levels/CMakeLists.txt @@ -2,3 +2,5 @@ find_package(gflags REQUIRED) add_executable(memgraph__e2e__isolation_levels isolation_levels.cpp) target_link_libraries(memgraph__e2e__isolation_levels gflags mgclient mg-utils mg-io Threads::Threads) + +copy_e2e_files(isolation_levels workloads.yaml) diff --git a/tests/e2e/isolation_levels/isolation_levels.cpp b/tests/e2e/isolation_levels/isolation_levels.cpp index 2ead05750..751e63594 100644 --- a/tests/e2e/isolation_levels/isolation_levels.cpp +++ b/tests/e2e/isolation_levels/isolation_levels.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -87,18 +87,13 @@ void SwitchToDB(const std::string &name, std::unique_ptr<mg::Client> &client) { void SwitchToCleanDB(std::unique_ptr<mg::Client> &client) { SwitchToDB("clean", client); } void SwitchToSameDB(std::unique_ptr<mg::Client> &main, std::unique_ptr<mg::Client> &client) { - MG_ASSERT(main->Execute("SHOW DATABASES;")); + MG_ASSERT(main->Execute("SHOW DATABASE;")); auto dbs = main->FetchAll(); MG_ASSERT(dbs, "Failed to show databases"); - for (const auto &elem : *dbs) { - MG_ASSERT(!elem.empty(), "Show databases wrong output"); - const auto &active = elem[1].ValueString(); - if (active == "*") { - const auto &name = elem[0].ValueString(); - SwitchToDB(std::string(name), client); - break; - } - } + MG_ASSERT(!dbs->empty(), "Show databases wrong output"); + MG_ASSERT(!(*dbs)[0].empty(), "Show databases wrong output"); + const auto &name = (*dbs)[0][0].ValueString(); + SwitchToDB(std::string(name), client); } void TestSnapshotIsolation(std::unique_ptr<mg::Client> &client) { diff --git a/tests/e2e/lba_procedures/CMakeLists.txt b/tests/e2e/lba_procedures/CMakeLists.txt index 8e1ebb41b..9547ef430 100644 --- a/tests/e2e/lba_procedures/CMakeLists.txt +++ b/tests/e2e/lba_procedures/CMakeLists.txt @@ -11,3 +11,5 @@ copy_lba_procedures_e2e_python_files(read_permission_queries.py) copy_lba_procedures_e2e_python_files(update_permission_queries.py) add_subdirectory(procedures) + +copy_e2e_files(lba_procedures workloads.yaml) diff --git a/tests/e2e/lba_procedures/show_privileges.py b/tests/e2e/lba_procedures/show_privileges.py index 1021ea7be..247140a60 100644 --- a/tests/e2e/lba_procedures/show_privileges.py +++ b/tests/e2e/lba_procedures/show_privileges.py @@ -40,6 +40,7 @@ BASIC_PRIVILEGES = [ "STORAGE_MODE", "MULTI_DATABASE_EDIT", "MULTI_DATABASE_USE", + "COORDINATOR", ] @@ -63,7 +64,7 @@ def test_lba_procedures_show_privileges_first_user(): cursor = connect(username="Josip", password="").cursor() result = execute_and_fetch_all(cursor, "SHOW PRIVILEGES FOR Josip;") - assert len(result) == 34 + assert len(result) == 35 fine_privilege_results = [res for res in result if res[0] not in BASIC_PRIVILEGES] diff --git a/tests/e2e/load_csv/CMakeLists.txt b/tests/e2e/load_csv/CMakeLists.txt index 368915dbe..6c1ebc38a 100644 --- a/tests/e2e/load_csv/CMakeLists.txt +++ b/tests/e2e/load_csv/CMakeLists.txt @@ -11,3 +11,5 @@ copy_load_csv_e2e_files(simple.csv) copy_load_csv_e2e_python_files(load_csv_nullif.py) copy_load_csv_e2e_files(nullif.csv) + +copy_e2e_files(load_csv workloads.yaml) diff --git a/tests/e2e/magic_functions/CMakeLists.txt b/tests/e2e/magic_functions/CMakeLists.txt index 3ab627e22..0f009f635 100644 --- a/tests/e2e/magic_functions/CMakeLists.txt +++ b/tests/e2e/magic_functions/CMakeLists.txt @@ -8,3 +8,5 @@ copy_magic_functions_e2e_python_files(conftest.py) copy_magic_functions_e2e_python_files(function_example.py) add_subdirectory(functions) + +copy_e2e_files(functions workloads.yaml) diff --git a/tests/e2e/memory/CMakeLists.txt b/tests/e2e/memory/CMakeLists.txt index 3c4cdc279..256107724 100644 --- a/tests/e2e/memory/CMakeLists.txt +++ b/tests/e2e/memory/CMakeLists.txt @@ -49,3 +49,5 @@ target_link_libraries(memgraph__e2e__procedure_memory_limit gflags mgclient mg-u add_executable(memgraph__e2e__procedure_memory_limit_multi_proc procedure_memory_limit_multi_proc.cpp) target_link_libraries(memgraph__e2e__procedure_memory_limit_multi_proc gflags mgclient mg-utils mg-io) + +copy_e2e_files(memory workloads.yaml) diff --git a/tests/e2e/mg_utils.py b/tests/e2e/mg_utils.py index 9eec91da0..74cc8dc3a 100644 --- a/tests/e2e/mg_utils.py +++ b/tests/e2e/mg_utils.py @@ -1,12 +1,11 @@ import time -def mg_sleep_and_assert(expected_value, function_to_retrieve_data, max_duration=20, time_between_attempt=0.05): +def mg_sleep_and_assert(expected_value, function_to_retrieve_data, max_duration=20, time_between_attempt=0.2): result = function_to_retrieve_data() start_time = time.time() while result != expected_value: - current_time = time.time() - duration = current_time - start_time + duration = time.time() - start_time if duration > max_duration: assert ( False diff --git a/tests/e2e/mock_api/CMakeLists.txt b/tests/e2e/mock_api/CMakeLists.txt index aa170dc62..ef5845b26 100644 --- a/tests/e2e/mock_api/CMakeLists.txt +++ b/tests/e2e/mock_api/CMakeLists.txt @@ -6,3 +6,5 @@ add_subdirectory(procedures) copy_mock_python_api_e2e_files(common.py) copy_mock_python_api_e2e_files(test_compare_mock.py) + +copy_e2e_files(mock_python_api workloads.yaml) diff --git a/tests/e2e/module_file_manager/CMakeLists.txt b/tests/e2e/module_file_manager/CMakeLists.txt index 84d8845ff..d8eea3f9b 100644 --- a/tests/e2e/module_file_manager/CMakeLists.txt +++ b/tests/e2e/module_file_manager/CMakeLists.txt @@ -2,3 +2,5 @@ find_package(gflags REQUIRED) add_executable(memgraph__e2e__module_file_manager module_file_manager.cpp) target_link_libraries(memgraph__e2e__module_file_manager gflags mgclient mg-utils mg-io Threads::Threads) + +copy_e2e_files(module_file_manager workloads.yaml) diff --git a/tests/e2e/monitoring_server/CMakeLists.txt b/tests/e2e/monitoring_server/CMakeLists.txt index 4c2c441e2..7062e978d 100644 --- a/tests/e2e/monitoring_server/CMakeLists.txt +++ b/tests/e2e/monitoring_server/CMakeLists.txt @@ -6,3 +6,5 @@ target_link_libraries(memgraph__e2e__monitoring_server mgclient mg-utils json gf add_executable(memgraph__e2e__monitoring_server_ssl monitoring_ssl.cpp) target_link_libraries(memgraph__e2e__monitoring_server_ssl mgclient mg-utils json gflags Boost::headers) + +copy_e2e_files(monitoring_server workloads.yaml) diff --git a/tests/e2e/python_query_modules_reloading/CMakeLists.txt b/tests/e2e/python_query_modules_reloading/CMakeLists.txt index ee8f29f90..27320e91b 100644 --- a/tests/e2e/python_query_modules_reloading/CMakeLists.txt +++ b/tests/e2e/python_query_modules_reloading/CMakeLists.txt @@ -6,3 +6,5 @@ copy_query_modules_reloading_procedures_e2e_python_files(common.py) copy_query_modules_reloading_procedures_e2e_python_files(test_reload_query_module.py) add_subdirectory(procedures) + +copy_e2e_files(python_query_modules_reloading workloads.yaml) diff --git a/tests/e2e/queries/CMakeLists.txt b/tests/e2e/queries/CMakeLists.txt index f672b8591..720599a18 100644 --- a/tests/e2e/queries/CMakeLists.txt +++ b/tests/e2e/queries/CMakeLists.txt @@ -4,3 +4,5 @@ endfunction() copy_queries_e2e_python_files(common.py) copy_queries_e2e_python_files(queries.py) + +copy_e2e_files(queries workloads.yaml) diff --git a/tests/e2e/query_modules/CMakeLists.txt b/tests/e2e/query_modules/CMakeLists.txt index a97bbf1a5..3af2b80b6 100644 --- a/tests/e2e/query_modules/CMakeLists.txt +++ b/tests/e2e/query_modules/CMakeLists.txt @@ -7,3 +7,5 @@ copy_query_modules_e2e_python_files(conftest.py) copy_query_modules_e2e_python_files(convert_test.py) copy_query_modules_e2e_python_files(mgps_test.py) copy_query_modules_e2e_python_files(schema_test.py) + +copy_e2e_files(query_modules workloads.yaml) diff --git a/tests/e2e/replication/CMakeLists.txt b/tests/e2e/replication/CMakeLists.txt index 39f179a3d..4abd10278 100644 --- a/tests/e2e/replication/CMakeLists.txt +++ b/tests/e2e/replication/CMakeLists.txt @@ -9,11 +9,13 @@ target_link_libraries(memgraph__e2e__replication__indices gflags mgclient mg-uti add_executable(memgraph__e2e__replication__read_write_benchmark read_write_benchmark.cpp) target_link_libraries(memgraph__e2e__replication__read_write_benchmark gflags json mgclient mg-utils mg-io Threads::Threads) -copy_e2e_python_files(replication_show common.py) -copy_e2e_python_files(replication_show conftest.py) -copy_e2e_python_files(replication_show show.py) -copy_e2e_python_files(replication_show show_while_creating_invalid_state.py) -copy_e2e_python_files(replication_show edge_delete.py) -copy_e2e_python_files_from_parent_folder(replication_show ".." memgraph.py) -copy_e2e_python_files_from_parent_folder(replication_show ".." interactive_mg_runner.py) -copy_e2e_python_files_from_parent_folder(replication_show ".." mg_utils.py) +copy_e2e_python_files(replication common.py) +copy_e2e_python_files(replication conftest.py) +copy_e2e_python_files(replication show.py) +copy_e2e_python_files(replication show_while_creating_invalid_state.py) +copy_e2e_python_files(replication edge_delete.py) +copy_e2e_python_files_from_parent_folder(replication ".." memgraph.py) +copy_e2e_python_files_from_parent_folder(replication ".." interactive_mg_runner.py) +copy_e2e_python_files_from_parent_folder(replication ".." mg_utils.py) + +copy_e2e_files(replication workloads.yaml) diff --git a/tests/e2e/replication/constraints.cpp b/tests/e2e/replication/constraints.cpp index 01c1217f2..6f7e2991a 100644 --- a/tests/e2e/replication/constraints.cpp +++ b/tests/e2e/replication/constraints.cpp @@ -49,7 +49,7 @@ int main(int argc, char **argv) { const auto label_name = (*data)[0][1].ValueString(); const auto property_name = (*data)[0][2].ValueList()[0].ValueString(); if (label_name != "Node" || property_name != "id") { - LOG_FATAL("{} does NOT hava valid constraint created.", database_endpoint); + LOG_FATAL("{} does NOT have a valid constraint created.", database_endpoint); } } else { LOG_FATAL("Unable to get CONSTRAINT INFO from {}", database_endpoint); diff --git a/tests/e2e/replication/show_while_creating_invalid_state.py b/tests/e2e/replication/show_while_creating_invalid_state.py index 74dcbce74..8da0c560a 100644 --- a/tests/e2e/replication/show_while_creating_invalid_state.py +++ b/tests/e2e/replication/show_while_creating_invalid_state.py @@ -308,7 +308,7 @@ def test_basic_recovery(connection): "--bolt-port", "7687", "--log-level=TRACE", - "--storage-recover-on-startup=true", + "--data-recovery-on-startup=true", "--replication-restore-state-on-startup=true", ], "log_file": "main.log", @@ -1754,5 +1754,74 @@ def test_triggers_on_create_before_commit_with_offline_sync_replica(): assert res_from_main == interactive_mg_runner.MEMGRAPH_INSTANCES["sync_replica2"].query(QUERY_TO_CHECK) +def test_replication_not_messed_up_by_CreateSnapshot(connection): + # Goal of this test is to check the replica can not run CreateSnapshot + # 1/ CREATE SNAPSHOT should raise a DatabaseError + + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION) + + cursor = connection(7688, "replica_1").cursor() + + # 1/ + with pytest.raises(mgclient.DatabaseError): + execute_and_fetch_all(cursor, "CREATE SNAPSHOT;") + + +def test_replication_not_messed_up_by_ShowIndexInfo(connection): + # Goal of this test is to check the replicas timestamp and hence ability to recieve MAINs writes + # is uneffected by SHOW INDEX INFO + + # 1/ Run SHOW INDEX INFO; multiple times on REPLICA + # 2/ Send a write from MAIN + # 3/ Check REPLICA processed the write + + BASIC_MEMGRAPH_INSTANCES_DESCRIPTION = { + "replica_1": { + "args": ["--bolt-port", "7688", "--log-level=TRACE"], + "log_file": "replica1.log", + "setup_queries": ["SET REPLICATION ROLE TO REPLICA WITH PORT 10001;"], + }, + "main": { + "args": ["--bolt-port", "7687", "--log-level=TRACE"], + "log_file": "main.log", + "setup_queries": [ + "REGISTER REPLICA replica_1 ASYNC TO '127.0.0.1:10001';", + ], + }, + } + + interactive_mg_runner.start_all(BASIC_MEMGRAPH_INSTANCES_DESCRIPTION) + + cursor = connection(7688, "replica_1").cursor() + + # 1/ + # This query use to incorrectly change REPLICA storage timestamp + # run this multiple times to try and get into error case of MAIN timestamp < REPLICA timestamp + for _ in range(20): + execute_and_fetch_all(cursor, "SHOW INDEX INFO;") + + cursor = connection(7687, "main").cursor() + + # 2/ + execute_and_fetch_all(cursor, "CREATE ();") + + def retrieve_data(): + replicas = interactive_mg_runner.MEMGRAPH_INSTANCES["main"].query("SHOW REPLICAS;") + return replicas + + expected_data = [ + ("replica_1", "127.0.0.1:10001", "async", 2, 0, "ready"), + ] + actual_data = mg_sleep_and_assert(expected_data, retrieve_data) + assert actual_data == expected_data + + # 3/ + cursor = connection(7688, "replica_1").cursor() + result = execute_and_fetch_all(cursor, "MATCH () RETURN count(*);") + + assert len(result) == 1 + assert result[0][0] == 1 # The one node was replicated from MAIN to REPLICA + + if __name__ == "__main__": sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/replication/workloads.yaml b/tests/e2e/replication/workloads.yaml index fc239b221..c455ccc76 100644 --- a/tests/e2e/replication/workloads.yaml +++ b/tests/e2e/replication/workloads.yaml @@ -11,7 +11,7 @@ template_validation_queries: &template_validation_queries template_simple_cluster: &template_simple_cluster cluster: replica_1: - args: [ "--bolt-port", "7688", "--log-level=TRACE" ] + args: [ "--bolt-port", "7688", "--log-level=TRACE"] log_file: "replication-e2e-replica1.log" setup_queries: [ "SET REPLICATION ROLE TO REPLICA WITH PORT 10001;" ] replica_2: @@ -25,6 +25,7 @@ template_simple_cluster: &template_simple_cluster "REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:10001'", "REGISTER REPLICA replica_2 ASYNC TO '127.0.0.1:10002'", ] + template_cluster: &template_cluster cluster: replica_1: @@ -50,7 +51,6 @@ template_cluster: &template_cluster "REGISTER REPLICA replica_2 SYNC TO '127.0.0.1:10002'", "REGISTER REPLICA replica_3 ASYNC TO '127.0.0.1:10003'" ] - <<: *template_validation_queries workloads: - name: "Constraints" diff --git a/tests/e2e/replication_experimental/CMakeLists.txt b/tests/e2e/replication_experimental/CMakeLists.txt new file mode 100644 index 000000000..cd6e09f38 --- /dev/null +++ b/tests/e2e/replication_experimental/CMakeLists.txt @@ -0,0 +1,10 @@ +find_package(gflags REQUIRED) + +copy_e2e_python_files(replication_experiment common.py) +copy_e2e_python_files(replication_experiment conftest.py) +copy_e2e_python_files(replication_experiment multitenancy.py) +copy_e2e_python_files_from_parent_folder(replication_experiment ".." memgraph.py) +copy_e2e_python_files_from_parent_folder(replication_experiment ".." interactive_mg_runner.py) +copy_e2e_python_files_from_parent_folder(replication_experiment ".." mg_utils.py) + +copy_e2e_files(replication_experiment workloads.yaml) diff --git a/tests/e2e/replication_experimental/common.py b/tests/e2e/replication_experimental/common.py new file mode 100644 index 000000000..dc104d628 --- /dev/null +++ b/tests/e2e/replication_experimental/common.py @@ -0,0 +1,25 @@ +# Copyright 2022 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import typing + +import mgclient + + +def execute_and_fetch_all(cursor: mgclient.Cursor, query: str, params: dict = {}) -> typing.List[tuple]: + cursor.execute(query, params) + return cursor.fetchall() + + +def connect(**kwargs) -> mgclient.Connection: + connection = mgclient.connect(**kwargs) + connection.autocommit = True + return connection diff --git a/tests/e2e/replication_experimental/conftest.py b/tests/e2e/replication_experimental/conftest.py new file mode 100644 index 000000000..f91333cbf --- /dev/null +++ b/tests/e2e/replication_experimental/conftest.py @@ -0,0 +1,33 @@ +# Copyright 2022 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import pytest +from common import connect, execute_and_fetch_all + + +@pytest.fixture(scope="function") +def connection(): + connection_holder = None + role_holder = None + + def inner_connection(port, role): + nonlocal connection_holder, role_holder + connection_holder = connect(host="localhost", port=port) + role_holder = role + return connection_holder + + yield inner_connection + + # Only main instance can be cleaned up because replicas do NOT accept + # writes. + if role_holder == "main": + cursor = connection_holder.cursor() + execute_and_fetch_all(cursor, "MATCH (n) DETACH DELETE n;") diff --git a/tests/e2e/replication_experimental/multitenancy.py b/tests/e2e/replication_experimental/multitenancy.py new file mode 100644 index 000000000..7eb699341 --- /dev/null +++ b/tests/e2e/replication_experimental/multitenancy.py @@ -0,0 +1,1046 @@ +# Copyright 2022 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import atexit +import os +import shutil +import sys +import tempfile +import time +from functools import partial + +import interactive_mg_runner +import mgclient +import pytest +from common import execute_and_fetch_all +from mg_utils import mg_sleep_and_assert + +interactive_mg_runner.SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) +interactive_mg_runner.PROJECT_DIR = os.path.normpath( + os.path.join(interactive_mg_runner.SCRIPT_DIR, "..", "..", "..", "..") +) +interactive_mg_runner.BUILD_DIR = os.path.normpath(os.path.join(interactive_mg_runner.PROJECT_DIR, "build")) +interactive_mg_runner.MEMGRAPH_BINARY = os.path.normpath(os.path.join(interactive_mg_runner.BUILD_DIR, "memgraph")) + +BOLT_PORTS = {"main": 7687, "replica_1": 7688, "replica_2": 7689} +REPLICATION_PORTS = {"replica_1": 10001, "replica_2": 10002} + +MEMGRAPH_INSTANCES_DESCRIPTION = { + "replica_1": { + "args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"], + "log_file": "replica1.log", + "setup_queries": [f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_1']};"], + }, + "replica_2": { + "args": ["--bolt-port", f"{BOLT_PORTS['replica_2']}", "--log-level=TRACE"], + "log_file": "replica2.log", + "setup_queries": [f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_2']};"], + }, + "main": { + "args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"], + "log_file": "main.log", + "setup_queries": [ + f"REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_1']}';", + f"REGISTER REPLICA replica_2 ASYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_2']}';", + ], + }, +} + +TEMP_DIR = tempfile.TemporaryDirectory().name + +MEMGRAPH_INSTANCES_DESCRIPTION_WITH_RECOVERY = { + "replica_1": { + "args": [ + "--bolt-port", + f"{BOLT_PORTS['replica_1']}", + "--log-level=TRACE", + "--replication-restore-state-on-startup", + "--data-recovery-on-startup", + ], + "log_file": "replica1.log", + "data_directory": TEMP_DIR + "/replica1", + }, + "replica_2": { + "args": [ + "--bolt-port", + f"{BOLT_PORTS['replica_2']}", + "--log-level=TRACE", + "--replication-restore-state-on-startup", + "--data-recovery-on-startup", + ], + "log_file": "replica2.log", + "data_directory": TEMP_DIR + "/replica2", + }, + "main": { + "args": [ + "--bolt-port", + f"{BOLT_PORTS['main']}", + "--log-level=TRACE", + "--replication-restore-state-on-startup", + "--data-recovery-on-startup", + ], + "log_file": "main.log", + "data_directory": TEMP_DIR + "/main", + }, +} + + +def safe_execute(function, *args): + try: + function(*args) + except: + pass + + +def setup_replication(connection): + # Setup replica1 + cursor = connection(BOLT_PORTS["replica_1"], "replica").cursor() + execute_and_fetch_all(cursor, f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_1']};") + # Setup replica2 + cursor = connection(BOLT_PORTS["replica_2"], "replica").cursor() + execute_and_fetch_all(cursor, f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_2']};") + # Setup main + cursor = connection(BOLT_PORTS["main"], "main").cursor() + execute_and_fetch_all(cursor, f"REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_1']}';") + execute_and_fetch_all(cursor, f"REGISTER REPLICA replica_2 ASYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_2']}';") + + +def setup_main(main_cursor): + execute_and_fetch_all(main_cursor, "USE DATABASE A;") + execute_and_fetch_all(main_cursor, "CREATE (:Node{on:'A'});") + execute_and_fetch_all(main_cursor, "CREATE (:Node)-[:EDGE]->(:Node)") + execute_and_fetch_all(main_cursor, "CREATE (:Node)-[:EDGE]->(:Node)") + execute_and_fetch_all(main_cursor, "CREATE (:Node)-[:EDGE]->(:Node)") + + execute_and_fetch_all(main_cursor, "USE DATABASE B;") + execute_and_fetch_all(main_cursor, "CREATE (:Node{on:'B'});") + execute_and_fetch_all(main_cursor, "CREATE (:Node{on:'B'});") + + +def show_replicas_func(cursor, db_name): + def func(): + execute_and_fetch_all(cursor, f"USE DATABASE {db_name};") + return set(execute_and_fetch_all(cursor, "SHOW REPLICAS;")) + + return func + + +def show_databases_func(cursor): + def func(): + return execute_and_fetch_all(cursor, "SHOW DATABASES;") + + return func + + +def get_number_of_nodes_func(cursor, db_name): + def func(): + execute_and_fetch_all(cursor, f"USE DATABASE {db_name};") + return execute_and_fetch_all(cursor, "MATCH (n) RETURN count(*);")[0][0] + + return func + + +def get_number_of_edges_func(cursor, db_name): + def func(): + execute_and_fetch_all(cursor, f"USE DATABASE {db_name};") + return execute_and_fetch_all(cursor, "MATCH ()-[r]->() RETURN count(*);")[0][0] + + return func + + +def test_manual_databases_create_multitenancy_replication(connection): + # Goal: to show that replication can be established against REPLICA which already + # has the clean databases we need + # 0/ MAIN CREATE DATABASE A + B + # REPLICA CREATE DATABASE A + B + # Setup replication + # 1/ Write to MAIN A, Write to MAIN B + # 2/ Validate replication of changes to A + B have arrived at REPLICA + + MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = { + "replica_1": { + "args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"], + "log_file": "replica1.log", + "setup_queries": [ + "CREATE DATABASE A;", + "CREATE DATABASE B;", + f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_1']};", + ], + }, + "replica_2": { + "args": ["--bolt-port", f"{BOLT_PORTS['replica_2']}", "--log-level=TRACE"], + "log_file": "replica2.log", + "setup_queries": [ + "CREATE DATABASE A;", + "CREATE DATABASE B;", + f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_2']};", + ], + }, + "main": { + "args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"], + "log_file": "main.log", + "setup_queries": [ + "CREATE DATABASE A;", + "CREATE DATABASE B;", + f"REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_1']}';", + f"REGISTER REPLICA replica_2 ASYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_2']}';", + ], + }, + } + + # 0/ + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL) + cursor = connection(BOLT_PORTS["main"], "main").cursor() + + # 1/ + execute_and_fetch_all(cursor, "USE DATABASE A;") + execute_and_fetch_all(cursor, "CREATE ();") + execute_and_fetch_all(cursor, "USE DATABASE B;") + execute_and_fetch_all(cursor, "CREATE ()-[:EDGE]->();") + + # 2/ + expected_data = { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 1, 0, "ready"), + ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 1, 0, "ready"), + } + mg_sleep_and_assert(expected_data, show_replicas_func(cursor, "A")) + + expected_data = { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 1, 0, "ready"), + ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 1, 0, "ready"), + } + mg_sleep_and_assert(expected_data, show_replicas_func(cursor, "B")) + + cursor_replica = connection(BOLT_PORTS["replica_1"], "replica").cursor() + assert get_number_of_nodes_func(cursor_replica, "A")() == 1 + assert get_number_of_edges_func(cursor_replica, "A")() == 0 + assert get_number_of_nodes_func(cursor_replica, "B")() == 2 + assert get_number_of_edges_func(cursor_replica, "B")() == 1 + + cursor_replica2 = connection(BOLT_PORTS["replica_1"], "replica_2").cursor() + assert get_number_of_nodes_func(cursor_replica2, "A")() == 1 + assert get_number_of_edges_func(cursor_replica2, "A")() == 0 + assert get_number_of_nodes_func(cursor_replica2, "B")() == 2 + assert get_number_of_edges_func(cursor_replica2, "B")() == 1 + + +def test_manual_databases_create_multitenancy_replication_branching(connection): + # Goal: to show that replication can be established against REPLICA which already + # has all the databases and the same data + # 0/ MAIN CREATE DATABASE A + B and fill with data + # REPLICA CREATE DATABASE A + B and fil with exact data + # Setup REPLICA + # 1/ Registering REPLICA on MAIN should not fail due to tenant branching + + MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = { + "replica_1": { + "args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"], + "log_file": "replica1.log", + "setup_queries": [ + "CREATE DATABASE A;", + "USE DATABASE A;", + "CREATE ()", + "CREATE DATABASE B;", + "USE DATABASE B;", + "CREATE ()-[:EDGE]->()", + f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_1']};", + ], + }, + "replica_2": { + "args": ["--bolt-port", f"{BOLT_PORTS['replica_2']}", "--log-level=TRACE"], + "log_file": "replica2.log", + "setup_queries": [ + "CREATE DATABASE A;", + "USE DATABASE A;", + "CREATE ()", + "CREATE DATABASE B;", + "USE DATABASE B;", + "CREATE ()-[:EDGE]->()", + f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_2']};", + ], + }, + "main": { + "args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"], + "log_file": "main.log", + "setup_queries": [ + "CREATE DATABASE A;", + "USE DATABASE A;", + "CREATE ()", + "CREATE DATABASE B;", + "USE DATABASE B;", + "CREATE ()-[:EDGE]->()", + ], + }, + } + + # 0/ + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL) + cursor = connection(BOLT_PORTS["main"], "main").cursor() + + # 1/ + failed = False + try: + execute_and_fetch_all( + cursor, f"REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_1']}';" + ) + except mgclient.DatabaseError: + failed = True + assert not failed + + try: + execute_and_fetch_all( + cursor, f"REGISTER REPLICA replica_2 ASYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_2']}';" + ) + except mgclient.DatabaseError: + failed = True + assert not failed + + +def test_manual_databases_create_multitenancy_replication_dirty_replica(connection): + # Goal: to show that replication can be established against REPLICA which already + # has all the databases we need, even when they branched + # 0/ MAIN CREATE DATABASE A + # REPLICA CREATE DATABASE A + # REPLICA write to A + # Setup REPLICA + # 1/ Register replica; should fail + + MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = { + "replica_1": { + "args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"], + "log_file": "replica1.log", + "setup_queries": [ + "CREATE DATABASE A;", + "USE DATABASE A;", + "CREATE (:Node{from:'A'})", + f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_1']};", + ], + }, + "replica_2": { + "args": ["--bolt-port", f"{BOLT_PORTS['replica_2']}", "--log-level=TRACE"], + "log_file": "replica2.log", + "setup_queries": [ + "CREATE DATABASE A;", + "USE DATABASE A;", + "CREATE (:Node{from:'A'})", + f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_2']};", + ], + }, + "main": { + "args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"], + "log_file": "main.log", + "setup_queries": [ + "CREATE DATABASE A;", + ], + }, + } + + # 0/ + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL) + cursor = connection(BOLT_PORTS["main"], "main").cursor() + + # 1/ + failed = False + try: + execute_and_fetch_all( + cursor, f"REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_1']}';" + ) + except mgclient.DatabaseError: + failed = True + assert not failed + + try: + execute_and_fetch_all( + cursor, f"REGISTER REPLICA replica_2 ASYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_2']}';" + ) + except mgclient.DatabaseError: + failed = True + assert not failed + + +def test_manual_databases_create_multitenancy_replication_main_behind(connection): + # Goal: to show that replication can be established against REPLICA which has + # different branched databases + # 0/ REPLICA CREATE DATABASE A + # REPLICA write to A + # Setup replication + # 1/ MAIN CREATE DATABASE A + # 2/ Check that database has been replicated + + MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = { + "replica_1": { + "args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"], + "log_file": "replica1.log", + "setup_queries": [ + "CREATE DATABASE A;", + "USE DATABASE A;", + "CREATE (:Node{from:'A'})", + f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_1']};", + ], + }, + "replica_2": { + "args": ["--bolt-port", f"{BOLT_PORTS['replica_2']}", "--log-level=TRACE"], + "log_file": "replica2.log", + "setup_queries": [ + "CREATE DATABASE A;", + "USE DATABASE A;", + "CREATE (:Node{from:'A'})", + f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_2']};", + ], + }, + "main": { + "args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"], + "log_file": "main.log", + "setup_queries": [ + f"REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_1']}';", + f"REGISTER REPLICA replica_2 ASYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_2']}';", + ], + }, + } + + # 0/ + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL) + main_cursor = connection(BOLT_PORTS["main"], "main").cursor() + + # 1/ + execute_and_fetch_all(main_cursor, "CREATE DATABASE A;") + + # 2/ + expected_data = { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 0, 0, "ready"), + ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 0, 0, "ready"), + } + mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "A")) + + databases_on_main = show_databases_func(main_cursor)() + + replica_cursor = connection(BOLT_PORTS["replica_1"], "replica").cursor() + mg_sleep_and_assert(databases_on_main, show_databases_func(replica_cursor)) + + replica_cursor = connection(BOLT_PORTS["replica_2"], "replica").cursor() + mg_sleep_and_assert(databases_on_main, show_databases_func(replica_cursor)) + + +def test_automatic_databases_create_multitenancy_replication(connection): + # Goal: to show that replication can be established against REPLICA where a new databases + # needs replication + # 0/ Setup replication + # 1/ MAIN CREATE DATABASE A + # 2/ Write to MAIN A + # 3/ Validate replication of changes to A have arrived at REPLICA + + # 0/ + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION) + main_cursor = connection(BOLT_PORTS["main"], "main").cursor() + + # 1/ + execute_and_fetch_all(main_cursor, "CREATE DATABASE A;") + execute_and_fetch_all(main_cursor, "CREATE DATABASE B;") + + # 2/ + execute_and_fetch_all(main_cursor, "USE DATABASE A;") + execute_and_fetch_all(main_cursor, "CREATE (:Node{on:'A'});") + execute_and_fetch_all(main_cursor, "CREATE (:Node)-[:EDGE]->(:Node)") + execute_and_fetch_all(main_cursor, "CREATE (:Node)-[:EDGE]->(:Node)") + execute_and_fetch_all(main_cursor, "CREATE (:Node)-[:EDGE]->(:Node)") + + # 3/ + expected_data = { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 7, 0, "ready"), + ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 7, 0, "ready"), + } + mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "A")) + + expected_data = { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 0, 0, "ready"), + ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 0, 0, "ready"), + } + mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "B")) + + cursor_replica = connection(BOLT_PORTS["replica_1"], "replica").cursor() + assert get_number_of_nodes_func(cursor_replica, "A")() == 7 + assert get_number_of_edges_func(cursor_replica, "A")() == 3 + assert get_number_of_nodes_func(cursor_replica, "B")() == 0 + assert get_number_of_edges_func(cursor_replica, "B")() == 0 + + cursor_replica = connection(BOLT_PORTS["replica_2"], "replica").cursor() + assert get_number_of_nodes_func(cursor_replica, "A")() == 7 + assert get_number_of_edges_func(cursor_replica, "A")() == 3 + assert get_number_of_nodes_func(cursor_replica, "B")() == 0 + assert get_number_of_edges_func(cursor_replica, "B")() == 0 + + +def test_automatic_databases_multitenancy_replication_predefined(connection): + # Goal: to show that replication can be established against REPLICA which doesn't + # have any additional databases; MAIN's database clean at registration time + # 0/ MAIN CREATE DATABASE A + B + # Setup replication + # 1/ Write to MAIN A, Write to MAIN B + # 2/ Validate replication of changes to A + B have arrived at REPLICA + + MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = { + "replica_1": { + "args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"], + "log_file": "replica1.log", + "setup_queries": [ + f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_1']};", + ], + }, + "main": { + "args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"], + "log_file": "main.log", + "setup_queries": [ + "CREATE DATABASE A;", + "CREATE DATABASE B;", + f"REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_1']}';", + ], + }, + } + + # 0/ + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL) + cursor = connection(BOLT_PORTS["main"], "main").cursor() + + # 1/ + execute_and_fetch_all(cursor, "USE DATABASE A;") + execute_and_fetch_all(cursor, "CREATE ();") + execute_and_fetch_all(cursor, "USE DATABASE B;") + execute_and_fetch_all(cursor, "CREATE ()-[:EDGE]->();") + + # 2/ + expected_data = { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 1, 0, "ready"), + } + mg_sleep_and_assert(expected_data, show_replicas_func(cursor, "A")) + + expected_data = { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 1, 0, "ready"), + } + mg_sleep_and_assert(expected_data, show_replicas_func(cursor, "B")) + + cursor_replica = connection(BOLT_PORTS["replica_1"], "replica").cursor() + assert get_number_of_nodes_func(cursor_replica, "A")() == 1 + assert get_number_of_edges_func(cursor_replica, "A")() == 0 + + +def test_automatic_databases_create_multitenancy_replication_dirty_main(connection): + # Goal: to show that replication can be established against REPLICA which doesn't + # have any additional databases; MAIN's database dirty at registration time + # 0/ MAIN CREATE DATABASE A + # MAIN write to A + # Setup replication + # 1/ Validate + + MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL = { + "replica_1": { + "args": ["--bolt-port", f"{BOLT_PORTS['replica_1']}", "--log-level=TRACE"], + "log_file": "replica1.log", + "setup_queries": [ + f"SET REPLICATION ROLE TO REPLICA WITH PORT {REPLICATION_PORTS['replica_1']};", + ], + }, + "main": { + "args": ["--bolt-port", f"{BOLT_PORTS['main']}", "--log-level=TRACE"], + "log_file": "main.log", + "setup_queries": [ + "CREATE DATABASE A;", + "USE DATABASE A;", + "CREATE (:Node{from:'A'})", + f"REGISTER REPLICA replica_1 SYNC TO '127.0.0.1:{REPLICATION_PORTS['replica_1']}';", + ], + }, + } + + # 0/ + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION_MANUAL) + cursor = connection(BOLT_PORTS["main"], "main").cursor() + + # 1/ + expected_data = { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 1, 0, "ready"), + } + mg_sleep_and_assert(expected_data, show_replicas_func(cursor, "A")) + + cursor_replica = connection(BOLT_PORTS["replica_1"], "replica").cursor() + execute_and_fetch_all(cursor_replica, "USE DATABASE A;") + actual_data = execute_and_fetch_all(cursor_replica, "MATCH (n) RETURN count(*);") + assert actual_data[0][0] == 1 # one node + actual_data = execute_and_fetch_all(cursor_replica, "MATCH ()-[r]->() RETURN count(*);") + assert actual_data[0][0] == 0 # zero relationships + + +@pytest.mark.parametrize("replica_name", [("replica_1"), ("replica_2")]) +def test_multitenancy_replication_restart_replica_w_fc(connection, replica_name): + # Goal: show that a replica can be recovered with the frequent checker + # 0/ Setup replication + # 1/ MAIN CREATE DATABASE A and B + # 2/ Write on MAIN to A and B + # 3/ Restart replica + # 4/ Validate data on replica + + # 0/ + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION) + main_cursor = connection(BOLT_PORTS["main"], "main").cursor() + + # 1/ + execute_and_fetch_all(main_cursor, "CREATE DATABASE A;") + execute_and_fetch_all(main_cursor, "CREATE DATABASE B;") + + # 2/ + setup_main(main_cursor) + + # 3/ + interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, replica_name) + time.sleep(3) # In order for the frequent check to run + # Check that the FC did invalidate + expected_data = { + "replica_1": { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 0, 0, "invalid"), + ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 7, 0, "ready"), + }, + "replica_2": { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 7, 0, "ready"), + ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 0, 0, "invalid"), + }, + } + assert expected_data[replica_name] == show_replicas_func(main_cursor, "A")() + # Restart + interactive_mg_runner.start(MEMGRAPH_INSTANCES_DESCRIPTION, replica_name) + + # 4/ + expected_data = { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 7, 0, "ready"), + ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 7, 0, "ready"), + } + mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "A")) + + expected_data = { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 3, 0, "ready"), + ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 3, 0, "ready"), + } + mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "B")) + + cursor_replica = connection(BOLT_PORTS[replica_name], "replica").cursor() + + assert get_number_of_nodes_func(cursor_replica, "A")() == 7 + assert get_number_of_edges_func(cursor_replica, "A")() == 3 + assert get_number_of_nodes_func(cursor_replica, "B")() == 2 + assert get_number_of_edges_func(cursor_replica, "B")() == 0 + + +@pytest.mark.parametrize("replica_name", [("replica_1"), ("replica_2")]) +def test_multitenancy_replication_restart_replica_wo_fc(connection, replica_name): + # Goal: show that a replica can be recovered without the frequent checker detecting it being down + # needs replicating over + # 0/ Setup replication + # 1/ MAIN CREATE DATABASE A and B + # 2/ Write on MAIN to A and B + # 3/ Restart replica + # 4/ Validate data on replica + + # 0/ + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION) + main_cursor = connection(BOLT_PORTS["main"], "main").cursor() + + # 1/ + execute_and_fetch_all(main_cursor, "CREATE DATABASE A;") + execute_and_fetch_all(main_cursor, "CREATE DATABASE B;") + + # 2/ + setup_main(main_cursor) + + # 3/ + interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, replica_name) + interactive_mg_runner.start(MEMGRAPH_INSTANCES_DESCRIPTION, replica_name) + + # 4/ + expected_data = { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 7, 0, "ready"), + ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 7, 0, "ready"), + } + mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "A")) + + expected_data = { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 3, 0, "ready"), + ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 3, 0, "ready"), + } + mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "B")) + + cursor_replica = connection(BOLT_PORTS[replica_name], "replica").cursor() + assert get_number_of_nodes_func(cursor_replica, "A")() == 7 + assert get_number_of_edges_func(cursor_replica, "A")() == 3 + assert get_number_of_nodes_func(cursor_replica, "B")() == 2 + assert get_number_of_edges_func(cursor_replica, "B")() == 0 + + +@pytest.mark.parametrize("replica_name", [("replica_1"), ("replica_2")]) +def test_multitenancy_replication_restart_replica_w_fc_w_rec(connection, replica_name): + # Goal: show that a replica recovers data on reconnect + # needs replicating over + # 0/ Setup replication + # 1/ MAIN CREATE DATABASE A and B + # 2/ Write on MAIN to A and B + # 3/ Restart replica + # 4/ Validate data on replica + + # 0/ + # Tmp dir should already be removed, but sometimes its not... + safe_execute(shutil.rmtree, TEMP_DIR) + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION_WITH_RECOVERY) + setup_replication(connection) + main_cursor = connection(BOLT_PORTS["main"], "main").cursor() + + # 1/ + execute_and_fetch_all(main_cursor, "CREATE DATABASE A;") + execute_and_fetch_all(main_cursor, "CREATE DATABASE B;") + + # 2/ + setup_main(main_cursor) + + # 3/ + interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION_WITH_RECOVERY, replica_name) + safe_execute(execute_and_fetch_all, main_cursor, "USE DATABASE A;") + safe_execute(execute_and_fetch_all, main_cursor, "CREATE (:Node{on:'A'});") + safe_execute(execute_and_fetch_all, main_cursor, "USE DATABASE B;") + safe_execute(execute_and_fetch_all, main_cursor, "CREATE (:Node{on:'B'});") + interactive_mg_runner.start(MEMGRAPH_INSTANCES_DESCRIPTION_WITH_RECOVERY, replica_name) + + # 4/ + cursor_replica = connection(BOLT_PORTS[replica_name], "replica").cursor() + + mg_sleep_and_assert(8, get_number_of_nodes_func(cursor_replica, "A")) + mg_sleep_and_assert(3, get_number_of_edges_func(cursor_replica, "A")) + + mg_sleep_and_assert(3, get_number_of_nodes_func(cursor_replica, "B")) + mg_sleep_and_assert(0, get_number_of_edges_func(cursor_replica, "B")) + + +@pytest.mark.parametrize("replica_name", [("replica_1"), ("replica_2")]) +def test_multitenancy_replication_drop_replica(connection, replica_name): + # Goal: show that the cluster can recover if a replica is dropped and registered again + # 0/ Setup replication + # 1/ MAIN CREATE DATABASE A and B + # 2/ Write on MAIN to A and B + # 3/ Drop and add the same replica + # 4/ Validate data on replica + + # 0/ + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION) + main_cursor = connection(BOLT_PORTS["main"], "main").cursor() + + # 1/ + execute_and_fetch_all(main_cursor, "CREATE DATABASE A;") + execute_and_fetch_all(main_cursor, "CREATE DATABASE B;") + + # 2/ + setup_main(main_cursor) + + # 3/ + execute_and_fetch_all(main_cursor, f"DROP REPLICA {replica_name};") + sync = {"replica_1": "SYNC", "replica_2": "ASYNC"} + execute_and_fetch_all( + main_cursor, + f"REGISTER REPLICA {replica_name} {sync[replica_name]} TO '127.0.0.1:{REPLICATION_PORTS[replica_name]}';", + ) + + # 4/ + expected_data = { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 7, 0, "ready"), + ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 7, 0, "ready"), + } + mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "A")) + + expected_data = { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 3, 0, "ready"), + ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 3, 0, "ready"), + } + mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "B")) + + cursor_replica = connection(BOLT_PORTS[replica_name], "replica").cursor() + assert get_number_of_nodes_func(cursor_replica, "A")() == 7 + assert get_number_of_edges_func(cursor_replica, "A")() == 3 + assert get_number_of_nodes_func(cursor_replica, "B")() == 2 + assert get_number_of_edges_func(cursor_replica, "B")() == 0 + + +def test_multitenancy_replication_restart_main(connection): + # Goal: show that the cluster can restore to a correct state if the MAIN restarts + # 0/ Setup replication + # 1/ MAIN CREATE DATABASE A and B + # 2/ Write on MAIN to A and B + # 3/ Restart main and write new data + # 4/ Validate data on replica + + # 0/ + # Tmp dir should already be removed, but sometimes its not... + safe_execute(shutil.rmtree, TEMP_DIR) + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION_WITH_RECOVERY) + setup_replication(connection) + main_cursor = connection(BOLT_PORTS["main"], "main").cursor() + + # 1/ + execute_and_fetch_all(main_cursor, "CREATE DATABASE A;") + execute_and_fetch_all(main_cursor, "CREATE DATABASE B;") + + # 2/ + setup_main(main_cursor) + + # 3/ + interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION_WITH_RECOVERY, "main") + interactive_mg_runner.start(MEMGRAPH_INSTANCES_DESCRIPTION_WITH_RECOVERY, "main") + main_cursor = connection(BOLT_PORTS["main"], "main").cursor() + + execute_and_fetch_all(main_cursor, "USE DATABASE A;") + execute_and_fetch_all(main_cursor, "CREATE (:Node{on:'A'});") + execute_and_fetch_all(main_cursor, "USE DATABASE B;") + execute_and_fetch_all(main_cursor, "CREATE (:Node{on:'B'});") + + # 4/ + cursor_replica = connection(BOLT_PORTS["replica_1"], "replica").cursor() + execute_and_fetch_all(cursor_replica, "USE DATABASE A;") + assert get_number_of_nodes_func(cursor_replica, "A")() == 8 + assert get_number_of_edges_func(cursor_replica, "A")() == 3 + assert get_number_of_nodes_func(cursor_replica, "B")() == 3 + assert get_number_of_edges_func(cursor_replica, "B")() == 0 + + cursor_replica = connection(BOLT_PORTS["replica_2"], "replica").cursor() + execute_and_fetch_all(cursor_replica, "USE DATABASE A;") + assert get_number_of_nodes_func(cursor_replica, "A")() == 8 + assert get_number_of_edges_func(cursor_replica, "A")() == 3 + assert get_number_of_nodes_func(cursor_replica, "B")() == 3 + assert get_number_of_edges_func(cursor_replica, "B")() == 0 + + +def test_automatic_databases_drop_multitenancy_replication(connection): + # Goal: show that drop database can be replicated + # 0/ Setup replication + # 1/ MAIN CREATE DATABASE A + # 2/ Write to MAIN A + # 3/ Validate replication of changes to A have arrived at REPLICA + # 4/ DROP DATABASE A/B + # 5/ Check that the drop replicated + + # 0/ + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION) + main_cursor = connection(BOLT_PORTS["main"], "main").cursor() + + # 1/ + execute_and_fetch_all(main_cursor, "CREATE DATABASE A;") + execute_and_fetch_all(main_cursor, "CREATE DATABASE B;") + + # 2/ + execute_and_fetch_all(main_cursor, "USE DATABASE A;") + execute_and_fetch_all(main_cursor, "CREATE (:Node{on:'A'});") + + # 3/ + expected_data = { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 1, 0, "ready"), + ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 1, 0, "ready"), + } + mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "A")) + + expected_data = { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 0, 0, "ready"), + ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 0, 0, "ready"), + } + mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "B")) + + # 4/ + execute_and_fetch_all(main_cursor, "USE DATABASE memgraph;") + execute_and_fetch_all(main_cursor, "DROP DATABASE A;") + execute_and_fetch_all(main_cursor, "DROP DATABASE B;") + + # 5/ + databases_on_main = show_databases_func(main_cursor)() + + replica_cursor = connection(BOLT_PORTS["replica_1"], "replica").cursor() + mg_sleep_and_assert(databases_on_main, show_databases_func(replica_cursor)) + + replica_cursor = connection(BOLT_PORTS["replica_2"], "replica").cursor() + mg_sleep_and_assert(databases_on_main, show_databases_func(replica_cursor)) + + +@pytest.mark.parametrize("replica_name", [("replica_1"), ("replica_2")]) +def test_drop_multitenancy_replication_restart_replica(connection, replica_name): + # Goal: show that the drop database can be restored + # 0/ Setup replication + # 1/ MAIN CREATE DATABASE A and B + # 2/ Write on MAIN to A and B + # 3/ Restart SYNC replica and drop database + # 4/ Validate data on replica + + # 0/ + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION) + main_cursor = connection(BOLT_PORTS["main"], "main").cursor() + + # 1/ + execute_and_fetch_all(main_cursor, "CREATE DATABASE A;") + execute_and_fetch_all(main_cursor, "CREATE DATABASE B;") + + # 2/ + setup_main(main_cursor) + + # 3/ + interactive_mg_runner.kill(MEMGRAPH_INSTANCES_DESCRIPTION, replica_name) + execute_and_fetch_all(main_cursor, "USE DATABASE memgraph;") + execute_and_fetch_all(main_cursor, "DROP DATABASE B;") + interactive_mg_runner.start(MEMGRAPH_INSTANCES_DESCRIPTION, replica_name) + + # 4/ + databases_on_main = show_databases_func(main_cursor)() + + replica_cursor = connection(BOLT_PORTS["replica_1"], "replica").cursor() + mg_sleep_and_assert(databases_on_main, show_databases_func(replica_cursor)) + + replica_cursor = connection(BOLT_PORTS["replica_2"], "replica").cursor() + mg_sleep_and_assert(databases_on_main, show_databases_func(replica_cursor)) + + +def test_multitenancy_drop_while_replica_using(connection): + # Goal: show that the replica can handle a transaction on a database being dropped (will persist until tx finishes) + # 0/ Setup replication + # 1/ MAIN CREATE DATABASE A + # 2/ Write to MAIN A + # 3/ Validate replication of changes to A have arrived at REPLICA + # 4/ Start A transaction on replica 1, Use A on replica2 + # 5/ Check that the drop replicated + # 6/ Validate that the transaction is still active and working and that the replica2 is not pointing to anything + + # 0/ + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION) + main_cursor = connection(BOLT_PORTS["main"], "main").cursor() + + # 1/ + execute_and_fetch_all(main_cursor, "CREATE DATABASE A;") + + # 2/ + execute_and_fetch_all(main_cursor, "USE DATABASE A;") + execute_and_fetch_all(main_cursor, "CREATE (:Node{on:'A'});") + + # 3/ + expected_data = { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 1, 0, "ready"), + ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 1, 0, "ready"), + } + mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "A")) + + # 4/ + replica1_cursor = connection(BOLT_PORTS["replica_1"], "replica").cursor() + replica2_cursor = connection(BOLT_PORTS["replica_2"], "replica").cursor() + + execute_and_fetch_all(replica1_cursor, "USE DATABASE A;") + execute_and_fetch_all(replica1_cursor, "BEGIN") + execute_and_fetch_all(replica2_cursor, "USE DATABASE A;") + + execute_and_fetch_all(main_cursor, "USE DATABASE memgraph;") + execute_and_fetch_all(main_cursor, "DROP DATABASE A;") + + # 5/ + # TODO Remove this once there is a replica state for the system + execute_and_fetch_all(main_cursor, "CREATE DATABASE B;") + execute_and_fetch_all(main_cursor, "USE DATABASE B;") + + expected_data = { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 0, 0, "ready"), + ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 0, 0, "ready"), + } + mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "B")) + + # 6/ + assert execute_and_fetch_all(replica1_cursor, "MATCH(n) RETURN count(*);")[0][0] == 1 + execute_and_fetch_all(replica1_cursor, "COMMIT") + failed = False + try: + execute_and_fetch_all(replica1_cursor, "MATCH(n) RETURN n;") + except mgclient.DatabaseError: + failed = True + assert failed + + failed = False + try: + execute_and_fetch_all(replica2_cursor, "MATCH(n) RETURN n;") + except mgclient.DatabaseError: + failed = True + assert failed + + +def test_multitenancy_drop_and_recreate_while_replica_using(connection): + # Goal: show that the replica can handle a transaction on a database being dropped and the same name reused + # Original storage should persist in a nameless state until tx is over + # needs replicating over + # 0/ Setup replication + # 1/ MAIN CREATE DATABASE A + # 2/ Write to MAIN A + # 3/ Validate replication of changes to A have arrived at REPLICA + # 4/ Start A transaction on replica 1, Use A on replica2 + # 5/ Check that the drop/create replicated + # 6/ Validate that the transaction is still active and working and that the replica2 is not pointing to anything + + # 0/ + interactive_mg_runner.start_all(MEMGRAPH_INSTANCES_DESCRIPTION) + main_cursor = connection(BOLT_PORTS["main"], "main").cursor() + + # 1/ + execute_and_fetch_all(main_cursor, "CREATE DATABASE A;") + + # 2/ + execute_and_fetch_all(main_cursor, "USE DATABASE A;") + execute_and_fetch_all(main_cursor, "CREATE (:Node{on:'A'});") + + # 3/ + expected_data = { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 1, 0, "ready"), + ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 1, 0, "ready"), + } + mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "A")) + + # 4/ + replica1_cursor = connection(BOLT_PORTS["replica_1"], "replica").cursor() + replica2_cursor = connection(BOLT_PORTS["replica_2"], "replica").cursor() + + execute_and_fetch_all(replica1_cursor, "USE DATABASE A;") + execute_and_fetch_all(replica1_cursor, "BEGIN") + execute_and_fetch_all(replica2_cursor, "USE DATABASE A;") + + execute_and_fetch_all(main_cursor, "USE DATABASE memgraph;") + execute_and_fetch_all(main_cursor, "DROP DATABASE A;") + + # 5/ + execute_and_fetch_all(main_cursor, "CREATE DATABASE A;") + execute_and_fetch_all(main_cursor, "USE DATABASE A;") + + expected_data = { + ("replica_1", f"127.0.0.1:{REPLICATION_PORTS['replica_1']}", "sync", 0, 0, "ready"), + ("replica_2", f"127.0.0.1:{REPLICATION_PORTS['replica_2']}", "async", 0, 0, "ready"), + } + mg_sleep_and_assert(expected_data, show_replicas_func(main_cursor, "A")) + + # 6/ + assert execute_and_fetch_all(replica1_cursor, "MATCH(n) RETURN count(*);")[0][0] == 1 + execute_and_fetch_all(replica1_cursor, "COMMIT") + failed = False + try: + execute_and_fetch_all(replica1_cursor, "MATCH(n) RETURN n;") + except mgclient.DatabaseError: + failed = True + assert failed + + failed = False + try: + execute_and_fetch_all(replica2_cursor, "MATCH(n) RETURN n;") + except mgclient.DatabaseError: + failed = True + assert failed + + +if __name__ == "__main__": + interactive_mg_runner.cleanup_directories_on_exit() + sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/replication_experimental/workloads.yaml b/tests/e2e/replication_experimental/workloads.yaml new file mode 100644 index 000000000..e48515f4f --- /dev/null +++ b/tests/e2e/replication_experimental/workloads.yaml @@ -0,0 +1,4 @@ +workloads: + - name: "Replicate multitenancy" + binary: "tests/e2e/pytest_runner.sh" + args: ["replication_experimental/multitenancy.py"] diff --git a/tests/e2e/run.sh b/tests/e2e/run.sh index 1aba6a517..88b70ae32 100755 --- a/tests/e2e/run.sh +++ b/tests/e2e/run.sh @@ -25,7 +25,7 @@ if [ "$#" -eq 0 ]; then # NOTE: If you want to run all tests under specific folder/section just # replace the dot (root directory below) with the folder name, e.g. # `--workloads-root-directory replication`. - python3 runner.py --workloads-root-directory . + python3 runner.py --workloads-root-directory "$SCRIPT_DIR/../../build" elif [ "$#" -eq 1 ]; then if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then print_help @@ -34,7 +34,7 @@ elif [ "$#" -eq 1 ]; then # NOTE: --workload-name comes from each individual folder/section # workloads.yaml file. E.g. `streams/workloads.yaml` has a list of # `workloads:` and each workload has it's `-name`. - python3 runner.py --workloads-root-directory . --workload-name "$1" + python3 runner.py --workloads-root-directory "$SCRIPT_DIR/../../build" --workload-name "$1" else print_help fi diff --git a/tests/e2e/runner.py b/tests/e2e/runner.py index 949670d43..ae022d4d8 100755 --- a/tests/e2e/runner.py +++ b/tests/e2e/runner.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + # Copyright 2021 Memgraph Ltd. # # Use of this software is governed by the Business Source License diff --git a/tests/e2e/server/CMakeLists.txt b/tests/e2e/server/CMakeLists.txt index a408f4a2e..2e62f2035 100644 --- a/tests/e2e/server/CMakeLists.txt +++ b/tests/e2e/server/CMakeLists.txt @@ -6,3 +6,5 @@ target_link_libraries(memgraph__e2e__server_connection mgclient mg-utils gflags) add_executable(memgraph__e2e__server_ssl_connection server_ssl_connection.cpp) target_link_libraries(memgraph__e2e__server_ssl_connection mgclient mg-utils gflags) + +copy_e2e_files(server workloads.yaml) diff --git a/tests/e2e/set_properties/CMakeLists.txt b/tests/e2e/set_properties/CMakeLists.txt index 10cc03584..66a8039b7 100644 --- a/tests/e2e/set_properties/CMakeLists.txt +++ b/tests/e2e/set_properties/CMakeLists.txt @@ -6,3 +6,5 @@ copy_set_properties_e2e_python_files(common.py) copy_set_properties_e2e_python_files(set_properties.py) add_subdirectory(procedures) + +copy_e2e_files(set_properties workloads.yaml) diff --git a/tests/e2e/show_index_info/CMakeLists.txt b/tests/e2e/show_index_info/CMakeLists.txt index dd9bd28bb..b5d154355 100644 --- a/tests/e2e/show_index_info/CMakeLists.txt +++ b/tests/e2e/show_index_info/CMakeLists.txt @@ -4,3 +4,5 @@ endfunction() copy_show_index_info_e2e_python_files(common.py) copy_show_index_info_e2e_python_files(test_show_index_info.py) + +copy_e2e_files(show_index_info workloads.yaml) diff --git a/tests/e2e/streams/CMakeLists.txt b/tests/e2e/streams/CMakeLists.txt index 3c0ffac98..cbca225f7 100644 --- a/tests/e2e/streams/CMakeLists.txt +++ b/tests/e2e/streams/CMakeLists.txt @@ -11,3 +11,5 @@ copy_streams_e2e_python_files(pulsar_streams_tests.py) add_subdirectory(transformations) copy_e2e_python_files_from_parent_folder(streams ".." mg_utils.py) + +copy_e2e_files(streams workloads.yaml) diff --git a/tests/e2e/streams/common.py b/tests/e2e/streams/common.py index 43b10ba1c..66a045970 100644 --- a/tests/e2e/streams/common.py +++ b/tests/e2e/streams/common.py @@ -9,12 +9,12 @@ # by the Apache License, Version 2.0, included in the file # licenses/APL.txt. +import time +from multiprocessing import Manager, Process, Value + import mgclient import pytest -import time - from mg_utils import mg_sleep_and_assert -from multiprocessing import Manager, Process, Value # These are the indices of the different values in the result of SHOW STREAM # query @@ -103,15 +103,53 @@ def get_stream_info(cursor, stream_name): def get_is_running(cursor, stream_name): stream_info = get_stream_info(cursor, stream_name) - - assert stream_info + assert stream_info is not None return stream_info[IS_RUNNING] -def start_stream(cursor, stream_name): - execute_and_fetch_all(cursor, f"START STREAM {stream_name}") +def create_stream( + cursor, + stream_name, + topics, + transformation, + consumer_group=None, + batch_interval=None, + batch_size=None, + bootstrap_servers=None, + configs=None, + credentials=None, +): + query_str = f"CREATE KAFKA STREAM {stream_name} TOPICS {topics} TRANSFORM {transformation}" + if consumer_group is not None: + query_str += f" CONSUMER_GROUP {consumer_group}" + if batch_interval is not None: + query_str += f" BATCH_INTERVAL {batch_interval}" + if batch_size is not None: + query_str += f" BATCH_SIZE {batch_size}" + if bootstrap_servers is not None: + query_str += f" BOOTSTRAP_SERVERS {bootstrap_servers}" + if configs is not None: + query_str += f" CONFIGS {configs}" + if credentials is not None: + query_str += f" CREDENTIALS {credentials}" + execute_and_fetch_all(cursor, query_str) + +def start_stream(cursor, stream_name, sleep=True): + # Sleep is needed because although is_running returns True, + # the stream cannot accept messages yet + execute_and_fetch_all(cursor, f"START STREAM {stream_name}") assert get_is_running(cursor, stream_name) + if sleep: + time.sleep(5) + + +def start_streams(cursor, stream_names): + # Start every stream but don't sleep after each creation + for stream_name in stream_names: + execute_and_fetch_all(cursor, f"START STREAM {stream_name}") + assert get_is_running(cursor, stream_name) + time.sleep(5) def start_stream_with_limit(cursor, stream_name, batch_limit, timeout=None): @@ -123,13 +161,11 @@ def start_stream_with_limit(cursor, stream_name, batch_limit, timeout=None): def stop_stream(cursor, stream_name): execute_and_fetch_all(cursor, f"STOP STREAM {stream_name}") - assert not get_is_running(cursor, stream_name) def drop_stream(cursor, stream_name): execute_and_fetch_all(cursor, f"DROP STREAM {stream_name}") - assert get_stream_info(cursor, stream_name) is None @@ -227,7 +263,7 @@ def test_start_and_stop_during_check( try: check_stream_proc.start() - time.sleep(0.5) + time.sleep(3) assert timed_wait(lambda: check_counter.value == CHECK_BEFORE_EXECUTE) assert timed_wait(lambda: get_is_running(cursor, "test_stream")) @@ -260,25 +296,22 @@ def test_start_and_stop_during_check( def test_start_checked_stream_after_timeout(connection, stream_creator): cursor = connection.cursor() - execute_and_fetch_all(cursor, stream_creator("test_stream")) + stream_name = "test_start_checked_stream_after_timeout" + execute_and_fetch_all(cursor, stream_creator(stream_name)) - TIMEOUT_IN_MS = 2000 - TIMEOUT_IN_SECONDS = TIMEOUT_IN_MS / 1000 + timeout_in_ms = 2000 def call_check(): - execute_and_fetch_all(connect().cursor(), f"CHECK STREAM test_stream TIMEOUT {TIMEOUT_IN_MS}") + execute_and_fetch_all(connect().cursor(), f"CHECK STREAM {stream_name} TIMEOUT {timeout_in_ms}") check_stream_proc = Process(target=call_check, daemon=True) - start = time.time() check_stream_proc.start() - assert timed_wait(lambda: get_is_running(cursor, "test_stream")) - start_stream(cursor, "test_stream") - end = time.time() + assert timed_wait(lambda: get_is_running(cursor, stream_name)) + start_stream(cursor, stream_name) - assert (end - start) < 1.3 * TIMEOUT_IN_SECONDS, "The START STREAM was blocked too long" - assert get_is_running(cursor, "test_stream") - stop_stream(cursor, "test_stream") + assert get_is_running(cursor, stream_name) + stop_stream(cursor, stream_name) def test_check_stream_same_number_of_queries_than_messages(connection, stream_creator, message_sender): @@ -313,7 +346,6 @@ def test_check_stream_same_number_of_queries_than_messages(connection, stream_cr # # {parameters: {"value": "Parameter: 04"}, query: "Message: 04"}] # # -Batch 3: [{parameters: {"value": "Parameter: 05"}, query: "Message: 05"}, # # {parameters: {"value": "Parameter: 06"}, query: "Message: 06"}] - assert len(test_results.value) == BATCH_LIMIT expected_queries_and_raw_messages_1 = ( @@ -351,7 +383,7 @@ def test_check_stream_different_number_of_queries_than_messages(connection, stre STREAM_NAME = "test_stream" cursor = connection.cursor() execute_and_fetch_all(cursor, stream_creator(STREAM_NAME, BATCH_SIZE)) - time.sleep(2) + time.sleep(3) results = Manager().Namespace() @@ -413,30 +445,32 @@ def test_check_stream_different_number_of_queries_than_messages(connection, stre assert expected_queries_and_raw_messages_3 == results.value[2] -def test_start_stream_with_batch_limit(connection, stream_creator, messages_sender): - STREAM_NAME = "test" +def test_start_stream_with_batch_limit(connection, stream_name, stream_creator, messages_sender): BATCH_LIMIT = 5 + TIMEOUT = 10000 cursor = connection.cursor() - execute_and_fetch_all(cursor, stream_creator(STREAM_NAME)) + execute_and_fetch_all(cursor, stream_creator()) + results = execute_and_fetch_all(connection.cursor(), "SHOW STREAMS") + assert len(results) == 1 - def start_new_stream_with_limit(stream_name, batch_limit): + def start_new_stream_with_limit(): connection = connect() cursor = connection.cursor() - start_stream_with_limit(cursor, stream_name, batch_limit) - - thread_stream_running = Process(target=start_new_stream_with_limit, daemon=True, args=(STREAM_NAME, BATCH_LIMIT)) - thread_stream_running.start() + start_stream_with_limit(cursor, stream_name, BATCH_LIMIT, TIMEOUT) def is_running(): - return get_is_running(cursor, STREAM_NAME) + return get_is_running(cursor, stream_name) + thread_stream_running = Process(target=start_new_stream_with_limit) + thread_stream_running.start() + + execute_and_fetch_all(connection.cursor(), "SHOW STREAMS") assert mg_sleep_and_assert(True, is_running) - messages_sender(BATCH_LIMIT - 1) # We have not sent enough batches to reach the limit. We check that the stream is still correctly running. - assert get_is_running(cursor, STREAM_NAME) + assert get_is_running(cursor, stream_name) # We send a last message to reach the batch_limit messages_sender(1) @@ -549,7 +583,6 @@ def test_check_while_stream_with_batch_limit_running(connection, stream_creator, STREAM_NAME = "test_batch_limit_and_check" BATCH_LIMIT = 1 TIMEOUT = 10000 - TIMEOUT_IN_SECONDS = TIMEOUT / 1000 cursor = connection.cursor() execute_and_fetch_all(cursor, stream_creator(STREAM_NAME)) @@ -625,7 +658,7 @@ def test_check_stream_with_batch_limit_with_invalid_batch_limit(connection, stre cursor = connection.cursor() execute_and_fetch_all(cursor, stream_creator(STREAM_NAME)) - time.sleep(2) + time.sleep(3) # 1/ checking with batch_limit=-10 batch_limit = -10 diff --git a/tests/e2e/streams/conftest.py b/tests/e2e/streams/conftest.py index a26138b53..1bf3544c2 100644 --- a/tests/e2e/streams/conftest.py +++ b/tests/e2e/streams/conftest.py @@ -9,15 +9,14 @@ # by the Apache License, Version 2.0, included in the file # licenses/APL.txt. +import pulsar import pytest +from common import NAME, PULSAR_SERVICE_URL, connect, execute_and_fetch_all from kafka import KafkaProducer from kafka.admin import KafkaAdminClient, NewTopic -import pulsar import requests -from common import NAME, connect, execute_and_fetch_all, PULSAR_SERVICE_URL - # To run these test locally a running Kafka sever is necessery. The test tries # to connect on localhost:9092. @@ -37,29 +36,22 @@ def connection(): def get_topics(num): - return [f'topic_{i}' for i in range(num)] + return [f"topic_{i}" for i in range(num)] @pytest.fixture(scope="function") def kafka_topics(): - admin_client = KafkaAdminClient( - bootstrap_servers="localhost:9092", - client_id="test") + admin_client = KafkaAdminClient(bootstrap_servers="localhost:29092", client_id="test") # The issue arises if we remove default kafka topics, e.g. # "__consumer_offsets" - previous_topics = [ - topic for topic in admin_client.list_topics() if topic != "__consumer_offsets"] + previous_topics = [topic for topic in admin_client.list_topics() if topic != "__consumer_offsets"] if previous_topics: admin_client.delete_topics(topics=previous_topics, timeout_ms=5000) topics = get_topics(3) topics_to_create = [] for topic in topics: - topics_to_create.append( - NewTopic( - name=topic, - num_partitions=1, - replication_factor=1)) + topics_to_create.append(NewTopic(name=topic, num_partitions=1, replication_factor=1)) admin_client.create_topics(new_topics=topics_to_create, timeout_ms=5000) yield topics @@ -68,7 +60,7 @@ def kafka_topics(): @pytest.fixture(scope="function") def kafka_producer(): - yield KafkaProducer(bootstrap_servers="localhost:9092") + yield KafkaProducer(bootstrap_servers=["localhost:29092"], api_version_auto_timeout_ms=10000) @pytest.fixture(scope="function") @@ -80,6 +72,5 @@ def pulsar_client(): def pulsar_topics(): topics = get_topics(3) for topic in topics: - requests.delete( - f'http://127.0.0.1:6652/admin/v2/persistent/public/default/{topic}?force=true') + requests.delete(f"http://localhost:6652/admin/v2/persistent/public/default/{topic}?force=true") yield topics diff --git a/tests/e2e/streams/kafka.yml b/tests/e2e/streams/kafka.yml deleted file mode 100644 index f9d213864..000000000 --- a/tests/e2e/streams/kafka.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: '3.7' -services: - zookeeper: - image: 'bitnami/zookeeper:latest' - ports: - - '2181:2181' - environment: - - ALLOW_ANONYMOUS_LOGIN=yes - kafka: - image: 'bitnami/kafka:latest' - ports: - - '9092:9092' - environment: - - KAFKA_BROKER_ID=1 - - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092 - - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://127.0.0.1:9092 - - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 - - ALLOW_PLAINTEXT_LISTENER=yes - depends_on: - - zookeeper diff --git a/tests/e2e/streams/kafka/docker-compose.yml b/tests/e2e/streams/kafka/docker-compose.yml new file mode 100755 index 000000000..3ab2166af --- /dev/null +++ b/tests/e2e/streams/kafka/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3' +services: + zookeeper: + image: 'bitnami/zookeeper:3.9.1' + ports: + - '2181:2181' + environment: + - ALLOW_ANONYMOUS_LOGIN=yes + kafka: + image: 'bitnami/kafka:3.6.1' + ports: + - '9092:9092' + - '29092:29092' + environment: + - KAFKA_BROKER_ID=1 + - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT + - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,PLAINTEXT_HOST://:29092 + - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092,PLAINTEXT_HOST://localhost:29092 + - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 + - ALLOW_PLAINTEXT_LISTENER=yes + depends_on: + - zookeeper diff --git a/tests/e2e/streams/kafka_streams_tests.py b/tests/e2e/streams/kafka_streams_tests.py index 3c1213019..b988a6c26 100755 --- a/tests/e2e/streams/kafka_streams_tests.py +++ b/tests/e2e/streams/kafka_streams_tests.py @@ -12,31 +12,30 @@ # licenses/APL.txt. import sys -import pytest -import mgclient import time -from mg_utils import mg_sleep_and_assert -from multiprocessing import Process, Value +from multiprocessing import Process + import common +import mgclient +import pytest +from mg_utils import mg_sleep_and_assert TRANSFORMATIONS_TO_CHECK_C = ["c_transformations.empty_transformation"] - TRANSFORMATIONS_TO_CHECK_PY = ["kafka_transform.simple", "kafka_transform.with_parameters"] +KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT = 60 @pytest.mark.parametrize("transformation", TRANSFORMATIONS_TO_CHECK_PY) def test_simple(kafka_producer, kafka_topics, connection, transformation): assert len(kafka_topics) > 0 + stream_name = "test_simple_" + transformation.split(".")[1] + cursor = connection.cursor() - common.execute_and_fetch_all( - cursor, - f"CREATE KAFKA STREAM test TOPICS {','.join(kafka_topics)} TRANSFORM {transformation}", - ) - common.start_stream(cursor, "test") - time.sleep(5) + common.create_stream(cursor, stream_name, ",".join(kafka_topics), transformation) + common.start_stream(cursor, stream_name) for topic in kafka_topics: - kafka_producer.send(topic, common.SIMPLE_MSG).get(timeout=60) + kafka_producer.send(topic, common.SIMPLE_MSG).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT) for topic in kafka_topics: common.kafka_check_vertex_exists_with_topic_and_payload(cursor, topic, common.SIMPLE_MSG) @@ -47,22 +46,15 @@ def test_separate_consumers(kafka_producer, kafka_topics, connection, transforma assert len(kafka_topics) > 0 cursor = connection.cursor() - stream_names = [] - for topic in kafka_topics: - stream_name = "stream_" + topic - stream_names.append(stream_name) - common.execute_and_fetch_all( - cursor, - f"CREATE KAFKA STREAM {stream_name} TOPICS {topic} TRANSFORM {transformation}", - ) + stream_names = ["stream_" + transformation.split(".")[1] + "_" + topic for topic in kafka_topics] - for stream_name in stream_names: - common.start_stream(cursor, stream_name) + for stream_name, topic in zip(stream_names, kafka_topics): + common.create_stream(cursor, stream_name, topic, transformation) - time.sleep(5) + common.start_streams(cursor, stream_names) for topic in kafka_topics: - kafka_producer.send(topic, common.SIMPLE_MSG).get(timeout=60) + kafka_producer.send(topic, common.SIMPLE_MSG).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT) for topic in kafka_topics: common.kafka_check_vertex_exists_with_topic_and_payload(cursor, topic, common.SIMPLE_MSG) @@ -77,23 +69,20 @@ def test_start_from_last_committed_offset(kafka_producer, kafka_topics, connecti # restarting Memgraph during a single workload cannot be done currently. assert len(kafka_topics) > 0 cursor = connection.cursor() - common.execute_and_fetch_all( - cursor, - f"CREATE KAFKA STREAM test TOPICS {kafka_topics[0]} TRANSFORM kafka_transform.simple", - ) - common.start_stream(cursor, "test") - time.sleep(1) - kafka_producer.send(kafka_topics[0], common.SIMPLE_MSG).get(timeout=60) + stream_name = "test_start_from_last_committed_offset" + common.create_stream(cursor, stream_name, kafka_topics[0], "kafka_transform.simple") + common.start_stream(cursor, stream_name) + kafka_producer.send(kafka_topics[0], common.SIMPLE_MSG).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT) common.kafka_check_vertex_exists_with_topic_and_payload(cursor, kafka_topics[0], common.SIMPLE_MSG) - common.stop_stream(cursor, "test") - common.drop_stream(cursor, "test") + common.stop_stream(cursor, stream_name) + common.drop_stream(cursor, stream_name) messages = [b"second message", b"third message"] for message in messages: - kafka_producer.send(kafka_topics[0], message).get(timeout=60) + kafka_producer.send(kafka_topics[0], message).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT) for message in messages: vertices_with_msg = common.execute_and_fetch_all( @@ -103,11 +92,8 @@ def test_start_from_last_committed_offset(kafka_producer, kafka_topics, connecti assert len(vertices_with_msg) == 0 - common.execute_and_fetch_all( - cursor, - f"CREATE KAFKA STREAM test TOPICS {kafka_topics[0]} TRANSFORM kafka_transform.simple", - ) - common.start_stream(cursor, "test") + common.create_stream(cursor, stream_name, kafka_topics[0], "kafka_transform.simple") + common.start_stream(cursor, stream_name) for message in messages: common.kafka_check_vertex_exists_with_topic_and_payload(cursor, kafka_topics[0], message) @@ -118,32 +104,26 @@ def test_check_stream(kafka_producer, kafka_topics, connection, transformation): assert len(kafka_topics) > 0 BATCH_SIZE = 1 INDEX_OF_FIRST_BATCH = 0 - cursor = connection.cursor() - common.execute_and_fetch_all( - cursor, - f"CREATE KAFKA STREAM test TOPICS {kafka_topics[0]} TRANSFORM {transformation} BATCH_SIZE {BATCH_SIZE}", - ) - common.start_stream(cursor, "test") - time.sleep(1) + stream_name = "test_check_stream_" + transformation.split(".")[1] - kafka_producer.send(kafka_topics[0], common.SIMPLE_MSG).get(timeout=60) - common.stop_stream(cursor, "test") + cursor = connection.cursor() + + common.create_stream(cursor, stream_name, kafka_topics[0], transformation, batch_size=BATCH_SIZE) + common.start_stream(cursor, stream_name) + kafka_producer.send(kafka_topics[0], common.SIMPLE_MSG).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT) + common.stop_stream(cursor, stream_name) messages = [b"first message", b"second message", b"third message"] for message in messages: - kafka_producer.send(kafka_topics[0], message).get(timeout=60) + kafka_producer.send(kafka_topics[0], message).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT) def check_check_stream(batch_limit): - assert transformation == "kafka_transform.simple" or transformation == "kafka_transform.with_parameters" - test_results = common.execute_and_fetch_all(cursor, f"CHECK STREAM test BATCH_LIMIT {batch_limit}") + test_results = common.execute_and_fetch_all(cursor, f"CHECK STREAM {stream_name} BATCH_LIMIT {batch_limit}") assert len(test_results) == batch_limit for i in range(batch_limit): message_as_str = messages[i].decode("utf-8") - assert ( - BATCH_SIZE == 1 - ) # If batch size != 1, then the usage of INDEX_OF_FIRST_BATCH must change: the result will have a list of queries (pair<parameters,query>) - + # If batch size != 1, then the usage of INDEX_OF_FIRST_BATCH must change: the result will have a list of queries (pair<parameters,query>) if transformation == "kafka_transform.simple": assert ( f"payload: '{message_as_str}'" @@ -165,41 +145,48 @@ def test_check_stream(kafka_producer, kafka_topics, connection, transformation): check_check_stream(1) check_check_stream(2) check_check_stream(3) - common.start_stream(cursor, "test") + common.start_stream(cursor, stream_name) for message in messages: common.kafka_check_vertex_exists_with_topic_and_payload(cursor, kafka_topics[0], message) -def test_show_streams(kafka_producer, kafka_topics, connection): +def test_show_streams(kafka_topics, connection): assert len(kafka_topics) > 1 cursor = connection.cursor() - common.execute_and_fetch_all( - cursor, - f"CREATE KAFKA STREAM default_values TOPICS {kafka_topics[0]} TRANSFORM kafka_transform.simple BOOTSTRAP_SERVERS 'localhost:9092'", - ) consumer_group = "my_special_consumer_group" BATCH_INTERVAL = 42 BATCH_SIZE = 3 - common.execute_and_fetch_all( + default_values_stream = "default_values" + complex_values_stream = "complex_values" + + common.create_stream( + cursor, default_values_stream, kafka_topics[0], "kafka_transform.simple", bootstrap_servers="'localhost:29092'" + ) + common.create_stream( cursor, - f"CREATE KAFKA STREAM complex_values TOPICS {','.join(kafka_topics)} TRANSFORM kafka_transform.with_parameters CONSUMER_GROUP {consumer_group} BATCH_INTERVAL {BATCH_INTERVAL} BATCH_SIZE {BATCH_SIZE} ", + complex_values_stream, + ",".join(kafka_topics), + "kafka_transform.with_parameters", + consumer_group=consumer_group, + batch_interval=BATCH_INTERVAL, + batch_size=BATCH_SIZE, ) assert len(common.execute_and_fetch_all(cursor, "SHOW STREAMS")) == 2 common.check_stream_info( cursor, - "default_values", - ("default_values", "kafka", 100, 1000, "kafka_transform.simple", None, False), + default_values_stream, + (default_values_stream, "kafka", 100, 1000, "kafka_transform.simple", None, False), ) common.check_stream_info( cursor, - "complex_values", + complex_values_stream, ( - "complex_values", + complex_values_stream, "kafka", BATCH_INTERVAL, BATCH_SIZE, @@ -219,7 +206,7 @@ def test_start_and_stop_during_check(kafka_producer, kafka_topics, connection, o return f"CREATE KAFKA STREAM {stream_name} TOPICS {kafka_topics[0]} TRANSFORM kafka_transform.simple BATCH_SIZE {BATCH_SIZE}" def message_sender(msg): - kafka_producer.send(kafka_topics[0], msg).get(timeout=60) + kafka_producer.send(kafka_topics[0], msg).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT) common.test_start_and_stop_during_check( operation, @@ -235,14 +222,12 @@ def test_check_already_started_stream(kafka_topics, connection): assert len(kafka_topics) > 0 cursor = connection.cursor() - common.execute_and_fetch_all( - cursor, - f"CREATE KAFKA STREAM started_stream TOPICS {kafka_topics[0]} TRANSFORM kafka_transform.simple", - ) - common.start_stream(cursor, "started_stream") + stream_name = "test_check_already_started_stream" + common.create_stream(cursor, stream_name, kafka_topics[0], "kafka_transform.simple") + common.start_stream(cursor, stream_name) with pytest.raises(mgclient.DatabaseError): - common.execute_and_fetch_all(cursor, "CHECK STREAM started_stream") + common.execute_and_fetch_all(cursor, f"CHECK STREAM {stream_name}") def test_start_checked_stream_after_timeout(kafka_topics, connection): @@ -254,19 +239,14 @@ def test_start_checked_stream_after_timeout(kafka_topics, connection): def test_restart_after_error(kafka_producer, kafka_topics, connection): cursor = connection.cursor() - common.execute_and_fetch_all( - cursor, - f"CREATE KAFKA STREAM test_stream TOPICS {kafka_topics[0]} TRANSFORM kafka_transform.query", - ) + stream_name = "test_restart_after_error" + common.create_stream(cursor, stream_name, kafka_topics[0], "kafka_transform.query") + common.start_stream(cursor, stream_name) - common.start_stream(cursor, "test_stream") - time.sleep(1) + kafka_producer.send(kafka_topics[0], common.SIMPLE_MSG).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT) + assert common.timed_wait(lambda: not common.get_is_running(cursor, stream_name)) - kafka_producer.send(kafka_topics[0], common.SIMPLE_MSG).get(timeout=60) - assert common.timed_wait(lambda: not common.get_is_running(cursor, "test_stream")) - - common.start_stream(cursor, "test_stream") - time.sleep(1) + common.start_stream(cursor, stream_name) kafka_producer.send(kafka_topics[0], b"CREATE (n:VERTEX { id : 42 })") assert common.check_one_result_row(cursor, "MATCH (n:VERTEX { id : 42 }) RETURN n") @@ -275,23 +255,21 @@ def test_restart_after_error(kafka_producer, kafka_topics, connection): def test_bootstrap_server(kafka_producer, kafka_topics, connection, transformation): assert len(kafka_topics) > 0 cursor = connection.cursor() - LOCAL = "localhost:9092" - common.execute_and_fetch_all( - cursor, - f"CREATE KAFKA STREAM test TOPICS {','.join(kafka_topics)} TRANSFORM {transformation} BOOTSTRAP_SERVERS '{LOCAL}'", - ) - common.start_stream(cursor, "test") - time.sleep(5) + local = "'localhost:29092'" + stream_name = "test_bootstrap_server_" + transformation.split(".")[1] + + common.create_stream(cursor, stream_name, ",".join(kafka_topics), transformation, bootstrap_servers=local) + common.start_stream(cursor, stream_name) for topic in kafka_topics: - kafka_producer.send(topic, common.SIMPLE_MSG).get(timeout=60) + kafka_producer.send(topic, common.SIMPLE_MSG).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT) for topic in kafka_topics: common.kafka_check_vertex_exists_with_topic_and_payload(cursor, topic, common.SIMPLE_MSG) @pytest.mark.parametrize("transformation", TRANSFORMATIONS_TO_CHECK_PY) -def test_bootstrap_server_empty(kafka_producer, kafka_topics, connection, transformation): +def test_bootstrap_server_empty(kafka_topics, connection, transformation): assert len(kafka_topics) > 0 cursor = connection.cursor() with pytest.raises(mgclient.DatabaseError): @@ -312,7 +290,7 @@ def test_set_offset(kafka_producer, kafka_topics, connection, transformation): messages = [f"{i} message" for i in range(1, 21)] for message in messages: - kafka_producer.send(kafka_topics[0], message.encode()).get(timeout=60) + kafka_producer.send(kafka_topics[0], message.encode()).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT) def consume(expected_msgs): common.start_stream(cursor, "test") @@ -352,7 +330,7 @@ def test_set_offset(kafka_producer, kafka_topics, connection, transformation): res = execute_set_offset_and_consume(-2, []) assert len(res) == 0 last_msg = "Final Message" - kafka_producer.send(kafka_topics[0], last_msg.encode()).get(timeout=60) + kafka_producer.send(kafka_topics[0], last_msg.encode()).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT) res = consume([last_msg]) assert len(res) == 1 assert comparison_check("Final Message", res[0]) @@ -361,21 +339,27 @@ def test_set_offset(kafka_producer, kafka_topics, connection, transformation): def test_info_procedure(kafka_topics, connection): cursor = connection.cursor() - STREAM_NAME = "test_stream" - CONFIGS = {"sasl.username": "michael.scott"} - LOCAL = "localhost:9092" - CREDENTIALS = {"sasl.password": "S3cr3tP4ssw0rd"} - CONSUMER_GROUP = "ConsumerGr" - common.execute_and_fetch_all( + stream_name = "test_stream" + configs = {"sasl.username": "michael.scott"} + local = "localhost:29092" + credentials = {"sasl.password": "S3cr3tP4ssw0rd"} + consumer_group = "ConsumerGr" + + common.create_stream( cursor, - f"CREATE KAFKA STREAM {STREAM_NAME} TOPICS {','.join(kafka_topics)} TRANSFORM kafka_transform.simple CONSUMER_GROUP {CONSUMER_GROUP} BOOTSTRAP_SERVERS '{LOCAL}' CONFIGS {CONFIGS} CREDENTIALS {CREDENTIALS}", + stream_name, + ",".join(kafka_topics), + "kafka_transform.simple", + consumer_group=consumer_group, + bootstrap_servers=f"'{local}'", + configs=configs, + credentials=credentials, ) + stream_info = common.execute_and_fetch_all(cursor, f"CALL mg.kafka_stream_info('{stream_name}') YIELD *") - stream_info = common.execute_and_fetch_all(cursor, f"CALL mg.kafka_stream_info('{STREAM_NAME}') YIELD *") + reducted_credentials = {key: "<REDUCTED>" for key in credentials.keys()} - reducted_credentials = {key: "<REDUCTED>" for key in CREDENTIALS.keys()} - - expected_stream_info = [(LOCAL, CONFIGS, CONSUMER_GROUP, reducted_credentials, kafka_topics)] + expected_stream_info = [(local, configs, consumer_group, reducted_credentials, kafka_topics)] common.validate_info(stream_info, expected_stream_info) @@ -398,7 +382,7 @@ def test_check_stream_same_number_of_queries_than_messages(kafka_producer, kafka return f"CREATE KAFKA STREAM {stream_name} TOPICS {kafka_topics[0]} TRANSFORM {TRANSFORMATION} BATCH_INTERVAL 3000 BATCH_SIZE {batch_size}" def message_sender(msg): - kafka_producer.send(kafka_topics[0], msg).get(timeout=60) + kafka_producer.send(kafka_topics[0], msg).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT) common.test_check_stream_same_number_of_queries_than_messages(connection, stream_creator, message_sender) @@ -409,30 +393,33 @@ def test_check_stream_different_number_of_queries_than_messages(kafka_producer, TRANSFORMATION = "common_transform.check_stream_with_filtering" def stream_creator(stream_name, batch_size): - return f"CREATE KAFKA STREAM {stream_name} TOPICS {kafka_topics[0]} TRANSFORM {TRANSFORMATION} BATCH_INTERVAL 3000 BATCH_SIZE {batch_size}" + return f"CREATE KAFKA STREAM {stream_name} TOPICS {kafka_topics[0]} TRANSFORM {TRANSFORMATION} BATCH_INTERVAL 3000 BATCH_SIZE {batch_size}" def message_sender(msg): - kafka_producer.send(kafka_topics[0], msg).get(timeout=60) + kafka_producer.send(kafka_topics[0], msg).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT) common.test_check_stream_different_number_of_queries_than_messages(connection, stream_creator, message_sender) def test_start_stream_with_batch_limit(kafka_producer, kafka_topics, connection): assert len(kafka_topics) > 0 + STREAM_NAME = "test_start_stream_with_batch_limit" - def stream_creator(stream_name): + def stream_creator(): return ( - f"CREATE KAFKA STREAM {stream_name} TOPICS {kafka_topics[0]} TRANSFORM kafka_transform.simple BATCH_SIZE 1" + f"CREATE KAFKA STREAM {STREAM_NAME} TOPICS {kafka_topics[0]} TRANSFORM kafka_transform.simple BATCH_SIZE 1" ) def messages_sender(nof_messages): - for x in range(nof_messages): - kafka_producer.send(kafka_topics[0], common.SIMPLE_MSG).get(timeout=60) + for _ in range(nof_messages): + kafka_producer.send(kafka_topics[0], common.SIMPLE_MSG).get( + timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT + ) - common.test_start_stream_with_batch_limit(connection, stream_creator, messages_sender) + common.test_start_stream_with_batch_limit(connection, STREAM_NAME, stream_creator, messages_sender) -def test_start_stream_with_batch_limit_timeout(kafka_producer, kafka_topics, connection): +def test_start_stream_with_batch_limit_timeout(kafka_topics, connection): assert len(kafka_topics) > 0 def stream_creator(stream_name): @@ -443,7 +430,7 @@ def test_start_stream_with_batch_limit_timeout(kafka_producer, kafka_topics, con common.test_start_stream_with_batch_limit_timeout(connection, stream_creator) -def test_start_stream_with_batch_limit_reaching_timeout(kafka_producer, kafka_topics, connection): +def test_start_stream_with_batch_limit_reaching_timeout(kafka_topics, connection): assert len(kafka_topics) > 0 def stream_creator(stream_name, batch_size): @@ -461,7 +448,7 @@ def test_start_stream_with_batch_limit_while_check_running(kafka_producer, kafka ) def message_sender(message): - kafka_producer.send(kafka_topics[0], message).get(timeout=6000) + kafka_producer.send(kafka_topics[0], message).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT) def setup_function(start_check_stream, cursor, stream_name, batch_limit, timeout): thread_stream_check = Process(target=start_check_stream, daemon=True, args=(stream_name, batch_limit, timeout)) @@ -488,12 +475,12 @@ def test_check_while_stream_with_batch_limit_running(kafka_producer, kafka_topic ) def message_sender(message): - kafka_producer.send(kafka_topics[0], message).get(timeout=6000) + kafka_producer.send(kafka_topics[0], message).get(timeout=KAFKA_PRODUCER_SENDING_MSG_DEFAULT_TIMEOUT) common.test_check_while_stream_with_batch_limit_running(connection, stream_creator, message_sender) -def test_start_stream_with_batch_limit_with_invalid_batch_limit(kafka_producer, kafka_topics, connection): +def test_start_stream_with_batch_limit_with_invalid_batch_limit(kafka_topics, connection): assert len(kafka_topics) > 0 def stream_creator(stream_name): @@ -504,7 +491,7 @@ def test_start_stream_with_batch_limit_with_invalid_batch_limit(kafka_producer, common.test_start_stream_with_batch_limit_with_invalid_batch_limit(connection, stream_creator) -def test_check_stream_with_batch_limit_with_invalid_batch_limit(kafka_producer, kafka_topics, connection): +def test_check_stream_with_batch_limit_with_invalid_batch_limit(kafka_topics, connection): assert len(kafka_topics) > 0 def stream_creator(stream_name): diff --git a/tests/e2e/streams/pulsar.yml b/tests/e2e/streams/pulsar.yml deleted file mode 100644 index 31241ad66..000000000 --- a/tests/e2e/streams/pulsar.yml +++ /dev/null @@ -1,8 +0,0 @@ -version: '3.7' -services: - pulsar: - image: 'apachepulsar/pulsar:latest' - ports: - - '6652:8080' - - '6650:6650' - entrypoint: ['bin/pulsar', 'standalone'] diff --git a/tests/e2e/streams/pulsar/docker-compose.yml b/tests/e2e/streams/pulsar/docker-compose.yml new file mode 100644 index 000000000..a490fbdb1 --- /dev/null +++ b/tests/e2e/streams/pulsar/docker-compose.yml @@ -0,0 +1,9 @@ +version: "3" + +services: + pulsar: + image: 'apachepulsar/pulsar:2.8.1' + ports: + - '6652:8080' + - '6650:6650' + entrypoint: [ 'bin/pulsar', 'standalone' ] diff --git a/tests/e2e/streams/pulsar_streams_tests.py b/tests/e2e/streams/pulsar_streams_tests.py index f68bf14d6..cf52416cb 100755 --- a/tests/e2e/streams/pulsar_streams_tests.py +++ b/tests/e2e/streams/pulsar_streams_tests.py @@ -12,11 +12,12 @@ # licenses/APL.txt. import sys -import pytest -import mgclient import time from multiprocessing import Process, Value + import common +import mgclient +import pytest TRANSFORMATIONS_TO_CHECK = ["pulsar_transform.simple", "pulsar_transform.with_parameters"] @@ -80,12 +81,6 @@ def test_start_from_latest_messages(pulsar_client, pulsar_topics, connection): # inbetween should be lost. Additionally, we check that consumer continues from the correct message # after stopping and starting again. assert len(pulsar_topics) > 0 - cursor = connection.cursor() - common.execute_and_fetch_all( - cursor, - f"CREATE PULSAR STREAM test TOPICS {pulsar_topics[0]} TRANSFORM pulsar_transform.simple", - ) - common.start_stream(cursor, "test") def assert_message_not_consumed(message): vertices_with_msg = common.execute_and_fetch_all( @@ -95,6 +90,13 @@ def test_start_from_latest_messages(pulsar_client, pulsar_topics, connection): assert len(vertices_with_msg) == 0 + cursor = connection.cursor() + common.execute_and_fetch_all( + cursor, + f"CREATE PULSAR STREAM test TOPICS {pulsar_topics[0]} TRANSFORM pulsar_transform.simple", + ) + common.start_stream(cursor, "test") + producer = pulsar_client.create_producer( common.pulsar_default_namespace_topic(pulsar_topics[0]), send_timeout_millis=60000 ) @@ -131,7 +133,7 @@ def test_start_from_latest_messages(pulsar_client, pulsar_topics, connection): producer.send(message) assert_message_not_consumed(message) - common.start_stream(cursor, "test") + common.start_stream(cursor, "test", sleep=False) assert_message_not_consumed(LOST_MESSAGE) @@ -338,19 +340,20 @@ def test_service_url(pulsar_client, pulsar_topics, connection, transformation): def test_start_stream_with_batch_limit(pulsar_client, pulsar_topics, connection): assert len(pulsar_topics) > 1 + STREAM_NAME = "test_start_stream_with_batch_limit" - def stream_creator(stream_name): - return f"CREATE PULSAR STREAM {stream_name} TOPICS {pulsar_topics[0]} TRANSFORM pulsar_transform.simple BATCH_SIZE 1" + def stream_creator(): + return f"CREATE PULSAR STREAM {STREAM_NAME} TOPICS {pulsar_topics[0]} TRANSFORM pulsar_transform.simple BATCH_SIZE 1" producer = pulsar_client.create_producer( common.pulsar_default_namespace_topic(pulsar_topics[0]), send_timeout_millis=60000 ) def messages_sender(nof_messages): - for x in range(nof_messages): + for _ in range(nof_messages): producer.send(common.SIMPLE_MSG) - common.test_start_stream_with_batch_limit(connection, stream_creator, messages_sender) + common.test_start_stream_with_batch_limit(connection, STREAM_NAME, stream_creator, messages_sender) def test_start_stream_with_batch_limit_timeout(pulsar_client, pulsar_topics, connection): @@ -409,7 +412,7 @@ def test_check_stream_same_number_of_queries_than_messages(pulsar_client, pulsar TRANSFORMATION = "common_transform.check_stream_no_filtering" def stream_creator(stream_name, batch_size): - return f"CREATE PULSAR STREAM {stream_name} TOPICS {pulsar_topics[0]} TRANSFORM {TRANSFORMATION} BATCH_INTERVAL 3000 BATCH_SIZE {batch_size} " + return f"CREATE PULSAR STREAM {stream_name} TOPICS {pulsar_topics[0]} TRANSFORM {TRANSFORMATION} BATCH_INTERVAL 3000 BATCH_SIZE {batch_size} " producer = pulsar_client.create_producer( common.pulsar_default_namespace_topic(pulsar_topics[0]), send_timeout_millis=60000 diff --git a/tests/e2e/temporal_types/CMakeLists.txt b/tests/e2e/temporal_types/CMakeLists.txt index aad9561fe..dac9c2000 100644 --- a/tests/e2e/temporal_types/CMakeLists.txt +++ b/tests/e2e/temporal_types/CMakeLists.txt @@ -4,3 +4,4 @@ find_package(gflags REQUIRED) add_executable(memgraph__e2e__temporal_roundtrip roundtrip.cpp) target_link_libraries(memgraph__e2e__temporal_roundtrip PUBLIC mgclient mg-utils gflags) +copy_e2e_files(temporal_roundtrip workloads.yaml) diff --git a/tests/e2e/transaction_queue/CMakeLists.txt b/tests/e2e/transaction_queue/CMakeLists.txt index 574c46bfd..f2e7db170 100644 --- a/tests/e2e/transaction_queue/CMakeLists.txt +++ b/tests/e2e/transaction_queue/CMakeLists.txt @@ -6,3 +6,5 @@ copy_query_modules_reloading_procedures_e2e_python_files(common.py) copy_query_modules_reloading_procedures_e2e_python_files(test_transaction_queue.py) add_subdirectory(procedures) + +copy_e2e_files(transaction_queue workloads.yaml) diff --git a/tests/e2e/transaction_rollback/CMakeLists.txt b/tests/e2e/transaction_rollback/CMakeLists.txt index a64d3bfeb..4b9fd289f 100644 --- a/tests/e2e/transaction_rollback/CMakeLists.txt +++ b/tests/e2e/transaction_rollback/CMakeLists.txt @@ -7,3 +7,5 @@ transaction_rollback_e2e_python_files(conftest.py) transaction_rollback_e2e_python_files(transaction.py) add_subdirectory(procedures) + +copy_e2e_files(transaction_rollback workloads.yaml) diff --git a/tests/e2e/triggers/CMakeLists.txt b/tests/e2e/triggers/CMakeLists.txt index 7b540d59f..8f5fe7676 100644 --- a/tests/e2e/triggers/CMakeLists.txt +++ b/tests/e2e/triggers/CMakeLists.txt @@ -27,3 +27,5 @@ endfunction() copy_triggers_e2e_python_files(common.py) copy_triggers_e2e_python_files(triggers_properties_false.py) + +copy_e2e_files(triggers workloads.yaml) diff --git a/tests/e2e/write_procedures/CMakeLists.txt b/tests/e2e/write_procedures/CMakeLists.txt index 27a9a73e2..f7dc2d8b3 100644 --- a/tests/e2e/write_procedures/CMakeLists.txt +++ b/tests/e2e/write_procedures/CMakeLists.txt @@ -8,3 +8,5 @@ copy_write_procedures_e2e_python_files(simple_write.py) copy_write_procedures_e2e_python_files(read_subgraph.py) add_subdirectory(procedures) + +copy_e2e_files(write_procedures workloads.yaml) diff --git a/tests/gql_behave/tests/memgraph_V1/features/functions.feature b/tests/gql_behave/tests/memgraph_V1/features/functions.feature index bc9121676..19a7f2332 100644 --- a/tests/gql_behave/tests/memgraph_V1/features/functions.feature +++ b/tests/gql_behave/tests/memgraph_V1/features/functions.feature @@ -60,6 +60,34 @@ Feature: Functions | false | | true | + Scenario: ToBoolean test 03: + Given an empty graph + And having executed + """ + CREATE (:Node {prop: ToBoolean("t")}); + """ + When executing query: + """ + MATCH (n:Node) RETURN n.prop; + """ + Then the result should be: + | n.prop | + | true | + + Scenario: ToBoolean test 03: + Given an empty graph + And having executed + """ + CREATE (:Node {prop: ToBoolean("f")}); + """ + When executing query: + """ + MATCH (n:Node) RETURN n.prop; + """ + Then the result should be: + | n.prop | + | false | + Scenario: ToInteger test 01: Given an empty graph And having executed diff --git a/tests/gql_behave/tests/memgraph_V1/features/list_operations.feature b/tests/gql_behave/tests/memgraph_V1/features/list_operations.feature index bfe6b6225..8c5538d6b 100644 --- a/tests/gql_behave/tests/memgraph_V1/features/list_operations.feature +++ b/tests/gql_behave/tests/memgraph_V1/features/list_operations.feature @@ -279,3 +279,15 @@ Feature: List operators Then the result should be: | o | | (:Node {Status: 'This is the status'}) | + + Scenario: Simple list pattern comprehension + Given graph "graph_keanu" + When executing query: + """ + MATCH (keanu:Person {name: 'Keanu Reeves'}) + RETURN [(keanu)-->(b:Movie) WHERE b.title CONTAINS 'Matrix' | b.released] AS years + """ + Then an error should be raised +# Then the result should be: +# | years | +# | [2021,2003,2003,1999] | diff --git a/tests/gql_behave/tests/memgraph_V1/features/match.feature b/tests/gql_behave/tests/memgraph_V1/features/match.feature index 227ad9ad6..0d0477ad9 100644 --- a/tests/gql_behave/tests/memgraph_V1/features/match.feature +++ b/tests/gql_behave/tests/memgraph_V1/features/match.feature @@ -771,3 +771,17 @@ Feature: Match Then the result should be: | path | | <(:label1 {id: 1})-[:type1 {id: 1}]->(:label2 {id: 2})-[:type1 {id: 2}]->(:label3 {id: 3})> | + + Scenario: Using path indentifier from CREATE in MERGE + Given an empty graph + And having executed: + """ + CREATE p0=()-[:T0]->() MERGE ({k:(size(p0))}); + """ + When executing query: + """ + MATCH (n {k: 1}) RETURN n; + """ + Then the result should be: + | n | + | ({k: 1}) | diff --git a/tests/gql_behave/tests/memgraph_V1/features/parameters.feature b/tests/gql_behave/tests/memgraph_V1/features/parameters.feature index 908507e43..d9b25ff8c 100644 --- a/tests/gql_behave/tests/memgraph_V1/features/parameters.feature +++ b/tests/gql_behave/tests/memgraph_V1/features/parameters.feature @@ -52,7 +52,7 @@ Feature: Parameters | [1, 2, 3] | Scenario: Parameters in match: - Given an empty graph + Given an empty graph And having executed: """ CREATE (a {x : 10}) @@ -66,3 +66,107 @@ Feature: Parameters Then the result should be: | a.x | | 10 | + + Scenario: Label parameters in match: + Given an empty graph + And having executed: + """ + CREATE (a:Label1 {x : 10}) + """ + And parameters are: + | a | 10 | + | label | Label1 | + When executing query: + """ + MATCH (a:$label {x : $a}) RETURN a + """ + Then the result should be: + | a | + | (:Label1{x: 10}) | + + Scenario: Label parameters in create and match + Given an empty graph + And parameters are: + | a | 10 | + | label | Label1 | + When executing query: + """ + CREATE (a:$label {x: $a}) + """ + When executing query: + """ + MATCH (a:$label {x: $a}) RETURN a + """ + Then the result should be: + | a | + | (:Label1{x: 10}) | + + Scenario: Label parameters in merge + Given an empty graph + And parameters are: + | a | 10 | + | label | Label1 | + When executing query: + """ + MERGE (a:$label {x: $a}) RETURN a + """ + Then the result should be: + | a | + | (:Label1{x: 10}) | + + Scenario: Label parameters in set label + Given an empty graph + And having executed: + """ + CREATE (a:Label1 {x : 10}) + """ + And parameters are: + | new_label | Label2 | + When executing query: + """ + MATCH (a:Label1 {x: 10}) SET a:$new_label + """ + When executing query: + """ + MATCH (a:Label1:Label1 {x: 10}) RETURN a + """ + Then the result should be: + | a | + | (:Label1:Label2 {x: 10}) | + + Scenario: Label parameters in remove label + Given an empty graph + And having executed: + """ + CREATE (a:Label1:LabelToRemove {x : 10}) + """ + And parameters are: + | label_to_remove | LabelToRemove | + When executing query: + """ + MATCH (a {x: 10}) REMOVE a:$label_to_remove + """ + When executing query: + """ + MATCH (a {x: 10}) RETURN a + """ + Then the result should be: + | a | + | (:Label1 {x: 10}) | + + Scenario: Parameters for limit in return returnBody + Given an empty graph + And having executed: + """ + FOREACH (id IN range(1, 10) | CREATE (:Node {id: id})) + """ + And parameters are: + | limit | 2 | + When executing query: + """ + MATCH (n) RETURN n LIMIT $limit + """ + Then the result should be: + | n | + | (:Node {id: 1}) | + | (:Node {id: 2}) | diff --git a/tests/gql_behave/tests/memgraph_V1/graphs/graph_keanu.cypher b/tests/gql_behave/tests/memgraph_V1/graphs/graph_keanu.cypher new file mode 100644 index 000000000..a7a72aced --- /dev/null +++ b/tests/gql_behave/tests/memgraph_V1/graphs/graph_keanu.cypher @@ -0,0 +1,16 @@ +CREATE + (keanu:Person {name: 'Keanu Reeves'}), + (johnnyMnemonic:Movie {title: 'Johnny Mnemonic', released: 1995}), + (theMatrixRevolutions:Movie {title: 'The Matrix Revolutions', released: 2003}), + (theMatrixReloaded:Movie {title: 'The Matrix Reloaded', released: 2003}), + (theReplacements:Movie {title: 'The Replacements', released: 2000}), + (theMatrix:Movie {title: 'The Matrix', released: 1999}), + (theDevilsAdvocate:Movie {title: 'The Devils Advocate', released: 1997}), + (theMatrixResurrections:Movie {title: 'The Matrix Resurrections', released: 2021}), + (keanu)-[:ACTED_IN]->(johnnyMnemonic), + (keanu)-[:ACTED_IN]->(theMatrixRevolutions), + (keanu)-[:ACTED_IN]->(theMatrixReloaded), + (keanu)-[:ACTED_IN]->(theReplacements), + (keanu)-[:ACTED_IN]->(theMatrix), + (keanu)-[:ACTED_IN]->(theDevilsAdvocate), + (keanu)-[:ACTED_IN]->(theMatrixResurrections); diff --git a/tests/integration/telemetry/client.cpp b/tests/integration/telemetry/client.cpp index 34e1c2a67..b93b1ada5 100644 --- a/tests/integration/telemetry/client.cpp +++ b/tests/integration/telemetry/client.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -13,6 +13,7 @@ #include "dbms/dbms_handler.hpp" #include "glue/auth_checker.hpp" +#include "glue/auth_global.hpp" #include "glue/auth_handler.hpp" #include "requests/requests.hpp" #include "storage/v2/config.hpp" @@ -32,9 +33,10 @@ int main(int argc, char **argv) { // Memgraph backend std::filesystem::path data_directory{std::filesystem::temp_directory_path() / "MG_telemetry_integration_test"}; - memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> auth_{data_directory / - "auth"}; - memgraph::glue::AuthQueryHandler auth_handler(&auth_, ""); + memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> auth_{ + data_directory / "auth", + memgraph::auth::Auth::Config{std::string{memgraph::glue::kDefaultUserRoleRegex}, "", true}}; + memgraph::glue::AuthQueryHandler auth_handler(&auth_); memgraph::glue::AuthChecker auth_checker(&auth_); memgraph::storage::Config db_config; @@ -44,7 +46,7 @@ int main(int argc, char **argv) { memgraph::dbms::DbmsHandler dbms_handler(db_config #ifdef MG_ENTERPRISE , - &auth_, false, false + &auth_, false #endif ); memgraph::query::InterpreterContext interpreter_context_({}, &dbms_handler, &repl_state, &auth_handler, diff --git a/tests/jepsen/run.sh b/tests/jepsen/run.sh index b366e7846..a1587c8a1 100755 --- a/tests/jepsen/run.sh +++ b/tests/jepsen/run.sh @@ -24,7 +24,7 @@ PRINT_CONTEXT() { HELP_EXIT() { echo "" - echo "HELP: $0 help|cluster-up|cluster-cleanup|cluster-dealloc|mgbuild|test|test-all-individually [args]" + echo "HELP: $0 help|cluster-up|cluster-refresh|cluster-cleanup|cluster-dealloc|mgbuild|test|test-all-individually [args]" echo "" echo " test args --binary MEMGRAPH_BINARY_PATH" echo " --ignore-run-stdout-logs Ignore lein run stdout logs." @@ -184,6 +184,37 @@ PROCESS_RESULTS() { INFO "Result processing (printing and packing) DONE." } +CLUSTER_UP() { + PRINT_CONTEXT + "$script_dir/jepsen/docker/bin/up" --daemon + sleep 10 + # Ensure all SSH connections between Jepsen containers work + for node in $(docker ps --filter name=jepsen* --filter status=running --format "{{.Names}}"); do + if [ "$node" == "jepsen-control" ]; then + continue + fi + node_hostname="${node##jepsen-}" + docker exec jepsen-control bash -c "ssh -oStrictHostKeyChecking=no -t $node_hostname exit" + done +} + +CLUSTER_DEALLOC() { + ps=$(docker ps --filter name=jepsen* --filter status=running -q) + if [[ ! -z ${ps} ]]; then + echo "Killing ${ps}" + docker rm -f ${ps} + imgs=$(docker images "jepsen*" -q) + if [[ ! -z ${imgs} ]]; then + echo "Removing ${imgs}" + docker images "jepsen*" -q | xargs docker image rmi -f + else + echo "No Jepsen images detected!" + fi + else + echo "No Jepsen containers detected!" + fi +} + # Initialize testing context by copying source/binary files. Inside CI, # Memgraph is tested on a single machine cluster based on Docker containers. # Once these tests will be part of the official Jepsen repo, the majority of @@ -196,8 +227,16 @@ case $1 in # the current cluster is broken because it relies on the folder. That can # happen easiliy because the jepsen folder is git ignored. cluster-up) - PRINT_CONTEXT - "$script_dir/jepsen/docker/bin/up" --daemon + CLUSTER_UP + ;; + + cluster-refresh) + CLUSTER_DEALLOC + CLUSTER_UP + ;; + + cluster-dealloc) + CLUSTER_DEALLOC ;; cluster-cleanup) @@ -212,23 +251,6 @@ case $1 in done ;; - cluster-dealloc) - ps=$(docker ps --filter name=jepsen* --filter status=running -q) - if [[ ! -z ${ps} ]]; then - echo "Killing ${ps}" - docker rm -f ${ps} - imgs=$(docker images "jepsen*" -q) - if [[ ! -z ${imgs} ]]; then - echo "Removing ${imgs}" - docker images "jepsen*" -q | xargs docker image rmi -f - else - echo "No Jepsen images detected!" - fi - else - echo "No Jepsen containers detected!" - fi - ;; - mgbuild) PRINT_CONTEXT echo "" diff --git a/tests/manual/expression_pretty_printer.cpp b/tests/manual/expression_pretty_printer.cpp index e2d58b350..20757b195 100644 --- a/tests/manual/expression_pretty_printer.cpp +++ b/tests/manual/expression_pretty_printer.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2023 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -17,6 +17,7 @@ #include "query/frontend/ast/cypher_main_visitor.hpp" #include "query/frontend/ast/pretty_print.hpp" #include "query/frontend/opencypher/parser.hpp" +#include "query/parameters.hpp" std::string AssembleQueryString(const std::string &expression_string) { return "return " + expression_string + " as expr"; @@ -24,8 +25,9 @@ std::string AssembleQueryString(const std::string &expression_string) { memgraph::query::Query *ParseQuery(const std::string &query_string, memgraph::query::AstStorage *ast_storage) { memgraph::query::frontend::ParsingContext context; + memgraph::query::Parameters parameters; memgraph::query::frontend::opencypher::Parser parser(query_string); - memgraph::query::frontend::CypherMainVisitor visitor(context, ast_storage); + memgraph::query::frontend::CypherMainVisitor visitor(context, ast_storage, ¶meters); visitor.visit(parser.tree()); return visitor.query(); diff --git a/tests/manual/interactive_planning.cpp b/tests/manual/interactive_planning.cpp index d5da0ba2b..f550b9724 100644 --- a/tests/manual/interactive_planning.cpp +++ b/tests/manual/interactive_planning.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -434,11 +434,12 @@ void ExaminePlans(memgraph::query::DbAccessor *dba, const memgraph::query::Symbo memgraph::query::Query *MakeAst(const std::string &query, memgraph::query::AstStorage *storage) { memgraph::query::frontend::ParsingContext parsing_context; + memgraph::query::Parameters parameters; parsing_context.is_query_cached = false; // query -> AST auto parser = std::make_unique<memgraph::query::frontend::opencypher::Parser>(query); // AST -> high level tree - memgraph::query::frontend::CypherMainVisitor visitor(parsing_context, storage); + memgraph::query::frontend::CypherMainVisitor visitor(parsing_context, storage, ¶meters); visitor.visit(parser->tree()); return visitor.query(); } @@ -462,7 +463,7 @@ auto MakeLogicalPlans(memgraph::query::CypherQuery *query, memgraph::query::AstS memgraph::query::AstStorage ast_copy; auto unoptimized_plan = plan->Clone(&ast_copy); auto rewritten_plan = post_process.Rewrite(std::move(plan), &ctx); - double cost = post_process.EstimatePlanCost(rewritten_plan, dba, symbol_table); + double cost = post_process.EstimatePlanCost(rewritten_plan, dba, symbol_table).cost; interactive_plans.push_back( InteractivePlan{std::move(unoptimized_plan), std::move(ast_copy), std::move(rewritten_plan), cost}); } diff --git a/tests/manual/query_planner.cpp b/tests/manual/query_planner.cpp index 700b70a7a..8f2c107bc 100644 --- a/tests/manual/query_planner.cpp +++ b/tests/manual/query_planner.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -14,14 +14,14 @@ #include <gflags/gflags.h> #include "storage/v2/inmemory/storage.hpp" - +using memgraph::replication_coordination_glue::ReplicationRole; DECLARE_int32(min_log_level); int main(int argc, char *argv[]) { gflags::ParseCommandLineFlags(&argc, &argv, true); spdlog::set_level(spdlog::level::err); std::unique_ptr<memgraph::storage::Storage> db(new memgraph::storage::InMemoryStorage()); - auto storage_dba = db->Access(); + auto storage_dba = db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); RunInteractivePlanning(&dba); return 0; diff --git a/tests/property_based/random_graph.cpp b/tests/property_based/random_graph.cpp index 1f12df2a1..097c2dc0e 100644 --- a/tests/property_based/random_graph.cpp +++ b/tests/property_based/random_graph.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -23,7 +23,7 @@ #include "storage/v2/inmemory/storage.hpp" #include "storage/v2/storage.hpp" #include "storage/v2/vertex_accessor.hpp" - +using memgraph::replication_coordination_glue::ReplicationRole; /** * It is possible to run test with custom seed with: * RC_PARAMS="seed=1" ./random_graph @@ -40,7 +40,7 @@ RC_GTEST_PROP(RandomGraph, RandomGraph, (std::vector<std::string> vertex_labels, std::unordered_map<memgraph::storage::VertexAccessor, std::string> vertex_label_map; std::unordered_map<memgraph::storage::EdgeAccessor, std::string> edge_type_map; - auto dba = db->Access(); + auto dba = db->Access(ReplicationRole::MAIN); for (auto label : vertex_labels) { auto vertex_accessor = dba->CreateVertex(); diff --git a/tests/stress/memory_limit.py b/tests/stress/memory_limit.py index 2b2084484..dd8225584 100644 --- a/tests/stress/memory_limit.py +++ b/tests/stress/memory_limit.py @@ -173,39 +173,49 @@ def run_writer(repetition_count: int, sleep_sec: float, worker_id: int) -> int: """ This writer creates lot of nodes on each write. Also it checks that query failed if memory limit is tried to be broken + + Return: + True if write suceeded + False otherwise """ session = SessionCache.argument_session(args) - def create() -> bool: + def try_create() -> bool: """ - Returns True if done, False if needs to continue + Function tries to create until memory limit is reached + Return: + True if it can continue creating (OOM not reached) + False otherwise """ - memory_tracker_data_before_start = get_tracker_data(session) - should_fail = memory_tracker_data_before_start >= 2048 - failed = False + should_continue = True try: try_execute( session, f"FOREACH (i in range(1,10000) | CREATE (:Node {{prop:'big string or something like that'}}))", ) except Exception as ex: - failed = True output = str(ex) - log.info("Exception in create", output) - assert "Memory limit exceeded!" in output + memory_over_2048_mb = False + memory_tracker_data_after_start = get_tracker_data(session) + if memory_tracker_data_after_start: + memory_over_2048_mb = memory_tracker_data_after_start >= 2048 + log.info( + "Exception in create, exception output:", + output, + f"Worker {worker_id} started iteration {curr_repetition}, memory over 2048MB: {memory_over_2048_mb}", + ) + has_oom_happend = "Memory limit exceeded!" in output and memory_over_2048_mb + should_continue = not has_oom_happend - if should_fail: - assert failed, "Query should have failed" - return False - return True + return should_continue curr_repetition = 0 while curr_repetition < repetition_count: log.info(f"Worker {worker_id} started iteration {curr_repetition}") - should_continue = create() + should_continue = try_create() if not should_continue: return True @@ -214,6 +224,7 @@ def run_writer(repetition_count: int, sleep_sec: float, worker_id: int) -> int: log.info(f"Worker {worker_id} created chain in iteration {curr_repetition}") curr_repetition += 1 + return False def execute_function(worker: Worker) -> Worker: diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 956cba781..6f7b3bbef 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -350,7 +350,7 @@ add_unit_test(storage_v2_wal_file.cpp) target_link_libraries(${test_prefix}storage_v2_wal_file mg-storage-v2 storage_test_utils fmt) add_unit_test(storage_v2_replication.cpp) -target_link_libraries(${test_prefix}storage_v2_replication mg-storage-v2 mg-dbms fmt) +target_link_libraries(${test_prefix}storage_v2_replication mg-storage-v2 mg-dbms fmt mg-repl_coord_glue) add_unit_test(storage_v2_isolation_level.cpp) target_link_libraries(${test_prefix}storage_v2_isolation_level mg-storage-v2) @@ -368,7 +368,7 @@ add_unit_test(storage_v2_storage_mode.cpp) target_link_libraries(${test_prefix}storage_v2_storage_mode mg-storage-v2 storage_test_utils mg-query mg-glue) add_unit_test(replication_persistence_helper.cpp) -target_link_libraries(${test_prefix}replication_persistence_helper mg-storage-v2) +target_link_libraries(${test_prefix}replication_persistence_helper mg-storage-v2 mg-repl_coord_glue) add_unit_test(auth_checker.cpp) target_link_libraries(${test_prefix}auth_checker mg-glue mg-auth) @@ -389,7 +389,7 @@ endif() # Test mg-slk if(MG_ENTERPRISE) add_unit_test(slk_advanced.cpp) - target_link_libraries(${test_prefix}slk_advanced mg-storage-v2) + target_link_libraries(${test_prefix}slk_advanced mg-storage-v2 mg-replication mg-coordination mg-repl_coord_glue) endif() add_unit_test(slk_core.cpp) @@ -415,6 +415,9 @@ if(MG_ENTERPRISE) add_unit_test_with_custom_main(dbms_handler.cpp) target_link_libraries(${test_prefix}dbms_handler mg-query mg-auth mg-glue mg-dbms) + + add_unit_test(multi_tenancy.cpp) + target_link_libraries(${test_prefix}multi_tenancy mg-query mg-auth mg-glue mg-dbms) else() add_unit_test_with_custom_main(dbms_handler_community.cpp) target_link_libraries(${test_prefix}dbms_handler_community mg-query mg-auth mg-glue mg-dbms) diff --git a/tests/unit/auth.cpp b/tests/unit/auth.cpp index 6dbe20914..bc2947a12 100644 --- a/tests/unit/auth.cpp +++ b/tests/unit/auth.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -19,6 +19,7 @@ #include "auth/auth.hpp" #include "auth/crypto.hpp" #include "auth/models.hpp" +#include "glue/auth_global.hpp" #include "license/license.hpp" #include "utils/cast.hpp" #include "utils/file.hpp" @@ -26,90 +27,70 @@ using namespace memgraph::auth; namespace fs = std::filesystem; -DECLARE_bool(auth_password_permit_null); -DECLARE_string(auth_password_strength_regex); DECLARE_string(password_encryption_algorithm); class AuthWithStorage : public ::testing::Test { protected: void SetUp() override { memgraph::utils::EnsureDir(test_folder_); - FLAGS_auth_password_permit_null = true; - FLAGS_auth_password_strength_regex = ".+"; - memgraph::license::global_license_checker.EnableTesting(); + auth.emplace(test_folder_ / ("unit_auth_test_" + std::to_string(static_cast<int>(getpid()))), auth_config); } void TearDown() override { fs::remove_all(test_folder_); } fs::path test_folder_{fs::temp_directory_path() / "MG_tests_unit_auth"}; - - Auth auth{test_folder_ / ("unit_auth_test_" + std::to_string(static_cast<int>(getpid())))}; + Auth::Config auth_config{}; + std::optional<Auth> auth{}; }; TEST_F(AuthWithStorage, AddRole) { - ASSERT_TRUE(auth.AddRole("admin")); - ASSERT_TRUE(auth.AddRole("user")); - ASSERT_FALSE(auth.AddRole("admin")); + ASSERT_TRUE(auth->AddRole("admin")); + ASSERT_TRUE(auth->AddRole("user")); + ASSERT_FALSE(auth->AddRole("admin")); } TEST_F(AuthWithStorage, RemoveRole) { - ASSERT_TRUE(auth.AddRole("admin")); - ASSERT_TRUE(auth.RemoveRole("admin")); - class AuthWithStorage : public ::testing::Test { - protected: - void SetUp() override { - memgraph::utils::EnsureDir(test_folder_); - FLAGS_auth_password_permit_null = true; - FLAGS_auth_password_strength_regex = ".+"; - - memgraph::license::global_license_checker.EnableTesting(); - } - - void TearDown() override { fs::remove_all(test_folder_); } - - fs::path test_folder_{fs::temp_directory_path() / "MG_tests_unit_auth"}; - - Auth auth{test_folder_ / ("unit_auth_test_" + std::to_string(static_cast<int>(getpid())))}; - }; - ASSERT_FALSE(auth.HasUsers()); - ASSERT_FALSE(auth.RemoveUser("test2")); - ASSERT_FALSE(auth.RemoveUser("test")); - ASSERT_FALSE(auth.HasUsers()); + ASSERT_TRUE(auth->AddRole("admin")); + ASSERT_TRUE(auth->RemoveRole("admin")); + ASSERT_FALSE(auth->HasUsers()); + ASSERT_FALSE(auth->RemoveUser("test2")); + ASSERT_FALSE(auth->RemoveUser("test")); + ASSERT_FALSE(auth->HasUsers()); } TEST_F(AuthWithStorage, Authenticate) { - ASSERT_FALSE(auth.HasUsers()); + ASSERT_FALSE(auth->HasUsers()); - auto user = auth.AddUser("test"); + auto user = auth->AddUser("test"); ASSERT_NE(user, std::nullopt); - ASSERT_TRUE(auth.HasUsers()); + ASSERT_TRUE(auth->HasUsers()); - ASSERT_TRUE(auth.Authenticate("test", "123")); + ASSERT_TRUE(auth->Authenticate("test", "123")); user->UpdatePassword("123"); - auth.SaveUser(*user); + auth->SaveUser(*user); - ASSERT_NE(auth.Authenticate("test", "123"), std::nullopt); + ASSERT_NE(auth->Authenticate("test", "123"), std::nullopt); - ASSERT_EQ(auth.Authenticate("test", "456"), std::nullopt); - ASSERT_NE(auth.Authenticate("test", "123"), std::nullopt); + ASSERT_EQ(auth->Authenticate("test", "456"), std::nullopt); + ASSERT_NE(auth->Authenticate("test", "123"), std::nullopt); user->UpdatePassword(); - auth.SaveUser(*user); + auth->SaveUser(*user); - ASSERT_NE(auth.Authenticate("test", "123"), std::nullopt); - ASSERT_NE(auth.Authenticate("test", "456"), std::nullopt); + ASSERT_NE(auth->Authenticate("test", "123"), std::nullopt); + ASSERT_NE(auth->Authenticate("test", "456"), std::nullopt); - ASSERT_EQ(auth.Authenticate("nonexistant", "123"), std::nullopt); + ASSERT_EQ(auth->Authenticate("nonexistant", "123"), std::nullopt); } TEST_F(AuthWithStorage, UserRolePermissions) { - ASSERT_FALSE(auth.HasUsers()); - ASSERT_TRUE(auth.AddUser("test")); - ASSERT_TRUE(auth.HasUsers()); + ASSERT_FALSE(auth->HasUsers()); + ASSERT_TRUE(auth->AddUser("test")); + ASSERT_TRUE(auth->HasUsers()); - auto user = auth.GetUser("test"); + auto user = auth->GetUser("test"); ASSERT_NE(user, std::nullopt); // Test initial user permissions. @@ -130,8 +111,8 @@ TEST_F(AuthWithStorage, UserRolePermissions) { ASSERT_EQ(user->permissions(), user->GetPermissions()); // Create role. - ASSERT_TRUE(auth.AddRole("admin")); - auto role = auth.GetRole("admin"); + ASSERT_TRUE(auth->AddRole("admin")); + auto role = auth->GetRole("admin"); ASSERT_NE(role, std::nullopt); // Assign permissions to role and role to user. @@ -163,11 +144,11 @@ TEST_F(AuthWithStorage, UserRolePermissions) { #ifdef MG_ENTERPRISE TEST_F(AuthWithStorage, UserRoleFineGrainedAccessHandler) { - ASSERT_FALSE(auth.HasUsers()); - ASSERT_TRUE(auth.AddUser("test")); - ASSERT_TRUE(auth.HasUsers()); + ASSERT_FALSE(auth->HasUsers()); + ASSERT_TRUE(auth->AddUser("test")); + ASSERT_TRUE(auth->HasUsers()); - auto user = auth.GetUser("test"); + auto user = auth->GetUser("test"); ASSERT_NE(user, std::nullopt); // Test initial user fine grained access permissions. @@ -204,8 +185,8 @@ TEST_F(AuthWithStorage, UserRoleFineGrainedAccessHandler) { user->GetFineGrainedAccessEdgeTypePermissions()); // Create role. - ASSERT_TRUE(auth.AddRole("admin")); - auto role = auth.GetRole("admin"); + ASSERT_TRUE(auth->AddRole("admin")); + auto role = auth->GetRole("admin"); ASSERT_NE(role, std::nullopt); // Grant label and edge type to role and role to user. @@ -236,44 +217,44 @@ TEST_F(AuthWithStorage, UserRoleFineGrainedAccessHandler) { TEST_F(AuthWithStorage, RoleManipulations) { { - auto user1 = auth.AddUser("user1"); + auto user1 = auth->AddUser("user1"); ASSERT_TRUE(user1); - auto role1 = auth.AddRole("role1"); + auto role1 = auth->AddRole("role1"); ASSERT_TRUE(role1); user1->SetRole(*role1); - auth.SaveUser(*user1); + auth->SaveUser(*user1); - auto user2 = auth.AddUser("user2"); + auto user2 = auth->AddUser("user2"); ASSERT_TRUE(user2); - auto role2 = auth.AddRole("role2"); + auto role2 = auth->AddRole("role2"); ASSERT_TRUE(role2); user2->SetRole(*role2); - auth.SaveUser(*user2); + auth->SaveUser(*user2); } { - auto user1 = auth.GetUser("user1"); + auto user1 = auth->GetUser("user1"); ASSERT_TRUE(user1); const auto *role1 = user1->role(); ASSERT_NE(role1, nullptr); ASSERT_EQ(role1->rolename(), "role1"); - auto user2 = auth.GetUser("user2"); + auto user2 = auth->GetUser("user2"); ASSERT_TRUE(user2); const auto *role2 = user2->role(); ASSERT_NE(role2, nullptr); ASSERT_EQ(role2->rolename(), "role2"); } - ASSERT_TRUE(auth.RemoveRole("role1")); + ASSERT_TRUE(auth->RemoveRole("role1")); { - auto user1 = auth.GetUser("user1"); + auto user1 = auth->GetUser("user1"); ASSERT_TRUE(user1); const auto *role = user1->role(); ASSERT_EQ(role, nullptr); - auto user2 = auth.GetUser("user2"); + auto user2 = auth->GetUser("user2"); ASSERT_TRUE(user2); const auto *role2 = user2->role(); ASSERT_NE(role2, nullptr); @@ -281,17 +262,17 @@ TEST_F(AuthWithStorage, RoleManipulations) { } { - auto role1 = auth.AddRole("role1"); + auto role1 = auth->AddRole("role1"); ASSERT_TRUE(role1); } { - auto user1 = auth.GetUser("user1"); + auto user1 = auth->GetUser("user1"); ASSERT_TRUE(user1); const auto *role1 = user1->role(); ASSERT_EQ(role1, nullptr); - auto user2 = auth.GetUser("user2"); + auto user2 = auth->GetUser("user2"); ASSERT_TRUE(user2); const auto *role2 = user2->role(); ASSERT_NE(role2, nullptr); @@ -299,7 +280,7 @@ TEST_F(AuthWithStorage, RoleManipulations) { } { - auto users = auth.AllUsers(); + auto users = auth->AllUsers(); std::sort(users.begin(), users.end(), [](const User &a, const User &b) { return a.username() < b.username(); }); ASSERT_EQ(users.size(), 2); ASSERT_EQ(users[0].username(), "user1"); @@ -307,7 +288,7 @@ TEST_F(AuthWithStorage, RoleManipulations) { } { - auto roles = auth.AllRoles(); + auto roles = auth->AllRoles(); std::sort(roles.begin(), roles.end(), [](const Role &a, const Role &b) { return a.rolename() < b.rolename(); }); ASSERT_EQ(roles.size(), 2); ASSERT_EQ(roles[0].rolename(), "role1"); @@ -315,7 +296,7 @@ TEST_F(AuthWithStorage, RoleManipulations) { } { - auto users = auth.AllUsersForRole("role2"); + auto users = auth->AllUsersForRole("role2"); ASSERT_EQ(users.size(), 1); ASSERT_EQ(users[0].username(), "user2"); } @@ -323,16 +304,16 @@ TEST_F(AuthWithStorage, RoleManipulations) { TEST_F(AuthWithStorage, UserRoleLinkUnlink) { { - auto user = auth.AddUser("user"); + auto user = auth->AddUser("user"); ASSERT_TRUE(user); - auto role = auth.AddRole("role"); + auto role = auth->AddRole("role"); ASSERT_TRUE(role); user->SetRole(*role); - auth.SaveUser(*user); + auth->SaveUser(*user); } { - auto user = auth.GetUser("user"); + auto user = auth->GetUser("user"); ASSERT_TRUE(user); const auto *role = user->role(); ASSERT_NE(role, nullptr); @@ -340,14 +321,14 @@ TEST_F(AuthWithStorage, UserRoleLinkUnlink) { } { - auto user = auth.GetUser("user"); + auto user = auth->GetUser("user"); ASSERT_TRUE(user); user->ClearRole(); - auth.SaveUser(*user); + auth->SaveUser(*user); } { - auto user = auth.GetUser("user"); + auto user = auth->GetUser("user"); ASSERT_TRUE(user); ASSERT_EQ(user->role(), nullptr); } @@ -355,19 +336,19 @@ TEST_F(AuthWithStorage, UserRoleLinkUnlink) { TEST_F(AuthWithStorage, UserPasswordCreation) { { - auto user = auth.AddUser("test"); + auto user = auth->AddUser("test"); ASSERT_TRUE(user); - ASSERT_TRUE(auth.Authenticate("test", "123")); - ASSERT_TRUE(auth.Authenticate("test", "456")); - ASSERT_TRUE(auth.RemoveUser(user->username())); + ASSERT_TRUE(auth->Authenticate("test", "123")); + ASSERT_TRUE(auth->Authenticate("test", "456")); + ASSERT_TRUE(auth->RemoveUser(user->username())); } { - auto user = auth.AddUser("test", "123"); + auto user = auth->AddUser("test", "123"); ASSERT_TRUE(user); - ASSERT_TRUE(auth.Authenticate("test", "123")); - ASSERT_FALSE(auth.Authenticate("test", "456")); - ASSERT_TRUE(auth.RemoveUser(user->username())); + ASSERT_TRUE(auth->Authenticate("test", "123")); + ASSERT_FALSE(auth->Authenticate("test", "456")); + ASSERT_TRUE(auth->RemoveUser(user->username())); } } @@ -382,36 +363,53 @@ TEST_F(AuthWithStorage, PasswordStrength) { const std::string kAlmostStrongPassword = "ThisPasswordMeetsAllButOneCriterion1234"; const std::string kStrongPassword = "ThisIsAVeryStrongPassword123$"; - auto user = auth.AddUser("user"); - ASSERT_TRUE(user); + { + auth.reset(); + auth.emplace(test_folder_ / ("unit_auth_test_" + std::to_string(static_cast<int>(getpid()))), + Auth::Config{std::string{memgraph::glue::kDefaultUserRoleRegex}, kWeakRegex, true}); + auto user = auth->AddUser("user1"); + ASSERT_TRUE(user); + ASSERT_NO_THROW(auth->UpdatePassword(*user, std::nullopt)); + ASSERT_NO_THROW(auth->UpdatePassword(*user, kWeakPassword)); + ASSERT_NO_THROW(auth->UpdatePassword(*user, kAlmostStrongPassword)); + ASSERT_NO_THROW(auth->UpdatePassword(*user, kStrongPassword)); + } - FLAGS_auth_password_permit_null = true; - FLAGS_auth_password_strength_regex = kWeakRegex; - ASSERT_NO_THROW(user->UpdatePassword()); - ASSERT_NO_THROW(user->UpdatePassword(kWeakPassword)); - ASSERT_NO_THROW(user->UpdatePassword(kAlmostStrongPassword)); - ASSERT_NO_THROW(user->UpdatePassword(kStrongPassword)); + { + auth.reset(); + auth.emplace(test_folder_ / ("unit_auth_test_" + std::to_string(static_cast<int>(getpid()))), + Auth::Config{std::string{memgraph::glue::kDefaultUserRoleRegex}, kWeakRegex, false}); + ASSERT_THROW(auth->AddUser("user2", std::nullopt), AuthException); + auto user = auth->AddUser("user2", kWeakPassword); + ASSERT_TRUE(user); + ASSERT_NO_THROW(auth->UpdatePassword(*user, kWeakPassword)); + ASSERT_NO_THROW(auth->UpdatePassword(*user, kAlmostStrongPassword)); + ASSERT_NO_THROW(auth->UpdatePassword(*user, kStrongPassword)); + } - FLAGS_auth_password_permit_null = false; - FLAGS_auth_password_strength_regex = kWeakRegex; - ASSERT_THROW(user->UpdatePassword(), AuthException); - ASSERT_NO_THROW(user->UpdatePassword(kWeakPassword)); - ASSERT_NO_THROW(user->UpdatePassword(kAlmostStrongPassword)); - ASSERT_NO_THROW(user->UpdatePassword(kStrongPassword)); + { + auth.reset(); + auth.emplace(test_folder_ / ("unit_auth_test_" + std::to_string(static_cast<int>(getpid()))), + Auth::Config{std::string{memgraph::glue::kDefaultUserRoleRegex}, kStrongRegex, true}); + auto user = auth->AddUser("user3"); + ASSERT_TRUE(user); + ASSERT_NO_THROW(auth->UpdatePassword(*user, std::nullopt)); + ASSERT_THROW(auth->UpdatePassword(*user, kWeakPassword), AuthException); + ASSERT_THROW(auth->UpdatePassword(*user, kAlmostStrongPassword), AuthException); + ASSERT_NO_THROW(auth->UpdatePassword(*user, kStrongPassword)); + } - FLAGS_auth_password_permit_null = true; - FLAGS_auth_password_strength_regex = kStrongRegex; - ASSERT_NO_THROW(user->UpdatePassword()); - ASSERT_THROW(user->UpdatePassword(kWeakPassword), AuthException); - ASSERT_THROW(user->UpdatePassword(kAlmostStrongPassword), AuthException); - ASSERT_NO_THROW(user->UpdatePassword(kStrongPassword)); - - FLAGS_auth_password_permit_null = false; - FLAGS_auth_password_strength_regex = kStrongRegex; - ASSERT_THROW(user->UpdatePassword(), AuthException); - ASSERT_THROW(user->UpdatePassword(kWeakPassword), AuthException); - ASSERT_THROW(user->UpdatePassword(kAlmostStrongPassword), AuthException); - ASSERT_NO_THROW(user->UpdatePassword(kStrongPassword)); + { + auth.reset(); + auth.emplace(test_folder_ / ("unit_auth_test_" + std::to_string(static_cast<int>(getpid()))), + Auth::Config{std::string{memgraph::glue::kDefaultUserRoleRegex}, kStrongRegex, false}); + ASSERT_THROW(auth->AddUser("user4", std::nullopt);, AuthException); + ASSERT_THROW(auth->AddUser("user4", kWeakPassword);, AuthException); + ASSERT_THROW(auth->AddUser("user4", kAlmostStrongPassword);, AuthException); + auto user = auth->AddUser("user4", kStrongPassword); + ASSERT_TRUE(user); + ASSERT_NO_THROW(auth->UpdatePassword(*user, kStrongPassword)); + } } TEST(AuthWithoutStorage, Permissions) { @@ -668,6 +666,17 @@ TEST(AuthWithoutStorage, UserSerializeDeserialize) { ASSERT_EQ(user, output); } +TEST(AuthWithoutStorage, UserSerializeDeserializeWithOutPassword) { + auto user = User("test"); + user.permissions().Grant(Permission::MATCH); + user.permissions().Deny(Permission::MERGE); + + auto data = user.Serialize(); + + auto output = User::Deserialize(data); + ASSERT_EQ(user, output); +} + TEST(AuthWithoutStorage, RoleSerializeDeserialize) { auto role = Role("test"); role.permissions().Grant(Permission::MATCH); @@ -680,30 +689,30 @@ TEST(AuthWithoutStorage, RoleSerializeDeserialize) { } TEST_F(AuthWithStorage, UserWithRoleSerializeDeserialize) { - auto role = auth.AddRole("role"); + auto role = auth->AddRole("role"); ASSERT_TRUE(role); role->permissions().Grant(Permission::MATCH); role->permissions().Deny(Permission::MERGE); - auth.SaveRole(*role); + auth->SaveRole(*role); - auto user = auth.AddUser("user"); + auto user = auth->AddUser("user"); ASSERT_TRUE(user); user->permissions().Grant(Permission::MATCH); user->permissions().Deny(Permission::MERGE); user->UpdatePassword("world"); user->SetRole(*role); - auth.SaveUser(*user); + auth->SaveUser(*user); - auto new_user = auth.GetUser("user"); + auto new_user = auth->GetUser("user"); ASSERT_TRUE(new_user); ASSERT_EQ(*user, *new_user); } TEST_F(AuthWithStorage, UserRoleUniqueName) { - ASSERT_TRUE(auth.AddUser("user")); - ASSERT_TRUE(auth.AddRole("role")); - ASSERT_FALSE(auth.AddRole("user")); - ASSERT_FALSE(auth.AddUser("role")); + ASSERT_TRUE(auth->AddUser("user")); + ASSERT_TRUE(auth->AddRole("role")); + ASSERT_FALSE(auth->AddRole("user")); + ASSERT_FALSE(auth->AddUser("role")); } TEST(AuthWithoutStorage, CaseInsensitivity) { @@ -718,8 +727,9 @@ TEST(AuthWithoutStorage, CaseInsensitivity) { { auto perms = Permissions(); auto fine_grained_access_handler = FineGrainedAccessHandler(); - auto user1 = User("test", "pw", perms, fine_grained_access_handler); - auto user2 = User("Test", "pw", perms, fine_grained_access_handler); + auto passwordHash = HashPassword("pw"); + auto user1 = User("test", passwordHash, perms, fine_grained_access_handler); + auto user2 = User("Test", passwordHash, perms, fine_grained_access_handler); ASSERT_EQ(user1, user2); ASSERT_EQ(user1.username(), user2.username()); ASSERT_EQ(user1.username(), "test"); @@ -748,58 +758,58 @@ TEST(AuthWithoutStorage, CaseInsensitivity) { TEST_F(AuthWithStorage, CaseInsensitivity) { // AddUser { - auto user = auth.AddUser("Alice", "alice"); + auto user = auth->AddUser("Alice", "alice"); ASSERT_TRUE(user); ASSERT_EQ(user->username(), "alice"); - ASSERT_FALSE(auth.AddUser("alice")); - ASSERT_FALSE(auth.AddUser("alicE")); + ASSERT_FALSE(auth->AddUser("alice")); + ASSERT_FALSE(auth->AddUser("alicE")); } { - auto user = auth.AddUser("BoB", "bob"); + auto user = auth->AddUser("BoB", "bob"); ASSERT_TRUE(user); ASSERT_EQ(user->username(), "bob"); - ASSERT_FALSE(auth.AddUser("bob")); - ASSERT_FALSE(auth.AddUser("bOb")); + ASSERT_FALSE(auth->AddUser("bob")); + ASSERT_FALSE(auth->AddUser("bOb")); } // Authenticate { - auto user = auth.Authenticate("alice", "alice"); + auto user = auth->Authenticate("alice", "alice"); ASSERT_TRUE(user); ASSERT_EQ(user->username(), "alice"); } { - auto user = auth.Authenticate("alICe", "alice"); + auto user = auth->Authenticate("alICe", "alice"); ASSERT_TRUE(user); ASSERT_EQ(user->username(), "alice"); } // GetUser { - auto user = auth.GetUser("alice"); + auto user = auth->GetUser("alice"); ASSERT_TRUE(user); ASSERT_EQ(user->username(), "alice"); } { - auto user = auth.GetUser("aLicE"); + auto user = auth->GetUser("aLicE"); ASSERT_TRUE(user); ASSERT_EQ(user->username(), "alice"); } - ASSERT_FALSE(auth.GetUser("carol")); + ASSERT_FALSE(auth->GetUser("carol")); // RemoveUser { - auto user = auth.AddUser("caRol", "carol"); + auto user = auth->AddUser("caRol", "carol"); ASSERT_TRUE(user); ASSERT_EQ(user->username(), "carol"); - ASSERT_TRUE(auth.RemoveUser("cAROl")); - ASSERT_FALSE(auth.RemoveUser("carol")); - ASSERT_FALSE(auth.GetUser("CAROL")); + ASSERT_TRUE(auth->RemoveUser("cAROl")); + ASSERT_FALSE(auth->RemoveUser("carol")); + ASSERT_FALSE(auth->GetUser("CAROL")); } // AllUsers { - auto users = auth.AllUsers(); + auto users = auth->AllUsers(); ASSERT_EQ(users.size(), 2); std::sort(users.begin(), users.end(), [](const auto &a, const auto &b) { return a.username() < b.username(); }); ASSERT_EQ(users[0].username(), "alice"); @@ -808,48 +818,48 @@ TEST_F(AuthWithStorage, CaseInsensitivity) { // AddRole { - auto role = auth.AddRole("Moderator"); + auto role = auth->AddRole("Moderator"); ASSERT_TRUE(role); ASSERT_EQ(role->rolename(), "moderator"); - ASSERT_FALSE(auth.AddRole("moderator")); - ASSERT_FALSE(auth.AddRole("MODERATOR")); + ASSERT_FALSE(auth->AddRole("moderator")); + ASSERT_FALSE(auth->AddRole("MODERATOR")); } { - auto role = auth.AddRole("adMIN"); + auto role = auth->AddRole("adMIN"); ASSERT_TRUE(role); ASSERT_EQ(role->rolename(), "admin"); - ASSERT_FALSE(auth.AddRole("Admin")); - ASSERT_FALSE(auth.AddRole("ADMIn")); + ASSERT_FALSE(auth->AddRole("Admin")); + ASSERT_FALSE(auth->AddRole("ADMIn")); } - ASSERT_FALSE(auth.AddRole("ALICE")); - ASSERT_FALSE(auth.AddUser("ModeRAtor")); + ASSERT_FALSE(auth->AddRole("ALICE")); + ASSERT_FALSE(auth->AddUser("ModeRAtor")); // GetRole { - auto role = auth.GetRole("moderator"); + auto role = auth->GetRole("moderator"); ASSERT_TRUE(role); ASSERT_EQ(role->rolename(), "moderator"); } { - auto role = auth.GetRole("MoDERATOR"); + auto role = auth->GetRole("MoDERATOR"); ASSERT_TRUE(role); ASSERT_EQ(role->rolename(), "moderator"); } - ASSERT_FALSE(auth.GetRole("root")); + ASSERT_FALSE(auth->GetRole("root")); // RemoveRole { - auto role = auth.AddRole("RooT"); + auto role = auth->AddRole("RooT"); ASSERT_TRUE(role); ASSERT_EQ(role->rolename(), "root"); - ASSERT_TRUE(auth.RemoveRole("rOOt")); - ASSERT_FALSE(auth.RemoveRole("RoOt")); - ASSERT_FALSE(auth.GetRole("RoOt")); + ASSERT_TRUE(auth->RemoveRole("rOOt")); + ASSERT_FALSE(auth->RemoveRole("RoOt")); + ASSERT_FALSE(auth->GetRole("RoOt")); } // AllRoles { - auto roles = auth.AllRoles(); + auto roles = auth->AllRoles(); ASSERT_EQ(roles.size(), 2); std::sort(roles.begin(), roles.end(), [](const auto &a, const auto &b) { return a.rolename() < b.rolename(); }); ASSERT_EQ(roles[0].rolename(), "admin"); @@ -858,14 +868,14 @@ TEST_F(AuthWithStorage, CaseInsensitivity) { // SaveRole { - auto role = auth.GetRole("MODErator"); + auto role = auth->GetRole("MODErator"); ASSERT_TRUE(role); ASSERT_EQ(role->rolename(), "moderator"); role->permissions().Grant(memgraph::auth::Permission::MATCH); - auth.SaveRole(*role); + auth->SaveRole(*role); } { - auto role = auth.GetRole("modeRATOR"); + auto role = auth->GetRole("modeRATOR"); ASSERT_TRUE(role); ASSERT_EQ(role->rolename(), "moderator"); ASSERT_EQ(role->permissions().Has(memgraph::auth::Permission::MATCH), memgraph::auth::PermissionLevel::GRANT); @@ -873,17 +883,17 @@ TEST_F(AuthWithStorage, CaseInsensitivity) { // SaveUser { - auto user = auth.GetUser("aLice"); + auto user = auth->GetUser("aLice"); ASSERT_TRUE(user); ASSERT_EQ(user->username(), "alice"); - auto role = auth.GetRole("moderAtor"); + auto role = auth->GetRole("moderAtor"); ASSERT_TRUE(role); ASSERT_EQ(role->rolename(), "moderator"); user->SetRole(*role); - auth.SaveUser(*user); + auth->SaveUser(*user); } { - auto user = auth.GetUser("aLIce"); + auto user = auth->GetUser("aLIce"); ASSERT_TRUE(user); ASSERT_EQ(user->username(), "alice"); const auto *role = user->role(); @@ -893,27 +903,27 @@ TEST_F(AuthWithStorage, CaseInsensitivity) { // AllUsersForRole { - auto carol = auth.AddUser("caROl"); + auto carol = auth->AddUser("caROl"); ASSERT_TRUE(carol); ASSERT_EQ(carol->username(), "carol"); - auto dave = auth.AddUser("daVe"); + auto dave = auth->AddUser("daVe"); ASSERT_TRUE(dave); ASSERT_EQ(dave->username(), "dave"); - auto admin = auth.GetRole("aDMin"); + auto admin = auth->GetRole("aDMin"); ASSERT_TRUE(admin); ASSERT_EQ(admin->rolename(), "admin"); carol->SetRole(*admin); - auth.SaveUser(*carol); + auth->SaveUser(*carol); dave->SetRole(*admin); - auth.SaveUser(*dave); + auth->SaveUser(*dave); } { - auto users = auth.AllUsersForRole("modeRAtoR"); + auto users = auth->AllUsersForRole("modeRAtoR"); ASSERT_EQ(users.size(), 1); ASSERT_EQ(users[0].username(), "alice"); } { - auto users = auth.AllUsersForRole("AdmiN"); + auto users = auth->AllUsersForRole("AdmiN"); ASSERT_EQ(users.size(), 2); std::sort(users.begin(), users.end(), [](const auto &a, const auto &b) { return a.username() < b.username(); }); ASSERT_EQ(users[0].username(), "carol"); @@ -922,53 +932,49 @@ TEST_F(AuthWithStorage, CaseInsensitivity) { } TEST(AuthWithoutStorage, Crypto) { - auto hash = EncryptPassword("hello"); - ASSERT_TRUE(VerifyPassword("hello", hash)); - ASSERT_FALSE(VerifyPassword("hello1", hash)); + auto hash = HashPassword("hello"); + ASSERT_TRUE(hash.VerifyPassword("hello")); + ASSERT_FALSE(hash.VerifyPassword("hello1")); } class AuthWithVariousEncryptionAlgorithms : public ::testing::Test { protected: - void SetUp() override { FLAGS_password_encryption_algorithm = "bcrypt"; } + void SetUp() override { SetHashAlgorithm("bcrypt"); } }; TEST_F(AuthWithVariousEncryptionAlgorithms, VerifyPasswordDefault) { - auto hash = EncryptPassword("hello"); - ASSERT_TRUE(VerifyPassword("hello", hash)); - ASSERT_FALSE(VerifyPassword("hello1", hash)); + auto hash = HashPassword("hello"); + ASSERT_TRUE(hash.VerifyPassword("hello")); + ASSERT_FALSE(hash.VerifyPassword("hello1")); } TEST_F(AuthWithVariousEncryptionAlgorithms, VerifyPasswordSHA256) { - FLAGS_password_encryption_algorithm = "sha256"; - auto hash = EncryptPassword("hello"); - ASSERT_TRUE(VerifyPassword("hello", hash)); - ASSERT_FALSE(VerifyPassword("hello1", hash)); + SetHashAlgorithm("sha256"); + auto hash = HashPassword("hello"); + ASSERT_TRUE(hash.VerifyPassword("hello")); + ASSERT_FALSE(hash.VerifyPassword("hello1")); } TEST_F(AuthWithVariousEncryptionAlgorithms, VerifyPasswordSHA256_1024) { - FLAGS_password_encryption_algorithm = "sha256-multiple"; - auto hash = EncryptPassword("hello"); - ASSERT_TRUE(VerifyPassword("hello", hash)); - ASSERT_FALSE(VerifyPassword("hello1", hash)); + SetHashAlgorithm("sha256-multiple"); + auto hash = HashPassword("hello"); + ASSERT_TRUE(hash.VerifyPassword("hello")); + ASSERT_FALSE(hash.VerifyPassword("hello1")); } -TEST_F(AuthWithVariousEncryptionAlgorithms, VerifyPasswordThrow) { - FLAGS_password_encryption_algorithm = "abcd"; - ASSERT_THROW(EncryptPassword("hello"), AuthException); +TEST_F(AuthWithVariousEncryptionAlgorithms, SetEncryptionAlgorithmNonsenseThrow) { + ASSERT_THROW(SetHashAlgorithm("abcd"), AuthException); } -TEST_F(AuthWithVariousEncryptionAlgorithms, VerifyPasswordEmptyEncryptionThrow) { - FLAGS_password_encryption_algorithm = ""; - ASSERT_THROW(EncryptPassword("hello"), AuthException); +TEST_F(AuthWithVariousEncryptionAlgorithms, SetEncryptionAlgorithmEmptyThrow) { + ASSERT_THROW(SetHashAlgorithm(""), AuthException); } class AuthWithStorageWithVariousEncryptionAlgorithms : public ::testing::Test { protected: void SetUp() override { memgraph::utils::EnsureDir(test_folder_); - FLAGS_auth_password_permit_null = true; - FLAGS_auth_password_strength_regex = ".+"; - FLAGS_password_encryption_algorithm = "bcrypt"; + SetHashAlgorithm("bcrypt"); memgraph::license::global_license_checker.EnableTesting(); } @@ -976,8 +982,8 @@ class AuthWithStorageWithVariousEncryptionAlgorithms : public ::testing::Test { void TearDown() override { fs::remove_all(test_folder_); } fs::path test_folder_{fs::temp_directory_path() / "MG_tests_unit_auth"}; - - Auth auth{test_folder_ / ("unit_auth_test_" + std::to_string(static_cast<int>(getpid())))}; + Auth::Config auth_config{}; + Auth auth{test_folder_ / ("unit_auth_test_" + std::to_string(static_cast<int>(getpid()))), auth_config}; }; TEST_F(AuthWithStorageWithVariousEncryptionAlgorithms, AddUserDefault) { @@ -987,25 +993,26 @@ TEST_F(AuthWithStorageWithVariousEncryptionAlgorithms, AddUserDefault) { } TEST_F(AuthWithStorageWithVariousEncryptionAlgorithms, AddUserSha256) { - FLAGS_password_encryption_algorithm = "sha256"; + SetHashAlgorithm("sha256"); auto user = auth.AddUser("Alice", "alice"); ASSERT_TRUE(user); ASSERT_EQ(user->username(), "alice"); } TEST_F(AuthWithStorageWithVariousEncryptionAlgorithms, AddUserSha256_1024) { - FLAGS_password_encryption_algorithm = "sha256-multiple"; + SetHashAlgorithm("sha256-multiple"); auto user = auth.AddUser("Alice", "alice"); ASSERT_TRUE(user); ASSERT_EQ(user->username(), "alice"); } -TEST_F(AuthWithStorageWithVariousEncryptionAlgorithms, AddUserThrow) { - FLAGS_password_encryption_algorithm = "abcd"; - ASSERT_THROW(auth.AddUser("Alice", "alice"), AuthException); -} - -TEST_F(AuthWithStorageWithVariousEncryptionAlgorithms, AddUserEmptyPasswordEncryptionThrow) { - FLAGS_password_encryption_algorithm = ""; - ASSERT_THROW(auth.AddUser("Alice", "alice"), AuthException); +TEST(Serialize, HashedPassword) { + for (auto algo : + {PasswordHashAlgorithm::BCRYPT, PasswordHashAlgorithm::SHA256, PasswordHashAlgorithm::SHA256_MULTIPLE}) { + auto sut = HashPassword("password", algo); + nlohmann::json j = sut; + auto ret = j.get<HashedPassword>(); + ASSERT_EQ(sut, ret); + ASSERT_TRUE(ret.VerifyPassword("password")); + } } diff --git a/tests/unit/auth_checker.cpp b/tests/unit/auth_checker.cpp index b048e9491..f4c499cd7 100644 --- a/tests/unit/auth_checker.cpp +++ b/tests/unit/auth_checker.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -22,7 +22,7 @@ #include "storage/v2/disk/storage.hpp" #include "storage/v2/inmemory/storage.hpp" #include "storage/v2/view.hpp" - +using memgraph::replication_coordination_glue::ReplicationRole; #ifdef MG_ENTERPRISE template <typename StorageType> class FineGrainedAuthCheckerFixture : public testing::Test { @@ -31,7 +31,7 @@ class FineGrainedAuthCheckerFixture : public testing::Test { memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; - std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access(ReplicationRole::MAIN)}; memgraph::query::DbAccessor dba{storage_dba.get()}; // make a V-graph (v3)<-[r2]-(v1)-[r1]->(v2) diff --git a/tests/unit/auth_handler.cpp b/tests/unit/auth_handler.cpp index 6537575fd..a162d1838 100644 --- a/tests/unit/auth_handler.cpp +++ b/tests/unit/auth_handler.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -26,8 +26,9 @@ class AuthQueryHandlerFixture : public testing::Test { protected: std::filesystem::path test_folder_{std::filesystem::temp_directory_path() / "MG_tests_unit_auth_handler"}; memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> auth{ - test_folder_ / ("unit_auth_handler_test_" + std::to_string(static_cast<int>(getpid())))}; - memgraph::glue::AuthQueryHandler auth_handler{&auth, memgraph::glue::kDefaultUserRoleRegex.data()}; + test_folder_ / ("unit_auth_handler_test_" + std::to_string(static_cast<int>(getpid()))), + memgraph::auth::Auth::Config{/* default */}}; + memgraph::glue::AuthQueryHandler auth_handler{&auth}; std::string user_name = "Mate"; std::string edge_type_repr = "EdgeType1"; @@ -56,7 +57,7 @@ TEST_F(AuthQueryHandlerFixture, GivenAuthQueryHandlerWhenInitializedHaveNoUserna } TEST_F(AuthQueryHandlerFixture, GivenUserWhenNoDeniesOrGrantsThenNothingIsReturned) { - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms}; auth->SaveUser(user); { ASSERT_EQ(auth_handler.GetUsernames().size(), 1); } @@ -70,7 +71,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenNoDeniesOrGrantsThenNothingIsReturn TEST_F(AuthQueryHandlerFixture, GivenUserWhenAddedGrantPermissionThenItIsReturned) { perms.Grant(memgraph::auth::Permission::MATCH); - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -91,7 +92,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenAddedGrantPermissionThenItIsReturne TEST_F(AuthQueryHandlerFixture, GivenUserWhenAddedDenyPermissionThenItIsReturned) { perms.Deny(memgraph::auth::Permission::MATCH); - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -113,7 +114,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenAddedDenyPermissionThenItIsReturned TEST_F(AuthQueryHandlerFixture, GivenUserWhenPrivilegeRevokedThenNothingIsReturned) { perms.Deny(memgraph::auth::Permission::MATCH); perms.Revoke(memgraph::auth::Permission::MATCH); - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -179,7 +180,7 @@ TEST_F(AuthQueryHandlerFixture, GivenRoleWhenPrivilegeRevokedThenNothingIsReturn TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedTwoPrivilegesThenBothAreReturned) { perms.Grant(memgraph::auth::Permission::MATCH); perms.Grant(memgraph::auth::Permission::CREATE); - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -190,7 +191,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserAndRoleWhenOneGrantedAndOtherGrantedThe perms.Grant(memgraph::auth::Permission::MATCH); memgraph::auth::Role role = memgraph::auth::Role{"Mates_role", perms}; auth->SaveRole(role); - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms}; user.SetRole(role); auth->SaveUser(user); @@ -214,7 +215,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserAndRoleWhenOneDeniedAndOtherDeniedThenB perms.Deny(memgraph::auth::Permission::MATCH); memgraph::auth::Role role = memgraph::auth::Role{"Mates_role", perms}; auth->SaveRole(role); - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms}; user.SetRole(role); auth->SaveUser(user); @@ -244,7 +245,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserAndRoleWhenOneGrantedAndOtherDeniedThen user_perms.Grant(memgraph::auth::Permission::MATCH); memgraph::auth::User user = memgraph::auth::User{ user_name, - "", + std::nullopt, user_perms, }; user.SetRole(role); @@ -274,7 +275,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserAndRoleWhenOneDeniedAndOtherGrantedThen memgraph::auth::Permissions user_perms{}; user_perms.Deny(memgraph::auth::Permission::MATCH); - memgraph::auth::User user = memgraph::auth::User{user_name, "", user_perms}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, user_perms}; user.SetRole(role); auth->SaveUser(user); @@ -304,7 +305,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedPrivilegeOnLabelThenIsDispla memgraph::auth::FineGrainedAccessPermissions{}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -333,7 +334,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedMultiplePrivilegesOnLabelThe memgraph::auth::FineGrainedAccessPermissions{}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -363,7 +364,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedAllPrivilegesOnLabelThenTopO memgraph::auth::FineGrainedAccessPermissions{}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -391,7 +392,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalPrivilegeOnLabelThenIs memgraph::auth::FineGrainedAccessPermissions{}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -420,7 +421,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalMultiplePrivilegesOnLa memgraph::auth::FineGrainedAccessPermissions{}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -450,7 +451,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalAllPrivilegesOnLabelTh memgraph::auth::FineGrainedAccessPermissions{}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -479,7 +480,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedPrivilegeOnEdgeTypeThenIsDis memgraph::auth::FineGrainedAccessPermissions{read_permission}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -508,7 +509,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedMultiplePrivilegesOnEdgeType memgraph::auth::FineGrainedAccessPermissions{read_permission}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -538,7 +539,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedAllPrivilegesOnEdgeTypeThenT memgraph::auth::FineGrainedAccessPermissions{read_permission}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -566,7 +567,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalPrivilegeOnEdgeTypeThe memgraph::auth::FineGrainedAccessPermissions{read_permission}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -596,7 +597,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalMultiplePrivilegesOnEd }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -626,7 +627,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalAllPrivilegesOnEdgeTyp memgraph::auth::FineGrainedAccessPermissions{read_permission}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -655,7 +656,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedAndDeniedOnLabelThenNoPermis memgraph::auth::FineGrainedAccessPermissions{}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -684,7 +685,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedAndDeniedOnEdgeTypeThenNoPer memgraph::auth::FineGrainedAccessPermissions{read_permission}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); @@ -713,7 +714,7 @@ TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedReadAndDeniedUpdateThenOneIs memgraph::auth::FineGrainedAccessPermissions{read_permission}, }; - memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + memgraph::auth::User user = memgraph::auth::User{user_name, std::nullopt, perms, handler}; auth->SaveUser(user); auto privileges = auth_handler.GetPrivileges(user_name); diff --git a/tests/unit/bfs_fine_grained.cpp b/tests/unit/bfs_fine_grained.cpp index 235431804..568206dfd 100644 --- a/tests/unit/bfs_fine_grained.cpp +++ b/tests/unit/bfs_fine_grained.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -42,7 +42,9 @@ class VertexDb : public Database { } } - std::unique_ptr<memgraph::storage::Storage::Accessor> Access() override { return db_->Access(); } + std::unique_ptr<memgraph::storage::Storage::Accessor> Access() override { + return db_->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN); + } std::unique_ptr<LogicalOperator> MakeBfsOperator(Symbol source_sym, Symbol sink_sym, Symbol edge_sym, EdgeAtom::Direction direction, diff --git a/tests/unit/bfs_single_node.cpp b/tests/unit/bfs_single_node.cpp index 37aae0491..a6816242d 100644 --- a/tests/unit/bfs_single_node.cpp +++ b/tests/unit/bfs_single_node.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -31,7 +31,9 @@ class SingleNodeDb : public Database { } } - std::unique_ptr<memgraph::storage::Storage::Accessor> Access() override { return db_->Access(); } + std::unique_ptr<memgraph::storage::Storage::Accessor> Access() override { + return db_->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN); + } std::unique_ptr<LogicalOperator> MakeBfsOperator(Symbol source_sym, Symbol sink_sym, Symbol edge_sym, EdgeAtom::Direction direction, diff --git a/tests/unit/bolt_encoder.cpp b/tests/unit/bolt_encoder.cpp index 6ffb4b496..19a958118 100644 --- a/tests/unit/bolt_encoder.cpp +++ b/tests/unit/bolt_encoder.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -182,7 +182,7 @@ void TestVertexAndEdgeWithDifferentStorages(std::unique_ptr<memgraph::storage::S output.clear(); // create vertex - auto dba = db->Access(); + auto dba = db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN); auto va1 = dba->CreateVertex(); auto va2 = dba->CreateVertex(); auto l1 = dba->NameToLabel("label1"); @@ -206,11 +206,11 @@ void TestVertexAndEdgeWithDifferentStorages(std::unique_ptr<memgraph::storage::S // check everything std::vector<Value> vals; - vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::VertexAccessor(va1)), *db, - memgraph::storage::View::NEW)); - vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::VertexAccessor(va2)), *db, - memgraph::storage::View::NEW)); - vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::EdgeAccessor(ea)), *db, + vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::VertexAccessor(va1)), + db.get(), memgraph::storage::View::NEW)); + vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::VertexAccessor(va2)), + db.get(), memgraph::storage::View::NEW)); + vals.push_back(*memgraph::glue::ToBoltValue(memgraph::query::TypedValue(memgraph::query::EdgeAccessor(ea)), db.get(), memgraph::storage::View::NEW)); bolt_encoder.MessageRecord(vals); diff --git a/tests/unit/clearing_old_disk_data.cpp b/tests/unit/clearing_old_disk_data.cpp index 92b0de134..395391e12 100644 --- a/tests/unit/clearing_old_disk_data.cpp +++ b/tests/unit/clearing_old_disk_data.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -20,6 +20,8 @@ #include "storage/v2/property_value.hpp" #include "storage/v2/view.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; + class ClearingOldDiskDataTest : public ::testing::Test { public: const std::string testSuite = "clearing_old_disk_data"; @@ -33,7 +35,7 @@ TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithVertexTimestampUpdate) { auto *tx_db = disk_storage->GetRocksDBStorage()->db_; ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 0); - auto acc1 = disk_storage->Access(std::nullopt); + auto acc1 = disk_storage->Access(ReplicationRole::MAIN); auto vertex1 = acc1->CreateVertex(); auto label1 = acc1->NameToLabel("DiskLabel"); auto property1 = acc1->NameToProperty("DiskProperty"); @@ -43,7 +45,7 @@ TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithVertexTimestampUpdate) { ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); - auto acc2 = disk_storage->Access(std::nullopt); + auto acc2 = disk_storage->Access(ReplicationRole::MAIN); auto vertex2 = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW).value(); /// This is the same property as in the first transaction, we just want to test /// the number of entries inside RocksDB when the timestamp changes @@ -58,7 +60,7 @@ TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithVertexValueUpdate) { auto *tx_db = disk_storage->GetRocksDBStorage()->db_; ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 0); - auto acc1 = disk_storage->Access(std::nullopt); + auto acc1 = disk_storage->Access(ReplicationRole::MAIN); auto vertex1 = acc1->CreateVertex(); auto label1 = acc1->NameToLabel("DiskLabel"); auto property1 = acc1->NameToProperty("DiskProperty"); @@ -68,7 +70,7 @@ TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithVertexValueUpdate) { ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); - auto acc2 = disk_storage->Access(std::nullopt); + auto acc2 = disk_storage->Access(ReplicationRole::MAIN); auto vertex2 = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW).value(); /// This is the same property as in the first transaction, we just want to test /// the number of entries inside RocksDB when the timestamp changes @@ -83,7 +85,7 @@ TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithVertexKeyUpdate) { auto *tx_db = disk_storage->GetRocksDBStorage()->db_; ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 0); - auto acc1 = disk_storage->Access(std::nullopt); + auto acc1 = disk_storage->Access(ReplicationRole::MAIN); auto vertex1 = acc1->CreateVertex(); auto label1 = acc1->NameToLabel("DiskLabel"); auto property1 = acc1->NameToProperty("DiskProperty"); @@ -93,7 +95,7 @@ TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithVertexKeyUpdate) { ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); - auto acc2 = disk_storage->Access(std::nullopt); + auto acc2 = disk_storage->Access(ReplicationRole::MAIN); auto vertex2 = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW).value(); auto label2 = acc2->NameToLabel("DiskLabel2"); ASSERT_TRUE(vertex2.AddLabel(label2).HasValue()); @@ -106,7 +108,7 @@ TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithEdgeTimestampUpdate) { auto *tx_db = disk_storage->GetRocksDBStorage()->db_; ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 0); - auto acc1 = disk_storage->Access(std::nullopt); + auto acc1 = disk_storage->Access(ReplicationRole::MAIN); auto label1 = acc1->NameToLabel("DiskLabel"); auto property1 = acc1->NameToProperty("DiskProperty"); @@ -126,7 +128,7 @@ TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithEdgeTimestampUpdate) { ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 5); - auto acc2 = disk_storage->Access(std::nullopt); + auto acc2 = disk_storage->Access(ReplicationRole::MAIN); auto from_vertex = acc2->FindVertex(from.Gid(), memgraph::storage::View::NEW).value(); auto ret = from_vertex.OutEdges(memgraph::storage::View::NEW); @@ -145,7 +147,7 @@ TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithEdgeValueUpdate) { auto *tx_db = disk_storage->GetRocksDBStorage()->db_; ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 0); - auto acc1 = disk_storage->Access(std::nullopt); + auto acc1 = disk_storage->Access(ReplicationRole::MAIN); auto label1 = acc1->NameToLabel("DiskLabel"); auto property1 = acc1->NameToProperty("DiskProperty"); @@ -165,7 +167,7 @@ TEST_F(ClearingOldDiskDataTest, TestNumOfEntriesWithEdgeValueUpdate) { ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 5); - auto acc2 = disk_storage->Access(std::nullopt); + auto acc2 = disk_storage->Access(ReplicationRole::MAIN); auto from_vertex = acc2->FindVertex(from.Gid(), memgraph::storage::View::NEW).value(); auto ret = from_vertex.OutEdges(memgraph::storage::View::NEW); diff --git a/tests/unit/cpp_api.cpp b/tests/unit/cpp_api.cpp index 012b2d713..84ca0b195 100644 --- a/tests/unit/cpp_api.cpp +++ b/tests/unit/cpp_api.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -43,7 +43,8 @@ struct CppApiTestFixture : public ::testing::Test { } memgraph::query::DbAccessor &CreateDbAccessor(const memgraph::storage::IsolationLevel isolationLevel) { - accessors_.push_back(storage->Access(isolationLevel)); + accessors_.push_back( + storage->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN, isolationLevel)); db_accessors_.emplace_back(accessors_.back().get()); return db_accessors_.back(); } diff --git a/tests/unit/cypher_main_visitor.cpp b/tests/unit/cypher_main_visitor.cpp index 31fd95c6c..1353a56dd 100644 --- a/tests/unit/cypher_main_visitor.cpp +++ b/tests/unit/cypher_main_visitor.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -37,11 +37,13 @@ #include "query/frontend/ast/cypher_main_visitor.hpp" #include "query/frontend/opencypher/parser.hpp" #include "query/frontend/stripped.hpp" +#include "query/parameters.hpp" #include "query/procedure/cypher_types.hpp" #include "query/procedure/mg_procedure_impl.hpp" #include "query/procedure/module.hpp" #include "query/typed_value.hpp" +#include "utils/logging.hpp" #include "utils/string.hpp" #include "utils/variant_helpers.hpp" @@ -118,7 +120,8 @@ class AstGenerator : public Base { public: Query *ParseQuery(const std::string &query_string) override { ::frontend::opencypher::Parser parser(query_string); - CypherMainVisitor visitor(context_, &ast_storage_); + Parameters parameters; + CypherMainVisitor visitor(context_, &ast_storage_, ¶meters); visitor.visit(parser.tree()); return visitor.query(); } @@ -151,6 +154,7 @@ class ClonedAstGenerator : public Base { public: Query *ParseQuery(const std::string &query_string) override { ::frontend::opencypher::Parser parser(query_string); + Parameters parameters; AstStorage tmp_storage; { // Add a label, property and edge type into temporary storage so @@ -159,7 +163,7 @@ class ClonedAstGenerator : public Base { tmp_storage.GetPropertyIx("fdjakfjdklfjdaslk"); tmp_storage.GetEdgeTypeIx("fdjkalfjdlkajfdkla"); } - CypherMainVisitor visitor(context_, &tmp_storage); + CypherMainVisitor visitor(context_, &tmp_storage, ¶meters); visitor.visit(parser.tree()); return visitor.query()->Clone(&ast_storage_); } @@ -182,8 +186,9 @@ class CachedAstGenerator : public Base { StrippedQuery stripped(query_string); parameters_ = stripped.literals(); ::frontend::opencypher::Parser parser(stripped.query()); + Parameters parameters; AstStorage tmp_storage; - CypherMainVisitor visitor(context_, &tmp_storage); + CypherMainVisitor visitor(context_, &tmp_storage, ¶meters); visitor.visit(parser.tree()); return visitor.query()->Clone(&ast_storage_); } @@ -2546,7 +2551,7 @@ TEST_P(CypherMainVisitorTest, ShowUsersForRole) { void check_replication_query(Base *ast_generator, const ReplicationQuery *query, const std::string name, const std::optional<TypedValue> socket_address, const ReplicationQuery::SyncMode sync_mode, const std::optional<TypedValue> port = {}) { - EXPECT_EQ(query->replica_name_, name); + EXPECT_EQ(query->instance_name_, name); EXPECT_EQ(query->sync_mode_, sync_mode); ASSERT_EQ(static_cast<bool>(query->socket_address_), static_cast<bool>(socket_address)); if (socket_address) { @@ -2593,7 +2598,7 @@ TEST_P(CypherMainVisitorTest, TestSetReplicationMode) { } { - const std::string query = "SET REPLICATION ROLE TO MAIN WITH PORT 10000"; + const std::string query = "SET REPLICATION ROLE TO REPLICA"; ASSERT_THROW(ast_generator.ParseQuery(query), SemanticException); } @@ -2636,7 +2641,7 @@ TEST_P(CypherMainVisitorTest, TestDeleteReplica) { std::string correct_query = "DROP REPLICA replica1"; auto *correct_query_parsed = dynamic_cast<ReplicationQuery *>(ast_generator.ParseQuery(correct_query)); ASSERT_TRUE(correct_query_parsed); - EXPECT_EQ(correct_query_parsed->replica_name_, "replica1"); + EXPECT_EQ(correct_query_parsed->instance_name_, "replica1"); } TEST_P(CypherMainVisitorTest, TestExplainRegularQuery) { @@ -3641,7 +3646,7 @@ TEST_P(CypherMainVisitorTest, MemoryLimit) { ASSERT_TRUE(query->single_query_); auto *single_query = query->single_query_; ASSERT_EQ(single_query->clauses_.size(), 2U); - auto *call_proc = dynamic_cast<CallProcedure *>(single_query->clauses_[0]); + [[maybe_unused]] auto *call_proc = dynamic_cast<CallProcedure *>(single_query->clauses_[0]); } { @@ -3701,7 +3706,7 @@ TEST_P(CypherMainVisitorTest, MemoryLimit) { ASSERT_TRUE(query->single_query_); auto *single_query = query->single_query_; ASSERT_EQ(single_query->clauses_.size(), 1U); - auto *call_proc = dynamic_cast<CallProcedure *>(single_query->clauses_[0]); + [[maybe_unused]] auto *call_proc = dynamic_cast<CallProcedure *>(single_query->clauses_[0]); } } diff --git a/tests/unit/database_get_info.cpp b/tests/unit/database_get_info.cpp index aac38159d..a8a275a61 100644 --- a/tests/unit/database_get_info.cpp +++ b/tests/unit/database_get_info.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -93,12 +93,12 @@ TYPED_TEST(InfoTest, InfoCheck) { { { - auto unique_acc = db_acc->storage()->UniqueAccess(); + auto unique_acc = db_acc->UniqueAccess(); ASSERT_FALSE(unique_acc->CreateExistenceConstraint(lbl, prop).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto unique_acc = db_acc->storage()->UniqueAccess(); + auto unique_acc = db_acc->UniqueAccess(); ASSERT_FALSE(unique_acc->DropExistenceConstraint(lbl, prop).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } @@ -108,7 +108,7 @@ TYPED_TEST(InfoTest, InfoCheck) { auto v2 = acc->CreateVertex(); auto v3 = acc->CreateVertex(); auto v4 = acc->CreateVertex(); - auto v5 = acc->CreateVertex(); + [[maybe_unused]] auto v5 = acc->CreateVertex(); ASSERT_FALSE(v2.AddLabel(lbl).HasError()); ASSERT_FALSE(v3.AddLabel(lbl).HasError()); @@ -123,49 +123,50 @@ TYPED_TEST(InfoTest, InfoCheck) { } { - auto unique_acc = db_acc->storage()->UniqueAccess(); + auto unique_acc = db_acc->UniqueAccess(); ASSERT_FALSE(unique_acc->CreateIndex(lbl).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto unique_acc = db_acc->storage()->UniqueAccess(); + auto unique_acc = db_acc->UniqueAccess(); ASSERT_FALSE(unique_acc->CreateIndex(lbl, prop).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto unique_acc = db_acc->storage()->UniqueAccess(); + auto unique_acc = db_acc->UniqueAccess(); ASSERT_FALSE(unique_acc->CreateIndex(lbl, prop2).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto unique_acc = db_acc->storage()->UniqueAccess(); + auto unique_acc = db_acc->UniqueAccess(); ASSERT_FALSE(unique_acc->DropIndex(lbl, prop).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto unique_acc = db_acc->storage()->UniqueAccess(); + auto unique_acc = db_acc->UniqueAccess(); ASSERT_FALSE(unique_acc->CreateUniqueConstraint(lbl, {prop2}).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto unique_acc = db_acc->storage()->UniqueAccess(); + auto unique_acc = db_acc->UniqueAccess(); ASSERT_FALSE(unique_acc->CreateUniqueConstraint(lbl2, {prop}).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto unique_acc = db_acc->storage()->UniqueAccess(); + auto unique_acc = db_acc->UniqueAccess(); ASSERT_FALSE(unique_acc->CreateUniqueConstraint(lbl3, {prop}).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto unique_acc = db_acc->storage()->UniqueAccess(); + auto unique_acc = db_acc->UniqueAccess(); ASSERT_EQ(unique_acc->DropUniqueConstraint(lbl, {prop2}), memgraph::storage::UniqueConstraints::DeletionStatus::SUCCESS); ASSERT_FALSE(unique_acc->Commit().HasError()); } - const auto &info = db_acc->GetInfo(true); // force to use configured directory + const auto &info = db_acc->GetInfo( + true, memgraph::replication_coordination_glue::ReplicationRole::MAIN); // force to use configured directory ASSERT_EQ(info.storage_info.vertex_count, 5); ASSERT_EQ(info.storage_info.edge_count, 2); diff --git a/tests/unit/dbms_database.cpp b/tests/unit/dbms_database.cpp index 20e1f55ac..535c0c055 100644 --- a/tests/unit/dbms_database.cpp +++ b/tests/unit/dbms_database.cpp @@ -29,7 +29,8 @@ memgraph::storage::Config default_conf(std::string name = "") { return {.durability = {.storage_directory = storage_directory / name, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL}, - .disk = {.main_storage_directory = storage_directory / name / "disk"}}; + .disk = {.main_storage_directory = storage_directory / name / "disk"}, + .salient.name = name.empty() ? std::string{"memgraph"} : name}; } class DBMS_Database : public ::testing::Test { @@ -55,20 +56,21 @@ TEST_F(DBMS_Database, New) { .durability = {.storage_directory = storage_directory / "db2", .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL}, - .disk = {.main_storage_directory = storage_directory / "disk"}}; - auto db2 = db_handler.New("db2", db_config, generic_repl_state); + .disk = {.main_storage_directory = storage_directory / "disk"}, + .salient.name = "db2"}; + auto db2 = db_handler.New(db_config, generic_repl_state); ASSERT_TRUE(db2.HasValue() && db2.GetValue()); ASSERT_TRUE(std::filesystem::exists(storage_directory / "db2")); } { // With default config - auto db3 = db_handler.New("db3", default_conf("db3"), generic_repl_state); + auto db3 = db_handler.New(default_conf("db3"), generic_repl_state); ASSERT_TRUE(db3.HasValue() && db3.GetValue()); ASSERT_TRUE(std::filesystem::exists(storage_directory / "db3")); - auto db4 = db_handler.New("db4", default_conf("four"), generic_repl_state); + auto db4 = db_handler.New(default_conf("four"), generic_repl_state); ASSERT_TRUE(db4.HasValue() && db4.GetValue()); ASSERT_TRUE(std::filesystem::exists(storage_directory / "four")); - auto db5 = db_handler.New("db5", default_conf("db3"), generic_repl_state); + auto db5 = db_handler.New(default_conf("db3"), generic_repl_state); ASSERT_TRUE(db5.HasError() && db5.GetError() == memgraph::dbms::NewError::EXISTS); } @@ -77,15 +79,15 @@ TEST_F(DBMS_Database, New) { ASSERT_EQ(all.size(), 3); ASSERT_EQ(all[0], "db2"); ASSERT_EQ(all[1], "db3"); - ASSERT_EQ(all[2], "db4"); + ASSERT_EQ(all[2], "four"); } TEST_F(DBMS_Database, Get) { memgraph::dbms::DatabaseHandler db_handler; - auto db1 = db_handler.New("db1", default_conf("db1"), generic_repl_state); - auto db2 = db_handler.New("db2", default_conf("db2"), generic_repl_state); - auto db3 = db_handler.New("db3", default_conf("db3"), generic_repl_state); + auto db1 = db_handler.New(default_conf("db1"), generic_repl_state); + auto db2 = db_handler.New(default_conf("db2"), generic_repl_state); + auto db3 = db_handler.New(default_conf("db3"), generic_repl_state); ASSERT_TRUE(db1.HasValue()); ASSERT_TRUE(db2.HasValue()); @@ -107,9 +109,9 @@ TEST_F(DBMS_Database, Get) { TEST_F(DBMS_Database, Delete) { memgraph::dbms::DatabaseHandler db_handler; - auto db1 = db_handler.New("db1", default_conf("db1"), generic_repl_state); - auto db2 = db_handler.New("db2", default_conf("db2"), generic_repl_state); - auto db3 = db_handler.New("db3", default_conf("db3"), generic_repl_state); + auto db1 = db_handler.New(default_conf("db1"), generic_repl_state); + auto db2 = db_handler.New(default_conf("db2"), generic_repl_state); + auto db3 = db_handler.New(default_conf("db3"), generic_repl_state); ASSERT_TRUE(db1.HasValue()); ASSERT_TRUE(db2.HasValue()); @@ -119,7 +121,7 @@ TEST_F(DBMS_Database, Delete) { // Release accessor to storage db1.GetValue().reset(); // Delete from handler - ASSERT_TRUE(db_handler.Delete("db1")); + ASSERT_TRUE(db_handler.TryDelete("db1")); ASSERT_FALSE(db_handler.Get("db1")); auto all = db_handler.All(); std::sort(all.begin(), all.end()); @@ -129,8 +131,8 @@ TEST_F(DBMS_Database, Delete) { } { - ASSERT_THROW(db_handler.Delete("db0"), memgraph::utils::BasicException); - ASSERT_THROW(db_handler.Delete("db1"), memgraph::utils::BasicException); + ASSERT_THROW(db_handler.TryDelete("db0"), memgraph::utils::BasicException); + ASSERT_THROW(db_handler.TryDelete("db1"), memgraph::utils::BasicException); auto all = db_handler.All(); std::sort(all.begin(), all.end()); ASSERT_EQ(all.size(), 2); @@ -144,17 +146,18 @@ TEST_F(DBMS_Database, DeleteAndRecover) { memgraph::dbms::DatabaseHandler db_handler; { - auto db1 = db_handler.New("db1", default_conf("db1"), generic_repl_state); - auto db2 = db_handler.New("db2", default_conf("db2"), generic_repl_state); + auto db1 = db_handler.New(default_conf("db1"), generic_repl_state); + auto db2 = db_handler.New(default_conf("db2"), generic_repl_state); memgraph::storage::Config conf_w_snap{ .durability = {.storage_directory = storage_directory / "db3", .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, .snapshot_on_exit = true}, - .disk = {.main_storage_directory = storage_directory / "db3" / "disk"}}; + .disk = {.main_storage_directory = storage_directory / "db3" / "disk"}, + .salient.name = "db3"}; - auto db3 = db_handler.New("db3", conf_w_snap, generic_repl_state); + auto db3 = db_handler.New(conf_w_snap, generic_repl_state); ASSERT_TRUE(db1.HasValue()); ASSERT_TRUE(db2.HasValue()); @@ -184,23 +187,24 @@ TEST_F(DBMS_Database, DeleteAndRecover) { } // Delete from handler - ASSERT_TRUE(db_handler.Delete("db1")); - ASSERT_TRUE(db_handler.Delete("db2")); - ASSERT_TRUE(db_handler.Delete("db3")); + ASSERT_TRUE(db_handler.TryDelete("db1")); + ASSERT_TRUE(db_handler.TryDelete("db2")); + ASSERT_TRUE(db_handler.TryDelete("db3")); { // Recover graphs (only db3) - auto db1 = db_handler.New("db1", default_conf("db1"), generic_repl_state); - auto db2 = db_handler.New("db2", default_conf("db2"), generic_repl_state); + auto db1 = db_handler.New(default_conf("db1"), generic_repl_state); + auto db2 = db_handler.New(default_conf("db2"), generic_repl_state); memgraph::storage::Config conf_w_rec{ .durability = {.storage_directory = storage_directory / "db3", .recover_on_startup = true, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL}, - .disk = {.main_storage_directory = storage_directory / "db3" / "disk"}}; + .disk = {.main_storage_directory = storage_directory / "db3" / "disk"}, + .salient.name = "db3"}; - auto db3 = db_handler.New("db3", conf_w_rec, generic_repl_state); + auto db3 = db_handler.New(conf_w_rec, generic_repl_state); // Check content { diff --git a/tests/unit/dbms_handler.cpp b/tests/unit/dbms_handler.cpp index 0ea4197fb..2abe0b77d 100644 --- a/tests/unit/dbms_handler.cpp +++ b/tests/unit/dbms_handler.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -25,8 +25,23 @@ #include "query/config.hpp" #include "query/interpreter.hpp" +namespace { +std::set<std::string> GetDirs(auto path) { + std::set<std::string> dirs; + // Clean the unused directories + for (const auto &entry : std::filesystem::directory_iterator(path)) { + const auto &name = entry.path().filename().string(); + if (entry.is_directory() && !name.empty() && name.front() != '.') { + dirs.emplace(name); + } + } + return dirs; +} +} // namespace + // Global std::filesystem::path storage_directory{std::filesystem::temp_directory_path() / "MG_test_unit_dbms_handler"}; +std::filesystem::path db_dir{storage_directory / "databases"}; static memgraph::storage::Config storage_conf; std::unique_ptr<memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock>> auth; @@ -51,8 +66,8 @@ class TestEnvironment : public ::testing::Environment { } auth = std::make_unique<memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock>>( - storage_directory / "auth"); - ptr_ = std::make_unique<memgraph::dbms::DbmsHandler>(storage_conf, auth.get(), false, true); + storage_directory / "auth", memgraph::auth::Auth::Config{/* default */}); + ptr_ = std::make_unique<memgraph::dbms::DbmsHandler>(storage_conf, auth.get(), false); } void TearDown() override { @@ -74,7 +89,7 @@ TEST(DBMS_Handler, Init) { std::vector<std::string> dirs = {"snapshots", "streams", "triggers", "wal"}; for (const auto &dir : dirs) ASSERT_TRUE(std::filesystem::exists(storage_directory / dir)) << (storage_directory / dir); - const auto db_path = storage_directory / "databases" / memgraph::dbms::kDefaultDB; + const auto db_path = db_dir / memgraph::dbms::kDefaultDB; ASSERT_TRUE(std::filesystem::exists(db_path)); for (const auto &dir : dirs) { std::error_code ec; @@ -92,10 +107,14 @@ TEST(DBMS_Handler, New) { ASSERT_EQ(all[0], memgraph::dbms::kDefaultDB); } { + const auto dirs = GetDirs(db_dir); auto db1 = dbms.New("db1"); ASSERT_TRUE(db1.HasValue()); ASSERT_TRUE(db1.GetValue()); - ASSERT_TRUE(std::filesystem::exists(storage_directory / "databases" / "db1")); + // New flow doesn't make db named directories + ASSERT_FALSE(std::filesystem::exists(db_dir / "db1")); + const auto dirs_w_db1 = GetDirs(db_dir); + ASSERT_EQ(dirs_w_db1.size(), dirs.size() + 1); ASSERT_TRUE(db1.GetValue()->storage() != nullptr); ASSERT_TRUE(db1.GetValue()->streams() != nullptr); ASSERT_TRUE(db1.GetValue()->trigger_store() != nullptr); @@ -111,9 +130,13 @@ TEST(DBMS_Handler, New) { ASSERT_TRUE(db2.HasError() && db2.GetError() == memgraph::dbms::NewError::EXISTS); } { + const auto dirs = GetDirs(db_dir); auto db3 = dbms.New("db3"); ASSERT_TRUE(db3.HasValue()); - ASSERT_TRUE(std::filesystem::exists(storage_directory / "databases" / "db3")); + // New flow doesn't make db named directories + ASSERT_FALSE(std::filesystem::exists(db_dir / "db3")); + const auto dirs_w_db3 = GetDirs(db_dir); + ASSERT_EQ(dirs_w_db3.size(), dirs.size() + 1); ASSERT_TRUE(db3.GetValue()->storage() != nullptr); ASSERT_TRUE(db3.GetValue()->streams() != nullptr); ASSERT_TRUE(db3.GetValue()->trigger_store() != nullptr); @@ -156,16 +179,16 @@ TEST(DBMS_Handler, Delete) { auto db1_acc = dbms.Get("db1"); // Holds access to database { - auto del = dbms.Delete(memgraph::dbms::kDefaultDB); + auto del = dbms.TryDelete(memgraph::dbms::kDefaultDB); ASSERT_TRUE(del.HasError() && del.GetError() == memgraph::dbms::DeleteError::DEFAULT_DB); } { - auto del = dbms.Delete("non-existent"); + auto del = dbms.TryDelete("non-existent"); ASSERT_TRUE(del.HasError() && del.GetError() == memgraph::dbms::DeleteError::NON_EXISTENT); } { // db1_acc is using db1 - auto del = dbms.Delete("db1"); + auto del = dbms.TryDelete("db1"); ASSERT_TRUE(del.HasError()); ASSERT_TRUE(del.GetError() == memgraph::dbms::DeleteError::USING); } @@ -173,15 +196,17 @@ TEST(DBMS_Handler, Delete) { // Reset db1_acc (releases access) so delete will succeed db1_acc.reset(); ASSERT_FALSE(db1_acc); - auto del = dbms.Delete("db1"); + auto del = dbms.TryDelete("db1"); ASSERT_FALSE(del.HasError()) << (int)del.GetError(); - auto del2 = dbms.Delete("db1"); + auto del2 = dbms.TryDelete("db1"); ASSERT_TRUE(del2.HasError() && del2.GetError() == memgraph::dbms::DeleteError::NON_EXISTENT); } { - auto del = dbms.Delete("db3"); + const auto dirs = GetDirs(db_dir); + auto del = dbms.TryDelete("db3"); ASSERT_FALSE(del.HasError()); - ASSERT_FALSE(std::filesystem::exists(storage_directory / "databases" / "db3")); + const auto dirs_wo_db3 = GetDirs(db_dir); + ASSERT_EQ(dirs_wo_db3.size(), dirs.size() - 1); } } diff --git a/tests/unit/dbms_handler_community.cpp b/tests/unit/dbms_handler_community.cpp index 860f70ba0..4a47e018b 100644 --- a/tests/unit/dbms_handler_community.cpp +++ b/tests/unit/dbms_handler_community.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -51,7 +51,7 @@ class TestEnvironment : public ::testing::Environment { } auth = std::make_unique<memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock>>( - storage_directory / "auth"); + storage_directory / "auth", memgraph::auth::Auth::Config{/* default */}); ptr_ = std::make_unique<memgraph::dbms::DbmsHandler>(storage_conf); } @@ -90,9 +90,9 @@ TEST(DBMS_Handler, Get) { ASSERT_TRUE(default_db->streams() != nullptr); ASSERT_TRUE(default_db->trigger_store() != nullptr); ASSERT_TRUE(default_db->thread_pool() != nullptr); - ASSERT_EQ(default_db->storage()->id(), memgraph::dbms::kDefaultDB); + ASSERT_EQ(default_db->storage()->name(), memgraph::dbms::kDefaultDB); auto conf = storage_conf; - conf.name = memgraph::dbms::kDefaultDB; + conf.salient.name = memgraph::dbms::kDefaultDB; ASSERT_EQ(default_db->storage()->config_, conf); } diff --git a/tests/unit/interpreter_faker.hpp b/tests/unit/interpreter_faker.hpp index 5823c6a87..3b6075911 100644 --- a/tests/unit/interpreter_faker.hpp +++ b/tests/unit/interpreter_faker.hpp @@ -21,8 +21,9 @@ struct InterpreterFaker { } auto Prepare(const std::string &query, const std::map<std::string, memgraph::storage::PropertyValue> ¶ms = {}) { - ResultStreamFaker stream(interpreter.current_db_.db_acc_->get()->storage()); const auto [header, _1, qid, _2] = interpreter.Prepare(query, params, {}); + auto &db = interpreter.current_db_.db_acc_; + ResultStreamFaker stream(db ? db->get()->storage() : nullptr); stream.Header(header); return std::make_pair(std::move(stream), qid); } diff --git a/tests/unit/multi_tenancy.cpp b/tests/unit/multi_tenancy.cpp new file mode 100644 index 000000000..59364776a --- /dev/null +++ b/tests/unit/multi_tenancy.cpp @@ -0,0 +1,377 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include <algorithm> +#include <cstdlib> +#include <filesystem> +#include <thread> + +#include "communication/bolt/v1/value.hpp" +#include "communication/result_stream_faker.hpp" +#include "csv/parsing.hpp" +#include "dbms/dbms_handler.hpp" +#include "disk_test_utils.hpp" +#include "flags/run_time_configurable.hpp" +#include "glue/communication.hpp" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "interpreter_faker.hpp" +#include "license/license.hpp" +#include "query/auth_checker.hpp" +#include "query/config.hpp" +#include "query/exceptions.hpp" +#include "query/interpreter.hpp" +#include "query/interpreter_context.hpp" +#include "query/metadata.hpp" +#include "query/stream.hpp" +#include "query/typed_value.hpp" +#include "query_common.hpp" +#include "replication/state.hpp" +#include "storage/v2/inmemory/storage.hpp" +#include "storage/v2/isolation_level.hpp" +#include "storage/v2/property_value.hpp" +#include "storage/v2/storage_mode.hpp" +#include "utils/logging.hpp" +#include "utils/lru_cache.hpp" +#include "utils/synchronized.hpp" + +namespace { +std::set<std::string> GetDirs(auto path) { + std::set<std::string> dirs; + // Clean the unused directories + for (const auto &entry : std::filesystem::directory_iterator(path)) { + const auto &name = entry.path().filename().string(); + if (entry.is_directory() && !name.empty() && name.front() != '.') { + dirs.emplace(name); + } + } + return dirs; +} + +auto RunMtQuery(auto &interpreter, const std::string &query, std::string_view res) { + auto [stream, qid] = interpreter.Prepare(query); + ASSERT_EQ(stream.GetHeader().size(), 1U); + EXPECT_EQ(stream.GetHeader()[0], "STATUS"); + interpreter.Pull(&stream, 1); + ASSERT_EQ(stream.GetSummary().count("has_more"), 1); + ASSERT_FALSE(stream.GetSummary().at("has_more").ValueBool()); + ASSERT_EQ(stream.GetResults()[0].size(), 1U); + ASSERT_EQ(stream.GetResults()[0][0].ValueString(), res); +} + +auto RunQuery(auto &interpreter, const std::string &query) { + auto [stream, qid] = interpreter.Prepare(query); + interpreter.Pull(&stream, 1); + return stream.GetResults(); +} + +void UseDatabase(auto &interpreter, const std::string &name, std::string_view res) { + RunMtQuery(interpreter, "USE DATABASE " + name, res); +} + +void DropDatabase(auto &interpreter, const std::string &name, std::string_view res) { + RunMtQuery(interpreter, "DROP DATABASE " + name, res); +} +} // namespace + +class MultiTenantTest : public ::testing::Test { + public: + std::filesystem::path data_directory = std::filesystem::temp_directory_path() / "MG_tests_unit_multi_tenancy"; + + MultiTenantTest() = default; + + memgraph::storage::Config config{ + [&]() { + memgraph::storage::Config config{}; + UpdatePaths(config, data_directory); + return config; + }() // iile + }; + + struct MinMemgraph { + explicit MinMemgraph(const memgraph::storage::Config &conf) + : auth{conf.durability.storage_directory / "auth", memgraph::auth::Auth::Config{/* default */}}, + dbms{conf, &auth, true}, + interpreter_context{{}, &dbms, &dbms.ReplicationState()} { + memgraph::utils::global_settings.Initialize(conf.durability.storage_directory / "settings"); + memgraph::license::RegisterLicenseSettings(memgraph::license::global_license_checker, + memgraph::utils::global_settings); + memgraph::flags::run_time::Initialize(); + memgraph::license::global_license_checker.CheckEnvLicense(); + } + + ~MinMemgraph() { memgraph::utils::global_settings.Finalize(); } + + auto NewInterpreter() { return InterpreterFaker{&interpreter_context, dbms.Get()}; } + + memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> auth; + memgraph::dbms::DbmsHandler dbms; + memgraph::query::InterpreterContext interpreter_context; + }; + + void SetUp() override { + TearDown(); + min_mg.emplace(config); + } + + void TearDown() override { + min_mg.reset(); + if (std::filesystem::exists(data_directory)) std::filesystem::remove_all(data_directory); + } + + auto NewInterpreter() { return min_mg->NewInterpreter(); } + + auto &DBMS() { return min_mg->dbms; } + + std::optional<MinMemgraph> min_mg; +}; + +TEST_F(MultiTenantTest, SimpleCreateDrop) { + // 1) Create multiple interpreters with the default db + // 2) Create multiple databases using both + // 3) Drop databases while the other is using + + // 1 + auto interpreter1 = this->NewInterpreter(); + auto interpreter2 = this->NewInterpreter(); + + // 2 + auto create = [&](auto &interpreter, const std::string &name, bool success) { + RunMtQuery(interpreter, "CREATE DATABASE " + name, + success ? ("Successfully created database " + name) : (name + " already exists.")); + }; + + create(interpreter1, "db1", true); + create(interpreter1, "db1", false); + create(interpreter2, "db1", false); + create(interpreter2, "db2", true); + create(interpreter1, "db2", false); + create(interpreter2, "db3", true); + create(interpreter2, "db4", true); + + // 3 + UseDatabase(interpreter1, "db2", "Using db2"); + UseDatabase(interpreter1, "db2", "Already using db2"); + UseDatabase(interpreter2, "db2", "Using db2"); + UseDatabase(interpreter1, "db4", "Using db4"); + + ASSERT_THROW(DropDatabase(interpreter1, memgraph::dbms::kDefaultDB.data(), ""), + memgraph::query::QueryRuntimeException); // default db + + DropDatabase(interpreter1, "db1", "Successfully deleted db1"); + ASSERT_THROW(DropDatabase(interpreter2, "db1", ""), memgraph::query::QueryRuntimeException); // No db1 + ASSERT_THROW(DropDatabase(interpreter1, "db1", ""), memgraph::query::QueryRuntimeException); // No db1 + + ASSERT_THROW(DropDatabase(interpreter1, "db2", ""), memgraph::query::QueryRuntimeException); // i2 using db2 + ASSERT_THROW(DropDatabase(interpreter1, "db4", ""), memgraph::query::QueryRuntimeException); // i1 using db4 +} + +TEST_F(MultiTenantTest, DbmsNewTryDelete) { + // 1) Create multiple interpreters with the default db + // 2) Create multiple databases using dbms + // 3) Try delete databases while the interpreters are using them + + // 1 + auto interpreter1 = this->NewInterpreter(); + auto interpreter2 = this->NewInterpreter(); + + // 2 + auto &dbms = DBMS(); + ASSERT_FALSE(dbms.New("db1").HasError()); + ASSERT_FALSE(dbms.New("db2").HasError()); + ASSERT_FALSE(dbms.New("db3").HasError()); + ASSERT_FALSE(dbms.New("db4").HasError()); + + // 3 + UseDatabase(interpreter2, "db2", "Using db2"); + UseDatabase(interpreter1, "db4", "Using db4"); + + ASSERT_FALSE(dbms.TryDelete("db1").HasError()); + ASSERT_TRUE(dbms.TryDelete("db2").HasError()); + ASSERT_FALSE(dbms.TryDelete("db3").HasError()); + ASSERT_TRUE(dbms.TryDelete("db4").HasError()); +} + +TEST_F(MultiTenantTest, DbmsUpdate) { + // 1) Create multiple interpreters with the default db + // 2) Create multiple databases using dbms + // 3) Try to update databases + + auto &dbms = DBMS(); + auto interpreter1 = this->NewInterpreter(); + + // Update clean default db + auto default_db = dbms.Get(); + const auto old_uuid = default_db->config().salient.uuid; + const memgraph::utils::UUID new_uuid{/* random */}; + const memgraph::storage::SalientConfig &config{.name = "memgraph", .uuid = new_uuid}; + auto new_default = dbms.Update(config); + ASSERT_TRUE(new_default.HasValue()); + ASSERT_NE(new_uuid, old_uuid); + ASSERT_EQ(default_db->storage(), new_default.GetValue()->storage()); + + // Add node to default + RunQuery(interpreter1, "CREATE (:Node)"); + + // Fail to update dirty default db + const memgraph::storage::SalientConfig &failing_config{.name = "memgraph", .uuid = {}}; + auto failed_update = dbms.Update(failing_config); + ASSERT_TRUE(failed_update.HasError()); + + // Succeed when updating with the same config + auto same_update = dbms.Update(config); + ASSERT_TRUE(same_update.HasValue()); + ASSERT_EQ(new_default.GetValue()->storage(), same_update.GetValue()->storage()); + + // Create new db + auto db1 = dbms.New("db1"); + ASSERT_FALSE(db1.HasError()); + RunMtQuery(interpreter1, "USE DATABASE db1", "Using db1"); + RunQuery(interpreter1, "CREATE (:NewNode)"); + RunQuery(interpreter1, "CREATE (:NewNode)"); + const auto db1_config_old = db1.GetValue()->config(); + + // Begin a transaction on db1 + auto interpreter2 = this->NewInterpreter(); + RunMtQuery(interpreter2, "USE DATABASE db1", "Using db1"); + ASSERT_EQ(RunQuery(interpreter2, "SHOW DATABASE")[0][0].ValueString(), "db1"); + RunQuery(interpreter2, "BEGIN"); + + // Update and check the new db in clean + auto interpreter3 = this->NewInterpreter(); + const memgraph::storage::SalientConfig &db1_config_new{.name = "db1", .uuid = {}}; + auto new_db1 = dbms.Update(db1_config_new); + ASSERT_TRUE(new_db1.HasValue()); + ASSERT_NE(db1_config_new.uuid, db1_config_old.salient.uuid); + RunMtQuery(interpreter3, "USE DATABASE db1", "Using db1"); + ASSERT_EQ(RunQuery(interpreter3, "MATCH(n) RETURN count(*)")[0][0].ValueInt(), 0); + + // Check that the interpreter1 is still valid, but lacking a db + ASSERT_THROW(RunQuery(interpreter1, "CREATE (:Node)"), memgraph::query::DatabaseContextRequiredException); + + // Check that the interpreter2 is still valid and pointing to the old db1 (until commit) + RunQuery(interpreter2, "CREATE (:NewNode)"); + ASSERT_EQ(RunQuery(interpreter2, "MATCH(n) RETURN count(*)")[0][0].ValueInt(), 3); + RunQuery(interpreter2, "COMMIT"); + ASSERT_THROW(RunQuery(interpreter2, "MATCH(n) RETURN n"), memgraph::query::DatabaseContextRequiredException); +} + +TEST_F(MultiTenantTest, DbmsNewDelete) { + // 1) Create multiple interpreters with the default db + // 2) Create multiple databases using dbms + // 3) Defer delete databases while the interpreters are using them + // 4) Database should be a zombie until the using interpreter retries to query it + // 5) Check it is deleted from disk + + // 1 + auto interpreter1 = this->NewInterpreter(); + auto interpreter2 = this->NewInterpreter(); + + // 2 + auto &dbms = DBMS(); + ASSERT_FALSE(dbms.New("db1").HasError()); + ASSERT_FALSE(dbms.New("db2").HasError()); + ASSERT_FALSE(dbms.New("db3").HasError()); + ASSERT_FALSE(dbms.New("db4").HasError()); + + // 3 + UseDatabase(interpreter2, "db2", "Using db2"); + UseDatabase(interpreter1, "db4", "Using db4"); + + RunQuery(interpreter1, "CREATE (:Node{on:\"db4\"})"); + RunQuery(interpreter1, "CREATE (:Node{on:\"db4\"})"); + RunQuery(interpreter1, "CREATE (:Node{on:\"db4\"})"); + RunQuery(interpreter1, "CREATE (:Node{on:\"db4\"})"); + RunQuery(interpreter2, "CREATE (:Node{on:\"db2\"})"); + RunQuery(interpreter2, "CREATE (:Node{on:\"db2\"})"); + + ASSERT_FALSE(dbms.Delete("db1").HasError()); + ASSERT_FALSE(dbms.Delete("db2").HasError()); + ASSERT_FALSE(dbms.Delete("db3").HasError()); + ASSERT_FALSE(dbms.Delete("db4").HasError()); + + // 4 + ASSERT_EQ(dbms.All().size(), 1); + ASSERT_EQ(GetDirs(data_directory / "databases").size(), 3); // All used databases remain on disk, but unusable + ASSERT_THROW(RunQuery(interpreter1, "MATCH(:Node{on:db4}) RETURN count(*)"), + memgraph::query::DatabaseContextRequiredException); + ASSERT_THROW(RunQuery(interpreter2, "MATCH(:Node{on:db2}) RETURN count(*)"), + memgraph::query::DatabaseContextRequiredException); + + // 5 + using namespace std::chrono_literals; + std::this_thread::sleep_for(100ms); // Wait for the filesystem to be updated + ASSERT_EQ(GetDirs(data_directory / "databases").size(), 1); // Databases deleted from disk + ASSERT_THROW(RunQuery(interpreter1, "MATCH(n) RETURN n"), memgraph::query::DatabaseContextRequiredException); + ASSERT_THROW(RunQuery(interpreter2, "MATCH(n) RETURN n"), memgraph::query::DatabaseContextRequiredException); +} + +TEST_F(MultiTenantTest, DbmsNewDeleteWTx) { + // 1) Create multiple interpreters with the default db + // 2) Create multiple databases using dbms + // 3) Defer delete databases while the interpreters are using them + // 4) Interpreters that had an open transaction before should still be working + // 5) New transactions on deleted databases should throw + // 6) Switching databases should still be possible + + // 1 + auto interpreter1 = this->NewInterpreter(); + auto interpreter2 = this->NewInterpreter(); + + // 2 + auto &dbms = DBMS(); + ASSERT_FALSE(dbms.New("db1").HasError()); + ASSERT_FALSE(dbms.New("db2").HasError()); + ASSERT_FALSE(dbms.New("db3").HasError()); + ASSERT_FALSE(dbms.New("db4").HasError()); + + // 3 + UseDatabase(interpreter2, "db2", "Using db2"); + UseDatabase(interpreter1, "db4", "Using db4"); + + RunQuery(interpreter1, "CREATE (:Node{on:\"db4\"})"); + RunQuery(interpreter1, "CREATE (:Node{on:\"db4\"})"); + RunQuery(interpreter1, "CREATE (:Node{on:\"db4\"})"); + RunQuery(interpreter1, "CREATE (:Node{on:\"db4\"})"); + RunQuery(interpreter2, "CREATE (:Node{on:\"db2\"})"); + RunQuery(interpreter2, "CREATE (:Node{on:\"db2\"})"); + + RunQuery(interpreter1, "BEGIN"); + RunQuery(interpreter2, "BEGIN"); + + ASSERT_FALSE(dbms.Delete("db1").HasError()); + ASSERT_FALSE(dbms.Delete("db2").HasError()); + ASSERT_FALSE(dbms.Delete("db3").HasError()); + ASSERT_FALSE(dbms.Delete("db4").HasError()); + + // 4 + ASSERT_EQ(dbms.All().size(), 1); + ASSERT_EQ(GetDirs(data_directory / "databases").size(), 3); // All used databases remain on disk, and usable + ASSERT_EQ(RunQuery(interpreter1, "MATCH(:Node{on:\"db4\"}) RETURN count(*)")[0][0].ValueInt(), 4); + ASSERT_EQ(RunQuery(interpreter2, "MATCH(:Node{on:\"db2\"}) RETURN count(*)")[0][0].ValueInt(), 2); + RunQuery(interpreter1, "MATCH(n:Node{on:\"db4\"}) DELETE n"); + RunQuery(interpreter2, "CREATE(:Node{on:\"db2\"})"); + ASSERT_EQ(RunQuery(interpreter1, "MATCH(:Node{on:\"db4\"}) RETURN count(*)")[0][0].ValueInt(), 0); + ASSERT_EQ(RunQuery(interpreter2, "MATCH(:Node{on:\"db2\"}) RETURN count(*)")[0][0].ValueInt(), 3); + RunQuery(interpreter1, "COMMIT"); + RunQuery(interpreter2, "COMMIT"); + + // 5 + using namespace std::chrono_literals; + std::this_thread::sleep_for(100ms); // Wait for the filesystem to be updated + ASSERT_EQ(GetDirs(data_directory / "databases").size(), 1); // Only the active databases remain + ASSERT_THROW(RunQuery(interpreter1, "MATCH(n) RETURN n"), memgraph::query::DatabaseContextRequiredException); + ASSERT_THROW(RunQuery(interpreter2, "MATCH(n) RETURN n"), memgraph::query::DatabaseContextRequiredException); + + // 6 + UseDatabase(interpreter2, memgraph::dbms::kDefaultDB.data(), "Using memgraph"); + UseDatabase(interpreter1, memgraph::dbms::kDefaultDB.data(), "Using memgraph"); +} diff --git a/tests/unit/plan_pretty_print.cpp b/tests/unit/plan_pretty_print.cpp index 97f1355cb..ef2395931 100644 --- a/tests/unit/plan_pretty_print.cpp +++ b/tests/unit/plan_pretty_print.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -43,7 +43,7 @@ class PrintToJsonTest : public ::testing::Test { PrintToJsonTest() : config(disk_test_utils::GenerateOnDiskConfig(testSuite)), db(new StorageType(config)), - dba_storage(db->Access()), + dba_storage(db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN)), dba(dba_storage.get()) {} ~PrintToJsonTest() override { diff --git a/tests/unit/query_cost_estimator.cpp b/tests/unit/query_cost_estimator.cpp index f3d6b3864..702b6e759 100644 --- a/tests/unit/query_cost_estimator.cpp +++ b/tests/unit/query_cost_estimator.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -17,12 +17,13 @@ #include "query/frontend/semantic/symbol_table.hpp" #include "query/plan/cost_estimator.hpp" #include "query/plan/operator.hpp" +#include "query/plan/rewrite/index_lookup.hpp" #include "storage/v2/inmemory/storage.hpp" #include "storage/v2/storage.hpp" using namespace memgraph::query; using namespace memgraph::query::plan; - +using memgraph::replication_coordination_glue::ReplicationRole; using CardParam = CostEstimator<memgraph::query::DbAccessor>::CardParam; using CostParam = CostEstimator<memgraph::query::DbAccessor>::CostParam; using MiscParam = CostEstimator<memgraph::query::DbAccessor>::MiscParam; @@ -51,16 +52,16 @@ class QueryCostEstimator : public ::testing::Test { void SetUp() override { { - auto unique_acc = db->UniqueAccess(); + auto unique_acc = db->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateIndex(label).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto unique_acc = db->UniqueAccess(); + auto unique_acc = db->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateIndex(label, property).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } - storage_dba.emplace(db->Access()); + storage_dba.emplace(db->Access(ReplicationRole::MAIN)); dba.emplace(storage_dba->get()); } @@ -83,7 +84,8 @@ class QueryCostEstimator : public ::testing::Test { } auto Cost() { - CostEstimator<memgraph::query::DbAccessor> cost_estimator(&*dba, symbol_table_, parameters_); + CostEstimator<memgraph::query::DbAccessor> cost_estimator(&*dba, symbol_table_, parameters_, + memgraph::query::plan::IndexHints()); last_op_->Accept(cost_estimator); return cost_estimator.cost(); } diff --git a/tests/unit/query_dump.cpp b/tests/unit/query_dump.cpp index 63019ad28..23eab17e0 100644 --- a/tests/unit/query_dump.cpp +++ b/tests/unit/query_dump.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -9,10 +9,12 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. +#include <gtest/gtest-typed-test.h> #include <gtest/gtest.h> #include <filesystem> #include <map> +#include <optional> #include <set> #include <vector> @@ -24,6 +26,8 @@ #include "query/interpreter.hpp" #include "query/interpreter_context.hpp" #include "query/stream/streams.hpp" +#include "query/trigger.hpp" +#include "query/trigger_context.hpp" #include "query/typed_value.hpp" #include "storage/v2/config.hpp" #include "storage/v2/disk/storage.hpp" @@ -137,7 +141,7 @@ DatabaseState GetState(memgraph::storage::Storage *db) { // Capture all vertices std::map<memgraph::storage::Gid, int64_t> gid_mapping; std::set<DatabaseState::Vertex> vertices; - auto dba = db->Access(); + auto dba = db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN); for (const auto &vertex : dba->Vertices(memgraph::storage::View::NEW)) { std::set<std::string, std::less<>> labels; auto maybe_labels = vertex.Labels(memgraph::storage::View::NEW); @@ -263,7 +267,7 @@ memgraph::storage::EdgeAccessor CreateEdge(memgraph::storage::Storage::Accessor } template <class... TArgs> -void VerifyQueries(const std::vector<std::vector<memgraph::communication::bolt::Value>> &results, TArgs &&...args) { +void VerifyQueries(const std::vector<std::vector<memgraph::communication::bolt::Value>> &results, TArgs &&... args) { std::vector<std::string> expected{std::forward<TArgs>(args)...}; std::vector<std::string> got; got.reserve(results.size()); @@ -329,9 +333,9 @@ TYPED_TEST(DumpTest, EmptyGraph) { ResultStreamFaker stream(this->db->storage()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = this->db->storage()->Access(); + auto acc = this->db->Access(); memgraph::query::DbAccessor dba(acc.get()); - memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); + memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db); } ASSERT_EQ(stream.GetResults().size(), 0); } @@ -339,7 +343,7 @@ TYPED_TEST(DumpTest, EmptyGraph) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(DumpTest, SingleVertex) { { - auto dba = this->db->storage()->Access(); + auto dba = this->db->Access(); CreateVertex(dba.get(), {}, {}, false); ASSERT_FALSE(dba->Commit().HasError()); } @@ -348,9 +352,9 @@ TYPED_TEST(DumpTest, SingleVertex) { ResultStreamFaker stream(this->db->storage()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = this->db->storage()->Access(); + auto acc = this->db->Access(); memgraph::query::DbAccessor dba(acc.get()); - memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); + memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});", kDropInternalIndex, kRemoveInternalLabelProperty); @@ -360,7 +364,7 @@ TYPED_TEST(DumpTest, SingleVertex) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(DumpTest, VertexWithSingleLabel) { { - auto dba = this->db->storage()->Access(); + auto dba = this->db->Access(); CreateVertex(dba.get(), {"Label1"}, {}, false); ASSERT_FALSE(dba->Commit().HasError()); } @@ -369,9 +373,9 @@ TYPED_TEST(DumpTest, VertexWithSingleLabel) { ResultStreamFaker stream(this->db->storage()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = this->db->storage()->Access(); + auto acc = this->db->Access(); memgraph::query::DbAccessor dba(acc.get()); - memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); + memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__:`Label1` {__mg_id__: 0});", kDropInternalIndex, kRemoveInternalLabelProperty); @@ -381,7 +385,7 @@ TYPED_TEST(DumpTest, VertexWithSingleLabel) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(DumpTest, VertexWithMultipleLabels) { { - auto dba = this->db->storage()->Access(); + auto dba = this->db->Access(); CreateVertex(dba.get(), {"Label1", "Label 2"}, {}, false); ASSERT_FALSE(dba->Commit().HasError()); } @@ -390,9 +394,9 @@ TYPED_TEST(DumpTest, VertexWithMultipleLabels) { ResultStreamFaker stream(this->db->storage()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = this->db->storage()->Access(); + auto acc = this->db->Access(); memgraph::query::DbAccessor dba(acc.get()); - memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); + memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__:`Label1`:`Label 2` {__mg_id__: 0});", kDropInternalIndex, @@ -403,7 +407,7 @@ TYPED_TEST(DumpTest, VertexWithMultipleLabels) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(DumpTest, VertexWithSingleProperty) { { - auto dba = this->db->storage()->Access(); + auto dba = this->db->Access(); CreateVertex(dba.get(), {}, {{"prop", memgraph::storage::PropertyValue(42)}}, false); ASSERT_FALSE(dba->Commit().HasError()); } @@ -412,9 +416,9 @@ TYPED_TEST(DumpTest, VertexWithSingleProperty) { ResultStreamFaker stream(this->db->storage()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = this->db->storage()->Access(); + auto acc = this->db->Access(); memgraph::query::DbAccessor dba(acc.get()); - memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); + memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0, `prop`: 42});", kDropInternalIndex, kRemoveInternalLabelProperty); @@ -424,7 +428,7 @@ TYPED_TEST(DumpTest, VertexWithSingleProperty) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(DumpTest, MultipleVertices) { { - auto dba = this->db->storage()->Access(); + auto dba = this->db->Access(); CreateVertex(dba.get(), {}, {}, false); CreateVertex(dba.get(), {}, {}, false); CreateVertex(dba.get(), {}, {}, false); @@ -435,9 +439,9 @@ TYPED_TEST(DumpTest, MultipleVertices) { ResultStreamFaker stream(this->db->storage()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = this->db->storage()->Access(); + auto acc = this->db->Access(); memgraph::query::DbAccessor dba(acc.get()); - memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); + memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});", "CREATE (:__mg_vertex__ {__mg_id__: 1});", "CREATE (:__mg_vertex__ {__mg_id__: 2});", @@ -447,7 +451,7 @@ TYPED_TEST(DumpTest, MultipleVertices) { TYPED_TEST(DumpTest, PropertyValue) { { - auto dba = this->db->storage()->Access(); + auto dba = this->db->Access(); auto null_value = memgraph::storage::PropertyValue(); auto int_value = memgraph::storage::PropertyValue(13); auto bool_value = memgraph::storage::PropertyValue(true); @@ -473,9 +477,9 @@ TYPED_TEST(DumpTest, PropertyValue) { ResultStreamFaker stream(this->db->storage()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = this->db->storage()->Access(); + auto acc = this->db->Access(); memgraph::query::DbAccessor dba(acc.get()); - memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); + memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0, `p1`: [{`prop 1`: 13, " @@ -489,7 +493,7 @@ TYPED_TEST(DumpTest, PropertyValue) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(DumpTest, SingleEdge) { { - auto dba = this->db->storage()->Access(); + auto dba = this->db->Access(); auto u = CreateVertex(dba.get(), {}, {}, false); auto v = CreateVertex(dba.get(), {}, {}, false); CreateEdge(dba.get(), &u, &v, "EdgeType", {}, false); @@ -500,9 +504,9 @@ TYPED_TEST(DumpTest, SingleEdge) { ResultStreamFaker stream(this->db->storage()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = this->db->storage()->Access(); + auto acc = this->db->Access(); memgraph::query::DbAccessor dba(acc.get()); - memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); + memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});", "CREATE (:__mg_vertex__ {__mg_id__: 1});", @@ -515,7 +519,7 @@ TYPED_TEST(DumpTest, SingleEdge) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(DumpTest, MultipleEdges) { { - auto dba = this->db->storage()->Access(); + auto dba = this->db->Access(); auto u = CreateVertex(dba.get(), {}, {}, false); auto v = CreateVertex(dba.get(), {}, {}, false); auto w = CreateVertex(dba.get(), {}, {}, false); @@ -529,9 +533,9 @@ TYPED_TEST(DumpTest, MultipleEdges) { ResultStreamFaker stream(this->db->storage()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = this->db->storage()->Access(); + auto acc = this->db->Access(); memgraph::query::DbAccessor dba(acc.get()); - memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); + memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});", "CREATE (:__mg_vertex__ {__mg_id__: 1});", "CREATE (:__mg_vertex__ {__mg_id__: 2});", @@ -548,7 +552,7 @@ TYPED_TEST(DumpTest, MultipleEdges) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(DumpTest, EdgeWithProperties) { { - auto dba = this->db->storage()->Access(); + auto dba = this->db->Access(); auto u = CreateVertex(dba.get(), {}, {}, false); auto v = CreateVertex(dba.get(), {}, {}, false); CreateEdge(dba.get(), &u, &v, "EdgeType", {{"prop", memgraph::storage::PropertyValue(13)}}, false); @@ -559,9 +563,9 @@ TYPED_TEST(DumpTest, EdgeWithProperties) { ResultStreamFaker stream(this->db->storage()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = this->db->storage()->Access(); + auto acc = this->db->Access(); memgraph::query::DbAccessor dba(acc.get()); - memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); + memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db); } VerifyQueries(stream.GetResults(), kCreateInternalIndex, "CREATE (:__mg_vertex__ {__mg_id__: 0});", "CREATE (:__mg_vertex__ {__mg_id__: 1});", @@ -574,7 +578,7 @@ TYPED_TEST(DumpTest, EdgeWithProperties) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(DumpTest, IndicesKeys) { { - auto dba = this->db->storage()->Access(); + auto dba = this->db->Access(); CreateVertex(dba.get(), {"Label1", "Label 2"}, {{"p", memgraph::storage::PropertyValue(1)}}, false); ASSERT_FALSE(dba->Commit().HasError()); } @@ -599,9 +603,9 @@ TYPED_TEST(DumpTest, IndicesKeys) { ResultStreamFaker stream(this->db->storage()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = this->db->storage()->Access(); + auto acc = this->db->Access(); memgraph::query::DbAccessor dba(acc.get()); - memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); + memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db); } VerifyQueries(stream.GetResults(), "CREATE INDEX ON :`Label1`(`prop`);", "CREATE INDEX ON :`Label 2`(`prop ```);", kCreateInternalIndex, "CREATE (:__mg_vertex__:`Label1`:`Label 2` {__mg_id__: 0, `p`: 1});", @@ -612,7 +616,7 @@ TYPED_TEST(DumpTest, IndicesKeys) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(DumpTest, ExistenceConstraints) { { - auto dba = this->db->storage()->Access(); + auto dba = this->db->Access(); CreateVertex(dba.get(), {"L`abel 1"}, {{"prop", memgraph::storage::PropertyValue(1)}}, false); ASSERT_FALSE(dba->Commit().HasError()); } @@ -628,9 +632,9 @@ TYPED_TEST(DumpTest, ExistenceConstraints) { ResultStreamFaker stream(this->db->storage()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = this->db->storage()->Access(); + auto acc = this->db->Access(); memgraph::query::DbAccessor dba(acc.get()); - memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); + memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db); } VerifyQueries(stream.GetResults(), "CREATE CONSTRAINT ON (u:`L``abel 1`) ASSERT EXISTS (u.`prop`);", kCreateInternalIndex, "CREATE (:__mg_vertex__:`L``abel 1` {__mg_id__: 0, `prop`: 1});", @@ -640,7 +644,7 @@ TYPED_TEST(DumpTest, ExistenceConstraints) { TYPED_TEST(DumpTest, UniqueConstraints) { { - auto dba = this->db->storage()->Access(); + auto dba = this->db->Access(); CreateVertex(dba.get(), {"Label"}, {{"prop", memgraph::storage::PropertyValue(1)}, {"prop2", memgraph::storage::PropertyValue(2)}}, false); @@ -663,9 +667,9 @@ TYPED_TEST(DumpTest, UniqueConstraints) { ResultStreamFaker stream(this->db->storage()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = this->db->storage()->Access(); + auto acc = this->db->Access(); memgraph::query::DbAccessor dba(acc.get()); - memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); + memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db); } VerifyQueries(stream.GetResults(), "CREATE CONSTRAINT ON (u:`Label`) ASSERT u.`prop`, u.`prop2` " @@ -682,7 +686,7 @@ TYPED_TEST(DumpTest, UniqueConstraints) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(DumpTest, CheckStateVertexWithMultipleProperties) { { - auto dba = this->db->storage()->Access(); + auto dba = this->db->Access(); std::map<std::string, memgraph::storage::PropertyValue> prop1 = { {"nested1", memgraph::storage::PropertyValue(1337)}, {"nested2", memgraph::storage::PropertyValue(3.14)}}; @@ -700,11 +704,13 @@ TYPED_TEST(DumpTest, CheckStateVertexWithMultipleProperties) { config.disk = disk_test_utils::GenerateOnDiskConfig("query-dump-s1").disk; config.force_on_disk = true; } - auto on_exit_s1 = memgraph::utils::OnScopeExit{[&]() { - if constexpr (std::is_same_v<TypeParam, memgraph::storage::DiskStorage>) { + auto clean_up_s1 = memgraph::utils::OnScopeExit{[&] { + if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) { disk_test_utils::RemoveRocksDbDirs("query-dump-s1"); } + std::filesystem::remove_all(config.durability.storage_directory); }}; + memgraph::replication::ReplicationState repl_state(ReplicationStateRootPath(config)); memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk(config, repl_state); @@ -722,9 +728,9 @@ TYPED_TEST(DumpTest, CheckStateVertexWithMultipleProperties) { ResultStreamFaker stream(this->db->storage()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = this->db->storage()->Access(); + auto acc = this->db->Access(); memgraph::query::DbAccessor dba(acc.get()); - memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); + memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db); } const auto &results = stream.GetResults(); ASSERT_GE(results.size(), 1); @@ -739,7 +745,7 @@ TYPED_TEST(DumpTest, CheckStateVertexWithMultipleProperties) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(DumpTest, CheckStateSimpleGraph) { { - auto dba = this->db->storage()->Access(); + auto dba = this->db->Access(); auto u = CreateVertex(dba.get(), {"Person"}, {{"name", memgraph::storage::PropertyValue("Ivan")}}); auto v = CreateVertex(dba.get(), {"Person"}, {{"name", memgraph::storage::PropertyValue("Josko")}}); auto w = CreateVertex( @@ -819,11 +825,13 @@ TYPED_TEST(DumpTest, CheckStateSimpleGraph) { config.disk = disk_test_utils::GenerateOnDiskConfig("query-dump-s2").disk; config.force_on_disk = true; } - auto on_exit_s2 = memgraph::utils::OnScopeExit{[&]() { - if constexpr (std::is_same_v<TypeParam, memgraph::storage::DiskStorage>) { + auto clean_up_s2 = memgraph::utils::OnScopeExit{[&] { + if (std::is_same<TypeParam, memgraph::storage::DiskStorage>::value) { disk_test_utils::RemoveRocksDbDirs("query-dump-s2"); } + std::filesystem::remove_all(config.durability.storage_directory); }}; + memgraph::replication::ReplicationState repl_state{ReplicationStateRootPath(config)}; memgraph::utils::Gatekeeper<memgraph::dbms::Database> db_gk{config, repl_state}; auto db_acc_opt = db_gk.access(); @@ -839,9 +847,9 @@ TYPED_TEST(DumpTest, CheckStateSimpleGraph) { ResultStreamFaker stream(this->db->storage()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); { - auto acc = this->db->storage()->Access(); + auto acc = this->db->Access(); memgraph::query::DbAccessor dba(acc.get()); - memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream); + memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db); } const auto &results = stream.GetResults(); // Indices and constraints are 4 queries and there must be at least one more @@ -862,7 +870,7 @@ TYPED_TEST(DumpTest, CheckStateSimpleGraph) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(DumpTest, ExecuteDumpDatabase) { { - auto dba = this->db->storage()->Access(); + auto dba = this->db->Access(); CreateVertex(dba.get(), {}, {}, false); ASSERT_FALSE(dba->Commit().HasError()); } @@ -925,7 +933,7 @@ TYPED_TEST(DumpTest, ExecuteDumpDatabaseInMulticommandTransaction) { // Create the vertex. { - auto dba = this->db->storage()->Access(); + auto dba = this->db->Access(); CreateVertex(dba.get(), {}, {}, false); ASSERT_FALSE(dba->Commit().HasError()); } @@ -1023,7 +1031,7 @@ TYPED_TEST(DumpTest, MultiplePartialPulls) { ASSERT_FALSE(unique_acc->Commit().HasError()); } - auto dba = this->db->storage()->Access(); + auto dba = this->db->Access(); auto p1 = CreateVertex(dba.get(), {"PERSON"}, {{"name", memgraph::storage::PropertyValue("Person1")}, {"surname", memgraph::storage::PropertyValue("Unique1")}}, @@ -1053,10 +1061,10 @@ TYPED_TEST(DumpTest, MultiplePartialPulls) { ResultStreamFaker stream(this->db->storage()); memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); - auto acc = this->db->storage()->Access(); + auto acc = this->db->Access(); memgraph::query::DbAccessor dba(acc.get()); - memgraph::query::PullPlanDump pullPlan{&dba}; + memgraph::query::PullPlanDump pullPlan{&dba, this->db}; auto offset_index = 0U; auto check_next = [&](const std::string &expected_row) mutable { @@ -1095,3 +1103,30 @@ TYPED_TEST(DumpTest, MultiplePartialPulls) { check_next(kDropInternalIndex); check_next(kRemoveInternalLabelProperty); } + +TYPED_TEST(DumpTest, DumpDatabaseWithTriggers) { + auto acc = this->db->storage()->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN); + memgraph::query::DbAccessor dba(acc.get()); + { + auto trigger_store = this->db.get()->trigger_store(); + const std::string trigger_name = "test_trigger"; + const std::string trigger_statement = "UNWIND createdVertices AS newNodes SET newNodes.created = timestamp()"; + memgraph::query::TriggerEventType trigger_event_type = memgraph::query::TriggerEventType::VERTEX_CREATE; + memgraph::query::TriggerPhase trigger_phase = memgraph::query::TriggerPhase::AFTER_COMMIT; + memgraph::utils::SkipList<memgraph::query::QueryCacheEntry> ast_cache; + memgraph::query::AllowEverythingAuthChecker auth_checker; + memgraph::query::InterpreterConfig::Query query_config; + memgraph::query::DbAccessor dba(acc.get()); + const std::map<std::string, memgraph::storage::PropertyValue> props; + trigger_store->AddTrigger(trigger_name, trigger_statement, props, trigger_event_type, trigger_phase, &ast_cache, + &dba, query_config, std::nullopt, &auth_checker); + } + { + ResultStreamFaker stream(this->db->storage()); + memgraph::query::AnyStream query_stream(&stream, memgraph::utils::NewDeleteResource()); + { memgraph::query::DumpDatabaseToCypherQueries(&dba, &query_stream, this->db); } + VerifyQueries(stream.GetResults(), + "CREATE TRIGGER test_trigger ON () CREATE AFTER COMMIT EXECUTE UNWIND createdVertices AS newNodes " + "SET newNodes.created = timestamp();"); + } +} diff --git a/tests/unit/query_expression_evaluator.cpp b/tests/unit/query_expression_evaluator.cpp index c9786fe5e..c725d7e54 100644 --- a/tests/unit/query_expression_evaluator.cpp +++ b/tests/unit/query_expression_evaluator.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -67,7 +67,7 @@ class ExpressionEvaluatorTest : public ::testing::Test { ExpressionEvaluatorTest() : config(disk_test_utils::GenerateOnDiskConfig(testSuite)), db(new StorageType(config)), - storage_dba(db->Access()), + storage_dba(db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN)), dba(storage_dba.get()) {} ~ExpressionEvaluatorTest() override { @@ -2075,9 +2075,10 @@ TYPED_TEST(FunctionTest, ToStringInteger) { } TYPED_TEST(FunctionTest, ToStringDouble) { - EXPECT_EQ(this->EvaluateFunction("TOSTRING", -42.42).ValueString(), "-42.420000"); - EXPECT_EQ(this->EvaluateFunction("TOSTRING", 0.0).ValueString(), "0.000000"); - EXPECT_EQ(this->EvaluateFunction("TOSTRING", 238910.2313217).ValueString(), "238910.231322"); + EXPECT_EQ(this->EvaluateFunction("TOSTRING", -42.42).ValueString(), "-42.420000000000002"); + EXPECT_EQ(this->EvaluateFunction("TOSTRING", 0.0).ValueString(), "0"); + EXPECT_EQ(this->EvaluateFunction("TOSTRING", 238910.2313217).ValueString(), "238910.231321700004628"); + EXPECT_EQ(this->EvaluateFunction("TOSTRING", 238910.23132171234).ValueString(), "238910.231321712344652"); } TYPED_TEST(FunctionTest, ToStringBool) { diff --git a/tests/unit/query_hint_provider.cpp b/tests/unit/query_hint_provider.cpp index 9973bf2ca..4165ef9d2 100644 --- a/tests/unit/query_hint_provider.cpp +++ b/tests/unit/query_hint_provider.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -39,7 +39,7 @@ class HintProviderSuite : public ::testing::Test { int symbol_count = 0; void SetUp() { - storage_dba.emplace(db->Access()); + storage_dba.emplace(db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN)); dba.emplace(storage_dba->get()); } diff --git a/tests/unit/query_plan_accumulate_aggregate.cpp b/tests/unit/query_plan_accumulate_aggregate.cpp index b1e9a62d0..c8f1c30c9 100644 --- a/tests/unit/query_plan_accumulate_aggregate.cpp +++ b/tests/unit/query_plan_accumulate_aggregate.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -25,6 +25,8 @@ #include "storage/v2/disk/storage.hpp" #include "storage/v2/inmemory/storage.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; + using namespace memgraph::query; using namespace memgraph::query::plan; using memgraph::query::test_common::ToIntList; @@ -95,7 +97,7 @@ TYPED_TEST(QueryPlanTest, Accumulate) { this->db.reset(nullptr); this->CleanStorageDirs(); this->db = std::make_unique<TypeParam>(this->config); - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto prop = dba.NameToProperty("x"); @@ -148,7 +150,7 @@ TYPED_TEST(QueryPlanTest, AccumulateAdvance) { this->db.reset(); this->CleanStorageDirs(); this->db = std::make_unique<TypeParam>(this->config); - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; NodeCreationInfo node; @@ -167,7 +169,7 @@ TYPED_TEST(QueryPlanTest, AccumulateAdvance) { template <typename StorageType> class QueryPlanAggregateOps : public QueryPlanTest<StorageType> { protected: - std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access()}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access(ReplicationRole::MAIN)}; memgraph::query::DbAccessor dba{storage_dba.get()}; memgraph::storage::PropertyId prop = this->db->NameToProperty("prop"); @@ -308,7 +310,7 @@ TYPED_TEST(QueryPlanTest, AggregateGroupByValues) { // Tests that distinct groups are aggregated properly for values of all types. // Also test the "remember" part of the Aggregation API as final results are // obtained via a property lookup of a remembered node. - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // a vector of memgraph::storage::PropertyValue to be set as property values on vertices @@ -370,7 +372,7 @@ TYPED_TEST(QueryPlanTest, AggregateMultipleGroupBy) { // in this test we have 3 different properties that have different values // for different records and assert that we get the correct combination // of values in our groups - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto prop1 = dba.NameToProperty("prop1"); @@ -401,7 +403,7 @@ TYPED_TEST(QueryPlanTest, AggregateMultipleGroupBy) { } TYPED_TEST(QueryPlanTest, AggregateNoInput) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; @@ -424,7 +426,7 @@ TYPED_TEST(QueryPlanTest, AggregateCountEdgeCases) { // - 2 vertices in database, property set on one // - 2 vertices in database, property set on both - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto prop = dba.NameToProperty("prop"); @@ -476,7 +478,7 @@ TYPED_TEST(QueryPlanTest, AggregateFirstValueTypes) { // testing exceptions that get emitted by the first-value // type check - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto v1 = dba.InsertVertex(); @@ -528,7 +530,7 @@ TYPED_TEST(QueryPlanTest, AggregateTypes) { // does not check all combinations that can result in an exception // (that logic is defined and tested by TypedValue) - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto p1 = dba.NameToProperty("p1"); // has only string props @@ -581,7 +583,7 @@ TYPED_TEST(QueryPlanTest, AggregateTypes) { } TYPED_TEST(QueryPlanTest, Unwind) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; @@ -717,7 +719,7 @@ TYPED_TEST(QueryPlanTest, AggregateGroupByValuesWithDistinct) { // Tests that distinct groups are aggregated properly for values of all types. // Also test the "remember" part of the Aggregation API as final results are // obtained via a property lookup of a remembered node. - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // a vector of memgraph::storage::PropertyValue to be set as property values on vertices @@ -782,7 +784,7 @@ TYPED_TEST(QueryPlanTest, AggregateMultipleGroupByWithDistinct) { // in this test we have 3 different properties that have different values // for different records and assert that we get the correct combination // of values in our groups - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto prop1 = dba.NameToProperty("prop1"); @@ -814,7 +816,7 @@ TYPED_TEST(QueryPlanTest, AggregateMultipleGroupByWithDistinct) { } TYPED_TEST(QueryPlanTest, AggregateNoInputWithDistinct) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; @@ -837,7 +839,7 @@ TYPED_TEST(QueryPlanTest, AggregateCountEdgeCasesWithDistinct) { // - 2 vertices in database, property set on one // - 2 vertices in database, property set on both - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto prop = dba.NameToProperty("prop"); @@ -889,7 +891,7 @@ TYPED_TEST(QueryPlanTest, AggregateFirstValueTypesWithDistinct) { // testing exceptions that get emitted by the first-value // type check - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto v1 = dba.InsertVertex(); @@ -941,7 +943,7 @@ TYPED_TEST(QueryPlanTest, AggregateTypesWithDistinct) { // does not check all combinations that can result in an exception // (that logic is defined and tested by TypedValue) - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto p1 = dba.NameToProperty("p1"); // has only string props diff --git a/tests/unit/query_plan_bag_semantics.cpp b/tests/unit/query_plan_bag_semantics.cpp index 6b1c7ab64..4f3bd5256 100644 --- a/tests/unit/query_plan_bag_semantics.cpp +++ b/tests/unit/query_plan_bag_semantics.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -31,6 +31,7 @@ #include "query_plan_common.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; using namespace memgraph::query; using namespace memgraph::query::plan; @@ -53,7 +54,7 @@ using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgra TYPED_TEST_CASE(QueryPlanTest, StorageTypes); TYPED_TEST(QueryPlanTest, Skip) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; @@ -81,7 +82,7 @@ TYPED_TEST(QueryPlanTest, Skip) { } TYPED_TEST(QueryPlanTest, Limit) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; @@ -112,7 +113,7 @@ TYPED_TEST(QueryPlanTest, CreateLimit) { // CREATE (n), (m) // MATCH (n) CREATE (m) LIMIT 1 // in the end we need to have 3 vertices in the db - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); dba.InsertVertex(); dba.InsertVertex(); @@ -133,7 +134,7 @@ TYPED_TEST(QueryPlanTest, CreateLimit) { } TYPED_TEST(QueryPlanTest, OrderBy) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; auto prop = dba.NameToProperty("prop"); @@ -204,7 +205,7 @@ TYPED_TEST(QueryPlanTest, OrderBy) { } TYPED_TEST(QueryPlanTest, OrderByMultiple) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; @@ -256,7 +257,7 @@ TYPED_TEST(QueryPlanTest, OrderByMultiple) { } TYPED_TEST(QueryPlanTest, OrderByExceptions) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; auto prop = dba.NameToProperty("prop"); diff --git a/tests/unit/query_plan_create_set_remove_delete.cpp b/tests/unit/query_plan_create_set_remove_delete.cpp index d0127c7c6..1fa400940 100644 --- a/tests/unit/query_plan_create_set_remove_delete.cpp +++ b/tests/unit/query_plan_create_set_remove_delete.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -38,6 +38,7 @@ using namespace memgraph::query; using namespace memgraph::query::plan; +using memgraph::replication_coordination_glue::ReplicationRole; template <typename StorageType> class QueryPlanTest : public testing::Test { @@ -58,7 +59,7 @@ using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgra TYPED_TEST_CASE(QueryPlanTest, StorageTypes); TYPED_TEST(QueryPlanTest, CreateNodeWithAttributes) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); memgraph::storage::LabelId label = dba.NameToLabel("Person"); @@ -102,7 +103,7 @@ TYPED_TEST(QueryPlanTest, CreateNodeWithAttributes) { TYPED_TEST(QueryPlanTest, FineGrainedCreateNodeWithAttributes) { memgraph::license::global_license_checker.EnableTesting(); memgraph::query::SymbolTable symbol_table; - auto dba = this->db->Access(); + auto dba = this->db->Access(ReplicationRole::MAIN); DbAccessor execution_dba(dba.get()); const auto label = dba->NameToLabel("label1"); const auto property = memgraph::storage::PropertyId::FromInt(1); @@ -142,7 +143,7 @@ TYPED_TEST(QueryPlanTest, FineGrainedCreateNodeWithAttributes) { TYPED_TEST(QueryPlanTest, CreateReturn) { // test CREATE (n:Person {age: 42}) RETURN n, n.age - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); memgraph::storage::LabelId label = dba.NameToLabel("Person"); @@ -182,7 +183,7 @@ TYPED_TEST(QueryPlanTest, FineGrainedCreateReturn) { memgraph::license::global_license_checker.EnableTesting(); // test CREATE (n:Person {age: 42}) RETURN n, n.age - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); const auto label = dba.NameToLabel("label"); @@ -237,7 +238,7 @@ TYPED_TEST(QueryPlanTest, FineGrainedCreateReturn) { #endif TYPED_TEST(QueryPlanTest, CreateExpand) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); memgraph::storage::LabelId label_node_1 = dba.NameToLabel("Node1"); @@ -315,7 +316,7 @@ TYPED_TEST(QueryPlanTest, CreateExpand) { template <typename StorageType> class CreateExpandWithAuthFixture : public QueryPlanTest<StorageType> { protected: - std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access()}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access(ReplicationRole::MAIN)}; memgraph::query::DbAccessor dba{storage_dba.get()}; SymbolTable symbol_table; @@ -445,7 +446,7 @@ TYPED_TEST(CreateExpandWithAuthFixture, CreateExpandWithCycleWithEverythingGrant } TYPED_TEST(QueryPlanTest, MatchCreateNode) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // add three nodes we'll match and expand-create from @@ -473,7 +474,7 @@ TYPED_TEST(QueryPlanTest, MatchCreateNode) { template <typename StorageType> class MatchCreateNodeWithAuthFixture : public QueryPlanTest<StorageType> { protected: - std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access()}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access(ReplicationRole::MAIN)}; memgraph::query::DbAccessor dba{storage_dba.get()}; SymbolTable symbol_table; @@ -553,7 +554,7 @@ TYPED_TEST(MatchCreateNodeWithAuthFixture, MatchCreateWithOneLabelDeniedThrows) #endif TYPED_TEST(QueryPlanTest, MatchCreateExpand) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // add three nodes we'll match and expand-create from @@ -601,7 +602,7 @@ TYPED_TEST(QueryPlanTest, MatchCreateExpand) { template <typename StorageType> class MatchCreateExpandWithAuthFixture : public QueryPlanTest<StorageType> { protected: - std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access()}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access(ReplicationRole::MAIN)}; memgraph::query::DbAccessor dba{storage_dba.get()}; SymbolTable symbol_table; @@ -746,7 +747,7 @@ TYPED_TEST(MatchCreateExpandWithAuthFixture, MatchCreateExpandWithCycleExecutesW #endif TYPED_TEST(QueryPlanTest, Delete) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // make a fully-connected (one-direction, no cycles) with 4 nodes @@ -818,7 +819,7 @@ TYPED_TEST(QueryPlanTest, Delete) { template <typename StorageType> class DeleteOperatorWithAuthFixture : public QueryPlanTest<StorageType> { protected: - std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access()}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access(ReplicationRole::MAIN)}; memgraph::query::DbAccessor dba{storage_dba.get()}; SymbolTable symbol_table; @@ -978,7 +979,7 @@ TYPED_TEST(QueryPlanTest, DeleteTwiceDeleteBlockingEdge) { // MATCH (n)-[r]-(m) [DETACH] DELETE n, r, m auto test_delete = [this](bool detach) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto v1 = dba.InsertVertex(); @@ -1013,7 +1014,7 @@ TYPED_TEST(QueryPlanTest, DeleteTwiceDeleteBlockingEdge) { TYPED_TEST(QueryPlanTest, DeleteReturn) { // MATCH (n) DETACH DELETE n RETURN n - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // graph with 4 vertices @@ -1047,7 +1048,7 @@ TYPED_TEST(QueryPlanTest, DeleteReturn) { TYPED_TEST(QueryPlanTest, DeleteNull) { // test (simplified) WITH Null as x delete x - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; @@ -1071,7 +1072,7 @@ TYPED_TEST(QueryPlanTest, DeleteAdvance) { auto advance = std::make_shared<Accumulate>(delete_op, std::vector<Symbol>{n.sym_}, true); auto res_sym = symbol_table.CreateSymbol("res", true); { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); dba.InsertVertex(); dba.AdvanceCommand(); @@ -1080,7 +1081,7 @@ TYPED_TEST(QueryPlanTest, DeleteAdvance) { EXPECT_EQ(1, PullAll(*produce, &context)); } { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); dba.InsertVertex(); dba.AdvanceCommand(); @@ -1092,7 +1093,7 @@ TYPED_TEST(QueryPlanTest, DeleteAdvance) { } TYPED_TEST(QueryPlanTest, SetProperty) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // graph with 4 vertices in connected pairs @@ -1148,7 +1149,7 @@ TYPED_TEST(QueryPlanTest, SetProperty) { TYPED_TEST(QueryPlanTest, SetProperties) { auto test_set_properties = [this](bool update) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // graph: ({a: 0})-[:R {b:1}]->({c:2}) @@ -1221,7 +1222,7 @@ TYPED_TEST(QueryPlanTest, SetProperties) { } TYPED_TEST(QueryPlanTest, SetLabels) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto label1 = dba.NameToLabel("label1"); @@ -1271,7 +1272,7 @@ TYPED_TEST(QueryPlanTest, SetLabelsWithFineGrained) { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto label1 = dba.NameToLabel("label1"); auto label2 = dba.NameToLabel("label2"); @@ -1288,7 +1289,7 @@ TYPED_TEST(QueryPlanTest, SetLabelsWithFineGrained) { { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto label1 = dba.NameToLabel("label1"); auto label2 = dba.NameToLabel("label2"); @@ -1307,7 +1308,7 @@ TYPED_TEST(QueryPlanTest, SetLabelsWithFineGrained) { user.fine_grained_access_handler().label_permissions().Grant("label3", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto label1 = dba.NameToLabel("label1"); auto label2 = dba.NameToLabel("label2"); @@ -1319,7 +1320,7 @@ TYPED_TEST(QueryPlanTest, SetLabelsWithFineGrained) { #endif TYPED_TEST(QueryPlanTest, RemoveProperty) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // graph with 4 vertices in connected pairs @@ -1381,7 +1382,7 @@ TYPED_TEST(QueryPlanTest, RemoveProperty) { } TYPED_TEST(QueryPlanTest, RemoveLabels) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto label1 = dba.NameToLabel("label1"); @@ -1442,7 +1443,7 @@ TYPED_TEST(QueryPlanTest, RemoveLabelsFineGrainedFiltering) { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto label1 = dba.NameToLabel("label1"); auto label2 = dba.NameToLabel("label2"); @@ -1459,7 +1460,7 @@ TYPED_TEST(QueryPlanTest, RemoveLabelsFineGrainedFiltering) { { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto label1 = dba.NameToLabel("label1"); auto label2 = dba.NameToLabel("label2"); @@ -1478,7 +1479,7 @@ TYPED_TEST(QueryPlanTest, RemoveLabelsFineGrainedFiltering) { user.fine_grained_access_handler().label_permissions().Grant("label3", memgraph::auth::FineGrainedPermission::CREATE_DELETE); - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto label1 = dba.NameToLabel("label1"); auto label2 = dba.NameToLabel("label2"); @@ -1490,7 +1491,7 @@ TYPED_TEST(QueryPlanTest, RemoveLabelsFineGrainedFiltering) { #endif TYPED_TEST(QueryPlanTest, NodeFilterSet) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // Create a graph such that (v1 {prop: 42}) is connected to v2 and v3. auto v1 = dba.InsertVertex(); @@ -1528,7 +1529,7 @@ TYPED_TEST(QueryPlanTest, NodeFilterSet) { } TYPED_TEST(QueryPlanTest, FilterRemove) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // Create a graph such that (v1 {prop: 42}) is connected to v2 and v3. auto v1 = dba.InsertVertex(); @@ -1562,7 +1563,7 @@ TYPED_TEST(QueryPlanTest, FilterRemove) { } TYPED_TEST(QueryPlanTest, SetRemove) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto v = dba.InsertVertex(); auto label1 = dba.NameToLabel("label1"); @@ -1591,7 +1592,7 @@ TYPED_TEST(QueryPlanTest, Merge) { // - merge_match branch looks for an expansion (any direction) // and sets some property (for result validation) // - merge_create branch just sets some other property - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto v1 = dba.InsertVertex(); auto v2 = dba.InsertVertex(); @@ -1633,7 +1634,7 @@ TYPED_TEST(QueryPlanTest, Merge) { TYPED_TEST(QueryPlanTest, MergeNoInput) { // merge with no input, creates a single node - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; @@ -1651,7 +1652,7 @@ TYPED_TEST(QueryPlanTest, MergeNoInput) { TYPED_TEST(QueryPlanTest, SetPropertyWithCaching) { // SET (Null).prop = 42 - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; auto prop = PROPERTY_PAIR(dba, "property"); @@ -1666,7 +1667,7 @@ TYPED_TEST(QueryPlanTest, SetPropertyWithCaching) { TYPED_TEST(QueryPlanTest, SetPropertyOnNull) { // SET (Null).prop = 42 - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; auto prop = PROPERTY_PAIR(dba, "property"); @@ -1681,7 +1682,7 @@ TYPED_TEST(QueryPlanTest, SetPropertyOnNull) { TYPED_TEST(QueryPlanTest, SetPropertiesOnNull) { // OPTIONAL MATCH (n) SET n = n - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; auto n = MakeScanAll(this->storage, symbol_table, "n"); @@ -1694,7 +1695,7 @@ TYPED_TEST(QueryPlanTest, SetPropertiesOnNull) { } TYPED_TEST(QueryPlanTest, UpdateSetPropertiesFromMap) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // Add a single vertex. ( {property: 43}) auto vertex_accessor = dba.InsertVertex(); @@ -1729,7 +1730,7 @@ TYPED_TEST(QueryPlanTest, UpdateSetPropertiesFromMap) { } TYPED_TEST(QueryPlanTest, SetPropertiesFromMapWithCaching) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // Add a single vertex. ({prop1: 43, prop2: 44}) @@ -1769,7 +1770,7 @@ TYPED_TEST(QueryPlanTest, SetPropertiesFromMapWithCaching) { TYPED_TEST(QueryPlanTest, SetLabelsOnNull) { // OPTIONAL MATCH (n) SET n :label - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto label = dba.NameToLabel("label"); SymbolTable symbol_table; @@ -1783,7 +1784,7 @@ TYPED_TEST(QueryPlanTest, SetLabelsOnNull) { TYPED_TEST(QueryPlanTest, RemovePropertyOnNull) { // REMOVE (Null).prop - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; auto prop = PROPERTY_PAIR(dba, "property"); @@ -1797,7 +1798,7 @@ TYPED_TEST(QueryPlanTest, RemovePropertyOnNull) { TYPED_TEST(QueryPlanTest, RemoveLabelsOnNull) { // OPTIONAL MATCH (n) REMOVE n :label - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto label = dba.NameToLabel("label"); SymbolTable symbol_table; @@ -1812,7 +1813,7 @@ TYPED_TEST(QueryPlanTest, RemoveLabelsOnNull) { TYPED_TEST(QueryPlanTest, DeleteSetProperty) { // MATCH (n) DELETE n SET n.property = 42 RETURN n - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // Add a single vertex. dba.InsertVertex(); @@ -1838,7 +1839,7 @@ TYPED_TEST(QueryPlanTest, DeleteSetProperty) { TYPED_TEST(QueryPlanTest, DeleteSetPropertiesFromMap) { // MATCH (n) DELETE n SET n = {property: 42} return n // MATCH (n) DELETE n SET n += {property: 42} return n - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // Add a single vertex. dba.InsertVertex(); @@ -1866,7 +1867,7 @@ TYPED_TEST(QueryPlanTest, DeleteSetPropertiesFromMap) { TYPED_TEST(QueryPlanTest, DeleteSetPropertiesFrom) { // MATCH (n) DELETE n SET n = n RETURN n - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // Add a single vertex. { @@ -1895,7 +1896,7 @@ TYPED_TEST(QueryPlanTest, DeleteSetPropertiesFrom) { TYPED_TEST(QueryPlanTest, DeleteRemoveLabels) { // MATCH (n) DELETE n REMOVE n :label return n - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // Add a single vertex. dba.InsertVertex(); @@ -1920,7 +1921,7 @@ TYPED_TEST(QueryPlanTest, DeleteRemoveLabels) { TYPED_TEST(QueryPlanTest, DeleteRemoveProperty) { // MATCH (n) DELETE n REMOVE n.property RETURN n - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // Add a single vertex. dba.InsertVertex(); @@ -1950,7 +1951,7 @@ TYPED_TEST(QueryPlanTest, DeleteRemoveProperty) { template <typename StorageType> class UpdatePropertiesWithAuthFixture : public QueryPlanTest<StorageType> { protected: - std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access()}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{this->db->Access(ReplicationRole::MAIN)}; memgraph::query::DbAccessor dba{storage_dba.get()}; SymbolTable symbol_table; @@ -2587,7 +2588,7 @@ class DynamicExpandFixture : public testing::Test { const std::string testSuite = "query_plan_create_set_remove_delete_dynamic_expand"; memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; - std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access(ReplicationRole::MAIN)}; memgraph::query::DbAccessor dba{storage_dba.get()}; SymbolTable symbol_table; AstStorage storage; diff --git a/tests/unit/query_plan_match_filter_return.cpp b/tests/unit/query_plan_match_filter_return.cpp index 6b856704d..d5468b6b5 100644 --- a/tests/unit/query_plan_match_filter_return.cpp +++ b/tests/unit/query_plan_match_filter_return.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -42,6 +42,7 @@ using namespace memgraph::query; using namespace memgraph::query::plan; +using memgraph::replication_coordination_glue::ReplicationRole; const std::string testSuite = "query_plan_match_filter_return"; @@ -50,7 +51,7 @@ class MatchReturnFixture : public testing::Test { protected: memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; - std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access(ReplicationRole::MAIN)}; memgraph::query::DbAccessor dba{storage_dba.get()}; AstStorage storage; SymbolTable symbol_table; @@ -239,7 +240,7 @@ class QueryPlan : public testing::Test { TYPED_TEST_CASE(QueryPlan, StorageTypes); TYPED_TEST(QueryPlan, MatchReturnCartesian) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); ASSERT_TRUE(dba.InsertVertex().AddLabel(dba.NameToLabel("l1")).HasValue()); @@ -263,7 +264,7 @@ TYPED_TEST(QueryPlan, MatchReturnCartesian) { } TYPED_TEST(QueryPlan, StandaloneReturn) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // add a few nodes to the database @@ -285,7 +286,7 @@ TYPED_TEST(QueryPlan, StandaloneReturn) { } TYPED_TEST(QueryPlan, NodeFilterLabelsAndProperties) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // add a few nodes to the database @@ -337,7 +338,7 @@ TYPED_TEST(QueryPlan, NodeFilterLabelsAndProperties) { } TYPED_TEST(QueryPlan, NodeFilterMultipleLabels) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // add a few nodes to the database @@ -382,7 +383,7 @@ TYPED_TEST(QueryPlan, NodeFilterMultipleLabels) { } TYPED_TEST(QueryPlan, Cartesian) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto add_vertex = [&dba](std::string label) { @@ -419,7 +420,7 @@ TYPED_TEST(QueryPlan, Cartesian) { } TYPED_TEST(QueryPlan, CartesianEmptySet) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; @@ -439,7 +440,7 @@ TYPED_TEST(QueryPlan, CartesianEmptySet) { } TYPED_TEST(QueryPlan, CartesianThreeWay) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto add_vertex = [&dba](std::string label) { auto vertex = dba.InsertVertex(); @@ -489,7 +490,7 @@ class ExpandFixture : public testing::Test { protected: memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; - std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access(ReplicationRole::MAIN)}; memgraph::query::DbAccessor dba{storage_dba.get()}; SymbolTable symbol_table; AstStorage storage; @@ -649,7 +650,7 @@ class QueryPlanExpandVariable : public testing::Test { memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; - std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access(ReplicationRole::MAIN)}; memgraph::query::DbAccessor dba{storage_dba.get()}; // labels for layers in the double chain std::vector<memgraph::storage::LabelId> labels; @@ -1804,7 +1805,7 @@ class QueryPlanExpandWeightedShortestPath : public testing::Test { protected: memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; - std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access(ReplicationRole::MAIN)}; memgraph::query::DbAccessor dba{storage_dba.get()}; std::pair<std::string, memgraph::storage::PropertyId> prop = PROPERTY_PAIR(dba, "property"); memgraph::storage::EdgeTypeId edge_type = dba.NameToEdgeType("edge_type"); @@ -2248,7 +2249,7 @@ class QueryPlanExpandAllShortestPaths : public testing::Test { protected: memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; - std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access(ReplicationRole::MAIN)}; memgraph::query::DbAccessor dba{storage_dba.get()}; std::pair<std::string, memgraph::storage::PropertyId> prop = PROPERTY_PAIR(dba, "property"); memgraph::storage::EdgeTypeId edge_type = dba.NameToEdgeType("edge_type"); @@ -2699,7 +2700,7 @@ TYPED_TEST(QueryPlanExpandAllShortestPaths, BasicWithFineGrainedFiltering) { #endif TYPED_TEST(QueryPlan, ExpandOptional) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; @@ -2750,7 +2751,7 @@ TYPED_TEST(QueryPlan, ExpandOptional) { } TYPED_TEST(QueryPlan, OptionalMatchEmptyDB) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; @@ -2767,7 +2768,7 @@ TYPED_TEST(QueryPlan, OptionalMatchEmptyDB) { } TYPED_TEST(QueryPlan, OptionalMatchEmptyDBExpandFromNode) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; // OPTIONAL MATCH (n) @@ -2790,7 +2791,7 @@ TYPED_TEST(QueryPlan, OptionalMatchEmptyDBExpandFromNode) { } TYPED_TEST(QueryPlan, OptionalMatchThenExpandToMissingNode) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // Make a graph with 2 connected, unlabeled nodes. auto v1 = dba.InsertVertex(); @@ -2834,7 +2835,7 @@ TYPED_TEST(QueryPlan, OptionalMatchThenExpandToMissingNode) { } TYPED_TEST(QueryPlan, ExpandExistingNode) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // make a graph (v1)->(v2) that @@ -2872,7 +2873,7 @@ TYPED_TEST(QueryPlan, ExpandExistingNode) { TYPED_TEST(QueryPlan, ExpandBothCycleEdgeCase) { // we're testing that expanding on BOTH // does only one expansion for a cycle - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto v = dba.InsertVertex(); @@ -2889,7 +2890,7 @@ TYPED_TEST(QueryPlan, ExpandBothCycleEdgeCase) { } TYPED_TEST(QueryPlan, EdgeFilter) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // make an N-star expanding from (v1) @@ -2949,7 +2950,7 @@ TYPED_TEST(QueryPlan, EdgeFilter) { } TYPED_TEST(QueryPlan, EdgeFilterMultipleTypes) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto v1 = dba.InsertVertex(); @@ -2979,7 +2980,7 @@ TYPED_TEST(QueryPlan, EdgeFilterMultipleTypes) { } TYPED_TEST(QueryPlan, Filter) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // add a 6 nodes with property 'prop', 2 have true as value @@ -3003,7 +3004,7 @@ TYPED_TEST(QueryPlan, Filter) { } TYPED_TEST(QueryPlan, EdgeUniquenessFilter) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // make a graph that has (v1)->(v2) and a recursive edge (v1)->(v1) @@ -3038,7 +3039,7 @@ TYPED_TEST(QueryPlan, Distinct) { // test queries like // UNWIND [1, 2, 3, 3] AS x RETURN DISTINCT x - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); SymbolTable symbol_table; @@ -3082,11 +3083,11 @@ TYPED_TEST(QueryPlan, Distinct) { TYPED_TEST(QueryPlan, ScanAllByLabel) { auto label = this->db->NameToLabel("label"); { - auto unique_acc = this->db->UniqueAccess(); + auto unique_acc = this->db->UniqueAccess(ReplicationRole::MAIN); [[maybe_unused]] auto _ = unique_acc->CreateIndex(label); ASSERT_FALSE(unique_acc->Commit().HasError()); } - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // Add a vertex with a label and one without. auto labeled_vertex = dba.InsertVertex(); @@ -3133,7 +3134,7 @@ TYPED_TEST(QueryPlan, ScanAllByLabelProperty) { memgraph::storage::PropertyValue( std::vector<memgraph::storage::PropertyValue>{memgraph::storage::PropertyValue(2)})}; { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); for (const auto &value : values) { auto vertex = dba.InsertVertex(); @@ -3144,12 +3145,12 @@ TYPED_TEST(QueryPlan, ScanAllByLabelProperty) { } { - auto unique_acc = this->db->UniqueAccess(); + auto unique_acc = this->db->UniqueAccess(ReplicationRole::MAIN); [[maybe_unused]] auto _ = unique_acc->CreateIndex(label, prop); ASSERT_FALSE(unique_acc->Commit().HasError()); } - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); ASSERT_EQ(14, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); @@ -3243,7 +3244,7 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyEqualityNoError) { auto label = this->db->NameToLabel("label"); auto prop = this->db->NameToProperty("prop"); { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto number_vertex = dba.InsertVertex(); ASSERT_TRUE(number_vertex.AddLabel(label).HasValue()); @@ -3254,12 +3255,12 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyEqualityNoError) { ASSERT_FALSE(dba.Commit().HasError()); } { - auto unique_acc = this->db->UniqueAccess(); + auto unique_acc = this->db->UniqueAccess(ReplicationRole::MAIN); [[maybe_unused]] auto _ = unique_acc->CreateIndex(label, prop); ASSERT_FALSE(unique_acc->Commit().HasError()); } - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); EXPECT_EQ(2, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); // MATCH (n :label {prop: 42}) @@ -3283,7 +3284,7 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyValueError) { auto label = this->db->NameToLabel("label"); auto prop = this->db->NameToProperty("prop"); { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); for (int i = 0; i < 2; ++i) { auto vertex = dba.InsertVertex(); @@ -3293,12 +3294,12 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyValueError) { ASSERT_FALSE(dba.Commit().HasError()); } { - auto unique_acc = this->db->UniqueAccess(); + auto unique_acc = this->db->UniqueAccess(ReplicationRole::MAIN); [[maybe_unused]] auto _ = unique_acc->CreateIndex(label, prop); ASSERT_FALSE(unique_acc->Commit().HasError()); } - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); EXPECT_EQ(2, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); // MATCH (m), (n :label {prop: m}) @@ -3316,7 +3317,7 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyRangeError) { auto label = this->db->NameToLabel("label"); auto prop = this->db->NameToProperty("prop"); { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); for (int i = 0; i < 2; ++i) { auto vertex = dba.InsertVertex(); @@ -3326,12 +3327,12 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyRangeError) { ASSERT_FALSE(dba.Commit().HasError()); } { - auto unique_acc = this->db->UniqueAccess(); + auto unique_acc = this->db->UniqueAccess(ReplicationRole::MAIN); [[maybe_unused]] auto _ = unique_acc->CreateIndex(label, prop); ASSERT_FALSE(unique_acc->Commit().HasError()); } - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); EXPECT_EQ(2, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); // MATCH (m), (n :label {prop: m}) @@ -3372,7 +3373,7 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyEqualNull) { auto label = this->db->NameToLabel("label"); auto prop = this->db->NameToProperty("prop"); { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto vertex = dba.InsertVertex(); ASSERT_TRUE(vertex.AddLabel(label).HasValue()); @@ -3382,12 +3383,12 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyEqualNull) { ASSERT_FALSE(dba.Commit().HasError()); } { - auto unique_acc = this->db->UniqueAccess(); + auto unique_acc = this->db->UniqueAccess(ReplicationRole::MAIN); [[maybe_unused]] auto _ = unique_acc->CreateIndex(label, prop); ASSERT_FALSE(unique_acc->Commit().HasError()); } - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); EXPECT_EQ(2, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); // MATCH (n :label {prop: 42}) @@ -3409,7 +3410,7 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyRangeNull) { auto label = this->db->NameToLabel("label"); auto prop = this->db->NameToProperty("prop"); { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto vertex = dba.InsertVertex(); ASSERT_TRUE(vertex.AddLabel(label).HasValue()); @@ -3419,12 +3420,12 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyRangeNull) { ASSERT_FALSE(dba.Commit().HasError()); } { - auto unique_acc = this->db->UniqueAccess(); + auto unique_acc = this->db->UniqueAccess(ReplicationRole::MAIN); [[maybe_unused]] auto _ = unique_acc->CreateIndex(label, prop); ASSERT_FALSE(unique_acc->Commit().HasError()); } - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); EXPECT_EQ(2, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); // MATCH (n :label) WHERE null <= n.prop < null @@ -3444,7 +3445,7 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyNoValueInIndexContinuation) { auto label = this->db->NameToLabel("label"); auto prop = this->db->NameToProperty("prop"); { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto v = dba.InsertVertex(); ASSERT_TRUE(v.AddLabel(label).HasValue()); @@ -3452,12 +3453,12 @@ TYPED_TEST(QueryPlan, ScanAllByLabelPropertyNoValueInIndexContinuation) { ASSERT_FALSE(dba.Commit().HasError()); } { - auto unique_acc = this->db->UniqueAccess(); + auto unique_acc = this->db->UniqueAccess(ReplicationRole::MAIN); [[maybe_unused]] auto _ = unique_acc->CreateIndex(label, prop); ASSERT_FALSE(unique_acc->Commit().HasError()); } - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); EXPECT_EQ(1, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); @@ -3487,7 +3488,7 @@ TYPED_TEST(QueryPlan, ScanAllEqualsScanAllByLabelProperty) { const int prop_value1 = 42, prop_value2 = 69; for (int i = 0; i < vertex_count; ++i) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto v = dba.InsertVertex(); ASSERT_TRUE(v.AddLabel(label).HasValue()); @@ -3497,14 +3498,14 @@ TYPED_TEST(QueryPlan, ScanAllEqualsScanAllByLabelProperty) { } { - auto unique_acc = this->db->UniqueAccess(); + auto unique_acc = this->db->UniqueAccess(ReplicationRole::MAIN); [[maybe_unused]] auto _ = unique_acc->CreateIndex(label, prop); ASSERT_FALSE(unique_acc->Commit().HasError()); } // Make sure there are `vertex_count` vertices { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); EXPECT_EQ(vertex_count, CountIterable(dba.Vertices(memgraph::storage::View::OLD))); } @@ -3512,7 +3513,7 @@ TYPED_TEST(QueryPlan, ScanAllEqualsScanAllByLabelProperty) { // Make sure there are `vertex_prop_count` results when using index auto count_with_index = [this, &label, &prop](int prop_value, int prop_count) { SymbolTable symbol_table; - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto scan_all_by_label_property_value = MakeScanAllByLabelPropertyValue(this->storage, symbol_table, "n", label, prop, "prop", LITERAL(prop_value)); @@ -3526,7 +3527,7 @@ TYPED_TEST(QueryPlan, ScanAllEqualsScanAllByLabelProperty) { // Make sure there are `vertex_count` results when using scan all auto count_with_scan_all = [this, &prop](int prop_value, int prop_count) { SymbolTable symbol_table; - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto scan_all = MakeScanAll(this->storage, symbol_table, "n"); auto e = PROPERTY_LOOKUP(dba, IDENT("n")->MapTo(scan_all.sym_), std::make_pair("prop", prop)); @@ -3551,7 +3552,7 @@ class ExistsFixture : public testing::Test { protected: memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; - std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access(ReplicationRole::MAIN)}; memgraph::query::DbAccessor dba{storage_dba.get()}; AstStorage storage; SymbolTable symbol_table; @@ -3779,7 +3780,7 @@ class SubqueriesFeature : public testing::Test { protected: memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; - std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access(ReplicationRole::MAIN)}; memgraph::query::DbAccessor dba{storage_dba.get()}; AstStorage storage; SymbolTable symbol_table; diff --git a/tests/unit/query_plan_operator_to_string.cpp b/tests/unit/query_plan_operator_to_string.cpp index 27d27f0d1..694552cf0 100644 --- a/tests/unit/query_plan_operator_to_string.cpp +++ b/tests/unit/query_plan_operator_to_string.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -37,7 +37,7 @@ class OperatorToStringTest : public ::testing::Test { OperatorToStringTest() : config(disk_test_utils::GenerateOnDiskConfig(testSuite)), db(new StorageType(config)), - dba_storage(db->Access()), + dba_storage(db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN)), dba(dba_storage.get()) {} ~OperatorToStringTest() override { diff --git a/tests/unit/query_plan_read_write_typecheck.cpp b/tests/unit/query_plan_read_write_typecheck.cpp index a369f44b9..f9f14902b 100644 --- a/tests/unit/query_plan_read_write_typecheck.cpp +++ b/tests/unit/query_plan_read_write_typecheck.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -36,7 +36,8 @@ class ReadWriteTypeCheckTest : public ::testing::Test { memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; - std::unique_ptr<memgraph::storage::Storage::Accessor> dba_storage{db->Access()}; + std::unique_ptr<memgraph::storage::Storage::Accessor> dba_storage{ + db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN)}; memgraph::query::DbAccessor dba{dba_storage.get()}; void TearDown() override { diff --git a/tests/unit/query_plan_v2_create_set_remove_delete.cpp b/tests/unit/query_plan_v2_create_set_remove_delete.cpp index 7aeeb4e5e..b82454682 100644 --- a/tests/unit/query_plan_v2_create_set_remove_delete.cpp +++ b/tests/unit/query_plan_v2_create_set_remove_delete.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -18,6 +18,7 @@ #include "query/plan/operator.hpp" #include "storage/v2/disk/storage.hpp" #include "storage/v2/inmemory/storage.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; template <typename StorageType> class QueryPlan : public testing::Test { @@ -37,7 +38,7 @@ using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgra TYPED_TEST_CASE(QueryPlan, StorageTypes); TYPED_TEST(QueryPlan, CreateNodeWithAttributes) { - auto dba = this->db->Access(); + auto dba = this->db->Access(ReplicationRole::MAIN); auto label = memgraph::storage::LabelId::FromInt(42); auto property = memgraph::storage::PropertyId::FromInt(1); @@ -75,7 +76,7 @@ TYPED_TEST(QueryPlan, CreateNodeWithAttributes) { TYPED_TEST(QueryPlan, ScanAllEmpty) { memgraph::query::AstStorage ast; memgraph::query::SymbolTable symbol_table; - auto dba = this->db->Access(); + auto dba = this->db->Access(ReplicationRole::MAIN); DbAccessor execution_dba(dba.get()); auto node_symbol = symbol_table.CreateSymbol("n", true); { @@ -100,13 +101,13 @@ TYPED_TEST(QueryPlan, ScanAllEmpty) { TYPED_TEST(QueryPlan, ScanAll) { { - auto dba = this->db->Access(); + auto dba = this->db->Access(ReplicationRole::MAIN); for (int i = 0; i < 42; ++i) dba->CreateVertex(); EXPECT_FALSE(dba->Commit().HasError()); } memgraph::query::AstStorage ast; memgraph::query::SymbolTable symbol_table; - auto dba = this->db->Access(); + auto dba = this->db->Access(ReplicationRole::MAIN); DbAccessor execution_dba(dba.get()); auto node_symbol = symbol_table.CreateSymbol("n", true); memgraph::query::plan::ScanAll scan_all(nullptr, node_symbol); @@ -121,12 +122,12 @@ TYPED_TEST(QueryPlan, ScanAll) { TYPED_TEST(QueryPlan, ScanAllByLabel) { auto label = this->db->NameToLabel("label"); { - auto unique_acc = this->db->UniqueAccess(); + auto unique_acc = this->db->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateIndex(label).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto dba = this->db->Access(); + auto dba = this->db->Access(ReplicationRole::MAIN); // Add some unlabeled vertices for (int i = 0; i < 12; ++i) dba->CreateVertex(); // Add labeled vertices @@ -136,7 +137,7 @@ TYPED_TEST(QueryPlan, ScanAllByLabel) { } EXPECT_FALSE(dba->Commit().HasError()); } - auto dba = this->db->Access(); + auto dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::AstStorage ast; memgraph::query::SymbolTable symbol_table; auto node_symbol = symbol_table.CreateSymbol("n", true); diff --git a/tests/unit/query_pretty_print.cpp b/tests/unit/query_pretty_print.cpp index 1b19f8aa8..ac789b1da 100644 --- a/tests/unit/query_pretty_print.cpp +++ b/tests/unit/query_pretty_print.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -36,7 +36,8 @@ class ExpressionPrettyPrinterTest : public ::testing::Test { const std::string testSuite = "query_pretty_print"; memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; - std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{ + db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN)}; memgraph::query::DbAccessor dba{storage_dba.get()}; AstStorage storage; diff --git a/tests/unit/query_procedure_mgp_type.cpp b/tests/unit/query_procedure_mgp_type.cpp index c5f30f3af..e12a61f28 100644 --- a/tests/unit/query_procedure_mgp_type.cpp +++ b/tests/unit/query_procedure_mgp_type.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -23,6 +23,8 @@ #include "disk_test_utils.hpp" #include "test_utils.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; + template <typename StorageType> class CypherType : public testing::Test { public: @@ -244,7 +246,7 @@ TYPED_TEST(CypherType, MapSatisfiesType) { } TYPED_TEST(CypherType, VertexSatisfiesType) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto vertex = dba.InsertVertex(); mgp_memory memory{memgraph::utils::NewDeleteResource()}; @@ -267,7 +269,7 @@ TYPED_TEST(CypherType, VertexSatisfiesType) { } TYPED_TEST(CypherType, EdgeSatisfiesType) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto v1 = dba.InsertVertex(); auto v2 = dba.InsertVertex(); @@ -291,7 +293,7 @@ TYPED_TEST(CypherType, EdgeSatisfiesType) { } TYPED_TEST(CypherType, PathSatisfiesType) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto v1 = dba.InsertVertex(); auto v2 = dba.InsertVertex(); diff --git a/tests/unit/query_procedure_py_module.cpp b/tests/unit/query_procedure_py_module.cpp index dd744229a..baef2e1c8 100644 --- a/tests/unit/query_procedure_py_module.cpp +++ b/tests/unit/query_procedure_py_module.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -21,6 +21,8 @@ #include "storage/v2/inmemory/storage.hpp" #include "test_utils.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; + template <typename StorageType> class PyModule : public testing::Test { public: @@ -116,7 +118,7 @@ static void AssertPickleAndCopyAreNotSupported(PyObject *py_obj) { TYPED_TEST(PyModule, PyVertex) { // Initialize the database with 2 vertices and 1 edge. { - auto dba = this->db->Access(); + auto dba = this->db->Access(ReplicationRole::MAIN); auto v1 = dba->CreateVertex(); auto v2 = dba->CreateVertex(); @@ -129,7 +131,7 @@ TYPED_TEST(PyModule, PyVertex) { ASSERT_FALSE(dba->Commit().HasError()); } // Get the first vertex as an mgp_value. - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); mgp_memory memory{memgraph::utils::NewDeleteResource()}; mgp_graph graph{&dba, memgraph::storage::View::OLD, nullptr, dba.GetStorageMode()}; @@ -165,7 +167,7 @@ TYPED_TEST(PyModule, PyVertex) { TYPED_TEST(PyModule, PyEdge) { // Initialize the database with 2 vertices and 1 edge. { - auto dba = this->db->Access(); + auto dba = this->db->Access(ReplicationRole::MAIN); auto v1 = dba->CreateVertex(); auto v2 = dba->CreateVertex(); @@ -179,7 +181,7 @@ TYPED_TEST(PyModule, PyEdge) { ASSERT_FALSE(dba->Commit().HasError()); } // Get the edge as an mgp_value. - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); mgp_memory memory{memgraph::utils::NewDeleteResource()}; mgp_graph graph{&dba, memgraph::storage::View::OLD, nullptr, dba.GetStorageMode()}; @@ -219,13 +221,13 @@ TYPED_TEST(PyModule, PyEdge) { TYPED_TEST(PyModule, PyPath) { { - auto dba = this->db->Access(); + auto dba = this->db->Access(ReplicationRole::MAIN); auto v1 = dba->CreateVertex(); auto v2 = dba->CreateVertex(); ASSERT_TRUE(dba->CreateEdge(&v1, &v2, dba->NameToEdgeType("type")).HasValue()); ASSERT_FALSE(dba->Commit().HasError()); } - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); mgp_memory memory{memgraph::utils::NewDeleteResource()}; mgp_graph graph{&dba, memgraph::storage::View::OLD, nullptr, dba.GetStorageMode()}; diff --git a/tests/unit/query_procedures_mgp_graph.cpp b/tests/unit/query_procedures_mgp_graph.cpp index 785aab2cf..cf3b5a137 100644 --- a/tests/unit/query_procedures_mgp_graph.cpp +++ b/tests/unit/query_procedures_mgp_graph.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -34,6 +34,8 @@ #include "utils/memory.hpp" #include "utils/variant_helpers.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; + #define EXPECT_SUCCESS(...) EXPECT_EQ(__VA_ARGS__, mgp_error::MGP_ERROR_NO_ERROR) namespace { @@ -155,7 +157,7 @@ class MgpGraphTest : public ::testing::Test { } memgraph::query::DbAccessor &CreateDbAccessor(const memgraph::storage::IsolationLevel isolationLevel) { - accessors_.push_back(storage->Access(isolationLevel)); + accessors_.push_back(storage->Access(ReplicationRole::MAIN, isolationLevel)); db_accessors_.emplace_back(accessors_.back().get()); return db_accessors_.back(); } @@ -194,7 +196,8 @@ TYPED_TEST(MgpGraphTest, CreateVertex) { return; } mgp_graph graph = this->CreateGraph(); - auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); + auto read_uncommited_accessor = + this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::READ_UNCOMMITTED); EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 0); MgpVertexPtr vertex{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_create_vertex, &graph, &this->memory)}; EXPECT_NE(vertex, nullptr); @@ -217,7 +220,8 @@ TYPED_TEST(MgpGraphTest, DeleteVertex) { ASSERT_FALSE(accessor.Commit().HasError()); } mgp_graph graph = this->CreateGraph(); - auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); + auto read_uncommited_accessor = + this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::READ_UNCOMMITTED); EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 1); MgpVertexPtr vertex{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{vertex_id.AsInt()}, &this->memory)}; @@ -233,7 +237,8 @@ TYPED_TEST(MgpGraphTest, DetachDeleteVertex) { } const auto vertex_ids = this->CreateEdge(); auto graph = this->CreateGraph(); - auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); + auto read_uncommited_accessor = + this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::READ_UNCOMMITTED); EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 2); MgpVertexPtr vertex{EXPECT_MGP_NO_ERROR(mgp_vertex *, mgp_graph_get_vertex_by_id, &graph, mgp_vertex_id{vertex_ids.front().AsInt()}, &this->memory)}; @@ -255,7 +260,8 @@ TYPED_TEST(MgpGraphTest, CreateDeleteWithImmutableGraph) { vertex_id = vertex.Gid(); ASSERT_FALSE(accessor.Commit().HasError()); } - auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); + auto read_uncommited_accessor = + this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::READ_UNCOMMITTED); EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 1); mgp_graph immutable_graph = this->CreateGraph(memgraph::storage::View::OLD); @@ -326,7 +332,8 @@ TYPED_TEST(MgpGraphTest, VertexSetProperty) { ASSERT_TRUE(result.HasValue()); ASSERT_FALSE(accessor.Commit().HasError()); } - auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); + auto read_uncommited_accessor = + this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::READ_UNCOMMITTED); EXPECT_EQ(CountVertices(*read_uncommited_accessor, memgraph::storage::View::NEW), 1); mgp_graph graph = this->CreateGraph(memgraph::storage::View::NEW); @@ -396,7 +403,8 @@ TYPED_TEST(MgpGraphTest, VertexAddLabel) { auto check_label = [&]() { EXPECT_NE(EXPECT_MGP_NO_ERROR(int, mgp_vertex_has_label_named, vertex.get(), label.data()), 0); - auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); + auto read_uncommited_accessor = + this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::READ_UNCOMMITTED); const auto maybe_vertex = read_uncommited_accessor->FindVertex(vertex_id, memgraph::storage::View::NEW); ASSERT_TRUE(maybe_vertex); const auto label_ids = maybe_vertex->Labels(memgraph::storage::View::NEW); @@ -433,7 +441,8 @@ TYPED_TEST(MgpGraphTest, VertexRemoveLabel) { auto check_label = [&]() { EXPECT_EQ(EXPECT_MGP_NO_ERROR(int, mgp_vertex_has_label_named, vertex.get(), label.data()), 0); - auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); + auto read_uncommited_accessor = + this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::READ_UNCOMMITTED); const auto maybe_vertex = read_uncommited_accessor->FindVertex(vertex_id, memgraph::storage::View::NEW); ASSERT_TRUE(maybe_vertex); const auto label_ids = maybe_vertex->Labels(memgraph::storage::View::NEW); @@ -622,14 +631,15 @@ TYPED_TEST(MgpGraphTest, EdgeSetProperty) { { const auto vertex_ids = this->CreateEdge(); from_vertex_id = vertex_ids[0]; - auto accessor = this->storage->Access(memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); + auto accessor = this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION); auto edge = get_edge(accessor.get()); const auto result = edge.SetProperty(accessor->NameToProperty(property_to_update), memgraph::storage::PropertyValue(42)); ASSERT_TRUE(result.HasValue()); ASSERT_FALSE(accessor->Commit().HasError()); } - auto read_uncommited_accessor = this->storage->Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED); + auto read_uncommited_accessor = + this->storage->Access(ReplicationRole::MAIN, memgraph::storage::IsolationLevel::READ_UNCOMMITTED); mgp_graph graph = this->CreateGraph(memgraph::storage::View::NEW); MgpEdgePtr edge; diff --git a/tests/unit/query_semantic.cpp b/tests/unit/query_semantic.cpp index 5d015f103..c4bb966eb 100644 --- a/tests/unit/query_semantic.cpp +++ b/tests/unit/query_semantic.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -34,7 +34,8 @@ class TestSymbolGenerator : public ::testing::Test { const std::string testSuite = "query_semantic"; memgraph::storage::Config config = disk_test_utils::GenerateOnDiskConfig(testSuite); std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config)}; - std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{ + db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN)}; memgraph::query::DbAccessor dba{storage_dba.get()}; AstStorage storage; diff --git a/tests/unit/query_trigger.cpp b/tests/unit/query_trigger.cpp index 0eb9602c3..1b2ca5e9c 100644 --- a/tests/unit/query_trigger.cpp +++ b/tests/unit/query_trigger.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -29,6 +29,8 @@ #include "utils/exceptions.hpp" #include "utils/memory.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; + namespace { const std::unordered_set<memgraph::query::TriggerEventType> kAllEventTypes{ memgraph::query::TriggerEventType::ANY, memgraph::query::TriggerEventType::VERTEX_CREATE, @@ -69,7 +71,7 @@ class TriggerContextTest : public ::testing::Test { } memgraph::storage::Storage::Accessor *StartTransaction() { - accessors.emplace_back(db->Access()); + accessors.emplace_back(db->Access(ReplicationRole::MAIN)); return accessors.back().get(); } @@ -902,7 +904,7 @@ class TriggerStoreTest : public ::testing::Test { config = disk_test_utils::GenerateOnDiskConfig(testSuite); storage = std::make_unique<StorageType>(config); - storage_accessor = storage->Access(); + storage_accessor = storage->Access(ReplicationRole::MAIN); dba.emplace(storage_accessor.get()); } diff --git a/tests/unit/query_variable_start_planner.cpp b/tests/unit/query_variable_start_planner.cpp index b9772c2d3..df7173db2 100644 --- a/tests/unit/query_variable_start_planner.cpp +++ b/tests/unit/query_variable_start_planner.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -28,6 +28,7 @@ #include "formatters.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; using namespace memgraph::query::plan; using memgraph::query::AstStorage; using Type = memgraph::query::EdgeAtom::Type; @@ -110,7 +111,7 @@ using StorageTypes = ::testing::Types<memgraph::storage::InMemoryStorage, memgra TYPED_TEST_CASE(TestVariableStartPlanner, StorageTypes); TYPED_TEST(TestVariableStartPlanner, MatchReturn) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // Make a graph (v1) -[:r]-> (v2) auto v1 = dba.InsertVertex(); @@ -127,7 +128,7 @@ TYPED_TEST(TestVariableStartPlanner, MatchReturn) { } TYPED_TEST(TestVariableStartPlanner, MatchTripletPatternReturn) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // Make a graph (v1) -[:r]-> (v2) -[:r]-> (v3) auto v1 = dba.InsertVertex(); @@ -159,7 +160,7 @@ TYPED_TEST(TestVariableStartPlanner, MatchTripletPatternReturn) { } TYPED_TEST(TestVariableStartPlanner, MatchOptionalMatchReturn) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // Make a graph (v1) -[:r]-> (v2) -[:r]-> (v3) auto v1 = dba.InsertVertex(); @@ -186,7 +187,7 @@ TYPED_TEST(TestVariableStartPlanner, MatchOptionalMatchReturn) { } TYPED_TEST(TestVariableStartPlanner, MatchOptionalMatchMergeReturn) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // Graph (v1) -[:r]-> (v2) memgraph::query::VertexAccessor v1(dba.InsertVertex()); @@ -210,7 +211,7 @@ TYPED_TEST(TestVariableStartPlanner, MatchOptionalMatchMergeReturn) { } TYPED_TEST(TestVariableStartPlanner, MatchWithMatchReturn) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // Graph (v1) -[:r]-> (v2) memgraph::query::VertexAccessor v1(dba.InsertVertex()); @@ -230,7 +231,7 @@ TYPED_TEST(TestVariableStartPlanner, MatchWithMatchReturn) { } TYPED_TEST(TestVariableStartPlanner, MatchVariableExpand) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); // Graph (v1) -[:r1]-> (v2) -[:r2]-> (v3) auto v1 = dba.InsertVertex(); @@ -253,7 +254,7 @@ TYPED_TEST(TestVariableStartPlanner, MatchVariableExpand) { } TYPED_TEST(TestVariableStartPlanner, MatchVariableExpandReferenceNode) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto id = dba.NameToProperty("id"); // Graph (v1 {id:1}) -[:r1]-> (v2 {id: 2}) -[:r2]-> (v3 {id: 3}) @@ -281,7 +282,7 @@ TYPED_TEST(TestVariableStartPlanner, MatchVariableExpandReferenceNode) { } TYPED_TEST(TestVariableStartPlanner, MatchVariableExpandBoth) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto id = dba.NameToProperty("id"); // Graph (v1 {id:1}) -[:r1]-> (v2) -[:r2]-> (v3) @@ -307,7 +308,7 @@ TYPED_TEST(TestVariableStartPlanner, MatchVariableExpandBoth) { } TYPED_TEST(TestVariableStartPlanner, MatchBfs) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto id = dba.NameToProperty("id"); // Graph (v1 {id:1}) -[:r1]-> (v2 {id: 2}) -[:r2]-> (v3 {id: 3}) @@ -334,7 +335,7 @@ TYPED_TEST(TestVariableStartPlanner, MatchBfs) { } TYPED_TEST(TestVariableStartPlanner, TestBasicSubquery) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto v1 = dba.InsertVertex(); @@ -356,7 +357,7 @@ TYPED_TEST(TestVariableStartPlanner, TestBasicSubquery) { } TYPED_TEST(TestVariableStartPlanner, TestBasicSubqueryWithMatching) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto v1 = dba.InsertVertex(); @@ -377,7 +378,7 @@ TYPED_TEST(TestVariableStartPlanner, TestBasicSubqueryWithMatching) { } TYPED_TEST(TestVariableStartPlanner, TestSubqueryWithUnion) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto id = dba.NameToProperty("id"); @@ -405,7 +406,7 @@ TYPED_TEST(TestVariableStartPlanner, TestSubqueryWithUnion) { } TYPED_TEST(TestVariableStartPlanner, TestSubqueryWithTripleUnion) { - auto storage_dba = this->db->Access(); + auto storage_dba = this->db->Access(ReplicationRole::MAIN); memgraph::query::DbAccessor dba(storage_dba.get()); auto id = dba.NameToProperty("id"); diff --git a/tests/unit/replication_persistence_helper.cpp b/tests/unit/replication_persistence_helper.cpp index ade9ef638..ef3ba254d 100644 --- a/tests/unit/replication_persistence_helper.cpp +++ b/tests/unit/replication_persistence_helper.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -22,12 +22,7 @@ using namespace memgraph::replication::durability; using namespace memgraph::replication; - -static_assert(sizeof(ReplicationRoleEntry) == 168, - "Most likely you modified ReplicationRoleEntry without updating the tests. "); - -static_assert(sizeof(ReplicationReplicaEntry) == 160, - "Most likely you modified ReplicationReplicaEntry without updating the tests."); +using namespace memgraph::replication_coordination_glue; TEST(ReplicationDurability, V1Main) { auto const role_entry = ReplicationRoleEntry{.version = DurabilityVersion::V1, diff --git a/tests/unit/skip_list.cpp b/tests/unit/skip_list.cpp index 86fb4ae57..bdebacc1e 100644 --- a/tests/unit/skip_list.cpp +++ b/tests/unit/skip_list.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -18,6 +18,20 @@ #include "utils/skip_list.hpp" #include "utils/timer.hpp" +#include <iterator> + +template <typename It, typename ConstIt> +concept CompatibleIterators = std::forward_iterator<It> && std::forward_iterator<ConstIt> && + requires(It it, ConstIt cit) { + { it == cit } -> std::same_as<bool>; + { it != cit } -> std::same_as<bool>; + { cit == it } -> std::same_as<bool>; + { cit != it } -> std::same_as<bool>; +}; + +using sut_t = memgraph::utils::SkipList<int64_t>::Accessor; +static_assert(CompatibleIterators<sut_t::iterator, sut_t::const_iterator>); + TEST(SkipList, Int) { memgraph::utils::SkipList<int64_t> list; { diff --git a/tests/unit/slk_advanced.cpp b/tests/unit/slk_advanced.cpp index fab936fe0..f41946388 100644 --- a/tests/unit/slk_advanced.cpp +++ b/tests/unit/slk_advanced.cpp @@ -1,4 +1,4 @@ -// Copyright 2022 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -11,10 +11,13 @@ #include <gtest/gtest.h> +#include "coordination/coordinator_config.hpp" +#include "coordination/coordinator_slk.hpp" +#include "replication/config.hpp" +#include "replication_coordination_glue/mode.hpp" +#include "slk_common.hpp" #include "storage/v2/property_value.hpp" #include "storage/v2/replication/slk.hpp" - -#include "slk_common.hpp" #include "storage/v2/temporal.hpp" TEST(SlkAdvanced, PropertyValueList) { @@ -114,3 +117,34 @@ TEST(SlkAdvanced, PropertyValueComplex) { ASSERT_EQ(original, decoded); } + +TEST(SlkAdvanced, ReplicationClientConfigs) { + using ReplicationClientInfo = memgraph::coordination::CoordinatorClientConfig::ReplicationClientInfo; + using ReplicationClientInfoVec = std::vector<ReplicationClientInfo>; + using ReplicationMode = memgraph::replication_coordination_glue::ReplicationMode; + + ReplicationClientInfoVec original{ReplicationClientInfo{.instance_name = "replica1", + .replication_mode = ReplicationMode::SYNC, + .replication_ip_address = "127.0.0.1", + .replication_port = 10000}, + ReplicationClientInfo{.instance_name = "replica2", + .replication_mode = ReplicationMode::ASYNC, + .replication_ip_address = "127.0.1.1", + .replication_port = 10010}, + ReplicationClientInfo{ + .instance_name = "replica3", + .replication_mode = ReplicationMode::ASYNC, + .replication_ip_address = "127.1.1.1", + .replication_port = 1110, + }}; + + memgraph::slk::Loopback loopback; + auto builder = loopback.GetBuilder(); + memgraph::slk::Save(original, builder); + + ReplicationClientInfoVec decoded; + auto reader = loopback.GetReader(); + memgraph::slk::Load(&decoded, reader); + + ASSERT_EQ(original, decoded); +} diff --git a/tests/unit/storage_rocks.cpp b/tests/unit/storage_rocks.cpp index 6d5db7d75..5cdaf4691 100644 --- a/tests/unit/storage_rocks.cpp +++ b/tests/unit/storage_rocks.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -30,6 +30,8 @@ #include "storage/v2/view.hpp" #include "utils/rocksdb_serialization.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; + // NOLINTNEXTLINE(google-build-using-namespace) using namespace memgraph::storage; @@ -57,14 +59,14 @@ class RocksDBStorageTest : public ::testing::TestWithParam<bool> { }; TEST_F(RocksDBStorageTest, SerializeVertexGID) { - auto acc = storage->Access(); + auto acc = storage->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); auto gid = vertex.Gid(); ASSERT_EQ(memgraph::utils::SerializeVertex(*vertex.vertex_), "|" + gid.ToString()); } TEST_F(RocksDBStorageTest, SerializeVertexGIDLabels) { - auto acc = storage->Access(); + auto acc = storage->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); auto ser_player_label = acc->NameToLabel("Player"); auto ser_user_label = acc->NameToLabel("User"); diff --git a/tests/unit/storage_v2.cpp b/tests/unit/storage_v2.cpp index 74ac5b3fa..7db51ddd4 100644 --- a/tests/unit/storage_v2.cpp +++ b/tests/unit/storage_v2.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -23,6 +23,8 @@ #include "storage/v2/vertex_accessor.hpp" #include "storage_test_utils.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; + using testing::Types; using testing::UnorderedElementsAre; @@ -53,7 +55,7 @@ TYPED_TEST_CASE(StorageV2Test, StorageTypes); TYPED_TEST(StorageV2Test, Commit) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value()); @@ -63,7 +65,7 @@ TYPED_TEST(StorageV2Test, Commit) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); ASSERT_TRUE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value()); EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 1U); ASSERT_TRUE(acc->FindVertex(gid, memgraph::storage::View::NEW).has_value()); @@ -71,7 +73,7 @@ TYPED_TEST(StorageV2Test, Commit) { acc->Abort(); } { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -87,7 +89,7 @@ TYPED_TEST(StorageV2Test, Commit) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value()); EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U); ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::NEW).has_value()); @@ -100,7 +102,7 @@ TYPED_TEST(StorageV2Test, Commit) { TYPED_TEST(StorageV2Test, Abort) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value()); @@ -110,7 +112,7 @@ TYPED_TEST(StorageV2Test, Abort) { acc->Abort(); } { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value()); EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U); ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::NEW).has_value()); @@ -124,7 +126,7 @@ TYPED_TEST(StorageV2Test, AdvanceCommandCommit) { memgraph::storage::Gid gid1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex1 = acc->CreateVertex(); gid1 = vertex1.Gid(); @@ -148,7 +150,7 @@ TYPED_TEST(StorageV2Test, AdvanceCommandCommit) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); ASSERT_TRUE(acc->FindVertex(gid1, memgraph::storage::View::OLD).has_value()); ASSERT_TRUE(acc->FindVertex(gid1, memgraph::storage::View::NEW).has_value()); ASSERT_TRUE(acc->FindVertex(gid2, memgraph::storage::View::OLD).has_value()); @@ -164,7 +166,7 @@ TYPED_TEST(StorageV2Test, AdvanceCommandAbort) { memgraph::storage::Gid gid1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex1 = acc->CreateVertex(); gid1 = vertex1.Gid(); @@ -188,7 +190,7 @@ TYPED_TEST(StorageV2Test, AdvanceCommandAbort) { acc->Abort(); } { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); ASSERT_FALSE(acc->FindVertex(gid1, memgraph::storage::View::OLD).has_value()); ASSERT_FALSE(acc->FindVertex(gid1, memgraph::storage::View::NEW).has_value()); ASSERT_FALSE(acc->FindVertex(gid2, memgraph::storage::View::OLD).has_value()); @@ -201,8 +203,8 @@ TYPED_TEST(StorageV2Test, AdvanceCommandAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(StorageV2Test, SnapshotIsolation) { - auto acc1 = this->store->Access(); - auto acc2 = this->store->Access(); + auto acc1 = this->store->Access(ReplicationRole::MAIN); + auto acc2 = this->store->Access(ReplicationRole::MAIN); auto vertex = acc1->CreateVertex(); auto gid = vertex.Gid(); @@ -223,7 +225,7 @@ TYPED_TEST(StorageV2Test, SnapshotIsolation) { acc2->Abort(); - auto acc3 = this->store->Access(); + auto acc3 = this->store->Access(ReplicationRole::MAIN); ASSERT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::OLD).has_value()); EXPECT_EQ(CountVertices(*acc3, memgraph::storage::View::OLD), 1U); ASSERT_TRUE(acc3->FindVertex(gid, memgraph::storage::View::NEW).has_value()); @@ -235,7 +237,7 @@ TYPED_TEST(StorageV2Test, SnapshotIsolation) { TYPED_TEST(StorageV2Test, AccessorMove) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); @@ -254,7 +256,7 @@ TYPED_TEST(StorageV2Test, AccessorMove) { ASSERT_FALSE(moved->Commit().HasError()); } { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); ASSERT_TRUE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value()); EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 1U); ASSERT_TRUE(acc->FindVertex(gid, memgraph::storage::View::NEW).has_value()); @@ -266,8 +268,8 @@ TYPED_TEST(StorageV2Test, AccessorMove) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(StorageV2Test, VertexDeleteCommit) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); - auto acc1 = this->store->Access(); // read transaction - auto acc2 = this->store->Access(); // write transaction + auto acc1 = this->store->Access(ReplicationRole::MAIN); // read transaction + auto acc2 = this->store->Access(ReplicationRole::MAIN); // write transaction // Create the vertex in transaction 2 { @@ -280,8 +282,8 @@ TYPED_TEST(StorageV2Test, VertexDeleteCommit) { ASSERT_FALSE(acc2->Commit().HasError()); } - auto acc3 = this->store->Access(); // read transaction - auto acc4 = this->store->Access(); // write transaction + auto acc3 = this->store->Access(ReplicationRole::MAIN); // read transaction + auto acc4 = this->store->Access(ReplicationRole::MAIN); // write transaction // Check whether the vertex exists in transaction 1 ASSERT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD).has_value()); @@ -314,7 +316,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteCommit) { ASSERT_FALSE(acc4->Commit().HasError()); } - auto acc5 = this->store->Access(); // read transaction + auto acc5 = this->store->Access(ReplicationRole::MAIN); // read transaction // Check whether the vertex exists in transaction 1 ASSERT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD).has_value()); @@ -339,8 +341,8 @@ TYPED_TEST(StorageV2Test, VertexDeleteCommit) { TYPED_TEST(StorageV2Test, VertexDeleteAbort) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); - auto acc1 = this->store->Access(); // read transaction - auto acc2 = this->store->Access(); // write transaction + auto acc1 = this->store->Access(ReplicationRole::MAIN); // read transaction + auto acc2 = this->store->Access(ReplicationRole::MAIN); // write transaction // Create the vertex in transaction 2 { @@ -353,8 +355,8 @@ TYPED_TEST(StorageV2Test, VertexDeleteAbort) { ASSERT_FALSE(acc2->Commit().HasError()); } - auto acc3 = this->store->Access(); // read transaction - auto acc4 = this->store->Access(); // write transaction (aborted) + auto acc3 = this->store->Access(ReplicationRole::MAIN); // read transaction + auto acc4 = this->store->Access(ReplicationRole::MAIN); // write transaction (aborted) // Check whether the vertex exists in transaction 1 ASSERT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD).has_value()); @@ -387,8 +389,8 @@ TYPED_TEST(StorageV2Test, VertexDeleteAbort) { acc4->Abort(); } - auto acc5 = this->store->Access(); // read transaction - auto acc6 = this->store->Access(); // write transaction + auto acc5 = this->store->Access(ReplicationRole::MAIN); // read transaction + auto acc6 = this->store->Access(ReplicationRole::MAIN); // write transaction // Check whether the vertex exists in transaction 1 ASSERT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD).has_value()); @@ -427,7 +429,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteAbort) { ASSERT_FALSE(acc6->Commit().HasError()); } - auto acc7 = this->store->Access(); // read transaction + auto acc7 = this->store->Access(ReplicationRole::MAIN); // read transaction // Check whether the vertex exists in transaction 1 ASSERT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD).has_value()); @@ -466,14 +468,14 @@ TYPED_TEST(StorageV2Test, VertexDeleteSerializationError) { // Create vertex { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); ASSERT_FALSE(acc->Commit().HasError()); } - auto acc1 = this->store->Access(); - auto acc2 = this->store->Access(); + auto acc1 = this->store->Access(ReplicationRole::MAIN); + auto acc2 = this->store->Access(ReplicationRole::MAIN); // Delete vertex in accessor 1 { @@ -546,7 +548,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteSerializationError) { // Check whether the vertex exists { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_FALSE(vertex); EXPECT_EQ(CountVertices(*acc, memgraph::storage::View::OLD), 0U); @@ -563,7 +565,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteSpecialCases) { // Create vertex and delete it in the same transaction, but abort the // transaction { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid1 = vertex.Gid(); ASSERT_FALSE(acc->FindVertex(gid1, memgraph::storage::View::OLD).has_value()); @@ -583,7 +585,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteSpecialCases) { // Create vertex and delete it in the same transaction { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid2 = vertex.Gid(); ASSERT_FALSE(acc->FindVertex(gid2, memgraph::storage::View::OLD).has_value()); @@ -603,7 +605,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteSpecialCases) { // Check whether the vertices exist { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); ASSERT_FALSE(acc->FindVertex(gid1, memgraph::storage::View::OLD).has_value()); ASSERT_FALSE(acc->FindVertex(gid1, memgraph::storage::View::NEW).has_value()); ASSERT_FALSE(acc->FindVertex(gid2, memgraph::storage::View::OLD).has_value()); @@ -620,7 +622,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteLabel) { // Create the vertex { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value()); @@ -630,7 +632,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteLabel) { // Add label, delete the vertex and check the label API (same command) { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -684,7 +686,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteLabel) { // Add label, delete the vertex and check the label API (different command) { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -775,7 +777,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteProperty) { // Create the vertex { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); ASSERT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD).has_value()); @@ -785,7 +787,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteProperty) { // Set property, delete the vertex and check the property API (same command) { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -833,7 +835,7 @@ TYPED_TEST(StorageV2Test, VertexDeleteProperty) { // Set property, delete the vertex and check the property API (different // command) { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -916,7 +918,7 @@ TYPED_TEST(StorageV2Test, VertexLabelCommit) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); @@ -948,7 +950,7 @@ TYPED_TEST(StorageV2Test, VertexLabelCommit) { spdlog::debug("Commit done"); } { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -977,7 +979,7 @@ TYPED_TEST(StorageV2Test, VertexLabelCommit) { spdlog::debug("Abort done"); } { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1009,7 +1011,7 @@ TYPED_TEST(StorageV2Test, VertexLabelCommit) { spdlog::debug("Commit done"); } { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1036,7 +1038,7 @@ TYPED_TEST(StorageV2Test, VertexLabelAbort) { // Create the vertex. { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); ASSERT_FALSE(acc->Commit().HasError()); @@ -1044,7 +1046,7 @@ TYPED_TEST(StorageV2Test, VertexLabelAbort) { // Add label 5, but abort the transaction. { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1077,7 +1079,7 @@ TYPED_TEST(StorageV2Test, VertexLabelAbort) { // Check that label 5 doesn't exist. { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1098,7 +1100,7 @@ TYPED_TEST(StorageV2Test, VertexLabelAbort) { // Add label 5. { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1131,7 +1133,7 @@ TYPED_TEST(StorageV2Test, VertexLabelAbort) { // Check that label 5 exists. { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1161,7 +1163,7 @@ TYPED_TEST(StorageV2Test, VertexLabelAbort) { // Remove label 5, but abort the transaction. { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1194,7 +1196,7 @@ TYPED_TEST(StorageV2Test, VertexLabelAbort) { // Check that label 5 exists. { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1224,7 +1226,7 @@ TYPED_TEST(StorageV2Test, VertexLabelAbort) { // Remove label 5. { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1257,7 +1259,7 @@ TYPED_TEST(StorageV2Test, VertexLabelAbort) { // Check that label 5 doesn't exist. { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1281,14 +1283,14 @@ TYPED_TEST(StorageV2Test, VertexLabelAbort) { TYPED_TEST(StorageV2Test, VertexLabelSerializationError) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); ASSERT_FALSE(acc->Commit().HasError()); } - auto acc1 = this->store->Access(); - auto acc2 = this->store->Access(); + auto acc1 = this->store->Access(ReplicationRole::MAIN); + auto acc2 = this->store->Access(ReplicationRole::MAIN); // Add label 1 in accessor 1. { @@ -1371,7 +1373,7 @@ TYPED_TEST(StorageV2Test, VertexLabelSerializationError) { // Check which labels exist. { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1402,7 +1404,7 @@ TYPED_TEST(StorageV2Test, VertexLabelSerializationError) { TYPED_TEST(StorageV2Test, VertexPropertyCommit) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); @@ -1440,7 +1442,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyCommit) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1468,7 +1470,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyCommit) { acc->Abort(); } { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1499,7 +1501,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyCommit) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1525,7 +1527,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyAbort) { // Create the vertex. { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); ASSERT_FALSE(acc->Commit().HasError()); @@ -1533,7 +1535,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyAbort) { // Set property 5 to "nandare", but abort the transaction. { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1573,7 +1575,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyAbort) { // Check that property 5 is null. { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1594,7 +1596,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyAbort) { // Set property 5 to "nandare". { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1634,7 +1636,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyAbort) { // Check that property 5 is "nandare". { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1664,7 +1666,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyAbort) { // Set property 5 to null, but abort the transaction. { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1705,7 +1707,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyAbort) { // Check that property 5 is "nandare". { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1735,7 +1737,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyAbort) { // Set property 5 to null. { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1776,7 +1778,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyAbort) { // Check that property 5 is null. { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1800,14 +1802,14 @@ TYPED_TEST(StorageV2Test, VertexPropertyAbort) { TYPED_TEST(StorageV2Test, VertexPropertySerializationError) { memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); ASSERT_FALSE(acc->Commit().HasError()); } - auto acc1 = this->store->Access(); - auto acc2 = this->store->Access(); + auto acc1 = this->store->Access(ReplicationRole::MAIN); + auto acc2 = this->store->Access(ReplicationRole::MAIN); // Set property 1 to 123 in accessor 1. { @@ -1884,7 +1886,7 @@ TYPED_TEST(StorageV2Test, VertexPropertySerializationError) { // Check which properties exist. { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1913,7 +1915,7 @@ TYPED_TEST(StorageV2Test, VertexPropertySerializationError) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(StorageV2Test, VertexLabelPropertyMixed) { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); auto label = acc->NameToLabel("label5"); @@ -2155,7 +2157,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyClear) { auto property1 = this->store->NameToProperty("property1"); auto property2 = this->store->NameToProperty("property2"); { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); @@ -2166,7 +2168,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyClear) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -2198,7 +2200,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyClear) { acc->Abort(); } { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -2209,7 +2211,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyClear) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -2242,7 +2244,7 @@ TYPED_TEST(StorageV2Test, VertexPropertyClear) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -2258,7 +2260,7 @@ TYPED_TEST(StorageV2Test, VertexNonexistentLabelPropertyEdgeAPI) { auto label = this->store->NameToLabel("label"); auto property = this->store->NameToProperty("property"); - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); // Check state before (OLD view). @@ -2314,8 +2316,8 @@ TYPED_TEST(StorageV2Test, VertexNonexistentLabelPropertyEdgeAPI) { } TYPED_TEST(StorageV2Test, VertexVisibilitySingleTransaction) { - auto acc1 = this->store->Access(); - auto acc2 = this->store->Access(); + auto acc1 = this->store->Access(ReplicationRole::MAIN); + auto acc2 = this->store->Access(ReplicationRole::MAIN); auto vertex = acc1->CreateVertex(); auto gid = vertex.Gid(); @@ -2334,7 +2336,7 @@ TYPED_TEST(StorageV2Test, VertexVisibilitySingleTransaction) { ASSERT_TRUE(vertex.SetProperty(acc1->NameToProperty("meaning"), memgraph::storage::PropertyValue(42)).HasValue()); - auto acc3 = this->store->Access(); + auto acc3 = this->store->Access(ReplicationRole::MAIN); EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); @@ -2371,8 +2373,8 @@ TYPED_TEST(StorageV2Test, VertexVisibilityMultipleTransactions) { memgraph::storage::Gid gid; { - auto acc1 = this->store->Access(); - auto acc2 = this->store->Access(); + auto acc1 = this->store->Access(ReplicationRole::MAIN); + auto acc2 = this->store->Access(ReplicationRole::MAIN); auto vertex = acc1->CreateVertex(); gid = vertex.Gid(); @@ -2401,8 +2403,8 @@ TYPED_TEST(StorageV2Test, VertexVisibilityMultipleTransactions) { } { - auto acc1 = this->store->Access(); - auto acc2 = this->store->Access(); + auto acc1 = this->store->Access(ReplicationRole::MAIN); + auto acc2 = this->store->Access(ReplicationRole::MAIN); auto vertex = acc1->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -2435,7 +2437,7 @@ TYPED_TEST(StorageV2Test, VertexVisibilityMultipleTransactions) { ASSERT_TRUE(vertex->SetProperty(acc1->NameToProperty("meaning"), memgraph::storage::PropertyValue(42)).HasValue()); - auto acc3 = this->store->Access(); + auto acc3 = this->store->Access(ReplicationRole::MAIN); EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); @@ -2477,15 +2479,15 @@ TYPED_TEST(StorageV2Test, VertexVisibilityMultipleTransactions) { } { - auto acc1 = this->store->Access(); - auto acc2 = this->store->Access(); + auto acc1 = this->store->Access(ReplicationRole::MAIN); + auto acc2 = this->store->Access(ReplicationRole::MAIN); auto vertex = acc1->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); ASSERT_TRUE(acc1->DeleteVertex(&*vertex).HasValue()); - auto acc3 = this->store->Access(); + auto acc3 = this->store->Access(ReplicationRole::MAIN); EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); @@ -2527,7 +2529,7 @@ TYPED_TEST(StorageV2Test, VertexVisibilityMultipleTransactions) { } { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); EXPECT_TRUE(acc->FindVertex(gid, memgraph::storage::View::OLD)); EXPECT_TRUE(acc->FindVertex(gid, memgraph::storage::View::NEW)); @@ -2541,15 +2543,15 @@ TYPED_TEST(StorageV2Test, VertexVisibilityMultipleTransactions) { } { - auto acc1 = this->store->Access(); - auto acc2 = this->store->Access(); + auto acc1 = this->store->Access(ReplicationRole::MAIN); + auto acc2 = this->store->Access(ReplicationRole::MAIN); auto vertex = acc1->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); ASSERT_TRUE(acc1->DeleteVertex(&*vertex).HasValue()); - auto acc3 = this->store->Access(); + auto acc3 = this->store->Access(ReplicationRole::MAIN); EXPECT_TRUE(acc1->FindVertex(gid, memgraph::storage::View::OLD)); EXPECT_FALSE(acc1->FindVertex(gid, memgraph::storage::View::NEW)); @@ -2591,7 +2593,7 @@ TYPED_TEST(StorageV2Test, VertexVisibilityMultipleTransactions) { } { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); EXPECT_FALSE(acc->FindVertex(gid, memgraph::storage::View::OLD)); EXPECT_FALSE(acc->FindVertex(gid, memgraph::storage::View::NEW)); @@ -2613,14 +2615,14 @@ TYPED_TEST(StorageV2Test, DeletedVertexAccessor) { std::optional<memgraph::storage::Gid> gid; // Create the vertex { - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); ASSERT_FALSE(vertex.SetProperty(property, property_value).HasError()); ASSERT_FALSE(acc->Commit().HasError()); } - auto acc = this->store->Access(); + auto acc = this->store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(*gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto maybe_deleted_vertex = acc->DeleteVertex(&*vertex); diff --git a/tests/unit/storage_v2_constraints.cpp b/tests/unit/storage_v2_constraints.cpp index dfee45a0e..7f03f40d1 100644 --- a/tests/unit/storage_v2_constraints.cpp +++ b/tests/unit/storage_v2_constraints.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -23,6 +23,8 @@ #include "disk_test_utils.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; + // NOLINTNEXTLINE(google-build-using-namespace) using namespace memgraph::storage; @@ -81,7 +83,7 @@ TYPED_TEST_CASE(ConstraintsTest, StorageTypes); // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateAndDrop) { { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_EQ(acc->ListAllConstraints().existence.size(), 0); ASSERT_NO_ERROR(acc->Commit()); } @@ -92,7 +94,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateAndDrop) { ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(acc->ListAllConstraints().existence, UnorderedElementsAre(std::make_pair(this->label1, this->prop1))); ASSERT_NO_ERROR(acc->Commit()); } @@ -103,7 +105,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateAndDrop) { ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(acc->ListAllConstraints().existence, UnorderedElementsAre(std::make_pair(this->label1, this->prop1))); ASSERT_NO_ERROR(acc->Commit()); } @@ -114,7 +116,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateAndDrop) { ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(acc->ListAllConstraints().existence, UnorderedElementsAre(std::make_pair(this->label1, this->prop1), std::make_pair(this->label2, this->prop1))); ASSERT_NO_ERROR(acc->Commit()); @@ -130,7 +132,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateAndDrop) { ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(acc->ListAllConstraints().existence, UnorderedElementsAre(std::make_pair(this->label2, this->prop1))); ASSERT_NO_ERROR(acc->Commit()); } @@ -145,7 +147,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateAndDrop) { ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_EQ(acc->ListAllConstraints().existence.size(), 0); ASSERT_NO_ERROR(acc->Commit()); } @@ -156,7 +158,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateAndDrop) { ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(acc->ListAllConstraints().existence, UnorderedElementsAre(std::make_pair(this->label2, this->prop1))); ASSERT_NO_ERROR(acc->Commit()); } @@ -165,7 +167,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateAndDrop) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateFailure1) { { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); ASSERT_NO_ERROR(acc->Commit()); @@ -180,7 +182,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateFailure1) { ASSERT_FALSE(unique_acc->Commit().HasError()); // TODO: Check if we are committing here? } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); for (auto vertex : acc->Vertices(View::OLD)) { ASSERT_NO_ERROR(acc->DeleteVertex(&vertex)); } @@ -197,7 +199,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateFailure1) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateFailure2) { { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); ASSERT_NO_ERROR(acc->Commit()); @@ -212,7 +214,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsCreateFailure2) { ASSERT_FALSE(unique_acc->Commit().HasError()); // TODO: Check if we are committing here? } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); for (auto vertex : acc->Vertices(View::OLD)) { ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1))); } @@ -236,7 +238,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsViolationOnCommit) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); @@ -248,7 +250,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsViolationOnCommit) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1))); @@ -256,7 +258,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsViolationOnCommit) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); for (auto vertex : acc->Vertices(View::OLD)) { ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue())); } @@ -269,7 +271,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsViolationOnCommit) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); for (auto vertex : acc->Vertices(View::OLD)) { ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue())); } @@ -285,7 +287,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsViolationOnCommit) { ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); ASSERT_NO_ERROR(acc->Commit()); @@ -295,7 +297,7 @@ TYPED_TEST(ConstraintsTest, ExistenceConstraintsViolationOnCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateAndDropAndList) { { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_EQ(acc->ListAllConstraints().unique.size(), 0); ASSERT_NO_ERROR(acc->Commit()); } @@ -307,7 +309,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateAndDropAndList) { ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(acc->ListAllConstraints().unique, UnorderedElementsAre(std::make_pair(this->label1, std::set<PropertyId>{this->prop1}))); ASSERT_NO_ERROR(acc->Commit()); @@ -320,7 +322,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateAndDropAndList) { ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(acc->ListAllConstraints().unique, UnorderedElementsAre(std::make_pair(this->label1, std::set<PropertyId>{this->prop1}))); ASSERT_NO_ERROR(acc->Commit()); @@ -333,7 +335,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateAndDropAndList) { ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(acc->ListAllConstraints().unique, UnorderedElementsAre(std::make_pair(this->label1, std::set<PropertyId>{this->prop1}), std::make_pair(this->label2, std::set<PropertyId>{this->prop1}))); @@ -352,7 +354,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateAndDropAndList) { ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(acc->ListAllConstraints().unique, UnorderedElementsAre(std::make_pair(this->label2, std::set<PropertyId>{this->prop1}))); ASSERT_NO_ERROR(acc->Commit()); @@ -370,7 +372,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateAndDropAndList) { ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_EQ(acc->ListAllConstraints().unique.size(), 0); ASSERT_NO_ERROR(acc->Commit()); } @@ -381,7 +383,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateAndDropAndList) { EXPECT_EQ(res.GetValue(), UniqueConstraints::CreationStatus::SUCCESS); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(acc->ListAllConstraints().unique, UnorderedElementsAre(std::make_pair(this->label2, std::set<PropertyId>{this->prop1}))); ASSERT_NO_ERROR(acc->Commit()); @@ -391,7 +393,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateAndDropAndList) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateFailure1) { { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); for (int i = 0; i < 2; ++i) { auto vertex1 = acc->CreateVertex(); ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); @@ -411,7 +413,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateFailure1) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); for (auto vertex : acc->Vertices(View::OLD)) { ASSERT_NO_ERROR(acc->DeleteVertex(&vertex)); } @@ -430,7 +432,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateFailure1) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateFailure2) { { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); for (int i = 0; i < 2; ++i) { auto vertex = acc->CreateVertex(); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); @@ -450,7 +452,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsCreateFailure2) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); int value = 0; for (auto vertex : acc->Vertices(View::OLD)) { ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(value))); @@ -473,7 +475,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsNoViolation1) { Gid gid1; Gid gid2; { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = acc->CreateVertex(); auto vertex2 = acc->CreateVertex(); gid1 = vertex1.Gid(); @@ -493,7 +495,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsNoViolation1) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = acc->FindVertex(gid1, View::OLD); auto vertex2 = acc->FindVertex(gid2, View::OLD); @@ -505,7 +507,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsNoViolation1) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = acc->FindVertex(gid1, View::OLD); auto vertex2 = acc->FindVertex(gid2, View::OLD); ASSERT_NO_ERROR(vertex1->SetProperty(this->prop1, PropertyValue(2))); @@ -528,8 +530,8 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsNoViolation2) { // tx1: B---SP(v1, 1)---SP(v1, 2)---OK-- // tx2: -B---SP(v2, 2)---SP(v2, 1)---OK- - auto acc1 = this->storage->Access(); - auto acc2 = this->storage->Access(); + auto acc1 = this->storage->Access(ReplicationRole::MAIN); + auto acc2 = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = acc1->CreateVertex(); auto vertex2 = acc2->CreateVertex(); @@ -561,7 +563,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsNoViolation3) { // tx2: --------------------B---SP(v1, 2)---OK-- // tx3: ---------------------B---SP(v2, 1)---OK- - auto acc1 = this->storage->Access(); + auto acc1 = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = acc1->CreateVertex(); auto gid = vertex1.Gid(); @@ -570,8 +572,8 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsNoViolation3) { ASSERT_NO_ERROR(acc1->Commit()); - auto acc2 = this->storage->Access(); - auto acc3 = this->storage->Access(); + auto acc2 = this->storage->Access(ReplicationRole::MAIN); + auto acc3 = this->storage->Access(ReplicationRole::MAIN); auto vertex2 = acc2->FindVertex(gid, View::NEW); // vertex1 == vertex2 auto vertex3 = acc3->CreateVertex(); @@ -599,7 +601,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsNoViolation4) { // tx2: --------------------B---SP(v2, 1)-----OK- // tx3: ---------------------B---SP(v1, 2)---OK-- - auto acc1 = this->storage->Access(); + auto acc1 = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = acc1->CreateVertex(); auto gid = vertex1.Gid(); @@ -607,8 +609,8 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsNoViolation4) { ASSERT_NO_ERROR(acc1->Commit()); - auto acc2 = this->storage->Access(); - auto acc3 = this->storage->Access(); + auto acc2 = this->storage->Access(ReplicationRole::MAIN); + auto acc3 = this->storage->Access(ReplicationRole::MAIN); auto vertex2 = acc2->CreateVertex(); auto vertex3 = acc3->FindVertex(gid, View::NEW); @@ -632,7 +634,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsViolationOnCommit1) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = acc->CreateVertex(); auto vertex2 = acc->CreateVertex(); ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); @@ -663,7 +665,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsViolationOnCommit2) { // tx2: -------------------------------B---SP(v1, 3)---OK---- // tx3: --------------------------------B---SP(v2, 3)---FAIL- - auto acc1 = this->storage->Access(); + auto acc1 = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = acc1->CreateVertex(); auto vertex2 = acc1->CreateVertex(); auto gid1 = vertex1.Gid(); @@ -676,8 +678,8 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsViolationOnCommit2) { ASSERT_NO_ERROR(acc1->Commit()); - auto acc2 = this->storage->Access(); - auto acc3 = this->storage->Access(); + auto acc2 = this->storage->Access(ReplicationRole::MAIN); + auto acc3 = this->storage->Access(ReplicationRole::MAIN); auto vertex3 = acc2->FindVertex(gid1, View::NEW); // vertex3 == vertex1 auto vertex4 = acc3->FindVertex(gid2, View::NEW); // vertex4 == vertex2 @@ -709,7 +711,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsViolationOnCommit3) { // tx2: -------------------------------B---SP(v1, 2)---FAIL-- // tx3: --------------------------------B---SP(v2, 1)---FAIL- - auto acc1 = this->storage->Access(); + auto acc1 = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = acc1->CreateVertex(); auto vertex2 = acc1->CreateVertex(); auto gid1 = vertex1.Gid(); @@ -722,8 +724,8 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsViolationOnCommit3) { ASSERT_NO_ERROR(acc1->Commit()); - auto acc2 = this->storage->Access(); - auto acc3 = this->storage->Access(); + auto acc2 = this->storage->Access(ReplicationRole::MAIN); + auto acc3 = this->storage->Access(ReplicationRole::MAIN); auto vertex3 = acc2->FindVertex(gid1, View::OLD); // vertex3 == vertex1 auto vertex4 = acc3->FindVertex(gid2, View::OLD); // vertex4 == vertex2 @@ -762,7 +764,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsLabelAlteration) { { // B---AL(v2)---SP(v1, 1)---SP(v2, 1)---OK - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = acc->CreateVertex(); auto vertex2 = acc->CreateVertex(); gid1 = vertex1.Gid(); @@ -782,8 +784,8 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsLabelAlteration) { // tx1: B---AL(v1)-----OK- // tx2: -B---RL(v2)---OK-- - auto acc1 = this->storage->Access(); - auto acc2 = this->storage->Access(); + auto acc1 = this->storage->Access(ReplicationRole::MAIN); + auto acc2 = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = acc1->FindVertex(gid1, View::OLD); auto vertex2 = acc2->FindVertex(gid2, View::OLD); @@ -811,7 +813,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsLabelAlteration) { { // B---AL(v2)---FAIL - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex2 = acc->FindVertex(gid2, View::OLD); ASSERT_NO_ERROR(vertex2->AddLabel(this->label1)); @@ -824,7 +826,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsLabelAlteration) { { // B---RL(v1)---OK - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = acc->FindVertex(gid1, View::OLD); ASSERT_NO_ERROR(vertex1->RemoveLabel(this->label1)); ASSERT_NO_ERROR(acc->Commit()); @@ -834,8 +836,8 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsLabelAlteration) { // tx1: B---AL(v1)-----FAIL // tx2: -B---AL(v2)---OK--- - auto acc1 = this->storage->Access(); - auto acc2 = this->storage->Access(); + auto acc1 = this->storage->Access(ReplicationRole::MAIN); + auto acc2 = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = acc1->FindVertex(gid1, View::OLD); auto vertex2 = acc2->FindVertex(gid2, View::OLD); @@ -911,7 +913,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsPropertySetSize) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(acc->ListAllConstraints().unique, UnorderedElementsAre(std::make_pair(this->label1, properties))); ASSERT_NO_ERROR(acc->Commit()); } @@ -922,7 +924,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsPropertySetSize) { ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); ASSERT_TRUE(acc->ListAllConstraints().unique.empty()); ASSERT_NO_ERROR(acc->Commit()); } @@ -951,7 +953,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsMultipleProperties) { Gid gid1; Gid gid2; { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = acc->CreateVertex(); auto vertex2 = acc->CreateVertex(); gid1 = vertex1.Gid(); @@ -971,7 +973,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsMultipleProperties) { // Try to change property of the second vertex so it becomes the same as the // first vertex-> It should fail. { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex2 = acc->FindVertex(gid2, View::OLD); ASSERT_NO_ERROR(vertex2->SetProperty(this->prop2, PropertyValue(2))); auto res = acc->Commit(); @@ -985,7 +987,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsMultipleProperties) { // both vertices should now be equal. However, this operation should succeed // since null value is treated as non-existing property. { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = acc->FindVertex(gid1, View::OLD); auto vertex2 = acc->FindVertex(gid2, View::OLD); ASSERT_NO_ERROR(vertex1->SetProperty(this->prop2, PropertyValue())); @@ -1005,7 +1007,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsInsertAbortInsert) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1))); @@ -1014,7 +1016,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsInsertAbortInsert) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); ASSERT_NO_ERROR(vertex.SetProperty(this->prop2, PropertyValue(2))); @@ -1034,7 +1036,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsInsertRemoveInsert) { Gid gid; { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); @@ -1044,14 +1046,14 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsInsertRemoveInsert) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, View::OLD); ASSERT_NO_ERROR(acc->DeleteVertex(&*vertex)); ASSERT_NO_ERROR(acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1))); @@ -1071,7 +1073,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsInsertRemoveAbortInsert) { Gid gid; { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); @@ -1081,14 +1083,14 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsInsertRemoveAbortInsert) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, View::OLD); ASSERT_NO_ERROR(acc->DeleteVertex(&*vertex)); acc->Abort(); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); ASSERT_NO_ERROR(vertex.SetProperty(this->prop2, PropertyValue(1))); @@ -1114,7 +1116,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsDeleteVertexSetProperty) { Gid gid1; Gid gid2; { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = acc->CreateVertex(); auto vertex2 = acc->CreateVertex(); gid1 = vertex1.Gid(); @@ -1129,8 +1131,8 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsDeleteVertexSetProperty) { } { - auto acc1 = this->storage->Access(); - auto acc2 = this->storage->Access(); + auto acc1 = this->storage->Access(ReplicationRole::MAIN); + auto acc2 = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = acc1->FindVertex(gid1, View::OLD); auto vertex2 = acc2->FindVertex(gid2, View::OLD); @@ -1156,7 +1158,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsInsertDropInsert) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1))); @@ -1172,7 +1174,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsInsertDropInsert) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); ASSERT_NO_ERROR(vertex.SetProperty(this->prop2, PropertyValue(2))); @@ -1194,7 +1196,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsComparePropertyValues) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(2))); @@ -1203,7 +1205,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsComparePropertyValues) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(1))); @@ -1212,7 +1214,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsComparePropertyValues) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); ASSERT_NO_ERROR(vertex.SetProperty(this->prop2, PropertyValue(0))); @@ -1238,7 +1240,7 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsClearOldData) { ASSERT_NO_ERROR(unique_acc->Commit()); } - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); ASSERT_NO_ERROR(vertex.SetProperty(this->prop1, PropertyValue(2))); @@ -1246,14 +1248,14 @@ TYPED_TEST(ConstraintsTest, UniqueConstraintsClearOldData) { ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); - auto acc2 = this->storage->Access(std::nullopt); + auto acc2 = this->storage->Access(ReplicationRole::MAIN); auto vertex2 = acc2->FindVertex(vertex.Gid(), memgraph::storage::View::NEW).value(); ASSERT_TRUE(vertex2.SetProperty(this->prop1, memgraph::storage::PropertyValue(2)).HasValue()); ASSERT_FALSE(acc2->Commit().HasError()); ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); - auto acc3 = this->storage->Access(std::nullopt); + auto acc3 = this->storage->Access(ReplicationRole::MAIN); auto vertex3 = acc3->FindVertex(vertex.Gid(), memgraph::storage::View::NEW).value(); ASSERT_TRUE(vertex3.SetProperty(this->prop1, memgraph::storage::PropertyValue(10)).HasValue()); ASSERT_FALSE(acc3->Commit().HasError()); diff --git a/tests/unit/storage_v2_durability_inmemory.cpp b/tests/unit/storage_v2_durability_inmemory.cpp index 8a6d26fd1..54671077f 100644 --- a/tests/unit/storage_v2_durability_inmemory.cpp +++ b/tests/unit/storage_v2_durability_inmemory.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -48,6 +48,7 @@ #include "utils/timer.hpp" #include "utils/uuid.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; using testing::Contains; using testing::UnorderedElementsAre; @@ -93,26 +94,26 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { { // Create label index. - auto unique_acc = store->UniqueAccess(); + auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateIndex(label_unindexed).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { // Create label index statistics. - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); acc->SetIndexStats(label_unindexed, memgraph::storage::LabelIndexStats{1, 2}); ASSERT_TRUE(acc->GetIndexStats(label_unindexed)); ASSERT_FALSE(acc->Commit().HasError()); } { // Create label+property index. - auto unique_acc = store->UniqueAccess(); + auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateIndex(label_indexed, property_id).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { // Create label+property index statistics. - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); acc->SetIndexStats(label_indexed, property_id, memgraph::storage::LabelPropertyIndexStats{1, 2, 3.4, 5.6, 0.0}); ASSERT_TRUE(acc->GetIndexStats(label_indexed, property_id)); ASSERT_FALSE(acc->Commit().HasError()); @@ -120,20 +121,20 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { { // Create existence constraint. - auto unique_acc = store->UniqueAccess(); + auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateExistenceConstraint(label_unindexed, property_id).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { // Create unique constraint. - auto unique_acc = store->UniqueAccess(); + auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateUniqueConstraint(label_unindexed, {property_id, property_extra}).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } // Create vertices. for (uint64_t i = 0; i < kNumBaseVertices; ++i) { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); base_vertex_gids_[i] = vertex.Gid(); if (i < kNumBaseVertices / 2) { @@ -150,7 +151,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { // Create edges. for (uint64_t i = 0; i < kNumBaseEdges; ++i) { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex1 = acc->FindVertex(base_vertex_gids_[(i / 2) % kNumBaseVertices], memgraph::storage::View::OLD); ASSERT_TRUE(vertex1); auto vertex2 = acc->FindVertex(base_vertex_gids_[(i / 3) % kNumBaseVertices], memgraph::storage::View::OLD); @@ -186,26 +187,26 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { { // Create label index. - auto unique_acc = store->UniqueAccess(); + auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateIndex(label_unused).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { // Create label index statistics. - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); acc->SetIndexStats(label_unused, memgraph::storage::LabelIndexStats{123, 9.87}); ASSERT_TRUE(acc->GetIndexStats(label_unused)); ASSERT_FALSE(acc->Commit().HasError()); } { // Create label+property index. - auto unique_acc = store->UniqueAccess(); + auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateIndex(label_indexed, property_count).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { // Create label+property index statistics. - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); acc->SetIndexStats(label_indexed, property_count, memgraph::storage::LabelPropertyIndexStats{456798, 312345, 12312312.2, 123123.2, 67876.9}); ASSERT_TRUE(acc->GetIndexStats(label_indexed, property_count)); @@ -214,25 +215,25 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { { // Create existence constraint. - auto unique_acc = store->UniqueAccess(); + auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateExistenceConstraint(label_unused, property_count).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { // Create unique constraint. - auto unique_acc = store->UniqueAccess(); + auto unique_acc = store->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateUniqueConstraint(label_unused, {property_count}).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } // Storage accessor. std::unique_ptr<memgraph::storage::Storage::Accessor> acc; - if (single_transaction) acc = store->Access(); + if (single_transaction) acc = store->Access(ReplicationRole::MAIN); // Create vertices. for (uint64_t i = 0; i < kNumExtendedVertices; ++i) { - if (!single_transaction) acc = store->Access(); + if (!single_transaction) acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); extended_vertex_gids_[i] = vertex.Gid(); if (i < kNumExtendedVertices / 2) { @@ -246,7 +247,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { // Create edges. for (uint64_t i = 0; i < kNumExtendedEdges; ++i) { - if (!single_transaction) acc = store->Access(); + if (!single_transaction) acc = store->Access(ReplicationRole::MAIN); auto vertex1 = acc->FindVertex(extended_vertex_gids_[(i / 5) % kNumExtendedVertices], memgraph::storage::View::NEW); ASSERT_TRUE(vertex1); @@ -285,7 +286,7 @@ class DurabilityTest : public ::testing::TestWithParam<bool> { auto et4 = store->NameToEdgeType("extended_et4"); // Create storage accessor. - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); // Verify indices info. { @@ -802,8 +803,8 @@ INSTANTIATE_TEST_CASE_P(EdgesWithoutProperties, DurabilityTest, ::testing::Value TEST_P(DurabilityTest, SnapshotOnExit) { // Create snapshot. { - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}; + memgraph::storage::Config config{.durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}}; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; CreateBaseDataset(db.storage(), GetParam()); @@ -818,15 +819,17 @@ TEST_P(DurabilityTest, SnapshotOnExit) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover snapshot. - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, GetParam()); // Try to use the storage. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -839,10 +842,12 @@ TEST_P(DurabilityTest, SnapshotPeriodic) { // Create snapshot. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, + .durability = {.storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT, - .snapshot_interval = std::chrono::milliseconds(2000)}}; + .snapshot_interval = std::chrono::milliseconds(2000)}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; CreateBaseDataset(db.storage(), GetParam()); @@ -855,15 +860,17 @@ TEST_P(DurabilityTest, SnapshotPeriodic) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover snapshot. - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; VerifyDataset(db.storage(), DatasetType::ONLY_BASE, GetParam()); // Try to use the storage. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -881,13 +888,16 @@ TEST_P(DurabilityTest, SnapshotFallback) { auto const snapshot_interval = std::chrono::milliseconds(3000); memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT, - .snapshot_interval = snapshot_interval, - .snapshot_retention_count = 10, // We don't anticipate that we make this many - }}; + + .durability = + { + .storage_directory = storage_directory, + .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT, + .snapshot_interval = snapshot_interval, + .snapshot_retention_count = 10, // We don't anticipate that we make this many + }, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; @@ -925,15 +935,17 @@ TEST_P(DurabilityTest, SnapshotFallback) { } // Recover snapshot. - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; VerifyDataset(db.storage(), DatasetType::ONLY_BASE, GetParam()); // Try to use the storage. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -945,12 +957,14 @@ TEST_P(DurabilityTest, SnapshotFallback) { TEST_P(DurabilityTest, SnapshotEverythingCorrupt) { // Create unrelated snapshot. { - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; - auto acc = db.storage()->Access(); + auto acc = db.Access(); for (uint64_t i = 0; i < 1000; ++i) { acc->CreateVertex(); } @@ -974,10 +988,12 @@ TEST_P(DurabilityTest, SnapshotEverythingCorrupt) { // Create snapshot. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, + .durability = {.storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT, - .snapshot_interval = std::chrono::milliseconds(2000)}}; + .snapshot_interval = std::chrono::milliseconds(2000)}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; @@ -1018,8 +1034,10 @@ TEST_P(DurabilityTest, SnapshotEverythingCorrupt) { ASSERT_DEATH( ([&]() { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; }()) // iile @@ -1031,11 +1049,13 @@ TEST_P(DurabilityTest, SnapshotEverythingCorrupt) { TEST_P(DurabilityTest, SnapshotRetention) { // Create unrelated snapshot. { - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; - auto acc = db.storage()->Access(); + auto acc = db.Access(); for (uint64_t i = 0; i < 1000; ++i) { acc->CreateVertex(); } @@ -1050,11 +1070,13 @@ TEST_P(DurabilityTest, SnapshotRetention) { // Create snapshot. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, + .durability = {.storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT, .snapshot_interval = std::chrono::milliseconds(2000), - .snapshot_retention_count = 3}}; + .snapshot_retention_count = 3}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; // Restore unrelated snapshots after the database has been started. @@ -1088,15 +1110,17 @@ TEST_P(DurabilityTest, SnapshotRetention) { } // Recover snapshot. - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; VerifyDataset(db.storage(), DatasetType::ONLY_BASE, GetParam()); // Try to use the storage. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -1108,8 +1132,10 @@ TEST_P(DurabilityTest, SnapshotRetention) { TEST_P(DurabilityTest, SnapshotMixedUUID) { // Create snapshot. { - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; CreateBaseDataset(db.storage(), GetParam()); @@ -1126,8 +1152,10 @@ TEST_P(DurabilityTest, SnapshotMixedUUID) { // Recover snapshot. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, GetParam()); @@ -1135,8 +1163,10 @@ TEST_P(DurabilityTest, SnapshotMixedUUID) { // Create another snapshot. { - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; CreateBaseDataset(db.storage(), GetParam()); @@ -1157,15 +1187,17 @@ TEST_P(DurabilityTest, SnapshotMixedUUID) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover snapshot. - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; VerifyDataset(db.storage(), DatasetType::ONLY_BASE, GetParam()); // Try to use the storage. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -1177,11 +1209,13 @@ TEST_P(DurabilityTest, SnapshotMixedUUID) { TEST_P(DurabilityTest, SnapshotBackup) { // Create snapshot. { - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; - auto acc = db.storage()->Access(); + auto acc = db.Access(); for (uint64_t i = 0; i < 1000; ++i) { acc->CreateVertex(); } @@ -1196,10 +1230,12 @@ TEST_P(DurabilityTest, SnapshotBackup) { // Start storage without recovery. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, + .durability = {.storage_directory = storage_directory, .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT, - .snapshot_interval = std::chrono::minutes(20)}}; + .snapshot_interval = std::chrono::minutes(20)}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; } @@ -1214,8 +1250,10 @@ TEST_P(DurabilityTest, SnapshotBackup) { TEST_F(DurabilityTest, SnapshotWithoutPropertiesOnEdgesRecoveryWithPropertiesOnEdges) { // Create snapshot. { - memgraph::storage::Config config{.items = {.properties_on_edges = false}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}, + .salient = {.items = {.properties_on_edges = false}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; CreateBaseDataset(db.storage(), false); @@ -1230,15 +1268,17 @@ TEST_F(DurabilityTest, SnapshotWithoutPropertiesOnEdgesRecoveryWithPropertiesOnE ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover snapshot. - memgraph::storage::Config config{.items = {.properties_on_edges = true}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = true}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, false); // Try to use the storage. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -1250,8 +1290,10 @@ TEST_F(DurabilityTest, SnapshotWithoutPropertiesOnEdgesRecoveryWithPropertiesOnE TEST_F(DurabilityTest, SnapshotWithPropertiesOnEdgesRecoveryWithoutPropertiesOnEdges) { // Create snapshot. { - memgraph::storage::Config config{.items = {.properties_on_edges = true}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}, + .salient = {.items = {.properties_on_edges = true}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; CreateBaseDataset(db.storage(), true); @@ -1269,8 +1311,10 @@ TEST_F(DurabilityTest, SnapshotWithPropertiesOnEdgesRecoveryWithoutPropertiesOnE ASSERT_DEATH( ([&]() { memgraph::storage::Config config{ - .items = {.properties_on_edges = false}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = false}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; }()) // iile @@ -1282,8 +1326,10 @@ TEST_F(DurabilityTest, SnapshotWithPropertiesOnEdgesRecoveryWithoutPropertiesOnE TEST_F(DurabilityTest, SnapshotWithPropertiesOnEdgesButUnusedRecoveryWithoutPropertiesOnEdges) { // Create snapshot. { - memgraph::storage::Config config{.items = {.properties_on_edges = true}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}, + .salient = {.items = {.properties_on_edges = true}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; CreateBaseDataset(db.storage(), true); @@ -1292,7 +1338,7 @@ TEST_F(DurabilityTest, SnapshotWithPropertiesOnEdgesButUnusedRecoveryWithoutProp VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, true); // Remove properties from edges. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); for (auto vertex : acc->Vertices(memgraph::storage::View::OLD)) { auto in_edges = vertex.InEdges(memgraph::storage::View::OLD); ASSERT_TRUE(in_edges.HasValue()); @@ -1325,15 +1371,17 @@ TEST_F(DurabilityTest, SnapshotWithPropertiesOnEdgesButUnusedRecoveryWithoutProp ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover snapshot. - memgraph::storage::Config config{.items = {.properties_on_edges = false}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = false}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, false); // Try to use the storage. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -1346,12 +1394,14 @@ TEST_P(DurabilityTest, WalBasic) { // Create WALs. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery}}; + + .durability = {.storage_directory = storage_directory, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20), + .wal_file_flush_every_n_tx = kFlushWalEvery}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; CreateBaseDataset(db.storage(), GetParam()); @@ -1364,15 +1414,17 @@ TEST_P(DurabilityTest, WalBasic) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover WALs. - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, GetParam()); // Try to use the storage. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -1385,16 +1437,18 @@ TEST_P(DurabilityTest, WalBackup) { // Create WALs. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20), - .wal_file_size_kibibytes = 1, - .wal_file_flush_every_n_tx = kFlushWalEvery}}; + + .durability = {.storage_directory = storage_directory, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20), + .wal_file_size_kibibytes = 1, + .wal_file_flush_every_n_tx = kFlushWalEvery}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; - auto acc = db.storage()->Access(); + auto acc = db.Access(); for (uint64_t i = 0; i < 1000; ++i) { acc->CreateVertex(); } @@ -1410,11 +1464,13 @@ TEST_P(DurabilityTest, WalBackup) { // Start storage without recovery. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20)}}; + + .durability = {.storage_directory = storage_directory, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20)}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; } @@ -1430,12 +1486,14 @@ TEST_P(DurabilityTest, WalAppendToExisting) { // Create WALs. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery}}; + + .durability = {.storage_directory = storage_directory, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20), + .wal_file_flush_every_n_tx = kFlushWalEvery}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; CreateBaseDataset(db.storage(), GetParam()); @@ -1449,8 +1507,10 @@ TEST_P(DurabilityTest, WalAppendToExisting) { // Recover WALs. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; VerifyDataset(db.storage(), DatasetType::ONLY_BASE, GetParam()); @@ -1459,13 +1519,15 @@ TEST_P(DurabilityTest, WalAppendToExisting) { // Recover WALs and create more WALs. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .recover_on_startup = true, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery}}; + + .durability = {.storage_directory = storage_directory, + .recover_on_startup = true, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20), + .wal_file_flush_every_n_tx = kFlushWalEvery}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; CreateExtendedDataset(db.storage()); @@ -1477,15 +1539,17 @@ TEST_P(DurabilityTest, WalAppendToExisting) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover WALs. - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, GetParam()); // Try to use the storage. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -1501,15 +1565,17 @@ TEST_P(DurabilityTest, WalCreateInSingleTransaction) { // Create WALs. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery}}; + + .durability = {.storage_directory = storage_directory, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20), + .wal_file_flush_every_n_tx = kFlushWalEvery}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto v1 = acc->CreateVertex(); gid_v1 = v1.Gid(); auto v2 = acc->CreateVertex(); @@ -1540,12 +1606,14 @@ TEST_P(DurabilityTest, WalCreateInSingleTransaction) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover WALs. - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto indices = acc->ListAllIndices(); ASSERT_EQ(indices.label.size(), 0); @@ -1628,7 +1696,7 @@ TEST_P(DurabilityTest, WalCreateInSingleTransaction) { // Try to use the storage. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -1641,50 +1709,52 @@ TEST_P(DurabilityTest, WalCreateAndRemoveEverything) { // Create WALs. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery}}; + + .durability = {.storage_directory = storage_directory, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20), + .wal_file_flush_every_n_tx = kFlushWalEvery}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; CreateBaseDataset(db.storage(), GetParam()); CreateExtendedDataset(db.storage()); auto indices = [&] { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto res = acc->ListAllIndices(); - acc->Commit(); + (void)acc->Commit(); return res; }(); // iile for (const auto &index : indices.label) { - auto unique_acc = db.storage()->UniqueAccess(); + auto unique_acc = db.UniqueAccess(); ASSERT_FALSE(unique_acc->DropIndex(index).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } for (const auto &index : indices.label_property) { - auto unique_acc = db.storage()->UniqueAccess(); + auto unique_acc = db.UniqueAccess(); ASSERT_FALSE(unique_acc->DropIndex(index.first, index.second).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } auto constraints = [&] { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto res = acc->ListAllConstraints(); - acc->Commit(); + (void)acc->Commit(); return res; }(); // iile for (const auto &constraint : constraints.existence) { - auto unique_acc = db.storage()->UniqueAccess(); + auto unique_acc = db.UniqueAccess(); ASSERT_FALSE(unique_acc->DropExistenceConstraint(constraint.first, constraint.second).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } for (const auto &constraint : constraints.unique) { - auto unique_acc = db.storage()->UniqueAccess(); + auto unique_acc = db.UniqueAccess(); ASSERT_EQ(unique_acc->DropUniqueConstraint(constraint.first, constraint.second), memgraph::storage::UniqueConstraints::DeletionStatus::SUCCESS); ASSERT_FALSE(unique_acc->Commit().HasError()); } - auto acc = db.storage()->Access(); + auto acc = db.Access(); for (auto vertex : acc->Vertices(memgraph::storage::View::OLD)) { ASSERT_TRUE(acc->DetachDeleteVertex(&vertex).HasValue()); } @@ -1697,12 +1767,14 @@ TEST_P(DurabilityTest, WalCreateAndRemoveEverything) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover WALs. - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto indices = acc->ListAllIndices(); ASSERT_EQ(indices.label.size(), 0); ASSERT_EQ(indices.label_property.size(), 0); @@ -1719,7 +1791,7 @@ TEST_P(DurabilityTest, WalCreateAndRemoveEverything) { // Try to use the storage. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -1735,18 +1807,21 @@ TEST_P(DurabilityTest, WalTransactionOrdering) { // Create WAL. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20), - .wal_file_size_kibibytes = 100000, - .wal_file_flush_every_n_tx = kFlushWalEvery, - }}; + + .durability = + { + .storage_directory = storage_directory, + .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20), + .wal_file_size_kibibytes = 100000, + .wal_file_flush_every_n_tx = kFlushWalEvery, + }, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; - auto acc1 = db.storage()->Access(); - auto acc2 = db.storage()->Access(); + auto acc1 = db.Access(); + auto acc2 = db.Access(); // Create vertex in transaction 2. { @@ -1756,7 +1831,7 @@ TEST_P(DurabilityTest, WalTransactionOrdering) { vertex2.SetProperty(db.storage()->NameToProperty("id"), memgraph::storage::PropertyValue(2)).HasValue()); } - auto acc3 = db.storage()->Access(); + auto acc3 = db.Access(); // Create vertex in transaction 3. { @@ -1834,12 +1909,14 @@ TEST_P(DurabilityTest, WalTransactionOrdering) { } // Recover WALs. - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; { - auto acc = db.storage()->Access(); + auto acc = db.Access(); for (auto [gid, id] : std::vector<std::pair<memgraph::storage::Gid, int64_t>>{{gid1, 1}, {gid2, 2}, {gid3, 3}}) { auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -1855,7 +1932,7 @@ TEST_P(DurabilityTest, WalTransactionOrdering) { // Try to use the storage. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -1868,19 +1945,21 @@ TEST_P(DurabilityTest, WalCreateAndRemoveOnlyBaseDataset) { // Create WALs. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery}}; + + .durability = {.storage_directory = storage_directory, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20), + .wal_file_flush_every_n_tx = kFlushWalEvery}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; CreateBaseDataset(db.storage(), GetParam()); CreateExtendedDataset(db.storage()); auto label_indexed = db.storage()->NameToLabel("base_indexed"); auto label_unindexed = db.storage()->NameToLabel("base_unindexed"); - auto acc = db.storage()->Access(); + auto acc = db.Access(); for (auto vertex : acc->Vertices(memgraph::storage::View::OLD)) { auto has_indexed = vertex.HasLabel(label_indexed, memgraph::storage::View::OLD); ASSERT_TRUE(has_indexed.HasValue()); @@ -1897,15 +1976,17 @@ TEST_P(DurabilityTest, WalCreateAndRemoveOnlyBaseDataset) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover WALs. - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; VerifyDataset(db.storage(), DatasetType::ONLY_EXTENDED_WITH_BASE_INDICES_AND_CONSTRAINTS, GetParam()); // Try to use the storage. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -1920,17 +2001,19 @@ TEST_P(DurabilityTest, WalDeathResilience) { // Create WALs. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery}}; + + .durability = {.storage_directory = storage_directory, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20), + .wal_file_flush_every_n_tx = kFlushWalEvery}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; // Create one million vertices. for (uint64_t i = 0; i < 1000000; ++i) { - auto acc = db.storage()->Access(); + auto acc = db.Access(); acc->CreateVertex(); MG_ASSERT(!acc->Commit().HasError(), "Couldn't commit transaction!"); } @@ -1957,18 +2040,21 @@ TEST_P(DurabilityTest, WalDeathResilience) { uint64_t count = 0; { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .recover_on_startup = true, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery, - }}; + + .durability = + { + .storage_directory = storage_directory, + .recover_on_startup = true, + .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20), + .wal_file_flush_every_n_tx = kFlushWalEvery, + }, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto iterable = acc->Vertices(memgraph::storage::View::OLD); for (auto it = iterable.begin(); it != iterable.end(); ++it) { ++count; @@ -1977,7 +2063,7 @@ TEST_P(DurabilityTest, WalDeathResilience) { } { - auto acc = db.storage()->Access(); + auto acc = db.Access(); for (uint64_t i = 0; i < kExtraItems; ++i) { acc->CreateVertex(); } @@ -1991,13 +2077,15 @@ TEST_P(DurabilityTest, WalDeathResilience) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover WALs. - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; { uint64_t current = 0; - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto iterable = acc->Vertices(memgraph::storage::View::OLD); for (auto it = iterable.begin(); it != iterable.end(); ++it) { ++current; @@ -2007,7 +2095,7 @@ TEST_P(DurabilityTest, WalDeathResilience) { // Try to use the storage. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -2020,16 +2108,18 @@ TEST_P(DurabilityTest, WalMissingSecond) { // Create unrelated WALs. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20), - .wal_file_size_kibibytes = 1, - .wal_file_flush_every_n_tx = kFlushWalEvery}}; + + .durability = {.storage_directory = storage_directory, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20), + .wal_file_size_kibibytes = 1, + .wal_file_flush_every_n_tx = kFlushWalEvery}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; - auto acc = db.storage()->Access(); + auto acc = db.Access(); for (uint64_t i = 0; i < 1000; ++i) { acc->CreateVertex(); } @@ -2046,26 +2136,28 @@ TEST_P(DurabilityTest, WalMissingSecond) { // Create WALs. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20), - .wal_file_size_kibibytes = 1, - .wal_file_flush_every_n_tx = kFlushWalEvery}}; + + .durability = {.storage_directory = storage_directory, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20), + .wal_file_size_kibibytes = 1, + .wal_file_flush_every_n_tx = kFlushWalEvery}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; const uint64_t kNumVertices = 1000; std::vector<memgraph::storage::Gid> gids; gids.reserve(kNumVertices); for (uint64_t i = 0; i < kNumVertices; ++i) { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); gids.push_back(vertex.Gid()); ASSERT_FALSE(acc->Commit().HasError()); } for (uint64_t i = 0; i < kNumVertices; ++i) { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD); ASSERT_TRUE(vertex); ASSERT_TRUE( @@ -2101,8 +2193,10 @@ TEST_P(DurabilityTest, WalMissingSecond) { ASSERT_DEATH( ([&]() { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; }()) // iile @@ -2115,16 +2209,18 @@ TEST_P(DurabilityTest, WalCorruptSecond) { // Create unrelated WALs. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20), - .wal_file_size_kibibytes = 1, - .wal_file_flush_every_n_tx = kFlushWalEvery}}; + + .durability = {.storage_directory = storage_directory, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20), + .wal_file_size_kibibytes = 1, + .wal_file_flush_every_n_tx = kFlushWalEvery}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; - auto acc = db.storage()->Access(); + auto acc = db.Access(); for (uint64_t i = 0; i < 1000; ++i) { acc->CreateVertex(); } @@ -2141,26 +2237,28 @@ TEST_P(DurabilityTest, WalCorruptSecond) { // Create WALs. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20), - .wal_file_size_kibibytes = 1, - .wal_file_flush_every_n_tx = kFlushWalEvery}}; + + .durability = {.storage_directory = storage_directory, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20), + .wal_file_size_kibibytes = 1, + .wal_file_flush_every_n_tx = kFlushWalEvery}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; const uint64_t kNumVertices = 1000; std::vector<memgraph::storage::Gid> gids; gids.reserve(kNumVertices); for (uint64_t i = 0; i < kNumVertices; ++i) { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); gids.push_back(vertex.Gid()); ASSERT_FALSE(acc->Commit().HasError()); } for (uint64_t i = 0; i < kNumVertices; ++i) { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->FindVertex(gids[i], memgraph::storage::View::OLD); ASSERT_TRUE(vertex); ASSERT_TRUE( @@ -2195,8 +2293,10 @@ TEST_P(DurabilityTest, WalCorruptSecond) { ASSERT_DEATH( ([&]() { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; }()) // iile @@ -2209,13 +2309,15 @@ TEST_P(DurabilityTest, WalCorruptLastTransaction) { // Create WALs { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20), - .wal_file_size_kibibytes = 1, - .wal_file_flush_every_n_tx = kFlushWalEvery}}; + + .durability = {.storage_directory = storage_directory, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20), + .wal_file_size_kibibytes = 1, + .wal_file_flush_every_n_tx = kFlushWalEvery}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; CreateBaseDataset(db.storage(), GetParam()); @@ -2236,8 +2338,10 @@ TEST_P(DurabilityTest, WalCorruptLastTransaction) { } // Recover WALs. - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; // The extended dataset shouldn't be recovered because its WAL transaction was @@ -2246,7 +2350,7 @@ TEST_P(DurabilityTest, WalCorruptLastTransaction) { // Try to use the storage. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -2259,16 +2363,18 @@ TEST_P(DurabilityTest, WalAllOperationsInSingleTransaction) { // Create WALs { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20), - .wal_file_size_kibibytes = 1, - .wal_file_flush_every_n_tx = kFlushWalEvery}}; + + .durability = {.storage_directory = storage_directory, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20), + .wal_file_size_kibibytes = 1, + .wal_file_flush_every_n_tx = kFlushWalEvery}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex1 = acc->CreateVertex(); auto vertex2 = acc->CreateVertex(); ASSERT_TRUE(vertex1.AddLabel(acc->NameToLabel("nandare")).HasValue()); @@ -2309,12 +2415,14 @@ TEST_P(DurabilityTest, WalAllOperationsInSingleTransaction) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover WALs. - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; { - auto acc = db.storage()->Access(); + auto acc = db.Access(); uint64_t count = 0; auto iterable = acc->Vertices(memgraph::storage::View::OLD); for (auto it = iterable.begin(); it != iterable.end(); ++it) { @@ -2325,7 +2433,7 @@ TEST_P(DurabilityTest, WalAllOperationsInSingleTransaction) { // Try to use the storage. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -2338,12 +2446,14 @@ TEST_P(DurabilityTest, WalAndSnapshot) { // Create snapshot and WALs. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::milliseconds(2000), - .wal_file_flush_every_n_tx = kFlushWalEvery}}; + + .durability = {.storage_directory = storage_directory, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::milliseconds(2000), + .wal_file_flush_every_n_tx = kFlushWalEvery}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; CreateBaseDataset(db.storage(), GetParam()); @@ -2357,15 +2467,17 @@ TEST_P(DurabilityTest, WalAndSnapshot) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover snapshot and WALs. - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, GetParam()); // Try to use the storage. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -2377,8 +2489,10 @@ TEST_P(DurabilityTest, WalAndSnapshot) { TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshot) { // Create snapshot. { - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; CreateBaseDataset(db.storage(), GetParam()); @@ -2392,8 +2506,10 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshot) { // Recover snapshot. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; VerifyDataset(db.storage(), DatasetType::ONLY_BASE, GetParam()); @@ -2402,13 +2518,15 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshot) { // Recover snapshot and create WALs. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .recover_on_startup = true, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery}}; + + .durability = {.storage_directory = storage_directory, + .recover_on_startup = true, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20), + .wal_file_flush_every_n_tx = kFlushWalEvery}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; CreateExtendedDataset(db.storage()); @@ -2420,15 +2538,17 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshot) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover snapshot and WALs. - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, GetParam()); // Try to use the storage. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -2440,8 +2560,10 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshot) { TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshotAndWal) { // Create snapshot. { - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; CreateBaseDataset(db.storage(), GetParam()); @@ -2455,8 +2577,10 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshotAndWal) { // Recover snapshot. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; VerifyDataset(db.storage(), DatasetType::ONLY_BASE, GetParam()); @@ -2465,13 +2589,15 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshotAndWal) { // Recover snapshot and create WALs. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .recover_on_startup = true, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery}}; + + .durability = {.storage_directory = storage_directory, + .recover_on_startup = true, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20), + .wal_file_flush_every_n_tx = kFlushWalEvery}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; CreateExtendedDataset(db.storage()); @@ -2486,17 +2612,19 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshotAndWal) { memgraph::storage::Gid vertex_gid; { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .recover_on_startup = true, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20), - .wal_file_flush_every_n_tx = kFlushWalEvery}}; + + .durability = {.storage_directory = storage_directory, + .recover_on_startup = true, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20), + .wal_file_flush_every_n_tx = kFlushWalEvery}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, GetParam()); - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); vertex_gid = vertex.Gid(); if (GetParam()) { @@ -2512,14 +2640,16 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshotAndWal) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover snapshot and WALs. - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, GetParam(), /* verify_info = */ false); { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->FindVertex(vertex_gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto labels = vertex->Labels(memgraph::storage::View::OLD); @@ -2537,7 +2667,7 @@ TEST_P(DurabilityTest, WalAndSnapshotAppendToExistingSnapshotAndWal) { // Try to use the storage. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -2550,16 +2680,18 @@ TEST_P(DurabilityTest, WalAndSnapshotWalRetention) { // Create unrelated WALs. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::minutes(20), - .wal_file_size_kibibytes = 1, - .wal_file_flush_every_n_tx = kFlushWalEvery}}; + + .durability = {.storage_directory = storage_directory, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::minutes(20), + .wal_file_size_kibibytes = 1, + .wal_file_flush_every_n_tx = kFlushWalEvery}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; - auto acc = db.storage()->Access(); + auto acc = db.Access(); for (uint64_t i = 0; i < 1000; ++i) { acc->CreateVertex(); } @@ -2578,13 +2710,15 @@ TEST_P(DurabilityTest, WalAndSnapshotWalRetention) { // Create snapshot and WALs. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::seconds(2), - .wal_file_size_kibibytes = 1, - .wal_file_flush_every_n_tx = 1}}; + + .durability = {.storage_directory = storage_directory, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::seconds(2), + .wal_file_size_kibibytes = 1, + .wal_file_flush_every_n_tx = 1}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; // Restore unrelated snapshots after the database has been started. @@ -2592,7 +2726,7 @@ TEST_P(DurabilityTest, WalAndSnapshotWalRetention) { memgraph::utils::Timer timer; // Allow at least 6 snapshots to be created. while (timer.Elapsed().count() < 13.0) { - auto acc = db.storage()->Access(); + auto acc = db.Access(); acc->CreateVertex(); ASSERT_FALSE(acc->Commit().HasError()); ++items_created; @@ -2613,11 +2747,13 @@ TEST_P(DurabilityTest, WalAndSnapshotWalRetention) { // Recover and verify data. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; - auto acc = db.storage()->Access(); + auto acc = db.Access(); for (uint64_t j = 0; j < items_created; ++j) { auto vertex = acc->FindVertex(memgraph::storage::Gid::FromUint(j), memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -2633,8 +2769,10 @@ TEST_P(DurabilityTest, WalAndSnapshotWalRetention) { ASSERT_DEATH( ([&]() { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; }()) // iile @@ -2647,14 +2785,16 @@ TEST_P(DurabilityTest, SnapshotAndWalMixedUUID) { // Create unrelated snapshot and WALs. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::seconds(2)}}; + + .durability = {.storage_directory = storage_directory, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::seconds(2)}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; - auto acc = db.storage()->Access(); + auto acc = db.Access(); for (uint64_t i = 0; i < 1000; ++i) { acc->CreateVertex(); } @@ -2670,11 +2810,13 @@ TEST_P(DurabilityTest, SnapshotAndWalMixedUUID) { // Create snapshot and WALs. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = { - .storage_directory = storage_directory, - .snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - .snapshot_interval = std::chrono::seconds(2)}}; + + .durability = {.storage_directory = storage_directory, + .snapshot_wal_mode = + memgraph::storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + .snapshot_interval = std::chrono::seconds(2)}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; CreateBaseDataset(db.storage(), GetParam()); @@ -2697,15 +2839,17 @@ TEST_P(DurabilityTest, SnapshotAndWalMixedUUID) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover snapshot and WALs. - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .recover_on_startup = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, .recover_on_startup = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, GetParam()); // Try to use the storage. { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -2718,8 +2862,10 @@ TEST_P(DurabilityTest, ParallelConstraintsRecovery) { // Create snapshot. { memgraph::storage::Config config{ - .items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true, .items_per_batch = 13}}; + + .durability = {.storage_directory = storage_directory, .snapshot_on_exit = true, .items_per_batch = 13}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; CreateBaseDataset(db.storage(), GetParam()); @@ -2734,17 +2880,19 @@ TEST_P(DurabilityTest, ParallelConstraintsRecovery) { ASSERT_EQ(GetBackupWalsList().size(), 0); // Recover snapshot. - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, - .recover_on_startup = true, - .snapshot_on_exit = false, - .items_per_batch = 13, - .allow_parallel_index_creation = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, + .recover_on_startup = true, + .snapshot_on_exit = false, + .items_per_batch = 13, + .allow_parallel_index_creation = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; memgraph::replication::ReplicationState repl_state{memgraph::storage::ReplicationStateRootPath(config)}; memgraph::dbms::Database db{config, repl_state}; VerifyDataset(db.storage(), DatasetType::BASE_WITH_EXTENDED, GetParam()); { - auto acc = db.storage()->Access(); + auto acc = db.Access(); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, db.storage()->NameToEdgeType("et")); ASSERT_TRUE(edge.HasValue()); @@ -2754,12 +2902,14 @@ TEST_P(DurabilityTest, ParallelConstraintsRecovery) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(DurabilityTest, ConstraintsRecoveryFunctionSetting) { - memgraph::storage::Config config{.items = {.properties_on_edges = GetParam()}, - .durability = {.storage_directory = storage_directory, - .recover_on_startup = true, - .snapshot_on_exit = false, - .items_per_batch = 13, - .allow_parallel_schema_creation = true}}; + memgraph::storage::Config config{ + .durability = {.storage_directory = storage_directory, + .recover_on_startup = true, + .snapshot_on_exit = false, + .items_per_batch = 13, + .allow_parallel_schema_creation = true}, + .salient = {.items = {.properties_on_edges = GetParam()}}, + }; // Create snapshot. { config.durability.recover_on_startup = false; diff --git a/tests/unit/storage_v2_edge_inmemory.cpp b/tests/unit/storage_v2_edge_inmemory.cpp index 6f3b0da02..50ae1f14f 100644 --- a/tests/unit/storage_v2_edge_inmemory.cpp +++ b/tests/unit/storage_v2_edge_inmemory.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -17,6 +17,7 @@ #include "storage/v2/inmemory/storage.hpp" #include "storage/v2/storage.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; using testing::UnorderedElementsAre; class StorageEdgeTest : public ::testing::TestWithParam<bool> {}; @@ -27,13 +28,13 @@ INSTANTIATE_TEST_CASE_P(EdgesWithoutProperties, StorageEdgeTest, ::testing::Valu // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}})); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->CreateVertex(); auto vertex_to = acc->CreateVertex(); gid_from = vertex_from.Gid(); @@ -43,7 +44,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -117,7 +118,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -218,13 +219,13 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}})); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_to = acc->CreateVertex(); auto vertex_from = acc->CreateVertex(); gid_to = vertex_to.Gid(); @@ -234,7 +235,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -302,7 +303,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -391,12 +392,12 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}})); memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertex { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid_vertex = vertex.Gid(); ASSERT_FALSE(acc->Commit().HasError()); @@ -404,7 +405,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -462,7 +463,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -537,13 +538,13 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}})); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->CreateVertex(); auto vertex_to = acc->CreateVertex(); gid_from = vertex_from.Gid(); @@ -553,7 +554,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { // Create edge, but abort the transaction { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -621,7 +622,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -650,7 +651,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -718,7 +719,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -807,13 +808,13 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}})); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_to = acc->CreateVertex(); auto vertex_from = acc->CreateVertex(); gid_to = vertex_to.Gid(); @@ -823,7 +824,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { // Create edge, but abort the transaction { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -891,7 +892,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -920,7 +921,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -988,7 +989,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -1077,12 +1078,12 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}})); memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertex { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid_vertex = vertex.Gid(); ASSERT_FALSE(acc->Commit().HasError()); @@ -1090,7 +1091,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { // Create edge, but abort the transaction { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -1148,7 +1149,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -1167,7 +1168,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -1225,7 +1226,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -1304,13 +1305,13 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}})); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->CreateVertex(); auto vertex_to = acc->CreateVertex(); gid_from = vertex_from.Gid(); @@ -1320,7 +1321,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -1388,7 +1389,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -1475,7 +1476,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { // Delete edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -1542,7 +1543,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -1573,13 +1574,13 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}})); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_to = acc->CreateVertex(); auto vertex_from = acc->CreateVertex(); gid_from = vertex_from.Gid(); @@ -1589,7 +1590,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -1657,7 +1658,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -1744,7 +1745,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { // Delete edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -1811,7 +1812,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -1842,12 +1843,12 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}})); memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertex { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid_vertex = vertex.Gid(); ASSERT_FALSE(acc->Commit().HasError()); @@ -1855,7 +1856,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -1913,7 +1914,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -1990,7 +1991,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { // Delete edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -2047,7 +2048,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -2068,13 +2069,13 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}})); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->CreateVertex(); auto vertex_to = acc->CreateVertex(); gid_from = vertex_from.Gid(); @@ -2084,7 +2085,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2152,7 +2153,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2239,7 +2240,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { // Delete the edge, but abort the transaction { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2306,7 +2307,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2393,7 +2394,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { // Delete the edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2460,7 +2461,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2491,13 +2492,13 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}})); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->CreateVertex(); auto vertex_to = acc->CreateVertex(); gid_from = vertex_from.Gid(); @@ -2507,7 +2508,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2575,7 +2576,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2662,7 +2663,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { // Delete the edge, but abort the transaction { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2729,7 +2730,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2817,7 +2818,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { // Delete the edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2884,7 +2885,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2915,12 +2916,12 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}})); memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertex { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid_vertex = vertex.Gid(); ASSERT_FALSE(acc->Commit().HasError()); @@ -2928,7 +2929,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -2986,7 +2987,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -3063,7 +3064,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { // Delete the edge, but abort the transaction { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -3120,7 +3121,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -3197,7 +3198,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { // Delete the edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -3254,7 +3255,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -3275,13 +3276,13 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}})); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create dataset { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->CreateVertex(); auto vertex_to = acc->CreateVertex(); @@ -3330,7 +3331,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) { // Detach delete vertex { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -3394,7 +3395,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) { // Check dataset { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_FALSE(vertex_from); @@ -3415,13 +3416,13 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}})); memgraph::storage::Gid gid_vertex1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_vertex2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create dataset { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex1 = acc->CreateVertex(); auto vertex2 = acc->CreateVertex(); @@ -3548,7 +3549,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) { // Detach delete vertex { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); ASSERT_TRUE(vertex1); @@ -3686,7 +3687,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) { // Check dataset { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); ASSERT_FALSE(vertex1); @@ -3745,13 +3746,13 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}})); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create dataset { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->CreateVertex(); auto vertex_to = acc->CreateVertex(); @@ -3800,7 +3801,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) { // Detach delete vertex, but abort the transaction { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -3864,7 +3865,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) { // Check dataset { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -3905,7 +3906,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) { // Detach delete vertex { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -3969,7 +3970,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) { // Check dataset { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_FALSE(vertex_from); @@ -3990,13 +3991,13 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = GetParam()}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = GetParam()}}})); memgraph::storage::Gid gid_vertex1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_vertex2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create dataset { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex1 = acc->CreateVertex(); auto vertex2 = acc->CreateVertex(); @@ -4123,7 +4124,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) { // Detach delete vertex, but abort the transaction { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); ASSERT_TRUE(vertex1); @@ -4261,7 +4262,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) { // Check dataset { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); ASSERT_TRUE(vertex1); @@ -4439,7 +4440,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) { // Detach delete vertex { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); ASSERT_TRUE(vertex1); @@ -4577,7 +4578,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) { // Check dataset { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); ASSERT_FALSE(vertex1); @@ -4636,10 +4637,10 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(StorageWithProperties, EdgePropertyCommit) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = true}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = true}}})); memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); auto et = acc->NameToEdgeType("et5"); @@ -4682,7 +4683,7 @@ TEST(StorageWithProperties, EdgePropertyCommit) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0]; @@ -4711,7 +4712,7 @@ TEST(StorageWithProperties, EdgePropertyCommit) { acc->Abort(); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0]; @@ -4743,7 +4744,7 @@ TEST(StorageWithProperties, EdgePropertyCommit) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0]; @@ -4767,12 +4768,12 @@ TEST(StorageWithProperties, EdgePropertyCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(StorageWithProperties, EdgePropertyAbort) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = true}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = true}}})); memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create the vertex. { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); auto et = acc->NameToEdgeType("et5"); @@ -4785,7 +4786,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) { // Set property 5 to "nandare", but abort the transaction. { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0]; @@ -4826,7 +4827,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) { // Check that property 5 is null. { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0]; @@ -4848,7 +4849,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) { // Set property 5 to "nandare". { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0]; @@ -4889,7 +4890,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) { // Check that property 5 is "nandare". { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0]; @@ -4920,7 +4921,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) { // Set property 5 to null, but abort the transaction. { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0]; @@ -4962,7 +4963,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) { // Check that property 5 is "nandare". { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0]; @@ -4993,7 +4994,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) { // Set property 5 to null. { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0]; @@ -5035,7 +5036,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) { // Check that property 5 is null. { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0]; @@ -5059,10 +5060,10 @@ TEST(StorageWithProperties, EdgePropertyAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(StorageWithProperties, EdgePropertySerializationError) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = true}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = true}}})); memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); auto et = acc->NameToEdgeType("et5"); @@ -5073,8 +5074,8 @@ TEST(StorageWithProperties, EdgePropertySerializationError) { ASSERT_FALSE(acc->Commit().HasError()); } - auto acc1 = store->Access(); - auto acc2 = store->Access(); + auto acc1 = store->Access(ReplicationRole::MAIN); + auto acc2 = store->Access(ReplicationRole::MAIN); // Set property 1 to 123 in accessor 1. { @@ -5139,7 +5140,7 @@ TEST(StorageWithProperties, EdgePropertySerializationError) { // Check which properties exist. { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0]; @@ -5169,12 +5170,12 @@ TEST(StorageWithProperties, EdgePropertySerializationError) { TEST(StorageWithProperties, EdgePropertyClear) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = true}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = true}}})); memgraph::storage::Gid gid; auto property1 = store->NameToProperty("property1"); auto property2 = store->NameToProperty("property2"); { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); auto et = acc->NameToEdgeType("et5"); @@ -5190,7 +5191,7 @@ TEST(StorageWithProperties, EdgePropertyClear) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0]; @@ -5223,7 +5224,7 @@ TEST(StorageWithProperties, EdgePropertyClear) { acc->Abort(); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0]; @@ -5235,7 +5236,7 @@ TEST(StorageWithProperties, EdgePropertyClear) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0]; @@ -5269,7 +5270,7 @@ TEST(StorageWithProperties, EdgePropertyClear) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0]; @@ -5285,10 +5286,10 @@ TEST(StorageWithProperties, EdgePropertyClear) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(StorageWithoutProperties, EdgePropertyAbort) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = false}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = false}}})); memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); auto et = acc->NameToEdgeType("et5"); @@ -5299,7 +5300,7 @@ TEST(StorageWithoutProperties, EdgePropertyAbort) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0]; @@ -5330,7 +5331,7 @@ TEST(StorageWithoutProperties, EdgePropertyAbort) { acc->Abort(); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0]; @@ -5354,10 +5355,10 @@ TEST(StorageWithoutProperties, EdgePropertyAbort) { TEST(StorageWithoutProperties, EdgePropertyClear) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = false}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = false}}})); memgraph::storage::Gid gid; { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); auto et = acc->NameToEdgeType("et5"); @@ -5368,7 +5369,7 @@ TEST(StorageWithoutProperties, EdgePropertyClear) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0]; @@ -5381,11 +5382,11 @@ TEST(StorageWithoutProperties, EdgePropertyClear) { TEST(StorageWithProperties, EdgeNonexistentPropertyAPI) { std::unique_ptr<memgraph::storage::Storage> store( - new memgraph::storage::InMemoryStorage({.items = {.properties_on_edges = true}})); + new memgraph::storage::InMemoryStorage({.salient = {.items = {.properties_on_edges = true}}})); auto property = store->NameToProperty("property"); - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, acc->NameToEdgeType("edge")); ASSERT_TRUE(edge.HasValue()); diff --git a/tests/unit/storage_v2_edge_ondisk.cpp b/tests/unit/storage_v2_edge_ondisk.cpp index 5d57c784f..7f3357b10 100644 --- a/tests/unit/storage_v2_edge_ondisk.cpp +++ b/tests/unit/storage_v2_edge_ondisk.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -18,6 +18,7 @@ #include "storage/v2/disk/storage.hpp" #include "storage/v2/storage.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; using testing::UnorderedElementsAre; class StorageEdgeTest : public ::testing::TestWithParam<bool> {}; @@ -30,14 +31,14 @@ const std::string testSuite = "storage_v2_edge_ondisk"; // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = GetParam(); + config.salient.items.properties_on_edges = GetParam(); std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->CreateVertex(); auto vertex_to = acc->CreateVertex(); gid_from = vertex_from.Gid(); @@ -47,7 +48,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -121,7 +122,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -223,14 +224,14 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = GetParam(); + config.salient.items.properties_on_edges = GetParam(); std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_to = acc->CreateVertex(); auto vertex_from = acc->CreateVertex(); gid_to = vertex_to.Gid(); @@ -240,7 +241,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -308,7 +309,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -398,13 +399,13 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = GetParam(); + config.salient.items.properties_on_edges = GetParam(); std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertex { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid_vertex = vertex.Gid(); ASSERT_FALSE(acc->Commit().HasError()); @@ -412,7 +413,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -470,7 +471,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -546,14 +547,14 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = GetParam(); + config.salient.items.properties_on_edges = GetParam(); std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->CreateVertex(); auto vertex_to = acc->CreateVertex(); gid_from = vertex_from.Gid(); @@ -563,7 +564,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { // Create edge, but abort the transaction { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -631,7 +632,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -660,7 +661,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -728,7 +729,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -818,14 +819,14 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = GetParam(); + config.salient.items.properties_on_edges = GetParam(); std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_to = acc->CreateVertex(); auto vertex_from = acc->CreateVertex(); gid_to = vertex_to.Gid(); @@ -835,7 +836,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { // Create edge, but abort the transaction { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -903,7 +904,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -932,7 +933,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -1000,7 +1001,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -1090,13 +1091,13 @@ TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = GetParam(); + config.salient.items.properties_on_edges = GetParam(); std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertex { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid_vertex = vertex.Gid(); ASSERT_FALSE(acc->Commit().HasError()); @@ -1104,7 +1105,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { // Create edge, but abort the transaction { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -1162,7 +1163,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -1181,7 +1182,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -1239,7 +1240,7 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -1319,14 +1320,14 @@ TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = GetParam(); + config.salient.items.properties_on_edges = GetParam(); std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->CreateVertex(); auto vertex_to = acc->CreateVertex(); gid_from = vertex_from.Gid(); @@ -1336,7 +1337,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -1404,7 +1405,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -1491,7 +1492,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { // Delete edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -1558,7 +1559,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -1590,14 +1591,14 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = GetParam(); + config.salient.items.properties_on_edges = GetParam(); std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_to = acc->CreateVertex(); auto vertex_from = acc->CreateVertex(); gid_from = vertex_from.Gid(); @@ -1607,7 +1608,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -1675,7 +1676,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -1762,7 +1763,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { // Delete edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -1829,7 +1830,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -1861,13 +1862,13 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = GetParam(); + config.salient.items.properties_on_edges = GetParam(); std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertex { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid_vertex = vertex.Gid(); ASSERT_FALSE(acc->Commit().HasError()); @@ -1875,7 +1876,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -1933,7 +1934,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -2010,7 +2011,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { // Delete edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -2067,7 +2068,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -2089,14 +2090,14 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = GetParam(); + config.salient.items.properties_on_edges = GetParam(); std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->CreateVertex(); auto vertex_to = acc->CreateVertex(); gid_from = vertex_from.Gid(); @@ -2106,7 +2107,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2174,7 +2175,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2261,7 +2262,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { // Delete the edge, but abort the transaction { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2328,7 +2329,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2415,7 +2416,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { // Delete the edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2482,7 +2483,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2514,14 +2515,14 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = GetParam(); + config.salient.items.properties_on_edges = GetParam(); std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertices { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->CreateVertex(); auto vertex_to = acc->CreateVertex(); gid_from = vertex_from.Gid(); @@ -2531,7 +2532,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2599,7 +2600,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2686,7 +2687,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { // Delete the edge, but abort the transaction { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2753,7 +2754,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2841,7 +2842,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { // Delete the edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2908,7 +2909,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -2940,13 +2941,13 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = GetParam(); + config.salient.items.properties_on_edges = GetParam(); std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create vertex { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid_vertex = vertex.Gid(); ASSERT_FALSE(acc->Commit().HasError()); @@ -2954,7 +2955,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { // Create edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -3012,7 +3013,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -3089,7 +3090,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { // Delete the edge, but abort the transaction { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -3146,7 +3147,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -3223,7 +3224,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { // Delete the edge { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -3280,7 +3281,7 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { // Check whether the edge exists { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid_vertex, memgraph::storage::View::NEW); ASSERT_TRUE(vertex); @@ -3302,14 +3303,14 @@ TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = GetParam(); + config.salient.items.properties_on_edges = GetParam(); std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create dataset { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->CreateVertex(); auto vertex_to = acc->CreateVertex(); @@ -3358,7 +3359,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) { // Detach delete vertex { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -3422,7 +3423,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) { // Check dataset { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_FALSE(vertex_from); @@ -3444,14 +3445,14 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = GetParam(); + config.salient.items.properties_on_edges = GetParam(); std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid_vertex1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_vertex2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create dataset { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex1 = acc->CreateVertex(); auto vertex2 = acc->CreateVertex(); @@ -3578,7 +3579,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) { // Detach delete vertex { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); ASSERT_TRUE(vertex1); @@ -3716,7 +3717,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) { // Check dataset { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); ASSERT_FALSE(vertex1); @@ -3776,14 +3777,14 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = GetParam(); + config.salient.items.properties_on_edges = GetParam(); std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create dataset { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->CreateVertex(); auto vertex_to = acc->CreateVertex(); @@ -3832,7 +3833,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) { // Detach delete vertex, but abort the transaction { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -3896,7 +3897,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) { // Check dataset { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -3937,7 +3938,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) { // Detach delete vertex { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_TRUE(vertex_from); @@ -4001,7 +4002,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) { // Check dataset { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex_from = acc->FindVertex(gid_from, memgraph::storage::View::NEW); auto vertex_to = acc->FindVertex(gid_to, memgraph::storage::View::NEW); ASSERT_FALSE(vertex_from); @@ -4023,14 +4024,14 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = GetParam(); + config.salient.items.properties_on_edges = GetParam(); std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid_vertex1 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); memgraph::storage::Gid gid_vertex2 = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create dataset { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex1 = acc->CreateVertex(); auto vertex2 = acc->CreateVertex(); @@ -4157,7 +4158,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) { // Detach delete vertex, but abort the transaction { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); ASSERT_TRUE(vertex1); @@ -4295,7 +4296,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) { // Check dataset { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); ASSERT_TRUE(vertex1); @@ -4473,7 +4474,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) { // Detach delete vertex { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); ASSERT_TRUE(vertex1); @@ -4611,7 +4612,7 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) { // Check dataset { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex1 = acc->FindVertex(gid_vertex1, memgraph::storage::View::NEW); auto vertex2 = acc->FindVertex(gid_vertex2, memgraph::storage::View::NEW); ASSERT_FALSE(vertex1); @@ -4671,11 +4672,11 @@ TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(StorageWithProperties, EdgePropertyCommit) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = true; + config.salient.items.properties_on_edges = true; std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); auto et = acc->NameToEdgeType("et5"); @@ -4718,7 +4719,7 @@ TEST(StorageWithProperties, EdgePropertyCommit) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -4748,7 +4749,7 @@ TEST(StorageWithProperties, EdgePropertyCommit) { acc->Abort(); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -4781,7 +4782,7 @@ TEST(StorageWithProperties, EdgePropertyCommit) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -4807,13 +4808,13 @@ TEST(StorageWithProperties, EdgePropertyCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(StorageWithProperties, EdgePropertyAbort) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = true; + config.salient.items.properties_on_edges = true; std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); // Create the vertex. { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); auto et = acc->NameToEdgeType("et5"); @@ -4826,7 +4827,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) { // Set property 5 to "nandare", but abort the transaction. { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -4868,7 +4869,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) { // Check that property 5 is null. { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); auto edge = vertex->OutEdges(memgraph::storage::View::NEW).GetValue().edges[0]; @@ -4890,7 +4891,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) { // Set property 5 to "nandare". { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -4932,7 +4933,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) { // Check that property 5 is "nandare". { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -4964,7 +4965,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) { // Set property 5 to null, but abort the transaction. { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -5007,7 +5008,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) { // Check that property 5 is "nandare". { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -5039,7 +5040,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) { // Set property 5 to null. { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -5082,7 +5083,7 @@ TEST(StorageWithProperties, EdgePropertyAbort) { // Check that property 5 is null. { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -5108,11 +5109,11 @@ TEST(StorageWithProperties, EdgePropertyAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(StorageWithProperties, EdgePropertySerializationError) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = true; + config.salient.items.properties_on_edges = true; std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); auto et = acc->NameToEdgeType("et5"); @@ -5123,8 +5124,8 @@ TEST(StorageWithProperties, EdgePropertySerializationError) { ASSERT_FALSE(acc->Commit().HasError()); } - auto acc1 = store->Access(); - auto acc2 = store->Access(); + auto acc1 = store->Access(ReplicationRole::MAIN); + auto acc2 = store->Access(ReplicationRole::MAIN); // Set property 1 to 123 in accessor 1. { @@ -5195,7 +5196,7 @@ TEST(StorageWithProperties, EdgePropertySerializationError) { // Check which properties exist. { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -5227,13 +5228,13 @@ TEST(StorageWithProperties, EdgePropertySerializationError) { TEST(StorageWithProperties, EdgePropertyClear) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = true; + config.salient.items.properties_on_edges = true; std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid; auto property1 = store->NameToProperty("property1"); auto property2 = store->NameToProperty("property2"); { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); auto et = acc->NameToEdgeType("et5"); @@ -5249,7 +5250,7 @@ TEST(StorageWithProperties, EdgePropertyClear) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -5283,7 +5284,7 @@ TEST(StorageWithProperties, EdgePropertyClear) { acc->Abort(); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -5296,7 +5297,7 @@ TEST(StorageWithProperties, EdgePropertyClear) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -5331,7 +5332,7 @@ TEST(StorageWithProperties, EdgePropertyClear) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -5349,11 +5350,11 @@ TEST(StorageWithProperties, EdgePropertyClear) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST(StorageWithoutProperties, EdgePropertyAbort) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = false; + config.salient.items.properties_on_edges = false; std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); spdlog::trace("Created vertex with gid: {}", gid.AsInt()); @@ -5365,7 +5366,7 @@ TEST(StorageWithoutProperties, EdgePropertyAbort) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -5397,7 +5398,7 @@ TEST(StorageWithoutProperties, EdgePropertyAbort) { acc->Abort(); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -5423,11 +5424,11 @@ TEST(StorageWithoutProperties, EdgePropertyAbort) { TEST(StorageWithoutProperties, EdgePropertyClear) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = false; + config.salient.items.properties_on_edges = false; std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); memgraph::storage::Gid gid; { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); gid = vertex.Gid(); auto et = acc->NameToEdgeType("et5"); @@ -5438,7 +5439,7 @@ TEST(StorageWithoutProperties, EdgePropertyClear) { ASSERT_FALSE(acc->Commit().HasError()); } { - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->FindVertex(gid, memgraph::storage::View::OLD); ASSERT_TRUE(vertex); @@ -5453,12 +5454,12 @@ TEST(StorageWithoutProperties, EdgePropertyClear) { TEST(StorageWithProperties, EdgeNonexistentPropertyAPI) { auto config = disk_test_utils::GenerateOnDiskConfig(testSuite); - config.items.properties_on_edges = true; + config.salient.items.properties_on_edges = true; std::unique_ptr<memgraph::storage::Storage> store(new memgraph::storage::DiskStorage(config)); auto property = store->NameToProperty("property"); - auto acc = store->Access(); + auto acc = store->Access(ReplicationRole::MAIN); auto vertex = acc->CreateVertex(); auto edge = acc->CreateEdge(&vertex, &vertex, acc->NameToEdgeType("edge")); ASSERT_TRUE(edge.HasValue()); diff --git a/tests/unit/storage_v2_gc.cpp b/tests/unit/storage_v2_gc.cpp index 122558c17..e619f3723 100644 --- a/tests/unit/storage_v2_gc.cpp +++ b/tests/unit/storage_v2_gc.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -14,6 +14,7 @@ #include "storage/v2/inmemory/storage.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; using testing::UnorderedElementsAre; // TODO: The point of these is not to test GC fully, these are just simple @@ -31,7 +32,7 @@ TEST(StorageV2Gc, Sanity) { std::vector<memgraph::storage::Gid> vertices; { - auto acc = storage->Access(); + auto acc = storage->Access(ReplicationRole::MAIN); // Create some vertices, but delete some of them immediately. for (uint64_t i = 0; i < 1000; ++i) { auto vertex = acc->CreateVertex(); @@ -63,7 +64,7 @@ TEST(StorageV2Gc, Sanity) { // Verify existing vertices and add labels to some of them. { - auto acc = storage->Access(); + auto acc = storage->Access(ReplicationRole::MAIN); for (uint64_t i = 0; i < 1000; ++i) { auto vertex = acc->FindVertex(vertices[i], memgraph::storage::View::OLD); EXPECT_EQ(vertex.has_value(), i % 5 != 0); @@ -101,7 +102,7 @@ TEST(StorageV2Gc, Sanity) { // Add and remove some edges. { - auto acc = storage->Access(); + auto acc = storage->Access(ReplicationRole::MAIN); for (uint64_t i = 0; i < 1000; ++i) { auto from_vertex = acc->FindVertex(vertices[i], memgraph::storage::View::OLD); auto to_vertex = acc->FindVertex(vertices[(i + 1) % 1000], memgraph::storage::View::OLD); @@ -171,13 +172,13 @@ TEST(StorageV2Gc, Indices) { std::make_unique<memgraph::storage::InMemoryStorage>(memgraph::storage::Config{ .gc = {.type = memgraph::storage::Config::Gc::Type::PERIODIC, .interval = std::chrono::milliseconds(100)}})); { - auto unique_acc = storage->UniqueAccess(); + auto unique_acc = storage->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateIndex(storage->NameToLabel("label")).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto acc0 = storage->Access(); + auto acc0 = storage->Access(ReplicationRole::MAIN); for (uint64_t i = 0; i < 1000; ++i) { auto vertex = acc0->CreateVertex(); ASSERT_TRUE(*vertex.AddLabel(acc0->NameToLabel("label"))); @@ -185,9 +186,9 @@ TEST(StorageV2Gc, Indices) { ASSERT_FALSE(acc0->Commit().HasError()); } { - auto acc1 = storage->Access(); + auto acc1 = storage->Access(ReplicationRole::MAIN); - auto acc2 = storage->Access(); + auto acc2 = storage->Access(ReplicationRole::MAIN); for (auto vertex : acc2->Vertices(memgraph::storage::View::OLD)) { ASSERT_TRUE(*vertex.RemoveLabel(acc2->NameToLabel("label"))); } diff --git a/tests/unit/storage_v2_get_info.cpp b/tests/unit/storage_v2_get_info.cpp index bb7d5c0a1..c0f7e2dbc 100644 --- a/tests/unit/storage_v2_get_info.cpp +++ b/tests/unit/storage_v2_get_info.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -22,7 +22,7 @@ // NOLINTNEXTLINE(google-build-using-namespace) using namespace memgraph::storage; - +using memgraph::replication_coordination_glue::ReplicationRole; constexpr auto testSuite = "storage_v2_get_info"; const std::filesystem::path storage_directory{std::filesystem::temp_directory_path() / testSuite}; @@ -63,22 +63,22 @@ TYPED_TEST(InfoTest, InfoCheck) { { { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateExistenceConstraint(lbl, prop).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->DropExistenceConstraint(lbl, prop).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto v1 = acc->CreateVertex(); auto v2 = acc->CreateVertex(); auto v3 = acc->CreateVertex(); auto v4 = acc->CreateVertex(); - auto v5 = acc->CreateVertex(); + [[maybe_unused]] auto v5 = acc->CreateVertex(); ASSERT_FALSE(v2.AddLabel(lbl).HasError()); ASSERT_FALSE(v3.AddLabel(lbl).HasError()); @@ -93,49 +93,49 @@ TYPED_TEST(InfoTest, InfoCheck) { } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateIndex(lbl).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateIndex(lbl, prop).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateIndex(lbl, prop2).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->DropIndex(lbl, prop).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateUniqueConstraint(lbl, {prop2}).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateUniqueConstraint(lbl2, {prop}).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); ASSERT_FALSE(unique_acc->CreateUniqueConstraint(lbl3, {prop}).HasError()); ASSERT_FALSE(unique_acc->Commit().HasError()); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); ASSERT_EQ(unique_acc->DropUniqueConstraint(lbl, {prop2}), memgraph::storage::UniqueConstraints::DeletionStatus::SUCCESS); ASSERT_FALSE(unique_acc->Commit().HasError()); } - StorageInfo info = this->storage->GetInfo(true); // force to use configured directory + StorageInfo info = this->storage->GetInfo(true, ReplicationRole::MAIN); // force to use configured directory ASSERT_EQ(info.vertex_count, 5); ASSERT_EQ(info.edge_count, 2); diff --git a/tests/unit/storage_v2_indices.cpp b/tests/unit/storage_v2_indices.cpp index 142e15522..8ee053087 100644 --- a/tests/unit/storage_v2_indices.cpp +++ b/tests/unit/storage_v2_indices.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -25,7 +25,7 @@ // NOLINTNEXTLINE(google-build-using-namespace) using namespace memgraph::storage; - +using memgraph::replication_coordination_glue::ReplicationRole; using testing::IsEmpty; using testing::Types; using testing::UnorderedElementsAre; @@ -39,7 +39,7 @@ class IndexTest : public testing::Test { void SetUp() override { config_ = disk_test_utils::GenerateOnDiskConfig(testSuite); this->storage = std::make_unique<StorageType>(config_); - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); this->prop_id = acc->NameToProperty("id"); this->prop_val = acc->NameToProperty("val"); this->label1 = acc->NameToLabel("label1"); @@ -89,13 +89,13 @@ TYPED_TEST_CASE(IndexTest, StorageTypes); // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(IndexTest, LabelIndexCreate) { { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_FALSE(acc->LabelIndexExists(this->label1)); EXPECT_EQ(acc->ListAllIndices().label.size(), 0); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); for (int i = 0; i < 10; ++i) { auto vertex = this->CreateVertex(acc.get()); ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? this->label1 : this->label2)); @@ -104,19 +104,19 @@ TYPED_TEST(IndexTest, LabelIndexCreate) { } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9)); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); for (int i = 10; i < 20; ++i) { auto vertex = this->CreateVertex(acc.get()); ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? this->label1 : this->label2)); @@ -138,7 +138,7 @@ TYPED_TEST(IndexTest, LabelIndexCreate) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); for (int i = 10; i < 20; ++i) { auto vertex = this->CreateVertex(acc.get()); ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? this->label1 : this->label2)); @@ -159,7 +159,7 @@ TYPED_TEST(IndexTest, LabelIndexCreate) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), @@ -179,13 +179,13 @@ TYPED_TEST(IndexTest, LabelIndexCreate) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(IndexTest, LabelIndexDrop) { { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_FALSE(acc->LabelIndexExists(this->label1)); EXPECT_EQ(acc->ListAllIndices().label.size(), 0); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); for (int i = 0; i < 10; ++i) { auto vertex = this->CreateVertex(acc.get()); ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? this->label1 : this->label2)); @@ -194,41 +194,41 @@ TYPED_TEST(IndexTest, LabelIndexDrop) { } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9)); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->DropIndex(this->label1).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_FALSE(acc->LabelIndexExists(this->label1)); EXPECT_EQ(acc->ListAllIndices().label.size(), 0); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_TRUE(unique_acc->DropIndex(this->label1).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_FALSE(acc->LabelIndexExists(this->label1)); EXPECT_EQ(acc->ListAllIndices().label.size(), 0); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); for (int i = 10; i < 20; ++i) { auto vertex = this->CreateVertex(acc.get()); ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? this->label1 : this->label2)); @@ -237,18 +237,18 @@ TYPED_TEST(IndexTest, LabelIndexDrop) { } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_TRUE(acc->LabelIndexExists(this->label1)); EXPECT_THAT(acc->ListAllIndices().label, UnorderedElementsAre(this->label1)); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); @@ -274,17 +274,17 @@ TYPED_TEST(IndexTest, LabelIndexBasic) { // vertices. // 4. Delete even numbered vertices. { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label2).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(acc->ListAllIndices().label, UnorderedElementsAre(this->label1, this->label2)); EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), IsEmpty()); EXPECT_THAT(this->GetIds(acc->Vertices(this->label2, View::OLD), View::OLD), IsEmpty()); @@ -347,18 +347,18 @@ TYPED_TEST(IndexTest, LabelIndexDuplicateVersions) { // the same vertex in the index (they only differ by the timestamp). This test // checks that duplicates are properly filtered out. { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label2).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); for (int i = 0; i < 5; ++i) { auto vertex = this->CreateVertex(acc.get()); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); @@ -370,7 +370,7 @@ TYPED_TEST(IndexTest, LabelIndexDuplicateVersions) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4)); for (auto vertex : acc->Vertices(View::OLD)) { @@ -395,19 +395,19 @@ TYPED_TEST(IndexTest, LabelIndexDuplicateVersions) { TYPED_TEST(IndexTest, LabelIndexTransactionalIsolation) { // Check that transactions only see entries they are supposed to see. { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label2).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } - auto acc_before = this->storage->Access(); - auto acc = this->storage->Access(); - auto acc_after = this->storage->Access(); + auto acc_before = this->storage->Access(ReplicationRole::MAIN); + auto acc = this->storage->Access(ReplicationRole::MAIN); + auto acc_after = this->storage->Access(ReplicationRole::MAIN); for (int i = 0; i < 5; ++i) { auto vertex = this->CreateVertex(acc.get()); @@ -422,7 +422,7 @@ TYPED_TEST(IndexTest, LabelIndexTransactionalIsolation) { ASSERT_NO_ERROR(acc->Commit()); - auto acc_after_commit = this->storage->Access(); + auto acc_after_commit = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(this->GetIds(acc_before->Vertices(this->label1, View::NEW), View::NEW), IsEmpty()); @@ -436,17 +436,17 @@ TYPED_TEST(IndexTest, LabelIndexTransactionalIsolation) { TYPED_TEST(IndexTest, LabelIndexCountEstimate) { if constexpr ((std::is_same_v<TypeParam, memgraph::storage::InMemoryStorage>)) { { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label2).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); for (int i = 0; i < 20; ++i) { auto vertex = this->CreateVertex(acc.get()); ASSERT_NO_ERROR(vertex.AddLabel(i % 3 ? this->label1 : this->label2)); @@ -460,23 +460,23 @@ TYPED_TEST(IndexTest, LabelIndexCountEstimate) { TYPED_TEST(IndexTest, LabelIndexDeletedVertex) { if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) { { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } - auto acc1 = this->storage->Access(); + auto acc1 = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = this->CreateVertex(acc1.get()); ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); auto vertex2 = this->CreateVertex(acc1.get()); ASSERT_NO_ERROR(vertex2.AddLabel(this->label1)); EXPECT_THAT(this->GetIds(acc1->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1)); ASSERT_NO_ERROR(acc1->Commit()); - auto acc2 = this->storage->Access(); + auto acc2 = this->storage->Access(ReplicationRole::MAIN); auto vertex_to_delete = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW); auto res = acc2->DeleteVertex(&*vertex_to_delete); ASSERT_FALSE(res.HasError()); ASSERT_NO_ERROR(acc2->Commit()); - auto acc3 = this->storage->Access(); + auto acc3 = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(this->GetIds(acc3->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1)); } } @@ -484,23 +484,23 @@ TYPED_TEST(IndexTest, LabelIndexDeletedVertex) { TYPED_TEST(IndexTest, LabelIndexRemoveIndexedLabel) { if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) { { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } - auto acc1 = this->storage->Access(); + auto acc1 = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = this->CreateVertex(acc1.get()); ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); auto vertex2 = this->CreateVertex(acc1.get()); ASSERT_NO_ERROR(vertex2.AddLabel(this->label1)); ASSERT_NO_ERROR(acc1->Commit()); - auto acc2 = this->storage->Access(); + auto acc2 = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(this->GetIds(acc2->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1)); auto vertex_to_delete = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW); auto res = vertex_to_delete->RemoveLabel(this->label1); ASSERT_FALSE(res.HasError()); ASSERT_NO_ERROR(acc2->Commit()); - auto acc3 = this->storage->Access(); + auto acc3 = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(this->GetIds(acc3->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(1)); } } @@ -508,17 +508,17 @@ TYPED_TEST(IndexTest, LabelIndexRemoveIndexedLabel) { TYPED_TEST(IndexTest, LabelIndexRemoveAndAddIndexedLabel) { if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) { { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } - auto acc1 = this->storage->Access(); + auto acc1 = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = this->CreateVertex(acc1.get()); ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); auto vertex2 = this->CreateVertex(acc1.get()); ASSERT_NO_ERROR(vertex2.AddLabel(this->label1)); ASSERT_NO_ERROR(acc1->Commit()); - auto acc2 = this->storage->Access(); + auto acc2 = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(this->GetIds(acc2->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1)); auto vertex_to_delete = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW); auto res_remove = vertex_to_delete->RemoveLabel(this->label1); @@ -526,7 +526,7 @@ TYPED_TEST(IndexTest, LabelIndexRemoveAndAddIndexedLabel) { auto res_add = vertex_to_delete->AddLabel(this->label1); ASSERT_FALSE(res_add.HasError()); ASSERT_NO_ERROR(acc2->Commit()); - auto acc3 = this->storage->Access(); + auto acc3 = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(this->GetIds(acc3->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1)); } } @@ -537,11 +537,11 @@ TYPED_TEST(IndexTest, LabelIndexClearOldDataFromDisk) { static_cast<memgraph::storage::DiskLabelIndex *>(this->storage->indices_.label_index_.get()); { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } - auto acc1 = this->storage->Access(); + auto acc1 = this->storage->Access(ReplicationRole::MAIN); auto vertex = this->CreateVertex(acc1.get()); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); ASSERT_NO_ERROR(vertex.SetProperty(this->prop_val, PropertyValue(10))); @@ -550,14 +550,14 @@ TYPED_TEST(IndexTest, LabelIndexClearOldDataFromDisk) { auto *tx_db = disk_label_index->GetRocksDBStorage()->db_; ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); - auto acc2 = this->storage->Access(std::nullopt); + auto acc2 = this->storage->Access(ReplicationRole::MAIN); auto vertex2 = acc2->FindVertex(vertex.Gid(), memgraph::storage::View::NEW).value(); ASSERT_TRUE(vertex2.SetProperty(this->prop_val, memgraph::storage::PropertyValue(10)).HasValue()); ASSERT_FALSE(acc2->Commit().HasError()); ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); - auto acc3 = this->storage->Access(std::nullopt); + auto acc3 = this->storage->Access(ReplicationRole::MAIN); auto vertex3 = acc3->FindVertex(vertex.Gid(), memgraph::storage::View::NEW).value(); ASSERT_TRUE(vertex3.SetProperty(this->prop_val, memgraph::storage::PropertyValue(15)).HasValue()); ASSERT_FALSE(acc3->Commit().HasError()); @@ -569,92 +569,92 @@ TYPED_TEST(IndexTest, LabelIndexClearOldDataFromDisk) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(IndexTest, LabelPropertyIndexCreateAndDrop) { { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_EQ(acc->ListAllIndices().label_property.size(), 0); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_id).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_TRUE(acc->LabelPropertyIndexExists(this->label1, this->prop_id)); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(acc->ListAllIndices().label_property, UnorderedElementsAre(std::make_pair(this->label1, this->prop_id))); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_FALSE(acc->LabelPropertyIndexExists(this->label2, this->prop_id)); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_TRUE(unique_acc->CreateIndex(this->label1, this->prop_id).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(acc->ListAllIndices().label_property, UnorderedElementsAre(std::make_pair(this->label1, this->prop_id))); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label2, this->prop_id).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_TRUE(acc->LabelPropertyIndexExists(this->label2, this->prop_id)); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT( acc->ListAllIndices().label_property, UnorderedElementsAre(std::make_pair(this->label1, this->prop_id), std::make_pair(this->label2, this->prop_id))); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->DropIndex(this->label1, this->prop_id).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_FALSE(acc->LabelPropertyIndexExists(this->label1, this->prop_id)); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(acc->ListAllIndices().label_property, UnorderedElementsAre(std::make_pair(this->label2, this->prop_id))); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_TRUE(unique_acc->DropIndex(this->label1, this->prop_id).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->DropIndex(this->label2, this->prop_id).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_FALSE(acc->LabelPropertyIndexExists(this->label2, this->prop_id)); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_EQ(acc->ListAllIndices().label_property.size(), 0); } } @@ -667,17 +667,17 @@ TYPED_TEST(IndexTest, LabelPropertyIndexCreateAndDrop) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(IndexTest, LabelPropertyIndexBasic) { { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_val).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label2, this->prop_val).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::OLD), View::OLD), IsEmpty()); for (int i = 0; i < 10; ++i) { @@ -762,13 +762,13 @@ TYPED_TEST(IndexTest, LabelPropertyIndexBasic) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(IndexTest, LabelPropertyIndexDuplicateVersions) { { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_val).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); for (int i = 0; i < 5; ++i) { auto vertex = this->CreateVertex(acc.get()); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); @@ -782,7 +782,7 @@ TYPED_TEST(IndexTest, LabelPropertyIndexDuplicateVersions) { } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4)); @@ -809,14 +809,14 @@ TYPED_TEST(IndexTest, LabelPropertyIndexDuplicateVersions) { // NOLINTNEXTLINE(hicpp-special-member-functions) TYPED_TEST(IndexTest, LabelPropertyIndexTransactionalIsolation) { { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_val).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } - auto acc_before = this->storage->Access(); - auto acc = this->storage->Access(); - auto acc_after = this->storage->Access(); + auto acc_before = this->storage->Access(ReplicationRole::MAIN); + auto acc = this->storage->Access(ReplicationRole::MAIN); + auto acc_after = this->storage->Access(ReplicationRole::MAIN); for (int i = 0; i < 5; ++i) { auto vertex = this->CreateVertex(acc.get()); @@ -833,7 +833,7 @@ TYPED_TEST(IndexTest, LabelPropertyIndexTransactionalIsolation) { ASSERT_NO_ERROR(acc->Commit()); - auto acc_after_commit = this->storage->Access(); + auto acc_after_commit = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(this->GetIds(acc_before->Vertices(this->label1, this->prop_val, View::NEW), View::NEW), IsEmpty()); @@ -852,13 +852,13 @@ TYPED_TEST(IndexTest, LabelPropertyIndexFiltering) { // properly. { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_val).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); for (int i = 0; i < 10; ++i) { auto vertex = this->CreateVertex(acc.get()); @@ -868,7 +868,7 @@ TYPED_TEST(IndexTest, LabelPropertyIndexFiltering) { ASSERT_NO_ERROR(acc->Commit()); } { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); for (int i = 0; i < 5; ++i) { EXPECT_THAT(this->GetIds(acc->Vertices(this->label1, this->prop_val, PropertyValue(i), View::OLD)), UnorderedElementsAre(2 * i, 2 * i + 1)); @@ -926,12 +926,12 @@ TYPED_TEST(IndexTest, LabelPropertyIndexFiltering) { TYPED_TEST(IndexTest, LabelPropertyIndexCountEstimate) { if constexpr ((std::is_same_v<TypeParam, memgraph::storage::InMemoryStorage>)) { { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_val).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); for (int i = 1; i <= 10; ++i) { for (int j = 0; j < i; ++j) { auto vertex = this->CreateVertex(acc.get()); @@ -954,7 +954,7 @@ TYPED_TEST(IndexTest, LabelPropertyIndexCountEstimate) { TYPED_TEST(IndexTest, LabelPropertyIndexMixedIteration) { { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_val).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } @@ -993,7 +993,7 @@ TYPED_TEST(IndexTest, LabelPropertyIndexMixedIteration) { // Create vertices, each with one of the values above. { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); for (const auto &value : values) { auto v = acc->CreateVertex(); ASSERT_TRUE(v.AddLabel(this->label1).HasValue()); @@ -1004,7 +1004,7 @@ TYPED_TEST(IndexTest, LabelPropertyIndexMixedIteration) { // Verify that all nodes are in the index. { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto iterable = acc->Vertices(this->label1, this->prop_val, View::OLD); auto it = iterable.begin(); for (const auto &value : values) { @@ -1021,7 +1021,7 @@ TYPED_TEST(IndexTest, LabelPropertyIndexMixedIteration) { auto verify = [&](const std::optional<memgraph::utils::Bound<PropertyValue>> &from, const std::optional<memgraph::utils::Bound<PropertyValue>> &to, const std::vector<PropertyValue> &expected) { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(ReplicationRole::MAIN); auto iterable = acc->Vertices(this->label1, this->prop_val, from, to, View::OLD); size_t i = 0; for (auto it = iterable.begin(); it != iterable.end(); ++it, ++i) { @@ -1166,11 +1166,11 @@ TYPED_TEST(IndexTest, LabelPropertyIndexMixedIteration) { TYPED_TEST(IndexTest, LabelPropertyIndexDeletedVertex) { if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) { { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_val).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } - auto acc1 = this->storage->Access(); + auto acc1 = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = this->CreateVertex(acc1.get()); ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); @@ -1183,13 +1183,13 @@ TYPED_TEST(IndexTest, LabelPropertyIndexDeletedVertex) { EXPECT_THAT(this->GetIds(acc1->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1)); ASSERT_NO_ERROR(acc1->Commit()); - auto acc2 = this->storage->Access(); + auto acc2 = this->storage->Access(ReplicationRole::MAIN); auto vertex_to_delete = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW); auto res = acc2->DeleteVertex(&*vertex_to_delete); ASSERT_FALSE(res.HasError()); ASSERT_NO_ERROR(acc2->Commit()); - auto acc3 = this->storage->Access(); + auto acc3 = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(this->GetIds(acc3->Vertices(this->label1, this->prop_val, View::NEW), View::NEW), UnorderedElementsAre(1)); } @@ -1199,11 +1199,11 @@ TYPED_TEST(IndexTest, LabelPropertyIndexDeletedVertex) { TYPED_TEST(IndexTest, LabelPropertyIndexRemoveIndexedLabel) { if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) { { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_val).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } - auto acc1 = this->storage->Access(); + auto acc1 = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = this->CreateVertex(acc1.get()); ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); @@ -1216,13 +1216,13 @@ TYPED_TEST(IndexTest, LabelPropertyIndexRemoveIndexedLabel) { EXPECT_THAT(this->GetIds(acc1->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1)); ASSERT_NO_ERROR(acc1->Commit()); - auto acc2 = this->storage->Access(); + auto acc2 = this->storage->Access(ReplicationRole::MAIN); auto vertex_to_delete = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW); auto res = vertex_to_delete->RemoveLabel(this->label1); ASSERT_FALSE(res.HasError()); ASSERT_NO_ERROR(acc2->Commit()); - auto acc3 = this->storage->Access(); + auto acc3 = this->storage->Access(ReplicationRole::MAIN); EXPECT_THAT(this->GetIds(acc3->Vertices(this->label1, this->prop_val, View::NEW), View::NEW), UnorderedElementsAre(1)); } @@ -1231,11 +1231,11 @@ TYPED_TEST(IndexTest, LabelPropertyIndexRemoveIndexedLabel) { TYPED_TEST(IndexTest, LabelPropertyIndexRemoveAndAddIndexedLabel) { if constexpr ((std::is_same_v<TypeParam, memgraph::storage::DiskStorage>)) { { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_val).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } - auto acc1 = this->storage->Access(); + auto acc1 = this->storage->Access(ReplicationRole::MAIN); auto vertex1 = this->CreateVertex(acc1.get()); ASSERT_NO_ERROR(vertex1.AddLabel(this->label1)); @@ -1248,7 +1248,7 @@ TYPED_TEST(IndexTest, LabelPropertyIndexRemoveAndAddIndexedLabel) { EXPECT_THAT(this->GetIds(acc1->Vertices(this->label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1)); ASSERT_NO_ERROR(acc1->Commit()); - auto acc2 = this->storage->Access(); + auto acc2 = this->storage->Access(ReplicationRole::MAIN); auto target_vertex = acc2->FindVertex(vertex1.Gid(), memgraph::storage::View::NEW); auto remove_res = target_vertex->RemoveLabel(this->label1); ASSERT_FALSE(remove_res.HasError()); @@ -1264,11 +1264,11 @@ TYPED_TEST(IndexTest, LabelPropertyIndexClearOldDataFromDisk) { static_cast<memgraph::storage::DiskLabelPropertyIndex *>(this->storage->indices_.label_property_index_.get()); { - auto unique_acc = this->storage->UniqueAccess(); + auto unique_acc = this->storage->UniqueAccess(ReplicationRole::MAIN); EXPECT_FALSE(unique_acc->CreateIndex(this->label1, this->prop_val).HasError()); ASSERT_NO_ERROR(unique_acc->Commit()); } - auto acc1 = this->storage->Access(); + auto acc1 = this->storage->Access(ReplicationRole::MAIN); auto vertex = this->CreateVertex(acc1.get()); ASSERT_NO_ERROR(vertex.AddLabel(this->label1)); ASSERT_NO_ERROR(vertex.SetProperty(this->prop_val, PropertyValue(10))); @@ -1277,14 +1277,14 @@ TYPED_TEST(IndexTest, LabelPropertyIndexClearOldDataFromDisk) { auto *tx_db = disk_label_property_index->GetRocksDBStorage()->db_; ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); - auto acc2 = this->storage->Access(std::nullopt); + auto acc2 = this->storage->Access(ReplicationRole::MAIN); auto vertex2 = acc2->FindVertex(vertex.Gid(), memgraph::storage::View::NEW).value(); ASSERT_TRUE(vertex2.SetProperty(this->prop_val, memgraph::storage::PropertyValue(10)).HasValue()); ASSERT_FALSE(acc2->Commit().HasError()); ASSERT_EQ(disk_test_utils::GetRealNumberOfEntriesInRocksDB(tx_db), 1); - auto acc3 = this->storage->Access(std::nullopt); + auto acc3 = this->storage->Access(ReplicationRole::MAIN); auto vertex3 = acc3->FindVertex(vertex.Gid(), memgraph::storage::View::NEW).value(); ASSERT_TRUE(vertex3.SetProperty(this->prop_val, memgraph::storage::PropertyValue(15)).HasValue()); ASSERT_FALSE(acc3->Commit().HasError()); diff --git a/tests/unit/storage_v2_isolation_level.cpp b/tests/unit/storage_v2_isolation_level.cpp index d2ae14d8f..39d7a92ec 100644 --- a/tests/unit/storage_v2_isolation_level.cpp +++ b/tests/unit/storage_v2_isolation_level.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -16,6 +16,7 @@ #include "storage/v2/inmemory/storage.hpp" #include "storage/v2/isolation_level.hpp" #include "utils/on_scope_exit.hpp" +using memgraph::replication_coordination_glue::ReplicationRole; namespace { int64_t VerticesCount(memgraph::storage::Storage::Accessor *accessor) { @@ -44,9 +45,9 @@ class StorageIsolationLevelTest : public ::testing::TestWithParam<memgraph::stor void TestVisibility(std::unique_ptr<memgraph::storage::Storage> &storage, const memgraph::storage::IsolationLevel &default_isolation_level, const memgraph::storage::IsolationLevel &override_isolation_level) { - auto creator = storage->Access(); - auto default_isolation_level_reader = storage->Access(); - auto override_isolation_level_reader = storage->Access(override_isolation_level); + auto creator = storage->Access(ReplicationRole::MAIN); + auto default_isolation_level_reader = storage->Access(ReplicationRole::MAIN); + auto override_isolation_level_reader = storage->Access(ReplicationRole::MAIN, override_isolation_level); ASSERT_EQ(VerticesCount(default_isolation_level_reader.get()), 0); ASSERT_EQ(VerticesCount(override_isolation_level_reader.get()), 0); @@ -89,7 +90,7 @@ class StorageIsolationLevelTest : public ::testing::TestWithParam<memgraph::stor ASSERT_FALSE(override_isolation_level_reader->Commit().HasError()); SCOPED_TRACE("Visibility after a new transaction is started"); - auto verifier = storage->Access(); + auto verifier = storage->Access(ReplicationRole::MAIN); ASSERT_EQ(VerticesCount(verifier.get()), iteration_count); ASSERT_FALSE(verifier->Commit().HasError()); } diff --git a/tests/unit/storage_v2_replication.cpp b/tests/unit/storage_v2_replication.cpp index f07130c4a..e572440ca 100644 --- a/tests/unit/storage_v2_replication.cpp +++ b/tests/unit/storage_v2_replication.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -22,6 +22,7 @@ #include <storage/v2/inmemory/storage.hpp> #include <storage/v2/property_value.hpp> #include <storage/v2/replication/enums.hpp> +#include "auth/auth.hpp" #include "dbms/database.hpp" #include "dbms/dbms_handler.hpp" #include "dbms/replication_handler.hpp" @@ -31,6 +32,7 @@ #include "storage/v2/indices/label_index_stats.hpp" #include "storage/v2/storage.hpp" #include "storage/v2/view.hpp" +#include "utils/rw_lock.hpp" #include "utils/synchronized.hpp" using testing::UnorderedElementsAre; @@ -39,9 +41,9 @@ using memgraph::dbms::RegisterReplicaError; using memgraph::dbms::ReplicationHandler; using memgraph::dbms::UnregisterReplicaResult; using memgraph::replication::ReplicationClientConfig; -using memgraph::replication::ReplicationMode; -using memgraph::replication::ReplicationRole; using memgraph::replication::ReplicationServerConfig; +using memgraph::replication_coordination_glue::ReplicationMode; +using memgraph::replication_coordination_glue::ReplicationRole; using memgraph::storage::Config; using memgraph::storage::EdgeAccessor; using memgraph::storage::Gid; @@ -64,26 +66,35 @@ class ReplicationTest : public ::testing::Test { void TearDown() override { Clear(); } Config main_conf = [&] { - Config config{.items = {.properties_on_edges = true}, - .durability = { - .snapshot_wal_mode = Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - }}; + Config config{ + .durability = + { + .snapshot_wal_mode = Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + }, + .salient.items = {.properties_on_edges = true}, + }; UpdatePaths(config, storage_directory); return config; }(); Config repl_conf = [&] { - Config config{.items = {.properties_on_edges = true}, - .durability = { - .snapshot_wal_mode = Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - }}; + Config config{ + .durability = + { + .snapshot_wal_mode = Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + }, + .salient.items = {.properties_on_edges = true}, + }; UpdatePaths(config, repl_storage_directory); return config; }(); Config repl2_conf = [&] { - Config config{.items = {.properties_on_edges = true}, - .durability = { - .snapshot_wal_mode = Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, - }}; + Config config{ + .durability = + { + .snapshot_wal_mode = Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL, + }, + .salient.items = {.properties_on_edges = true}, + }; UpdatePaths(config, repl2_storage_directory); return config; }(); @@ -102,20 +113,23 @@ class ReplicationTest : public ::testing::Test { struct MinMemgraph { MinMemgraph(const memgraph::storage::Config &conf) - : dbms{conf + : auth{conf.durability.storage_directory / "auth", memgraph::auth::Auth::Config{/* default */}}, + dbms{conf #ifdef MG_ENTERPRISE , - reinterpret_cast< - memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *>(0), - true, false + &auth, true #endif }, repl_state{dbms.ReplicationState()}, - db{*dbms.Get().get()}, + db_acc{dbms.Get()}, + db{*db_acc.get()}, repl_handler(dbms) { } + + memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> auth; memgraph::dbms::DbmsHandler dbms; memgraph::replication::ReplicationState &repl_state; + memgraph::dbms::DatabaseAccess db_acc; memgraph::dbms::Database &db; ReplicationHandler repl_handler; }; @@ -152,7 +166,7 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) { ASSERT_TRUE(v.AddLabel(main.db.storage()->NameToLabel(vertex_label)).HasValue()); ASSERT_TRUE(v.SetProperty(main.db.storage()->NameToProperty(vertex_property), PropertyValue(vertex_property_value)) .HasValue()); - ASSERT_FALSE(acc->Commit().HasError()); + ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError()); } { @@ -178,7 +192,7 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) { auto v = acc->FindVertex(*vertex_gid, View::OLD); ASSERT_TRUE(v); ASSERT_TRUE(v->RemoveLabel(main.db.storage()->NameToLabel(vertex_label)).HasValue()); - ASSERT_FALSE(acc->Commit().HasError()); + ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError()); } { @@ -197,7 +211,7 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) { auto v = acc->FindVertex(*vertex_gid, View::OLD); ASSERT_TRUE(v); ASSERT_TRUE(acc->DeleteVertex(&*v).HasValue()); - ASSERT_FALSE(acc->Commit().HasError()); + ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError()); } { @@ -224,7 +238,7 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) { ASSERT_TRUE(edge.SetProperty(main.db.storage()->NameToProperty(edge_property), PropertyValue(edge_property_value)) .HasValue()); edge_gid.emplace(edge.Gid()); - ASSERT_FALSE(acc->Commit().HasError()); + ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError()); } const auto find_edge = [&](const auto &edges, const Gid edge_gid) -> std::optional<EdgeAccessor> { @@ -261,7 +275,7 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) { auto edge = find_edge(out_edges->edges, *edge_gid); ASSERT_TRUE(edge); ASSERT_TRUE(acc->DeleteEdge(&*edge).HasValue()); - ASSERT_FALSE(acc->Commit().HasError()); + ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError()); } { @@ -287,25 +301,25 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) { { auto unique_acc = main.db.UniqueAccess(); ASSERT_FALSE(unique_acc->CreateIndex(main.db.storage()->NameToLabel(label)).HasError()); - ASSERT_FALSE(unique_acc->Commit().HasError()); + ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError()); } { auto unique_acc = main.db.UniqueAccess(); unique_acc->SetIndexStats(main.db.storage()->NameToLabel(label), l_stats); - ASSERT_FALSE(unique_acc->Commit().HasError()); + ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError()); } { auto unique_acc = main.db.UniqueAccess(); ASSERT_FALSE( unique_acc->CreateIndex(main.db.storage()->NameToLabel(label), main.db.storage()->NameToProperty(property)) .HasError()); - ASSERT_FALSE(unique_acc->Commit().HasError()); + ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError()); } { auto unique_acc = main.db.UniqueAccess(); unique_acc->SetIndexStats(main.db.storage()->NameToLabel(label), main.db.storage()->NameToProperty(property), lp_stats); - ASSERT_FALSE(unique_acc->Commit().HasError()); + ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError()); } { auto unique_acc = main.db.UniqueAccess(); @@ -313,7 +327,7 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) { ->CreateExistenceConstraint(main.db.storage()->NameToLabel(label), main.db.storage()->NameToProperty(property)) .HasError()); - ASSERT_FALSE(unique_acc->Commit().HasError()); + ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError()); } { auto unique_acc = main.db.UniqueAccess(); @@ -322,7 +336,7 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) { {main.db.storage()->NameToProperty(property), main.db.storage()->NameToProperty(property_extra)}) .HasError()); - ASSERT_FALSE(unique_acc->Commit().HasError()); + ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError()); } { @@ -360,24 +374,24 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) { { auto unique_acc = main.db.UniqueAccess(); unique_acc->DeleteLabelIndexStats(main.db.storage()->NameToLabel(label)); - ASSERT_FALSE(unique_acc->Commit().HasError()); + ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError()); } { auto unique_acc = main.db.UniqueAccess(); ASSERT_FALSE(unique_acc->DropIndex(main.db.storage()->NameToLabel(label)).HasError()); - ASSERT_FALSE(unique_acc->Commit().HasError()); + ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError()); } { auto unique_acc = main.db.UniqueAccess(); unique_acc->DeleteLabelPropertyIndexStats(main.db.storage()->NameToLabel(label)); - ASSERT_FALSE(unique_acc->Commit().HasError()); + ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError()); } { auto unique_acc = main.db.UniqueAccess(); ASSERT_FALSE( unique_acc->DropIndex(main.db.storage()->NameToLabel(label), main.db.storage()->NameToProperty(property)) .HasError()); - ASSERT_FALSE(unique_acc->Commit().HasError()); + ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError()); } { auto unique_acc = main.db.UniqueAccess(); @@ -385,7 +399,7 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) { ->DropExistenceConstraint(main.db.storage()->NameToLabel(label), main.db.storage()->NameToProperty(property)) .HasError()); - ASSERT_FALSE(unique_acc->Commit().HasError()); + ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError()); } { auto unique_acc = main.db.UniqueAccess(); @@ -393,7 +407,7 @@ TEST_F(ReplicationTest, BasicSynchronousReplicationTest) { main.db.storage()->NameToLabel(label), {main.db.storage()->NameToProperty(property), main.db.storage()->NameToProperty(property_extra)}), memgraph::storage::UniqueConstraints::DeletionStatus::SUCCESS); - ASSERT_FALSE(unique_acc->Commit().HasError()); + ASSERT_FALSE(unique_acc->Commit({}, main.db_acc).HasError()); } { @@ -455,21 +469,21 @@ TEST_F(ReplicationTest, MultipleSynchronousReplicationTest) { ASSERT_TRUE(v.SetProperty(main.db.storage()->NameToProperty(vertex_property), PropertyValue(vertex_property_value)) .HasValue()); vertex_gid.emplace(v.Gid()); - ASSERT_FALSE(acc->Commit().HasError()); + ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError()); } - const auto check_replica = [&](Storage *replica_store) { - auto acc = replica_store->Access(); + const auto check_replica = [&](memgraph::dbms::Database &replica_database) { + auto acc = replica_database.Access(); const auto v = acc->FindVertex(*vertex_gid, View::OLD); ASSERT_TRUE(v); const auto labels = v->Labels(View::OLD); ASSERT_TRUE(labels.HasValue()); - ASSERT_THAT(*labels, UnorderedElementsAre(replica_store->NameToLabel(vertex_label))); + ASSERT_THAT(*labels, UnorderedElementsAre(replica_database.storage()->NameToLabel(vertex_label))); ASSERT_FALSE(acc->Commit().HasError()); }; - check_replica(replica1.db.storage()); - check_replica(replica2.db.storage()); + check_replica(replica1.db); + check_replica(replica2.db); auto handler = main.repl_handler; handler.UnregisterReplica(replicas[1]); @@ -477,12 +491,12 @@ TEST_F(ReplicationTest, MultipleSynchronousReplicationTest) { auto acc = main.db.Access(); auto v = acc->CreateVertex(); vertex_gid.emplace(v.Gid()); - ASSERT_FALSE(acc->Commit().HasError()); + ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError()); } // REPLICA1 should contain the new vertex { - auto acc = replica1.db.storage()->Access(); + auto acc = replica1.db.Access(); const auto v = acc->FindVertex(*vertex_gid, View::OLD); ASSERT_TRUE(v); ASSERT_FALSE(acc->Commit().HasError()); @@ -490,7 +504,7 @@ TEST_F(ReplicationTest, MultipleSynchronousReplicationTest) { // REPLICA2 should not contain the new vertex { - auto acc = replica2.db.storage()->Access(); + auto acc = replica2.db.Access(); const auto v = acc->FindVertex(*vertex_gid, View::OLD); ASSERT_FALSE(v); ASSERT_FALSE(acc->Commit().HasError()); @@ -515,7 +529,7 @@ TEST_F(ReplicationTest, RecoveryProcess) { // Create the vertex before registering a replica auto v = acc->CreateVertex(); vertex_gids.emplace_back(v.Gid()); - ASSERT_FALSE(acc->Commit().HasError()); + ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError()); } } @@ -531,13 +545,13 @@ TEST_F(ReplicationTest, RecoveryProcess) { auto acc = main.db.Access(); auto v = acc->CreateVertex(); vertex_gids.emplace_back(v.Gid()); - ASSERT_FALSE(acc->Commit().HasError()); + ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError()); } { auto acc = main.db.Access(); auto v = acc->CreateVertex(); vertex_gids.emplace_back(v.Gid()); - ASSERT_FALSE(acc->Commit().HasError()); + ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError()); } } @@ -560,7 +574,7 @@ TEST_F(ReplicationTest, RecoveryProcess) { ASSERT_TRUE( v->SetProperty(main.db.storage()->NameToProperty(property_name), PropertyValue(property_value)).HasValue()); } - ASSERT_FALSE(acc->Commit().HasError()); + ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError()); } static constexpr const auto *vertex_label = "vertex_label"; @@ -594,7 +608,7 @@ TEST_F(ReplicationTest, RecoveryProcess) { ASSERT_TRUE(v); ASSERT_TRUE(v->AddLabel(main.db.storage()->NameToLabel(vertex_label)).HasValue()); } - ASSERT_FALSE(acc->Commit().HasError()); + ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError()); } { auto acc = replica.db.Access(); @@ -663,7 +677,7 @@ TEST_F(ReplicationTest, BasicAsynchronousReplicationTest) { auto acc = main.db.Access(); auto v = acc->CreateVertex(); created_vertices.push_back(v.Gid()); - ASSERT_FALSE(acc->Commit().HasError()); + ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError()); if (i == 0) { ASSERT_EQ(main.db.storage()->GetReplicaState("REPLICA_ASYNC"), ReplicaState::REPLICATING); @@ -677,7 +691,7 @@ TEST_F(ReplicationTest, BasicAsynchronousReplicationTest) { } ASSERT_TRUE(std::all_of(created_vertices.begin(), created_vertices.end(), [&](const auto vertex_gid) { - auto acc = replica_async.db.storage()->Access(); + auto acc = replica_async.db.Access(); auto v = acc->FindVertex(vertex_gid, View::OLD); const bool exists = v.has_value(); EXPECT_FALSE(acc->Commit().HasError()); @@ -723,16 +737,16 @@ TEST_F(ReplicationTest, EpochTest) { auto acc = main.db.Access(); const auto v = acc->CreateVertex(); vertex_gid.emplace(v.Gid()); - ASSERT_FALSE(acc->Commit().HasError()); + ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError()); } { - auto acc = replica1.db.storage()->Access(); + auto acc = replica1.db.Access(); const auto v = acc->FindVertex(*vertex_gid, View::OLD); ASSERT_TRUE(v); - ASSERT_FALSE(acc->Commit().HasError()); + ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError()); } { - auto acc = replica2.db.storage()->Access(); + auto acc = replica2.db.Access(); const auto v = acc->FindVertex(*vertex_gid, View::OLD); ASSERT_TRUE(v); ASSERT_FALSE(acc->Commit().HasError()); @@ -756,17 +770,17 @@ TEST_F(ReplicationTest, EpochTest) { { auto acc = main.db.Access(); acc->CreateVertex(); - ASSERT_FALSE(acc->Commit().HasError()); + ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError()); } { - auto acc = replica1.db.storage()->Access(); + auto acc = replica1.db.Access(); auto v = acc->CreateVertex(); vertex_gid.emplace(v.Gid()); - ASSERT_FALSE(acc->Commit().HasError()); + ASSERT_FALSE(acc->Commit({}, replica1.db_acc).HasError()); } // Replica1 should forward it's vertex to Replica2 { - auto acc = replica2.db.storage()->Access(); + auto acc = replica2.db.Access(); const auto v = acc->FindVertex(*vertex_gid, View::OLD); ASSERT_TRUE(v); ASSERT_FALSE(acc->Commit().HasError()); @@ -790,12 +804,12 @@ TEST_F(ReplicationTest, EpochTest) { auto acc = main.db.Access(); const auto v = acc->CreateVertex(); vertex_gid.emplace(v.Gid()); - ASSERT_FALSE(acc->Commit().HasError()); + ASSERT_FALSE(acc->Commit({}, main.db_acc).HasError()); } // Replica1 is not compatible with the main so it shouldn't contain // it's newest vertex { - auto acc = replica1.db.storage()->Access(); + auto acc = replica1.db.Access(); const auto v = acc->FindVertex(*vertex_gid, View::OLD); ASSERT_FALSE(v); ASSERT_FALSE(acc->Commit().HasError()); @@ -926,7 +940,7 @@ TEST_F(ReplicationTest, ReplicationReplicaWithExistingEndPoint) { .ip_address = local_host, .port = common_port, }) - .GetError() == RegisterReplicaError::END_POINT_EXISTS); + .GetError() == RegisterReplicaError::ENDPOINT_EXISTS); } TEST_F(ReplicationTest, RestoringReplicationAtStartupAfterDroppingReplica) { diff --git a/tests/unit/storage_v2_show_storage_info.cpp b/tests/unit/storage_v2_show_storage_info.cpp index da788978e..73d33a77d 100644 --- a/tests/unit/storage_v2_show_storage_info.cpp +++ b/tests/unit/storage_v2_show_storage_info.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -44,7 +44,7 @@ class ShowStorageInfoTest : public testing::Test { }; TEST_F(ShowStorageInfoTest, CountOnAbort) { - auto acc = this->storage->Access(); + auto acc = this->storage->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN); auto src_vertex = acc->CreateVertex(); auto dest_vertex = acc->CreateVertex(); auto et = acc->NameToEdgeType("et5"); diff --git a/tests/unit/storage_v2_storage_mode.cpp b/tests/unit/storage_v2_storage_mode.cpp index 49ee633c5..487319d3c 100644 --- a/tests/unit/storage_v2_storage_mode.cpp +++ b/tests/unit/storage_v2_storage_mode.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -44,8 +44,8 @@ TEST_P(StorageModeTest, Mode) { .transaction{.isolation_level = memgraph::storage::IsolationLevel::SNAPSHOT_ISOLATION}}); static_cast<memgraph::storage::InMemoryStorage *>(storage.get())->SetStorageMode(storage_mode); - auto creator = storage->Access(); - auto other_analytics_mode_reader = storage->Access(); + auto creator = storage->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN); + auto other_analytics_mode_reader = storage->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN); ASSERT_EQ(CountVertices(*creator, memgraph::storage::View::OLD), 0); ASSERT_EQ(CountVertices(*other_analytics_mode_reader, memgraph::storage::View::OLD), 0); diff --git a/tests/unit/typed_value.cpp b/tests/unit/typed_value.cpp index 5f2f8f3bf..41dd6e3ba 100644 --- a/tests/unit/typed_value.cpp +++ b/tests/unit/typed_value.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -37,7 +37,8 @@ class AllTypesFixture : public testing::Test { std::vector<TypedValue> values_; memgraph::storage::Config config_{disk_test_utils::GenerateOnDiskConfig(testSuite)}; std::unique_ptr<memgraph::storage::Storage> db{new StorageType(config_)}; - std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{db->Access()}; + std::unique_ptr<memgraph::storage::Storage::Accessor> storage_dba{ + db->Access(memgraph::replication_coordination_glue::ReplicationRole::MAIN)}; memgraph::query::DbAccessor dba{storage_dba.get()}; void SetUp() override { diff --git a/tests/unit/utils_string.cpp b/tests/unit/utils_string.cpp index cefe57a6a..fdf64ae9f 100644 --- a/tests/unit/utils_string.cpp +++ b/tests/unit/utils_string.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -171,3 +171,22 @@ TEST(String, Substr) { EXPECT_EQ(Substr(string, string.size() - 1, 1), string.substr(string.size() - 1, 1)); EXPECT_EQ(Substr(string, string.size() - 1, 2), string.substr(string.size() - 1, 2)); } + +TEST(String, DoubleToString) { + EXPECT_EQ(DoubleToString(0), "0"); + EXPECT_EQ(DoubleToString(1), "1"); + EXPECT_EQ(DoubleToString(1234567890123456), "1234567890123456"); + EXPECT_EQ(DoubleToString(static_cast<double>(12345678901234567)), "12345678901234568"); + EXPECT_EQ(DoubleToString(0.5), "0.5"); + EXPECT_EQ(DoubleToString(1.0), "1"); + EXPECT_EQ(DoubleToString(5.8), "5.8"); + EXPECT_EQ(DoubleToString(1.01234000), "1.01234"); + EXPECT_EQ(DoubleToString(1.036837585345), "1.036837585345"); + EXPECT_EQ(DoubleToString(103.6837585345), "103.683758534500001"); + EXPECT_EQ(DoubleToString(1.01234567890123456789), "1.012345678901235"); + EXPECT_EQ(DoubleToString(1234567.01234567891234567), "1234567.012345678871498"); + EXPECT_EQ(DoubleToString(0.00001), "0.00001"); + EXPECT_EQ(DoubleToString(0.00000000000001), "0.00000000000001"); + EXPECT_EQ(DoubleToString(0.000000000000001), "0.000000000000001"); + EXPECT_EQ(DoubleToString(0.0000000000000001), "0"); +}