Compare commits
29 Commits
add-coordi
...
add-mgp-mo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc5237a4fd | ||
|
|
9f7118d893 | ||
|
|
38ade99652 | ||
|
|
6706ebfa2b | ||
|
|
e7f6a5f4f4 | ||
|
|
071df2f439 | ||
|
|
7f10636470 | ||
|
|
76589903a4 | ||
|
|
a8b625d861 | ||
|
|
9c89fce249 | ||
|
|
5e5f215be4 | ||
|
|
23dff58d22 | ||
|
|
0a7a7bc0d1 | ||
|
|
c772cab766 | ||
|
|
2e4d27c59a | ||
|
|
31f15b3651 | ||
|
|
b3d0c2ccc2 | ||
|
|
d4bcdb77ad | ||
|
|
1ba2f4e619 | ||
|
|
4bb9238679 | ||
|
|
bd11266f82 | ||
|
|
57e40a2b18 | ||
|
|
0fb8e4116f | ||
|
|
7128e1cea8 | ||
|
|
4788a633a6 | ||
|
|
ce2705d012 | ||
|
|
686fadf072 | ||
|
|
9e76021b94 | ||
|
|
cd37de481e |
129
.github/workflows/diff.yaml
vendored
129
.github/workflows/diff.yaml
vendored
@@ -229,10 +229,6 @@ jobs:
|
||||
# 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: |
|
||||
@@ -271,6 +267,13 @@ jobs:
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure -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: |
|
||||
cd tests
|
||||
@@ -279,6 +282,14 @@ 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
|
||||
|
||||
- name: Run stress test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
@@ -326,6 +337,116 @@ jobs:
|
||||
# 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@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: |
|
||||
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
|
||||
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@v3
|
||||
if: always()
|
||||
with:
|
||||
name: "Test data"
|
||||
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@v3
|
||||
with:
|
||||
# Number of commits to fetch. `0` indicates all history for all
|
||||
# branches and tags. (default: 1)
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
- name: Build release binaries
|
||||
run: |
|
||||
# Activate toolchain.
|
||||
source /opt/toolchain-v4/activate
|
||||
|
||||
# Initialize dependencies.
|
||||
./init
|
||||
|
||||
# Build 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
|
||||
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@v3
|
||||
if: always()
|
||||
with:
|
||||
name: "Test data"
|
||||
path: |
|
||||
# multiple paths could be defined
|
||||
build/logs
|
||||
|
||||
release_jepsen_test:
|
||||
name: "Release Jepsen Test"
|
||||
runs-on: [self-hosted, Linux, X64, Debian10, JepsenControl]
|
||||
|
||||
318
.github/workflows/release_centos8.yaml
vendored
318
.github/workflows/release_centos8.yaml
vendored
@@ -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
|
||||
15
.github/workflows/release_debian10.yaml
vendored
15
.github/workflows/release_debian10.yaml
vendored
@@ -281,6 +281,13 @@ jobs:
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- 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: |
|
||||
cd tests
|
||||
@@ -289,6 +296,14 @@ 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
|
||||
|
||||
- name: Run stress test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
|
||||
15
.github/workflows/release_ubuntu2004.yaml
vendored
15
.github/workflows/release_ubuntu2004.yaml
vendored
@@ -281,6 +281,13 @@ jobs:
|
||||
cd build
|
||||
ctest -R memgraph__unit --output-on-failure
|
||||
|
||||
- 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: |
|
||||
cd tests
|
||||
@@ -289,6 +296,14 @@ 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
|
||||
|
||||
- name: Run stress test (plain)
|
||||
run: |
|
||||
cd tests/stress
|
||||
|
||||
32
ADRs/001_tantivy.md
Normal file
32
ADRs/001_tantivy.md
Normal file
@@ -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.**
|
||||
34
ADRs/002_nuraft.md
Normal file
34
ADRs/002_nuraft.md
Normal file
@@ -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).
|
||||
67
ADRs/README.md
Normal file
67
ADRs/README.md
Normal file
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
17
query_modules/mgp_mock_example.py
Normal file
17
query_modules/mgp_mock_example.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# https://memgraph.com/docs/custom-query-modules/python
|
||||
# changes_logger.py OR can be directly pasted into the Lab
|
||||
import mgp_mock as mgp
|
||||
|
||||
@mgp.read_proc
|
||||
def log(
|
||||
context: mgp.ProcCtx,
|
||||
data: mgp.Nullable[mgp.Any]
|
||||
) -> mgp.Record(done=bool):
|
||||
# https://memgraph.com/docs/custom-query-modules/python/python-api#logger-objects
|
||||
logger = mgp.Logger()
|
||||
for changes in data:
|
||||
for item in changes:
|
||||
logger.info(str(item))
|
||||
return mgp.Record(done=True)
|
||||
|
||||
log(mgp.ProcCtx, ["bla"])
|
||||
@@ -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
|
||||
|
||||
@@ -255,10 +255,8 @@ 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 (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 +283,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 +293,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 +312,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 +325,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 +340,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);
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ 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:
|
||||
|
||||
@@ -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
|
||||
@@ -62,7 +62,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 +119,8 @@ std::string PermissionToString(Permission permission) {
|
||||
return "MULTI_DATABASE_EDIT";
|
||||
case Permission::MULTI_DATABASE_USE:
|
||||
return "MULTI_DATABASE_USE";
|
||||
case Permission::COORDINATOR:
|
||||
return "COORDINATOR";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,13 +489,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 +526,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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -48,6 +48,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 +247,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 +260,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 +292,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 +300,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 +313,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),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)});
|
||||
}
|
||||
|
||||
25
src/coordination/CMakeLists.txt
Normal file
25
src/coordination/CMakeLists.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
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_entity_info.hpp
|
||||
include/coordination/coordinator_exceptions.hpp
|
||||
include/coordination/coordinator_slk.hpp
|
||||
include/coordination/constants.hpp
|
||||
|
||||
PRIVATE
|
||||
coordinator_client.cpp
|
||||
coordinator_state.cpp
|
||||
coordinator_rpc.cpp
|
||||
coordinator_server.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
|
||||
)
|
||||
113
src/coordination/coordinator_client.cpp
Normal file
113
src/coordination/coordinator_client.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
// 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(const CoordinatorClientConfig &config)
|
||||
: rpc_context_{CreateClientContext(config)},
|
||||
rpc_client_{io::network::Endpoint(io::network::Endpoint::needs_resolving, config.ip_address, config.port),
|
||||
&rpc_context_},
|
||||
config_{config} {}
|
||||
|
||||
CoordinatorClient::~CoordinatorClient() {
|
||||
auto exit_job = utils::OnScopeExit([&] {
|
||||
StopFrequentCheck();
|
||||
thread_pool_.Shutdown();
|
||||
});
|
||||
const auto endpoint = rpc_client_.Endpoint();
|
||||
// Logging can throw
|
||||
spdlog::trace("Closing replication client on {}:{}", endpoint.address, endpoint.port);
|
||||
}
|
||||
|
||||
void CoordinatorClient::StartFrequentCheck() {
|
||||
MG_ASSERT(config_.health_check_frequency_sec > std::chrono::seconds(0),
|
||||
"Health check frequency must be greater than 0");
|
||||
replica_checker_.Run(
|
||||
"Coord checker", config_.health_check_frequency_sec,
|
||||
[last_response_time = &last_response_time_, rpc_client = &rpc_client_] {
|
||||
try {
|
||||
{
|
||||
auto stream{rpc_client->Stream<memgraph::replication_coordination_glue::FrequentHeartbeatRpc>()};
|
||||
stream.AwaitResponse();
|
||||
last_response_time->store(std::chrono::system_clock::now(), std::memory_order_acq_rel);
|
||||
}
|
||||
} catch (const rpc::RpcFailedException &) {
|
||||
// Nothing to do...wait for a reconnect
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CoordinatorClient::StopFrequentCheck() { replica_checker_.Stop(); }
|
||||
|
||||
bool CoordinatorClient::DoHealthCheck() const {
|
||||
auto current_time = std::chrono::system_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::seconds>(current_time -
|
||||
last_response_time_.load(std::memory_order_acquire));
|
||||
return duration.count() <= alive_response_time_difference_sec_;
|
||||
}
|
||||
|
||||
auto CoordinatorClient::InstanceName() const -> std::string_view { return config_.instance_name; }
|
||||
auto CoordinatorClient::Endpoint() const -> io::network::Endpoint const & { return rpc_client_.Endpoint(); }
|
||||
auto CoordinatorClient::Config() const -> CoordinatorClientConfig const & { return config_; }
|
||||
|
||||
auto CoordinatorClient::ReplicationClientInfo() const -> CoordinatorClientConfig::ReplicationClientInfo const & {
|
||||
MG_ASSERT(config_.replication_client_info.has_value(), "No ReplicationClientInfo for MAIN instance!");
|
||||
return *config_.replication_client_info;
|
||||
}
|
||||
|
||||
////// AF design choice
|
||||
auto CoordinatorClient::ReplicationClientInfo() -> std::optional<CoordinatorClientConfig::ReplicationClientInfo> & {
|
||||
MG_ASSERT(config_.replication_client_info.has_value(), "No ReplicationClientInfo for MAIN instance!");
|
||||
return config_.replication_client_info;
|
||||
}
|
||||
|
||||
void CoordinatorClient::UpdateTimeCheck(const std::chrono::system_clock::time_point &last_checked_time) {
|
||||
last_response_time_.store(last_checked_time, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
auto CoordinatorClient::GetLastTimeResponse() -> std::chrono::system_clock::time_point { return last_response_time_; }
|
||||
|
||||
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 perform failover!");
|
||||
return false;
|
||||
}
|
||||
spdlog::info("Sent failover RPC from coordinator to new main!");
|
||||
return true;
|
||||
}
|
||||
} catch (const rpc::RpcFailedException &) {
|
||||
spdlog::error("Failed to send failover RPC from coordinator to new main!");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
69
src/coordination/coordinator_rpc.cpp
Normal file
69
src/coordination/coordinator_rpc.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
// 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);
|
||||
}
|
||||
|
||||
} // 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};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
} // namespace slk
|
||||
|
||||
} // namespace memgraph
|
||||
|
||||
#endif
|
||||
57
src/coordination/coordinator_server.cpp
Normal file
57
src/coordination/coordinator_server.cpp
Normal file
@@ -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
|
||||
221
src/coordination/coordinator_state.cpp
Normal file
221
src/coordination/coordinator_state.cpp
Normal file
@@ -0,0 +1,221 @@
|
||||
// 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_state.hpp"
|
||||
#include <span>
|
||||
#include "coordination/coordinator_client.hpp"
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
#include "coordination/coordinator_config.hpp"
|
||||
#include "coordination/coordinator_entity_info.hpp"
|
||||
#include "flags/replication.hpp"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/variant_helpers.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <exception>
|
||||
#include <optional>
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
namespace {
|
||||
|
||||
bool CheckName(const std::list<CoordinatorClient> &replicas, const CoordinatorClientConfig &config) {
|
||||
auto name_matches = [&instance_name = config.instance_name](auto const &replica) {
|
||||
return replica.InstanceName() == instance_name;
|
||||
};
|
||||
return std::any_of(replicas.begin(), replicas.end(), name_matches);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
CoordinatorState::CoordinatorState() {
|
||||
MG_ASSERT(!(FLAGS_coordinator && FLAGS_coordinator_server_port),
|
||||
"Instance cannot be a coordinator and have registered coordinator server.");
|
||||
|
||||
if (FLAGS_coordinator_server_port) {
|
||||
auto const config = CoordinatorServerConfig{
|
||||
.ip_address = kDefaultReplicationServerIp,
|
||||
.port = static_cast<uint16_t>(FLAGS_coordinator_server_port),
|
||||
};
|
||||
|
||||
data_ = CoordinatorMainReplicaData{.coordinator_server_ = std::make_unique<CoordinatorServer>(config)};
|
||||
}
|
||||
}
|
||||
|
||||
auto CoordinatorState::RegisterReplica(const CoordinatorClientConfig &config)
|
||||
-> utils::BasicResult<RegisterMainReplicaCoordinatorStatus, CoordinatorClient *> {
|
||||
const auto name_endpoint_status =
|
||||
std::visit(memgraph::utils::Overloaded{[](const CoordinatorMainReplicaData & /*coordinator_main_replica_data*/) {
|
||||
return RegisterMainReplicaCoordinatorStatus::NOT_COORDINATOR;
|
||||
},
|
||||
[&config](const CoordinatorData &coordinator_data) {
|
||||
if (memgraph::coordination::CheckName(
|
||||
coordinator_data.registered_replicas_, config)) {
|
||||
return RegisterMainReplicaCoordinatorStatus::NAME_EXISTS;
|
||||
}
|
||||
return RegisterMainReplicaCoordinatorStatus::SUCCESS;
|
||||
}},
|
||||
data_);
|
||||
|
||||
if (name_endpoint_status != RegisterMainReplicaCoordinatorStatus::SUCCESS) {
|
||||
return name_endpoint_status;
|
||||
}
|
||||
|
||||
// Maybe no need to return client if you can start replica client here
|
||||
return &std::get<CoordinatorData>(data_).registered_replicas_.emplace_back(config);
|
||||
}
|
||||
|
||||
auto CoordinatorState::RegisterMain(const CoordinatorClientConfig &config)
|
||||
-> utils::BasicResult<RegisterMainReplicaCoordinatorStatus, CoordinatorClient *> {
|
||||
const auto endpoint_status = std::visit(
|
||||
memgraph::utils::Overloaded{
|
||||
[](const CoordinatorMainReplicaData & /*coordinator_main_replica_data*/) {
|
||||
return RegisterMainReplicaCoordinatorStatus::NOT_COORDINATOR;
|
||||
},
|
||||
[](const CoordinatorData & /*coordinator_data*/) { return RegisterMainReplicaCoordinatorStatus::SUCCESS; }},
|
||||
data_);
|
||||
|
||||
if (endpoint_status != RegisterMainReplicaCoordinatorStatus::SUCCESS) {
|
||||
return endpoint_status;
|
||||
}
|
||||
|
||||
auto ®istered_main = std::get<CoordinatorData>(data_).registered_main_;
|
||||
registered_main = std::make_unique<CoordinatorClient>(config);
|
||||
return registered_main.get();
|
||||
}
|
||||
|
||||
auto CoordinatorState::ShowReplicas() const -> std::vector<CoordinatorEntityInfo> {
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorData>(data_),
|
||||
"Can't call show replicas on data_, as variant holds wrong alternative");
|
||||
std::vector<CoordinatorEntityInfo> result;
|
||||
const auto ®istered_replicas = std::get<CoordinatorData>(data_).registered_replicas_;
|
||||
result.reserve(registered_replicas.size());
|
||||
std::ranges::transform(registered_replicas, std::back_inserter(result), [](const auto &replica) {
|
||||
return CoordinatorEntityInfo{replica.InstanceName(), replica.Endpoint()};
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
auto CoordinatorState::ShowMain() const -> std::optional<CoordinatorEntityInfo> {
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorData>(data_),
|
||||
"Can't call show main on data_, as variant holds wrong alternative");
|
||||
const auto ®istered_main = std::get<CoordinatorData>(data_).registered_main_;
|
||||
if (registered_main) {
|
||||
return CoordinatorEntityInfo{registered_main->InstanceName(), registered_main->Endpoint()};
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto CoordinatorState::PingReplicas() const -> std::unordered_map<std::string_view, bool> {
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorData>(data_),
|
||||
"Can't call ping replicas on data_, as variant holds wrong alternative");
|
||||
std::unordered_map<std::string_view, bool> result;
|
||||
const auto ®istered_replicas = std::get<CoordinatorData>(data_).registered_replicas_;
|
||||
result.reserve(registered_replicas.size());
|
||||
for (const CoordinatorClient &replica_client : registered_replicas) {
|
||||
result.emplace(replica_client.InstanceName(), replica_client.DoHealthCheck());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto CoordinatorState::PingMain() const -> std::optional<CoordinatorEntityHealthInfo> {
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorData>(data_),
|
||||
"Can't call show main on data_, as variant holds wrong alternative");
|
||||
const auto ®istered_main = std::get<CoordinatorData>(data_).registered_main_;
|
||||
if (registered_main) {
|
||||
return CoordinatorEntityHealthInfo{registered_main->InstanceName(), registered_main->DoHealthCheck()};
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto CoordinatorState::DoFailover() -> DoFailoverStatus {
|
||||
// 1. MAIN is already down, stop sending frequent checks
|
||||
// 2. find new replica (coordinator)
|
||||
// 3. make copy replica's client as potential new main client (coordinator)
|
||||
// 4. send failover RPC to new main (coordinator and new main)
|
||||
// 5. exchange old main to new main (coordinator)
|
||||
// 6. remove replica which was promoted to main from all replicas -> this will shut down RPC frequent check client
|
||||
// (coordinator)
|
||||
// 7. for new main start frequent checks (coordinator)
|
||||
|
||||
MG_ASSERT(std::holds_alternative<CoordinatorData>(data_), "Cannot do failover since variant holds wrong alternative");
|
||||
using ReplicationClientInfo = CoordinatorClientConfig::ReplicationClientInfo;
|
||||
|
||||
// 1.
|
||||
auto ¤t_main = std::get<CoordinatorData>(data_).registered_main_;
|
||||
|
||||
if (!current_main) {
|
||||
return DoFailoverStatus::CLUSTER_UNINITIALIZED;
|
||||
}
|
||||
|
||||
if (current_main->DoHealthCheck()) {
|
||||
return DoFailoverStatus::MAIN_ALIVE;
|
||||
}
|
||||
current_main->StopFrequentCheck();
|
||||
|
||||
// 2.
|
||||
// Get all replicas and find new main
|
||||
auto ®istered_replicas = std::get<CoordinatorData>(data_).registered_replicas_;
|
||||
|
||||
const auto chosen_replica = std::ranges::find_if(
|
||||
registered_replicas, [](const CoordinatorClient &replica) { return replica.DoHealthCheck(); });
|
||||
if (chosen_replica == registered_replicas.end()) {
|
||||
return DoFailoverStatus::ALL_REPLICAS_DOWN;
|
||||
}
|
||||
|
||||
std::vector<ReplicationClientInfo> repl_clients_info;
|
||||
repl_clients_info.reserve(registered_replicas.size() - 1);
|
||||
std::ranges::for_each(registered_replicas, [&chosen_replica, &repl_clients_info](const CoordinatorClient &replica) {
|
||||
if (replica != *chosen_replica) {
|
||||
repl_clients_info.emplace_back(replica.ReplicationClientInfo());
|
||||
}
|
||||
});
|
||||
|
||||
// 3.
|
||||
// Set on coordinator data of new main
|
||||
// allocate resources for new main, clear replication info on this replica as main
|
||||
// set last response time
|
||||
auto potential_new_main = std::make_unique<CoordinatorClient>(chosen_replica->Config());
|
||||
potential_new_main->ReplicationClientInfo().reset();
|
||||
potential_new_main->UpdateTimeCheck(chosen_replica->GetLastTimeResponse());
|
||||
|
||||
// 4.
|
||||
if (!chosen_replica->SendPromoteReplicaToMainRpc(std::move(repl_clients_info))) {
|
||||
spdlog::error("Sent RPC message, but exception was caught, aborting Failover");
|
||||
// TODO: new status and rollback all changes that were done...
|
||||
MG_ASSERT(false, "RPC message failed");
|
||||
}
|
||||
|
||||
// 5.
|
||||
current_main = std::move(potential_new_main);
|
||||
|
||||
// 6. remove old replica
|
||||
// TODO: Stop pinging chosen_replica before failover.
|
||||
// Check that it doesn't fail when you call StopFrequentCheck if it is already stopped
|
||||
registered_replicas.erase(chosen_replica);
|
||||
|
||||
// 7.
|
||||
current_main->StartFrequentCheck();
|
||||
|
||||
return DoFailoverStatus::SUCCESS;
|
||||
}
|
||||
|
||||
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
|
||||
22
src/coordination/include/coordination/constants.hpp
Normal file
22
src/coordination/include/coordination/constants.hpp
Normal file
@@ -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
|
||||
69
src/coordination/include/coordination/coordinator_client.hpp
Normal file
69
src/coordination/include/coordination/coordinator_client.hpp
Normal file
@@ -0,0 +1,69 @@
|
||||
// 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"
|
||||
#include "utils/thread_pool.hpp"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
class CoordinatorClient {
|
||||
public:
|
||||
explicit CoordinatorClient(const CoordinatorClientConfig &config);
|
||||
|
||||
~CoordinatorClient();
|
||||
|
||||
CoordinatorClient(CoordinatorClient &other) = delete;
|
||||
CoordinatorClient &operator=(CoordinatorClient const &other) = delete;
|
||||
|
||||
CoordinatorClient(CoordinatorClient &&) noexcept = delete;
|
||||
CoordinatorClient &operator=(CoordinatorClient &&) noexcept = delete;
|
||||
|
||||
void StartFrequentCheck();
|
||||
void StopFrequentCheck();
|
||||
|
||||
auto DoHealthCheck() const -> bool;
|
||||
auto SendPromoteReplicaToMainRpc(
|
||||
std::vector<CoordinatorClientConfig::ReplicationClientInfo> replication_clients_info) const -> bool;
|
||||
|
||||
auto InstanceName() const -> std::string_view;
|
||||
auto Endpoint() const -> io::network::Endpoint const &;
|
||||
auto Config() const -> CoordinatorClientConfig const &;
|
||||
auto ReplicationClientInfo() const -> CoordinatorClientConfig::ReplicationClientInfo const &;
|
||||
auto ReplicationClientInfo() -> std::optional<CoordinatorClientConfig::ReplicationClientInfo> &;
|
||||
void UpdateTimeCheck(const std::chrono::system_clock::time_point &last_checked_time);
|
||||
auto GetLastTimeResponse() -> std::chrono::system_clock::time_point;
|
||||
|
||||
friend bool operator==(CoordinatorClient const &first, CoordinatorClient const &second) {
|
||||
return first.config_ == second.config_;
|
||||
}
|
||||
|
||||
private:
|
||||
utils::ThreadPool thread_pool_{1};
|
||||
utils::Scheduler replica_checker_;
|
||||
|
||||
communication::ClientContext rpc_context_;
|
||||
mutable rpc::Client rpc_client_;
|
||||
CoordinatorClientConfig config_;
|
||||
|
||||
std::atomic<std::chrono::system_clock::time_point> last_response_time_{};
|
||||
static constexpr int alive_response_time_difference_sec_{5};
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
77
src/coordination/include/coordination/coordinator_config.hpp
Normal file
77
src/coordination/include/coordination/coordinator_config.hpp
Normal file
@@ -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 "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 {
|
||||
const std::string instance_name;
|
||||
const std::string ip_address;
|
||||
const uint16_t port{};
|
||||
|
||||
// Frequency with which coordinator pings main/replicas about it status
|
||||
const std::chrono::seconds health_check_frequency_sec{1};
|
||||
|
||||
// Info which coordinator will send to new main when performing failover
|
||||
struct ReplicationClientInfo {
|
||||
// Should 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;
|
||||
};
|
||||
|
||||
std::optional<ReplicationClientInfo> replication_client_info;
|
||||
|
||||
struct SSL {
|
||||
const std::string key_file;
|
||||
const std::string cert_file;
|
||||
|
||||
friend bool operator==(const SSL &, const SSL &) = default;
|
||||
};
|
||||
|
||||
const 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
|
||||
@@ -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 "io/network/endpoint.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
struct CoordinatorEntityInfo {
|
||||
std::string_view name;
|
||||
const io::network::Endpoint &endpoint;
|
||||
};
|
||||
|
||||
struct CoordinatorEntityHealthInfo {
|
||||
std::string_view name;
|
||||
bool alive;
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
|
||||
#endif
|
||||
@@ -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
|
||||
66
src/coordination/include/coordination/coordinator_rpc.hpp
Normal file
66
src/coordination/include/coordination/coordinator_rpc.hpp
Normal file
@@ -0,0 +1,66 @@
|
||||
// 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>;
|
||||
|
||||
} // 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);
|
||||
|
||||
} // namespace memgraph::slk
|
||||
|
||||
#endif
|
||||
44
src/coordination/include/coordination/coordinator_server.hpp
Normal file
44
src/coordination/include/coordination/coordinator_server.hpp
Normal file
@@ -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
|
||||
38
src/coordination/include/coordination/coordinator_slk.hpp
Normal file
38
src/coordination/include/coordination/coordinator_slk.hpp
Normal file
@@ -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
|
||||
94
src/coordination/include/coordination/coordinator_state.hpp
Normal file
94
src/coordination/include/coordination/coordinator_state.hpp
Normal file
@@ -0,0 +1,94 @@
|
||||
// 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_entity_info.hpp"
|
||||
#include "coordination/coordinator_server.hpp"
|
||||
#include "rpc/server.hpp"
|
||||
#include "utils/result.hpp"
|
||||
#include "utils/rw_spin_lock.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
|
||||
#include <list>
|
||||
#include <variant>
|
||||
|
||||
namespace memgraph::coordination {
|
||||
|
||||
enum class RegisterMainReplicaCoordinatorStatus : uint8_t {
|
||||
NAME_EXISTS,
|
||||
END_POINT_EXISTS,
|
||||
COULD_NOT_BE_PERSISTED,
|
||||
NOT_COORDINATOR,
|
||||
SUCCESS
|
||||
};
|
||||
|
||||
enum class DoFailoverStatus : uint8_t { SUCCESS, ALL_REPLICAS_DOWN, MAIN_ALIVE, CLUSTER_UNINITIALIZED };
|
||||
|
||||
class CoordinatorState {
|
||||
public:
|
||||
CoordinatorState();
|
||||
~CoordinatorState() = default;
|
||||
|
||||
CoordinatorState(const CoordinatorState &) = delete;
|
||||
CoordinatorState &operator=(const CoordinatorState &) = delete;
|
||||
|
||||
CoordinatorState(CoordinatorState &&other) noexcept : data_(std::move(other.data_)) {}
|
||||
|
||||
CoordinatorState &operator=(CoordinatorState &&other) noexcept {
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
data_ = std::move(other.data_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto RegisterReplica(const CoordinatorClientConfig &config)
|
||||
-> utils::BasicResult<RegisterMainReplicaCoordinatorStatus, CoordinatorClient *>;
|
||||
|
||||
auto RegisterMain(const CoordinatorClientConfig &config)
|
||||
-> utils::BasicResult<RegisterMainReplicaCoordinatorStatus, CoordinatorClient *>;
|
||||
|
||||
auto ShowReplicas() const -> std::vector<CoordinatorEntityInfo>;
|
||||
|
||||
auto PingReplicas() const -> std::unordered_map<std::string_view, bool>;
|
||||
|
||||
auto ShowMain() const -> std::optional<CoordinatorEntityInfo>;
|
||||
|
||||
auto PingMain() const -> std::optional<CoordinatorEntityHealthInfo>;
|
||||
|
||||
// The client code must check that the server exists before calling this method.
|
||||
auto GetCoordinatorServer() const -> CoordinatorServer &;
|
||||
|
||||
auto DoFailover() -> DoFailoverStatus;
|
||||
|
||||
private:
|
||||
// TODO: Data is not thread safe
|
||||
|
||||
// Coordinator stores registered replicas and main
|
||||
struct CoordinatorData {
|
||||
std::list<CoordinatorClient> registered_replicas_;
|
||||
std::unique_ptr<CoordinatorClient> registered_main_;
|
||||
};
|
||||
|
||||
// Data which each main and replica stores
|
||||
struct CoordinatorMainReplicaData {
|
||||
std::unique_ptr<CoordinatorServer> coordinator_server_;
|
||||
};
|
||||
|
||||
std::variant<CoordinatorData, CoordinatorMainReplicaData> data_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::coordination
|
||||
#endif
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
99
src/dbms/coordinator_handler.cpp
Normal file
99
src/dbms/coordinator_handler.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
// 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_handler.hpp"
|
||||
|
||||
#include "dbms/dbms_handler.hpp"
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
CoordinatorHandler::CoordinatorHandler(DbmsHandler &dbms_handler) : dbms_handler_(dbms_handler) {}
|
||||
|
||||
auto CoordinatorHandler::RegisterReplicaOnCoordinator(const memgraph::coordination::CoordinatorClientConfig &config)
|
||||
-> utils::BasicResult<RegisterMainReplicaCoordinatorStatus> {
|
||||
auto instance_client = dbms_handler_.CoordinatorState().RegisterReplica(config);
|
||||
using repl_status = memgraph::coordination::RegisterMainReplicaCoordinatorStatus;
|
||||
using dbms_status = memgraph::dbms::RegisterMainReplicaCoordinatorStatus;
|
||||
if (instance_client.HasError()) {
|
||||
switch (instance_client.GetError()) {
|
||||
case memgraph::coordination::RegisterMainReplicaCoordinatorStatus::NOT_COORDINATOR:
|
||||
MG_ASSERT(false, "Only coordinator instance can register main and replica!");
|
||||
return {};
|
||||
case repl_status::NAME_EXISTS:
|
||||
return dbms_status::NAME_EXISTS;
|
||||
case repl_status::END_POINT_EXISTS:
|
||||
return dbms_status::END_POINT_EXISTS;
|
||||
case repl_status::COULD_NOT_BE_PERSISTED:
|
||||
return dbms_status::COULD_NOT_BE_PERSISTED;
|
||||
case repl_status::SUCCESS:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
instance_client.GetValue()->StartFrequentCheck();
|
||||
return {};
|
||||
}
|
||||
|
||||
auto CoordinatorHandler::RegisterMainOnCoordinator(const memgraph::coordination::CoordinatorClientConfig &config)
|
||||
-> utils::BasicResult<RegisterMainReplicaCoordinatorStatus> {
|
||||
auto instance_client = dbms_handler_.CoordinatorState().RegisterMain(config);
|
||||
if (instance_client.HasError()) switch (instance_client.GetError()) {
|
||||
case memgraph::coordination::RegisterMainReplicaCoordinatorStatus::NOT_COORDINATOR:
|
||||
MG_ASSERT(false, "Only coordinator instance can register main and replica!");
|
||||
case memgraph::coordination::RegisterMainReplicaCoordinatorStatus::NAME_EXISTS:
|
||||
return memgraph::dbms::RegisterMainReplicaCoordinatorStatus::NAME_EXISTS;
|
||||
case memgraph::coordination::RegisterMainReplicaCoordinatorStatus::END_POINT_EXISTS:
|
||||
return memgraph::dbms::RegisterMainReplicaCoordinatorStatus::END_POINT_EXISTS;
|
||||
case memgraph::coordination::RegisterMainReplicaCoordinatorStatus::COULD_NOT_BE_PERSISTED:
|
||||
return memgraph::dbms::RegisterMainReplicaCoordinatorStatus::COULD_NOT_BE_PERSISTED;
|
||||
case memgraph::coordination::RegisterMainReplicaCoordinatorStatus::SUCCESS:
|
||||
break;
|
||||
}
|
||||
|
||||
instance_client.GetValue()->StartFrequentCheck();
|
||||
return {};
|
||||
}
|
||||
|
||||
auto CoordinatorHandler::ShowReplicasOnCoordinator() const -> std::vector<coordination::CoordinatorEntityInfo> {
|
||||
return dbms_handler_.CoordinatorState().ShowReplicas();
|
||||
}
|
||||
|
||||
auto CoordinatorHandler::PingReplicasOnCoordinator() const -> std::unordered_map<std::string_view, bool> {
|
||||
return dbms_handler_.CoordinatorState().PingReplicas();
|
||||
}
|
||||
|
||||
auto CoordinatorHandler::ShowMainOnCoordinator() const -> std::optional<coordination::CoordinatorEntityInfo> {
|
||||
return dbms_handler_.CoordinatorState().ShowMain();
|
||||
}
|
||||
|
||||
auto CoordinatorHandler::PingMainOnCoordinator() const -> std::optional<coordination::CoordinatorEntityHealthInfo> {
|
||||
return dbms_handler_.CoordinatorState().PingMain();
|
||||
}
|
||||
|
||||
auto CoordinatorHandler::DoFailover() const -> DoFailoverStatus {
|
||||
auto status = dbms_handler_.CoordinatorState().DoFailover();
|
||||
switch (status) {
|
||||
case memgraph::coordination::DoFailoverStatus::ALL_REPLICAS_DOWN:
|
||||
return memgraph::dbms::DoFailoverStatus::ALL_REPLICAS_DOWN;
|
||||
case memgraph::coordination::DoFailoverStatus::SUCCESS:
|
||||
return memgraph::dbms::DoFailoverStatus::SUCCESS;
|
||||
case memgraph::coordination::DoFailoverStatus::MAIN_ALIVE:
|
||||
return memgraph::dbms::DoFailoverStatus::MAIN_ALIVE;
|
||||
case memgraph::coordination::DoFailoverStatus::CLUSTER_UNINITIALIZED:
|
||||
return memgraph::dbms::DoFailoverStatus::CLUSTER_UNINITIALIZED;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
|
||||
#endif
|
||||
67
src/dbms/coordinator_handler.hpp
Normal file
67
src/dbms/coordinator_handler.hpp
Normal file
@@ -0,0 +1,67 @@
|
||||
// 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 <cstdint>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace memgraph::coordination {
|
||||
struct CoordinatorEntityInfo;
|
||||
struct CoordinatorEntityHealthInfo;
|
||||
struct CoordinatorClientConfig;
|
||||
} // namespace memgraph::coordination
|
||||
|
||||
namespace memgraph::dbms {
|
||||
|
||||
enum class RegisterMainReplicaCoordinatorStatus : uint8_t {
|
||||
NAME_EXISTS,
|
||||
END_POINT_EXISTS,
|
||||
COULD_NOT_BE_PERSISTED,
|
||||
NOT_COORDINATOR,
|
||||
SUCCESS
|
||||
};
|
||||
|
||||
enum class DoFailoverStatus : uint8_t { SUCCESS, ALL_REPLICAS_DOWN, MAIN_ALIVE, CLUSTER_UNINITIALIZED };
|
||||
|
||||
class DbmsHandler;
|
||||
|
||||
class CoordinatorHandler {
|
||||
public:
|
||||
explicit CoordinatorHandler(DbmsHandler &dbms_handler);
|
||||
|
||||
auto RegisterReplicaOnCoordinator(const memgraph::coordination::CoordinatorClientConfig &config)
|
||||
-> utils::BasicResult<RegisterMainReplicaCoordinatorStatus>;
|
||||
|
||||
auto RegisterMainOnCoordinator(const memgraph::coordination::CoordinatorClientConfig &config)
|
||||
-> utils::BasicResult<RegisterMainReplicaCoordinatorStatus>;
|
||||
|
||||
auto ShowReplicasOnCoordinator() const -> std::vector<memgraph::coordination::CoordinatorEntityInfo>;
|
||||
|
||||
auto ShowMainOnCoordinator() const -> std::optional<memgraph::coordination::CoordinatorEntityInfo>;
|
||||
|
||||
auto PingReplicasOnCoordinator() const -> std::unordered_map<std::string_view, bool>;
|
||||
|
||||
auto PingMainOnCoordinator() const -> std::optional<memgraph::coordination::CoordinatorEntityHealthInfo>;
|
||||
|
||||
auto DoFailover() const -> DoFailoverStatus;
|
||||
|
||||
private:
|
||||
DbmsHandler &dbms_handler_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
#endif
|
||||
106
src/dbms/coordinator_handlers.cpp
Normal file
106
src/dbms/coordinator_handlers.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
// 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"
|
||||
|
||||
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 from coordinator server");
|
||||
CoordinatorHandlers::PromoteReplicaToMainHandler(dbms_handler, req_reader, 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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
std::vector<replication::ReplicationClientConfig> clients_config;
|
||||
clients_config.reserve(req.replication_clients_info.size());
|
||||
std::ranges::transform(req.replication_clients_info, std::back_inserter(clients_config),
|
||||
[](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,
|
||||
};
|
||||
});
|
||||
|
||||
std::ranges::for_each(clients_config, [&dbms_handler, &repl_state, &res_builder](const auto &config) {
|
||||
auto instance_client = repl_state.RegisterReplica(config);
|
||||
if (instance_client.HasError()) {
|
||||
switch (instance_client.GetError()) {
|
||||
case memgraph::replication::RegisterReplicaError::NOT_MAIN:
|
||||
spdlog::error("Failover must be performed to main!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
case memgraph::replication::RegisterReplicaError::NAME_EXISTS:
|
||||
spdlog::error("Replica with the same name already exists!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
case memgraph::replication::RegisterReplicaError::END_POINT_EXISTS:
|
||||
spdlog::error("Replica with the same endpoint already exists!");
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
auto &instance_client_ref = *instance_client.GetValue();
|
||||
const bool all_clients_good = memgraph::dbms::RegisterAllDatabasesClients(dbms_handler, instance_client_ref);
|
||||
|
||||
if (!all_clients_good) {
|
||||
spdlog::error("Failed to register all databases to the REPLICA \"{}\"", config.name);
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{false}, res_builder);
|
||||
return;
|
||||
}
|
||||
|
||||
StartReplicaClient(dbms_handler, instance_client_ref);
|
||||
});
|
||||
|
||||
slk::Save(coordination::PromoteReplicaToMainRes{true}, res_builder);
|
||||
}
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
#endif
|
||||
33
src/dbms/coordinator_handlers.hpp
Normal file
33
src/dbms/coordinator_handlers.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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);
|
||||
};
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
|
||||
#endif
|
||||
@@ -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 {
|
||||
|
||||
@@ -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::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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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::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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -124,7 +101,8 @@ auto ReplicationHandler::RegisterReplica(const memgraph::replication::Replicatio
|
||||
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()) {
|
||||
if (instance_client.HasError()) {
|
||||
switch (instance_client.GetError()) {
|
||||
case memgraph::replication::RegisterReplicaError::NOT_MAIN:
|
||||
MG_ASSERT(false, "Only main instance can register a replica!");
|
||||
return {};
|
||||
@@ -137,40 +115,27 @@ auto ReplicationHandler::RegisterReplica(const memgraph::replication::Replicatio
|
||||
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(*instance_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::HandleErrorOnReplicaClient(instance_client);
|
||||
if (dbms_error.has_value()) {
|
||||
return *dbms_error;
|
||||
}
|
||||
auto &instance_client_ptr = instance_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;
|
||||
}
|
||||
@@ -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; });
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
|
||||
@@ -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 "dbms/database.hpp"
|
||||
#include "replication/role.hpp"
|
||||
#include "storage/v2/storage.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 UnregisterReplicaResult : uint8_t {
|
||||
NOT_MAIN,
|
||||
COULD_NOT_BE_PERSISTED,
|
||||
@@ -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
|
||||
|
||||
64
src/dbms/transaction.hpp
Normal file
64
src/dbms/transaction.hpp
Normal file
@@ -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
|
||||
100
src/dbms/utils.hpp
Normal file
100
src/dbms/utils.hpp
Normal file
@@ -0,0 +1,100 @@
|
||||
// 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) {
|
||||
// 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 (!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<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 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;
|
||||
}
|
||||
storage_clients.push_back(std::move(client));
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
return all_clients_good;
|
||||
}
|
||||
|
||||
inline std::optional<RegisterReplicaError> HandleErrorOnReplicaClient(
|
||||
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::END_POINT_EXISTS:
|
||||
return dbms::RegisterReplicaError::END_POINT_EXISTS;
|
||||
case replication::RegisterReplicaError::COULD_NOT_BE_PERSISTED:
|
||||
return dbms::RegisterReplicaError::COULD_NOT_BE_PERSISTED;
|
||||
case replication::RegisterReplicaError::SUCCESS:
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace memgraph::dbms
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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,11 +112,6 @@ 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)
|
||||
|
||||
26
src/flags/replication.cpp
Normal file
26
src/flags/replication.cpp
Normal file
@@ -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");
|
||||
26
src/flags/replication.hpp
Normal file
26
src/flags/replication.hpp
Normal file
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -294,7 +294,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
|
||||
}
|
||||
|
||||
@@ -393,7 +393,7 @@ std::vector<std::vector<memgraph::query::TypedValue>> AuthQueryHandler::GetDatab
|
||||
}
|
||||
}
|
||||
|
||||
bool AuthQueryHandler::SetMainDatabase(const std::string &db, const std::string &username) {
|
||||
bool AuthQueryHandler::SetMainDatabase(std::string_view db, const std::string &username) {
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw memgraph::query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
|
||||
@@ -44,7 +44,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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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_) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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::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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.") {}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
@@ -3025,17 +3028,9 @@ class ReplicationQuery : public memgraph::query::Query {
|
||||
static const utils::TypeInfo kType;
|
||||
const utils::TypeInfo &GetTypeInfo() const override { return kType; }
|
||||
|
||||
enum class Action {
|
||||
SET_REPLICATION_ROLE,
|
||||
SHOW_REPLICATION_ROLE,
|
||||
REGISTER_REPLICA,
|
||||
REGISTER_MAIN,
|
||||
DROP_REPLICA,
|
||||
SHOW_REPLICAS,
|
||||
SHOW_REPLICATION_CLUSTER
|
||||
};
|
||||
enum class Action { SET_REPLICATION_ROLE, SHOW_REPLICATION_ROLE, REGISTER_REPLICA, DROP_REPLICA, SHOW_REPLICAS };
|
||||
|
||||
enum class ReplicationRole { MAIN, REPLICA, COORDINATOR };
|
||||
enum class ReplicationRole { MAIN, REPLICA };
|
||||
|
||||
enum class SyncMode { SYNC, ASYNC };
|
||||
|
||||
@@ -3047,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_;
|
||||
|
||||
@@ -3056,10 +3052,57 @@ 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_MAIN_COORDINATOR_SERVER,
|
||||
REGISTER_REPLICA_COORDINATOR_SERVER,
|
||||
SHOW_REPLICATION_CLUSTER,
|
||||
DO_FAILOVER
|
||||
};
|
||||
|
||||
enum class ReplicationRole { MAIN, REPLICA };
|
||||
|
||||
enum class SyncMode { SYNC, ASYNC };
|
||||
|
||||
CoordinatorQuery() = default;
|
||||
|
||||
DEFVISITABLE(QueryVisitor<void>);
|
||||
|
||||
memgraph::query::CoordinatorQuery::Action action_;
|
||||
memgraph::query::CoordinatorQuery::ReplicationRole role_;
|
||||
std::string instance_name_;
|
||||
memgraph::query::Expression *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->role_ = role_;
|
||||
object->instance_name_ = instance_name_;
|
||||
object->socket_address_ = socket_address_ ? 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;
|
||||
}
|
||||
|
||||
@@ -3528,6 +3571,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;
|
||||
@@ -3561,7 +3663,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_;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,31 +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;
|
||||
if (ctx->MAIN()) {
|
||||
// Can set a port only if query is executed on coordinator
|
||||
|
||||
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!");
|
||||
}
|
||||
|
||||
} 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!");
|
||||
}
|
||||
} else if (ctx->COORDINATOR()) {
|
||||
replication_query->role_ = ReplicationQuery::ReplicationRole::COORDINATOR;
|
||||
if (ctx->WITH() || ctx->PORT()) {
|
||||
throw SyntaxException("Coordinator shouldn't have port as an integer literal!");
|
||||
}
|
||||
replication_query->port_ = std::any_cast<Expression *>(ctx->port->accept(this));
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -349,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()) {
|
||||
@@ -358,40 +368,88 @@ 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;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitRegisterMain(MemgraphCypher::RegisterMainContext *ctx) {
|
||||
auto *replication_query = storage_->Create<ReplicationQuery>();
|
||||
replication_query->action_ = ReplicationQuery::Action::REGISTER_MAIN;
|
||||
// License check is done in the interpreter.
|
||||
antlrcpp::Any CypherMainVisitor::visitRegisterCoordinatorServer(MemgraphCypher::RegisterCoordinatorServerContext *ctx) {
|
||||
MG_ASSERT(ctx->children.size() == 1, "RegisterCoordinatorServerQuery should have exactly one child!");
|
||||
auto *coordinator_query = std::any_cast<CoordinatorQuery *>(ctx->children[0]->accept(this));
|
||||
query_ = coordinator_query;
|
||||
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;
|
||||
}
|
||||
|
||||
// License check is done in the interpreter
|
||||
antlrcpp::Any CypherMainVisitor::visitRegisterReplicaCoordinatorServer(
|
||||
MemgraphCypher::RegisterReplicaCoordinatorServerContext *ctx) {
|
||||
auto *coordinator_query = storage_->Create<CoordinatorQuery>();
|
||||
if (!ctx->socketAddress()->literal()->StringLiteral()) {
|
||||
throw SemanticException("Socket address should be a string literal!");
|
||||
}
|
||||
replication_query->socket_address_ = std::any_cast<Expression *>(ctx->socketAddress()->accept(this));
|
||||
return replication_query;
|
||||
|
||||
if (!ctx->coordinatorSocketAddress()->literal()->StringLiteral()) {
|
||||
throw SemanticException("Coordinator socket address should be a string literal!");
|
||||
}
|
||||
coordinator_query->action_ = CoordinatorQuery::Action::REGISTER_REPLICA_COORDINATOR_SERVER;
|
||||
coordinator_query->role_ = CoordinatorQuery::ReplicationRole::REPLICA;
|
||||
coordinator_query->socket_address_ = std::any_cast<Expression *>(ctx->socketAddress()->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->SYNC()) {
|
||||
coordinator_query->sync_mode_ = memgraph::query::CoordinatorQuery::SyncMode::SYNC;
|
||||
} else if (ctx->ASYNC()) {
|
||||
coordinator_query->sync_mode_ = memgraph::query::CoordinatorQuery::SyncMode::ASYNC;
|
||||
}
|
||||
|
||||
return coordinator_query;
|
||||
}
|
||||
|
||||
// License check is done in the interpreter
|
||||
antlrcpp::Any CypherMainVisitor::visitRegisterMainCoordinatorServer(
|
||||
MemgraphCypher::RegisterMainCoordinatorServerContext *ctx) {
|
||||
if (!ctx->coordinatorSocketAddress()->literal()->StringLiteral()) {
|
||||
throw SemanticException("Coordinator socket address should be a string literal!");
|
||||
}
|
||||
auto *coordinator_query = storage_->Create<CoordinatorQuery>();
|
||||
coordinator_query->action_ = CoordinatorQuery::Action::REGISTER_MAIN_COORDINATOR_SERVER;
|
||||
coordinator_query->role_ = CoordinatorQuery::ReplicationRole::MAIN;
|
||||
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));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitShowReplicationCluster(MemgraphCypher::ShowReplicationClusterContext *ctx) {
|
||||
auto *replication_query = storage_->Create<ReplicationQuery>();
|
||||
replication_query->action_ = ReplicationQuery::Action::SHOW_REPLICATION_CLUSTER;
|
||||
return replication_query;
|
||||
// License check is done in the interpreter
|
||||
antlrcpp::Any CypherMainVisitor::visitDoFailover(MemgraphCypher::DoFailoverContext * /*ctx*/) {
|
||||
auto *coordinator_query = storage_->Create<CoordinatorQuery>();
|
||||
coordinator_query->action_ = CoordinatorQuery::Action::DO_FAILOVER;
|
||||
query_ = coordinator_query;
|
||||
return coordinator_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitLockPathQuery(MemgraphCypher::LockPathQueryContext *ctx) {
|
||||
@@ -1661,6 +1719,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!");
|
||||
}
|
||||
|
||||
@@ -1775,7 +1834,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));
|
||||
@@ -1848,7 +1911,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;
|
||||
}
|
||||
@@ -1984,6 +2055,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)));
|
||||
@@ -2478,6 +2561,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
|
||||
@@ -2538,6 +2623,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));
|
||||
}
|
||||
@@ -2876,6 +2974,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_;
|
||||
|
||||
@@ -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) {
|
||||
@@ -221,11 +223,6 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
*/
|
||||
antlrcpp::Any visitRegisterReplica(MemgraphCypher::RegisterReplicaContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return ReplicationQuery*
|
||||
*/
|
||||
antlrcpp::Any visitRegisterMain(MemgraphCypher::RegisterMainContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return ReplicationQuery*
|
||||
*/
|
||||
@@ -237,10 +234,36 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
antlrcpp::Any visitShowReplicas(MemgraphCypher::ShowReplicasContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return ReplicationQuery*
|
||||
* @return CoordinatorQuery*
|
||||
*/
|
||||
antlrcpp::Any visitCoordinatorQuery(MemgraphCypher::CoordinatorQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return CoordinatorQuery*
|
||||
*/
|
||||
antlrcpp::Any visitRegisterCoordinatorServer(MemgraphCypher::RegisterCoordinatorServerContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return CoordinatorQuery*
|
||||
*/
|
||||
antlrcpp::Any visitRegisterMainCoordinatorServer(MemgraphCypher::RegisterMainCoordinatorServerContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return CoordinatorQuery*
|
||||
*/
|
||||
antlrcpp::Any visitRegisterReplicaCoordinatorServer(
|
||||
MemgraphCypher::RegisterReplicaCoordinatorServerContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return CoordinatorQuery*
|
||||
*/
|
||||
antlrcpp::Any visitShowReplicationCluster(MemgraphCypher::ShowReplicationClusterContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return CoordinatorQuery*
|
||||
*/
|
||||
antlrcpp::Any visitDoFailover(MemgraphCypher::DoFailoverContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return LockPathQuery*
|
||||
*/
|
||||
@@ -686,6 +709,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*>>
|
||||
*/
|
||||
@@ -851,6 +879,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*
|
||||
*/
|
||||
@@ -995,6 +1028,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
*/
|
||||
antlrcpp::Any visitDropDatabase(MemgraphCypher::DropDatabaseContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return MultiDatabaseQuery*
|
||||
*/
|
||||
antlrcpp::Any visitShowDatabase(MemgraphCypher::ShowDatabaseContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return ShowDatabasesQuery*
|
||||
*/
|
||||
@@ -1032,6 +1070,8 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
// return.
|
||||
bool in_with_ = false;
|
||||
|
||||
Parameters *parameters_;
|
||||
|
||||
QueryInfo query_info_;
|
||||
};
|
||||
} // namespace memgraph::query::frontend
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 ) ;
|
||||
|
||||
|
||||
@@ -42,17 +42,18 @@ memgraphCypherKeyword : cypherKeyword
|
||||
| CONSUMER_GROUP
|
||||
| CREATE_DELETE
|
||||
| CREDENTIALS
|
||||
| COORDINATOR
|
||||
| CSV
|
||||
| DATA
|
||||
| DELIMITER
|
||||
| DATABASE
|
||||
| DENY
|
||||
| DROP
|
||||
| DO
|
||||
| DUMP
|
||||
| EDGE
|
||||
| EDGE_TYPES
|
||||
| EXECUTE
|
||||
| FAILOVER
|
||||
| FOR
|
||||
| FOREACH
|
||||
| FREE
|
||||
@@ -152,6 +153,7 @@ query : cypherQuery
|
||||
| multiDatabaseQuery
|
||||
| showDatabases
|
||||
| edgeImportModeQuery
|
||||
| coordinatorQuery
|
||||
;
|
||||
|
||||
cypherQuery : ( indexHints )? singleQuery ( cypherUnion )* ( queryMemoryLimit )? ;
|
||||
@@ -182,8 +184,11 @@ replicationQuery : setReplicationRole
|
||||
| registerReplica
|
||||
| dropReplica
|
||||
| showReplicas
|
||||
| registerMain
|
||||
;
|
||||
|
||||
coordinatorQuery : registerCoordinatorServer
|
||||
| showReplicationCluster
|
||||
| doFailover
|
||||
;
|
||||
|
||||
triggerQuery : createTrigger
|
||||
@@ -247,6 +252,8 @@ transactionQueueQuery : showTransactions
|
||||
|
||||
showTransactions : SHOW TRANSACTIONS ;
|
||||
|
||||
doFailover : DO FAILOVER ;
|
||||
|
||||
terminateTransactions : TERMINATE TRANSACTIONS transactionIdList;
|
||||
|
||||
loadCsv : LOAD CSV FROM csvFile ( WITH | NO ) HEADER
|
||||
@@ -326,6 +333,7 @@ privilege : CREATE
|
||||
| STORAGE_MODE
|
||||
| MULTI_DATABASE_EDIT
|
||||
| MULTI_DATABASE_USE
|
||||
| COORDINATOR
|
||||
;
|
||||
|
||||
granularPrivilege : NOTHING | READ | UPDATE | CREATE_DELETE ;
|
||||
@@ -362,26 +370,32 @@ dumpQuery : DUMP DATABASE ;
|
||||
|
||||
analyzeGraphQuery : ANALYZE GRAPH ( ON LABELS ( listOfColonSymbolicNames | ASTERISK ) ) ? ( DELETE STATISTICS ) ? ;
|
||||
|
||||
setReplicationRole : SET REPLICATION ROLE TO ( MAIN | REPLICA | COORDINATOR )
|
||||
setReplicationRole : SET REPLICATION ROLE TO ( MAIN | REPLICA )
|
||||
( WITH PORT port=literal ) ? ;
|
||||
|
||||
showReplicationRole : SHOW REPLICATION ROLE ;
|
||||
|
||||
replicaName : symbolicName ;
|
||||
showReplicationCluster : SHOW REPLICATION CLUSTER ;
|
||||
|
||||
instanceName : symbolicName ;
|
||||
|
||||
socketAddress : literal ;
|
||||
|
||||
registerReplica : REGISTER REPLICA replicaName ( SYNC | ASYNC )
|
||||
coordinatorSocketAddress : literal ;
|
||||
|
||||
registerReplica : REGISTER REPLICA instanceName ( SYNC | ASYNC )
|
||||
TO socketAddress ;
|
||||
|
||||
registerMain : REGISTER MAIN TO socketAddress ;
|
||||
registerReplicaCoordinatorServer: REGISTER REPLICA instanceName ( ASYNC | SYNC ) TO socketAddress WITH COORDINATOR SERVER ON coordinatorSocketAddress ;
|
||||
|
||||
dropReplica : DROP REPLICA replicaName ;
|
||||
registerMainCoordinatorServer: REGISTER MAIN instanceName WITH COORDINATOR SERVER ON coordinatorSocketAddress ;
|
||||
|
||||
registerCoordinatorServer : registerMainCoordinatorServer | registerReplicaCoordinatorServer ;
|
||||
|
||||
dropReplica : DROP REPLICA instanceName ;
|
||||
|
||||
showReplicas : SHOW REPLICAS ;
|
||||
|
||||
showReplicationCluster : SHOW REPLICATION CLUSTER ;
|
||||
|
||||
lockPathQuery : ( LOCK | UNLOCK ) DATA DIRECTORY | DATA DIRECTORY LOCK STATUS;
|
||||
|
||||
freeMemoryQuery : FREE MEMORY ;
|
||||
@@ -487,6 +501,7 @@ transactionId : literal ;
|
||||
multiDatabaseQuery : createDatabase
|
||||
| useDatabase
|
||||
| dropDatabase
|
||||
| showDatabase
|
||||
;
|
||||
|
||||
createDatabase : CREATE DATABASE databaseName ;
|
||||
@@ -495,6 +510,8 @@ useDatabase : USE DATABASE databaseName ;
|
||||
|
||||
dropDatabase : DROP DATABASE databaseName ;
|
||||
|
||||
showDatabase : SHOW DATABASE ;
|
||||
|
||||
showDatabases : SHOW DATABASES ;
|
||||
|
||||
edgeImportModeQuery : EDGE IMPORT MODE ( ACTIVE | INACTIVE ) ;
|
||||
|
||||
@@ -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 ;
|
||||
@@ -107,6 +111,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 ;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()); }
|
||||
|
||||
@@ -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);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user