Compare commits

...

18 Commits

Author SHA1 Message Date
Boris Tasevski
2303c40992 Added support for additional python decorator timed_read_proc 2021-10-19 13:57:23 +02:00
Antonio Andelic
a647eb7926 Bump version to 2.0.1 2021-10-13 10:10:43 +02:00
Antonio Andelic
e9a937ad6d Update license headers and change date (#270) 2021-10-13 10:06:07 +02:00
Antonio Andelic
6dc2cdfae4 Fix trigger with detach delete (#266) 2021-10-12 16:21:28 +02:00
g-despot
69ea410406 Add CONTRIBUTING and CODE_OF_CONDUCT (#269) 2021-10-12 14:11:07 +02:00
Kostas Kyrimis
24a576c8e9 Make write procedures trigger aware (#262) 2021-10-11 13:39:57 +02:00
Antonio Andelic
d417ffee6e Update community build step (#261) 2021-10-08 14:36:17 +02:00
Antonio Andelic
f868b51483 Update mgconsole version (#260) 2021-10-08 11:19:21 +02:00
g-despot
26dc73c809 Update installation in README (#263) 2021-10-08 10:28:09 +02:00
g-despot
9119c89eba Add updated startup message (#259) 2021-10-07 15:56:06 +02:00
Antonio Andelic
4e7ea34ae9 Add message with link utility (#243) 2021-10-07 14:51:30 +02:00
János Benjamin Antal
d7d291217b Remove debug print (#251) 2021-10-07 09:14:05 +02:00
Marko Budiselić
42facfacbc Add support for Debian 11 (#212) 2021-10-06 16:17:41 +02:00
János Benjamin Antal
bd1e848363 Add issue template (#258) 2021-10-06 11:52:15 +02:00
antonio2368
0ffb58b764 Fix docker packaging and add for platform build (#257) 2021-10-06 10:20:37 +02:00
g-despot
9551b6973a Fix URL in build badge 2021-10-05 14:30:09 +02:00
Jure Bajic
f7ef5f50a4 Update workflow (#255) 2021-10-05 09:03:48 +02:00
Mislav Vuletić
046cd80054 Make readme awesome (#228)
Co-authored-by: antonio2368 <antonio2368@users.noreply.github.com>
Co-authored-by: Ivan Despot <ivan.g.despot@outlook.com>
2021-10-04 16:15:59 +02:00
54 changed files with 1311 additions and 300 deletions

View File

@@ -30,6 +30,14 @@ for file in $modified_files; do
if [ $code -ne 0 ]; then
break
fi
echo "Running header checker..."
$project_folder/tools/header-checker.py $tmpdir/$file
code=$?
if [ $code -ne 0 ]; then
break
fi
done;
return $code

34
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,34 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG] "
labels: bug
assignees: gitbuda, antonio2368
---
**Memgraph version**
Which version did you use?
**Environment**
Some information about the environment you are using Memgraph on: operating
system, how do you connect, with or without docker, which driver etc.
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Run the following query '...'
2. Click on '....'
**Expected behavior**
A clear and concise description of what you expected to happen.
**Logs**
If applicable, add logs of Memgraph, CLI output or screenshots to help explain
your problem.
**Additional context**
Add any other context about the problem here.

View File

@@ -1,7 +1,8 @@
name: Diff
on:
push:
workflow_dispatch:
pull_request:
paths-ignore:
- 'docs/**'
- '**/*.md'
@@ -47,32 +48,6 @@ jobs:
cd build
ctest -R memgraph__unit --output-on-failure -j$THREADS
- name: Run stress test
run: |
cd tests/stress
./continuous_integration
- name: Create community DEB package
run: |
# Activate toolchain.
source /opt/toolchain-v3/activate
cd build
# create mgconsole
# we use the -B to force the build
make -j$THREADS -B mgconsole
# Create community DEB package.
mkdir output && cd output
cpack -G DEB --config ../CPackConfig.cmake
- name: Save community DEB package
uses: actions/upload-artifact@v2
with:
name: "Community DEB package"
path: build/output/memgraph*.deb
code_analysis:
name: "Code analysis"
runs-on: [self-hosted, Linux, X64, Diff]
@@ -257,6 +232,15 @@ jobs:
tests/gql_behave/gql_behave_status.csv
tests/gql_behave/gql_behave_status.html
- name: Run unit tests
run: |
# Activate toolchain.
source /opt/toolchain-v3/activate
# Run unit tests.
cd build
ctest -R memgraph__unit --output-on-failure -j$THREADS
- name: Run e2e tests
run: |
# TODO(gitbuda): Setup mgclient and pymgclient properly.

View File

@@ -19,7 +19,7 @@ jobs:
- name: "Upload package"
uses: actions/upload-artifact@v2
with:
name: centos-7_enterprise
name: centos-7
path: build/output/centos-7/memgraph*.rpm
centos-8:
@@ -36,7 +36,7 @@ jobs:
- name: "Upload package"
uses: actions/upload-artifact@v2
with:
name: centos-8_enterprise
name: centos-8
path: build/output/centos-8/memgraph*.rpm
debian-9:
@@ -53,7 +53,7 @@ jobs:
- name: "Upload package"
uses: actions/upload-artifact@v2
with:
name: debian-9_enterprise
name: debian-9
path: build/output/debian-9/memgraph*.deb
debian-10:
@@ -70,9 +70,26 @@ jobs:
- name: "Upload package"
uses: actions/upload-artifact@v2
with:
name: debian-10_enterprise
name: debian-10
path: build/output/debian-10/memgraph*.deb
debian-11:
runs-on: [self-hosted, DockerMgBuild]
timeout-minutes: 60
steps:
- name: "Set up repository"
uses: actions/checkout@v2
with:
fetch-depth: 0 # Required because of release/get_version.py
- name: "Build package"
run: |
./release/package/run.sh package debian-11
- name: "Upload package"
uses: actions/upload-artifact@v2
with:
name: debian-11
path: build/output/debian-11/memgraph*.deb
docker:
runs-on: [self-hosted, DockerMgBuild]
timeout-minutes: 60
@@ -89,7 +106,7 @@ jobs:
- name: "Upload package"
uses: actions/upload-artifact@v2
with:
name: docker_enterprise
name: docker
path: build/output/docker/memgraph*.tar.gz
ubuntu-1804:
@@ -106,7 +123,7 @@ jobs:
- name: "Upload package"
uses: actions/upload-artifact@v2
with:
name: ubuntu-1804_enterprise
name: ubuntu-1804
path: build/output/ubuntu-18.04/memgraph*.deb
ubuntu-2004:
@@ -123,5 +140,22 @@ jobs:
- name: "Upload package"
uses: actions/upload-artifact@v2
with:
name: ubuntu-2004_enterprise
name: ubuntu-2004
path: build/output/ubuntu-20.04/memgraph*.deb
debian-10-platform:
runs-on: [self-hosted, DockerMgBuild]
timeout-minutes: 60
steps:
- name: "Set up repository"
uses: actions/checkout@v2
with:
fetch-depth: 0 # Required because of release/get_version.py
- name: "Build package"
run: |
./release/package/run.sh package debian-10 --for-platform
- name: "Upload package"
uses: actions/upload-artifact@v2
with:
name: debian-10-platform
path: build/output/debian-10/memgraph*.deb

View File

@@ -36,28 +36,6 @@ jobs:
cmake -DCMAKE_BUILD_TYPE=release -DMG_ENTERPRISE=OFF ..
make -j$THREADS
- name: Create community RPM package
run: |
# Activate toolchain.
source /opt/toolchain-v3/activate
cd build
# create mgconsole
# we use the -B to force the build
make -j$THREADS -B mgconsole
# Create community RPM package.
mkdir output && cd output
cpack -G RPM --config ../CPackConfig.cmake
rpmlint memgraph*.rpm
- name: Save community RPM package
uses: actions/upload-artifact@v2
with:
name: "Community RPM package"
path: build/output/memgraph*.rpm
- name: Run unit tests
run: |
# Activate toolchain.
@@ -67,16 +45,6 @@ jobs:
cd build
ctest -R memgraph__unit --output-on-failure
- name: Run stress test (plain)
run: |
cd tests/stress
./continuous_integration
- name: Run stress test (large)
run: |
cd tests/stress
./continuous_integration --large-dataset
coverage_build:
name: "Coverage build"
runs-on: [self-hosted, Linux, X64, CentOS8]
@@ -301,6 +269,15 @@ jobs:
tests/gql_behave/gql_behave_status.csv
tests/gql_behave/gql_behave_status.html
- name: Run unit tests
run: |
# Activate toolchain.
source /opt/toolchain-v3/activate
# Run unit tests.
cd build
ctest -R memgraph__unit --output-on-failure
- name: Run e2e tests
run: |
# TODO(gitbuda): Setup mgclient and pymgclient properly.

View File

@@ -36,27 +36,6 @@ jobs:
cmake -DCMAKE_BUILD_TYPE=release -DMG_ENTERPRISE=OFF ..
make -j$THREADS
- name: Create community DEB package
run: |
# Activate toolchain.
source /opt/toolchain-v3/activate
cd build
# create mgconsole
# we use the -B to force the build
make -j$THREADS -B mgconsole
# Create community DEB package.
mkdir output && cd output
cpack -G DEB --config ../CPackConfig.cmake
- name: Save community DEB package
uses: actions/upload-artifact@v2
with:
name: "Community DEB package"
path: build/output/memgraph*.deb
- name: Run unit tests
run: |
# Activate toolchain.
@@ -66,16 +45,6 @@ jobs:
cd build
ctest -R memgraph__unit --output-on-failure
- name: Run stress test (plain)
run: |
cd tests/stress
./continuous_integration
- name: Run stress test (large)
run: |
cd tests/stress
./continuous_integration --large-dataset
coverage_build:
name: "Coverage build"
runs-on: [self-hosted, Linux, X64, Debian10]
@@ -299,6 +268,15 @@ jobs:
tests/gql_behave/gql_behave_status.csv
tests/gql_behave/gql_behave_status.html
- name: Run unit tests
run: |
# Activate toolchain.
source /opt/toolchain-v3/activate
# Run unit tests.
cd build
ctest -R memgraph__unit --output-on-failure
- name: Run e2e tests
run: |
# TODO(gitbuda): Setup mgclient and pymgclient properly.

View File

@@ -36,27 +36,6 @@ jobs:
cmake -DCMAKE_BUILD_TYPE=release -DMG_ENTERPRISE=OFF ..
make -j$THREADS
- name: Create community DEB package
run: |
# Activate toolchain.
source /opt/toolchain-v3/activate
cd build
# create mgconsole
# we use the -B to force the build
make -j$THREADS -B mgconsole
# Create community DEB package.
mkdir output && cd output
cpack -G DEB --config ../CPackConfig.cmake
- name: Save community DEB package
uses: actions/upload-artifact@v2
with:
name: "Community DEB package"
path: build/output/memgraph*.deb
- name: Run unit tests
run: |
# Activate toolchain.
@@ -66,16 +45,6 @@ jobs:
cd build
ctest -R memgraph__unit --output-on-failure
- name: Run stress test (plain)
run: |
cd tests/stress
./continuous_integration
- name: Run stress test (large)
run: |
cd tests/stress
./continuous_integration --large-dataset
coverage_build:
name: "Coverage build"
runs-on: [self-hosted, Linux, X64, Ubuntu20.04]
@@ -299,6 +268,15 @@ jobs:
tests/gql_behave/gql_behave_status.csv
tests/gql_behave/gql_behave_status.html
- name: Run unit tests
run: |
# Activate toolchain.
source /opt/toolchain-v3/activate
# Run unit tests.
cd build
ctest -R memgraph__unit --output-on-failure
- name: Run e2e tests
run: |
# TODO(gitbuda): Setup mgclient and pymgclient properly.

View File

@@ -35,9 +35,6 @@ else()
message(FATAL_ERROR "Couldn't find clang and/or clang++!")
endif()
option(BUILD_FOR_DOCKER "Build Memgraph binary for docker." OFF)
message(STATUS "BUILD_FOR_DOCKER: ${BUILD_FOR_DOCKER}")
# -----------------------------------------------------------------------------
project(memgraph)
@@ -57,7 +54,7 @@ option(MG_ENTERPRISE "Build Memgraph Enterprise Edition" ON)
# Set the current version here to override the automatic version detection. The
# version must be specified as `X.Y.Z`. Primarily used when building new patch
# versions.
set(MEMGRAPH_OVERRIDE_VERSION "")
set(MEMGRAPH_OVERRIDE_VERSION "2.0.1")
# Custom suffix that this version should have. The suffix can be any arbitrary
# string. Primarily used when building a version for a specific customer.

127
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,127 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of
any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing other's private information, such as a physical or email address,
without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[contact@memgraph.com](contact@memgraph.com). All complaints will be reviewed
and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the Contributor Covenant, version 2.1,
available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder][mozilla coc].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq).
Translations are available at
[https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations).

121
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,121 @@
# How to contribute?
This is a general purpose guide for contributing to Memgraph. We're still
working out the kinks to make contributing to this project as easy and
transparent as possible, but we're not quite there yet. Hopefully, this document
makes the process for contributing clear and answers some questions that you may
have.
- [How to contribute?](#how-to-contribute)
- [Open development](#open-development)
- [Branch organization](#branch-organization)
- [Bugs & changes](#bugs--changes)
- [Where to find known issues?](#where-to-find-known-issues)
- [Proposing a change](#proposing-a-change)
- [Your first pull request](#your-first-pull-request)
- [Sending a pull request](#sending-a-pull-request)
- [Style guide](#style-guide)
- [How to get in touch?](#how-to-get-in-touch)
- [Code of Conduct](#code-of-conduct)
- [License](#license)
- [Attribution](#attribution)
## Open development
All work on Memgraph is done via [GitHub](https://github.com/memgraph/memgraph).
Both core team members and external contributors send pull requests which go
through the same review process.
## Branch organization
Most pull requests should target the [`master
branch`](https://github.com/memgraph/memgraph/tree/master). We only use separate
branches for developing new features and fixing bugs before they are merged with
`master`. We do our best to keep `master` in good shape, with all tests passing.
Code that lands in `master` must be compatible with the latest stable release.
It may contain additional features but no breaking changes if it's not
absolutely necessary. We should be able to release a new minor version from the
tip of `master` at any time.
## Bugs & changes
### Where to find known issues?
We are using [GitHub Issues](https://github.com/memgraph/memgraph/issues) for
our public bugs. We keep a close eye on this and try to make it clear when we
have an internal fix in progress. Before filing a new task, try to make sure
your problem doesn't already exist.
### Proposing a change
If you intend to change the public API, or make any non-trivial changes to the
implementation, we recommend [filing an
issue](https://github.com/memgraph/memgraph/issues/new). This lets us reach an
agreement on your proposal before you put significant effort into it.
If you're only fixing a bug, it's fine to submit a pull request right away but
we still recommend to file an issue detailing what you're fixing. This is
helpful in case we don't accept that specific fix but want to keep track of the
issue.
### Your first pull request
Working on your first Pull Request? You can learn how from this free video
series:
**[How to Contribute to an Open Source Project on
GitHub](https://app.egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github)**
If you decide to fix an issue, please be sure to check the comment thread in
case somebody is already working on a fix. If nobody is working on it at the
moment, please leave a comment stating that you intend to work on it so other
people don't accidentally duplicate your effort.
If somebody claims an issue but doesn't follow up for more than two weeks, it's
fine to take it over but you should still leave a comment.
### Sending a pull request
The core team is monitoring for pull requests. We will review your pull request
and either merge it, request changes to it, or close it with an explanation.
**Before submitting a pull request,** please make sure the following is done:
1. Fork [the repository](https://github.com/memgraph/memgraph) and create your
branch from `master`.
2. If you've fixed a bug or added code that should be tested, add tests!
3. Use the formatter `clang-format` for C/C++ code and `flake8` for Python code.
`clang-format` will automatically detect the `.clang-format` file in the root
directory while `flake8` can be used with the default configuration.
### Style guide
Memgraph uses the [Google Style
Guide](https://google.github.io/styleguide/cppguide.html) for C++ in most of its
code. You should follow them whenever writing new code.
## How to get in touch?
Aside from communicating directly via Pull Requests and Issues, the Memgraph
Community [Discord Server](https://discord.gg/memgraph) is the best place for
conversing with project maintainers and other community members.
## [Code of Conduct](https://github.com/memgraph/memgraph/blob/master/CODE_OF_CONDUCT.md)
Memgraph has adopted the [Contributor
Covenant](https://www.contributor-covenant.org/) as its Code of Conduct, and we
expect project participants to adhere to it. Please read [the full
text](https://github.com/memgraph/memgraph/blob/master/CODE_OF_CONDUCT.md) so
that you can understand what actions will and will not be tolerated.
## License
By contributing to Memgraph, you agree that your contributions will be licensed
under the [Memgraph licensing
scheme](https://github.com/memgraph/memgraph/blob/master/LICENSE).
## Attribution
This Contributing guide is adapted from the **React.js Contributing guide**
available at
[https://reactjs.org/docs/how-to-contribute.html](https://reactjs.org/docs/how-to-contribute.html).

135
README.md
View File

@@ -1,21 +1,128 @@
# Memgraph
<p align="center">
<img width="400px" src="https://uploads-ssl.webflow.com/5e7ceb09657a69bdab054b3a/5e7ceb09657a6937ab054bba_Black_Original%20_Logo.png">
</p>
Memgraph is an ACID compliant high performance transactional distributed
in-memory graph database featuring runtime native query compiling, lock free
data structures, multi-version concurrency control and asynchronous IO.
---
## Development Documentation
<p align="center">
Build modern, graph-based applications on top of your streaming data in minutes.
</p>
Please continue in
[Notion](https://www.notion.so/memgraph/memgraph-0428591638604c8385550e214ea9f3e6).
<p align="center">
<a href="https://github.com/memgraph/memgraph/blob/master/licenses/APL.txt">
<img src="https://img.shields.io/badge/license-APL-green" alt="license" title="license"/>
</a>
<a href="https://github.com/memgraph/memgraph/blob/master/licenses/BSL.txt">
<img src="https://img.shields.io/badge/license-BSL-yellowgreen" alt="license" title="license"/>
</a>
<a href="https://github.com/memgraph/memgraph/blob/master/licenses/MEL.txt" alt="Documentation">
<img src="https://img.shields.io/badge/license-MEL-yellow" alt="license" title="license"/>
</a>
</p>
## User Documentation
<p align="center">
<a href="https://github.com/memgraph/memgraph">
<img src="https://img.shields.io/github/workflow/status/memgraph/memgraph/Release%20Ubuntu%2020.04/master" alt="build" title="build"/>
</a>
<a href="https://memgraph.com/docs/" alt="Documentation">
<img src="https://img.shields.io/badge/documentation-Memgraph-orange" />
</a>
</p>
Memgraph user documentation is maintained within
[docs](https://github.com/memgraph/docs) repository. The documentation is also
available on [GitBook](https://docs.memgraph.com).
<p align="center">
<a href="https://memgr.ph/join-discord">
<img src="https://img.shields.io/badge/Discord-7289DA?style=for-the-badge&logo=discord&logoColor=white" alt="Discord"/>
</a>
</p>
## Licences
## :clipboard: Description
* [Community](release/LICENSE_COMMUNITY.md)
* [Enterprise](release/LICENSE_ENTERPRISE.md)
Memgraph is a streaming graph application platform that helps you wrangle your
streaming data, build sophisticated models that you can query in real-time, and
develop graph applications.
Memgraph directly connects to your streaming infrastructure. You can ingest data
from sources like Kafka, SQL, or plain CSV files. Memgraph provides a standard
interface to query your data with Cypher, a widely-used and declarative query
language that is easy to write, understand and optimize for performance. This is
achieved by using the property graph data model, which stores data in terms of
objects, their attributes, and the relationships that connect them. This is a
natural and effective way to model many real-world problems without relying on
complex SQL schemas.
Memgraph is implemented in C/C++ and leverages an in-memory first architecture
to ensure that youre getting the best possible performance consistently and
without surprises. Its also ACID-compliant and highly available.
## :floppy_disk: Download & Install
### Windows
[![Windows](https://img.shields.io/badge/Windows-Docker-0078D6?style=for-the-badge&logo=windows&logoColor=white)](https://memgraph.com/docs/memgraph/install-memgraph-on-windows-docker)
[![Windows](https://img.shields.io/badge/Windows-WSL-0078D6?style=for-the-badge&logo=windows&logoColor=white)](https://memgraph.com/docs/memgraph/install-memgraph-on-windows-wsl)
### macOS
[![macOS](https://img.shields.io/badge/macOS-Docker-000000?style=for-the-badge&logo=macos&logoColor=F0F0F0)](https://memgraph.com/docs/memgraph/install-memgraph-on-macos-docker)
### Linux
[![Linux](https://img.shields.io/badge/Linux-Docker-FCC624?style=for-the-badge&logo=linux&logoColor=black)](https://memgraph.com/docs/memgraph/install-memgraph-on-linux-docker)
[![Debian](https://img.shields.io/badge/Debian-D70A53?style=for-the-badge&logo=debian&logoColor=white)](https://memgraph.com/docs/memgraph/install-memgraph-on-debian)
[![Ubuntu](https://img.shields.io/badge/Ubuntu-E95420?style=for-the-badge&logo=ubuntu&logoColor=white)](https://memgraph.com/docs/memgraph/install-memgraph-on-ubuntu)
[![Cent
OS](https://img.shields.io/badge/cent%20os-002260?style=for-the-badge&logo=centos&logoColor=F0F0F0)](https://memgraph.com/docs/memgraph/install-memgraph-from-rpm)
You can find the binaries and Docker images on the [Download
Hub](https://memgraph.com/download) and the installation instructions in the
[official documentation](https://memgraph.com/docs/memgraph/installation).
## :zap: Features
- Run Python, Rust, and C/C++ code natively, check out the
[MAGE](https://github.com/memgraph/mage) graph algorithm library
- Native support for machine learning
- Streaming support
- Replication
- Authentication and authorization
- ACID compliance
## :bookmark_tabs: Documentation
The Memgraph documentation is available at
[memgraph.com/docs](https://memgraph.com/docs).
## :question: Configuration
Command line options that Memgraph accepts are available in the [reference
guide](https://memgraph.com/docs/memgraph/reference-guide/configuration).
## :trophy: Contributing
The main purpose of this repository is to continue evolving Memgraph, making it
faster and easier to use. Development of Memgraph happens in the open on GitHub,
and we are grateful to the community for contributing bug fixes and
improvements. Read below to learn how you can take part in improving Memgraph.
### Code of Conduct
Memgraph has adopted a Code of Conduct that we expect project participants to
adhere to. Please read [the full text](CODE_OF_CONDUCT.md) so that you can
understand what actions will and will not be tolerated.
### Contributing Guide
Read our [contributing guide](CONTRIBUTING.md) to learn about our development
process and how to propose bug fixes and improvements.
### :scroll: License
Memgraph Community is available under the [BSL
license](./licenses/BSL.txt).</br> Memgraph Enterprise is available under the
[MEL license](./licenses/MEL.txt).
<p align="center">
<a href="#">
<img src="https://img.shields.io/badge/⬆back_to_top_⬆-white" alt="Back to top" title="Back to top"/>
</a>
</p>

95
environment/os/debian-11.sh Executable file
View File

@@ -0,0 +1,95 @@
#!/bin/bash
set -Eeuo pipefail
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
source "$DIR/../util.sh"
TOOLCHAIN_BUILD_DEPS=(
coreutils gcc g++ build-essential make # generic build tools
wget # used for archive download
gnupg # used for archive signature verification
tar gzip bzip2 xz-utils unzip # used for archive unpacking
zlib1g-dev # zlib library used for all builds
libexpat1-dev libipt-dev libbabeltrace-dev liblzma-dev python3-dev texinfo # for gdb
libcurl4-openssl-dev # for cmake
libreadline-dev # for cmake and llvm
libffi-dev libxml2-dev # for llvm
libedit-dev libpcre3-dev automake bison # for swig
)
TOOLCHAIN_RUN_DEPS=(
make # generic build tools
tar gzip bzip2 xz-utils # used for archive unpacking
zlib1g # zlib library used for all builds
libexpat1 libipt2 libbabeltrace1 liblzma5 python3 # for gdb
libcurl4 # for cmake
libreadline8 # for cmake and llvm
libffi7 libxml2 # for llvm
)
MEMGRAPH_BUILD_DEPS=(
git # source code control
make pkg-config # build system
curl wget # for downloading libs
uuid-dev default-jre-headless # required by antlr
libreadline-dev # for memgraph console
libpython3-dev python3-dev # for query modules
libssl-dev
libseccomp-dev
netcat # tests are using nc to wait for memgraph
python3 virtualenv python3-virtualenv python3-pip # for qa, macro_benchmark and stress tests
python3-yaml # for the configuration generator
libcurl4-openssl-dev # mg-requests
sbcl # for custom Lisp C++ preprocessing
doxygen graphviz # source documentation generators
mono-runtime mono-mcs zip unzip default-jdk-headless # for driver tests
dotnet-sdk-3.1 golang nodejs npm
autoconf # for jemalloc code generation
)
list() {
echo "$1"
}
check() {
check_all_dpkg "$1"
}
install() {
cat >/etc/apt/sources.list <<EOF
deb http://deb.debian.org/debian bullseye main
deb-src http://deb.debian.org/debian bullseye main
deb http://deb.debian.org/debian-security/ bullseye-security main
deb-src http://deb.debian.org/debian-security/ bullseye-security main
deb http://deb.debian.org/debian bullseye-updates main
deb-src http://deb.debian.org/debian bullseye-updates main
EOF
cd "$DIR"
apt update
# If GitHub Actions runner is installed, append LANG to the environment.
# Python related tests doesn't work the LANG export.
if [ -d "/home/gh/actions-runner" ]; then
echo "LANG=en_US.utf8" >> /home/gh/actions-runner/.env
else
echo "NOTE: export LANG=en_US.utf8"
fi
apt install -y wget
for pkg in $1; do
if [ "$pkg" == dotnet-sdk-3.1 ]; then
if ! dpkg -s "$pkg" 2>/dev/null >/dev/null; then
wget -nv https://packages.microsoft.com/config/debian/10/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
dpkg -i packages-microsoft-prod.deb
apt-get update
apt-get install -y apt-transport-https dotnet-sdk-3.1
fi
continue
fi
apt install -y "$pkg"
done
}
deps=$2"[*]"
"$1" "${!deps}"

View File

@@ -1,7 +1,8 @@
# Copyright 2021 Memgraph Ltd.
#
# Use of this software is governed by the Business Source 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.
# 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
@@ -1117,7 +1118,7 @@ def raise_if_does_not_meet_requirements(func: typing.Callable[..., Record]):
def _register_proc(func: typing.Callable[..., Record],
is_write: bool):
is_write: bool, is_timed: bool):
raise_if_does_not_meet_requirements(func)
register_func = (
_mgp.Module.add_write_procedure if is_write
@@ -1127,13 +1128,23 @@ def _register_proc(func: typing.Callable[..., Record],
if params and params[0].annotation is ProcCtx:
@wraps(func)
def wrapper(graph, args):
return func(ProcCtx(graph), *args)
start = datetime.datetime.now()
result = func(ProcCtx(graph), *args)
if is_timed:
delta = datetime.datetime.now() - start
result.fields["duration"] = delta.total_seconds() * 10**6 + delta.microseconds % 10**6
return result
params = params[1:]
mgp_proc = register_func(_mgp._MODULE, wrapper)
else:
@wraps(func)
def wrapper(graph, args):
return func(*args)
start = datetime.datetime.now()
result = func(*args)
if is_timed:
delta = datetime.datetime.now() - start
result.fields["duration"] = delta.total_seconds() * 10**6 + delta.microseconds % 10**6
return result
mgp_proc = register_func(_mgp._MODULE, wrapper)
for param in params:
name = param.name
@@ -1156,10 +1167,17 @@ def _register_proc(func: typing.Callable[..., Record],
mgp_proc.add_deprecated_result(name, cypher_type)
else:
mgp_proc.add_result(name, _typing_to_cypher_type(type_))
if is_timed:
mgp_proc.add_result("duration", _typing_to_cypher_type(int))
return func
def read_proc(func: typing.Callable[..., Record]):
_read_proc(func, False)
def timed_read_proc(func: typing.Callable[..., Record]):
_read_proc(func, True)
def _read_proc(func: typing.Callable[..., Record], is_timed: bool):
"""
Register `func` as a read-only procedure of the current module.
@@ -1199,7 +1217,7 @@ def read_proc(func: typing.Callable[..., Record]):
CALL example.procedure(1) YIELD args, result;
Naturally, you may pass in different arguments or yield less fields.
"""
return _register_proc(func, False)
return _register_proc(func, False, is_timed)
def write_proc(func: typing.Callable[..., Record]):
@@ -1249,7 +1267,7 @@ def write_proc(func: typing.Callable[..., Record]):
CALL example.procedure("single argument") YIELD result;
Naturally, you may pass in different arguments.
"""
return _register_proc(func, True)
return _register_proc(func, True, False)
class InvalidMessageError(Exception):

View File

@@ -221,7 +221,7 @@ pymgclient_tag="4f85c179e56302d46a1e3e2cf43509db65f062b3" # (2021-01-15)
repo_clone_try_double "${primary_urls[pymgclient]}" "${secondary_urls[pymgclient]}" "pymgclient" "$pymgclient_tag"
# mgconsole
mgconsole_tag="01ae99bfce772e540e75c076ba03cf06c0c2ac7d" # (2021-05-26)
mgconsole_tag="v1.1.0" # (2021-10-07)
repo_clone_try_double "${primary_urls[mgconsole]}" "${secondary_urls[mgconsole]}" "mgconsole" "$mgconsole_tag"
spdlog_tag="46d418164dd4cd9822cf8ca62a116a3f71569241" # (2020-12-01)

View File

@@ -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: 2025-10-05
CHANGE DATE: 2025-10-12
CHANGE LICENSE: Apache License, Version 2.0
For information about alternative licensing arrangements, please visit: https://memgraph.com/legal.

View File

@@ -56,7 +56,10 @@ def procedure(context: mgp.ProcCtx,
@mgp.write_proc
def write_procedure(context: mgp.ProcCtx, property_name: str, property_value: mgp.Nullable[mgp.Any]) -> mgp.Record(created_vertex=mgp.Vertex):
def write_procedure(context: mgp.ProcCtx,
property_name: str,
property_value: mgp.Nullable[mgp.Any]
) -> mgp.Record(created_vertex=mgp.Vertex):
"""
This example procedure creates a new vertex with the specified property
and connects it to all existing vertex which has the same property with
@@ -86,7 +89,6 @@ def write_procedure(context: mgp.ProcCtx, property_name: str, property_value: mg
vertex.properties.set(property_name, property_value)
# Connect the new vertex to the other vertices
for v in vertices_to_connect:
print("ALMA")
context.graph.create_edge(vertex, v, mgp.EdgeType("HAS_SAME_VALUE"))
return mgp.Record(created_vertex=vertex)

View File

@@ -35,8 +35,7 @@ cd ${working_dir}
# Extract version and offering from deb name.
deb_name=`echo $(basename "$deb_path") | sed 's/.deb$//'`
version=`echo ${deb_name} | cut -d '_' -f 2 | rev | cut -d '-' -f 2- | rev | tr '+~' '__'`
offering=`echo ${version} | cut -d '-' -f 2`
dockerfile_path="${working_dir}/memgraph_${offering}.dockerfile"
dockerfile_path="${working_dir}/memgraph.dockerfile"
image_name="memgraph:${version}"
package_name="memgraph-${version}-docker.tar.gz"

View File

@@ -0,0 +1,17 @@
FROM debian:11
ARG TOOLCHAIN_VERSION
# Stops tzdata interactive configuration.
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update && apt install -y \
ca-certificates wget git
# Do NOT be smart here and clean the cache because the container is used in the
# stateful context.
RUN wget -q https://s3-eu-west-1.amazonaws.com/deps.memgraph.io/${TOOLCHAIN_VERSION}/${TOOLCHAIN_VERSION}-binaries-debian-11.tar.gz \
-O ${TOOLCHAIN_VERSION}-binaries-debian-11.tar.gz \
&& tar xzvf ${TOOLCHAIN_VERSION}-binaries-debian-11.tar.gz -C /opt
ENTRYPOINT ["sleep", "infinity"]

View File

@@ -16,6 +16,10 @@ services:
build:
context: debian-10
container_name: "mgbuild_debian-10"
mgbuild_debian-11:
build:
context: debian-11
container_name: "mgbuild_debian-11"
mgbuild_ubuntu-18.04:
build:
context: ubuntu-18.04

View File

@@ -3,14 +3,14 @@
set -Eeuo pipefail
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
SUPPORTED_OS=(centos-7 centos-8 debian-9 debian-10 ubuntu-18.04 ubuntu-20.04)
SUPPORTED_OS=(centos-7 centos-8 debian-9 debian-10 debian-11 ubuntu-18.04 ubuntu-20.04)
PROJECT_ROOT="$SCRIPT_DIR/../.."
TOOLCHAIN_VERSION="toolchain-v3"
ACTIVATE_TOOLCHAIN="source /opt/${TOOLCHAIN_VERSION}/activate"
HOST_OUTPUT_DIR="$PROJECT_ROOT/build/output"
print_help () {
echo "$0 init|package {os} [--for-docker]|docker|test"
echo "$0 init|package {os} [--for-docker|--for-platform]|docker|test"
echo ""
echo " OSs: ${SUPPORTED_OS[*]}"
exit 1
@@ -28,10 +28,15 @@ make_package () {
if [[ "$os" =~ ^"ubuntu".* ]]; then
package_command=" cpack -G DEB --config ../CPackConfig.cmake "
fi
docker_flag=" -DBUILD_FOR_DOCKER=OFF "
if [[ "$#" -gt 2 ]]; then
if [[ "$3" == "--for-docker" ]]; then
docker_flag=" -DBUILD_FOR_DOCKER=ON "
telemetry_id_override_flag=""
if [[ "$#" -gt 1 ]]; then
if [[ "$2" == "--for-docker" ]]; then
telemetry_id_override_flag=" -DMG_TELEMETRY_ID_OVERRIDE=DOCKER "
elif [[ "$2" == "--for-platform" ]]; then
telemetry_id_override_flag=" -DMG_TELEMETRY_ID_OVERRIDE=DOCKER-PLATFORM"
else
print_help
exit
fi
fi
build_container="mgbuild_$os"
@@ -64,7 +69,7 @@ make_package () {
echo "Building targeted package..."
docker exec "$build_container" bash -c "cd /memgraph && ./init"
docker exec "$build_container" bash -c "cd $container_build_dir && rm -rf ./*"
docker exec "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && cmake -DCMAKE_BUILD_TYPE=release $docker_flag .."
docker exec "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && cmake -DCMAKE_BUILD_TYPE=release $telemetry_id_override_flag .."
# ' is used instead of " because we need to run make within the allowed
# container resources.
# shellcheck disable=SC2016

View File

@@ -19,6 +19,7 @@
#include "utils/flag_validation.hpp"
#include "utils/license.hpp"
#include "utils/logging.hpp"
#include "utils/message.hpp"
#include "utils/settings.hpp"
#include "utils/string.hpp"
@@ -102,17 +103,15 @@ std::optional<User> Auth::Authenticate(const std::string &username, const std::s
if (FLAGS_auth_module_create_missing_user) {
user = AddUser(username, password);
if (!user) {
spdlog::warn(
"Couldn't authenticate user '{}' using the auth module because "
"the user already exists as a role!",
username);
spdlog::warn(utils::MessageWithLink(
"Couldn't create the missing user '{}' using the auth module because the user already exists as a role.",
username, "https://memgr.ph/auth"));
return std::nullopt;
}
} else {
spdlog::warn(
"Couldn't authenticate user '{}' using the auth module because the "
"user doesn't exist!",
username);
spdlog::warn(utils::MessageWithLink(
"Couldn't authenticate user '{}' using the auth module because the user doesn't exist.", username,
"https://memgr.ph/auth"));
return std::nullopt;
}
} else {
@@ -126,17 +125,16 @@ std::optional<User> Auth::Authenticate(const std::string &username, const std::s
role = AddRole(rolename);
if (!role) {
spdlog::warn(
"Couldn't authenticate user '{}' using the auth module "
"because the user's role '{}' already exists as a user!",
username, rolename);
utils::MessageWithLink("Couldn't authenticate user '{}' using the auth module because the user's "
"role '{}' already exists as a user.",
username, rolename, "https://memgr.ph/auth"));
return std::nullopt;
}
SaveRole(*role);
} else {
spdlog::warn(
"Couldn't authenticate user '{}' using the auth module because "
"the user's role '{}' doesn't exist!",
username, rolename);
spdlog::warn(utils::MessageWithLink(
"Couldn't authenticate user '{}' using the auth module because the user's role '{}' doesn't exist.",
username, rolename, "https://memgr.ph/auth"));
return std::nullopt;
}
}
@@ -150,11 +148,13 @@ std::optional<User> Auth::Authenticate(const std::string &username, const std::s
} else {
auto user = GetUser(username);
if (!user) {
spdlog::warn("Couldn't authenticate user '{}' because the user doesn't exist", username);
spdlog::warn(utils::MessageWithLink("Couldn't authenticate user '{}' because the user doesn't exist.", username,
"https://memgr.ph/auth"));
return std::nullopt;
}
if (!user->CheckPassword(password)) {
spdlog::warn("Couldn't authenticate user '{}'", username);
spdlog::warn(utils::MessageWithLink("Couldn't authenticate user '{}' because the password is not correct.",
username, "https://memgr.ph/auth"));
return std::nullopt;
}
return user;

View File

@@ -23,6 +23,7 @@
#include "communication/exceptions.hpp"
#include "utils/likely.hpp"
#include "utils/logging.hpp"
#include "utils/message.hpp"
namespace communication::bolt {
@@ -64,7 +65,8 @@ inline std::pair<std::string, std::string> ExceptionToErrorMessage(const std::ex
// All exceptions used in memgraph are derived from BasicException. Since
// we caught some other exception we don't know what is going on. Return
// DatabaseError, log real message and return generic string.
spdlog::error("Unknown exception occurred during query execution {}", e.what());
spdlog::error(
utils::MessageWithLink("Unknown exception occurred during query execution {}.", e.what(), "https://memgr.ph/unknown"));
return {"Memgraph.DatabaseError.MemgraphError.MemgraphError",
"An unknown exception occurred, this is unexpected. Real message "
"should be in database logs."};

View File

@@ -24,6 +24,7 @@
#include "communication/listener.hpp"
#include "io/network/socket.hpp"
#include "utils/logging.hpp"
#include "utils/message.hpp"
#include "utils/thread.hpp"
namespace communication {
@@ -88,13 +89,13 @@ class Server final {
alive_.store(true);
if (!socket_.Bind(endpoint_)) {
spdlog::error("Cannot bind to socket on {}", endpoint_);
spdlog::error(utils::MessageWithLink("Cannot bind to socket on endpoint {}.", endpoint_, "https://memgr.ph/socket"));
alive_.store(false);
return false;
}
socket_.SetTimeout(1, 0);
if (!socket_.Listen(1024)) {
spdlog::error("Cannot listen on socket {}", endpoint_);
spdlog::error(utils::MessageWithLink("Cannot listen on socket {}", endpoint_, "https://memgr.ph/socket"));
alive_.store(false);
return false;
}

View File

@@ -29,6 +29,7 @@
#include "io/network/socket.hpp"
#include "io/network/stream_buffer.hpp"
#include "utils/logging.hpp"
#include "utils/message.hpp"
#include "utils/on_scope_exit.hpp"
#include "utils/spin_lock.hpp"
@@ -190,10 +191,10 @@ class Session final {
throw utils::BasicException(SslGetLastError());
} else {
// This is a fatal error.
spdlog::error(
"An unknown error occured while processing SSL message."
" Please make sure that you have SSL properly configured on "
"the server and the client.");
spdlog::error(utils::MessageWithLink(
"An unknown error occurred while processing SSL messages. "
"Please make sure that you have SSL properly configured on the server and the client.",
"https://memgr.ph/ssl"));
throw utils::BasicException(SslGetLastError());
}
} else if (len == 0) {

View File

@@ -18,6 +18,7 @@
#include "io/network/endpoint.hpp"
#include "io/network/network_error.hpp"
#include "utils/logging.hpp"
#include "utils/message.hpp"
#include "utils/string.hpp"
namespace io::network {
@@ -65,15 +66,17 @@ std::optional<std::pair<std::string, uint16_t>> Endpoint::ParseSocketOrIpAddress
try {
int_port = utils::ParseInt(parts[1]);
} catch (utils::BasicException &e) {
spdlog::error("Invalid port number: {}", parts[1]);
spdlog::error(utils::MessageWithLink("Invalid port number {}.", parts[1], "https://memgr.ph/ports"));
return std::nullopt;
}
if (int_port < 0) {
spdlog::error("Port number must be a positive integer!");
spdlog::error(utils::MessageWithLink("Invalid port number {}. The port number must be a positive integer.",
int_port, "https://memgr.ph/ports"));
return std::nullopt;
}
if (int_port > std::numeric_limits<uint16_t>::max()) {
spdlog::error("Port number exceeded maximum possible size!");
spdlog::error(utils::MessageWithLink("Invalid port number. The port number exceedes the maximum possible size.",
"https://memgr.ph/ports"));
return std::nullopt;
}

View File

@@ -53,6 +53,7 @@
#include "utils/license.hpp"
#include "utils/logging.hpp"
#include "utils/memory_tracker.hpp"
#include "utils/message.hpp"
#include "utils/readable_size.hpp"
#include "utils/rw_lock.hpp"
#include "utils/settings.hpp"
@@ -998,13 +999,16 @@ int main(int argc, char **argv) {
auto gil = py::EnsureGIL();
auto maybe_exc = py::AppendToSysPath(py_support_dir.c_str());
if (maybe_exc) {
spdlog::error("Unable to load support for embedded Python: {}", *maybe_exc);
spdlog::error(
utils::MessageWithLink("Unable to load support for embedded Python: {}.", *maybe_exc, "https://memgr.ph/python"));
}
} else {
spdlog::error("Unable to load support for embedded Python: missing directory {}", py_support_dir);
spdlog::error(utils::MessageWithLink("Unable to load support for embedded Python: missing directory {}.",
py_support_dir, "https://memgr.ph/python"));
}
} catch (const std::filesystem::filesystem_error &e) {
spdlog::error("Unable to load support for embedded Python: {}", e.what());
spdlog::error(
utils::MessageWithLink("Unable to load support for embedded Python: {}.", e.what(), "https://memgr.ph/python"));
}
// Initialize the communication library.
@@ -1021,7 +1025,8 @@ int main(int argc, char **argv) {
mem_log_scheduler.Run("Memory warning", std::chrono::seconds(3), [] {
auto free_ram = utils::sysinfo::AvailableMemory();
if (free_ram && *free_ram / 1024 < FLAGS_memory_warning_threshold)
spdlog::warn("Running out of available RAM, only {} MB left", *free_ram / 1024);
spdlog::warn(utils::MessageWithLink("Running out of available RAM, only {} MB left.", *free_ram / 1024,
"https://memgr.ph/ram"));
});
} else {
// Kernel version for the `MemAvailable` value is from: man procfs
@@ -1033,6 +1038,7 @@ int main(int argc, char **argv) {
}
std::cout << "You are running Memgraph v" << gflags::VersionString() << std::endl;
std::cout << "To get started with Memgraph, visit https://memgr.ph/start" << std::endl;
auto data_directory = std::filesystem::path(FLAGS_data_directory);
@@ -1152,7 +1158,7 @@ int main(int argc, char **argv) {
service_name = "BoltS";
spdlog::info("Using secure Bolt connection (with SSL)");
} else {
spdlog::warn("Using non-secure Bolt connection (without SSL)");
spdlog::warn(utils::MessageWithLink("Using non-secure Bolt connection (without SSL).", "https://memgr.ph/ssl"));
}
ServerT server({FLAGS_bolt_address, static_cast<uint16_t>(FLAGS_bolt_port)}, &session_data, &context,

View File

@@ -23,6 +23,7 @@
#include "storage/v2/storage.hpp"
#include "utils/exceptions.hpp"
#include "utils/logging.hpp"
#include "utils/message.hpp"
#include "utils/string.hpp"
#include "utils/timer.hpp"
#include "version.hpp"
@@ -433,7 +434,7 @@ void ProcessNodeRow(storage::Storage *store, const std::vector<Field> &fields, c
auto it = node_id_map->find(node_id);
if (it != node_id_map->end()) {
if (FLAGS_skip_duplicate_nodes) {
spdlog::warn("Skipping duplicate node with ID '{}'", node_id);
spdlog::warn(utils::MessageWithLink("Skipping duplicate node with ID '{}'.", node_id, "https://memgr.ph/csv"));
return;
} else {
throw LoadException("Node with ID '{}' already exists", node_id);
@@ -524,7 +525,8 @@ void ProcessRelationshipsRow(storage::Storage *store, const std::vector<Field> &
auto it = node_id_map.find(node_id);
if (it == node_id_map.end()) {
if (FLAGS_skip_bad_relationships) {
spdlog::warn("Skipping bad relationship with START_ID '{}'", node_id);
spdlog::warn(
utils::MessageWithLink("Skipping bad relationship with START_ID '{}'.", node_id, "https://memgr.ph/csv"));
return;
} else {
throw LoadException("Node with ID '{}' does not exist", node_id);
@@ -541,7 +543,7 @@ void ProcessRelationshipsRow(storage::Storage *store, const std::vector<Field> &
auto it = node_id_map.find(node_id);
if (it == node_id_map.end()) {
if (FLAGS_skip_bad_relationships) {
spdlog::warn("Skipping bad relationship with END_ID '{}'", node_id);
spdlog::warn(utils::MessageWithLink("Skipping bad relationship with END_ID '{}'.", node_id, "https://memgr.ph/csv"));
return;
} else {
throw LoadException("Node with ID '{}' does not exist", node_id);

View File

@@ -1,7 +1,8 @@
;; Copyright 2021 Memgraph Ltd.
;;
;; Use of this software is governed by the Business Source 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.
;; 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

View File

@@ -2,7 +2,8 @@
* Copyright 2021 Memgraph Ltd.
*
* Use of this software is governed by the Business Source 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.
* 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

View File

@@ -2,7 +2,8 @@
* Copyright 2021 Memgraph Ltd.
*
* Use of this software is governed by the Business Source 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.
* 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

View File

@@ -1,7 +1,8 @@
;; Copyright 2021 Memgraph Ltd.
;;
;; Use of this software is governed by the Business Source 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.
;; 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

View File

@@ -1908,13 +1908,20 @@ bool Delete::DeleteCursor::Pull(Frame &frame, ExecutionContext &context) {
throw QueryRuntimeException("Unexpected error when deleting a node.");
}
}
if (context.trigger_context_collector &&
context.trigger_context_collector->ShouldRegisterDeletedObject<EdgeAccessor>() && res.GetValue()) {
context.trigger_context_collector->RegisterDeletedObject(res.GetValue()->first);
for (const auto &deleted_edge : res.GetValue()->second) {
context.trigger_context_collector->RegisterDeletedObject(deleted_edge);
std::invoke([&] {
if (!context.trigger_context_collector || !*res) {
return;
}
}
context.trigger_context_collector->RegisterDeletedObject((*res)->first);
if (!context.trigger_context_collector->ShouldRegisterDeletedObject<query::EdgeAccessor>()) {
return;
}
for (const auto &edge : (*res)->second) {
context.trigger_context_collector->RegisterDeletedObject(edge);
}
});
} else {
auto res = dba.RemoveVertex(&va);
if (res.HasError()) {

View File

@@ -1,7 +1,8 @@
;; Copyright 2021 Memgraph Ltd.
;;
;; Use of this software is governed by the Business Source 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.
;; 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

View File

@@ -1565,9 +1565,8 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam
if (!MgpVertexIsMutable(*v)) {
throw ImmutableObjectException{"Cannot set a property on an immutable vertex!"};
}
const auto result =
v->impl.SetProperty(v->graph->impl->NameToProperty(property_name), ToPropertyValue(*property_value));
const auto prop_key = v->graph->impl->NameToProperty(property_name);
const auto result = v->impl.SetProperty(prop_key, ToPropertyValue(*property_value));
if (result.HasError()) {
switch (result.GetError()) {
case storage::Error::DELETED_OBJECT:
@@ -1581,6 +1580,18 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam
throw SerializationException{"Cannot serialize setting a property of a vertex."};
}
}
auto *trigger_ctx_collector = v->graph->ctx->trigger_context_collector;
if (!trigger_ctx_collector || !trigger_ctx_collector->ShouldRegisterObjectPropertyChange<query::VertexAccessor>()) {
return;
}
const auto old_value = query::TypedValue(*result);
if (property_value->type == mgp_value_type::MGP_VALUE_TYPE_NULL) {
trigger_ctx_collector->RegisterRemovedObjectProperty(v->impl, prop_key, old_value);
return;
}
const auto new_value = ToTypedValue(*property_value, property_value->memory);
trigger_ctx_collector->RegisterSetObjectProperty(v->impl, prop_key, old_value, new_value);
});
}
@@ -1589,7 +1600,8 @@ mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) {
if (!MgpVertexIsMutable(*v)) {
throw ImmutableObjectException{"Cannot add a label to an immutable vertex!"};
}
const auto result = v->impl.AddLabel(v->graph->impl->NameToLabel(label.name));
const auto label_id = v->graph->impl->NameToLabel(label.name);
const auto result = v->impl.AddLabel(label_id);
if (result.HasError()) {
switch (result.GetError()) {
@@ -1604,6 +1616,10 @@ mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) {
throw SerializationException{"Cannot serialize adding a label to a vertex."};
}
}
if (v->graph->ctx->trigger_context_collector) {
v->graph->ctx->trigger_context_collector->RegisterSetVertexLabel(v->impl, label_id);
}
});
}
@@ -1612,7 +1628,8 @@ mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) {
if (!MgpVertexIsMutable(*v)) {
throw ImmutableObjectException{"Cannot remove a label from an immutable vertex!"};
}
const auto result = v->impl.RemoveLabel(v->graph->impl->NameToLabel(label.name));
const auto label_id = v->graph->impl->NameToLabel(label.name);
const auto result = v->impl.RemoveLabel(label_id);
if (result.HasError()) {
switch (result.GetError()) {
@@ -1627,6 +1644,9 @@ mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) {
throw SerializationException{"Cannot serialize removing a label from a vertex."};
}
}
if (v->graph->ctx->trigger_context_collector) {
v->graph->ctx->trigger_context_collector->RegisterRemovedVertexLabel(v->impl, label_id);
}
});
}
@@ -1954,8 +1974,8 @@ mgp_error mgp_edge_set_property(struct mgp_edge *e, const char *property_name, m
if (!MgpEdgeIsMutable(*e)) {
throw ImmutableObjectException{"Cannot set a property on an immutable edge!"};
}
const auto result =
e->impl.SetProperty(e->from.graph->impl->NameToProperty(property_name), ToPropertyValue(*property_value));
const auto prop_key = e->from.graph->impl->NameToProperty(property_name);
const auto result = e->impl.SetProperty(prop_key, ToPropertyValue(*property_value));
if (result.HasError()) {
switch (result.GetError()) {
@@ -1971,6 +1991,18 @@ mgp_error mgp_edge_set_property(struct mgp_edge *e, const char *property_name, m
throw SerializationException{"Cannot serialize setting a property of an edge."};
}
}
auto *trigger_ctx_collector = e->from.graph->ctx->trigger_context_collector;
if (!trigger_ctx_collector || !trigger_ctx_collector->ShouldRegisterObjectPropertyChange<query::EdgeAccessor>()) {
return;
}
const auto old_value = query::TypedValue(*result);
if (property_value->type == mgp_value_type::MGP_VALUE_TYPE_NULL) {
e->from.graph->ctx->trigger_context_collector->RegisterRemovedObjectProperty(e->impl, prop_key, old_value);
return;
}
const auto new_value = ToTypedValue(*property_value, property_value->memory);
e->from.graph->ctx->trigger_context_collector->RegisterSetObjectProperty(e->impl, prop_key, old_value, new_value);
});
}
@@ -2024,8 +2056,10 @@ mgp_error mgp_graph_create_vertex(struct mgp_graph *graph, mgp_memory *memory, m
if (!MgpGraphIsMutable(*graph)) {
throw ImmutableObjectException{"Cannot create a vertex in an immutable graph!"};
}
auto vertex = graph->impl->InsertVertex();
if (graph->ctx->trigger_context_collector) {
graph->ctx->trigger_context_collector->RegisterCreatedObject(vertex);
}
return NewRawMgpObject<mgp_vertex>(memory, vertex, graph);
},
result);
@@ -2051,6 +2085,9 @@ mgp_error mgp_graph_delete_vertex(struct mgp_graph *graph, mgp_vertex *vertex) {
throw SerializationException{"Cannot serialize removing a vertex."};
}
}
if (graph->ctx->trigger_context_collector && *result) {
graph->ctx->trigger_context_collector->RegisterDeletedObject(**result);
}
});
}
@@ -2073,6 +2110,18 @@ mgp_error mgp_graph_detach_delete_vertex(struct mgp_graph *graph, mgp_vertex *ve
throw SerializationException{"Cannot serialize removing a vertex."};
}
}
auto *trigger_ctx_collector = graph->ctx->trigger_context_collector;
if (!trigger_ctx_collector || !*result) {
return;
}
trigger_ctx_collector->RegisterDeletedObject((*result)->first);
if (!trigger_ctx_collector->ShouldRegisterDeletedObject<query::EdgeAccessor>()) {
return;
}
for (const auto &edge : (*result)->second) {
trigger_ctx_collector->RegisterDeletedObject(edge);
}
});
}
@@ -2098,6 +2147,9 @@ mgp_error mgp_graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *
throw SerializationException{"Cannot serialize creating an edge."};
}
}
if (graph->ctx->trigger_context_collector) {
graph->ctx->trigger_context_collector->RegisterCreatedObject(*edge);
}
return NewRawMgpObject<mgp_edge>(memory, edge.GetValue(), from->graph);
},
result);
@@ -2122,6 +2174,9 @@ mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, mgp_edge *edge) {
throw SerializationException{"Cannot serialize removing an edge."};
}
}
if (graph->ctx->trigger_context_collector && *result) {
graph->ctx->trigger_context_collector->RegisterDeletedObject(**result);
}
});
}

View File

@@ -24,6 +24,7 @@ extern "C" {
#include "query/procedure/py_module.hpp"
#include "utils/file.hpp"
#include "utils/logging.hpp"
#include "utils/message.hpp"
#include "utils/pmr/vector.hpp"
#include "utils/string.hpp"
@@ -324,14 +325,15 @@ bool SharedLibraryModule::Load(const std::filesystem::path &file_path) {
dlerror(); // Clear any existing error.
handle_ = dlopen(file_path.c_str(), RTLD_NOW | RTLD_LOCAL);
if (!handle_) {
spdlog::error("Unable to load module {}; {}", file_path, dlerror());
// NOLINTNEXTLINE(concurrency-mt-unsafe)
spdlog::error(utils::MessageWithLink("Unable to load module {}; {}.", file_path, dlerror(), "https://memgr.ph/modules"));
return false;
}
// Get required mgp_init_module
init_fn_ = reinterpret_cast<int (*)(mgp_module *, mgp_memory *)>(dlsym(handle_, "mgp_init_module"));
char *dl_errored = dlerror();
if (!init_fn_ || dl_errored) {
spdlog::error("Unable to load module {}; {}", file_path, dl_errored);
spdlog::error(utils::MessageWithLink("Unable to load module {}; {}.", file_path, dl_errored, "https://memgr.ph/modules"));
dlclose(handle_);
handle_ = nullptr;
return false;
@@ -382,7 +384,8 @@ bool SharedLibraryModule::Close() {
spdlog::warn("When closing module {}; mgp_shutdown_module returned {}", file_path_, shutdown_res);
}
if (dlclose(handle_) != 0) {
spdlog::error("Failed to close module {}; {}", file_path_, dlerror());
// NOLINTNEXTLINE(concurrency-mt-unsafe)
spdlog::error(utils::MessageWithLink("Failed to close module {}; {}.", file_path_, dlerror(), "https://memgr.ph/modules"));
return false;
}
spdlog::info("Closed module {}", file_path_);
@@ -441,7 +444,7 @@ bool PythonModule::Load(const std::filesystem::path &file_path) {
auto gil = py::EnsureGIL();
auto maybe_exc = py::AppendToSysPath(file_path.parent_path().c_str());
if (maybe_exc) {
spdlog::error("Unable to load module {}; {}", file_path, *maybe_exc);
spdlog::error(utils::MessageWithLink("Unable to load module {}; {}.", file_path, *maybe_exc, "https://memgr.ph/modules"));
return false;
}
bool succ = true;
@@ -466,7 +469,7 @@ bool PythonModule::Load(const std::filesystem::path &file_path) {
return true;
}
auto exc_info = py::FetchError().value();
spdlog::error("Unable to load module {}; {}", file_path, exc_info);
spdlog::error(utils::MessageWithLink("Unable to load module {}; {}.", file_path, exc_info, "https://memgr.ph/modules"));
return false;
}
@@ -509,7 +512,7 @@ namespace {
std::unique_ptr<Module> LoadModuleFromFile(const std::filesystem::path &path) {
const auto &ext = path.extension();
if (ext != ".so" && ext != ".py") {
spdlog::warn("Unknown query module file {}", path);
spdlog::warn(utils::MessageWithLink("Unknown query module file {}.", path, "https://memgr.ph/modules"));
return nullptr;
}
std::unique_ptr<Module> module;
@@ -531,7 +534,7 @@ bool ModuleRegistry::RegisterModule(const std::string_view &name, std::unique_pt
MG_ASSERT(!name.empty(), "Module name cannot be empty");
MG_ASSERT(module, "Tried to register an invalid module");
if (modules_.find(name) != modules_.end()) {
spdlog::error("Unable to overwrite an already loaded module {}", name);
spdlog::error(utils::MessageWithLink("Unable to overwrite an already loaded module {}.", name, "https://memgr.ph/modules"));
return false;
}
modules_.emplace(name, std::move(module));
@@ -561,7 +564,7 @@ void ModuleRegistry::SetModulesDirectory(std::vector<std::filesystem::path> modu
bool ModuleRegistry::LoadModuleIfFound(const std::filesystem::path &modules_dir, const std::string_view name) {
if (!utils::DirExists(modules_dir)) {
spdlog::error("Module directory {} doesn't exist", modules_dir);
spdlog::error(utils::MessageWithLink("Module directory {} doesn't exist.", modules_dir, "https://memgr.ph/modules"));
return false;
}
for (const auto &entry : std::filesystem::directory_iterator(modules_dir)) {
@@ -598,7 +601,7 @@ bool ModuleRegistry::LoadOrReloadModuleFromName(const std::string_view name) {
void ModuleRegistry::LoadModulesFromDirectory(const std::filesystem::path &modules_dir) {
if (modules_dir.empty()) return;
if (!utils::DirExists(modules_dir)) {
spdlog::error("Module directory {} doesn't exist", modules_dir);
spdlog::error(utils::MessageWithLink("Module directory {} doesn't exist.", modules_dir, "https://memgr.ph/modules"));
return;
}
for (const auto &entry : std::filesystem::directory_iterator(modules_dir)) {

View File

@@ -29,6 +29,7 @@
#include "storage/v2/durability/wal.hpp"
#include "utils/logging.hpp"
#include "utils/memory_tracker.hpp"
#include "utils/message.hpp"
namespace storage::durability {
@@ -168,7 +169,8 @@ std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_di
spdlog::info("Recovering persisted data using snapshot ({}) and WAL directory ({}).", snapshot_directory,
wal_directory);
if (!utils::DirExists(snapshot_directory) && !utils::DirExists(wal_directory)) {
spdlog::warn("Snapshot or WAL directory don't exist, there is nothing to recover.");
spdlog::warn(utils::MessageWithLink("Snapshot or WAL directory don't exist, there is nothing to recover.",
"https://memgr.ph/durability"));
return std::nullopt;
}
@@ -242,7 +244,7 @@ std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_di
}
MG_ASSERT(!error_code, "Couldn't recover data because an error occurred: {}!", error_code.message());
if (wal_files.empty()) {
spdlog::warn("No snapshot or WAL file found!");
spdlog::warn(utils::MessageWithLink("No snapshot or WAL file found.", "https://memgr.ph/durability"));
return std::nullopt;
}
std::sort(wal_files.begin(), wal_files.end());
@@ -254,7 +256,7 @@ std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_di
auto maybe_wal_files = GetWalFiles(wal_directory, *uuid);
if (!maybe_wal_files) {
spdlog::warn("Couldn't get WAL file info from the WAL directory!");
spdlog::warn(utils::MessageWithLink("Couldn't get WAL file info from the WAL directory.", "https://memgr.ph/durability"));
return std::nullopt;
}

View File

@@ -22,6 +22,7 @@
#include "storage/v2/vertex_accessor.hpp"
#include "utils/file_locker.hpp"
#include "utils/logging.hpp"
#include "utils/message.hpp"
namespace storage::durability {
@@ -913,9 +914,8 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps
if (error_code) {
spdlog::error(
"Couldn't ensure that exactly {} snapshots exist because an error "
"occurred: {}",
snapshot_retention_count, error_code.message());
utils::MessageWithLink("Couldn't ensure that exactly {} snapshots exist because an error occurred: {}.",
snapshot_retention_count, error_code.message(), "https://memgr.ph/snapshots"));
}
std::sort(old_snapshot_files.begin(), old_snapshot_files.end());
if (old_snapshot_files.size() > snapshot_retention_count - 1) {
@@ -945,9 +945,9 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps
if (error_code) {
spdlog::error(
"Couldn't ensure that only the absolutely necessary WAL files exist "
"because an error occurred: {}",
error_code.message());
utils::MessageWithLink("Couldn't ensure that only the absolutely necessary WAL files exist "
"because an error occurred: {}.",
error_code.message(), "https://memgr.ph/snapshots"));
}
std::sort(wal_files.begin(), wal_files.end());
uint64_t snapshot_start_timestamp = transaction->start_timestamp;

View File

@@ -20,6 +20,7 @@
#include "storage/v2/transaction.hpp"
#include "utils/file_locker.hpp"
#include "utils/logging.hpp"
#include "utils/message.hpp"
namespace storage {
@@ -105,12 +106,13 @@ void Storage::ReplicationClient::TryInitializeClient() {
} catch (const rpc::RpcFailedException &) {
std::unique_lock client_guarde{client_lock_};
replica_state_.store(replication::ReplicaState::INVALID);
spdlog::error("Failed to connect to replica {} at {}", name_, rpc_client_->Endpoint());
spdlog::error(utils::MessageWithLink("Failed to connect to replica {} at the endpoint {}.", name_,
rpc_client_->Endpoint(), "https://memgr.ph/replication"));
}
}
void Storage::ReplicationClient::HandleRpcFailure() {
spdlog::error("Couldn't replicate data to {}", name_);
spdlog::error(utils::MessageWithLink("Couldn't replicate data to {}.", name_, "https://memgr.ph/replication"));
thread_pool_.AddTask([this] {
rpc_client_->Abort();
this->TryInitializeClient();

View File

@@ -1,7 +1,8 @@
;; Copyright 2021 Memgraph Ltd.
;;
;; Use of this software is governed by the Business Source 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.
;; 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

View File

@@ -33,6 +33,7 @@
#include "utils/file.hpp"
#include "utils/logging.hpp"
#include "utils/memory_tracker.hpp"
#include "utils/message.hpp"
#include "utils/rw_lock.hpp"
#include "utils/spin_lock.hpp"
#include "utils/stat.hpp"
@@ -376,7 +377,7 @@ Storage::Storage(Config config)
if (auto maybe_error = this->CreateSnapshot(); maybe_error.HasError()) {
switch (maybe_error.GetError()) {
case CreateSnapshotError::DisabledForReplica:
spdlog::warn("Snapshots are disabled for replicas!");
spdlog::warn(utils::MessageWithLink("Snapshots are disabled for replicas.", "https://memgr.ph/replication"));
break;
}
}
@@ -413,7 +414,7 @@ Storage::~Storage() {
if (auto maybe_error = this->CreateSnapshot(); maybe_error.HasError()) {
switch (maybe_error.GetError()) {
case CreateSnapshotError::DisabledForReplica:
spdlog::warn("Snapshots are disabled for replicas!");
spdlog::warn(utils::MessageWithLink("Snapshots are disabled for replicas.", "https://memgr.ph/replication"));
break;
}
}

View File

@@ -5,6 +5,8 @@ set(telemetry_src_files
add_library(telemetry_lib STATIC ${telemetry_src_files})
target_link_libraries(telemetry_lib mg-requests mg-kvstore mg-utils)
if (BUILD_FOR_DOCKER)
target_compile_definitions(telemetry_lib PRIVATE DOCKER_BUILD)
option(MG_TELEMETRY_ID_OVERRIDE "Override for the telemetry ID" STRING)
if (MG_TELEMETRY_ID_OVERRIDE)
message(WARNING "Using telemetry ID override: ${MG_TELEMETRY_ID_OVERRIDE}")
target_compile_definitions(telemetry_lib PRIVATE MG_TELEMETRY_ID_OVERRIDE="${MG_TELEMETRY_ID_OVERRIDE}")
endif()

View File

@@ -26,8 +26,8 @@
namespace telemetry {
namespace {
std::string GetMachineId() {
#ifdef DOCKER_BUILD
return "DOCKER";
#ifdef MG_TELEMETRY_ID_OVERRIDE
return MG_TELEMETRY_ID_OVERRIDE;
#else
// We assume we're on linux and we need to read the machine id from /etc/machine-id
const auto machine_id_lines = utils::ReadLines("/etc/machine-id");

23
src/utils/message.hpp Normal file
View File

@@ -0,0 +1,23 @@
// Copyright 2021 Memgraph Ltd.
//
// Use of this software is governed by the Business Source 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 <fmt/format.h>
namespace utils {
template <typename... Args>
std::string MessageWithLink(const std::string_view format, Args &&...args) {
return fmt::format(fmt::format("{} For more details, visit {{}}.", format), std::forward<Args>(args)...);
}
} // namespace utils

View File

@@ -12,3 +12,9 @@ target_link_libraries(memgraph__e2e__triggers__on_delete memgraph__e2e__triggers
add_executable(memgraph__e2e__triggers__privileges privilige_check.cpp)
target_link_libraries(memgraph__e2e__triggers__privileges memgraph__e2e__triggers_common)
add_subdirectory(procedures)
add_dependencies(memgraph__e2e__triggers__on_create memgraph__e2e__triggers__write.py)
add_dependencies(memgraph__e2e__triggers__on_update memgraph__e2e__triggers__write.py)
add_dependencies(memgraph__e2e__triggers__on_delete memgraph__e2e__triggers__write.py)

View File

@@ -100,3 +100,10 @@ void CheckVertexExists(mg::Client &client, std::string_view label, int vertex_id
MG_ASSERT(VertexExists(client, label, vertex_id), "Expected vertex doesn't exist with label {} and id {}!", label,
vertex_id);
}
void ExecuteCreateVertex(mg::Client &client, int id) {
client.Execute(fmt::format("CALL write.create_vertex({}) YIELD v RETURN v", id));
const auto v1 = client.FetchAll();
MG_ASSERT(v1);
MG_ASSERT(v1->size() == 1);
}

View File

@@ -21,4 +21,5 @@ void CheckNumberOfAllVertices(mg::Client &client, int expected_number_of_vertice
std::optional<mg::Value> GetVertex(mg::Client &client, std::string_view label, int vertex_id);
bool VertexExists(mg::Client &client, std::string_view label, int vertex_id);
void CheckVertexMissing(mg::Client &client, std::string_view label, int vertex_id);
void CheckVertexExists(mg::Client &client, std::string_view label, int vertex_id);
void CheckVertexExists(mg::Client &client, std::string_view label, int vertex_id);
void ExecuteCreateVertex(mg::Client &client, int id);

View File

@@ -103,5 +103,34 @@ int main(int argc, char **argv) {
run_create_trigger_tests(kBeforeCommit);
run_create_trigger_tests(kAfterCommit);
const auto run_create_trigger_write_proc_create_vertex_test = [&]() {
CreateOnCreateTriggers(*client, true);
ExecuteCreateVertex(*client, 1);
constexpr auto kNumberOfExpectedVertices = 3;
CheckNumberOfAllVertices(*client, kNumberOfExpectedVertices);
CheckVertexExists(*client, kTriggerCreatedVertexLabel, 1);
CheckVertexExists(*client, kTriggerCreatedObjectLabel, 1);
DropOnCreateTriggers(*client);
client->Execute("MATCH (n) DETACH DELETE n;");
client->DiscardAll();
};
run_create_trigger_write_proc_create_vertex_test();
const auto run_create_trigger_write_proc_create_edge_test = [&]() {
ExecuteCreateVertex(*client, 1);
ExecuteCreateVertex(*client, 2);
CreateOnCreateTriggers(*client, true);
client->Execute("MATCH (n {id:1}), (m {id:2}) CALL write.create_edge(n, m, 'edge') YIELD e RETURN e");
client->DiscardAll();
constexpr auto kNumberOfExpectedVertices = 4;
CheckNumberOfAllVertices(*client, kNumberOfExpectedVertices);
CheckVertexExists(*client, kTriggerCreatedEdgeLabel, 1);
CheckVertexExists(*client, kTriggerCreatedObjectLabel, 1);
DropOnCreateTriggers(*client);
client->Execute("MATCH (n) DETACH DELETE n;");
client->DiscardAll();
};
run_create_trigger_write_proc_create_edge_test();
return 0;
}

View File

@@ -1,5 +1,6 @@
#include <string>
#include <string_view>
#include <unordered_set>
#include <gflags/gflags.h>
#include <mgclient.hpp>
@@ -10,6 +11,12 @@ constexpr std::string_view kTriggerDeletedVertexLabel{"DELETED_VERTEX"};
constexpr std::string_view kTriggerDeletedEdgeLabel{"DELETED_EDGE"};
constexpr std::string_view kTriggerDeletedObjectLabel{"DELETED_OBJECT"};
enum class AllowedTriggerType : uint8_t {
VERTEX,
EDGE,
OBJECT,
};
void DetachDeleteVertex(mg::Client &client, int vertex_id) {
mg::Map parameters{{"id", mg::Value{vertex_id}}};
client.Execute(fmt::format("MATCH (n: {} {{id: $id}}) DETACH DELETE n", kVertexLabel),
@@ -23,43 +30,77 @@ void DeleteEdge(mg::Client &client, int edge_id) {
client.DiscardAll();
}
void CreateOnDeleteTriggers(mg::Client &client, bool is_before) {
void CreateOnDeleteTriggers(mg::Client &client, bool is_before,
const std::unordered_set<AllowedTriggerType> &allowed_trigger_types) {
const std::string_view before_or_after = is_before ? "BEFORE" : "AFTER";
client.Execute(
fmt::format("CREATE TRIGGER DeletedVerticesTrigger ON () DELETE "
"{} COMMIT "
"EXECUTE "
"UNWIND deletedVertices as deletedVertex "
"CREATE (n: {} {{ id: deletedVertex.id }})",
before_or_after, kTriggerDeletedVertexLabel));
client.DiscardAll();
client.Execute(
fmt::format("CREATE TRIGGER DeletedEdgesTrigger ON --> DELETE "
"{} COMMIT "
"EXECUTE "
"UNWIND deletedEdges as deletedEdge "
"CREATE (n: {} {{ id: deletedEdge.id }})",
before_or_after, kTriggerDeletedEdgeLabel));
client.DiscardAll();
client.Execute(
fmt::format("CREATE TRIGGER DeletedObjectsTrigger ON DELETE "
"{} COMMIT "
"EXECUTE "
"UNWIND deletedObjects as deletedObjectEvent "
"WITH CASE deletedObjectEvent.event_type WHEN \"deleted_vertex\" THEN deletedObjectEvent.vertex.id "
"ELSE deletedObjectEvent.edge.id END as id "
"CREATE (n: {} {{ id: id }})",
before_or_after, kTriggerDeletedObjectLabel));
client.DiscardAll();
const auto create_on_vertex_delete_trigger = [&, before_or_after] {
client.Execute(
fmt::format("CREATE TRIGGER DeletedVerticesTrigger ON () DELETE "
"{} COMMIT "
"EXECUTE "
"UNWIND deletedVertices as deletedVertex "
"CREATE (n: {} {{ id: deletedVertex.id }})",
before_or_after, kTriggerDeletedVertexLabel));
client.DiscardAll();
};
const auto create_on_edge_delete_trigger = [&, before_or_after] {
client.Execute(
fmt::format("CREATE TRIGGER DeletedEdgesTrigger ON --> DELETE "
"{} COMMIT "
"EXECUTE "
"UNWIND deletedEdges as deletedEdge "
"CREATE (n: {} {{ id: deletedEdge.id }})",
before_or_after, kTriggerDeletedEdgeLabel));
client.DiscardAll();
};
const auto create_on_object_delete_trigger = [&, before_or_after] {
client.Execute(
fmt::format("CREATE TRIGGER DeletedObjectsTrigger ON DELETE "
"{} COMMIT "
"EXECUTE "
"UNWIND deletedObjects as deletedObjectEvent "
"WITH CASE deletedObjectEvent.event_type WHEN \"deleted_vertex\" THEN deletedObjectEvent.vertex.id "
"ELSE deletedObjectEvent.edge.id END as id "
"CREATE (n: {} {{ id: id }})",
before_or_after, kTriggerDeletedObjectLabel));
client.DiscardAll();
};
for (const auto allowed_trigger_type : allowed_trigger_types) {
switch (allowed_trigger_type) {
case AllowedTriggerType::VERTEX:
create_on_vertex_delete_trigger();
break;
case AllowedTriggerType::EDGE:
create_on_edge_delete_trigger();
break;
case AllowedTriggerType::OBJECT:
create_on_object_delete_trigger();
break;
}
}
}
void DropOnDeleteTriggers(mg::Client &client) {
client.Execute("DROP TRIGGER DeletedVerticesTrigger");
client.DiscardAll();
client.Execute("DROP TRIGGER DeletedEdgesTrigger");
client.DiscardAll();
client.Execute("DROP TRIGGER DeletedObjectsTrigger");
client.DiscardAll();
void DropOnDeleteTriggers(mg::Client &client, const std::unordered_set<AllowedTriggerType> &allowed_trigger_types) {
for (const auto allowed_trigger_type : allowed_trigger_types) {
switch (allowed_trigger_type) {
case AllowedTriggerType::VERTEX: {
client.Execute("DROP TRIGGER DeletedVerticesTrigger");
client.DiscardAll();
}
case AllowedTriggerType::EDGE: {
client.Execute("DROP TRIGGER DeletedEdgesTrigger");
client.DiscardAll();
}
case AllowedTriggerType::OBJECT: {
client.Execute("DROP TRIGGER DeletedObjectsTrigger");
client.DiscardAll();
}
}
}
}
struct EdgeInfo {
@@ -68,6 +109,10 @@ struct EdgeInfo {
int edge_id;
};
void ValidateVertexExistance(mg::Client &client, const bool should_exist, const std::string_view label, const int id) {
should_exist ? CheckVertexExists(client, label, id) : CheckVertexMissing(client, label, id);
};
int main(int argc, char **argv) {
gflags::SetUsageMessage("Memgraph E2E ON DELETE Triggers");
gflags::ParseCommandLineFlags(&argc, &argv, true);
@@ -77,11 +122,12 @@ int main(int argc, char **argv) {
auto client = Connect();
const auto run_delete_trigger_tests = [&](bool is_before) {
const std::array vertex_ids{1, 2, 3, 4};
const std::array edges{EdgeInfo{vertex_ids[0], vertex_ids[1], 5}, EdgeInfo{vertex_ids[2], vertex_ids[3], 6}};
const auto run_delete_trigger_tests = [&](const bool is_before,
const std::unordered_set<AllowedTriggerType> &allowed_trigger_types) {
constexpr std::array vertex_ids{1, 2, 3, 4};
constexpr std::array edges{EdgeInfo{vertex_ids[0], vertex_ids[1], 5}, EdgeInfo{vertex_ids[2], vertex_ids[3], 6}};
{
CreateOnDeleteTriggers(*client, is_before);
CreateOnDeleteTriggers(*client, is_before, allowed_trigger_types);
client->BeginTransaction();
for (const auto vertex_id : vertex_ids) {
@@ -100,34 +146,153 @@ int main(int argc, char **argv) {
// :VERTEX x 4
// deleted :VERTEX x -1
// :DELETED_VERTEX x 1
// :DELETED_EDGE x 2
// :DELETED_OBJECT x 3
constexpr auto kNumberOfExpectedVertices = 9;
auto number_of_expected_vertices = 3;
for (const auto allowed_trigger_type : allowed_trigger_types) {
switch (allowed_trigger_type) {
case AllowedTriggerType::VERTEX:
number_of_expected_vertices += 1;
break;
case AllowedTriggerType::EDGE:
number_of_expected_vertices += 2;
break;
case AllowedTriggerType::OBJECT:
number_of_expected_vertices += 3;
break;
}
}
if (is_before) {
CheckNumberOfAllVertices(*client, kNumberOfExpectedVertices);
CheckNumberOfAllVertices(*client, number_of_expected_vertices);
} else {
WaitForNumberOfAllVertices(*client, kNumberOfExpectedVertices);
WaitForNumberOfAllVertices(*client, number_of_expected_vertices);
}
CheckVertexExists(*client, kTriggerDeletedVertexLabel, vertex_ids[0]);
CheckVertexExists(*client, kTriggerDeletedObjectLabel, vertex_ids[0]);
ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::VERTEX),
kTriggerDeletedVertexLabel, vertex_ids[0]);
ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::OBJECT),
kTriggerDeletedObjectLabel, vertex_ids[0]);
for (const auto &edge : edges) {
CheckVertexExists(*client, kTriggerDeletedEdgeLabel, edge.edge_id);
CheckVertexExists(*client, kTriggerDeletedObjectLabel, edge.edge_id);
ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::EDGE),
kTriggerDeletedEdgeLabel, edge.edge_id);
ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::OBJECT),
kTriggerDeletedObjectLabel, edge.edge_id);
}
DropOnDeleteTriggers(*client);
DropOnDeleteTriggers(*client, allowed_trigger_types);
client->Execute("MATCH (n) DETACH DELETE n;");
client->DiscardAll();
}
};
const auto run_delete_trigger_write_procedure_tests =
[&](const std::unordered_set<AllowedTriggerType> &allowed_trigger_types) {
ExecuteCreateVertex(*client, 2);
ExecuteCreateVertex(*client, 3);
client->Execute("MATCH (n {id:2}), (m {id:3}) CALL write.create_edge(n, m, 'edge') YIELD e RETURN e");
client->DiscardAll();
CreateOnDeleteTriggers(*client, true, allowed_trigger_types);
client->Execute("MATCH ()-[e]->() CALL write.delete_edge(e)");
client->DiscardAll();
client->Execute("MATCH (n {id:2}) CALL write.delete_vertex(n)");
client->DiscardAll();
auto number_of_expected_vertices = 1;
for (const auto allowed_trigger_type : allowed_trigger_types) {
switch (allowed_trigger_type) {
case AllowedTriggerType::VERTEX:
number_of_expected_vertices += 1;
break;
case AllowedTriggerType::EDGE:
number_of_expected_vertices += 1;
break;
case AllowedTriggerType::OBJECT:
number_of_expected_vertices += 2;
break;
}
}
CheckNumberOfAllVertices(*client, number_of_expected_vertices);
ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::EDGE),
kTriggerDeletedEdgeLabel, 1);
ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::OBJECT),
kTriggerDeletedObjectLabel, 1);
ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::VERTEX),
kTriggerDeletedVertexLabel, 2);
ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::OBJECT),
kTriggerDeletedObjectLabel, 2);
DropOnDeleteTriggers(*client, allowed_trigger_types);
client->Execute("MATCH (n) DETACH DELETE n;");
client->DiscardAll();
};
const auto run_delete_trigger_write_procedure_delete_detach_test =
[&](const std::unordered_set<AllowedTriggerType> &allowed_trigger_types) {
ExecuteCreateVertex(*client, 2);
ExecuteCreateVertex(*client, 3);
client->Execute("MATCH (n {id:2}), (m {id:3}) CALL write.create_edge(n, m, 'edge') YIELD e RETURN e");
client->DiscardAll();
CreateOnDeleteTriggers(*client, true, allowed_trigger_types);
client->Execute("MATCH (v {id:2}) CALL write.detach_delete_vertex(v)");
client->DiscardAll();
auto number_of_expected_vertices = 1;
for (const auto allowed_trigger_type : allowed_trigger_types) {
switch (allowed_trigger_type) {
case AllowedTriggerType::VERTEX:
number_of_expected_vertices += 1;
break;
case AllowedTriggerType::EDGE:
number_of_expected_vertices += 1;
break;
case AllowedTriggerType::OBJECT:
number_of_expected_vertices += 2;
break;
}
}
CheckNumberOfAllVertices(*client, number_of_expected_vertices);
ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::EDGE),
kTriggerDeletedEdgeLabel, 1);
ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::OBJECT),
kTriggerDeletedObjectLabel, 1);
ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::VERTEX),
kTriggerDeletedVertexLabel, 2);
ValidateVertexExistance(*client, allowed_trigger_types.contains(AllowedTriggerType::OBJECT),
kTriggerDeletedObjectLabel, 2);
DropOnDeleteTriggers(*client, allowed_trigger_types);
client->Execute("MATCH (n) DETACH DELETE n;");
client->DiscardAll();
};
const auto run_for_trigger_combinations = [&](const bool is_before) {
const std::array trigger_type_combinations{
std::unordered_set{AllowedTriggerType::VERTEX},
std::unordered_set{AllowedTriggerType::EDGE},
std::unordered_set{AllowedTriggerType::OBJECT},
std::unordered_set{AllowedTriggerType::VERTEX, AllowedTriggerType::EDGE},
std::unordered_set{AllowedTriggerType::VERTEX, AllowedTriggerType::OBJECT},
std::unordered_set{AllowedTriggerType::EDGE, AllowedTriggerType::OBJECT},
std::unordered_set{AllowedTriggerType::VERTEX, AllowedTriggerType::EDGE, AllowedTriggerType::OBJECT},
};
for (const auto &allowed_trigger_types : trigger_type_combinations) {
run_delete_trigger_tests(is_before, allowed_trigger_types);
run_delete_trigger_write_procedure_tests(allowed_trigger_types);
run_delete_trigger_write_procedure_delete_detach_test(allowed_trigger_types);
}
};
constexpr bool kBeforeCommit = true;
constexpr bool kAfterCommit = false;
run_delete_trigger_tests(kBeforeCommit);
run_delete_trigger_tests(kAfterCommit);
run_for_trigger_combinations(kBeforeCommit);
run_for_trigger_combinations(kAfterCommit);
return 0;
}

View File

@@ -282,5 +282,87 @@ int main(int argc, char **argv) {
run_update_trigger_tests(kBeforeCommit);
run_update_trigger_tests(kAfterCommit);
const auto run_update_trigger_write_procedure_vertex_set_property_test = [&]() {
CreateOnUpdateTriggers(*client, true);
ExecuteCreateVertex(*client, 1);
client->Execute("MATCH (n) CALL write.set_property(n)");
client->DiscardAll();
constexpr auto kNumberOfExpectedVertices = 4;
constexpr int expected_updated_id = 2;
CheckNumberOfAllVertices(*client, kNumberOfExpectedVertices);
CheckVertexExists(*client, kTriggerUpdatedVertexLabel, expected_updated_id);
CheckVertexExists(*client, kTriggerUpdatedObjectLabel, expected_updated_id);
CheckVertexExists(*client, kTriggerSetVertexPropertyLabel, expected_updated_id);
client->Execute(fmt::format("MATCH (n1:{}), (n2:{}), (n3:{}) DELETE n1, n2, n3", kTriggerUpdatedVertexLabel,
kTriggerUpdatedObjectLabel, kTriggerSetVertexPropertyLabel));
client->DiscardAll();
client->Execute("MATCH (n) CALL write.remove_property(n)");
client->DiscardAll();
CheckNumberOfAllVertices(*client, kNumberOfExpectedVertices);
CheckVertexExists(*client, kTriggerUpdatedVertexLabel, expected_updated_id);
CheckVertexExists(*client, kTriggerUpdatedObjectLabel, expected_updated_id);
CheckVertexExists(*client, kTriggerRemovedVertexPropertyLabel, 2);
DropOnUpdateTriggers(*client);
client->Execute("MATCH (n) DETACH DELETE n;");
client->DiscardAll();
};
run_update_trigger_write_procedure_vertex_set_property_test();
const auto run_update_trigger_write_procedure_edge_set_property_test = [&]() {
CreateOnUpdateTriggers(*client, true);
ExecuteCreateVertex(*client, 1);
ExecuteCreateVertex(*client, 3);
client->Execute("MATCH (n {id:1}), (m {id:3}) CALL write.create_edge(n, m, 'edge') YIELD e RETURN e");
client->DiscardAll();
client->Execute("MATCH ()-[e]->() CALL write.set_property(e)");
client->DiscardAll();
constexpr auto kNumberOfExpectedVertices = 5;
constexpr int expected_updated_id = 2;
CheckNumberOfAllVertices(*client, kNumberOfExpectedVertices);
CheckVertexExists(*client, kTriggerSetEdgePropertyLabel, expected_updated_id);
CheckVertexExists(*client, kTriggerUpdatedObjectLabel, expected_updated_id);
CheckVertexExists(*client, kTriggerUpdatedEdgeLabel, expected_updated_id);
client->Execute(fmt::format("MATCH (n1:{}), (n2:{}), (n3:{}) DELETE n1, n2, n3", kTriggerSetEdgePropertyLabel,
kTriggerUpdatedObjectLabel, kTriggerUpdatedEdgeLabel));
client->DiscardAll();
client->Execute("MATCH ()-[e]->() CALL write.remove_property(e)");
client->DiscardAll();
CheckNumberOfAllVertices(*client, kNumberOfExpectedVertices);
CheckVertexExists(*client, kTriggerUpdatedObjectLabel, expected_updated_id);
CheckVertexExists(*client, kTriggerUpdatedEdgeLabel, expected_updated_id);
CheckVertexExists(*client, kTriggerRemovedEdgePropertyLabel, 2);
DropOnUpdateTriggers(*client);
client->Execute("MATCH (n) DETACH DELETE n;");
client->DiscardAll();
};
run_update_trigger_write_procedure_edge_set_property_test();
const auto run_update_trigger_write_procedure_set_label_test = [&]() {
ExecuteCreateVertex(*client, 1);
client->Execute("MATCH (n) CALL write.add_label(n, 'label') YIELD o RETURN o");
client->DiscardAll();
CreateOnUpdateTriggers(*client, true);
client->Execute("MATCH (n) CALL write.add_label(n, 'new') YIELD o RETURN o");
client->DiscardAll();
constexpr auto kNumberOfExpectedVertices = 4;
CheckNumberOfAllVertices(*client, kNumberOfExpectedVertices);
CheckVertexExists(*client, kTriggerSetVertexLabelLabel, 1);
CheckVertexExists(*client, kTriggerUpdatedVertexLabel, 1);
CheckVertexExists(*client, kTriggerUpdatedObjectLabel, 1);
client->Execute(fmt::format("MATCH (n1:{}), (n2:{}), (n3:{}) DELETE n1, n2, n3", kTriggerSetVertexLabelLabel,
kTriggerUpdatedVertexLabel, kTriggerUpdatedObjectLabel));
client->DiscardAll();
client->Execute("MATCH (n:new) CALL write.remove_label(n, 'new') YIELD o RETURN o");
client->DiscardAll();
CheckNumberOfAllVertices(*client, kNumberOfExpectedVertices);
CheckVertexExists(*client, kTriggerRemovedVertexLabelLabel, 1);
CheckVertexExists(*client, kTriggerUpdatedVertexLabel, 1);
CheckVertexExists(*client, kTriggerUpdatedObjectLabel, 1);
DropOnUpdateTriggers(*client);
client->Execute("MATCH (n) DETACH DELETE n;");
client->DiscardAll();
};
run_update_trigger_write_procedure_set_label_test();
return 0;
}

View File

@@ -0,0 +1 @@
copy_e2e_python_files(triggers write.py)

View File

@@ -0,0 +1,68 @@
import mgp
@mgp.write_proc
def create_vertex(ctx: mgp.ProcCtx, id: mgp.Any) -> mgp.Record(v=mgp.Any):
v = None
try:
v = ctx.graph.create_vertex()
v.properties.set("id", id)
v.properties.set("tbd", 0)
except RuntimeError as e:
return mgp.Record(v=str(e))
return mgp.Record(v=v)
@mgp.write_proc
def delete_vertex(ctx: mgp.ProcCtx, v: mgp.Any) -> mgp.Record():
ctx.graph.delete_vertex(v)
return mgp.Record()
@mgp.write_proc
def detach_delete_vertex(ctx: mgp.ProcCtx, v: mgp.Any) -> mgp.Record():
ctx.graph.detach_delete_vertex(v)
return mgp.Record()
@mgp.write_proc
def create_edge(ctx: mgp.ProcCtx, from_vertex: mgp.Vertex,
to_vertex: mgp.Vertex,
edge_type: str) -> mgp.Record(e=mgp.Any):
e = None
try:
e = ctx.graph.create_edge(
from_vertex, to_vertex, mgp.EdgeType(edge_type))
e.properties.set("id", 1);
e.properties.set("tbd", 0);
except RuntimeError as ex:
return mgp.Record(e=str(ex))
return mgp.Record(e=e)
@mgp.write_proc
def delete_edge(ctx: mgp.ProcCtx, edge: mgp.Edge) -> mgp.Record():
ctx.graph.delete_edge(edge)
return mgp.Record()
@mgp.write_proc
def set_property(ctx: mgp.ProcCtx, object: mgp.Any) -> mgp.Record():
object.properties.set("id", 2)
return mgp.Record()
@mgp.write_proc
def remove_property(ctx: mgp.ProcCtx, object: mgp.Any) -> mgp.Record():
object.properties.set("tbd", None)
return mgp.Record()
@mgp.write_proc
def add_label(ctx: mgp.ProcCtx, object: mgp.Any,
name: str) -> mgp.Record(o=mgp.Any):
object.add_label(name)
return mgp.Record(o=object)
@mgp.write_proc
def remove_label(ctx: mgp.ProcCtx, object: mgp.Any,
name: str) -> mgp.Record(o=mgp.Any):
object.remove_label(name)
return mgp.Record(o=object)

View File

@@ -11,14 +11,17 @@ workloads:
- name: "ON CREATE Triggers"
binary: "tests/e2e/triggers/memgraph__e2e__triggers__on_create"
args: ["--bolt-port", *bolt_port]
proc: "tests/e2e/triggers/procedures/"
<<: *template_cluster
- name: "ON UPDATE Triggers"
binary: "tests/e2e/triggers/memgraph__e2e__triggers__on_update"
args: ["--bolt-port", *bolt_port]
proc: "tests/e2e/triggers/procedures/"
<<: *template_cluster
- name: "ON DELETE Triggers"
binary: "tests/e2e/triggers/memgraph__e2e__triggers__on_delete"
args: ["--bolt-port", *bolt_port]
proc: "tests/e2e/triggers/procedures/"
<<: *template_cluster
- name: "Triggers privilege check"
binary: "tests/e2e/triggers/memgraph__e2e__triggers__privileges"

View File

@@ -9,6 +9,7 @@
#include "mg_procedure.h"
#include "query/db_accessor.hpp"
#include "query/plan/operator.hpp"
#include "query/procedure/mg_procedure_impl.hpp"
#include "storage/v2/id_types.hpp"
#include "storage/v2/property_value.hpp"
@@ -89,7 +90,7 @@ void CheckEdgeCountBetween(const MgpVertexPtr &from, const MgpVertexPtr &to, con
struct MgpGraphTest : public ::testing::Test {
mgp_graph CreateGraph(const storage::View view = storage::View::NEW) {
// the execution context can be null as it shouldn't be used in these tests
return mgp_graph{&CreateDbAccessor(storage::IsolationLevel::SNAPSHOT_ISOLATION), view, nullptr};
return mgp_graph{&CreateDbAccessor(storage::IsolationLevel::SNAPSHOT_ISOLATION), view, ctx_.get()};
}
std::array<storage::Gid, 2> CreateEdge() {
@@ -133,6 +134,7 @@ struct MgpGraphTest : public ::testing::Test {
private:
std::list<storage::Storage::Accessor> accessors_;
std::list<query::DbAccessor> db_accessors_;
std::unique_ptr<query::ExecutionContext> ctx_ = std::make_unique<query::ExecutionContext>();
};
TEST_F(MgpGraphTest, IsMutable) {

46
tools/header-checker.py Executable file
View File

@@ -0,0 +1,46 @@
#!/usr/bin/python3
import argparse
import sys
BSL_HEADER = """// Copyright 2021 Memgraph Ltd.
//
// Use of this software is governed by the Business Source 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."""
MEL_HEADER = """// Copyright 2021 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
// this file except in compliance with the License. You may obtain a copy of the License at https://memgraph.com/legal.
"""
def main():
parser = argparse.ArgumentParser()
parser.add_argument("file", help="directory with source files", nargs="?")
args = parser.parse_args()
with open(args.file, 'r') as f:
content = f.read()
has_header = content.startswith(BSL_HEADER) or content.startswith(MEL_HEADER)
if not has_header:
def red(s):
return f"\x1b[31m{s}\x1b[0m"
sys.stdout.writelines(red("The file is missing a header. Please add the BSL or MEL license header!\n"))
sys.exit(1)
sys.exit(0)
if __name__ == '__main__':
main()