Compare commits

...

36 Commits

Author SHA1 Message Date
Jure Bajic
a9491d3e68 Update license date (#561) 2022-09-20 14:23:24 +02:00
Marko Budiselić
b42e47b0be Reduce the size of TypedValue (#560)
- Reduce the size of TypedValue
- Fix double allocation
- Add `Graph` to `TypedValue` unit tests
- Fix allocator usage in `TypedValue`
- Add graph projection to `long_running.cpp` stress test
2022-09-20 14:21:34 +02:00
Marko Budiselić
898c894a48 Merge pull request #484 from memgraph/E129-MG-label-based-authorization
* Add label-based authorization

Co-authored-by: Boris Taševski <boris.tasevski@memgraph.com>
Co-authored-by: Josip Mrden <josip.mrden@memgraph.com>
Co-authored-by: Niko Krvavica <niko.krvavica@memgraph.com>
Co-authored-by: Bruno Sacaric <bruno.sacaric@memgraph.com>
2022-09-16 15:12:54 +02:00
niko4299
a3c2492672 Add fine grained access control to mgbench (#522) 2022-09-15 21:33:15 +02:00
Boris Taševski
a0b8871b36 Fix cland tidy errors and other warning (#555) 2022-09-15 15:51:35 +02:00
Marko Budiselic
bb6cf35441 Merge master cpp module API 2022-09-15 11:29:52 +02:00
Ante Pušić
5bc301d21d Add C++ query modules API (#546)
Co-authored-by: Ante Pusic <ante.pusic@memgraph.com>
Co-authored-by: Josip Mrden <josip.mrden@memgraph.com>
2022-09-15 11:26:26 +02:00
Marko Budiselic
1b89e679df Merge master 2022-09-15 08:32:41 +02:00
Boris Taševski
43e0520bc8 Merge master (#554) 2022-09-15 07:25:36 +02:00
Bruno Sačarić
2c8e45e889 Add run_id to the query summary (#548) 2022-09-14 20:21:06 +02:00
Boris Taševski
0876a8848d Merge master to epic and fix differences (#552) 2022-09-14 18:36:21 +02:00
Boris Taševski
fb4641a6be Fix logic in fine grained permissions (#551) 2022-09-14 12:39:23 +02:00
niko4299
201f75e809 Add MG_ENTERPRISE and license checks (#547) 2022-09-14 01:10:28 +02:00
niko4299
dc8dad9794 Add authorization in SetLabels, RemoveLabels, Allshortestpath cursor (#537) 2022-09-13 17:14:23 +02:00
Boris Taševski
aa02745915 [E129-MG < T1030-MG] Tech debts (#540)
* renamed parameters (#539)

* added variable declarations in ifs; minor code improvements; (#541)

* dba parameter removed (#543)

* Accept -> Has rename; HasGlobalPermissionOnVertices/Edges -> HasGlobalPrivilegeOnVertices/Edges (#545)

* replaced passing dba from reference to pointer
2022-09-13 11:37:17 +02:00
Josipmrden
b2d5a8eeca [E129-MG < T1040-MG] Add exceptions in LBA cursors (#536)
Exceptions added in update and create delete operators instead of logging
2022-09-12 14:04:40 +02:00
Boris Taševski
c09b175c76 [E129-MG < T1006-MG] Expand C API with LBA checks (#527)
* [T1006-MG < T1017-MG] Add LBA checks to all read procedures in C API (#515)

* Initial Impl

* NextPermittedEdge introduced

* revert moving constructor to cpp

* edge from and edge to methods expanded with lba check

* minor fix

* added check to path expand procedure

* Added integration tests for read query procedures

* additional check

* changed iterator type to reference

* comments from pr

Co-authored-by: Josip Mrden <josip.mrden@memgraph.io>

* [T1006-MG < T1018-MG] Add LBA checks to all update procedures in C API (#516)

* Initial Impl

* NextPermittedEdge introduced

* revert moving constructor to cpp

* edge from and edge to methods expanded with lba check

* minor fix

* extended update methods

* added check to path expand procedure

* Added integration tests for read query procedures

* Added integration tests for update query modules

* additional check

* changed iterator type to reference

* fixed bug in Update property for node; fixed 2 e2e tests

* replaced enum

Co-authored-by: Josip Mrden <josip.mrden@memgraph.io>

* [T1006-MG < T1019-MG] Add LBA checks to all Create and Delete procedures in C API (#517)

* Initial Impl

* NextPermittedEdge introduced

* revert moving constructor to cpp

* edge from and edge to methods expanded with lba check

* minor fix

* extended update methods

* initial implementation

* added check to path expand procedure

* Added integration tests for read query procedures

* Added integration tests for update query modules

* Added unit tests for creation of vertex, adding and removing vertex label

* additional check

* changed iterator type to reference

* Added unit tests for create edge

* Corrected query module in create edge

* fixed bug in Update property for node; fixed 2 e2e tests

* fixed merge errors

* Expanded FineGrainedAuthChecker with HasGlobalPermissionOnVertices and HasGlobalPermissionOnEdges

* Removed two wrong checks; Added two global checks

* return null added

* introduced new mgp_error value

* fixed endless loop

* replaced enum

* intermediate

* tests updated

* PermissionDeniedError -> AuthorizationError rename

* rename in enum permission_denied error -> authorization error

* mgp_vertex_remove_label check improved

* quotes changed; order of imports fixed

* string constant introduced

* import fixed

* yaml format

Co-authored-by: Josip Mrden <josip.mrden@memgraph.io>

Co-authored-by: Josip Mrden <josip.mrden@memgraph.io>
2022-09-08 17:48:34 +02:00
Kostas Kyrimis
f1fe77adfb Graph project feature implementation (#508) (#535) 2022-09-07 16:00:49 +03:00
Josip Mrden
35f8978560 Merge branch 'master' into E129-MG-label-based-authorization 2022-09-07 09:28:32 +02:00
Josip Matak
9e8fb2516b Add all shortest path algorithm (#409) 2022-09-06 16:21:32 +02:00
Josip Mrden
0a66feccff Merge branch 'master' into E129-MG-label-based-authorization 2022-09-06 11:14:27 +02:00
Boris Taševski
d008a2ad8d [E129-MG < T1007-MG] Expand Cursors with LBA checks (#524)
* [T1007-MG < T0997-MG] Authorization on paths (#501)

* Added read authorization in paths operators

* [T1007-MG < T1016-MG] Added authorization in create and delete operators (#513)

* Added authorization in RemoveNodeCursor, RemoveExpandCursor, CreateNodeCursor, CreateExpandCursor,MergeCursor

* [T1007-MG < T1014-MG] Add authorization to read operators (#520)

Added label based access control to read operators (ScanAll).

* [T1007-MG < T1015-MG] Add authorization to update operators (SetProperty, SetProperties, RemoveProperty) (#521)

Added label based authorization to update operators

Co-authored-by: niko4299 <51059248+niko4299@users.noreply.github.com>
Co-authored-by: Josip Mrden <josip.mrden@memgraph.io>
2022-09-02 17:12:07 +02:00
Josipmrden
7478300762 [E129-MG < T997-MG] Show label privileges (#506)
Added showing of label privileges functionality to fine grained access control.
2022-08-31 12:14:16 +02:00
János Benjamin Antal
0bc298c3ad Fix handling of the ROUTE Bolt message (#475)
The fields of ROUTE message were not read from the input buffer, thus the
input buffer got corrupted. Sending a new message to the server would result
reading the remaining fields from the buffer, which means reading some values
instead of message signature. Because of this unmet expectation, Memgraph closed
the connection. With this fix, the fields of the ROUTE message are properly
read and ignored.
2022-08-26 13:19:27 +02:00
Boris Taševski
05f120b7d4 [E129-MG < T1004-MG] Expand cypher with more granular label permissions (#500)
* Added enum for more granular access control; Expanded functionality of fine grained access checker; Propagated changes to Edit, Deny and Revoke permissions methods in interpreter

* Introduced Merge method for merging two colle with permissions

* e2e tests implementation started

* Expanded cypher to support fine grained permissions

* ast.lcp::AuthQuery removed labels, added support for label permissions

* promoted label permissions to vector

* removed unnecesary enum value

* expanded glue/auth with LabelPrivilegeToLabelPermission

* added const

* extended Grant Deny and Revoke Privileges with new label privileges

* extended Edit Grant Deny and Revoke Privileges to properly use new model

* Fixed unit tests

* FineGrainedAccessChecker Grant and Deny methods reworked

* Revoke cypher slightly reworked; Revoke for labels works without label permissions

* EditPermission's label_permission lambda now takes two parameters

* constants naming enforced; replaced asterisks with string constant

* removed faulty test addition

* Naming fixes; FineGrainedAccessChecker unit tests introduced

* unnecessary includes removed; minor code improvements

* minor fix

* Access checker reworked; denies and grant merged into single permission object; Created global_permission that applies to all non-created permissions. Grant, Deny and Revoke reworked; Merge method reworked

* Fixed wrong check;

* Fix after merge; renamed constants; removed unused constant

* Fix after merge; workloads.yaml for lbaprocedures e2e tests updated with new grammar

* Fixes after merge

* Fixes after merge

* fixed Revoke that was not fixed after the merge

* updated cypher main visitor tests

* PR review changes; Naming and const fixed, replaced double tertiary with lambda

* unwrapping the iterator fix

* merge 1003 minor fix

* minor spelling fixes

* Introduced visitPrivilegesList because of the doubled code

* const added

* string const to enum

* redundant braces

* added const

* minor code improvement

* e2e tests expanded

* if -> switch

* enum class inherits uint8_t now

* LabelPrililege::EDIT -> LabelPrivilege::UPDATE

* LabelPermission -> EntityPermission; LabelPrivilege -> EntityPrivilege

* EntityPrivilege -> FineGrainedPrivilege; EntityPermission -> FineGrainedPermission
2022-08-22 14:11:43 +02:00
antoniofilipovic
d73d153978 Add logging API (#417) 2022-08-22 14:47:52 +03:00
Boris Taševski
b489ac7cff [E129-MG < T1003-MG] Expand fine grained access checker with more granular permissions (#496)
* Added enum for more granular access control; Expanded functionality of fine grained access checker; Propagated changes to Edit, Deny and Revoke permissions methods in interpreter

* Introduced Merge method for merging two colle with permissions

* e2e tests implementation started

* FineGrainedAccessChecker Grant and Deny methods reworked

* removed faulty test addition

* Naming fixes; FineGrainedAccessChecker unit tests introduced

* unnecessary includes removed; minor code improvements

* Access checker reworked; denies and grant merged into single permission object; Created global_permission that applies to all non-created permissions. Grant, Deny and Revoke reworked; Merge method reworked

* Fixed wrong check;

* PR review changes; Naming and const fixed, replaced double tertiary with lambda

* unwrapping the iterator fix

* minor spelling fixes
2022-08-18 16:59:38 +02:00
niko4299
e15576f56c [E129-MG <-T0982-MG] implement edge type filtering (#489)
* GRANT, REVOKE, DENY and access_checker DONE

* Added AccessChecker to ExecutionContext

* grammar expanded; (#462)

* current

* T0954 mg expand user and role to hold permissions on labels (#465)

* added FineGrainedAccessPermissions class to model

* expanded user and role with fine grained access permissions

* fixed grammar

* [E129 < T0953-MG] GRANT, DENY, REVOKE added in interpreter and mainVisitor (#464)

* GRANT, DENY, REVOKE added in interpreter and mainVisitor

* Commented labelPermissons

* remove labelsPermission adding

* Fixed

* Removed extra lambda

* fixed

* [E129<-T0955-MG] Expand ExecutionContext with label related information (#467)

* added

* Added FineGrainedAccessChecker to Context

* fixed

* Added filtering

* testing

* Added edge filtering to storage, need to add filtering in simple Expand in operator.cpp

* Removed storage changes

* MATCH filtering working

* EdgeTypeFiltering working, just need to test everything again

* Removed FineGrainedAccessChecker

* Removed Expand Path

* Fix

* Tested FineGrainedAccessHandler, need to test AuthChecker

* Added integration test for lba

* Fixed merge conflicts

* PR fix

* fixed

* PR fix

* Fix test

* removed .vscode, .cache, .githooks

* githooks

* added tests

* fixed build

* Changed ast.lcp and User pointer to value in context.hpp

* Fixed test

* Remove denies on grant all

* AuthChecker

* Pr fix, auth_checker still not fixed

* Create mg-glue and extract UserBasedAuthChecker from AuthChecker

* Build fixed, need to fix test

* e2e tests

* e2e test working

* Added unit test, e2e and FineGrainedChecker

* Mege E129, auth_checker tests

* Fixed test

* e2e fix

Co-authored-by: Boris Taševski <36607228+BorisTasevski@users.noreply.github.com>
Co-authored-by: josipmrden <josip.mrden@external-basf.com>
Co-authored-by: János Benjamin Antal <benjamin.antal@memgraph.io>
2022-08-16 15:57:23 +02:00
Boris Taševski
a98463b0bd [E129 < T0996] C-API: Implement using Fine Grained Access Checker in iterator over vertices (#494)
* implemented skipping vertices in Constructor and mgp_vertices_iterator_next

* Added utility function for moving iterator to next permitted vertex

* removed ifdef directive

* NextPermitted parameter type changed from mgp_vertices_iterator* to mgp_vertices_iterator&

* created support for lba-procedures e2e testing; Added test for vertex iterator skipping unauthorized vertices

* removed fixture from tests; converted generator to regular function;
2022-08-12 19:34:47 +02:00
Kruno Golubic
705631a35d Create README file for CSV Import Tools (#493)
Co-authored-by: Marko Budiselić <marko.budiselic@memgraph.com>
2022-08-11 16:10:36 +02:00
Jeremy B
d4f0bb0e38 Correct inconsistencies w.r.t. sync replication (#435)
Add a report for the case where a sync replica does not confirm within a timeout:
-Add a new exception: ReplicationException to be returned when one sync replica does not confirm the reception of messages (new data, new constraint/index, or for triggers)
-Update the logic to throw the ReplicationException when needed for insertion of new data, triggers, or creation of new constraint/index
-Add end-to-end tests to cover the loss of connection with sync/async replicas when adding new data, adding new constraint/indexes, and triggers

Add end-to-end tests to cover the creation and drop of indexes, existence constraints, and uniqueness constraints

Improved tooling function mg_sleep_and_assert to also show the last result when duration is exceeded
2022-08-09 11:29:55 +02:00
Jure Bajic
531db2d47c Fix WebSocket test (#485)
* Fix websocket unit tests hanging
* Rename websocket to monitoring unit test
2022-08-08 14:49:48 +02:00
Boris Taševski
116262d9a0 [E129 < T0956] Filtering nodes in ScanAll cursor [Niko] (#492)
* implemented scanall filtering

* minor code refactor

* FindNextNode -> FindNextVertex
2022-08-04 19:20:17 +02:00
gvolfing
bbfef45b37 Add command to return startup config (#459)
Add a new command that is able to return the set of configurations that that the
given instance of memgraph was started up with. The returned information
currently consists of the name, the default and the current value of each flag.
The hidden property of three flags were removed, namely --query-cost-planner,
--query-vertex-count-to-expand-existing and --query-max-plans. The flag
--log-link-basename was completely removed since it is not used.
2022-08-03 18:08:44 +02:00
János Benjamin Antal
05b00edfd4 Declare mgp_func_context outside the callback function (#481) 2022-08-03 15:15:53 +02:00
Boris Taševski
480df4ed69 Merge old Label Based Auth Epic branch into new one because of commits with bad checks on the old epic branch (#478)
* grammar expanded; (#462)

* T0954 mg expand user and role to hold permissions on labels (#465)

* added FineGrainedAccessPermissions class to model

* expanded user and role with fine grained access permissions

* fixed grammar

* [E129 < T0953-MG] GRANT, DENY, REVOKE added in interpreter and mainVisitor (#464)

* GRANT, DENY, REVOKE added in interpreter and mainVisitor

* Commented labelPermissons

* remove labelsPermission adding

* Removed extra lambda

* [E129<-T0955-MG] Expand ExecutionContext with label related information (#467)

* Added FineGrainedAccessChecker to Context

* fixed failing tests for label based authorization (#480)

* Marked FineGrainedAccessChecker ctor explicit; Introduced change to clang-tidy; (#483)

Co-authored-by: niko4299 <51059248+niko4299@users.noreply.github.com>
2022-08-02 12:51:22 +02:00
185 changed files with 20425 additions and 2413 deletions

View File

@@ -130,7 +130,7 @@ jobs:
source /opt/toolchain-v4/activate
# Restrict clang-tidy results only to the modified parts
git diff -U0 ${{ env.BASE_BRANCH }}... -- src | ./tools/github/clang-tidy/clang-tidy-diff.py -p 1 -j $THREADS -path build | tee ./build/clang_tidy_output.txt
git diff -U0 ${{ env.BASE_BRANCH }}... -- src | ./tools/github/clang-tidy/clang-tidy-diff.py -p 1 -j $THREADS -path build -regex ".+\.cpp" | tee ./build/clang_tidy_output.txt
# Fail if any warning is reported
! cat ./build/clang_tidy_output.txt | ./tools/github/clang-tidy/grep_error_lines.sh > /dev/null

View File

@@ -0,0 +1,204 @@
# CSV Import Tool Documentation
CSV is a universal and very versatile data format used to store large quantities
of data. Each Memgraph database instance has a CSV import tool installed called
`mg_import_csv`. The CSV import tool should be used for initial bulk ingestion
of data into the database. Upon ingestion, the CSV importer creates a snapshot
that will be used by the database to recover its state on its next startup.
If you are already familiar with the Neo4j bulk import tool, then using the
`mg_import_csv` tool should be easy. The CSV import tool is fully compatible
with the [Neo4j CSV
format](https://neo4j.com/docs/operations-manual/current/tools/import/). If you
already have a pipeline set-up for Neo4j, you should only replace `neo4j-admin
import` with `mg_import_csv`.
## CSV File Format
Each row of a CSV file represents a single entry that should be imported into
the database. Both nodes and relationships can be imported into the database
using CSV files.
Each set of CSV files must have a header that describes the data that is stored
in the CSV files. Each field in the CSV header is in the format
`<name>[:<type>]` which identifies the name that should be used for that column
and the type that should be used for that column. The type is optional and
defaults to `string` (see the following chapter).
Each CSV field must be divided using the delimiter and each CSV field can either
be quoted or unquoted. When the field is quoted, the first and last character in
the field *must* be the quote character. If the field isn't quoted, and a quote
character appears in it, it is treated as a regular character. If a quote
character appears inside a quoted string then the quote character must be
doubled in order to escape it. Line feeds and carriage returns are ignored in
the CSV file, also, the file can't contain a NULL character.
## Properties
Both nodes and relationships can have properties added to them. When importing
properties, the CSV importer uses the name specified in the header of the
corresponding CSV column for the name of the property. A property is designated
by specifying one of the following types in the header:
- `integer`, `int`, `long`, `byte`, `short`: creates an integer property
- `float`, `double`: creates a float property
- `boolean`, `bool`: creates a boolean property
- `string`, `char`: creates a string property
When importing a boolean value, the CSV field should contain exactly the text
`true` to import a `True` boolean value. All other text values are treated as a
boolean value `False`.
If you want to import an array of values, you can do so by appending `[]` to any
of the above types. The values of the array are then determined by splitting
the raw CSV value using the array delimiter character.
Assuming that the array delimiter is `;`, the following example:
```plaintext
first_name,last_name:string,number:integer,aliases:string[]
John,Doe,1,Johnny;Jo;J-man
Melissa,Doe,2,Mel
```
Will yield these results:
```plaintext
CREATE ({first_name: "John", last_name: "Doe", number: 1, aliases: ["Johnny", "Jo", "J-man"]});
CREATE ({first_name: "Melissa", last_name: "Doe", number: 2, aliases: ["Mel"]});
```
### Nodes
When importing nodes, several more types can be specified in the header of the
CSV file (along with all property types):
- `ID`: id of the node that should be used as the node ID when importing
relationships
- `LABEL`: designates that the field contains additional labels for the node
- `IGNORE`: designates that the field should be ignored
The `ID` field type sets the internal ID that will be used for the node when
creating relationships. It is optional and nodes that don't have an ID value
specified will be imported, but can't be connected to any relationships. If you
want to save the ID value as a property in the database, just specify a name for
the ID (`user_id:ID`). If you just want to use the ID during the import, leave
out the name of the field (`:ID`). The `ID` field also supports creating
separate ID spaces. The ID space is specified with the ID space name appended
to the `ID` type in parentheses (`ID(user)`). That allows you to have the same
IDs (by value) for multiple different node files (for example, numbers from 1 to
N). The IDs in each ID space will be treated as an independent set of IDs that
don't interfere with IDs in another ID space.
The `LABEL` field type adds additional labels to the node. The value is treated
as an array type so that multiple additional labels can be specified for each
node. The value is split using the array delimiter (`--array-delimiter` flag).
### Relationships
In order to be able to import relationships, you must import the nodes in the
same invocation of `mg_import_csv` that is used to import the relationships.
When importing relationships, several more types can be specified in the header
of the CSV file (along with all property types):
- `START_ID`: id of the start node that should be connected with the
relationship
- `END_ID`: id of the end node that should be connected with the relationship
- `TYPE`: designates the type of the relationship
- `IGNORE`: designates that the field should be ignored
The `START_ID` field type sets the start node that should be connected with the
relationship to the end node. The field *must* be specified and the node ID
must be one of the node IDs that were specified in the node CSV files. The name
of this field is ignored. If the node ID is in an ID space, you can specify the
ID space for the in the same way as for the node ID (`START_ID(user)`).
The `END_ID` field type sets the end node that should be connected with the
relationship to the start node. The field *must* be specified and the node ID
must be one of the node IDs that were specified in the node CSV files. The name
of this field is ignored. If the node ID is in an ID space, you can specify the
ID space for the in the same way as for the node ID (`END_ID(user)`).
The `TYPE` field type sets the type of the relationship. Each relationship
*must* have a relationship type, but it doesn't necessarily need to be specified
in the CSV file, it can also be set externally for the whole CSV file. The name
of this field is ignored.
## CSV Importer Flags
The importer has many command line options that allow you to customize the way
the importer loads your data.
The two main flags that are used to specify the input CSV files are `--nodes`
and `--relationships`. Basic description of these flags is provided in the table
and more detailed explainion can be found further down bellow.
| Flag | Description |
|-----------------------| -------------- |
|`--nodes` | Used to specify CSV files that contain the nodes to the importer. |
|`--relationships` | Used to specify CSV files that contain the relationships to the importer.|
|`--delimiter` | Sets the delimiter that should be used when splitting the CSV fields (default `,`)|
|`--quote` | Sets the quote character that should be used to quote a CSV field (default `"`)|
|`--array-delimiter` | Sets the delimiter that should be used when splitting array values (default `;`)|
|`--id-type` | Specifies which data type should be used to store the supplied <br /> node IDs when storing them as properties (if the field name is supplied). <br /> The supported values are either `STRING` or `INTEGER`. (default `STRING`)|
|`--ignore-empty-strings` | Instructs the importer to treat all empty strings as `Null` values <br /> instead of an empty string value (default `false`)|
|`--ignore-extra-columns` | Instructs the importer to ignore all columns (instead of raising an error) <br /> that aren't specified after the last specified column in the CSV header. (default `false`) |
| `--skip-bad-relationships`| Instructs the importer to ignore all relationships (instead of raising an error) <br /> that refer to nodes that don't exist in the node files. (default `false`) |
|`--skip-duplicate-nodes` | Instructs the importer to ignore all duplicate nodes (instead of raising an error). <br /> Duplicate nodes are nodes that have an ID that is the same as another node that was already imported. (default `false`) |
| `--trim-strings`| Instructs the importer to trim all of the loaded CSV field values before processing them further. <br /> Trimming the fields removes all leading and trailing whitespace from them. (default `false`) |
The `--nodes` and `--relationships` flags are used to specify CSV files that
contain the nodes and relationships to the importer. Multiple files can be
specified in each supplied `--nodes` or `--relationships` flag. Files that are
supplied in one `--nodes` or `--relationships` flag are treated by the CSV
parser as one big CSV file. Only the first line of the first file is parsed for
the CSV header, all other files (and rows) are treated as data. This is useful
when you have a very large CSV file and don't want to edit its first line just
to add a CSV header. Instead, you can specify the header in a separate file
(e.g. `users_header.csv` or `friendships_header.csv`) and have the data intact
in the large file (e.g. `users.csv` or `friendships.csv`). Also, you can supply
additional labels for each set of node files.
The format of `--nodes` flag is:
`[<label>[:<label>]...=]<file>[,<file>][,<file>]...`. Take note that only the
first `<file>` part is mandatory, all other parts of the flag value are
optional. Multiple `--nodes` flags can be supplied to describe multiple sets of
different node files. For the importer to work, at least one `--nodes` flag
*must* be supplied.
The format of `--relationships` flag is: `[<type>=]<file>[,<file>][,<file>]...`.
Take note that only the first `<file>` part is mandatory, all other parts of the
flag value are optional. Multiple `--relationships` flags can be supplied to
describe multiple sets of different relationship files. The `--relationships`
flag isn't mandatory.
## CSV Parser Logic
The CSV parser uses the same logic as the standard Python CSV parser. The data
is parsed in the same way as the following snippet:
```python
import csv
for row in csv.reader(stream, strict=True):
# process 'row'
```
Python uses 'excel' as the default dialect when parsing CSV files and the
default settings for the CSV parser are:
- delimiter: `','`
- doublequote: `True`
- escapechar: `None`
- lineterminator: `'\r\n'`
- quotechar: `'"'`
- skipinitialspace: `False`
The above snippet can be expanded to:
```python
import csv
for row in csv.reader(stream, delimiter=',', doublequote=True,
escapechar=None, lineterminator='\r\n',
quotechar='"', skipinitialspace=False,
strict=True):
# process 'row'
```
For more information about the meaning of the above values, see:
https://docs.python.org/3/library/csv.html#csv.Dialect

693
include/_mgp.hpp Normal file
View File

@@ -0,0 +1,693 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
/// @file _mgp.hpp
///
/// The file contains methods that connect mg procedures and the outside code
/// Methods like mapping a graph into memory or assigning new mg results or
/// their properties are implemented.
#pragma once
#include "mg_exceptions.hpp"
#include "mg_procedure.h"
namespace mgp {
namespace {
void MgExceptionHandle(mgp_error result_code) {
switch (result_code) {
case mgp_error::MGP_ERROR_UNKNOWN_ERROR:
throw mg_exception::UnknownException();
case mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE:
throw mg_exception::AllocationException();
case mgp_error::MGP_ERROR_INSUFFICIENT_BUFFER:
throw mg_exception::InsufficientBufferException();
case mgp_error::MGP_ERROR_OUT_OF_RANGE:
throw mg_exception::OutOfRangeException();
case mgp_error::MGP_ERROR_LOGIC_ERROR:
throw mg_exception::LogicException();
case mgp_error::MGP_ERROR_DELETED_OBJECT:
throw mg_exception::DeletedObjectException();
case mgp_error::MGP_ERROR_INVALID_ARGUMENT:
throw mg_exception::InvalidArgumentException();
case mgp_error::MGP_ERROR_KEY_ALREADY_EXISTS:
throw mg_exception::KeyAlreadyExistsException();
case mgp_error::MGP_ERROR_IMMUTABLE_OBJECT:
throw mg_exception::ImmutableObjectException();
case mgp_error::MGP_ERROR_VALUE_CONVERSION:
throw mg_exception::ValueConversionException();
case mgp_error::MGP_ERROR_SERIALIZATION_ERROR:
throw mg_exception::SerializationException();
default:
return;
}
}
template <typename TResult, typename TFunc, typename... TArgs>
TResult MgInvoke(TFunc func, TArgs... args) {
TResult result{};
auto result_code = func(args..., &result);
MgExceptionHandle(result_code);
return result;
}
template <typename TFunc, typename... TArgs>
void MgInvokeVoid(TFunc func, TArgs... args) {
auto result_code = func(args...);
MgExceptionHandle(result_code);
}
} // namespace
// mgp_value
// Make value
mgp_value *value_make_null(mgp_memory *memory) { return MgInvoke<mgp_value *>(mgp_value_make_null, memory); }
mgp_value *value_make_bool(int val, mgp_memory *memory) {
return MgInvoke<mgp_value *>(mgp_value_make_bool, val, memory);
}
mgp_value *value_make_int(int64_t val, mgp_memory *memory) {
return MgInvoke<mgp_value *>(mgp_value_make_int, val, memory);
}
mgp_value *value_make_double(double val, mgp_memory *memory) {
return MgInvoke<mgp_value *>(mgp_value_make_double, val, memory);
}
mgp_value *value_make_string(const char *val, mgp_memory *memory) {
return MgInvoke<mgp_value *>(mgp_value_make_string, val, memory);
}
mgp_value *value_make_list(mgp_list *val) { return MgInvoke<mgp_value *>(mgp_value_make_list, val); }
mgp_value *value_make_map(mgp_map *val) { return MgInvoke<mgp_value *>(mgp_value_make_map, val); }
mgp_value *value_make_vertex(mgp_vertex *val) { return MgInvoke<mgp_value *>(mgp_value_make_vertex, val); }
mgp_value *value_make_edge(mgp_edge *val) { return MgInvoke<mgp_value *>(mgp_value_make_edge, val); }
mgp_value *value_make_path(mgp_path *val) { return MgInvoke<mgp_value *>(mgp_value_make_path, val); }
mgp_value *value_make_date(mgp_date *val) { return MgInvoke<mgp_value *>(mgp_value_make_date, val); }
mgp_value *value_make_local_time(mgp_local_time *val) { return MgInvoke<mgp_value *>(mgp_value_make_local_time, val); }
mgp_value *value_make_local_date_time(mgp_local_date_time *val) {
return MgInvoke<mgp_value *>(mgp_value_make_local_date_time, val);
}
mgp_value *value_make_duration(mgp_duration *val) { return MgInvoke<mgp_value *>(mgp_value_make_duration, val); }
// Copy value
// TODO: implement within MGP API
// with primitive types ({bool, int, double, string}), create a new identical value
// otherwise call mgp_##TYPE_copy and convert tpye
mgp_value *value_copy(mgp_value *val, mgp_memory *memory) { return MgInvoke<mgp_value *>(mgp_value_copy, val, memory); }
// Destroy value
void value_destroy(mgp_value *val) { mgp_value_destroy(val); }
// Get value of type
mgp_value_type value_get_type(mgp_value *val) { return MgInvoke<mgp_value_type>(mgp_value_get_type, val); }
bool value_get_bool(mgp_value *val) { return MgInvoke<int>(mgp_value_get_bool, val); }
int64_t value_get_int(mgp_value *val) { return MgInvoke<int64_t>(mgp_value_get_int, val); }
double value_get_double(mgp_value *val) { return MgInvoke<double>(mgp_value_get_double, val); }
const char *value_get_string(mgp_value *val) { return MgInvoke<const char *>(mgp_value_get_string, val); }
mgp_list *value_get_list(mgp_value *val) { return MgInvoke<mgp_list *>(mgp_value_get_list, val); }
mgp_map *value_get_map(mgp_value *val) { return MgInvoke<mgp_map *>(mgp_value_get_map, val); }
mgp_vertex *value_get_vertex(mgp_value *val) { return MgInvoke<mgp_vertex *>(mgp_value_get_vertex, val); }
mgp_edge *value_get_edge(mgp_value *val) { return MgInvoke<mgp_edge *>(mgp_value_get_edge, val); }
mgp_path *value_get_path(mgp_value *val) { return MgInvoke<mgp_path *>(mgp_value_get_path, val); }
mgp_date *value_get_date(mgp_value *val) { return MgInvoke<mgp_date *>(mgp_value_get_date, val); }
mgp_local_time *value_get_local_time(mgp_value *val) {
return MgInvoke<mgp_local_time *>(mgp_value_get_local_time, val);
}
mgp_local_date_time *value_get_local_date_time(mgp_value *val) {
return MgInvoke<mgp_local_date_time *>(mgp_value_get_local_date_time, val);
}
mgp_duration *value_get_duration(mgp_value *val) { return MgInvoke<mgp_duration *>(mgp_value_get_duration, val); }
// Check type of value
bool value_is_null(mgp_value *val) { return MgInvoke<int>(mgp_value_is_null, val); }
bool value_is_bool(mgp_value *val) { return MgInvoke<int>(mgp_value_is_bool, val); }
bool value_is_int(mgp_value *val) { return MgInvoke<int>(mgp_value_is_int, val); }
bool value_is_double(mgp_value *val) { return MgInvoke<int>(mgp_value_is_double, val); }
bool value_is_string(mgp_value *val) { return MgInvoke<int>(mgp_value_is_string, val); }
bool value_is_list(mgp_value *val) { return MgInvoke<int>(mgp_value_is_list, val); }
bool value_is_map(mgp_value *val) { return MgInvoke<int>(mgp_value_is_map, val); }
bool value_is_vertex(mgp_value *val) { return MgInvoke<int>(mgp_value_is_vertex, val); }
bool value_is_edge(mgp_value *val) { return MgInvoke<int>(mgp_value_is_edge, val); }
bool value_is_path(mgp_value *val) { return MgInvoke<int>(mgp_value_is_path, val); }
bool value_is_date(mgp_value *val) { return MgInvoke<int>(mgp_value_is_date, val); }
bool value_is_local_time(mgp_value *val) { return MgInvoke<int>(mgp_value_is_local_time, val); }
bool value_is_local_date_time(mgp_value *val) { return MgInvoke<int>(mgp_value_is_local_date_time, val); }
bool value_is_duration(mgp_value *val) { return MgInvoke<int>(mgp_value_is_duration, val); }
// Get type
mgp_type *type_any() { return MgInvoke<mgp_type *>(mgp_type_any); }
mgp_type *type_bool() { return MgInvoke<mgp_type *>(mgp_type_bool); }
mgp_type *type_string() { return MgInvoke<mgp_type *>(mgp_type_string); }
mgp_type *type_int() { return MgInvoke<mgp_type *>(mgp_type_int); }
mgp_type *type_float() { return MgInvoke<mgp_type *>(mgp_type_float); }
mgp_type *type_number() { return MgInvoke<mgp_type *>(mgp_type_number); }
mgp_type *type_list(mgp_type *element_type) { return MgInvoke<mgp_type *>(mgp_type_list, element_type); }
mgp_type *type_map() { return MgInvoke<mgp_type *>(mgp_type_map); }
mgp_type *type_node() { return MgInvoke<mgp_type *>(mgp_type_node); }
mgp_type *type_relationship() { return MgInvoke<mgp_type *>(mgp_type_relationship); }
mgp_type *type_path() { return MgInvoke<mgp_type *>(mgp_type_path); }
mgp_type *type_date() { return MgInvoke<mgp_type *>(mgp_type_date); }
mgp_type *type_local_time() { return MgInvoke<mgp_type *>(mgp_type_local_time); }
mgp_type *type_local_date_time() { return MgInvoke<mgp_type *>(mgp_type_local_date_time); }
mgp_type *type_duration() { return MgInvoke<mgp_type *>(mgp_type_duration); }
mgp_type *type_nullable(mgp_type *type) { return MgInvoke<mgp_type *>(mgp_type_nullable, type); }
// mgp_graph
bool graph_is_mutable(mgp_graph *graph) { return MgInvoke<int>(mgp_graph_is_mutable, graph); }
mgp_vertex *graph_create_vertex(mgp_graph *graph, mgp_memory *memory) {
return MgInvoke<mgp_vertex *>(mgp_graph_create_vertex, graph, memory);
}
void graph_delete_vertex(mgp_graph *graph, mgp_vertex *vertex) { MgInvokeVoid(mgp_graph_delete_vertex, graph, vertex); }
void graph_detach_delete_vertex(mgp_graph *graph, mgp_vertex *vertex) {
MgInvokeVoid(mgp_graph_detach_delete_vertex, graph, vertex);
}
mgp_edge *graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *to, mgp_edge_type type,
mgp_memory *memory) {
return MgInvoke<mgp_edge *>(mgp_graph_create_edge, graph, from, to, type, memory);
}
void graph_delete_edge(mgp_graph *graph, mgp_edge *edge) { MgInvokeVoid(mgp_graph_delete_edge, graph, edge); }
mgp_vertex *graph_get_vertex_by_id(mgp_graph *g, mgp_vertex_id id, mgp_memory *memory) {
return MgInvoke<mgp_vertex *>(mgp_graph_get_vertex_by_id, g, id, memory);
}
mgp_vertices_iterator *graph_iter_vertices(mgp_graph *g, mgp_memory *memory) {
return MgInvoke<mgp_vertices_iterator *>(mgp_graph_iter_vertices, g, memory);
}
// mgp_vertices_iterator
void vertices_iterator_destroy(mgp_vertices_iterator *it) { mgp_vertices_iterator_destroy(it); }
mgp_vertex *vertices_iterator_get(mgp_vertices_iterator *it) {
return MgInvoke<mgp_vertex *>(mgp_vertices_iterator_get, it);
}
mgp_vertex *vertices_iterator_next(mgp_vertices_iterator *it) {
return MgInvoke<mgp_vertex *>(mgp_vertices_iterator_next, it);
}
// mgp_edges_iterator
void edges_iterator_destroy(mgp_edges_iterator *it) { mgp_edges_iterator_destroy(it); }
mgp_edge *edges_iterator_get(mgp_edges_iterator *it) { return MgInvoke<mgp_edge *>(mgp_edges_iterator_get, it); }
mgp_edge *edges_iterator_next(mgp_edges_iterator *it) { return MgInvoke<mgp_edge *>(mgp_edges_iterator_next, it); }
// mgp_properties_iterator
void properties_iterator_destroy(mgp_properties_iterator *it) { mgp_properties_iterator_destroy(it); }
mgp_property *properties_iterator_get(mgp_properties_iterator *it) {
return MgInvoke<mgp_property *>(mgp_properties_iterator_get, it);
}
mgp_property *properties_iterator_next(mgp_properties_iterator *it) {
return MgInvoke<mgp_property *>(mgp_properties_iterator_next, it);
}
// Container {mgp_list, mgp_map} methods
// mgp_list
mgp_list *list_make_empty(size_t capacity, mgp_memory *memory) {
return MgInvoke<mgp_list *>(mgp_list_make_empty, capacity, memory);
}
mgp_list *list_copy(mgp_list *list, mgp_memory *memory) { return MgInvoke<mgp_list *>(mgp_list_copy, list, memory); }
void list_destroy(mgp_list *list) { mgp_list_destroy(list); }
void list_append(mgp_list *list, mgp_value *val) { MgInvokeVoid(mgp_list_append, list, val); }
void list_append_extend(mgp_list *list, mgp_value *val) { MgInvokeVoid(mgp_list_append_extend, list, val); }
size_t list_size(mgp_list *list) { return MgInvoke<size_t>(mgp_list_size, list); }
size_t list_capacity(mgp_list *list) { return MgInvoke<size_t>(mgp_list_capacity, list); }
mgp_value *list_at(mgp_list *list, size_t index) { return MgInvoke<mgp_value *>(mgp_list_at, list, index); }
// mgp_map
mgp_map *map_make_empty(mgp_memory *memory) { return MgInvoke<mgp_map *>(mgp_map_make_empty, memory); }
mgp_map *map_copy(mgp_map *map, mgp_memory *memory) { return MgInvoke<mgp_map *>(mgp_map_copy, map, memory); }
void map_destroy(mgp_map *map) { mgp_map_destroy(map); }
void map_insert(mgp_map *map, const char *key, mgp_value *value) { MgInvokeVoid(mgp_map_insert, map, key, value); }
size_t map_size(mgp_map *map) { return MgInvoke<size_t>(mgp_map_size, map); }
mgp_value *map_at(mgp_map *map, const char *key) { return MgInvoke<mgp_value *>(mgp_map_at, map, key); }
const char *map_item_key(mgp_map_item *item) { return MgInvoke<const char *>(mgp_map_item_key, item); }
mgp_value *map_item_value(mgp_map_item *item) { return MgInvoke<mgp_value *>(mgp_map_item_value, item); }
mgp_map_items_iterator *map_iter_items(mgp_map *map, mgp_memory *memory) {
return MgInvoke<mgp_map_items_iterator *>(mgp_map_iter_items, map, memory);
}
void map_items_iterator_destroy(mgp_map_items_iterator *it) { mgp_map_items_iterator_destroy(it); }
mgp_map_item *map_items_iterator_get(mgp_map_items_iterator *it) {
return MgInvoke<mgp_map_item *>(mgp_map_items_iterator_get, it);
}
mgp_map_item *map_items_iterator_next(mgp_map_items_iterator *it) {
return MgInvoke<mgp_map_item *>(mgp_map_items_iterator_next, it);
}
// mgp_vertex
mgp_vertex_id vertex_get_id(mgp_vertex *v) { return MgInvoke<mgp_vertex_id>(mgp_vertex_get_id, v); }
mgp_vertex *vertex_copy(mgp_vertex *v, mgp_memory *memory) {
return MgInvoke<mgp_vertex *>(mgp_vertex_copy, v, memory);
}
void vertex_destroy(mgp_vertex *v) { mgp_vertex_destroy(v); }
bool vertex_equal(mgp_vertex *v1, mgp_vertex *v2) { return MgInvoke<int>(mgp_vertex_equal, v1, v2); }
size_t vertex_labels_count(mgp_vertex *v) { return MgInvoke<size_t>(mgp_vertex_labels_count, v); }
mgp_label vertex_label_at(mgp_vertex *v, size_t index) { return MgInvoke<mgp_label>(mgp_vertex_label_at, v, index); }
bool vertex_has_label(mgp_vertex *v, mgp_label label) { return MgInvoke<int>(mgp_vertex_has_label, v, label); }
bool vertex_has_label_named(mgp_vertex *v, const char *label_name) {
return MgInvoke<int>(mgp_vertex_has_label_named, v, label_name);
}
void vertex_add_label(mgp_vertex *vertex, mgp_label label) { MgInvokeVoid(mgp_vertex_add_label, vertex, label); }
mgp_value *vertex_get_property(mgp_vertex *v, const char *property_name, mgp_memory *memory) {
return MgInvoke<mgp_value *>(mgp_vertex_get_property, v, property_name, memory);
}
mgp_properties_iterator *vertex_iter_properties(mgp_vertex *v, mgp_memory *memory) {
return MgInvoke<mgp_properties_iterator *>(mgp_vertex_iter_properties, v, memory);
}
mgp_edges_iterator *vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory) {
return MgInvoke<mgp_edges_iterator *>(mgp_vertex_iter_in_edges, v, memory);
}
mgp_edges_iterator *vertex_iter_out_edges(mgp_vertex *v, mgp_memory *memory) {
return MgInvoke<mgp_edges_iterator *>(mgp_vertex_iter_out_edges, v, memory);
}
// mgp_edge
mgp_edge_id edge_get_id(mgp_edge *e) { return MgInvoke<mgp_edge_id>(mgp_edge_get_id, e); }
mgp_edge *edge_copy(mgp_edge *e, mgp_memory *memory) { return MgInvoke<mgp_edge *>(mgp_edge_copy, e, memory); }
void edge_destroy(mgp_edge *e) { mgp_edge_destroy(e); }
bool edge_equal(mgp_edge *e1, mgp_edge *e2) { return MgInvoke<int>(mgp_edge_equal, e1, e2); }
mgp_edge_type edge_get_type(mgp_edge *e) { return MgInvoke<mgp_edge_type>(mgp_edge_get_type, e); }
mgp_vertex *edge_get_from(mgp_edge *e) { return MgInvoke<mgp_vertex *>(mgp_edge_get_from, e); }
mgp_vertex *edge_get_to(mgp_edge *e) { return MgInvoke<mgp_vertex *>(mgp_edge_get_to, e); }
mgp_value *edge_get_property(mgp_edge *e, const char *property_name, mgp_memory *memory) {
return MgInvoke<mgp_value *>(mgp_edge_get_property, e, property_name, memory);
}
mgp_properties_iterator *edge_iter_properties(mgp_edge *e, mgp_memory *memory) {
return MgInvoke<mgp_properties_iterator *>(mgp_edge_iter_properties, e, memory);
}
// mgp_path
mgp_path *path_make_with_start(mgp_vertex *vertex, mgp_memory *memory) {
return MgInvoke<mgp_path *>(mgp_path_make_with_start, vertex, memory);
}
mgp_path *path_copy(mgp_path *path, mgp_memory *memory) { return MgInvoke<mgp_path *>(mgp_path_copy, path, memory); }
void path_destroy(mgp_path *path) { mgp_path_destroy(path); }
void path_expand(mgp_path *path, mgp_edge *edge) { MgInvokeVoid(mgp_path_expand, path, edge); }
size_t path_size(mgp_path *path) { return MgInvoke<size_t>(mgp_path_size, path); }
mgp_vertex *path_vertex_at(mgp_path *path, size_t index) {
return MgInvoke<mgp_vertex *>(mgp_path_vertex_at, path, index);
}
mgp_edge *path_edge_at(mgp_path *path, size_t index) { return MgInvoke<mgp_edge *>(mgp_path_edge_at, path, index); }
bool path_equal(mgp_path *p1, mgp_path *p2) { return MgInvoke<int>(mgp_path_equal, p1, p2); }
// Temporal type {mgp_date, mgp_local_time, mgp_local_date_time, mgp_duration} methods
// mgp_date
mgp_date *date_from_string(const char *string, mgp_memory *memory) {
return MgInvoke<mgp_date *>(mgp_date_from_string, string, memory);
}
mgp_date *date_from_parameters(mgp_date_parameters *parameters, mgp_memory *memory) {
return MgInvoke<mgp_date *>(mgp_date_from_parameters, parameters, memory);
}
mgp_date *date_copy(mgp_date *date, mgp_memory *memory) { return MgInvoke<mgp_date *>(mgp_date_copy, date, memory); }
void date_destroy(mgp_date *date) { mgp_date_destroy(date); }
bool date_equal(mgp_date *first, mgp_date *second) { return MgInvoke<int>(mgp_date_equal, first, second); }
int date_get_year(mgp_date *date) { return MgInvoke<int>(mgp_date_get_year, date); }
int date_get_month(mgp_date *date) { return MgInvoke<int>(mgp_date_get_month, date); }
int date_get_day(mgp_date *date) { return MgInvoke<int>(mgp_date_get_day, date); }
int64_t date_timestamp(mgp_date *date) { return MgInvoke<int64_t>(mgp_date_timestamp, date); }
mgp_date *date_now(mgp_memory *memory) { return MgInvoke<mgp_date *>(mgp_date_now, memory); }
mgp_date *date_add_duration(mgp_date *date, mgp_duration *dur, mgp_memory *memory) {
return MgInvoke<mgp_date *>(mgp_date_add_duration, date, dur, memory);
}
mgp_date *date_sub_duration(mgp_date *date, mgp_duration *dur, mgp_memory *memory) {
return MgInvoke<mgp_date *>(mgp_date_sub_duration, date, dur, memory);
}
mgp_duration *date_diff(mgp_date *first, mgp_date *second, mgp_memory *memory) {
return MgInvoke<mgp_duration *>(mgp_date_diff, first, second, memory);
}
// mgp_local_time
mgp_local_time *local_time_from_string(const char *string, mgp_memory *memory) {
return MgInvoke<mgp_local_time *>(mgp_local_time_from_string, string, memory);
}
mgp_local_time *local_time_from_parameters(mgp_local_time_parameters *parameters, mgp_memory *memory) {
return MgInvoke<mgp_local_time *>(mgp_local_time_from_parameters, parameters, memory);
}
mgp_local_time *local_time_copy(mgp_local_time *local_time, mgp_memory *memory) {
return MgInvoke<mgp_local_time *>(mgp_local_time_copy, local_time, memory);
}
void local_time_destroy(mgp_local_time *local_time) { mgp_local_time_destroy(local_time); }
bool local_time_equal(mgp_local_time *first, mgp_local_time *second) {
return MgInvoke<int>(mgp_local_time_equal, first, second);
}
int local_time_get_hour(mgp_local_time *local_time) { return MgInvoke<int>(mgp_local_time_get_hour, local_time); }
int local_time_get_minute(mgp_local_time *local_time) { return MgInvoke<int>(mgp_local_time_get_minute, local_time); }
int local_time_get_second(mgp_local_time *local_time) { return MgInvoke<int>(mgp_local_time_get_second, local_time); }
int local_time_get_millisecond(mgp_local_time *local_time) {
return MgInvoke<int>(mgp_local_time_get_millisecond, local_time);
}
int local_time_get_microsecond(mgp_local_time *local_time) {
return MgInvoke<int>(mgp_local_time_get_microsecond, local_time);
}
int64_t local_time_timestamp(mgp_local_time *local_time) {
return MgInvoke<int64_t>(mgp_local_time_timestamp, local_time);
}
mgp_local_time *local_time_now(mgp_memory *memory) { return MgInvoke<mgp_local_time *>(mgp_local_time_now, memory); }
mgp_local_time *local_time_add_duration(mgp_local_time *local_time, mgp_duration *dur, mgp_memory *memory) {
return MgInvoke<mgp_local_time *>(mgp_local_time_add_duration, local_time, dur, memory);
}
mgp_local_time *local_time_sub_duration(mgp_local_time *local_time, mgp_duration *dur, mgp_memory *memory) {
return MgInvoke<mgp_local_time *>(mgp_local_time_sub_duration, local_time, dur, memory);
}
mgp_duration *local_time_diff(mgp_local_time *first, mgp_local_time *second, mgp_memory *memory) {
return MgInvoke<mgp_duration *>(mgp_local_time_diff, first, second, memory);
}
// mgp_local_date_time
mgp_local_date_time *local_date_time_from_string(const char *string, mgp_memory *memory) {
return MgInvoke<mgp_local_date_time *>(mgp_local_date_time_from_string, string, memory);
}
mgp_local_date_time *local_date_time_from_parameters(mgp_local_date_time_parameters *parameters, mgp_memory *memory) {
return MgInvoke<mgp_local_date_time *>(mgp_local_date_time_from_parameters, parameters, memory);
}
mgp_local_date_time *local_date_time_copy(mgp_local_date_time *local_date_time, mgp_memory *memory) {
return MgInvoke<mgp_local_date_time *>(mgp_local_date_time_copy, local_date_time, memory);
}
void local_date_time_destroy(mgp_local_date_time *local_date_time) { mgp_local_date_time_destroy(local_date_time); }
bool local_date_time_equal(mgp_local_date_time *first, mgp_local_date_time *second) {
return MgInvoke<int>(mgp_local_date_time_equal, first, second);
}
int local_date_time_get_year(mgp_local_date_time *local_date_time) {
return MgInvoke<int>(mgp_local_date_time_get_year, local_date_time);
}
int local_date_time_get_month(mgp_local_date_time *local_date_time) {
return MgInvoke<int>(mgp_local_date_time_get_month, local_date_time);
}
int local_date_time_get_day(mgp_local_date_time *local_date_time) {
return MgInvoke<int>(mgp_local_date_time_get_day, local_date_time);
}
int local_date_time_get_hour(mgp_local_date_time *local_date_time) {
return MgInvoke<int>(mgp_local_date_time_get_hour, local_date_time);
}
int local_date_time_get_minute(mgp_local_date_time *local_date_time) {
return MgInvoke<int>(mgp_local_date_time_get_minute, local_date_time);
}
int local_date_time_get_second(mgp_local_date_time *local_date_time) {
return MgInvoke<int>(mgp_local_date_time_get_second, local_date_time);
}
int local_date_time_get_millisecond(mgp_local_date_time *local_date_time) {
return MgInvoke<int>(mgp_local_date_time_get_millisecond, local_date_time);
}
int local_date_time_get_microsecond(mgp_local_date_time *local_date_time) {
return MgInvoke<int>(mgp_local_date_time_get_microsecond, local_date_time);
}
int64_t local_date_time_timestamp(mgp_local_date_time *local_date_time) {
return MgInvoke<int64_t>(mgp_local_date_time_timestamp, local_date_time);
}
mgp_local_date_time *local_date_time_now(mgp_memory *memory) {
return MgInvoke<mgp_local_date_time *>(mgp_local_date_time_now, memory);
}
mgp_local_date_time *local_date_time_add_duration(mgp_local_date_time *local_date_time, mgp_duration *dur,
mgp_memory *memory) {
return MgInvoke<mgp_local_date_time *>(mgp_local_date_time_add_duration, local_date_time, dur, memory);
}
mgp_local_date_time *local_date_time_sub_duration(mgp_local_date_time *local_date_time, mgp_duration *dur,
mgp_memory *memory) {
return MgInvoke<mgp_local_date_time *>(mgp_local_date_time_sub_duration, local_date_time, dur, memory);
}
mgp_duration *local_date_time_diff(mgp_local_date_time *first, mgp_local_date_time *second, mgp_memory *memory) {
return MgInvoke<mgp_duration *>(mgp_local_date_time_diff, first, second, memory);
}
// mgp_duration
mgp_duration *duration_from_string(const char *string, mgp_memory *memory) {
return MgInvoke<mgp_duration *>(mgp_duration_from_string, string, memory);
}
mgp_duration *duration_from_parameters(mgp_duration_parameters *parameters, mgp_memory *memory) {
return MgInvoke<mgp_duration *>(mgp_duration_from_parameters, parameters, memory);
}
mgp_duration *duration_from_microseconds(int64_t microseconds, mgp_memory *memory) {
return MgInvoke<mgp_duration *>(mgp_duration_from_microseconds, microseconds, memory);
}
mgp_duration *duration_copy(mgp_duration *duration, mgp_memory *memory) {
return MgInvoke<mgp_duration *>(mgp_duration_copy, duration, memory);
}
void duration_destroy(mgp_duration *duration) { mgp_duration_destroy(duration); }
int64_t duration_get_microseconds(mgp_duration *duration) {
return MgInvoke<int64_t>(mgp_duration_get_microseconds, duration);
}
bool duration_equal(mgp_duration *first, mgp_duration *second) {
return MgInvoke<int>(mgp_duration_equal, first, second);
}
mgp_duration *duration_neg(mgp_duration *duration, mgp_memory *memory) {
return MgInvoke<mgp_duration *>(mgp_duration_neg, duration, memory);
}
mgp_duration *duration_add(mgp_duration *first, mgp_duration *second, mgp_memory *memory) {
return MgInvoke<mgp_duration *>(mgp_duration_add, first, second, memory);
}
mgp_duration *duration_sub(mgp_duration *first, mgp_duration *second, mgp_memory *memory) {
return MgInvoke<mgp_duration *>(mgp_duration_sub, first, second, memory);
}
// Procedure
mgp_proc *module_add_read_procedure(mgp_module *module, const char *name, mgp_proc_cb cb) {
return MgInvoke<mgp_proc *>(mgp_module_add_read_procedure, module, name, cb);
}
mgp_proc *module_add_write_procedure(mgp_module *module, const char *name, mgp_proc_cb cb) {
return MgInvoke<mgp_proc *>(mgp_module_add_write_procedure, module, name, cb);
}
void proc_add_arg(mgp_proc *proc, const char *name, mgp_type *type) {
MgInvokeVoid(mgp_proc_add_arg, proc, name, type);
}
void proc_add_opt_arg(mgp_proc *proc, const char *name, mgp_type *type, mgp_value *default_value) {
MgInvokeVoid(mgp_proc_add_opt_arg, proc, name, type, default_value);
}
void proc_add_result(mgp_proc *proc, const char *name, mgp_type *type) {
MgInvokeVoid(mgp_proc_add_result, proc, name, type);
}
void proc_add_deprecated_result(mgp_proc *proc, const char *name, mgp_type *type) {
MgInvokeVoid(mgp_proc_add_deprecated_result, proc, name, type);
}
bool must_abort(mgp_graph *graph) { return mgp_must_abort(graph); }
// mgp_result
void result_set_error_msg(mgp_result *res, const char *error_msg) {
MgInvokeVoid(mgp_result_set_error_msg, res, error_msg);
}
mgp_result_record *result_new_record(mgp_result *res) {
return MgInvoke<mgp_result_record *>(mgp_result_new_record, res);
}
void result_record_insert(mgp_result_record *record, const char *field_name, mgp_value *val) {
MgInvokeVoid(mgp_result_record_insert, record, field_name, val);
}
// Function
mgp_func *module_add_function(mgp_module *module, const char *name, mgp_func_cb cb) {
return MgInvoke<mgp_func *>(mgp_module_add_function, module, name, cb);
}
void func_add_arg(mgp_func *func, const char *name, mgp_type *type) {
MgInvokeVoid(mgp_func_add_arg, func, name, type);
}
void func_add_opt_arg(mgp_func *func, const char *name, mgp_type *type, mgp_value *default_value) {
MgInvokeVoid(mgp_func_add_opt_arg, func, name, type, default_value);
}
void func_result_set_error_msg(mgp_func_result *res, const char *msg, mgp_memory *memory) {
MgInvokeVoid(mgp_func_result_set_error_msg, res, msg, memory);
}
void func_result_set_value(mgp_func_result *res, mgp_value *value, mgp_memory *memory) {
MgInvokeVoid(mgp_func_result_set_value, res, value, memory);
}
} // namespace mgp

57
include/mg_exceptions.hpp Normal file
View File

@@ -0,0 +1,57 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <exception>
#include <iostream>
namespace mg_exception {
struct NotEnoughMemoryException : public std::exception {
const char *what() const throw() { return "Not enough memory!"; }
};
struct UnknownException : public std::exception {
const char *what() const throw() { return "Unknown exception!"; }
};
struct AllocationException : public std::exception {
const char *what() const throw() { return "Could not allocate memory!"; }
};
struct InsufficientBufferException : public std::exception {
const char *what() const throw() { return "Buffer is not sufficient to process procedure!"; }
};
struct OutOfRangeException : public std::exception {
const char *what() const throw() { return "Index out of range!"; }
};
struct LogicException : public std::exception {
const char *what() const throw() { return "Logic exception, check the procedure signature!"; }
};
struct DeletedObjectException : public std::exception {
const char *what() const throw() { return "Object is deleted!"; }
};
struct InvalidArgumentException : public std::exception {
const char *what() const throw() { return "Invalid argument!"; }
};
struct InvalidIDException : public std::exception {
const char *what() const throw() { return "Invalid ID!"; }
};
struct KeyAlreadyExistsException : public std::exception {
const char *what() const throw() { return "Key you are trying to set already exists!"; }
};
struct ImmutableObjectException : public std::exception {
const char *what() const throw() { return "Object you are trying to change is immutable!"; }
};
struct ValueConversionException : public std::exception {
const char *what() const throw() { return "Error in value conversion!"; }
};
struct SerializationException : public std::exception {
const char *what() const throw() { return "Error in serialization!"; }
};
} // namespace mg_exception

View File

@@ -37,12 +37,19 @@ extern "C" {
/// All functions return an error code that can be used to figure out whether the API call was successful or not. In
/// case of failure, the specific error code can be used to identify the reason of the failure.
MGP_ENUM_CLASS MGP_NODISCARD mgp_error{
MGP_ERROR_NO_ERROR, MGP_ERROR_UNKNOWN_ERROR,
MGP_ERROR_UNABLE_TO_ALLOCATE, MGP_ERROR_INSUFFICIENT_BUFFER,
MGP_ERROR_OUT_OF_RANGE, MGP_ERROR_LOGIC_ERROR,
MGP_ERROR_DELETED_OBJECT, MGP_ERROR_INVALID_ARGUMENT,
MGP_ERROR_KEY_ALREADY_EXISTS, MGP_ERROR_IMMUTABLE_OBJECT,
MGP_ERROR_VALUE_CONVERSION, MGP_ERROR_SERIALIZATION_ERROR,
MGP_ERROR_NO_ERROR,
MGP_ERROR_UNKNOWN_ERROR,
MGP_ERROR_UNABLE_TO_ALLOCATE,
MGP_ERROR_INSUFFICIENT_BUFFER,
MGP_ERROR_OUT_OF_RANGE,
MGP_ERROR_LOGIC_ERROR,
MGP_ERROR_DELETED_OBJECT,
MGP_ERROR_INVALID_ARGUMENT,
MGP_ERROR_KEY_ALREADY_EXISTS,
MGP_ERROR_IMMUTABLE_OBJECT,
MGP_ERROR_VALUE_CONVERSION,
MGP_ERROR_SERIALIZATION_ERROR,
MGP_ERROR_AUTHORIZATION_ERROR,
};
///@}
@@ -164,6 +171,8 @@ enum mgp_value_type {
MGP_VALUE_TYPE_DURATION,
};
enum mgp_error mgp_value_copy(struct mgp_value *val, struct mgp_memory *memory, struct mgp_value **result);
/// Free the memory used by the given mgp_value instance.
void mgp_value_destroy(struct mgp_value *val);
@@ -399,6 +408,8 @@ enum mgp_error mgp_value_get_duration(struct mgp_value *val, struct mgp_duration
/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_list.
enum mgp_error mgp_list_make_empty(size_t capacity, struct mgp_memory *memory, struct mgp_list **result);
enum mgp_error mgp_list_copy(struct mgp_list *list, struct mgp_memory *memory, struct mgp_list **result);
/// Free the memory used by the given mgp_list and contained elements.
void mgp_list_destroy(struct mgp_list *list);
@@ -437,6 +448,8 @@ enum mgp_error mgp_list_at(struct mgp_list *list, size_t index, struct mgp_value
/// mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE is returned if unable to allocate a mgp_map.
enum mgp_error mgp_map_make_empty(struct mgp_memory *memory, struct mgp_map **result);
enum mgp_error mgp_map_copy(struct mgp_map *map, struct mgp_memory *memory, struct mgp_map **result);
/// Free the memory used by the given mgp_map and contained items.
void mgp_map_destroy(struct mgp_map *map);
@@ -1292,6 +1305,12 @@ struct mgp_proc;
/// Describes a Memgraph magic function.
struct mgp_func;
/// All available log levels that can be used in mgp_log function
MGP_ENUM_CLASS mgp_log_level{
MGP_LOG_LEVEL_TRACE, MGP_LOG_LEVEL_DEBUG, MGP_LOG_LEVEL_INFO,
MGP_LOG_LEVEL_WARN, MGP_LOG_LEVEL_ERROR, MGP_LOG_LEVEL_CRITICAL,
};
/// Entry-point for a query module read procedure, invoked through openCypher.
///
/// Passed in arguments will not live longer than the callback's execution.
@@ -1386,6 +1405,9 @@ enum mgp_error mgp_proc_add_result(struct mgp_proc *proc, const char *name, stru
/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid result name.
/// RETURN mgp_error::MGP_ERROR_LOGIC_ERROR if a result field with the same name was already added.
enum mgp_error mgp_proc_add_deprecated_result(struct mgp_proc *proc, const char *name, struct mgp_type *type);
/// Log a message on a certain level.
enum mgp_error mgp_log(enum mgp_log_level log_level, const char *output);
///@}
/// @name Execution
@@ -1512,6 +1534,10 @@ enum mgp_error mgp_module_add_transformation(struct mgp_module *module, const ch
///
///@{
/// State of the database that is exposed to magic functions. Currently it is unused, but it enables extending the
/// functionalities of magic functions in future without breaking the API.
struct mgp_func_context;
/// Add a required argument to a function.
///
/// The order of the added arguments corresponds to the signature of the openCypher function.

2800
include/mgp.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -134,6 +134,15 @@ class SerializationError(_mgp.SerializationError):
pass
class AuthorizationError(_mgp.AuthorizationError):
"""
Signals that the user doesn't have sufficient permissions to perform
procedure call.
"""
pass
class Label:
"""Label of a `Vertex`."""
@@ -146,7 +155,7 @@ class Label:
def name(self) -> str:
"""
Get the name of the label.
Returns:
A string that represents the name of the label.
@@ -195,20 +204,20 @@ class Properties:
def get(self, property_name: str, default=None) -> object:
"""
Get the value of a property with the given name or return default value.
Args:
Args:
property_name: String that represents property name.
default: Default value return if there is no property.
Returns:
Any object value that property under `property_name` has or default value otherwise.
Raises:
InvalidContextError: If `edge` or `vertex` is out of context.
Returns:
Any object value that property under `property_name` has or default value otherwise.
Raises:
InvalidContextError: If `edge` or `vertex` is out of context.
UnableToAllocateError: If unable to allocate a `mgp.Value`.
DeletedObjectError: If the `object` has been deleted.
Examples:
Examples:
```
vertex.properties.get(property_name)
edge.properties.get(property_name)
@@ -227,23 +236,23 @@ class Properties:
Set the value of the property. When the value is `None`, then the
property is removed.
Args:
property_name: String that represents property name.
Args:
property_name: String that represents property name.
value: Object that represents value to be set.
Raises:
Raises:
UnableToAllocateError: If unable to allocate memory for storing the property.
ImmutableObjectError: If the object is immutable.
DeletedObjectError: If the object has been deleted.
SerializationError: If the object has been modified by another transaction.
ValueConversionError: If `value` is vertex, edge or path.
Examples:
```
vertex.properties.set(property_name, value)
edge.properties.set(property_name, value)
```
"""
self[property_name] = value
@@ -252,15 +261,15 @@ class Properties:
Iterate over the properties. Doesnt return a dynamic view of the properties but copies the
current properties.
Returns:
Returns:
Iterable `Property` of names and values.
Raises:
InvalidContextError: If edge or vertex is out of context.
Raises:
InvalidContextError: If edge or vertex is out of context.
UnableToAllocateError: If unable to allocate an iterator.
DeletedObjectError: If the object has been deleted.
Examples:
Examples:
```
items = vertex.properties.items()
for it in items:
@@ -290,15 +299,15 @@ class Properties:
Iterate over property names. Doesnt return a dynamic view of the property names but copies the
name of the current properties.
Returns:
Returns:
Iterable list of strings that represent names/keys of properties.
Raises:
Raises:
InvalidContextError: If edge or vertex is out of context.
UnableToAllocateError: If unable to allocate an iterator.
DeletedObjectError: If the object has been deleted.
Examples:
Examples:
```
graph.vertex.properties.keys()
graph.edge.properties.keys()
@@ -314,20 +323,20 @@ class Properties:
Iterate over property values. Doesnt return a dynamic view of the property values but copies the
value of the current properties.
Returns:
Returns:
Iterable list of property values.
Raises:
Raises:
InvalidContextError: If edge or vertex is out of context.
UnableToAllocateError: If unable to allocate an iterator.
DeletedObjectError: If the object has been deleted.
Examples:
Examples:
```
vertex.properties.values()
edge.properties.values()
```
"""
if not self._vertex_or_edge.is_valid():
raise InvalidContextError()
@@ -338,15 +347,15 @@ class Properties:
"""
Get the number of properties.
Returns:
Returns:
A number of properties on vertex or edge.
Raises:
Raises:
InvalidContextError: If edge or vertex is out of context.
UnableToAllocateError: If unable to allocate an iterator.
DeletedObjectError: If the object has been deleted.
Examples:
Examples:
```
len(vertex.properties)
len(edge.properties)
@@ -363,15 +372,15 @@ class Properties:
"""
Iterate over property names.
Returns:
Returns:
Iterable list of strings that represent names of properties.
Raises:
Raises:
InvalidContextError: If edge or vertex is out of context.
UnableToAllocateError: If unable to allocate an iterator.
DeletedObjectError: If the object has been deleted.
Examples:
Examples:
```
iter(vertex.properties)
iter(edge.properties)
@@ -386,24 +395,24 @@ class Properties:
def __getitem__(self, property_name: str) -> object:
"""
Get the value of a property with the given name or raise KeyError.
Args:
Args:
property_name: String that represents property name.
Returns:
Returns:
Any value that property under property_name have.
Raises:
Raises:
InvalidContextError: If edge or vertex is out of context.
UnableToAllocateError: If unable to allocate a mgp.Value.
DeletedObjectError: If the object has been deleted.
Examples:
Examples:
```
vertex.properties[property_name]
edge.properties[property_name]
```
"""
if not self._vertex_or_edge.is_valid():
raise InvalidContextError()
@@ -417,18 +426,18 @@ class Properties:
Set the value of the property. When the value is `None`, then the
property is removed.
Args:
Args:
property_name: String that represents property name.
value: Object that represents value to be set.
Raises:
UnableToAllocateError: If unable to allocate memory for storing the property.
ImmutableObjectError: If the object is immutable.
DeletedObjectError: If the object has been deleted.
SerializationError: If the object has been modified by another transaction.
ValueConversionError: If `value` is vertex, edge or path.
Examples:
Examples:
```
vertex.properties[property_name] = value
edge.properties[property_name] = value
@@ -443,18 +452,18 @@ class Properties:
"""
Check if there is a property with the given name.
Args:
Args:
property_name: String that represents property name
Returns:
Bool value that depends if there is with a given name.
Raises:
Bool value that depends if there is with a given name.
Raises:
InvalidContextError: If edge or vertex is out of context.
UnableToAllocateError: If unable to allocate a mgp.Value.
DeletedObjectError: If the object has been deleted.
Examples:
Examples:
```
if property_name in vertex.properties:
```
@@ -483,7 +492,7 @@ class EdgeType:
def name(self) -> str:
"""
Get the name of EdgeType.
Returns:
A string that represents the name of EdgeType.
@@ -512,7 +521,7 @@ class Edge:
Access to an Edge is only valid during a single execution of a procedure in
a query. You should not globally store an instance of an Edge. Using an
invalid Edge instance will raise InvalidContextError.
"""
__slots__ = ("_edge",)
@@ -532,10 +541,10 @@ class Edge:
def is_valid(self) -> bool:
"""
Check if `edge` is in a valid context and may be used.
Returns:
A `bool` value depends on if the `edge` is in a valid context.
A `bool` value depends on if the `edge` is in a valid context.
Examples:
```edge.is_valid()```
@@ -543,15 +552,15 @@ class Edge:
return self._edge.is_valid()
def underlying_graph_is_mutable(self) -> bool:
"""
Check if the `graph` can be modified.
"""
Check if the `graph` can be modified.
Returns:
Returns:
A `bool` value depends on if the `graph` is mutable.
Examples:
Examples:
```edge.underlying_graph_is_mutable()```
"""
if not self.is_valid():
raise InvalidContextError()
@@ -564,10 +573,10 @@ class Edge:
Returns:
`EdgeId` represents ID of the edge.
Raises:
Raises:
InvalidContextError: If edge is out of context.
Examples:
```edge.id```
"""
@@ -581,12 +590,12 @@ class Edge:
Get the type of edge.
Returns:
`EdgeType` describing the type of edge.
`EdgeType` describing the type of edge.
Raises:
InvalidContextError: If edge is out of context.
Examples:
Examples:
```edge.type```
"""
if not self.is_valid():
@@ -598,10 +607,10 @@ class Edge:
"""
Get the source vertex.
Returns:
Returns:
`Vertex` from where the edge is directed.
Raises:
Raises:
InvalidContextError: If edge is out of context.
Examples:
@@ -615,14 +624,14 @@ class Edge:
def to_vertex(self) -> "Vertex":
"""
Get the destination vertex.
Returns:
Returns:
`Vertex` to where the edge is directed.
Raises:
Raises:
InvalidContextError: If edge is out of context.
Examples:
Examples:
```edge.to_vertex```
"""
if not self.is_valid():
@@ -635,7 +644,7 @@ class Edge:
Get the properties of the edge.
Returns:
All `Properties` of edge.
All `Properties` of edge.
Raises:
InvalidContextError: If edge is out of context.
@@ -692,9 +701,9 @@ class Vertex:
Checks if `Vertex` is in valid context and may be used.
Returns:
A `bool` value depends on if the `Vertex` is in a valid context.
Examples:
A `bool` value depends on if the `Vertex` is in a valid context.
Examples:
```vertex.is_valid()```
"""
@@ -702,14 +711,14 @@ class Vertex:
def underlying_graph_is_mutable(self) -> bool:
"""
Check if the `graph` is mutable.
Check if the `graph` is mutable.
Returns:
Returns:
A `bool` value depends on if the `graph` is mutable.
Examples:
Examples:
```vertex.underlying_graph_is_mutable()```
"""
if not self.is_valid():
raise InvalidContextError()
@@ -722,10 +731,10 @@ class Vertex:
Returns:
`VertexId` represents ID of the vertex.
Raises:
Raises:
InvalidContextError: If vertex is out of context.
Examples:
```vertex.id```
"""
@@ -738,15 +747,15 @@ class Vertex:
"""
Get the labels of the vertex.
Returns:
Returns:
A tuple of `Label` representing vertex Labels
Raises:
Raises:
InvalidContextError: If vertex is out of context.
OutOfRangeError: If some of the labels are removed while collecting the labels.
DeletedObjectError: If `Vertex` has been deleted.
Examples:
Examples:
```vertex.labels```
"""
if not self.is_valid():
@@ -757,17 +766,17 @@ class Vertex:
"""
Add the label to the vertex.
Args:
label: String label to be added.
Raises:
Args:
label: String label to be added.
Raises:
InvalidContextError: If `Vertex` is out of context.
UnableToAllocateError: If unable to allocate memory for storing the label.
ImmutableObjectError: If `Vertex` is immutable.
DeletedObjectError: If `Vertex` has been deleted.
SerializationError: If `Vertex` has been modified by another transaction.
Examples:
Examples:
```vertex.add_label(label)```
"""
if not self.is_valid():
@@ -778,15 +787,15 @@ class Vertex:
"""
Remove the label from the vertex.
Args:
label: String label to be deleted
Raises:
Args:
label: String label to be deleted
Raises:
InvalidContextError: If `Vertex` is out of context.
ImmutableObjectError: If `Vertex` is immutable.
DeletedObjectError: If `Vertex` has been deleted.
SerializationError: If `Vertex` has been modified by another transaction.
Examples:
Examples:
```vertex.remove_label(label)```
"""
if not self.is_valid():
@@ -798,13 +807,13 @@ class Vertex:
"""
Get the properties of the vertex.
Returns:
Returns:
`Properties` on a current vertex.
Raises:
Raises:
InvalidContextError: If `Vertex` is out of context.
Examples:
Examples:
```vertex.properties```
"""
if not self.is_valid():
@@ -820,13 +829,13 @@ class Vertex:
Returns:
Iterable list of `Edge` objects that are directed in towards the current vertex.
Raises:
Raises:
InvalidContextError: If `Vertex` is out of context.
UnableToAllocateError: If unable to allocate an iterator.
DeletedObjectError: If `Vertex` has been deleted.
Examples:
Examples:
```for edge in vertex.in_edges:```
"""
if not self.is_valid():
@@ -850,12 +859,12 @@ class Vertex:
Returns:
Iterable list of `Edge` objects that are directed out of the current vertex.
Raises:
Raises:
InvalidContextError: If `Vertex` is out of context.
UnableToAllocateError: If unable to allocate an iterator.
DeletedObjectError: If `Vertex` has been deleted.
Examples:
Examples:
```for edge in vertex.out_edges:```
"""
if not self.is_valid():
@@ -888,7 +897,7 @@ class Path:
def __init__(self, starting_vertex_or_path: typing.Union[_mgp.Path, Vertex]):
"""Initialize with a starting Vertex.
Raises:
Raises:
InvalidContextError: If passed in Vertex is invalid.
UnableToAllocateError: If cannot allocate a path.
"""
@@ -932,10 +941,10 @@ class Path:
def is_valid(self) -> bool:
"""
Check if `Path` is in valid context and may be used.
Returns:
A `bool` value depends on if the `Path` is in a valid context.
A `bool` value depends on if the `Path` is in a valid context.
Examples:
```path.is_valid()```
"""
@@ -948,15 +957,15 @@ class Path:
The last vertex on the path will become the other endpoint of the given
edge, as continued from the current last vertex.
Args:
Args:
edge: `Edge` that is added to the path
Raises:
Raises:
InvalidContextError: If using an invalid `Path` instance or if passed in `Edge` is invalid.
LogicErrorError: If the current last vertex in the path is not part of the given edge.
UnableToAllocateError: If unable to allocate memory for path extension.
Examples:
Examples:
```path.expand(edge)```
"""
if not isinstance(edge, Edge):
@@ -973,14 +982,14 @@ class Path:
"""
Vertices are ordered from the start to the end of the path.
Returns:
A tuple of `Vertex` objects order from start to end of the path.
Returns:
A tuple of `Vertex` objects order from start to end of the path.
Raises:
InvalidContextError: If using an invalid Path instance.
Examples:
```path.vertices```
```path.vertices```
"""
if not self.is_valid():
raise InvalidContextError()
@@ -994,11 +1003,11 @@ class Path:
"""
Edges are ordered from the start to the end of the path.
Returns:
Returns:
A tuple of `Edge` objects order from start to end of the path
Raises:
Raises:
InvalidContextError: If using an invalid `Path` instance.
Examples:
Examples:
```path.edges```
"""
if not self.is_valid():
@@ -1039,10 +1048,10 @@ class Vertices:
def is_valid(self) -> bool:
"""
Check if `Vertices` is in valid context and may be used.
Returns:
A `bool` value depends on if the `Vertices` is in valid context.
A `bool` value depends on if the `Vertices` is in valid context.
Examples:
```vertices.is_valid()```
"""
@@ -1052,14 +1061,14 @@ class Vertices:
"""
Iterate over vertices.
Returns:
Iterable list of `Vertex` objects.
Returns:
Iterable list of `Vertex` objects.
Raises:
Raises:
InvalidContextError: If context is invalid.
UnableToAllocateError: If unable to allocate an iterator or a vertex.
Examples:
Examples:
```
for vertex in graph.vertices:
```
@@ -1080,18 +1089,18 @@ class Vertices:
def __contains__(self, vertex):
"""
Check if Vertices contain the given vertex.
Check if Vertices contain the given vertex.
Args:
Args:
vertex: `Vertex` to be checked if it is a part of graph `Vertices`.
Returns:
Bool value depends if there is `Vertex` in graph `Vertices`.
Bool value depends if there is `Vertex` in graph `Vertices`.
Raises:
UnableToAllocateError: If unable to allocate the vertex.
Examples:
Examples:
```if vertex in graph.vertices:```
"""
try:
@@ -1104,14 +1113,14 @@ class Vertices:
"""
Get the number of vertices.
Returns:
Returns:
A number of vertices in the graph.
Raises:
Raises:
InvalidContextError: If context is invalid.
UnableToAllocateError: If unable to allocate an iterator or a vertex.
Examples:
Examples:
```len(graph.vertices)```
"""
if not self._len:
@@ -1140,9 +1149,9 @@ class Graph:
Check if `graph` is in a valid context and may be used.
Returns:
A `bool` value depends on if the `graph` is in a valid context.
Examples:
A `bool` value depends on if the `graph` is in a valid context.
Examples:
```graph.is_valid()```
"""
@@ -1169,7 +1178,7 @@ class Graph:
Examples:
```graph.get_vertex_by_id(vertex_id)```
"""
if not self.is_valid():
raise InvalidContextError()
@@ -1207,11 +1216,11 @@ class Graph:
def is_mutable(self) -> bool:
"""
Check if the graph is mutable. Thus it can be used to modify vertices and edges.
Returns:
A `bool` value that depends if the graph is mutable or not.
Examples:
Returns:
A `bool` value that depends if the graph is mutable or not.
Examples:
```graph.is_mutable()```
"""
if not self.is_valid():
@@ -1222,14 +1231,14 @@ class Graph:
"""
Create an empty vertex.
Returns:
Created `Vertex`.
Returns:
Created `Vertex`.
Raises:
Raises:
ImmutableObjectError: If `graph` is immutable.
UnableToAllocateError: If unable to allocate a vertex.
Examples:
Examples:
Creating an empty vertex.
```vertex = graph.create_vertex()```
@@ -1249,7 +1258,7 @@ class Graph:
LogicErrorError: If `vertex` has edges.
SerializationError: If `vertex` has been modified by
another transaction.
Examples:
Examples:
```graph.delete_vertex(vertex)```
"""
@@ -1260,14 +1269,14 @@ class Graph:
def detach_delete_vertex(self, vertex: Vertex) -> None:
"""
Delete a vertex and all of its edges.
Args:
Args:
vertex: `Vertex` to be deleted with all of its edges
Raises:
Raises:
ImmutableObjectError: If `graph` is immutable.
SerializationError: If `vertex` has been modified by another transaction.
Examples:
Examples:
```graph.detach_delete_vertex(vertex)```
"""
if not self.is_valid():
@@ -1277,18 +1286,18 @@ class Graph:
def create_edge(self, from_vertex: Vertex, to_vertex: Vertex, edge_type: EdgeType) -> None:
"""
Create an edge.
Args:
from_vertex: `Vertex` from where edge is directed.
to_vertex: `Vertex' to where edge is directed.
edge_type: `EdgeType` defines the type of edge.
Args:
from_vertex: `Vertex` from where edge is directed.
to_vertex: `Vertex' to where edge is directed.
edge_type: `EdgeType` defines the type of edge.
Raises:
ImmutableObjectError: If `graph` is immutable.
UnableToAllocateError: If unable to allocate an edge.
DeletedObjectError: If `from_vertex` or `to_vertex` has been deleted.
SerializationError: If `from_vertex` or `to_vertex` has been modified by another transaction.
Examples:
Examples:
```graph.create_edge(from_vertex, vertex, edge_type)```
"""
if not self.is_valid():
@@ -1301,8 +1310,8 @@ class Graph:
Args:
edge: `Edge` to be deleted
Raises:
Raises:
ImmutableObjectError if `graph` is immutable.
Raise SerializationError if `edge`, its source or destination vertex has been modified by another transaction.
"""
@@ -1337,15 +1346,15 @@ class ProcCtx:
@property
def graph(self) -> Graph:
"""
Access to `Graph` object.
Returns:
Graph object.
Access to `Graph` object.
Raises:
Returns:
Graph object.
Raises:
InvalidContextError: If context is invalid.
Examples:
Examples:
```context.graph```
"""
if not self.is_valid():
@@ -1969,6 +1978,8 @@ def _wrap_exceptions():
raise ValueConversionError(e)
except _mgp.SerializationError as e:
raise SerializationError(e)
except _mgp.AuthorizationError as e:
raise AuthorizationError(e)
return wrapped_func
@@ -2004,4 +2015,82 @@ def _wrap_exceptions():
setattr(module, name, wrap_function(obj))
class Logger:
"""Represents a Logger through which it is possible
to send logs via API to the graph database.
The best way to use this Logger is to have one per query module."""
__slots__ = ("_logger",)
def __init__(self):
self._logger = _mgp._LOGGER
def info(self, out: str) -> None:
"""
Log message on INFO level..
Args:
out: String message to be logged.
Examples:
```logger.info("Hello from query module.")```
"""
self._logger.info(out)
def warning(self, out: str) -> None:
"""
Log message on WARNING level..
Args:
out: String message to be logged.
Examples:
```logger.warning("Hello from query module.")```
"""
self._logger.warning(out)
def critical(self, out: str) -> None:
"""
Log message on CRITICAL level..
Args:
out: String message to be logged.
Examples:
```logger.critical("Hello from query module.")```
"""
self._logger.critical(out)
def error(self, out: str) -> None:
"""
Log message on ERROR level..
Args:
out: String message to be logged.
Examples:
```logger.error("Hello from query module.")```
"""
self._logger.error(out)
def trace(self, out: str) -> None:
"""
Log message on TRACE level..
Args:
out: String message to be logged.
Examples:
```logger.trace("Hello from query module.")```
"""
self._logger.trace(out)
def debug(self, out: str) -> None:
"""
Log message on DEBUG level..
Args:
out: String message to be logged.
Examples:
```logger.debug("Hello from query module.")```
"""
self._logger.debug(out)
_wrap_exceptions()

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: 2026-27-04
CHANGE DATE: 2026-20-09
CHANGE LICENSE: Apache License, Version 2.0
For information about alternative licensing arrangements, please visit: https://memgraph.com/legal.

View File

@@ -8,29 +8,40 @@ disallow_in_source_build()
# Everything that is installed here, should be under the "query_modules" component.
set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME "query_modules")
add_library(example SHARED example.c)
target_include_directories(example PRIVATE ${CMAKE_SOURCE_DIR}/include)
target_compile_options(example PRIVATE -Wall)
# Strip the library in release build.
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
if (lower_build_type STREQUAL "release")
add_custom_command(TARGET example POST_BUILD
COMMAND strip -s $<TARGET_FILE:example>
COMMENT "Stripping symbols and sections from example module")
endif()
install(PROGRAMS $<TARGET_FILE:example>
add_library(example_c SHARED example.c)
target_include_directories(example_c PRIVATE ${CMAKE_SOURCE_DIR}/include)
target_compile_options(example_c PRIVATE -Wall)
# Strip C example in release build.
if (lower_build_type STREQUAL "release")
add_custom_command(TARGET example_c POST_BUILD
COMMAND strip -s $<TARGET_FILE:example_c>
COMMENT "Stripping symbols and sections from the C example module")
endif()
install(PROGRAMS $<TARGET_FILE:example_c>
DESTINATION lib/memgraph/query_modules
RENAME example.so)
RENAME example_c.so)
# Also install the source of the example, so user can read it.
install(FILES example.c DESTINATION lib/memgraph/query_modules/src)
# Install the Python example
install(FILES example.py DESTINATION lib/memgraph/query_modules RENAME py_example.py)
add_library(example_cpp SHARED example.cpp)
target_include_directories(example_cpp PRIVATE ${CMAKE_SOURCE_DIR}/include)
target_compile_options(example_cpp PRIVATE -Wall)
# Strip C++ example in release build.
if (lower_build_type STREQUAL "release")
add_custom_command(TARGET example_cpp POST_BUILD
COMMAND strip -s $<TARGET_FILE:example_cpp>
COMMENT "Stripping symbols and sections from the C++ example module")
endif()
install(PROGRAMS $<TARGET_FILE:example_cpp>
DESTINATION lib/memgraph/query_modules
RENAME example_cpp.so)
# Also install the source of the example, so user can read it.
install(FILES example.cpp DESTINATION lib/memgraph/query_modules/src)
# Install the Python modules
# Install the Python example and modules
install(FILES example.py DESTINATION lib/memgraph/query_modules RENAME py_example.py)
install(FILES graph_analyzer.py DESTINATION lib/memgraph/query_modules)
install(FILES mgp_networkx.py DESTINATION lib/memgraph/query_modules)
install(FILES nxalg.py DESTINATION lib/memgraph/query_modules)

103
query_modules/example.cpp Normal file
View File

@@ -0,0 +1,103 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include <mgp.hpp>
void ProcImpl(std::vector<mgp::Value> arguments, mgp::Graph graph, mgp::RecordFactory record_factory) {
auto record = record_factory.NewRecord();
record.Insert("out", true);
}
void SampleReadProc(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
try {
mgp::memory = memory;
std::vector<mgp::Value> arguments;
for (size_t i = 0; i < mgp::list_size(args); i++) {
auto arg = mgp::Value(mgp::list_at(args, i));
arguments.push_back(arg);
}
ProcImpl(arguments, mgp::Graph(memgraph_graph), mgp::RecordFactory(result));
} catch (const std::exception &e) {
mgp::result_set_error_msg(result, e.what());
return;
}
}
void AddXNodes(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory) {
mgp::memory = memory;
auto graph = mgp::Graph(memgraph_graph);
std::vector<mgp::Value> arguments;
for (size_t i = 0; i < mgp::list_size(args); i++) {
auto arg = mgp::Value(mgp::list_at(args, i));
arguments.push_back(arg);
}
for (int i = 0; i < arguments[0].ValueInt(); i++) {
graph.CreateNode();
}
}
void Multiply(mgp_list *args, mgp_func_context *ctx, mgp_func_result *res, mgp_memory *memory) {
mgp::memory = memory;
std::vector<mgp::Value> arguments;
for (size_t i = 0; i < mgp::list_size(args); i++) {
auto arg = mgp::Value(mgp::list_at(args, i));
arguments.push_back(arg);
}
auto result = mgp::Result(res);
auto first = arguments[0].ValueInt();
auto second = arguments[1].ValueInt();
result.SetValue(first * second);
}
extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *memory) {
try {
mgp::memory = memory;
AddProcedure(SampleReadProc, "return_true", mgp::ProdecureType::Read,
{mgp::Parameter("param_1", mgp::Type::Int), mgp::Parameter("param_2", mgp::Type::Double, 2.3)},
{mgp::Return("out", mgp::Type::Bool)}, module, memory);
} catch (const std::exception &e) {
return 1;
}
try {
mgp::memory = memory;
mgp::AddProcedure(AddXNodes, "add_x_nodes", mgp::ProdecureType::Write, {mgp::Parameter("param_1", mgp::Type::Int)},
{}, module, memory);
} catch (const std::exception &e) {
return 1;
}
try {
mgp::memory = memory;
mgp::AddFunction(Multiply, "multiply",
{mgp::Parameter("int", mgp::Type::Int), mgp::Parameter("int", mgp::Type::Int, (int64_t)3)}, module,
memory);
} catch (const std::exception &e) {
return 1;
}
return 0;
}
extern "C" int mgp_shutdown_module() { return 0; }

View File

@@ -173,6 +173,10 @@ class SerializationError(Exception):
pass
class AuthorizationError(Exception):
pass
def type_nullable(elem: Any):
pass

View File

@@ -12,6 +12,7 @@ add_subdirectory(memory)
add_subdirectory(storage/v2)
add_subdirectory(integrations)
add_subdirectory(query)
add_subdirectory(glue)
add_subdirectory(slk)
add_subdirectory(rpc)
add_subdirectory(auth)
@@ -31,13 +32,11 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
# Memgraph Single Node v2 Executable
# ----------------------------------------------------------------------------
set(mg_single_node_v2_sources
glue/communication.cpp
memgraph.cpp
glue/auth.cpp
memgraph.cpp
)
set(mg_single_node_v2_libs stdc++fs Threads::Threads
telemetry_lib mg-query mg-communication mg-memory mg-utils mg-auth mg-license mg-settings)
telemetry_lib mg-query mg-communication mg-memory mg-utils mg-auth mg-license mg-settings mg-glue)
if (MG_ENTERPRISE)
# These are enterprise subsystems
set(mg_single_node_v2_libs ${mg_single_node_v2_libs} mg-audit)
@@ -99,9 +98,15 @@ install(PROGRAMS $<TARGET_FILE:memgraph>
# Install Python source for supporting our embedded Python.
install(FILES ${CMAKE_SOURCE_DIR}/include/mgp.py
DESTINATION lib/memgraph/python_support)
# Install the include file for writing custom procedures.
# Install the includes file for writing custom procedures in C and C++>
install(FILES ${CMAKE_SOURCE_DIR}/include/mg_procedure.h
DESTINATION include/memgraph)
install(FILES ${CMAKE_SOURCE_DIR}/include/_mgp.hpp
DESTINATION include/memgraph)
install(FILES ${CMAKE_SOURCE_DIR}/include/mg_exceptions.hpp
DESTINATION include/memgraph)
install(FILES ${CMAKE_SOURCE_DIR}/include/mgp.hpp
DESTINATION include/memgraph)
# Install the config file (must use absolute path).
install(FILES ${CMAKE_BINARY_DIR}/config/memgraph.conf
DESTINATION /etc/memgraph RENAME memgraph.conf)

View File

@@ -226,7 +226,7 @@ std::vector<auth::User> Auth::AllUsers() const {
if (username != utils::ToLowerCase(username)) continue;
auto user = GetUser(username);
if (user) {
ret.push_back(*user);
ret.push_back(std::move(*user));
}
}
return ret;
@@ -306,7 +306,7 @@ std::vector<auth::User> Auth::AllUsersForRole(const std::string &rolename_orig)
if (it->second == rolename) {
auto user = GetUser(username);
if (user) {
ret.push_back(*user);
ret.push_back(std::move(*user));
} else {
throw AuthException("Couldn't load user '{}'!", username);
}

View File

@@ -21,7 +21,7 @@
namespace memgraph::auth {
/**
* This class serves as the main Authentication/Authorization storage.
* It provides functions for managing Users, Roles and Permissions.
* It provides functions for managing Users, Roles, Permissions and FineGrainedAccessPermissions.
* NOTE: The non-const functions in this class aren't thread safe.
* TODO (mferencevic): Disable user/role modification functions when they are
* being managed by the auth module.

View File

@@ -8,6 +8,7 @@
#include "auth/models.hpp"
#include <cstdint>
#include <regex>
#include <gflags/gflags.h>
@@ -16,6 +17,7 @@
#include "auth/exceptions.hpp"
#include "utils/cast.hpp"
#include "utils/license.hpp"
#include "utils/logging.hpp"
#include "utils/settings.hpp"
#include "utils/string.hpp"
@@ -30,6 +32,7 @@ DEFINE_string(auth_password_strength_regex, default_password_regex.data(),
namespace memgraph::auth {
namespace {
// Constant list of all available permissions.
const std::vector<Permission> kPermissionsAll = {
Permission::MATCH, Permission::CREATE, Permission::MERGE, Permission::DELETE,
@@ -98,6 +101,55 @@ std::string PermissionLevelToString(PermissionLevel level) {
}
}
#ifdef MG_ENTERPRISE
FineGrainedPermission PermissionToFineGrainedPermission(const uint64_t permission) {
if (permission & FineGrainedPermission::CREATE_DELETE) {
return FineGrainedPermission::CREATE_DELETE;
}
if (permission & FineGrainedPermission::UPDATE) {
return FineGrainedPermission::UPDATE;
}
if (permission & FineGrainedPermission::READ) {
return FineGrainedPermission::READ;
}
return FineGrainedPermission::NOTHING;
}
std::string FineGrainedPermissionToString(const FineGrainedPermission level) {
switch (level) {
case FineGrainedPermission::CREATE_DELETE:
return "CREATE_DELETE";
case FineGrainedPermission::UPDATE:
return "UPDATE";
case FineGrainedPermission::READ:
return "READ";
case FineGrainedPermission::NOTHING:
return "NOTHING";
}
}
FineGrainedAccessPermissions Merge(const FineGrainedAccessPermissions &first,
const FineGrainedAccessPermissions &second) {
std::unordered_map<std::string, uint64_t> permissions{first.GetPermissions()};
std::optional<uint64_t> global_permission;
if (second.GetGlobalPermission().has_value()) {
global_permission = *second.GetGlobalPermission();
} else if (first.GetGlobalPermission().has_value()) {
global_permission = *first.GetGlobalPermission();
}
for (const auto &[label_name, permission] : second.GetPermissions()) {
permissions[label_name] = permission;
}
return FineGrainedAccessPermissions(permissions, global_permission);
}
#endif
Permissions::Permissions(uint64_t grants, uint64_t denies) {
// The deny bitmask has higher priority than the grant bitmask.
denies_ = denies;
@@ -171,7 +223,7 @@ Permissions Permissions::Deserialize(const nlohmann::json &data) {
if (!data["grants"].is_number_unsigned() || !data["denies"].is_number_unsigned()) {
throw AuthException("Couldn't load permissions data!");
}
return {data["grants"], data["denies"]};
return Permissions{data["grants"], data["denies"]};
}
uint64_t Permissions::grants() const { return grants_; }
@@ -183,19 +235,191 @@ bool operator==(const Permissions &first, const Permissions &second) {
bool operator!=(const Permissions &first, const Permissions &second) { return !(first == second); }
Role::Role(const std::string &rolename) : rolename_(utils::ToLowerCase(rolename)) {}
#ifdef MG_ENTERPRISE
FineGrainedAccessPermissions::FineGrainedAccessPermissions(const std::unordered_map<std::string, uint64_t> &permissions,
const std::optional<uint64_t> &global_permission)
: permissions_(permissions), global_permission_(global_permission) {}
PermissionLevel FineGrainedAccessPermissions::Has(const std::string &permission,
const FineGrainedPermission fine_grained_permission) const {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return PermissionLevel::GRANT;
}
const auto concrete_permission = std::invoke([&]() -> uint64_t {
if (permissions_.contains(permission)) {
return permissions_.at(permission);
}
if (global_permission_.has_value()) {
return global_permission_.value();
}
return 0;
});
const auto temp_permission = concrete_permission & fine_grained_permission;
return temp_permission > 0 ? PermissionLevel::GRANT : PermissionLevel::DENY;
}
void FineGrainedAccessPermissions::Grant(const std::string &permission,
const FineGrainedPermission fine_grained_permission) {
if (permission == kAsterisk) {
global_permission_ = CalculateGrant(fine_grained_permission);
} else {
permissions_[permission] = CalculateGrant(fine_grained_permission);
}
}
void FineGrainedAccessPermissions::Revoke(const std::string &permission) {
if (permission == kAsterisk) {
permissions_.clear();
global_permission_ = std::nullopt;
} else {
permissions_.erase(permission);
}
}
nlohmann::json FineGrainedAccessPermissions::Serialize() const {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return {};
}
nlohmann::json data = nlohmann::json::object();
data["permissions"] = permissions_;
data["global_permission"] = global_permission_.has_value() ? global_permission_.value() : -1;
return data;
}
FineGrainedAccessPermissions FineGrainedAccessPermissions::Deserialize(const nlohmann::json &data) {
if (!data.is_object()) {
throw AuthException("Couldn't load permissions data!");
}
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return FineGrainedAccessPermissions{};
}
std::optional<uint64_t> global_permission;
if (data["global_permission"].empty() || data["global_permission"] == -1) {
global_permission = std::nullopt;
} else {
global_permission = data["global_permission"];
}
return FineGrainedAccessPermissions(data["permissions"], global_permission);
}
const std::unordered_map<std::string, uint64_t> &FineGrainedAccessPermissions::GetPermissions() const {
return permissions_;
}
const std::optional<uint64_t> &FineGrainedAccessPermissions::GetGlobalPermission() const { return global_permission_; };
uint64_t FineGrainedAccessPermissions::CalculateGrant(FineGrainedPermission fine_grained_permission) {
uint64_t shift{1};
uint64_t result{0};
auto uint_fine_grained_permission = static_cast<uint64_t>(fine_grained_permission);
while (uint_fine_grained_permission > 0) {
result |= uint_fine_grained_permission;
uint_fine_grained_permission >>= shift;
}
return result;
}
bool operator==(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second) {
return first.GetPermissions() == second.GetPermissions() &&
first.GetGlobalPermission() == second.GetGlobalPermission();
}
bool operator!=(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second) {
return !(first == second);
}
FineGrainedAccessHandler::FineGrainedAccessHandler(FineGrainedAccessPermissions labelPermissions,
FineGrainedAccessPermissions edgeTypePermissions)
: label_permissions_(std::move(labelPermissions)), edge_type_permissions_(std::move(edgeTypePermissions)) {}
const FineGrainedAccessPermissions &FineGrainedAccessHandler::label_permissions() const { return label_permissions_; }
FineGrainedAccessPermissions &FineGrainedAccessHandler::label_permissions() { return label_permissions_; }
const FineGrainedAccessPermissions &FineGrainedAccessHandler::edge_type_permissions() const {
return edge_type_permissions_;
}
FineGrainedAccessPermissions &FineGrainedAccessHandler::edge_type_permissions() { return edge_type_permissions_; }
nlohmann::json FineGrainedAccessHandler::Serialize() const {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return {};
}
nlohmann::json data = nlohmann::json::object();
data["label_permissions"] = label_permissions_.Serialize();
data["edge_type_permissions"] = edge_type_permissions_.Serialize();
return data;
}
FineGrainedAccessHandler FineGrainedAccessHandler::Deserialize(const nlohmann::json &data) {
if (!data.is_object()) {
throw AuthException("Couldn't load role data!");
}
if (!data["label_permissions"].is_object() || !data["edge_type_permissions"].is_object()) {
throw AuthException("Couldn't load label_permissions or edge_type_permissions data!");
}
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return FineGrainedAccessHandler{};
}
auto label_permissions = FineGrainedAccessPermissions::Deserialize(data["label_permissions"]);
auto edge_type_permissions = FineGrainedAccessPermissions::Deserialize(data["edge_type_permissions"]);
return FineGrainedAccessHandler(std::move(label_permissions), std::move(edge_type_permissions));
}
bool operator==(const FineGrainedAccessHandler &first, const FineGrainedAccessHandler &second) {
return first.label_permissions_ == second.label_permissions_ &&
first.edge_type_permissions_ == second.edge_type_permissions_;
}
bool operator!=(const FineGrainedAccessHandler &first, const FineGrainedAccessHandler &second) {
return !(first == second);
}
#endif
Role::Role(const std::string &rolename) : rolename_(utils::ToLowerCase(rolename)) {}
Role::Role(const std::string &rolename, const Permissions &permissions)
: rolename_(utils::ToLowerCase(rolename)), permissions_(permissions) {}
#ifdef MG_ENTERPRISE
Role::Role(const std::string &rolename, const Permissions &permissions,
FineGrainedAccessHandler fine_grained_access_handler)
: rolename_(utils::ToLowerCase(rolename)),
permissions_(permissions),
fine_grained_access_handler_(std::move(fine_grained_access_handler)) {}
#endif
const std::string &Role::rolename() const { return rolename_; }
const Permissions &Role::permissions() const { return permissions_; }
Permissions &Role::permissions() { return permissions_; }
#ifdef MG_ENTERPRISE
const FineGrainedAccessHandler &Role::fine_grained_access_handler() const { return fine_grained_access_handler_; }
FineGrainedAccessHandler &Role::fine_grained_access_handler() { return fine_grained_access_handler_; }
const FineGrainedAccessPermissions &Role::GetFineGrainedAccessLabelPermissions() const {
return fine_grained_access_handler_.label_permissions();
}
const FineGrainedAccessPermissions &Role::GetFineGrainedAccessEdgeTypePermissions() const {
return fine_grained_access_handler_.edge_type_permissions();
}
#endif
nlohmann::json Role::Serialize() const {
nlohmann::json data = nlohmann::json::object();
data["rolename"] = rolename_;
data["permissions"] = permissions_.Serialize();
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize();
} else {
data["fine_grained_access_handler"] = {};
}
#endif
return data;
}
@@ -207,18 +431,43 @@ Role Role::Deserialize(const nlohmann::json &data) {
throw AuthException("Couldn't load role data!");
}
auto permissions = Permissions::Deserialize(data["permissions"]);
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
if (!data["fine_grained_access_handler"].is_object()) {
throw AuthException("Couldn't load user data!");
}
auto fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]);
return {data["rolename"], permissions, std::move(fine_grained_access_handler)};
}
#endif
return {data["rolename"], permissions};
}
bool operator==(const Role &first, const Role &second) {
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return first.rolename_ == second.rolename_ && first.permissions_ == second.permissions_ &&
first.fine_grained_access_handler_ == second.fine_grained_access_handler_;
}
#endif
return first.rolename_ == second.rolename_ && first.permissions_ == second.permissions_;
}
User::User(const std::string &username) : username_(utils::ToLowerCase(username)) {}
User::User() {}
User::User(const std::string &username) : username_(utils::ToLowerCase(username)) {}
User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions)
: username_(utils::ToLowerCase(username)), password_hash_(password_hash), permissions_(permissions) {}
#ifdef MG_ENTERPRISE
User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions,
FineGrainedAccessHandler fine_grained_access_handler)
: username_(utils::ToLowerCase(username)),
password_hash_(password_hash),
permissions_(permissions),
fine_grained_access_handler_(std::move(fine_grained_access_handler)) {}
#endif
bool User::CheckPassword(const std::string &password) {
if (password_hash_.empty()) return true;
return VerifyPassword(password, password_hash_);
@@ -260,17 +509,47 @@ void User::ClearRole() { role_ = std::nullopt; }
Permissions User::GetPermissions() const {
if (role_) {
return Permissions(permissions_.grants() | role_->permissions().grants(),
permissions_.denies() | role_->permissions().denies());
return Permissions{permissions_.grants() | role_->permissions().grants(),
permissions_.denies() | role_->permissions().denies()};
}
return permissions_;
}
#ifdef MG_ENTERPRISE
FineGrainedAccessPermissions User::GetFineGrainedAccessLabelPermissions() const {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return FineGrainedAccessPermissions{};
}
if (role_) {
return Merge(role()->fine_grained_access_handler().label_permissions(),
fine_grained_access_handler_.label_permissions());
}
return fine_grained_access_handler_.label_permissions();
}
FineGrainedAccessPermissions User::GetFineGrainedAccessEdgeTypePermissions() const {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return FineGrainedAccessPermissions{};
}
if (role_) {
return Merge(role()->fine_grained_access_handler().edge_type_permissions(),
fine_grained_access_handler_.edge_type_permissions());
}
return fine_grained_access_handler_.edge_type_permissions();
}
#endif
const std::string &User::username() const { return username_; }
const Permissions &User::permissions() const { return permissions_; }
Permissions &User::permissions() { return permissions_; }
#ifdef MG_ENTERPRISE
const FineGrainedAccessHandler &User::fine_grained_access_handler() const { return fine_grained_access_handler_; }
FineGrainedAccessHandler &User::fine_grained_access_handler() { return fine_grained_access_handler_; }
#endif
const Role *User::role() const {
if (role_.has_value()) {
return &role_.value();
@@ -283,6 +562,13 @@ nlohmann::json User::Serialize() const {
data["username"] = username_;
data["password_hash"] = password_hash_;
data["permissions"] = permissions_.Serialize();
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize();
} else {
data["fine_grained_access_handler"] = {};
}
#endif
// The role shouldn't be serialized here, it is stored as a foreign key.
return data;
}
@@ -295,11 +581,28 @@ User User::Deserialize(const nlohmann::json &data) {
throw AuthException("Couldn't load user data!");
}
auto permissions = Permissions::Deserialize(data["permissions"]);
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
if (!data["fine_grained_access_handler"].is_object()) {
throw AuthException("Couldn't load user data!");
}
auto fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]);
return {data["username"], data["password_hash"], permissions, fine_grained_access_handler};
}
#endif
return {data["username"], data["password_hash"], permissions};
}
bool operator==(const User &first, const User &second) {
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return first.username_ == second.username_ && first.password_hash_ == second.password_hash_ &&
first.permissions_ == second.permissions_ && first.role_ == second.role_ &&
first.fine_grained_access_handler_ == second.fine_grained_access_handler_;
}
#endif
return first.username_ == second.username_ && first.password_hash_ == second.password_hash_ &&
first.permissions_ == second.permissions_ && first.role_ == second.role_;
}
} // namespace memgraph::auth

View File

@@ -10,10 +10,12 @@
#include <optional>
#include <string>
#include <unordered_map>
#include <json/json.hpp>
namespace memgraph::auth {
const std::string kAsterisk = "*";
// These permissions must have values that are applicable for usage in a
// bitmask.
// clang-format off
@@ -42,22 +44,61 @@ enum class Permission : uint64_t {
};
// clang-format on
#ifdef MG_ENTERPRISE
// clang-format off
enum class FineGrainedPermission : uint64_t {
NOTHING = 0,
READ = 1,
UPDATE = 1U << 1U,
CREATE_DELETE = 1U << 2U
};
// clang-format on
constexpr inline uint64_t operator|(FineGrainedPermission lhs, FineGrainedPermission rhs) {
return static_cast<uint64_t>(lhs) | static_cast<uint64_t>(rhs);
}
constexpr inline uint64_t operator|(uint64_t lhs, FineGrainedPermission rhs) {
return lhs | static_cast<uint64_t>(rhs);
}
constexpr inline uint64_t operator&(uint64_t lhs, FineGrainedPermission rhs) {
return (lhs & static_cast<uint64_t>(rhs)) != 0;
}
constexpr uint64_t kLabelPermissionAll = memgraph::auth::FineGrainedPermission::CREATE_DELETE |
memgraph::auth::FineGrainedPermission::UPDATE |
memgraph::auth::FineGrainedPermission::READ;
constexpr uint64_t kLabelPermissionMax = static_cast<uint64_t>(memgraph::auth::FineGrainedPermission::CREATE_DELETE);
constexpr uint64_t kLabelPermissionMin = static_cast<uint64_t>(memgraph::auth::FineGrainedPermission::READ);
#endif
// Function that converts a permission to its string representation.
std::string PermissionToString(Permission permission);
// Class that indicates what permission level the user/role has.
enum class PermissionLevel {
GRANT,
NEUTRAL,
DENY,
};
enum class PermissionLevel : uint8_t { GRANT, NEUTRAL, DENY };
// Function that converts a permission level to its string representation.
std::string PermissionLevelToString(PermissionLevel level);
#ifdef MG_ENTERPRISE
// Function that converts a label permission level to its string representation.
std::string FineGrainedPermissionToString(FineGrainedPermission level);
// Constructs a label permission from a permission
FineGrainedPermission PermissionToFineGrainedPermission(uint64_t permission);
#endif
class Permissions final {
public:
Permissions(uint64_t grants = 0, uint64_t denies = 0);
explicit Permissions(uint64_t grants = 0, uint64_t denies = 0);
Permissions(const Permissions &) = default;
Permissions &operator=(const Permissions &) = default;
Permissions(Permissions &&) noexcept = default;
Permissions &operator=(Permissions &&) noexcept = default;
~Permissions() = default;
PermissionLevel Has(Permission permission) const;
@@ -88,16 +129,96 @@ bool operator==(const Permissions &first, const Permissions &second);
bool operator!=(const Permissions &first, const Permissions &second);
#ifdef MG_ENTERPRISE
class FineGrainedAccessPermissions final {
public:
explicit FineGrainedAccessPermissions(const std::unordered_map<std::string, uint64_t> &permissions = {},
const std::optional<uint64_t> &global_permission = std::nullopt);
FineGrainedAccessPermissions(const FineGrainedAccessPermissions &) = default;
FineGrainedAccessPermissions &operator=(const FineGrainedAccessPermissions &) = default;
FineGrainedAccessPermissions(FineGrainedAccessPermissions &&) = default;
FineGrainedAccessPermissions &operator=(FineGrainedAccessPermissions &&) = default;
~FineGrainedAccessPermissions() = default;
PermissionLevel Has(const std::string &permission, FineGrainedPermission fine_grained_permission) const;
void Grant(const std::string &permission, FineGrainedPermission fine_grained_permission);
void Revoke(const std::string &permission);
nlohmann::json Serialize() const;
/// @throw AuthException if unable to deserialize.
static FineGrainedAccessPermissions Deserialize(const nlohmann::json &data);
const std::unordered_map<std::string, uint64_t> &GetPermissions() const;
const std::optional<uint64_t> &GetGlobalPermission() const;
private:
std::unordered_map<std::string, uint64_t> permissions_{};
std::optional<uint64_t> global_permission_;
static uint64_t CalculateGrant(FineGrainedPermission fine_grained_permission);
};
bool operator==(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second);
bool operator!=(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second);
class FineGrainedAccessHandler final {
public:
explicit FineGrainedAccessHandler(FineGrainedAccessPermissions labelPermissions = FineGrainedAccessPermissions(),
FineGrainedAccessPermissions edgeTypePermissions = FineGrainedAccessPermissions());
FineGrainedAccessHandler(const FineGrainedAccessHandler &) = default;
FineGrainedAccessHandler &operator=(const FineGrainedAccessHandler &) = default;
FineGrainedAccessHandler(FineGrainedAccessHandler &&) noexcept = default;
FineGrainedAccessHandler &operator=(FineGrainedAccessHandler &&) noexcept = default;
~FineGrainedAccessHandler() = default;
const FineGrainedAccessPermissions &label_permissions() const;
FineGrainedAccessPermissions &label_permissions();
const FineGrainedAccessPermissions &edge_type_permissions() const;
FineGrainedAccessPermissions &edge_type_permissions();
nlohmann::json Serialize() const;
/// @throw AuthException if unable to deserialize.
static FineGrainedAccessHandler Deserialize(const nlohmann::json &data);
friend bool operator==(const FineGrainedAccessHandler &first, const FineGrainedAccessHandler &second);
private:
FineGrainedAccessPermissions label_permissions_;
FineGrainedAccessPermissions edge_type_permissions_;
};
bool operator==(const FineGrainedAccessHandler &first, const FineGrainedAccessHandler &second);
#endif
class Role final {
public:
Role(const std::string &rolename);
explicit Role(const std::string &rolename);
Role(const std::string &rolename, const Permissions &permissions);
#ifdef MG_ENTERPRISE
Role(const std::string &rolename, const Permissions &permissions,
FineGrainedAccessHandler fine_grained_access_handler);
#endif
Role(const Role &) = default;
Role &operator=(const Role &) = default;
Role(Role &&) noexcept = default;
Role &operator=(Role &&) noexcept = default;
~Role() = default;
const std::string &rolename() const;
const Permissions &permissions() const;
Permissions &permissions();
#ifdef MG_ENTERPRISE
const FineGrainedAccessHandler &fine_grained_access_handler() const;
FineGrainedAccessHandler &fine_grained_access_handler();
const FineGrainedAccessPermissions &GetFineGrainedAccessLabelPermissions() const;
const FineGrainedAccessPermissions &GetFineGrainedAccessEdgeTypePermissions() const;
#endif
nlohmann::json Serialize() const;
/// @throw AuthException if unable to deserialize.
@@ -108,6 +229,9 @@ class Role final {
private:
std::string rolename_;
Permissions permissions_;
#ifdef MG_ENTERPRISE
FineGrainedAccessHandler fine_grained_access_handler_;
#endif
};
bool operator==(const Role &first, const Role &second);
@@ -115,9 +239,19 @@ bool operator==(const Role &first, const Role &second);
// TODO (mferencevic): Implement password expiry.
class User final {
public:
User(const std::string &username);
User();
explicit User(const std::string &username);
User(const std::string &username, const std::string &password_hash, const Permissions &permissions);
#ifdef MG_ENTERPRISE
User(const std::string &username, const std::string &password_hash, const Permissions &permissions,
FineGrainedAccessHandler fine_grained_access_handler);
#endif
User(const User &) = default;
User &operator=(const User &) = default;
User(User &&) noexcept = default;
User &operator=(User &&) noexcept = default;
~User() = default;
/// @throw AuthException if unable to verify the password.
bool CheckPassword(const std::string &password);
@@ -131,6 +265,12 @@ class User final {
Permissions GetPermissions() const;
#ifdef MG_ENTERPRISE
FineGrainedAccessPermissions GetFineGrainedAccessLabelPermissions() const;
FineGrainedAccessPermissions GetFineGrainedAccessEdgeTypePermissions() const;
const FineGrainedAccessHandler &fine_grained_access_handler() const;
FineGrainedAccessHandler &fine_grained_access_handler();
#endif
const std::string &username() const;
const Permissions &permissions() const;
@@ -149,8 +289,16 @@ class User final {
std::string username_;
std::string password_hash_;
Permissions permissions_;
#ifdef MG_ENTERPRISE
FineGrainedAccessHandler fine_grained_access_handler_;
#endif
std::optional<Role> role_;
};
bool operator==(const User &first, const User &second);
#ifdef MG_ENTERPRISE
FineGrainedAccessPermissions Merge(const FineGrainedAccessPermissions &first,
const FineGrainedAccessPermissions &second);
#endif
} // namespace memgraph::auth

View File

@@ -7,6 +7,7 @@ set(communication_src_files
websocket/listener.cpp
websocket/session.cpp
bolt/v1/value.cpp
bolt/client.cpp
buffer.cpp
client.cpp
context.cpp

View File

@@ -0,0 +1,262 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "communication/bolt/client.hpp"
#include "communication/bolt/v1/codes.hpp"
#include "communication/bolt/v1/value.hpp"
#include "utils/logging.hpp"
namespace {
constexpr uint8_t kBoltV43Version[4] = {0x00, 0x00, 0x03, 0x04};
constexpr uint8_t kEmptyBoltVersion[4] = {0x00, 0x00, 0x00, 0x00};
} // namespace
namespace memgraph::communication::bolt {
Client::Client(communication::ClientContext &context) : client_{&context} {}
void Client::Connect(const io::network::Endpoint &endpoint, const std::string &username, const std::string &password,
const std::string &client_name) {
if (!client_.Connect(endpoint)) {
throw ClientFatalException("Couldn't connect to {}!", endpoint);
}
if (!client_.Write(kPreamble, sizeof(kPreamble), true)) {
spdlog::error("Couldn't send preamble!");
throw ServerCommunicationException();
}
if (!client_.Write(kBoltV43Version, sizeof(kBoltV43Version), true)) {
spdlog::error("Couldn't send protocol version!");
throw ServerCommunicationException();
}
for (int i = 0; i < 3; ++i) {
if (!client_.Write(kEmptyBoltVersion, sizeof(kEmptyBoltVersion), i != 2)) {
spdlog::error("Couldn't send protocol version!");
throw ServerCommunicationException();
}
}
if (!client_.Read(sizeof(kBoltV43Version))) {
spdlog::error("Couldn't get negotiated protocol version!");
throw ServerCommunicationException();
}
if (memcmp(kBoltV43Version, client_.GetData(), sizeof(kBoltV43Version)) != 0) {
spdlog::error("Server negotiated unsupported protocol version!");
throw ClientFatalException("The server negotiated an usupported protocol version!");
}
client_.ShiftData(sizeof(kBoltV43Version));
if (!encoder_.MessageInit({{"user_agent", client_name},
{"scheme", "basic"},
{"principal", username},
{"credentials", password},
{"routing", {}}})) {
spdlog::error("Couldn't send init message!");
throw ServerCommunicationException();
}
Signature signature{};
Value metadata;
if (!ReadMessage(signature, metadata)) {
spdlog::error("Couldn't read init message response!");
throw ServerCommunicationException();
}
if (signature != Signature::Success) {
spdlog::error("Handshake failed!");
throw ClientFatalException("Handshake with the server failed!");
}
spdlog::debug("Metadata of init message response: {}", metadata);
}
QueryData Client::Execute(const std::string &query, const std::map<std::string, Value> &parameters) {
if (!client_.IsConnected()) {
throw ClientFatalException("You must first connect to the server before using the client!");
}
spdlog::debug("Sending run message with statement: '{}'; parameters: {}", query, parameters);
// It is super critical from performance point of view to send the pull message right after the run message. Otherwise
// the performance will degrade multiple magnitudes.
encoder_.MessageRun(query, parameters, {});
encoder_.MessagePull({});
spdlog::debug("Reading run message response");
Signature signature{};
Value fields;
if (!ReadMessage(signature, fields)) {
throw ServerCommunicationException();
}
if (fields.type() != Value::Type::Map) {
throw ServerMalformedDataException();
}
if (signature == Signature::Failure) {
HandleFailure<ClientQueryException>(fields.ValueMap());
}
if (signature != Signature::Success) {
throw ServerMalformedDataException();
}
spdlog::debug("Reading pull_all message response");
Marker marker{};
Value metadata;
std::vector<std::vector<Value>> records;
while (true) {
if (!GetMessage()) {
throw ServerCommunicationException();
}
if (!decoder_.ReadMessageHeader(&signature, &marker)) {
throw ServerCommunicationException();
}
if (signature == Signature::Record) {
Value record;
if (!decoder_.ReadValue(&record, Value::Type::List)) {
throw ServerCommunicationException();
}
records.emplace_back(std::move(record.ValueList()));
} else if (signature == Signature::Success) {
if (!decoder_.ReadValue(&metadata)) {
throw ServerCommunicationException();
}
break;
} else if (signature == Signature::Failure) {
Value data;
if (!decoder_.ReadValue(&data)) {
throw ServerCommunicationException();
}
HandleFailure<ClientQueryException>(data.ValueMap());
} else {
throw ServerMalformedDataException();
}
}
if (metadata.type() != Value::Type::Map) {
throw ServerMalformedDataException();
}
QueryData ret{{}, std::move(records), std::move(metadata.ValueMap())};
auto &header = fields.ValueMap();
if (header.find("fields") == header.end()) {
throw ServerMalformedDataException();
}
if (header["fields"].type() != Value::Type::List) {
throw ServerMalformedDataException();
}
auto &field_vector = header["fields"].ValueList();
for (auto &field_item : field_vector) {
if (field_item.type() != Value::Type::String) {
throw ServerMalformedDataException();
}
ret.fields.emplace_back(std::move(field_item.ValueString()));
}
return ret;
}
void Client::Reset() {
if (!client_.IsConnected()) {
throw ClientFatalException("You must first connect to the server before using the client!");
}
spdlog::debug("Sending reset message");
encoder_.MessageReset();
Signature signature{};
Value fields;
// In Execute the pull message is sent right after the run message without reading the answer for the run message.
// That means some of the messages sent might get ignored.
while (true) {
if (!ReadMessage(signature, fields)) {
throw ServerCommunicationException();
}
if (signature == Signature::Success) {
break;
}
if (signature != Signature::Ignored) {
throw ServerMalformedDataException();
}
}
}
std::optional<std::map<std::string, Value>> Client::Route(const std::map<std::string, Value> &routing,
const std::vector<Value> &bookmarks,
const std::optional<std::string> &db) {
if (!client_.IsConnected()) {
throw ClientFatalException("You must first connect to the server before using the client!");
}
spdlog::debug("Sending route message with routing: {}; bookmarks: {}; db: {}", routing, bookmarks,
db.has_value() ? *db : Value());
encoder_.MessageRoute(routing, bookmarks, db);
spdlog::debug("Reading route message response");
Signature signature{};
Value fields;
if (!ReadMessage(signature, fields)) {
throw ServerCommunicationException();
}
if (signature == Signature::Ignored) {
return std::nullopt;
}
if (signature == Signature::Failure) {
HandleFailure(fields.ValueMap());
}
if (signature != Signature::Success) {
throw ServerMalformedDataException{};
}
return fields.ValueMap();
}
void Client::Close() { client_.Close(); };
bool Client::GetMessage() {
client_.ClearData();
while (true) {
if (!client_.Read(kChunkHeaderSize)) return false;
size_t chunk_size = client_.GetData()[0];
chunk_size <<= 8U;
chunk_size += client_.GetData()[1];
if (chunk_size == 0) return true;
if (!client_.Read(chunk_size)) return false;
if (decoder_buffer_.GetChunk() != ChunkState::Whole) return false;
client_.ClearData();
}
return true;
}
bool Client::ReadMessage(Signature &signature, Value &ret) {
Marker marker{};
if (!GetMessage()) return false;
if (!decoder_.ReadMessageHeader(&signature, &marker)) return false;
return ReadMessageData(marker, ret);
}
bool Client::ReadMessageData(Marker marker, Value &ret) {
if (marker == Marker::TinyStruct) {
ret = Value();
return true;
}
if (marker == Marker::TinyStruct1) {
return decoder_.ReadValue(&ret);
}
return false;
}
} // namespace memgraph::communication::bolt

View File

@@ -11,6 +11,12 @@
#pragma once
#include <map>
#include <optional>
#include <string>
#include <vector>
#include "communication/bolt/v1/codes.hpp"
#include "communication/bolt/v1/decoder/chunked_decoder_buffer.hpp"
#include "communication/bolt/v1/decoder/decoder.hpp"
#include "communication/bolt/v1/encoder/chunked_encoder_buffer.hpp"
@@ -19,22 +25,17 @@
#include "communication/context.hpp"
#include "io/network/endpoint.hpp"
#include "utils/exceptions.hpp"
#include "utils/logging.hpp"
namespace memgraph::communication::bolt {
/// This exception is thrown whenever an error occurs during query execution
/// that isn't fatal (eg. mistyped query or some transient error occurred).
/// It should be handled by everyone who uses the client.
class ClientQueryException : public utils::BasicException {
class FailureResponseException : public utils::BasicException {
public:
using utils::BasicException::BasicException;
FailureResponseException() : utils::BasicException{"Couldn't execute query!"} {}
ClientQueryException() : utils::BasicException("Couldn't execute query!") {}
explicit FailureResponseException(const std::string &message) : utils::BasicException{message} {}
template <class... Args>
ClientQueryException(const std::string &code, Args &&...args)
: utils::BasicException(std::forward<Args>(args)...), code_(code) {}
FailureResponseException(const std::string &code, const std::string &message)
: utils::BasicException{message}, code_{code} {}
const std::string &code() const { return code_; }
@@ -42,6 +43,14 @@ class ClientQueryException : public utils::BasicException {
std::string code_;
};
/// This exception is thrown whenever an error occurs during query execution
/// that isn't fatal (eg. mistyped query or some transient error occurred).
/// It should be handled by everyone who uses the client.
class ClientQueryException : public FailureResponseException {
public:
using FailureResponseException::FailureResponseException;
};
/// This exception is thrown whenever a fatal error occurs during query
/// execution and/or connecting to the server.
/// It should be handled by everyone who uses the client.
@@ -76,12 +85,13 @@ struct QueryData {
/// server. It supports both SSL and plaintext connections.
class Client final {
public:
explicit Client(communication::ClientContext *context) : client_(context) {}
explicit Client(communication::ClientContext &context);
Client(const Client &) = delete;
Client(Client &&) = delete;
Client &operator=(const Client &) = delete;
Client &operator=(Client &&) = delete;
~Client() = default;
/// Method used to connect to the server. Before executing queries this method
/// should be called to set-up the connection to the server. After the
@@ -89,50 +99,7 @@ class Client final {
/// established connection.
/// @throws ClientFatalException when we couldn't connect to the server
void Connect(const io::network::Endpoint &endpoint, const std::string &username, const std::string &password,
const std::string &client_name = "memgraph-bolt") {
if (!client_.Connect(endpoint)) {
throw ClientFatalException("Couldn't connect to {}!", endpoint);
}
if (!client_.Write(kPreamble, sizeof(kPreamble), true)) {
SPDLOG_ERROR("Couldn't send preamble!");
throw ServerCommunicationException();
}
for (int i = 0; i < 4; ++i) {
if (!client_.Write(kProtocol, sizeof(kProtocol), i != 3)) {
SPDLOG_ERROR("Couldn't send protocol version!");
throw ServerCommunicationException();
}
}
if (!client_.Read(sizeof(kProtocol))) {
SPDLOG_ERROR("Couldn't get negotiated protocol version!");
throw ServerCommunicationException();
}
if (memcmp(kProtocol, client_.GetData(), sizeof(kProtocol)) != 0) {
SPDLOG_ERROR("Server negotiated unsupported protocol version!");
throw ClientFatalException("The server negotiated an usupported protocol version!");
}
client_.ShiftData(sizeof(kProtocol));
if (!encoder_.MessageInit(client_name, {{"scheme", "basic"}, {"principal", username}, {"credentials", password}})) {
SPDLOG_ERROR("Couldn't send init message!");
throw ServerCommunicationException();
}
Signature signature;
Value metadata;
if (!ReadMessage(&signature, &metadata)) {
SPDLOG_ERROR("Couldn't read init message response!");
throw ServerCommunicationException();
}
if (signature != Signature::Success) {
SPDLOG_ERROR("Handshake failed!");
throw ClientFatalException("Handshake with the server failed!");
}
SPDLOG_INFO("Metadata of init message response: {}", metadata);
}
const std::string &client_name = "memgraph-bolt");
/// Function used to execute queries against the server. Before you can
/// execute queries you must connect the client to the server.
@@ -140,168 +107,41 @@ class Client final {
/// executing the query (eg. mistyped query,
/// etc.)
/// @throws ClientFatalException when we couldn't communicate with the server
QueryData Execute(const std::string &query, const std::map<std::string, Value> &parameters) {
if (!client_.IsConnected()) {
throw ClientFatalException("You must first connect to the server before using the client!");
}
SPDLOG_INFO("Sending run message with statement: '{}'; parameters: {}", query, parameters);
encoder_.MessageRun(query, parameters);
encoder_.MessagePullAll();
SPDLOG_INFO("Reading run message response");
Signature signature;
Value fields;
if (!ReadMessage(&signature, &fields)) {
throw ServerCommunicationException();
}
if (fields.type() != Value::Type::Map) {
throw ServerMalformedDataException();
}
if (signature == Signature::Failure) {
HandleFailure();
auto &tmp = fields.ValueMap();
auto it = tmp.find("message");
if (it != tmp.end()) {
auto it_code = tmp.find("code");
if (it_code != tmp.end()) {
throw ClientQueryException(it_code->second.ValueString(), it->second.ValueString());
} else {
throw ClientQueryException("", it->second.ValueString());
}
}
throw ClientQueryException();
} else if (signature != Signature::Success) {
throw ServerMalformedDataException();
}
SPDLOG_INFO("Reading pull_all message response");
Marker marker;
Value metadata;
std::vector<std::vector<Value>> records;
while (true) {
if (!GetMessage()) {
throw ServerCommunicationException();
}
if (!decoder_.ReadMessageHeader(&signature, &marker)) {
throw ServerCommunicationException();
}
if (signature == Signature::Record) {
Value record;
if (!decoder_.ReadValue(&record, Value::Type::List)) {
throw ServerCommunicationException();
}
records.emplace_back(std::move(record.ValueList()));
} else if (signature == Signature::Success) {
if (!decoder_.ReadValue(&metadata)) {
throw ServerCommunicationException();
}
break;
} else if (signature == Signature::Failure) {
Value data;
if (!decoder_.ReadValue(&data)) {
throw ServerCommunicationException();
}
HandleFailure();
auto &tmp = data.ValueMap();
auto it = tmp.find("message");
if (it != tmp.end()) {
auto it_code = tmp.find("code");
if (it_code != tmp.end()) {
throw ClientQueryException(it_code->second.ValueString(), it->second.ValueString());
} else {
throw ClientQueryException("", it->second.ValueString());
}
}
throw ClientQueryException();
} else {
throw ServerMalformedDataException();
}
}
if (metadata.type() != Value::Type::Map) {
throw ServerMalformedDataException();
}
QueryData ret{{}, std::move(records), std::move(metadata.ValueMap())};
auto &header = fields.ValueMap();
if (header.find("fields") == header.end()) {
throw ServerMalformedDataException();
}
if (header["fields"].type() != Value::Type::List) {
throw ServerMalformedDataException();
}
auto &field_vector = header["fields"].ValueList();
for (auto &field_item : field_vector) {
if (field_item.type() != Value::Type::String) {
throw ServerMalformedDataException();
}
ret.fields.emplace_back(std::move(field_item.ValueString()));
}
return ret;
}
QueryData Execute(const std::string &query, const std::map<std::string, Value> &parameters);
/// Close the active client connection.
void Close() { client_.Close(); };
void Close();
/// Can be used to reset the active client connection. Reset is automatically sent after receiving a failure message
/// from the server, which result in throwing an FailureResponseException or any exception derived from it.
void Reset();
/// Can be used to send a route message.
std::optional<std::map<std::string, Value>> Route(const std::map<std::string, Value> &routing,
const std::vector<Value> &bookmarks,
const std::optional<std::string> &db);
private:
bool GetMessage() {
client_.ClearData();
while (true) {
if (!client_.Read(kChunkHeaderSize)) return false;
using ClientEncoder = ClientEncoder<ChunkedEncoderBuffer<communication::ClientOutputStream>>;
size_t chunk_size = client_.GetData()[0];
chunk_size <<= 8;
chunk_size += client_.GetData()[1];
if (chunk_size == 0) return true;
if (!client_.Read(chunk_size)) return false;
if (decoder_buffer_.GetChunk() != ChunkState::Whole) return false;
client_.ClearData();
}
return true;
}
bool ReadMessage(Signature *signature, Value *ret) {
Marker marker;
if (!GetMessage()) return false;
if (!decoder_.ReadMessageHeader(signature, &marker)) return false;
return ReadMessageData(marker, ret);
}
bool ReadMessageData(Marker marker, Value *ret) {
if (marker == Marker::TinyStruct) {
*ret = Value();
return true;
} else if (marker == Marker::TinyStruct1) {
return decoder_.ReadValue(ret);
}
return false;
}
void HandleFailure() {
if (!encoder_.MessageAckFailure()) {
throw ServerCommunicationException();
}
while (true) {
Signature signature;
Value data;
if (!ReadMessage(&signature, &data)) {
throw ServerCommunicationException();
}
if (signature == Signature::Success) {
break;
} else if (signature != Signature::Ignored) {
throw ServerMalformedDataException();
template <typename TException = FailureResponseException>
[[noreturn]] void HandleFailure(const std::map<std::string, Value> &response_map) {
Reset();
auto it = response_map.find("message");
if (it != response_map.end()) {
auto it_code = response_map.find("code");
if (it_code != response_map.end()) {
throw TException(it_code->second.ValueString(), it->second.ValueString());
}
throw TException("", it->second.ValueString());
}
throw TException();
}
bool GetMessage();
bool ReadMessage(Signature &signature, Value &ret);
bool ReadMessageData(Marker marker, Value &ret);
// client
communication::Client client_;
communication::ClientInputStream input_stream_{client_};
@@ -313,6 +153,6 @@ class Client final {
// encoder objects
ChunkedEncoderBuffer<communication::ClientOutputStream> encoder_buffer_{output_stream_};
ClientEncoder<ChunkedEncoderBuffer<communication::ClientOutputStream>> encoder_{encoder_buffer_};
ClientEncoder encoder_{encoder_buffer_};
};
} // namespace memgraph::communication::bolt

View File

@@ -16,7 +16,6 @@
namespace memgraph::communication::bolt {
inline constexpr uint8_t kPreamble[4] = {0x60, 0x60, 0xB0, 0x17};
inline constexpr uint8_t kProtocol[4] = {0x00, 0x00, 0x00, 0x01};
enum class Signature : uint8_t {
Noop = 0x00,

View File

@@ -11,6 +11,11 @@
#pragma once
#include <map>
#include <optional>
#include <string>
#include <vector>
#include "communication/bolt/v1/codes.hpp"
#include "communication/bolt/v1/encoder/base_encoder.hpp"
@@ -30,6 +35,7 @@ class ClientEncoder : private BaseEncoder<Buffer> {
using BaseEncoder<Buffer>::WriteList;
using BaseEncoder<Buffer>::WriteMap;
using BaseEncoder<Buffer>::WriteString;
using BaseEncoder<Buffer>::WriteNull;
using BaseEncoder<Buffer>::buffer_;
public:
@@ -38,10 +44,9 @@ class ClientEncoder : private BaseEncoder<Buffer> {
/**
* Writes a Init message.
*
* From the Bolt v1 documentation:
* InitMessage (signature=0x01) {
* String clientName
* Map<String,Value> authToken
* From the Bolt v4.3 documentation:
* HelloMess (signature=0x01) {
* Map<String,Value> extra
* }
*
* @param client_name the name of the connected client
@@ -49,11 +54,10 @@ class ClientEncoder : private BaseEncoder<Buffer> {
* @returns true if the data was successfully sent to the client
* when flushing, false otherwise
*/
bool MessageInit(const std::string client_name, const std::map<std::string, Value> &auth_token) {
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct2));
bool MessageInit(const std::map<std::string, Value> &extra) {
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct1));
WriteRAW(utils::UnderlyingCast(Signature::Init));
WriteString(client_name);
WriteMap(auth_token);
WriteMap(extra);
// Try to flush all remaining data in the buffer, but tell it that we will
// send more data (the end of message chunk).
if (!buffer_.Flush(true)) return false;
@@ -64,10 +68,11 @@ class ClientEncoder : private BaseEncoder<Buffer> {
/**
* Writes a Run message.
*
* From the Bolt v1 documentation:
* From the Bolt v4.3 documentation:
* RunMessage (signature=0x10) {
* String statement
* Map<String,Value> parameters
* String statement
* Map<String,Value> parameters
* Map<String,Value> extra
* }
*
* @param statement the statement that should be executed
@@ -75,11 +80,13 @@ class ClientEncoder : private BaseEncoder<Buffer> {
* @returns true if the data was successfully sent to the client
* when flushing, false otherwise
*/
bool MessageRun(const std::string &statement, const std::map<std::string, Value> &parameters, bool have_more = true) {
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct2));
bool MessageRun(const std::string &statement, const std::map<std::string, Value> &parameters,
const std::map<std::string, Value> &extra, bool have_more = true) {
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct3));
WriteRAW(utils::UnderlyingCast(Signature::Run));
WriteString(statement);
WriteMap(parameters);
WriteMap(extra);
// Try to flush all remaining data in the buffer, but tell it that we will
// send more data (the end of message chunk).
if (!buffer_.Flush(true)) return false;
@@ -90,18 +97,20 @@ class ClientEncoder : private BaseEncoder<Buffer> {
}
/**
* Writes a DiscardAll message.
* Writes a Discard message.
*
* From the Bolt v1 documentation:
* From the Bolt v4.3 documentation:
* DiscardMessage (signature=0x2F) {
* Map<String,Value> extra
* }
*
* @returns true if the data was successfully sent to the client
* when flushing, false otherwise
*/
bool MessageDiscardAll() {
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct));
bool MessageDiscard(const std::map<std::string, Value> &extra) {
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct1));
WriteRAW(utils::UnderlyingCast(Signature::Discard));
WriteMap(extra);
// Try to flush all remaining data in the buffer, but tell it that we will
// send more data (the end of message chunk).
if (!buffer_.Flush(true)) return false;
@@ -112,36 +121,18 @@ class ClientEncoder : private BaseEncoder<Buffer> {
/**
* Writes a PullAll message.
*
* From the Bolt v1 documentation:
* PullAllMessage (signature=0x3F) {
* From the Bolt v4.3 documentation:
* PullMessage (signature=0x3F) {
* Map<String,Value> extra
* }
*
* @returns true if the data was successfully sent to the client
* when flushing, false otherwise
*/
bool MessagePullAll() {
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct));
bool MessagePull(const std::map<std::string, Value> &extra) {
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct1));
WriteRAW(utils::UnderlyingCast(Signature::Pull));
// Try to flush all remaining data in the buffer, but tell it that we will
// send more data (the end of message chunk).
if (!buffer_.Flush(true)) return false;
// Flush an empty chunk to indicate that the message is done.
return buffer_.Flush();
}
/**
* Writes a AckFailure message.
*
* From the Bolt v1 documentation:
* AckFailureMessage (signature=0x0E) {
* }
*
* @returns true if the data was successfully sent to the client
* when flushing, false otherwise
*/
bool MessageAckFailure() {
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct));
WriteRAW(utils::UnderlyingCast(Signature::AckFailure));
WriteMap(extra);
// Try to flush all remaining data in the buffer, but tell it that we will
// send more data (the end of message chunk).
if (!buffer_.Flush(true)) return false;
@@ -152,7 +143,7 @@ class ClientEncoder : private BaseEncoder<Buffer> {
/**
* Writes a Reset message.
*
* From the Bolt v1 documentation:
* From the Bolt v4.3 documentation:
* ResetMessage (signature=0x0F) {
* }
*
@@ -168,5 +159,36 @@ class ClientEncoder : private BaseEncoder<Buffer> {
// Flush an empty chunk to indicate that the message is done.
return buffer_.Flush();
}
/**
* Writes a Route message.
*
* From the Bolt v4.3 documentation:
* RouteMessage (signature=0x0F) {
* Map<String,Value> routing
* List<String> bookmarks
* String db
* }
*
* @returns true if the data was successfully sent to the client
* when flushing, false otherwise
*/
bool MessageRoute(const std::map<std::string, Value> &routing, const std::vector<Value> &bookmarks,
const std::optional<std::string> &db) {
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct3));
WriteRAW(utils::UnderlyingCast(Signature::Route));
WriteMap(routing);
WriteList(bookmarks);
if (db.has_value()) {
WriteString(*db);
} else {
WriteNull();
}
// Try to flush all remaining data in the buffer, but tell it that we will
// send more data (the end of message chunk).
if (!buffer_.Flush(true)) return false;
// Flush an empty chunk to indicate that the message is done.
return buffer_.Flush();
}
};
} // namespace memgraph::communication::bolt

View File

@@ -117,29 +117,6 @@ class Encoder : private BaseEncoder<Buffer> {
return buffer_.Flush();
}
/**
* Sends an Ignored message.
*
* From the bolt v1 documentation:
* IgnoredMessage (signature=0x7E) {
* Map<String,Value> metadata
* }
*
* @param metadata the metadata map object that should be sent
* @returns true if the data was successfully sent to the client,
* false otherwise
*/
bool MessageIgnored(const std::map<std::string, Value> &metadata) {
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct1));
WriteRAW(utils::UnderlyingCast(Signature::Ignored));
WriteMap(metadata);
// Try to flush all remaining data in the buffer, but tell it that we will
// send more data (the end of message chunk).
if (!buffer_.Flush(true)) return false;
// Flush an empty chunk to indicate that the message is done.
return buffer_.Flush();
}
/**
* Sends an Ignored message.
*

View File

@@ -15,6 +15,7 @@
#include "communication/bolt/v1/codes.hpp"
#include "communication/bolt/v1/state.hpp"
#include "communication/bolt/v1/states/handlers.hpp"
#include "communication/bolt/v1/value.hpp"
#include "utils/cast.hpp"
#include "utils/likely.hpp"
@@ -30,8 +31,8 @@ namespace memgraph::communication::bolt {
*/
template <typename TSession>
State StateErrorRun(TSession &session, State state) {
Marker marker;
Signature signature;
Marker marker{};
Signature signature{};
if (!session.decoder_.ReadMessageHeader(&signature, &marker)) {
spdlog::trace("Missing header data!");
return State::Close;
@@ -45,54 +46,49 @@ State StateErrorRun(TSession &session, State state) {
// Clear the data buffer if it has any leftover data.
session.encoder_buffer_.Clear();
if ((session.version_.major == 1 && signature == Signature::AckFailure) || signature == Signature::Reset) {
if (signature == Signature::AckFailure) {
spdlog::trace("AckFailure received");
} else {
spdlog::trace("Reset received");
}
if (session.version_.major == 1 && signature == Signature::AckFailure) {
spdlog::trace("AckFailure received");
if (!session.encoder_.MessageSuccess()) {
spdlog::trace("Couldn't send success message!");
return State::Close;
}
if (signature == Signature::Reset) {
session.Abort();
return State::Idle;
}
// We got AckFailure get back to right state.
MG_ASSERT(state == State::Error, "Shouldn't happen");
return State::Idle;
} else {
uint8_t value = utils::UnderlyingCast(marker);
// All bolt client messages have less than 15 parameters so if we receive
// anything than a TinyStruct it's an error.
if ((value & 0xF0) != utils::UnderlyingCast(Marker::TinyStruct)) {
spdlog::trace("Expected TinyStruct marker, but received 0x{:02X}!", value);
return State::Close;
}
// We need to clean up all parameters from this command.
value &= 0x0F; // The length is stored in the lower nibble.
Value dv;
for (int i = 0; i < value; ++i) {
if (!session.decoder_.ReadValue(&dv)) {
spdlog::trace("Couldn't clean up parameter {} / {}!", i, value);
return State::Close;
}
}
// Ignore this message.
if (!session.encoder_.MessageIgnored()) {
spdlog::trace("Couldn't send ignored message!");
return State::Close;
}
// Cleanup done, command ignored, stay in error state.
return state;
}
if (signature == Signature::Reset) {
spdlog::trace("Reset received");
return HandleReset(session, marker);
}
uint8_t value = utils::UnderlyingCast(marker);
// All bolt client messages have less than 15 parameters so if we receive
// anything than a TinyStruct it's an error.
if ((value & 0xF0U) != utils::UnderlyingCast(Marker::TinyStruct)) {
spdlog::trace("Expected TinyStruct marker, but received 0x{:02X}!", value);
return State::Close;
}
// We need to clean up all parameters from this command.
value &= 0x0FU; // The length is stored in the lower nibble.
Value dv;
for (int i = 0; i < value; ++i) {
if (!session.decoder_.ReadValue(&dv)) {
spdlog::trace("Couldn't clean up parameter {} / {}!", i, value);
return State::Close;
}
}
// Ignore this message.
if (!session.encoder_.MessageIgnored()) {
spdlog::trace("Couldn't send ignored message!");
return State::Close;
}
// Cleanup done, command ignored, stay in error state.
return state;
}
} // namespace memgraph::communication::bolt

View File

@@ -74,7 +74,7 @@ State RunHandlerV4(Signature signature, TSession &session, State state, Marker m
}
case Signature::Route: {
if constexpr (bolt_minor >= 3) {
if (signature == Signature::Route) return HandleRoute<TSession>(session);
if (signature == Signature::Route) return HandleRoute<TSession>(session, marker);
} else {
spdlog::trace("Supported only in bolt v4.3");
return State::Close;

View File

@@ -18,6 +18,7 @@
#include "communication/bolt/v1/codes.hpp"
#include "communication/bolt/v1/constants.hpp"
#include "communication/bolt/v1/exceptions.hpp"
#include "communication/bolt/v1/state.hpp"
#include "communication/bolt/v1/value.hpp"
#include "communication/exceptions.hpp"
@@ -136,7 +137,7 @@ template <bool is_pull, typename TSession>
State HandlePullDiscardV1(TSession &session, const State state, const Marker marker) {
const auto expected_marker = Marker::TinyStruct;
if (marker != expected_marker) {
spdlog::trace("Expected {} marker, but received 0x{:02X}!", "TinyStruct", utils::UnderlyingCast(marker));
spdlog::trace("Expected TinyStruct marker, but received 0x{:02X}!", utils::UnderlyingCast(marker));
return State::Close;
}
@@ -157,7 +158,7 @@ template <bool is_pull, typename TSession>
State HandlePullDiscardV4(TSession &session, const State state, const Marker marker) {
const auto expected_marker = Marker::TinyStruct1;
if (marker != expected_marker) {
spdlog::trace("Expected {} marker, but received 0x{:02X}!", "TinyStruct1", utils::UnderlyingCast(marker));
spdlog::trace("Expected TinyStruct1 marker, but received 0x{:02X}!", utils::UnderlyingCast(marker));
return State::Close;
}
@@ -216,7 +217,8 @@ State HandleRunV1(TSession &session, const State state, const Marker marker) {
session.version_.major == 1 ? "TinyStruct2" : "TinyStruct3", utils::UnderlyingCast(marker));
return State::Close;
}
Value query, params;
Value query;
Value params;
if (!session.decoder_.ReadValue(&query, Value::Type::String)) {
spdlog::trace("Couldn't read query string!");
return State::Close;
@@ -234,10 +236,12 @@ template <typename TSession>
State HandleRunV4(TSession &session, const State state, const Marker marker) {
const auto expected_marker = Marker::TinyStruct3;
if (marker != expected_marker) {
spdlog::trace("Expected {} marker, but received 0x{:02X}!", "TinyStruct3", utils::UnderlyingCast(marker));
spdlog::trace("Expected TinyStruct3 marker, but received 0x{:02X}!", utils::UnderlyingCast(marker));
return State::Close;
}
Value query, params, extra;
Value query;
Value params;
Value extra;
if (!session.decoder_.ReadValue(&query, Value::Type::String)) {
spdlog::trace("Couldn't read query string!");
return State::Close;
@@ -292,9 +296,6 @@ State HandleReset(TSession &session, const Marker marker) {
return State::Close;
}
// Clear all pending data and send a success message.
session.encoder_buffer_.Clear();
if (!session.encoder_.MessageSuccess()) {
spdlog::trace("Couldn't send success message!");
return State::Close;
@@ -403,12 +404,33 @@ State HandleGoodbye() {
}
template <typename TSession>
State HandleRoute(TSession &session) {
// Route message is not implemented since it is neo4j specific, therefore we
// will receive it an inform user that there is no implementation.
State HandleRoute(TSession &session, const Marker marker) {
// Route message is not implemented since it is Neo4j specific, therefore we will receive it and inform user that
// there is no implementation. Before that, we have to read out the fields from the buffer to leave it in a clean
// state.
if (marker != Marker::TinyStruct3) {
spdlog::trace("Expected TinyStruct3 marker, but received 0x{:02x}!", utils::UnderlyingCast(marker));
return State::Close;
}
Value routing;
if (!session.decoder_.ReadValue(&routing, Value::Type::Map)) {
spdlog::trace("Couldn't read routing field!");
return State::Close;
}
Value bookmarks;
if (!session.decoder_.ReadValue(&bookmarks, Value::Type::List)) {
spdlog::trace("Couldn't read bookmarks field!");
return State::Close;
}
Value db;
if (!session.decoder_.ReadValue(&db)) {
spdlog::trace("Couldn't read db field!");
return State::Close;
}
session.encoder_buffer_.Clear();
bool fail_sent =
session.encoder_.MessageFailure({{"code", 66}, {"message", "Route message not supported in Memgraph!"}});
session.encoder_.MessageFailure({{"code", "66"}, {"message", "Route message is not supported in Memgraph!"}});
if (!fail_sent) {
spdlog::trace("Couldn't send failure message!");
return State::Close;

4
src/glue/CMakeLists.txt Normal file
View File

@@ -0,0 +1,4 @@
set(mg_glue_sources auth.cpp auth_checker.cpp auth_handler.cpp communication.cpp)
add_library(mg-glue STATIC ${mg_glue_sources})
target_link_libraries(mg-glue mg-query mg-auth)

View File

@@ -10,6 +10,7 @@
// licenses/APL.txt.
#include "glue/auth.hpp"
#include "auth/models.hpp"
namespace memgraph::glue {
@@ -59,4 +60,20 @@ auth::Permission PrivilegeToPermission(query::AuthQuery::Privilege privilege) {
return auth::Permission::WEBSOCKET;
}
}
#ifdef MG_ENTERPRISE
auth::FineGrainedPermission FineGrainedPrivilegeToFineGrainedPermission(
const query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) {
switch (fine_grained_privilege) {
case query::AuthQuery::FineGrainedPrivilege::NOTHING:
return auth::FineGrainedPermission::NOTHING;
case query::AuthQuery::FineGrainedPrivilege::READ:
return auth::FineGrainedPermission::READ;
case query::AuthQuery::FineGrainedPrivilege::UPDATE:
return auth::FineGrainedPermission::UPDATE;
case query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE:
return auth::FineGrainedPermission::CREATE_DELETE;
}
}
#endif
} // namespace memgraph::glue

View File

@@ -20,4 +20,12 @@ namespace memgraph::glue {
*/
auth::Permission PrivilegeToPermission(query::AuthQuery::Privilege privilege);
#ifdef MG_ENTERPRISE
/**
* Converts query::AuthQuery::FineGrainedPrivilege to its corresponding
* auth::EntityPermission.
*/
auth::FineGrainedPermission FineGrainedPrivilegeToFineGrainedPermission(
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege);
#endif
} // namespace memgraph::glue

171
src/glue/auth_checker.cpp Normal file
View File

@@ -0,0 +1,171 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "glue/auth_checker.hpp"
#include "auth/auth.hpp"
#include "auth/models.hpp"
#include "glue/auth.hpp"
#include "query/frontend/ast/ast.hpp"
#include "utils/license.hpp"
#include "utils/synchronized.hpp"
#ifdef MG_ENTERPRISE
namespace {
bool IsUserAuthorizedLabels(const memgraph::auth::User &user, const memgraph::query::DbAccessor *dba,
const std::vector<memgraph::storage::LabelId> &labels,
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return true;
}
return std::all_of(labels.begin(), labels.end(), [dba, &user, fine_grained_privilege](const auto &label) {
return user.GetFineGrainedAccessLabelPermissions().Has(
dba->LabelToName(label), memgraph::glue::FineGrainedPrivilegeToFineGrainedPermission(
fine_grained_privilege)) == memgraph::auth::PermissionLevel::GRANT;
});
}
bool IsUserAuthorizedGloballyLabels(const memgraph::auth::User &user,
const memgraph::auth::FineGrainedPermission fine_grained_permission) {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return true;
}
return user.GetFineGrainedAccessLabelPermissions().Has(memgraph::auth::kAsterisk, fine_grained_permission) ==
memgraph::auth::PermissionLevel::GRANT;
}
bool IsUserAuthorizedGloballyEdges(const memgraph::auth::User &user,
const memgraph::auth::FineGrainedPermission fine_grained_permission) {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return true;
}
return user.GetFineGrainedAccessEdgeTypePermissions().Has(memgraph::auth::kAsterisk, fine_grained_permission) ==
memgraph::auth::PermissionLevel::GRANT;
}
bool IsUserAuthorizedEdgeType(const memgraph::auth::User &user, const memgraph::query::DbAccessor *dba,
const memgraph::storage::EdgeTypeId &edgeType,
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return true;
}
return user.GetFineGrainedAccessEdgeTypePermissions().Has(
dba->EdgeTypeToName(edgeType), memgraph::glue::FineGrainedPrivilegeToFineGrainedPermission(
fine_grained_privilege)) == memgraph::auth::PermissionLevel::GRANT;
}
} // namespace
#endif
namespace memgraph::glue {
AuthChecker::AuthChecker(
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth)
: auth_(auth) {}
bool AuthChecker::IsUserAuthorized(const std::optional<std::string> &username,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) const {
std::optional<memgraph::auth::User> maybe_user;
{
auto locked_auth = auth_->ReadLock();
if (!locked_auth->HasUsers()) {
return true;
}
if (username.has_value()) {
maybe_user = locked_auth->GetUser(*username);
}
}
return maybe_user.has_value() && IsUserAuthorized(*maybe_user, privileges);
}
#ifdef MG_ENTERPRISE
std::unique_ptr<memgraph::query::FineGrainedAuthChecker> AuthChecker::GetFineGrainedAuthChecker(
const std::string &username, const memgraph::query::DbAccessor *dba) const {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return {};
}
try {
auto locked_auth = auth_->Lock();
auto user = locked_auth->GetUser(username);
if (!user) {
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
}
return std::make_unique<memgraph::glue::FineGrainedAuthChecker>(std::move(*user), dba);
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
#endif
bool AuthChecker::IsUserAuthorized(const memgraph::auth::User &user,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) {
const auto user_permissions = user.GetPermissions();
return std::all_of(privileges.begin(), privileges.end(), [&user_permissions](const auto privilege) {
return user_permissions.Has(memgraph::glue::PrivilegeToPermission(privilege)) ==
memgraph::auth::PermissionLevel::GRANT;
});
}
#ifdef MG_ENTERPRISE
FineGrainedAuthChecker::FineGrainedAuthChecker(auth::User user, const memgraph::query::DbAccessor *dba)
: user_{std::move(user)}, dba_(dba){};
bool FineGrainedAuthChecker::Has(const memgraph::query::VertexAccessor &vertex, const memgraph::storage::View view,
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const {
auto maybe_labels = vertex.Labels(view);
if (maybe_labels.HasError()) {
switch (maybe_labels.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
throw memgraph::query::QueryRuntimeException("Trying to get labels from a deleted node.");
case memgraph::storage::Error::NONEXISTENT_OBJECT:
throw memgraph::query::QueryRuntimeException("Trying to get labels from a node that doesn't exist.");
case memgraph::storage::Error::SERIALIZATION_ERROR:
case memgraph::storage::Error::VERTEX_HAS_EDGES:
case memgraph::storage::Error::PROPERTIES_DISABLED:
throw memgraph::query::QueryRuntimeException("Unexpected error when getting labels.");
}
}
return IsUserAuthorizedLabels(user_, dba_, *maybe_labels, fine_grained_privilege);
}
bool FineGrainedAuthChecker::Has(const memgraph::query::EdgeAccessor &edge,
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const {
return IsUserAuthorizedEdgeType(user_, dba_, edge.EdgeType(), fine_grained_privilege);
}
bool FineGrainedAuthChecker::Has(const std::vector<memgraph::storage::LabelId> &labels,
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const {
return IsUserAuthorizedLabels(user_, dba_, labels, fine_grained_privilege);
}
bool FineGrainedAuthChecker::Has(const memgraph::storage::EdgeTypeId &edge_type,
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const {
return IsUserAuthorizedEdgeType(user_, dba_, edge_type, fine_grained_privilege);
}
bool FineGrainedAuthChecker::HasGlobalPrivilegeOnVertices(
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return true;
}
return IsUserAuthorizedGloballyLabels(user_, FineGrainedPrivilegeToFineGrainedPermission(fine_grained_privilege));
}
bool FineGrainedAuthChecker::HasGlobalPrivilegeOnEdges(
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return true;
}
return IsUserAuthorizedGloballyEdges(user_, FineGrainedPrivilegeToFineGrainedPermission(fine_grained_privilege));
};
#endif
} // namespace memgraph::glue

67
src/glue/auth_checker.hpp Normal file
View File

@@ -0,0 +1,67 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include "auth/auth.hpp"
#include "glue/auth.hpp"
#include "query/auth_checker.hpp"
#include "query/db_accessor.hpp"
#include "query/frontend/ast/ast.hpp"
namespace memgraph::glue {
class AuthChecker : public query::AuthChecker {
public:
explicit AuthChecker(
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth);
bool IsUserAuthorized(const std::optional<std::string> &username,
const std::vector<query::AuthQuery::Privilege> &privileges) const override;
#ifdef MG_ENTERPRISE
std::unique_ptr<memgraph::query::FineGrainedAuthChecker> GetFineGrainedAuthChecker(
const std::string &username, const memgraph::query::DbAccessor *dba) const override;
#endif
[[nodiscard]] static bool IsUserAuthorized(const memgraph::auth::User &user,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges);
private:
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
};
#ifdef MG_ENTERPRISE
class FineGrainedAuthChecker : public query::FineGrainedAuthChecker {
public:
explicit FineGrainedAuthChecker(auth::User user, const memgraph::query::DbAccessor *dba);
bool Has(const query::VertexAccessor &vertex, memgraph::storage::View view,
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const override;
bool Has(const query::EdgeAccessor &edge,
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const override;
bool Has(const std::vector<memgraph::storage::LabelId> &labels,
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const override;
bool Has(const memgraph::storage::EdgeTypeId &edge_type,
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const override;
bool HasGlobalPrivilegeOnVertices(
memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const override;
bool HasGlobalPrivilegeOnEdges(
memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const override;
private:
auth::User user_;
const memgraph::query::DbAccessor *dba_;
};
#endif
} // namespace memgraph::glue

664
src/glue/auth_handler.cpp Normal file
View File

@@ -0,0 +1,664 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "glue/auth_handler.hpp"
#include <sstream>
#include <fmt/format.h>
#include "auth/models.hpp"
#include "glue/auth.hpp"
#include "utils/license.hpp"
namespace {
struct PermissionForPrivilegeResult {
std::string permission;
memgraph::auth::PermissionLevel permission_level;
std::string description;
};
struct FineGrainedPermissionForPrivilegeResult {
std::string permission;
#ifdef MG_ENTERPRISE
memgraph::auth::FineGrainedPermission permission_level;
#endif
std::string description;
};
PermissionForPrivilegeResult GetPermissionForPrivilegeForUserOrRole(
const memgraph::auth::Permissions &permissions, const memgraph::query::AuthQuery::Privilege &privilege,
const std::string &user_or_role) {
PermissionForPrivilegeResult container;
const auto permission = memgraph::glue::PrivilegeToPermission(privilege);
container.permission = memgraph::auth::PermissionToString(permission);
container.permission_level = permissions.Has(permission);
switch (container.permission_level) {
case memgraph::auth::PermissionLevel::GRANT:
container.description = "GRANTED TO " + user_or_role;
break;
case memgraph::auth::PermissionLevel::DENY:
container.description = "DENIED TO " + user_or_role;
break;
case memgraph::auth::PermissionLevel::NEUTRAL:
break;
}
return container;
}
std::vector<std::vector<memgraph::query::TypedValue>> ConstructPrivilegesResult(
const std::vector<PermissionForPrivilegeResult> &privileges) {
std::vector<std::vector<memgraph::query::TypedValue>> grants;
grants.reserve(privileges.size());
for (const auto &permission : privileges) {
grants.push_back({memgraph::query::TypedValue(permission.permission),
memgraph::query::TypedValue(memgraph::auth::PermissionLevelToString(permission.permission_level)),
memgraph::query::TypedValue(permission.description)});
}
return grants;
}
std::vector<std::vector<memgraph::query::TypedValue>> ShowUserPrivileges(
const std::optional<memgraph::auth::User> &user) {
std::vector<PermissionForPrivilegeResult> privilege_results;
const auto &permissions = user->GetPermissions();
const auto &user_level_permissions = user->permissions();
for (const auto &privilege : memgraph::query::kPrivilegesAll) {
auto user_permission_result = GetPermissionForPrivilegeForUserOrRole(permissions, privilege, "USER");
auto user_only_permissions_result =
GetPermissionForPrivilegeForUserOrRole(user_level_permissions, privilege, "USER");
if (user_permission_result.permission_level != memgraph::auth::PermissionLevel::NEUTRAL) {
std::vector<std::string> full_description;
if (user_only_permissions_result.permission_level != memgraph::auth::PermissionLevel::NEUTRAL) {
full_description.emplace_back(user_only_permissions_result.description);
}
if (const auto *role = user->role(); role != nullptr) {
auto role_permission_result = GetPermissionForPrivilegeForUserOrRole(role->permissions(), privilege, "ROLE");
if (role_permission_result.permission_level != memgraph::auth::PermissionLevel::NEUTRAL) {
full_description.emplace_back(role_permission_result.description);
}
}
privilege_results.push_back(PermissionForPrivilegeResult{user_permission_result.permission,
user_permission_result.permission_level,
memgraph::utils::Join(full_description, ", ")});
}
}
return ConstructPrivilegesResult(privilege_results);
}
std::vector<std::vector<memgraph::query::TypedValue>> ShowRolePrivileges(
const std::optional<memgraph::auth::Role> &role) {
std::vector<PermissionForPrivilegeResult> privilege_results;
const auto &permissions = role->permissions();
for (const auto &privilege : memgraph::query::kPrivilegesAll) {
auto role_permission_result = GetPermissionForPrivilegeForUserOrRole(permissions, privilege, "ROLE");
if (role_permission_result.permission_level != memgraph::auth::PermissionLevel::NEUTRAL) {
privilege_results.push_back(role_permission_result);
}
}
return ConstructPrivilegesResult(privilege_results);
}
#ifdef MG_ENTERPRISE
std::vector<FineGrainedPermissionForPrivilegeResult> GetFineGrainedPermissionForPrivilegeForUserOrRole(
const memgraph::auth::FineGrainedAccessPermissions &permissions, const std::string &permission_type,
const std::string &user_or_role) {
std::vector<FineGrainedPermissionForPrivilegeResult> fine_grained_permissions;
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return fine_grained_permissions;
}
const auto global_permission = permissions.GetGlobalPermission();
if (global_permission.has_value()) {
const auto &permission_level = memgraph::auth::PermissionToFineGrainedPermission(global_permission.value());
std::stringstream permission_representation;
permission_representation << "ALL " << permission_type << "S";
const auto &permission_level_representation =
permission_level == memgraph::auth::FineGrainedPermission::NOTHING ? "DENIED" : "GRANTED";
const auto permission_description =
fmt::format("GLOBAL {0} PERMISSION {1} TO {2}", permission_type, permission_level_representation, user_or_role);
fine_grained_permissions.push_back(FineGrainedPermissionForPrivilegeResult{
permission_representation.str(), permission_level, permission_description});
}
for (const auto &[label, permission] : permissions.GetPermissions()) {
auto permission_level = memgraph::auth::PermissionToFineGrainedPermission(permission);
std::stringstream permission_representation;
permission_representation << permission_type << " :" << label;
const auto &permission_level_representation =
permission_level == memgraph::auth::FineGrainedPermission::NOTHING ? "DENIED" : "GRANTED";
const auto permission_description =
fmt::format("{0} PERMISSION {1} TO {2}", permission_type, permission_level_representation, user_or_role);
fine_grained_permissions.push_back(FineGrainedPermissionForPrivilegeResult{
permission_representation.str(), permission_level, permission_description});
}
return fine_grained_permissions;
}
std::vector<std::vector<memgraph::query::TypedValue>> ConstructFineGrainedPrivilegesResult(
const std::vector<FineGrainedPermissionForPrivilegeResult> &privileges) {
std::vector<std::vector<memgraph::query::TypedValue>> grants;
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return {};
}
grants.reserve(privileges.size());
for (const auto &permission : privileges) {
grants.push_back(
{memgraph::query::TypedValue(permission.permission),
memgraph::query::TypedValue(memgraph::auth::FineGrainedPermissionToString(permission.permission_level)),
memgraph::query::TypedValue(permission.description)});
}
return grants;
}
std::vector<std::vector<memgraph::query::TypedValue>> ShowFineGrainedUserPrivileges(
const std::optional<memgraph::auth::User> &user) {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return {};
}
const auto &label_permissions = user->GetFineGrainedAccessLabelPermissions();
const auto &edge_type_permissions = user->GetFineGrainedAccessEdgeTypePermissions();
auto all_fine_grained_permissions =
GetFineGrainedPermissionForPrivilegeForUserOrRole(label_permissions, "LABEL", "USER");
auto edge_type_fine_grained_permissions =
GetFineGrainedPermissionForPrivilegeForUserOrRole(edge_type_permissions, "EDGE_TYPE", "USER");
all_fine_grained_permissions.insert(all_fine_grained_permissions.end(), edge_type_fine_grained_permissions.begin(),
edge_type_fine_grained_permissions.end());
return ConstructFineGrainedPrivilegesResult(all_fine_grained_permissions);
}
std::vector<std::vector<memgraph::query::TypedValue>> ShowFineGrainedRolePrivileges(
const std::optional<memgraph::auth::Role> &role) {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return {};
}
const auto &label_permissions = role->GetFineGrainedAccessLabelPermissions();
const auto &edge_type_permissions = role->GetFineGrainedAccessEdgeTypePermissions();
auto all_fine_grained_permissions =
GetFineGrainedPermissionForPrivilegeForUserOrRole(label_permissions, "LABEL", "USER");
auto edge_type_fine_grained_permissions =
GetFineGrainedPermissionForPrivilegeForUserOrRole(edge_type_permissions, "EDGE_TYPE", "USER");
all_fine_grained_permissions.insert(all_fine_grained_permissions.end(), edge_type_fine_grained_permissions.begin(),
edge_type_fine_grained_permissions.end());
return ConstructFineGrainedPrivilegesResult(all_fine_grained_permissions);
}
#endif
} // namespace
namespace memgraph::glue {
AuthQueryHandler::AuthQueryHandler(
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
std::string name_regex_string)
: auth_(auth), name_regex_string_(std::move(name_regex_string)), name_regex_(name_regex_string_) {}
bool AuthQueryHandler::CreateUser(const std::string &username, const std::optional<std::string> &password) {
if (name_regex_string_ != kDefaultUserRoleRegex) {
if (const auto license_check_result =
memgraph::utils::license::global_license_checker.IsValidLicense(memgraph::utils::global_settings);
license_check_result.HasError()) {
throw memgraph::auth::AuthException(
"Custom user/role regex is a Memgraph Enterprise feature. Please set the config "
"(\"--auth-user-or-role-name-regex\") to its default value (\"{}\") or remove the flag.\n{}",
kDefaultUserRoleRegex,
memgraph::utils::license::LicenseCheckErrorToString(license_check_result.GetError(), "user/role regex"));
}
}
if (!std::regex_match(username, name_regex_)) {
throw query::QueryRuntimeException("Invalid user name.");
}
try {
const auto [first_user, user_added] = std::invoke([&, this] {
auto locked_auth = auth_->Lock();
const auto first_user = !locked_auth->HasUsers();
const auto user_added = locked_auth->AddUser(username, password).has_value();
return std::make_pair(first_user, user_added);
});
if (first_user) {
spdlog::info("{} is first created user. Granting all privileges.", username);
GrantPrivilege(username, memgraph::query::kPrivilegesAll
#ifdef MG_ENTERPRISE
,
{{{memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE, {memgraph::auth::kAsterisk}}}},
{
{
{
memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE, {
memgraph::auth::kAsterisk
}
}
}
}
#endif
);
}
return user_added;
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
bool AuthQueryHandler::DropUser(const std::string &username) {
if (!std::regex_match(username, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user name.");
}
try {
auto locked_auth = auth_->Lock();
auto user = locked_auth->GetUser(username);
if (!user) return false;
return locked_auth->RemoveUser(username);
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
void AuthQueryHandler::SetPassword(const std::string &username, const std::optional<std::string> &password) {
if (!std::regex_match(username, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user name.");
}
try {
auto locked_auth = auth_->Lock();
auto user = locked_auth->GetUser(username);
if (!user) {
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist.", username);
}
user->UpdatePassword(password);
locked_auth->SaveUser(*user);
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
bool AuthQueryHandler::CreateRole(const std::string &rolename) {
if (!std::regex_match(rolename, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid role name.");
}
try {
auto locked_auth = auth_->Lock();
return locked_auth->AddRole(rolename).has_value();
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
bool AuthQueryHandler::DropRole(const std::string &rolename) {
if (!std::regex_match(rolename, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid role name.");
}
try {
auto locked_auth = auth_->Lock();
auto role = locked_auth->GetRole(rolename);
if (!role) {
return false;
};
return locked_auth->RemoveRole(rolename);
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
std::vector<memgraph::query::TypedValue> AuthQueryHandler::GetUsernames() {
try {
auto locked_auth = auth_->ReadLock();
std::vector<memgraph::query::TypedValue> usernames;
const auto &users = locked_auth->AllUsers();
usernames.reserve(users.size());
for (const auto &user : users) {
usernames.emplace_back(user.username());
}
return usernames;
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
std::vector<memgraph::query::TypedValue> AuthQueryHandler::GetRolenames() {
try {
auto locked_auth = auth_->ReadLock();
std::vector<memgraph::query::TypedValue> rolenames;
const auto &roles = locked_auth->AllRoles();
rolenames.reserve(roles.size());
for (const auto &role : roles) {
rolenames.emplace_back(role.rolename());
}
return rolenames;
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
std::optional<std::string> AuthQueryHandler::GetRolenameForUser(const std::string &username) {
if (!std::regex_match(username, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user name.");
}
try {
auto locked_auth = auth_->ReadLock();
auto user = locked_auth->GetUser(username);
if (!user) {
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
}
if (const auto *role = user->role(); role != nullptr) {
return role->rolename();
}
return std::nullopt;
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
std::vector<memgraph::query::TypedValue> AuthQueryHandler::GetUsernamesForRole(const std::string &rolename) {
if (!std::regex_match(rolename, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid role name.");
}
try {
auto locked_auth = auth_->ReadLock();
auto role = locked_auth->GetRole(rolename);
if (!role) {
throw memgraph::query::QueryRuntimeException("Role '{}' doesn't exist.", rolename);
}
std::vector<memgraph::query::TypedValue> usernames;
const auto &users = locked_auth->AllUsersForRole(rolename);
usernames.reserve(users.size());
for (const auto &user : users) {
usernames.emplace_back(user.username());
}
return usernames;
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
void AuthQueryHandler::SetRole(const std::string &username, const std::string &rolename) {
if (!std::regex_match(username, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user name.");
}
if (!std::regex_match(rolename, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid role name.");
}
try {
auto locked_auth = auth_->Lock();
auto user = locked_auth->GetUser(username);
if (!user) {
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
}
auto role = locked_auth->GetRole(rolename);
if (!role) {
throw memgraph::query::QueryRuntimeException("Role '{}' doesn't exist .", rolename);
}
if (const auto *current_role = user->role(); current_role != nullptr) {
throw memgraph::query::QueryRuntimeException("User '{}' is already a member of role '{}'.", username,
current_role->rolename());
}
user->SetRole(*role);
locked_auth->SaveUser(*user);
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
void AuthQueryHandler::ClearRole(const std::string &username) {
if (!std::regex_match(username, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user name.");
}
try {
auto locked_auth = auth_->Lock();
auto user = locked_auth->GetUser(username);
if (!user) {
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
}
user->ClearRole();
locked_auth->SaveUser(*user);
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
std::vector<std::vector<memgraph::query::TypedValue>> AuthQueryHandler::GetPrivileges(const std::string &user_or_role) {
if (!std::regex_match(user_or_role, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user or role name.");
}
try {
auto locked_auth = auth_->ReadLock();
std::vector<std::vector<memgraph::query::TypedValue>> grants;
#ifdef MG_ENTERPRISE
std::vector<std::vector<memgraph::query::TypedValue>> fine_grained_grants;
#endif
auto user = locked_auth->GetUser(user_or_role);
auto role = locked_auth->GetRole(user_or_role);
if (!user && !role) {
throw memgraph::query::QueryRuntimeException("User or role '{}' doesn't exist.", user_or_role);
}
if (user) {
grants = ShowUserPrivileges(user);
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
fine_grained_grants = ShowFineGrainedUserPrivileges(user);
}
#endif
} else {
grants = ShowRolePrivileges(role);
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
fine_grained_grants = ShowFineGrainedRolePrivileges(role);
}
#endif
}
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
grants.insert(grants.end(), fine_grained_grants.begin(), fine_grained_grants.end());
}
#endif
return grants;
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
void AuthQueryHandler::GrantPrivilege(
const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges
#ifdef MG_ENTERPRISE
,
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
&label_privileges,
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
&edge_type_privileges
#endif
) {
EditPermissions(
user_or_role, privileges,
#ifdef MG_ENTERPRISE
label_privileges, edge_type_privileges,
#endif
[](auto &permissions, const auto &permission) {
// TODO (mferencevic): should we first check that the
// privilege is granted/denied/revoked before
// unconditionally granting/denying/revoking it?
permissions.Grant(permission);
}
#ifdef MG_ENTERPRISE
,
[](auto &fine_grained_permissions, const auto &privilege_collection) {
for (const auto &[privilege, entities] : privilege_collection) {
const auto &permission = memgraph::glue::FineGrainedPrivilegeToFineGrainedPermission(privilege);
for (const auto &entity : entities) {
fine_grained_permissions.Grant(entity, permission);
}
}
}
#endif
);
} // namespace memgraph::glue
void AuthQueryHandler::DenyPrivilege(const std::string &user_or_role,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) {
EditPermissions(
user_or_role, privileges,
#ifdef MG_ENTERPRISE
{}, {},
#endif
[](auto &permissions, const auto &permission) {
// TODO (mferencevic): should we first check that the
// privilege is granted/denied/revoked before
// unconditionally granting/denying/revoking it?
permissions.Deny(permission);
}
#ifdef MG_ENTERPRISE
,
[](auto &fine_grained_permissions, const auto &privilege_collection) {}
#endif
);
}
void AuthQueryHandler::RevokePrivilege(
const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges
#ifdef MG_ENTERPRISE
,
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
&label_privileges,
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
&edge_type_privileges
#endif
) {
EditPermissions(
user_or_role, privileges,
#ifdef MG_ENTERPRISE
label_privileges, edge_type_privileges,
#endif
[](auto &permissions, const auto &permission) {
// TODO (mferencevic): should we first check that the
// privilege is granted/denied/revoked before
// unconditionally granting/denying/revoking it?
permissions.Revoke(permission);
}
#ifdef MG_ENTERPRISE
,
[](auto &fine_grained_permissions, const auto &privilege_collection) {
for ([[maybe_unused]] const auto &[privilege, entities] : privilege_collection) {
for (const auto &entity : entities) {
fine_grained_permissions.Revoke(entity);
}
}
}
#endif
);
} // namespace memgraph::glue
template <class TEditPermissionsFun
#ifdef MG_ENTERPRISE
,
class TEditFineGrainedPermissionsFun
#endif
>
void AuthQueryHandler::EditPermissions(
const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges
#ifdef MG_ENTERPRISE
,
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
&label_privileges,
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
&edge_type_privileges
#endif
,
const TEditPermissionsFun &edit_permissions_fun
#ifdef MG_ENTERPRISE
,
const TEditFineGrainedPermissionsFun &edit_fine_grained_permissions_fun
#endif
) {
if (!std::regex_match(user_or_role, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user or role name.");
}
try {
std::vector<memgraph::auth::Permission> permissions;
permissions.reserve(privileges.size());
for (const auto &privilege : privileges) {
permissions.push_back(memgraph::glue::PrivilegeToPermission(privilege));
}
auto locked_auth = auth_->Lock();
auto user = locked_auth->GetUser(user_or_role);
auto role = locked_auth->GetRole(user_or_role);
if (!user && !role) {
throw memgraph::query::QueryRuntimeException("User or role '{}' doesn't exist.", user_or_role);
}
if (user) {
for (const auto &permission : permissions) {
edit_permissions_fun(user->permissions(), permission);
}
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
for (const auto &label_privilege_collection : label_privileges) {
edit_fine_grained_permissions_fun(user->fine_grained_access_handler().label_permissions(),
label_privilege_collection);
}
for (const auto &edge_type_privilege_collection : edge_type_privileges) {
edit_fine_grained_permissions_fun(user->fine_grained_access_handler().edge_type_permissions(),
edge_type_privilege_collection);
}
}
#endif
locked_auth->SaveUser(*user);
} else {
for (const auto &permission : permissions) {
edit_permissions_fun(role->permissions(), permission);
}
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
for (const auto &label_privilege : label_privileges) {
edit_fine_grained_permissions_fun(user->fine_grained_access_handler().label_permissions(), label_privilege);
}
for (const auto &edge_type_privilege : edge_type_privileges) {
edit_fine_grained_permissions_fun(role->fine_grained_access_handler().edge_type_permissions(),
edge_type_privilege);
}
}
#endif
locked_auth->SaveRole(*role);
}
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
} // namespace memgraph::glue

109
src/glue/auth_handler.hpp Normal file
View File

@@ -0,0 +1,109 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <regex>
#include "auth/auth.hpp"
#include "glue/auth.hpp"
#include "query/interpreter.hpp"
#include "utils/license.hpp"
#include "utils/string.hpp"
namespace memgraph::glue {
inline constexpr std::string_view kDefaultUserRoleRegex = "[a-zA-Z0-9_.+-@]+";
class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
std::string name_regex_string_;
std::regex name_regex_;
public:
AuthQueryHandler(memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
std::string name_regex_string);
bool CreateUser(const std::string &username, const std::optional<std::string> &password) override;
bool DropUser(const std::string &username) override;
void SetPassword(const std::string &username, const std::optional<std::string> &password) override;
bool CreateRole(const std::string &rolename) override;
bool DropRole(const std::string &rolename) override;
std::vector<memgraph::query::TypedValue> GetUsernames() override;
std::vector<memgraph::query::TypedValue> GetRolenames() override;
std::optional<std::string> GetRolenameForUser(const std::string &username) override;
std::vector<memgraph::query::TypedValue> GetUsernamesForRole(const std::string &rolename) override;
void SetRole(const std::string &username, const std::string &rolename) override;
void ClearRole(const std::string &username) override;
std::vector<std::vector<memgraph::query::TypedValue>> GetPrivileges(const std::string &user_or_role) override;
void GrantPrivilege(
const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges
#ifdef MG_ENTERPRISE
,
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
&label_privileges,
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
&edge_type_privileges
#endif
) override;
void DenyPrivilege(const std::string &user_or_role,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) override;
void RevokePrivilege(
const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges
#ifdef MG_ENTERPRISE
,
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
&label_privileges,
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
&edge_type_privileges
#endif
) override;
private:
template <class TEditPermissionsFun
#ifdef MG_ENTERPRISE
,
class TEditFineGrainedPermissionsFun
#endif
>
void EditPermissions(
const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges
#ifdef MG_ENTERPRISE
,
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
&label_privileges,
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
&edge_type_privileges
#endif
,
const TEditPermissionsFun &edit_permissions_fun
#ifdef MG_ENTERPRISE
,
const TEditFineGrainedPermissionsFun &edit_fine_grained_permissions_fun
#endif
);
};
} // namespace memgraph::glue

View File

@@ -127,6 +127,10 @@ storage::Result<Value> ToBoltValue(const query::TypedValue &value, const storage
return Value(value.ValueLocalDateTime());
case query::TypedValue::Type::Duration:
return Value(value.ValueDuration());
case query::TypedValue::Type::Graph:
auto maybe_graph = ToBoltGraph(value.ValueGraph(), db, view);
if (maybe_graph.HasError()) return maybe_graph.GetError();
return Value(std::move(*maybe_graph));
}
}
@@ -183,6 +187,30 @@ storage::Result<communication::bolt::Path> ToBoltPath(const query::Path &path, c
return communication::bolt::Path(vertices, edges);
}
storage::Result<std::map<std::string, Value>> ToBoltGraph(const query::Graph &graph, const storage::Storage &db,
storage::View view) {
std::map<std::string, Value> map;
std::vector<Value> vertices;
vertices.reserve(graph.vertices().size());
for (const auto &v : graph.vertices()) {
auto maybe_vertex = ToBoltVertex(v, db, view);
if (maybe_vertex.HasError()) return maybe_vertex.GetError();
vertices.emplace_back(Value(std::move(*maybe_vertex)));
}
map.emplace("nodes", Value(vertices));
std::vector<Value> edges;
edges.reserve(graph.edges().size());
for (const auto &e : graph.edges()) {
auto maybe_edge = ToBoltEdge(e, db, view);
if (maybe_edge.HasError()) return maybe_edge.GetError();
edges.emplace_back(Value(std::move(*maybe_edge)));
}
map.emplace("edges", Value(edges));
return std::move(map);
}
storage::PropertyValue ToPropertyValue(const Value &value) {
switch (value.type()) {
case Value::Type::Null:

View File

@@ -51,6 +51,15 @@ storage::Result<communication::bolt::Edge> ToBoltEdge(const storage::EdgeAccesso
storage::Result<communication::bolt::Path> ToBoltPath(const query::Path &path, const storage::Storage &db,
storage::View view);
/// @param query::Graph for converting to communication::bolt::Map.
/// @param storage::Storage for ToBoltVertex and ToBoltEdge.
/// @param storage::View for ToBoltVertex and ToBoltEdge.
///
/// @throw std::bad_alloc
storage::Result<std::map<std::string, communication::bolt::Value>> ToBoltGraph(const query::Graph &graph,
const storage::Storage &db,
storage::View view);
/// @param query::TypedValue for converting to communication::bolt::Value.
/// @param storage::Storage for ToBoltVertex and ToBoltEdge.
/// @param storage::View for ToBoltVertex and ToBoltEdge.

View File

@@ -19,6 +19,7 @@
#include <functional>
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <regex>
#include <string>
@@ -32,9 +33,12 @@
#include <spdlog/sinks/dist_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include "auth/models.hpp"
#include "communication/bolt/v1/constants.hpp"
#include "communication/websocket/auth.hpp"
#include "communication/websocket/server.hpp"
#include "glue/auth_checker.hpp"
#include "glue/auth_handler.hpp"
#include "helpers.hpp"
#include "py/py.hpp"
#include "query/auth_checker.hpp"
@@ -164,8 +168,6 @@ DEFINE_string(bolt_server_name_for_init, "",
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_string(data_directory, "mg_data", "Path to directory in which to save all permanent data.");
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_HIDDEN_string(log_link_basename, "", "Basename used for symlink creation to the last log file.");
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_uint64(memory_warning_threshold, 1024,
"Memory warning threshold, in MB. If Memgraph detects there is "
"less available RAM it will log a warning. Set to 0 to "
@@ -216,11 +218,6 @@ DEFINE_bool(telemetry_enabled, false,
"the database runtime (vertex and edge counts and resource usage) "
"to allow for easier improvement of the product.");
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_bool(storage_restore_replicas_on_startup, true,
"Controls replicas should be restored automatically."); // TODO(42jeremy) this must be removed once T0835
// is implemented.
// Streams flags
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_uint32(
@@ -354,7 +351,8 @@ DEFINE_VALIDATED_string(query_modules_directory, "",
});
// Logging flags
DEFINE_bool(also_log_to_stderr, false, "Log messages go to stderr in addition to logfiles");
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_HIDDEN_bool(also_log_to_stderr, false, "Log messages go to stderr in addition to logfiles");
DEFINE_string(log_file, "", "Path to where the log should be stored.");
namespace {
@@ -434,9 +432,9 @@ void AddLoggerSink(spdlog::sink_ptr new_sink) {
} // namespace
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_string(license_key, "", "License key for Memgraph Enterprise.");
DEFINE_HIDDEN_string(license_key, "", "License key for Memgraph Enterprise.");
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_string(organization_name, "", "Organization name.");
DEFINE_HIDDEN_string(organization_name, "", "Organization name.");
/// Encapsulates Dbms and Interpreter that are passed through the network server
/// and worker to the session.
@@ -464,394 +462,14 @@ struct SessionData {
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth;
#endif
// NOTE: run_id should be const but that complicates code a lot.
std::optional<std::string> run_id;
};
inline constexpr std::string_view default_user_role_regex = "[a-zA-Z0-9_.+-@]+";
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_string(auth_user_or_role_name_regex, default_user_role_regex.data(),
DEFINE_string(auth_user_or_role_name_regex, memgraph::glue::kDefaultUserRoleRegex.data(),
"Set to the regular expression that each user or role name must fulfill.");
class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
std::string name_regex_string_;
std::regex name_regex_;
public:
AuthQueryHandler(memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
std::string name_regex_string)
: auth_(auth), name_regex_string_(std::move(name_regex_string)), name_regex_(name_regex_string_) {}
bool CreateUser(const std::string &username, const std::optional<std::string> &password) override {
if (name_regex_string_ != default_user_role_regex) {
if (const auto license_check_result =
memgraph::utils::license::global_license_checker.IsValidLicense(memgraph::utils::global_settings);
license_check_result.HasError()) {
throw memgraph::auth::AuthException(
"Custom user/role regex is a Memgraph Enterprise feature. Please set the config "
"(\"--auth-user-or-role-name-regex\") to its default value (\"{}\") or remove the flag.\n{}",
default_user_role_regex,
memgraph::utils::license::LicenseCheckErrorToString(license_check_result.GetError(), "user/role regex"));
}
}
if (!std::regex_match(username, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user name.");
}
try {
const auto [first_user, user_added] = std::invoke([&, this] {
auto locked_auth = auth_->Lock();
const auto first_user = !locked_auth->HasUsers();
const auto user_added = locked_auth->AddUser(username, password).has_value();
return std::make_pair(first_user, user_added);
});
if (first_user) {
spdlog::info("{} is first created user. Granting all privileges.", username);
GrantPrivilege(username, memgraph::query::kPrivilegesAll);
}
return user_added;
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
bool DropUser(const std::string &username) override {
if (!std::regex_match(username, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user name.");
}
try {
auto locked_auth = auth_->Lock();
auto user = locked_auth->GetUser(username);
if (!user) return false;
return locked_auth->RemoveUser(username);
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
void SetPassword(const std::string &username, const std::optional<std::string> &password) override {
if (!std::regex_match(username, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user name.");
}
try {
auto locked_auth = auth_->Lock();
auto user = locked_auth->GetUser(username);
if (!user) {
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist.", username);
}
user->UpdatePassword(password);
locked_auth->SaveUser(*user);
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
bool CreateRole(const std::string &rolename) override {
if (!std::regex_match(rolename, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid role name.");
}
try {
auto locked_auth = auth_->Lock();
return locked_auth->AddRole(rolename).has_value();
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
bool DropRole(const std::string &rolename) override {
if (!std::regex_match(rolename, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid role name.");
}
try {
auto locked_auth = auth_->Lock();
auto role = locked_auth->GetRole(rolename);
if (!role) return false;
return locked_auth->RemoveRole(rolename);
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
std::vector<memgraph::query::TypedValue> GetUsernames() override {
try {
auto locked_auth = auth_->ReadLock();
std::vector<memgraph::query::TypedValue> usernames;
const auto &users = locked_auth->AllUsers();
usernames.reserve(users.size());
for (const auto &user : users) {
usernames.emplace_back(user.username());
}
return usernames;
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
std::vector<memgraph::query::TypedValue> GetRolenames() override {
try {
auto locked_auth = auth_->ReadLock();
std::vector<memgraph::query::TypedValue> rolenames;
const auto &roles = locked_auth->AllRoles();
rolenames.reserve(roles.size());
for (const auto &role : roles) {
rolenames.emplace_back(role.rolename());
}
return rolenames;
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
std::optional<std::string> GetRolenameForUser(const std::string &username) override {
if (!std::regex_match(username, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user name.");
}
try {
auto locked_auth = auth_->ReadLock();
auto user = locked_auth->GetUser(username);
if (!user) {
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
}
if (const auto *role = user->role(); role != nullptr) {
return role->rolename();
}
return std::nullopt;
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
std::vector<memgraph::query::TypedValue> GetUsernamesForRole(const std::string &rolename) override {
if (!std::regex_match(rolename, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid role name.");
}
try {
auto locked_auth = auth_->ReadLock();
auto role = locked_auth->GetRole(rolename);
if (!role) {
throw memgraph::query::QueryRuntimeException("Role '{}' doesn't exist.", rolename);
}
std::vector<memgraph::query::TypedValue> usernames;
const auto &users = locked_auth->AllUsersForRole(rolename);
usernames.reserve(users.size());
for (const auto &user : users) {
usernames.emplace_back(user.username());
}
return usernames;
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
void SetRole(const std::string &username, const std::string &rolename) override {
if (!std::regex_match(username, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user name.");
}
if (!std::regex_match(rolename, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid role name.");
}
try {
auto locked_auth = auth_->Lock();
auto user = locked_auth->GetUser(username);
if (!user) {
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
}
auto role = locked_auth->GetRole(rolename);
if (!role) {
throw memgraph::query::QueryRuntimeException("Role '{}' doesn't exist .", rolename);
}
if (const auto *current_role = user->role(); current_role != nullptr) {
throw memgraph::query::QueryRuntimeException("User '{}' is already a member of role '{}'.", username,
current_role->rolename());
}
user->SetRole(*role);
locked_auth->SaveUser(*user);
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
void ClearRole(const std::string &username) override {
if (!std::regex_match(username, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user name.");
}
try {
auto locked_auth = auth_->Lock();
auto user = locked_auth->GetUser(username);
if (!user) {
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
}
user->ClearRole();
locked_auth->SaveUser(*user);
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
std::vector<std::vector<memgraph::query::TypedValue>> GetPrivileges(const std::string &user_or_role) override {
if (!std::regex_match(user_or_role, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user or role name.");
}
try {
auto locked_auth = auth_->ReadLock();
std::vector<std::vector<memgraph::query::TypedValue>> grants;
auto user = locked_auth->GetUser(user_or_role);
auto role = locked_auth->GetRole(user_or_role);
if (!user && !role) {
throw memgraph::query::QueryRuntimeException("User or role '{}' doesn't exist.", user_or_role);
}
if (user) {
const auto &permissions = user->GetPermissions();
for (const auto &privilege : memgraph::query::kPrivilegesAll) {
auto permission = memgraph::glue::PrivilegeToPermission(privilege);
auto effective = permissions.Has(permission);
if (permissions.Has(permission) != memgraph::auth::PermissionLevel::NEUTRAL) {
std::vector<std::string> description;
auto user_level = user->permissions().Has(permission);
if (user_level == memgraph::auth::PermissionLevel::GRANT) {
description.emplace_back("GRANTED TO USER");
} else if (user_level == memgraph::auth::PermissionLevel::DENY) {
description.emplace_back("DENIED TO USER");
}
if (const auto *role = user->role(); role != nullptr) {
auto role_level = role->permissions().Has(permission);
if (role_level == memgraph::auth::PermissionLevel::GRANT) {
description.emplace_back("GRANTED TO ROLE");
} else if (role_level == memgraph::auth::PermissionLevel::DENY) {
description.emplace_back("DENIED TO ROLE");
}
}
grants.push_back({memgraph::query::TypedValue(memgraph::auth::PermissionToString(permission)),
memgraph::query::TypedValue(memgraph::auth::PermissionLevelToString(effective)),
memgraph::query::TypedValue(memgraph::utils::Join(description, ", "))});
}
}
} else {
const auto &permissions = role->permissions();
for (const auto &privilege : memgraph::query::kPrivilegesAll) {
auto permission = memgraph::glue::PrivilegeToPermission(privilege);
auto effective = permissions.Has(permission);
if (effective != memgraph::auth::PermissionLevel::NEUTRAL) {
std::string description;
if (effective == memgraph::auth::PermissionLevel::GRANT) {
description = "GRANTED TO ROLE";
} else if (effective == memgraph::auth::PermissionLevel::DENY) {
description = "DENIED TO ROLE";
}
grants.push_back({memgraph::query::TypedValue(memgraph::auth::PermissionToString(permission)),
memgraph::query::TypedValue(memgraph::auth::PermissionLevelToString(effective)),
memgraph::query::TypedValue(description)});
}
}
}
return grants;
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
void GrantPrivilege(const std::string &user_or_role,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) override {
EditPermissions(user_or_role, privileges, [](auto *permissions, const auto &permission) {
// TODO (mferencevic): should we first check that the
// privilege is granted/denied/revoked before
// unconditionally granting/denying/revoking it?
permissions->Grant(permission);
});
}
void DenyPrivilege(const std::string &user_or_role,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) override {
EditPermissions(user_or_role, privileges, [](auto *permissions, const auto &permission) {
// TODO (mferencevic): should we first check that the
// privilege is granted/denied/revoked before
// unconditionally granting/denying/revoking it?
permissions->Deny(permission);
});
}
void RevokePrivilege(const std::string &user_or_role,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) override {
EditPermissions(user_or_role, privileges, [](auto *permissions, const auto &permission) {
// TODO (mferencevic): should we first check that the
// privilege is granted/denied/revoked before
// unconditionally granting/denying/revoking it?
permissions->Revoke(permission);
});
}
private:
template <class TEditFun>
void EditPermissions(const std::string &user_or_role,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges, const TEditFun &edit_fun) {
if (!std::regex_match(user_or_role, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user or role name.");
}
try {
std::vector<memgraph::auth::Permission> permissions;
permissions.reserve(privileges.size());
for (const auto &privilege : privileges) {
permissions.push_back(memgraph::glue::PrivilegeToPermission(privilege));
}
auto locked_auth = auth_->Lock();
auto user = locked_auth->GetUser(user_or_role);
auto role = locked_auth->GetRole(user_or_role);
if (!user && !role) {
throw memgraph::query::QueryRuntimeException("User or role '{}' doesn't exist.", user_or_role);
}
if (user) {
for (const auto &permission : permissions) {
edit_fun(&user->permissions(), permission);
}
locked_auth->SaveUser(*user);
} else {
for (const auto &permission : permissions) {
edit_fun(&role->permissions(), permission);
}
locked_auth->SaveRole(*role);
}
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
};
class AuthChecker final : public memgraph::query::AuthChecker {
public:
explicit AuthChecker(
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth)
: auth_{auth} {}
static bool IsUserAuthorized(const memgraph::auth::User &user,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) {
const auto user_permissions = user.GetPermissions();
return std::all_of(privileges.begin(), privileges.end(), [&user_permissions](const auto privilege) {
return user_permissions.Has(memgraph::glue::PrivilegeToPermission(privilege)) ==
memgraph::auth::PermissionLevel::GRANT;
});
}
bool IsUserAuthorized(const std::optional<std::string> &username,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) const final {
std::optional<memgraph::auth::User> maybe_user;
{
auto locked_auth = auth_->ReadLock();
if (!locked_auth->HasUsers()) {
return true;
}
if (username.has_value()) {
maybe_user = locked_auth->GetUser(*username);
}
}
return maybe_user.has_value() && IsUserAuthorized(*maybe_user, privileges);
}
private:
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
};
class BoltSession final : public memgraph::communication::bolt::Session<memgraph::communication::v2::InputStream,
memgraph::communication::v2::OutputStream> {
public:
@@ -866,7 +484,8 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
#if MG_ENTERPRISE
audit_log_(data->audit_log),
#endif
endpoint_(endpoint) {
endpoint_(endpoint),
run_id_(data->run_id) {
}
using memgraph::communication::bolt::Session<memgraph::communication::v2::InputStream,
@@ -894,7 +513,7 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
#endif
try {
auto result = interpreter_.Prepare(query, params_pv, username);
if (user_ && !AuthChecker::IsUserAuthorized(*user_, result.privileges)) {
if (user_ && !memgraph::glue::AuthChecker::IsUserAuthorized(*user_, result.privileges)) {
interpreter_.Abort();
throw memgraph::communication::bolt::ClientError(
"You are not authorized to execute this query! Please contact "
@@ -906,6 +525,8 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
// Wrap QueryException into ClientError, because we want to allow the
// client to fix their query.
throw memgraph::communication::bolt::ClientError(e.what());
} catch (const memgraph::query::ReplicationException &e) {
throw memgraph::communication::bolt::ClientError(e.what());
}
}
@@ -958,6 +579,14 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
}
decoded_summary.emplace(kv.first, std::move(*maybe_value));
}
// Add this memgraph instance run_id, received from telemetry
// This is sent with every query, instead of only on bolt init inside
// communication/bolt/v1/states/init.hpp because neo4jdriver does not
// read the init message.
if (auto run_id = run_id_; run_id) {
decoded_summary.emplace("run_id", *run_id);
}
return decoded_summary;
} catch (const memgraph::query::QueryException &e) {
// Wrap QueryException into ClientError, because we want to allow the
@@ -1009,6 +638,8 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
memgraph::audit::Log *audit_log_;
#endif
memgraph::communication::v2::ServerEndpoint endpoint_;
// NOTE: run_id should be const but that complicates code a lot.
std::optional<std::string> run_id_;
};
using ServerT = memgraph::communication::v2::Server<BoltSession, SessionData>;
@@ -1201,7 +832,7 @@ int main(int argc, char **argv) {
.wal_file_size_kibibytes = FLAGS_storage_wal_file_size_kib,
.wal_file_flush_every_n_tx = FLAGS_storage_wal_file_flush_every_n_tx,
.snapshot_on_exit = FLAGS_storage_snapshot_on_exit,
.restore_replicas_on_startup = FLAGS_storage_restore_replicas_on_startup},
.restore_replicas_on_startup = true},
.transaction = {.isolation_level = ParseIsolationLevel()}};
if (FLAGS_storage_snapshot_interval_sec == 0) {
if (FLAGS_storage_wal_enabled) {
@@ -1242,8 +873,8 @@ int main(int argc, char **argv) {
memgraph::query::procedure::gModuleRegistry.SetModulesDirectory(query_modules_directories, FLAGS_data_directory);
memgraph::query::procedure::gModuleRegistry.UnloadAndLoadModulesFromDirectories();
AuthQueryHandler auth_handler(&auth, FLAGS_auth_user_or_role_name_regex);
AuthChecker auth_checker{&auth};
memgraph::glue::AuthQueryHandler auth_handler(&auth, FLAGS_auth_user_or_role_name_regex);
memgraph::glue::AuthChecker auth_checker{&auth};
interpreter_context.auth = &auth_handler;
interpreter_context.auth_checker = &auth_checker;
@@ -1280,6 +911,7 @@ int main(int argc, char **argv) {
if (FLAGS_telemetry_enabled) {
telemetry.emplace("https://telemetry.memgraph.com/88b5e7e8-746a-11e8-9f85-538a9e9690cc/",
data_directory / "telemetry", std::chrono::minutes(10));
session_data.run_id = telemetry->GetRunId();
telemetry->AddCollector("storage", [&db]() -> nlohmann::json {
auto info = db.GetInfo();
return {{"vertices", info.vertex_count}, {"edges", info.edge_count}};

View File

@@ -39,7 +39,9 @@ set(mg_query_sources
stream/common.cpp
trigger.cpp
trigger_context.cpp
typed_value.cpp)
typed_value.cpp
graph.cpp
db_accessor.cpp)
find_package(Boost REQUIRED)

View File

@@ -11,19 +11,97 @@
#pragma once
#include "query/db_accessor.hpp"
#include "query/frontend/ast/ast.hpp"
#include "storage/v2/id_types.hpp"
namespace memgraph::query {
class FineGrainedAuthChecker;
class AuthChecker {
public:
virtual bool IsUserAuthorized(const std::optional<std::string> &username,
const std::vector<query::AuthQuery::Privilege> &privileges) const = 0;
virtual ~AuthChecker() = default;
[[nodiscard]] virtual bool IsUserAuthorized(const std::optional<std::string> &username,
const std::vector<query::AuthQuery::Privilege> &privileges) const = 0;
#ifdef MG_ENTERPRISE
[[nodiscard]] virtual std::unique_ptr<FineGrainedAuthChecker> GetFineGrainedAuthChecker(
const std::string &username, const memgraph::query::DbAccessor *db_accessor) const = 0;
#endif
};
#ifdef MG_ENTERPRISE
class FineGrainedAuthChecker {
public:
virtual ~FineGrainedAuthChecker() = default;
[[nodiscard]] virtual bool Has(const query::VertexAccessor &vertex, memgraph::storage::View view,
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const = 0;
[[nodiscard]] virtual bool Has(const query::EdgeAccessor &edge,
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const = 0;
[[nodiscard]] virtual bool Has(const std::vector<memgraph::storage::LabelId> &labels,
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const = 0;
[[nodiscard]] virtual bool Has(const memgraph::storage::EdgeTypeId &edge_type,
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const = 0;
[[nodiscard]] virtual bool HasGlobalPrivilegeOnVertices(
memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const = 0;
[[nodiscard]] virtual bool HasGlobalPrivilegeOnEdges(
memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const = 0;
};
class AllowEverythingAuthChecker final : public query::AuthChecker {
bool IsUserAuthorized(const std::optional<std::string> &username,
const std::vector<query::AuthQuery::Privilege> &privileges) const override {
class AllowEverythingFineGrainedAuthChecker final : public query::FineGrainedAuthChecker {
public:
bool Has(const VertexAccessor & /*vertex*/, const memgraph::storage::View /*view*/,
const query::AuthQuery::FineGrainedPrivilege /*fine_grained_privilege*/) const override {
return true;
}
};
} // namespace memgraph::query
bool Has(const memgraph::query::EdgeAccessor & /*edge*/,
const query::AuthQuery::FineGrainedPrivilege /*fine_grained_privilege*/) const override {
return true;
}
bool Has(const std::vector<memgraph::storage::LabelId> & /*labels*/,
const query::AuthQuery::FineGrainedPrivilege /*fine_grained_privilege*/) const override {
return true;
}
bool Has(const memgraph::storage::EdgeTypeId & /*edge_type*/,
const query::AuthQuery::FineGrainedPrivilege /*fine_grained_privilege*/) const override {
return true;
}
bool HasGlobalPrivilegeOnVertices(
const memgraph::query::AuthQuery::FineGrainedPrivilege /*fine_grained_privilege*/) const override {
return true;
}
bool HasGlobalPrivilegeOnEdges(
const memgraph::query::AuthQuery::FineGrainedPrivilege /*fine_grained_privilege*/) const override {
return true;
}
}; // namespace memgraph::query
#endif
class AllowEverythingAuthChecker final : public query::AuthChecker {
public:
bool IsUserAuthorized(const std::optional<std::string> & /*username*/,
const std::vector<query::AuthQuery::Privilege> & /*privileges*/) const override {
return true;
}
#ifdef MG_ENTERPRISE
std::unique_ptr<FineGrainedAuthChecker> GetFineGrainedAuthChecker(const std::string & /*username*/,
const query::DbAccessor * /*dba*/) const override {
return std::make_unique<AllowEverythingFineGrainedAuthChecker>();
}
#endif
}; // namespace memgraph::query
} // namespace memgraph::query

View File

@@ -61,6 +61,7 @@ bool TypedValueCompare(const TypedValue &a, const TypedValue &b) {
case TypedValue::Type::Vertex:
case TypedValue::Type::Edge:
case TypedValue::Type::Path:
case TypedValue::Type::Graph:
throw QueryRuntimeException("Comparison is not defined for values of type {}.", a.type());
case TypedValue::Type::Null:
LOG_FATAL("Invalid type");

View File

@@ -11,6 +11,7 @@
#pragma once
#include <memory>
#include <type_traits>
#include "query/common.hpp"
@@ -72,6 +73,9 @@ struct ExecutionContext {
ExecutionStats execution_stats;
TriggerContextCollector *trigger_context_collector{nullptr};
utils::AsyncTimer timer;
#ifdef MG_ENTERPRISE
std::unique_ptr<FineGrainedAuthChecker> auth_checker{nullptr};
#endif
};
static_assert(std::is_move_assignable_v<ExecutionContext>, "ExecutionContext must be move assignable!");

View File

@@ -12,7 +12,7 @@
#include "query/cypher_query_interpreter.hpp"
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_HIDDEN_bool(query_cost_planner, true, "Use the cost-estimating query planner.");
DEFINE_bool(query_cost_planner, true, "Use the cost-estimating query planner.");
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_VALIDATED_int32(query_plan_cache_ttl, 60, "Time to live for cached query plans, in seconds.",
FLAG_IN_RANGE(0, std::numeric_limits<int32_t>::max()));

148
src/query/db_accessor.cpp Normal file
View File

@@ -0,0 +1,148 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "query/db_accessor.hpp"
#include "query/graph.hpp"
#include <cppitertools/filter.hpp>
#include <cppitertools/imap.hpp>
#include "utils/pmr/unordered_set.hpp"
namespace memgraph::query {
SubgraphDbAccessor::SubgraphDbAccessor(query::DbAccessor db_accessor, Graph *graph)
: db_accessor_(db_accessor), graph_(graph) {}
storage::PropertyId SubgraphDbAccessor::NameToProperty(const std::string_view name) {
return db_accessor_.NameToProperty(name);
}
storage::LabelId SubgraphDbAccessor::NameToLabel(const std::string_view name) { return db_accessor_.NameToLabel(name); }
storage::EdgeTypeId SubgraphDbAccessor::NameToEdgeType(const std::string_view name) {
return db_accessor_.NameToEdgeType(name);
}
const std::string &SubgraphDbAccessor::PropertyToName(storage::PropertyId prop) const {
return db_accessor_.PropertyToName(prop);
}
const std::string &SubgraphDbAccessor::LabelToName(storage::LabelId label) const {
return db_accessor_.LabelToName(label);
}
const std::string &SubgraphDbAccessor::EdgeTypeToName(storage::EdgeTypeId type) const {
return db_accessor_.EdgeTypeToName(type);
}
storage::Result<std::optional<EdgeAccessor>> SubgraphDbAccessor::RemoveEdge(EdgeAccessor *edge) {
if (!this->graph_->ContainsEdge(*edge)) {
throw std::logic_error{"Projected graph must contain edge!"};
}
auto result = db_accessor_.RemoveEdge(edge);
if (result.HasError() || !*result) {
return result;
}
return this->graph_->RemoveEdge(*edge);
}
storage::Result<EdgeAccessor> SubgraphDbAccessor::InsertEdge(SubgraphVertexAccessor *from, SubgraphVertexAccessor *to,
const storage::EdgeTypeId &edge_type) {
VertexAccessor *from_impl = &from->impl_;
VertexAccessor *to_impl = &to->impl_;
if (!this->graph_->ContainsVertex(*from_impl) || !this->graph_->ContainsVertex(*to_impl)) {
throw std::logic_error{"Projected graph must contain both vertices to insert edge!"};
}
auto result = db_accessor_.InsertEdge(from_impl, to_impl, edge_type);
if (result.HasError()) {
return result;
}
this->graph_->InsertEdge(*result);
return result;
}
storage::Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>>
SubgraphDbAccessor::DetachRemoveVertex( // NOLINT(readability-convert-member-functions-to-static)
SubgraphVertexAccessor *) { // NOLINT(hicpp-named-parameter)
throw std::logic_error{
"Vertex holds only partial information about edges. Cannot detach delete safely while using projected graph."};
}
storage::Result<std::optional<VertexAccessor>> SubgraphDbAccessor::RemoveVertex(
SubgraphVertexAccessor *subgraphvertex_accessor) {
VertexAccessor *vertex_accessor = &subgraphvertex_accessor->impl_;
if (!this->graph_->ContainsVertex(*vertex_accessor)) {
throw std::logic_error{"Projected graph must contain vertex!"};
}
auto result = db_accessor_.RemoveVertex(vertex_accessor);
if (result.HasError() || !*result) {
return result;
}
return this->graph_->RemoveVertex(*vertex_accessor);
}
SubgraphVertexAccessor SubgraphDbAccessor::InsertVertex() {
VertexAccessor vertex = db_accessor_.InsertVertex();
this->graph_->InsertVertex(vertex);
return SubgraphVertexAccessor(vertex, this->getGraph());
}
VerticesIterable SubgraphDbAccessor::Vertices(storage::View) { // NOLINT(hicpp-named-parameter)
return VerticesIterable(&graph_->vertices());
}
std::optional<VertexAccessor> SubgraphDbAccessor::FindVertex(storage::Gid gid, storage::View view) {
std::optional<VertexAccessor> maybe_vertex = db_accessor_.FindVertex(gid, view);
if (maybe_vertex && this->graph_->ContainsVertex(*maybe_vertex)) {
return *maybe_vertex;
}
return std::nullopt;
}
query::Graph *SubgraphDbAccessor::getGraph() { return graph_; }
VertexAccessor SubgraphVertexAccessor::GetVertexAccessor() const { return impl_; }
auto SubgraphVertexAccessor::OutEdges(storage::View view) const -> decltype(impl_.OutEdges(view)) {
auto maybe_edges = impl_.impl_.OutEdges(view, {});
if (maybe_edges.HasError()) return maybe_edges.GetError();
auto edges = std::move(*maybe_edges);
auto graph_edges = graph_->edges();
std::vector<storage::EdgeAccessor> filteredOutEdges;
for (auto &edge : edges) {
auto edge_q = EdgeAccessor(edge);
if (graph_edges.contains(edge_q)) {
filteredOutEdges.push_back(edge);
}
}
return iter::imap(VertexAccessor::MakeEdgeAccessor, std::move(filteredOutEdges));
}
auto SubgraphVertexAccessor::InEdges(storage::View view) const -> decltype(impl_.InEdges(view)) {
auto maybe_edges = impl_.impl_.InEdges(view, {});
if (maybe_edges.HasError()) return maybe_edges.GetError();
auto edges = std::move(*maybe_edges);
auto graph_edges = graph_->edges();
std::vector<storage::EdgeAccessor> filteredOutEdges;
for (auto &edge : edges) {
auto edge_q = EdgeAccessor(edge);
if (graph_edges.contains(edge_q)) {
filteredOutEdges.push_back(edge);
}
}
return iter::imap(VertexAccessor::MakeEdgeAccessor, std::move(filteredOutEdges));
}
} // namespace memgraph::query

View File

@@ -20,6 +20,8 @@
#include "storage/v2/id_types.hpp"
#include "storage/v2/property_value.hpp"
#include "storage/v2/result.hpp"
#include "utils/pmr/unordered_set.hpp"
#include "utils/variant_helpers.hpp"
///////////////////////////////////////////////////////////
// Our communication layer and query engine don't mix
@@ -45,6 +47,7 @@
namespace memgraph::query {
class Graph;
class VertexAccessor;
class EdgeAccessor final {
@@ -185,38 +188,123 @@ inline VertexAccessor EdgeAccessor::From() const { return VertexAccessor(impl_.F
inline bool EdgeAccessor::IsCycle() const { return To() == From(); }
class DbAccessor final {
storage::Storage::Accessor *accessor_;
class SubgraphVertexAccessor final {
public:
query::VertexAccessor impl_;
query::Graph *graph_;
class VerticesIterable final {
storage::VerticesIterable iterable_;
explicit SubgraphVertexAccessor(query::VertexAccessor impl, query::Graph *graph_) : impl_(impl), graph_(graph_) {}
bool operator==(const SubgraphVertexAccessor &v) const noexcept {
static_assert(noexcept(impl_ == v.impl_));
return impl_ == v.impl_;
}
auto InEdges(storage::View view) const -> decltype(impl_.OutEdges(view));
auto OutEdges(storage::View view) const -> decltype(impl_.OutEdges(view));
auto Labels(storage::View view) const { return impl_.Labels(view); }
storage::Result<bool> AddLabel(storage::LabelId label) { return impl_.AddLabel(label); }
storage::Result<bool> RemoveLabel(storage::LabelId label) { return impl_.RemoveLabel(label); }
storage::Result<bool> HasLabel(storage::View view, storage::LabelId label) const {
return impl_.HasLabel(view, label);
}
auto Properties(storage::View view) const { return impl_.Properties(view); }
storage::Result<storage::PropertyValue> GetProperty(storage::View view, storage::PropertyId key) const {
return impl_.GetProperty(view, key);
}
storage::Gid Gid() const noexcept { return impl_.Gid(); }
storage::Result<storage::PropertyValue> SetProperty(storage::PropertyId key, const storage::PropertyValue &value) {
return impl_.SetProperty(key, value);
}
VertexAccessor GetVertexAccessor() const;
};
} // namespace memgraph::query
namespace std {
template <>
struct hash<memgraph::query::VertexAccessor> {
size_t operator()(const memgraph::query::VertexAccessor &v) const { return std::hash<decltype(v.impl_)>{}(v.impl_); }
};
template <>
struct hash<memgraph::query::EdgeAccessor> {
size_t operator()(const memgraph::query::EdgeAccessor &e) const { return std::hash<decltype(e.impl_)>{}(e.impl_); }
};
} // namespace std
namespace memgraph::query {
class VerticesIterable final {
std::variant<storage::VerticesIterable, std::unordered_set<VertexAccessor, std::hash<VertexAccessor>,
std::equal_to<void>, utils::Allocator<VertexAccessor>> *>
iterable_;
public:
class Iterator final {
std::variant<storage::VerticesIterable::Iterator,
std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
utils::Allocator<VertexAccessor>>::iterator>
it_;
public:
class Iterator final {
storage::VerticesIterable::Iterator it_;
explicit Iterator(storage::VerticesIterable::Iterator it) : it_(it) {}
explicit Iterator(std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
utils::Allocator<VertexAccessor>>::iterator it)
: it_(it) {}
public:
explicit Iterator(storage::VerticesIterable::Iterator it) : it_(it) {}
VertexAccessor operator*() const {
return std::visit([](auto it_) { return VertexAccessor(*it_); }, it_);
}
VertexAccessor operator*() const { return VertexAccessor(*it_); }
Iterator &operator++() {
std::visit([this](auto it_) { this->it_ = ++it_; }, it_);
return *this;
}
Iterator &operator++() {
++it_;
return *this;
}
bool operator==(const Iterator &other) const { return it_ == other.it_; }
bool operator==(const Iterator &other) const { return it_ == other.it_; }
bool operator!=(const Iterator &other) const { return !(other == *this); }
};
explicit VerticesIterable(storage::VerticesIterable iterable) : iterable_(std::move(iterable)) {}
Iterator begin() { return Iterator(iterable_.begin()); }
Iterator end() { return Iterator(iterable_.end()); }
bool operator!=(const Iterator &other) const { return !(other == *this); }
};
explicit VerticesIterable(storage::VerticesIterable iterable) : iterable_(std::move(iterable)) {}
explicit VerticesIterable(std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
utils::Allocator<VertexAccessor>> *vertices)
: iterable_(vertices) {}
Iterator begin() {
return std::visit(memgraph::utils::Overloaded{
[](storage::VerticesIterable &iterable_) { return Iterator(iterable_.begin()); },
[](std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
utils::Allocator<VertexAccessor>> *iterable_) {
return Iterator(iterable_->begin());
}},
iterable_);
}
Iterator end() {
return std::visit(
memgraph::utils::Overloaded{
[](storage::VerticesIterable &iterable_) { return Iterator(iterable_.end()); },
[](std::unordered_set<VertexAccessor, std::hash<VertexAccessor>, std::equal_to<void>,
utils::Allocator<VertexAccessor>> *iterable_) { return Iterator(iterable_->end()); }},
iterable_);
}
};
class DbAccessor final {
storage::Storage::Accessor *accessor_;
public:
explicit DbAccessor(storage::Storage::Accessor *accessor) : accessor_(accessor) {}
@@ -324,7 +412,7 @@ class DbAccessor final {
void AdvanceCommand() { accessor_->AdvanceCommand(); }
utils::BasicResult<storage::ConstraintViolation, void> Commit() { return accessor_->Commit(); }
utils::BasicResult<storage::StorageDataManipulationError, void> Commit() { return accessor_->Commit(); }
void Abort() { accessor_->Abort(); }
@@ -358,18 +446,44 @@ class DbAccessor final {
storage::ConstraintsInfo ListAllConstraints() const { return accessor_->ListAllConstraints(); }
};
class SubgraphDbAccessor final {
DbAccessor db_accessor_;
Graph *graph_;
public:
explicit SubgraphDbAccessor(DbAccessor db_accessor, Graph *graph);
static SubgraphDbAccessor *MakeSubgraphDbAccessor(DbAccessor *db_accessor, Graph *graph);
storage::PropertyId NameToProperty(std::string_view name);
storage::LabelId NameToLabel(std::string_view name);
storage::EdgeTypeId NameToEdgeType(std::string_view name);
const std::string &PropertyToName(storage::PropertyId prop) const;
const std::string &LabelToName(storage::LabelId label) const;
const std::string &EdgeTypeToName(storage::EdgeTypeId type) const;
storage::Result<std::optional<EdgeAccessor>> RemoveEdge(EdgeAccessor *edge);
storage::Result<EdgeAccessor> InsertEdge(SubgraphVertexAccessor *from, SubgraphVertexAccessor *to,
const storage::EdgeTypeId &edge_type);
storage::Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachRemoveVertex(
SubgraphVertexAccessor *vertex_accessor);
storage::Result<std::optional<VertexAccessor>> RemoveVertex(SubgraphVertexAccessor *vertex_accessor);
SubgraphVertexAccessor InsertVertex();
VerticesIterable Vertices(storage::View view);
std::optional<VertexAccessor> FindVertex(storage::Gid gid, storage::View view);
Graph *getGraph();
};
} // namespace memgraph::query
namespace std {
template <>
struct hash<memgraph::query::VertexAccessor> {
size_t operator()(const memgraph::query::VertexAccessor &v) const { return std::hash<decltype(v.impl_)>{}(v.impl_); }
};
template <>
struct hash<memgraph::query::EdgeAccessor> {
size_t operator()(const memgraph::query::EdgeAccessor &e) const { return std::hash<decltype(e.impl_)>{}(e.impl_); }
};
} // namespace std

View File

@@ -188,6 +188,12 @@ class FreeMemoryModificationInMulticommandTxException : public QueryException {
: QueryException("Free memory query not allowed in multicommand transactions.") {}
};
class ShowConfigModificationInMulticommandTxException : public QueryException {
public:
ShowConfigModificationInMulticommandTxException()
: QueryException("Show config query not allowed in multicommand transactions.") {}
};
class TriggerModificationInMulticommandTxException : public QueryException {
public:
TriggerModificationInMulticommandTxException()
@@ -224,4 +230,11 @@ class VersionInfoInMulticommandTxException : public QueryException {
: QueryException("Version info query not allowed in multicommand transactions.") {}
};
class ReplicationException : public utils::BasicException {
public:
using utils::BasicException::BasicException;
explicit ReplicationException(const std::string &message)
: utils::BasicException("Replication Exception: {} Check the status of the replicas using 'SHOW REPLICA' query.",
message) {}
};
} // namespace memgraph::query

View File

@@ -464,7 +464,7 @@ cpp<#
:documentation "Symbol table position of the symbol this Aggregation is mapped to."))
(:public
(lcp:define-enum op
(count min max sum avg collect-list collect-map)
(count min max sum avg collect-list collect-map project)
(:serialize))
#>cpp
Aggregation() = default;
@@ -475,10 +475,11 @@ cpp<#
static const constexpr char *const kSum = "SUM";
static const constexpr char *const kAvg = "AVG";
static const constexpr char *const kCollect = "COLLECT";
static const constexpr char *const kProject = "PROJECT";
static std::string OpToString(Op op) {
const char *op_strings[] = {kCount, kMin, kMax, kSum,
kAvg, kCollect, kCollect};
kAvg, kCollect, kCollect, kProject};
return op_strings[static_cast<int>(op)];
}
@@ -1380,7 +1381,7 @@ cpp<#
:documentation "Variable where the total weight for weighted shortest path will be stored."))
(:public
(lcp:define-enum type
(single depth-first breadth-first weighted-shortest-path)
(single depth-first breadth-first weighted-shortest-path all-shortest-paths)
(:serialize))
(lcp:define-enum direction
(in out both)
@@ -1432,6 +1433,7 @@ cpp<#
case Type::DEPTH_FIRST:
case Type::BREADTH_FIRST:
case Type::WEIGHTED_SHORTEST_PATH:
case Type::ALL_SHORTEST_PATHS:
return true;
case Type::SINGLE:
return false;
@@ -2234,6 +2236,7 @@ cpp<#
(:serialize (:slk))
(:clone))
(lcp:define-class auth-query (query)
((action "Action" :scope :public)
(user "std::string" :scope :public)
@@ -2242,7 +2245,9 @@ cpp<#
(password "Expression *" :initval "nullptr" :scope :public
:slk-save #'slk-save-ast-pointer
:slk-load (slk-load-ast-pointer "Expression"))
(privileges "std::vector<Privilege>" :scope :public))
(privileges "std::vector<Privilege>" :scope :public)
(label-privileges "std::vector<std::unordered_map<FineGrainedPrivilege, std::vector<std::string>>>" :scope :public)
(edge-type-privileges "std::vector<std::unordered_map<FineGrainedPrivilege, std::vector<std::string>>>" :scope :public))
(:public
(lcp:define-enum action
(create-role drop-role show-roles create-user set-password drop-user
@@ -2255,6 +2260,9 @@ cpp<#
dump replication durability read_file free_memory trigger config stream module_read module_write
websocket)
(:serialize))
(lcp:define-enum fine-grained-privilege
(nothing read update create_delete)
(:serialize))
#>cpp
AuthQuery() = default;
@@ -2264,13 +2272,17 @@ cpp<#
#>cpp
AuthQuery(Action action, std::string user, std::string role,
std::string user_or_role, Expression *password,
std::vector<Privilege> privileges)
std::vector<Privilege> privileges,
std::vector<std::unordered_map<FineGrainedPrivilege, std::vector<std::string>>> label_privileges,
std::vector<std::unordered_map<FineGrainedPrivilege, std::vector<std::string>>> edge_type_privileges)
: action_(action),
user_(user),
role_(role),
user_or_role_(user_or_role),
password_(password),
privileges_(privileges) {}
privileges_(privileges),
label_privileges_(label_privileges),
edge_type_privileges_(edge_type_privileges) {}
cpp<#)
(:private
#>cpp
@@ -2665,5 +2677,13 @@ cpp<#
(:serialize (:slk))
(:clone))
(lcp:define-class show-config-query (query) ()
(:public
#>cpp
DEFVISITABLE(QueryVisitor<void>);
cpp<#)
(:serialize (:slk))
(:clone))
(lcp:pop-namespace) ;; namespace query
(lcp:pop-namespace) ;; namespace memgraph

View File

@@ -94,6 +94,7 @@ class StreamQuery;
class SettingQuery;
class VersionQuery;
class Foreach;
class ShowConfigQuery;
using TreeCompositeVisitor = utils::CompositeVisitor<
SingleQuery, CypherUnion, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator, AdditionOperator,
@@ -125,9 +126,9 @@ class ExpressionVisitor
None, ParameterLookup, Identifier, PrimitiveLiteral, RegexMatch> {};
template <class TResult>
class QueryVisitor
: public utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery, InfoQuery,
ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery, FreeMemoryQuery, TriggerQuery,
IsolationLevelQuery, CreateSnapshotQuery, StreamQuery, SettingQuery, VersionQuery> {};
class QueryVisitor : public utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery,
InfoQuery, ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery,
FreeMemoryQuery, TriggerQuery, IsolationLevelQuery, CreateSnapshotQuery,
StreamQuery, SettingQuery, VersionQuery, ShowConfigQuery> {};
} // namespace memgraph::query

View File

@@ -10,6 +10,7 @@
// licenses/APL.txt.
#include "query/frontend/ast/cypher_main_visitor.hpp"
#include <support/Any.h>
#include <algorithm>
#include <climits>
@@ -44,6 +45,8 @@ namespace memgraph::query::frontend {
const std::string CypherMainVisitor::kAnonPrefix = "anon";
namespace {
enum class EntityType : uint8_t { LABELS, EDGE_TYPES };
template <typename TVisitor>
std::optional<std::pair<memgraph::query::Expression *, size_t>> VisitMemoryLimit(
MemgraphCypher::MemoryLimitContext *memory_limit_ctx, TVisitor *visitor) {
@@ -1275,10 +1278,14 @@ antlrcpp::Any CypherMainVisitor::visitGrantPrivilege(MemgraphCypher::GrantPrivil
AuthQuery *auth = storage_->Create<AuthQuery>();
auth->action_ = AuthQuery::Action::GRANT_PRIVILEGE;
auth->user_or_role_ = std::any_cast<std::string>(ctx->userOrRole->accept(this));
if (ctx->privilegeList()) {
for (auto *privilege : ctx->privilegeList()->privilege()) {
auth->privileges_.push_back(std::any_cast<AuthQuery::Privilege>(privilege->accept(this)));
}
if (ctx->grantPrivilegesList()) {
const auto [label_privileges, edge_type_privileges, privileges] = std::any_cast<
std::tuple<std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>,
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>,
std::vector<memgraph::query::AuthQuery::Privilege>>>(ctx->grantPrivilegesList()->accept(this));
auth->label_privileges_ = label_privileges;
auth->edge_type_privileges_ = edge_type_privileges;
auth->privileges_ = privileges;
} else {
/* grant all privileges */
auth->privileges_ = kPrivilegesAll;
@@ -1293,10 +1300,8 @@ antlrcpp::Any CypherMainVisitor::visitDenyPrivilege(MemgraphCypher::DenyPrivileg
AuthQuery *auth = storage_->Create<AuthQuery>();
auth->action_ = AuthQuery::Action::DENY_PRIVILEGE;
auth->user_or_role_ = std::any_cast<std::string>(ctx->userOrRole->accept(this));
if (ctx->privilegeList()) {
for (auto *privilege : ctx->privilegeList()->privilege()) {
auth->privileges_.push_back(std::any_cast<AuthQuery::Privilege>(privilege->accept(this)));
}
if (ctx->privilegesList()) {
auth->privileges_ = std::any_cast<std::vector<AuthQuery::Privilege>>(ctx->privilegesList()->accept(this));
} else {
/* deny all privileges */
auth->privileges_ = kPrivilegesAll;
@@ -1304,6 +1309,50 @@ antlrcpp::Any CypherMainVisitor::visitDenyPrivilege(MemgraphCypher::DenyPrivileg
return auth;
}
/**
* @return AuthQuery*
*/
antlrcpp::Any CypherMainVisitor::visitPrivilegesList(MemgraphCypher::PrivilegesListContext *ctx) {
std::vector<AuthQuery::Privilege> privileges{};
for (const auto &privilege : ctx->privilege()) {
privileges.push_back(std::any_cast<AuthQuery::Privilege>(privilege->accept(this)));
}
return privileges;
}
/**
* @return std::tuple<std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>,
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>,
std::vector<memgraph::query::AuthQuery::Privilege>>
*/
antlrcpp::Any CypherMainVisitor::visitGrantPrivilegesList(MemgraphCypher::GrantPrivilegesListContext *ctx) {
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> label_privileges;
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> edge_type_privileges;
std::vector<memgraph::query::AuthQuery::Privilege> privileges;
for (auto *it : ctx->privilegeOrEntityPrivileges()) {
if (it->entityPrivilegeList()) {
const auto result =
std::any_cast<std::pair<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>,
std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>>(
it->entityPrivilegeList()->accept(this));
if (!result.first.empty()) {
label_privileges.emplace_back(result.first);
}
if (!result.second.empty()) {
edge_type_privileges.emplace_back(result.second);
}
} else {
privileges.push_back(std::any_cast<AuthQuery::Privilege>(it->privilege()->accept(this)));
}
}
return std::tuple<std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>,
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>,
std::vector<memgraph::query::AuthQuery::Privilege>>(label_privileges, edge_type_privileges,
privileges);
}
/**
* @return AuthQuery*
*/
@@ -1311,9 +1360,22 @@ antlrcpp::Any CypherMainVisitor::visitRevokePrivilege(MemgraphCypher::RevokePriv
AuthQuery *auth = storage_->Create<AuthQuery>();
auth->action_ = AuthQuery::Action::REVOKE_PRIVILEGE;
auth->user_or_role_ = std::any_cast<std::string>(ctx->userOrRole->accept(this));
if (ctx->privilegeList()) {
for (auto *privilege : ctx->privilegeList()->privilege()) {
auth->privileges_.push_back(std::any_cast<AuthQuery::Privilege>(privilege->accept(this)));
if (ctx->revokePrivilegesList()) {
for (auto *it : ctx->revokePrivilegesList()->privilegeOrEntities()) {
if (it->entitiesList()) {
const auto entity_type = std::any_cast<EntityType>(it->entityType()->accept(this));
if (entity_type == EntityType::LABELS) {
auth->label_privileges_.push_back(
{{AuthQuery::FineGrainedPrivilege::CREATE_DELETE,
std::any_cast<std::vector<std::string>>(it->entitiesList()->accept(this))}});
} else {
auth->edge_type_privileges_.push_back(
{{AuthQuery::FineGrainedPrivilege::CREATE_DELETE,
std::any_cast<std::vector<std::string>>(it->entitiesList()->accept(this))}});
}
} else {
auth->privileges_.push_back(std::any_cast<AuthQuery::Privilege>(it->privilege()->accept(this)));
}
}
} else {
/* revoke all privileges */
@@ -1322,6 +1384,48 @@ antlrcpp::Any CypherMainVisitor::visitRevokePrivilege(MemgraphCypher::RevokePriv
return auth;
}
/**
* @return std::pair<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>,
std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
*/
antlrcpp::Any CypherMainVisitor::visitEntityPrivilegeList(MemgraphCypher::EntityPrivilegeListContext *ctx) {
std::pair<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>,
std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
result;
for (auto *it : ctx->entityPrivilege()) {
const auto key = std::any_cast<AuthQuery::FineGrainedPrivilege>(it->granularPrivilege()->accept(this));
const auto entityType = std::any_cast<EntityType>(it->entityType()->accept(this));
auto value = std::any_cast<std::vector<std::string>>(it->entitiesList()->accept(this));
switch (entityType) {
case EntityType::LABELS:
result.first[key] = std::move(value);
break;
case EntityType::EDGE_TYPES:
result.second[key] = std::move(value);
break;
}
}
return result;
}
/**
* @return std::vector<std::string>
*/
antlrcpp::Any CypherMainVisitor::visitEntitiesList(MemgraphCypher::EntitiesListContext *ctx) {
std::vector<std::string> entities;
if (ctx->listOfEntities()) {
for (auto *entity : ctx->listOfEntities()->entity()) {
entities.push_back(std::any_cast<std::string>(entity->symbolicName()->accept(this)));
}
} else {
entities.emplace_back("*");
}
return entities;
}
/**
* @return AuthQuery::Privilege
*/
@@ -1350,6 +1454,26 @@ antlrcpp::Any CypherMainVisitor::visitPrivilege(MemgraphCypher::PrivilegeContext
LOG_FATAL("Should not get here - unknown privilege!");
}
/**
* @return AuthQuery::FineGrainedPrivilege
*/
antlrcpp::Any CypherMainVisitor::visitGranularPrivilege(MemgraphCypher::GranularPrivilegeContext *ctx) {
if (ctx->NOTHING()) return AuthQuery::FineGrainedPrivilege::NOTHING;
if (ctx->READ()) return AuthQuery::FineGrainedPrivilege::READ;
if (ctx->UPDATE()) return AuthQuery::FineGrainedPrivilege::UPDATE;
if (ctx->CREATE_DELETE()) return AuthQuery::FineGrainedPrivilege::CREATE_DELETE;
LOG_FATAL("Should not get here - unknown fine grained privilege!");
}
/**
* @return EntityType
*/
antlrcpp::Any CypherMainVisitor::visitEntityType(MemgraphCypher::EntityTypeContext *ctx) {
if (ctx->LABELS()) return EntityType::LABELS;
if (ctx->EDGE_TYPES()) return EntityType::EDGE_TYPES;
LOG_FATAL("Should not get here - unknown entity type!");
}
/**
* @return AuthQuery*
*/
@@ -1619,9 +1743,10 @@ antlrcpp::Any CypherMainVisitor::visitRelationshipPattern(MemgraphCypher::Relati
auto relationshipLambdas = relationshipDetail->relationshipLambda();
if (variableExpansion) {
if (relationshipDetail->total_weight && edge->type_ != EdgeAtom::Type::WEIGHTED_SHORTEST_PATH)
if (relationshipDetail->total_weight && edge->type_ != EdgeAtom::Type::WEIGHTED_SHORTEST_PATH &&
edge->type_ != EdgeAtom::Type::ALL_SHORTEST_PATHS)
throw SemanticException(
"Variable for total weight is allowed only with weighted shortest "
"Variable for total weight is allowed only with weighted and all shortest "
"path expansion.");
auto visit_lambda = [this](auto *lambda) {
EdgeAtom::Lambda edge_lambda;
@@ -1646,14 +1771,19 @@ antlrcpp::Any CypherMainVisitor::visitRelationshipPattern(MemgraphCypher::Relati
throw SemanticException(
"Lambda for calculating weights is mandatory with weighted "
"shortest path expansion.");
else if (edge->type_ == EdgeAtom::Type::ALL_SHORTEST_PATHS)
throw SemanticException(
"Lambda for calculating weights is mandatory with all "
"shortest paths expansion.");
// In variable expansion inner variables are mandatory.
anonymous_identifiers.push_back(&edge->filter_lambda_.inner_edge);
anonymous_identifiers.push_back(&edge->filter_lambda_.inner_node);
break;
case 1:
if (edge->type_ == EdgeAtom::Type::WEIGHTED_SHORTEST_PATH) {
// For wShortest, the first (and required) lambda is used for weight
// calculation.
if (edge->type_ == EdgeAtom::Type::WEIGHTED_SHORTEST_PATH ||
edge->type_ == EdgeAtom::Type::ALL_SHORTEST_PATHS) {
// For wShortest and allShortest, the first (and required) lambda is
// used for weight calculation.
edge->weight_lambda_ = visit_lambda(relationshipLambdas[0]);
visit_total_weight();
// Add mandatory inner variables for filter lambda.
@@ -1665,7 +1795,7 @@ antlrcpp::Any CypherMainVisitor::visitRelationshipPattern(MemgraphCypher::Relati
}
break;
case 2:
if (edge->type_ != EdgeAtom::Type::WEIGHTED_SHORTEST_PATH)
if (edge->type_ != EdgeAtom::Type::WEIGHTED_SHORTEST_PATH && edge->type_ != EdgeAtom::Type::ALL_SHORTEST_PATHS)
throw SemanticException("Only one filter lambda can be supplied.");
edge->weight_lambda_ = visit_lambda(relationshipLambdas[0]);
visit_total_weight();
@@ -1724,6 +1854,8 @@ antlrcpp::Any CypherMainVisitor::visitVariableExpansion(MemgraphCypher::Variable
edge_type = EdgeAtom::Type::BREADTH_FIRST;
else if (!ctx->getTokens(MemgraphCypher::WSHORTEST).empty())
edge_type = EdgeAtom::Type::WEIGHTED_SHORTEST_PATH;
else if (!ctx->getTokens(MemgraphCypher::ALLSHORTEST).empty())
edge_type = EdgeAtom::Type::ALL_SHORTEST_PATHS;
Expression *lower = nullptr;
Expression *upper = nullptr;
@@ -1734,7 +1866,8 @@ antlrcpp::Any CypherMainVisitor::visitVariableExpansion(MemgraphCypher::Variable
auto *bound = std::any_cast<Expression *>(ctx->expression()[0]->accept(this));
if (!dots_tokens.size()) {
// Case -[*bound]-
if (edge_type != EdgeAtom::Type::WEIGHTED_SHORTEST_PATH) lower = bound;
if (edge_type != EdgeAtom::Type::WEIGHTED_SHORTEST_PATH && edge_type != EdgeAtom::Type::ALL_SHORTEST_PATHS)
lower = bound;
upper = bound;
} else if (dots_tokens[0]->getSourceInterval().startsAfter(ctx->expression()[0]->getSourceInterval())) {
// Case -[*bound..]-
@@ -1748,8 +1881,8 @@ antlrcpp::Any CypherMainVisitor::visitVariableExpansion(MemgraphCypher::Variable
lower = std::any_cast<Expression *>(ctx->expression()[0]->accept(this));
upper = std::any_cast<Expression *>(ctx->expression()[1]->accept(this));
}
if (lower && edge_type == EdgeAtom::Type::WEIGHTED_SHORTEST_PATH)
throw SemanticException("Lower bound is not allowed in weighted shortest path expansion.");
if (lower && (edge_type == EdgeAtom::Type::WEIGHTED_SHORTEST_PATH || edge_type == EdgeAtom::Type::ALL_SHORTEST_PATHS))
throw SemanticException("Lower bound is not allowed in weighted or all shortest path expansion.");
return std::make_tuple(edge_type, lower, upper);
}
@@ -2117,6 +2250,10 @@ antlrcpp::Any CypherMainVisitor::visitFunctionInvocation(MemgraphCypher::Functio
return static_cast<Expression *>(
storage_->Create<Aggregation>(expressions[0], nullptr, Aggregation::Op::COLLECT_LIST));
}
if (function_name == Aggregation::kProject) {
return static_cast<Expression *>(
storage_->Create<Aggregation>(expressions[0], nullptr, Aggregation::Op::PROJECT));
}
}
if (expressions.size() == 2U && function_name == Aggregation::kCollect) {
@@ -2125,8 +2262,8 @@ antlrcpp::Any CypherMainVisitor::visitFunctionInvocation(MemgraphCypher::Functio
}
auto is_user_defined_function = [](const std::string &function_name) {
// Dots are present only in user-defined functions, since modules are case-sensitive, so must be user-defined
// functions. Builtin functions should be case insensitive.
// Dots are present only in user-defined functions, since modules are case-sensitive, so must be
// user-defined functions. Builtin functions should be case insensitive.
return function_name.find('.') != std::string::npos;
};
@@ -2142,8 +2279,8 @@ antlrcpp::Any CypherMainVisitor::visitFunctionInvocation(MemgraphCypher::Functio
antlrcpp::Any CypherMainVisitor::visitFunctionName(MemgraphCypher::FunctionNameContext *ctx) {
auto function_name = ctx->getText();
// Dots are present only in user-defined functions, since modules are case-sensitive, so must be user-defined
// functions. Builtin functions should be case insensitive.
// Dots are present only in user-defined functions, since modules are case-sensitive, so must be
// user-defined functions. Builtin functions should be case insensitive.
if (function_name.find('.') != std::string::npos) {
return function_name;
}
@@ -2353,6 +2490,11 @@ antlrcpp::Any CypherMainVisitor::visitForeach(MemgraphCypher::ForeachContext *ct
return for_each;
}
antlrcpp::Any CypherMainVisitor::visitShowConfigQuery(MemgraphCypher::ShowConfigQueryContext * /*ctx*/) {
query_ = storage_->Create<ShowConfigQuery>();
return query_;
}
LabelIx CypherMainVisitor::AddLabel(const std::string &name) { return storage_->GetLabelIx(name); }
PropertyIx CypherMainVisitor::AddProperty(const std::string &name) { return storage_->GetPropertyIx(name); }

View File

@@ -453,6 +453,8 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
*/
antlrcpp::Any visitClearRole(MemgraphCypher::ClearRoleContext *ctx) override;
void extractPrivilege(AuthQuery *auth, antlropencypher::MemgraphCypher::PrivilegeContext *privilege);
/**
* @return AuthQuery*
*/
@@ -463,11 +465,42 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
*/
antlrcpp::Any visitDenyPrivilege(MemgraphCypher::DenyPrivilegeContext *ctx) override;
/**
* @return AuthQuery*
*/
antlrcpp::Any visitGrantPrivilegesList(MemgraphCypher::GrantPrivilegesListContext *ctx) override;
/**
* @return AuthQuery*
*/
antlrcpp::Any visitPrivilegesList(MemgraphCypher::PrivilegesListContext *ctx) override;
/**
* @return AuthQuery*
*/
antlrcpp::Any visitRevokePrivilege(MemgraphCypher::RevokePrivilegeContext *ctx) override;
/**
* @return std::pair<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>,
std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
*/
antlrcpp::Any visitEntityPrivilegeList(MemgraphCypher::EntityPrivilegeListContext *ctx) override;
/**
* @return std::vector<std::string>
*/
antlrcpp::Any visitEntitiesList(MemgraphCypher::EntitiesListContext *ctx) override;
/**
* @return AuthQuery::FineGrainedPrivilege
*/
antlrcpp::Any visitGranularPrivilege(MemgraphCypher::GranularPrivilegeContext *ctx) override;
/**
* @return std::string
*/
antlrcpp::Any visitEntityType(MemgraphCypher::EntityTypeContext *ctx) override;
/**
* @return AuthQuery::Privilege
*/
@@ -849,6 +882,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
*/
antlrcpp::Any visitForeach(MemgraphCypher::ForeachContext *ctx) override;
/**
* @return ShowConfigQuery*
*/
antlrcpp::Any visitShowConfigQuery(MemgraphCypher::ShowConfigQueryContext *ctx) override;
public:
Query *query() { return query_; }
const static std::string kAnonPrefix;

View File

@@ -172,7 +172,7 @@ relationshipDetail : '[' ( name=variable )? ( relationshipTypes )? ( variableExp
relationshipLambda: '(' traversed_edge=variable ',' traversed_node=variable '|' expression ')';
variableExpansion : '*' (BFS | WSHORTEST)? ( expression )? ( '..' ( expression )? )? ;
variableExpansion : '*' (BFS | WSHORTEST | ALLSHORTEST)? ( expression )? ( '..' ( expression )? )? ;
properties : mapLiteral
| parameter
@@ -381,6 +381,7 @@ cypherKeyword : ALL
| WHERE
| WITH
| WSHORTEST
| ALLSHORTEST
| XOR
| YIELD
;

View File

@@ -139,6 +139,7 @@ WHEN : W H E N ;
WHERE : W H E R E ;
WITH : W I T H ;
WSHORTEST : W S H O R T E S T ;
ALLSHORTEST : A L L S H O R T E S T ;
XOR : X O R ;
YIELD : Y I E L D ;

View File

@@ -37,6 +37,7 @@ memgraphCypherKeyword : cypherKeyword
| CONFIG
| CONFIGS
| CONSUMER_GROUP
| CREATE_DELETE
| CREDENTIALS
| CSV
| DATA
@@ -45,6 +46,7 @@ memgraphCypherKeyword : cypherKeyword
| DENY
| DROP
| DUMP
| EDGE_TYPES
| EXECUTE
| FOR
| FOREACH
@@ -56,6 +58,7 @@ memgraphCypherKeyword : cypherKeyword
| IDENTIFIED
| ISOLATION
| KAFKA
| LABELS
| LEVEL
| LOAD
| LOCK
@@ -63,6 +66,7 @@ memgraphCypherKeyword : cypherKeyword
| MODE
| NEXT
| NO
| NOTHING
| PASSWORD
| PULSAR
| PORT
@@ -122,6 +126,7 @@ query : cypherQuery
| streamQuery
| settingQuery
| versionQuery
| showConfigQuery
;
authQuery : createRole
@@ -227,11 +232,11 @@ setRole : SET ROLE FOR user=userOrRoleName TO role=userOrRoleName;
clearRole : CLEAR ROLE FOR user=userOrRoleName ;
grantPrivilege : GRANT ( ALL PRIVILEGES | privileges=privilegeList ) TO userOrRole=userOrRoleName ;
grantPrivilege : GRANT ( ALL PRIVILEGES | privileges=grantPrivilegesList ) TO userOrRole=userOrRoleName ;
denyPrivilege : DENY ( ALL PRIVILEGES | privileges=privilegeList ) TO userOrRole=userOrRoleName ;
denyPrivilege : DENY ( ALL PRIVILEGES | privileges=privilegesList ) TO userOrRole=userOrRoleName ;
revokePrivilege : REVOKE ( ALL PRIVILEGES | privileges=privilegeList ) FROM userOrRole=userOrRoleName ;
revokePrivilege : REVOKE ( ALL PRIVILEGES | privileges=revokePrivilegesList ) FROM userOrRole=userOrRoleName ;
privilege : CREATE
| DELETE
@@ -256,7 +261,29 @@ privilege : CREATE
| WEBSOCKET
;
privilegeList : privilege ( ',' privilege )* ;
granularPrivilege : NOTHING | READ | UPDATE | CREATE_DELETE ;
entityType : LABELS | EDGE_TYPES ;
privilegeOrEntityPrivileges : privilege | entityPrivileges=entityPrivilegeList ;
grantPrivilegesList : privilegeOrEntityPrivileges ( ',' privilegeOrEntityPrivileges )* ;
entityPrivilegeList : entityPrivilege ( ',' entityPrivilege )* ;
entityPrivilege : granularPrivilege ON entityType entities=entitiesList ;
privilegeOrEntities : privilege | entityType entities=entitiesList ;
revokePrivilegesList : privilegeOrEntities ( ',' privilegeOrEntities )* ;
privilegesList : privilege ( ',' privilege )* ;
entitiesList : ASTERISK | listOfEntities ;
listOfEntities : entity ( ',' entity )* ;
entity : COLON symbolicName ;
showPrivileges : SHOW PRIVILEGES FOR userOrRole=userOrRoleName ;
@@ -372,4 +399,6 @@ showSetting : SHOW DATABASE SETTING settingName ;
showSettings : SHOW DATABASE SETTINGS ;
showConfigQuery : SHOW CONFIG ;
versionQuery : SHOW VERSION ;

View File

@@ -42,6 +42,7 @@ COMMITTED : C O M M I T T E D ;
CONFIG : C O N F I G ;
CONFIGS : C O N F I G S;
CONSUMER_GROUP : C O N S U M E R UNDERSCORE G R O U P ;
CREATE_DELETE : C R E A T E UNDERSCORE D E L E T E ;
CREDENTIALS : C R E D E N T I A L S ;
CSV : C S V ;
DATA : D A T A ;
@@ -66,6 +67,7 @@ IDENTIFIED : I D E N T I F I E D ;
IGNORE : I G N O R E ;
ISOLATION : I S O L A T I O N ;
KAFKA : K A F K A ;
LABELS : L A B E L S ;
LEVEL : L E V E L ;
LOAD : L O A D ;
LOCK : L O C K ;
@@ -75,6 +77,7 @@ MODULE_READ : M O D U L E UNDERSCORE R E A D ;
MODULE_WRITE : M O D U L E UNDERSCORE W R I T E ;
NEXT : N E X T ;
NO : N O ;
NOTHING : N O T H I N G ;
PASSWORD : P A S S W O R D ;
PORT : P O R T ;
PRIVILEGES : P R I V I L E G E S ;
@@ -114,3 +117,4 @@ USER : U S E R ;
USERS : U S E R S ;
VERSION : V E R S I O N ;
WEBSOCKET : W E B S O C K E T ;
EDGE_TYPES : E D G E UNDERSCORE T Y P E S ;

View File

@@ -66,6 +66,8 @@ class PrivilegeExtractor : public QueryVisitor<void>, public HierarchicalTreeVis
void Visit(FreeMemoryQuery &free_memory_query) override { AddPrivilege(AuthQuery::Privilege::FREE_MEMORY); }
void Visit(ShowConfigQuery & /*show_config_query*/) override { AddPrivilege(AuthQuery::Privilege::CONFIG); }
void Visit(TriggerQuery &trigger_query) override { AddPrivilege(AuthQuery::Privilege::TRIGGER); }
void Visit(StreamQuery &stream_query) override { AddPrivilege(AuthQuery::Privilege::STREAM); }

View File

@@ -204,8 +204,11 @@ const trie::Trie kKeywords = {"union",
"pulsar",
"service_url",
"version",
"websocket"
"foreach"};
"config",
"websocket",
"foreach",
"labels",
"edge_types"};
// Unicode codepoints that are allowed at the start of the unescaped name.
const std::bitset<kBitsetSize> kUnescapedNameAllowedStarts(

73
src/query/graph.cpp Normal file
View File

@@ -0,0 +1,73 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "query/graph.hpp"
#include "query/path.hpp"
namespace memgraph::query {
Graph::Graph(utils::MemoryResource *memory) : vertices_(memory), edges_(memory) {}
Graph::Graph(const Graph &other, utils::MemoryResource *memory)
: vertices_(other.vertices_, memory), edges_(other.edges_, memory) {}
Graph::Graph(Graph &&other) noexcept : Graph(std::move(other), other.GetMemoryResource()) {}
Graph::Graph(const Graph &other)
: Graph(other,
std::allocator_traits<allocator_type>::select_on_container_copy_construction(other.GetMemoryResource())
.GetMemoryResource()) {}
Graph::Graph(Graph &&other, utils::MemoryResource *memory)
: vertices_(std::move(other.vertices_), memory), edges_(std::move(other.edges_), memory) {}
void Graph::Expand(const Path &path) {
const auto &path_vertices_ = path.vertices();
const auto &path_edges_ = path.edges();
std::for_each(path_vertices_.begin(), path_vertices_.end(), [this](const VertexAccessor v) { vertices_.insert(v); });
std::for_each(path_edges_.begin(), path_edges_.end(), [this](const EdgeAccessor e) { edges_.insert(e); });
}
void Graph::InsertVertex(const VertexAccessor &vertex) { vertices_.insert(vertex); }
void Graph::InsertEdge(const EdgeAccessor &edge) { edges_.insert(edge); }
bool Graph::ContainsVertex(const VertexAccessor &vertex) { return vertices_.contains(vertex); }
bool Graph::ContainsEdge(const EdgeAccessor &edge) { return edges_.contains(edge); }
std::optional<VertexAccessor> Graph::RemoveVertex(const VertexAccessor &vertex) {
if (!ContainsVertex(vertex)) {
return std::nullopt;
}
auto value = vertices_.erase(vertex);
if (value == 0) {
return std::nullopt;
}
return vertex;
}
std::optional<EdgeAccessor> Graph::RemoveEdge(const EdgeAccessor &edge) {
auto value = edges_.erase(edge);
if (value == 0) {
return std::nullopt;
}
return edge;
}
utils::pmr::unordered_set<VertexAccessor> &Graph::vertices() { return vertices_; }
utils::pmr::unordered_set<EdgeAccessor> &Graph::edges() { return edges_; }
const utils::pmr::unordered_set<VertexAccessor> &Graph::vertices() const { return vertices_; }
const utils::pmr::unordered_set<EdgeAccessor> &Graph::edges() const { return edges_; }
utils::MemoryResource *Graph::GetMemoryResource() const { return vertices_.get_allocator().GetMemoryResource(); }
} // namespace memgraph::query

114
src/query/graph.hpp Normal file
View File

@@ -0,0 +1,114 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <functional>
#include <utility>
#include "query/db_accessor.hpp"
#include "utils/logging.hpp"
#include "utils/memory.hpp"
#include "utils/pmr/unordered_set.hpp"
#include "utils/pmr/vector.hpp"
namespace memgraph::query {
class Path;
/**
* A data structure that holds a graph. A graph consists of at least one
* vertex, and zero or more edges.
*/
class Graph final {
public:
/** Allocator type so that STL containers are aware that we need one */
using allocator_type = utils::Allocator<Graph>;
/**
* Create the graph with no elements
* Allocations are done using the given MemoryResource.
*/
explicit Graph(utils::MemoryResource *memory);
/**
* Construct a copy of other.
* utils::MemoryResource is obtained by calling
* std::allocator_traits<>::
* select_on_container_copy_construction(other.GetMemoryResource()).
* Since we use utils::Allocator, which does not propagate, this means that we
* will default to utils::NewDeleteResource().
*/
Graph(const Graph &other);
/** Construct a copy using the given utils::MemoryResource */
Graph(const Graph &other, utils::MemoryResource *memory);
/**
* Construct with the value of other.
* utils::MemoryResource is obtained from other. After the move, other will be
* empty.
*/
Graph(Graph &&other) noexcept;
/**
* Construct with the value of other, but use the given utils::MemoryResource.
* After the move, other may not be empty if `*memory !=
* *other.GetMemoryResource()`, because an element-wise move will be
* performed.
*/
Graph(Graph &&other, utils::MemoryResource *memory);
/** Expands the graph with the given path. */
void Expand(const Path &path);
/** Inserts the vertex in the graph. */
void InsertVertex(const VertexAccessor &vertex);
/** Inserts the edge in the graph. */
void InsertEdge(const EdgeAccessor &edge);
/** Checks whether the graph contains the vertex. */
bool ContainsVertex(const VertexAccessor &vertex);
/** Checks whether the graph contains the edge. */
bool ContainsEdge(const EdgeAccessor &edge);
/** Removes the vertex from the graph if the vertex is in the graph. */
std::optional<VertexAccessor> RemoveVertex(const VertexAccessor &vertex);
/** Removes the vertex from the graph if the vertex is in the graph. */
std::optional<EdgeAccessor> RemoveEdge(const EdgeAccessor &edge);
/** Return the out edges of the given vertex. */
std::vector<EdgeAccessor> OutEdges(VertexAccessor vertex_accessor);
/** Copy assign other, utils::MemoryResource of `this` is used */
Graph &operator=(const Graph &) = default;
/** Move assign other, utils::MemoryResource of `this` is used. */
Graph &operator=(Graph &&) noexcept = default;
~Graph() = default;
utils::pmr::unordered_set<VertexAccessor> &vertices();
utils::pmr::unordered_set<EdgeAccessor> &edges();
const utils::pmr::unordered_set<VertexAccessor> &vertices() const;
const utils::pmr::unordered_set<EdgeAccessor> &edges() const;
utils::MemoryResource *GetMemoryResource() const;
private:
// Contains all the vertices in the Graph.
utils::pmr::unordered_set<VertexAccessor> vertices_;
// Contains all the edges in the Graph
utils::pmr::unordered_set<EdgeAccessor> edges_;
};
} // namespace memgraph::query

View File

@@ -587,6 +587,8 @@ TypedValue ValueType(const TypedValue *args, int64_t nargs, const FunctionContex
return TypedValue("LOCAL_DATE_TIME", ctx.memory);
case TypedValue::Type::Duration:
return TypedValue("DURATION", ctx.memory);
case TypedValue::Type::Graph:
throw QueryRuntimeException("Cannot fetch graph as it is not standardized openCypher type name");
}
}

View File

@@ -313,6 +313,25 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
}
return std::nullopt;
};
auto maybe_graph = [this](const auto &graph, const auto &prop_name) -> std::optional<TypedValue> {
if (prop_name == "nodes") {
utils::pmr::vector<TypedValue> vertices(ctx_->memory);
vertices.reserve(graph.vertices().size());
for (const auto &v : graph.vertices()) {
vertices.emplace_back(TypedValue(v, ctx_->memory));
}
return TypedValue(vertices, ctx_->memory);
}
if (prop_name == "edges") {
utils::pmr::vector<TypedValue> edges(ctx_->memory);
edges.reserve(graph.edges().size());
for (const auto &e : graph.edges()) {
edges.emplace_back(TypedValue(e, ctx_->memory));
}
return TypedValue(edges, ctx_->memory);
}
return std::nullopt;
};
switch (expression_result.type()) {
case TypedValue::Type::Null:
return TypedValue(ctx_->memory);
@@ -365,6 +384,14 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
}
throw QueryRuntimeException("Invalid property name {} for LocalDateTime", prop_name);
}
case TypedValue::Type::Graph: {
const auto &prop_name = property_lookup.property_.name;
const auto &graph = expression_result.ValueGraph();
if (auto graph_field = maybe_graph(graph, prop_name); graph_field) {
return std::move(*graph_field);
}
throw QueryRuntimeException("Invalid property name {} for Graph", prop_name);
}
default:
throw QueryRuntimeException("Only nodes, edges, maps and temporal types have properties to be looked-up.");
}

View File

@@ -20,7 +20,10 @@
#include <functional>
#include <limits>
#include <optional>
#include <unordered_map>
#include <variant>
#include "auth/models.hpp"
#include "glue/communication.hpp"
#include "memory/memory_control.hpp"
#include "query/constants.hpp"
@@ -43,8 +46,9 @@
#include "query/stream/common.hpp"
#include "query/trigger.hpp"
#include "query/typed_value.hpp"
#include "storage/v2/edge.hpp"
#include "storage/v2/id_types.hpp"
#include "storage/v2/property_value.hpp"
#include "storage/v2/replication/enums.hpp"
#include "utils/algorithm.hpp"
#include "utils/csv_parsing.hpp"
#include "utils/event_counter.hpp"
@@ -75,6 +79,9 @@ extern const Event TriggersCreated;
namespace memgraph::query {
template <typename>
constexpr auto kAlwaysFalse = false;
namespace {
void UpdateTypeCount(const plan::ReadWriteTypeChecker::RWType type) {
switch (type) {
@@ -259,6 +266,7 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
private:
storage::Storage *db_;
};
/// returns false if the replication role can't be set
/// @throw QueryRuntimeException if an error ocurred.
@@ -273,6 +281,7 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
// TODO: MemoryResource for EvaluationContext, it should probably be passed as
// the argument to Callback.
evaluation_context.timestamp = QueryTimestamp();
evaluation_context.parameters = parameters;
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, db_accessor, storage::View::OLD);
@@ -280,6 +289,12 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
std::string rolename = auth_query->role_;
std::string user_or_role = auth_query->user_or_role_;
std::vector<AuthQuery::Privilege> privileges = auth_query->privileges_;
#ifdef MG_ENTERPRISE
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> label_privileges =
auth_query->label_privileges_;
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> edge_type_privileges =
auth_query->edge_type_privileges_;
#endif
auto password = EvaluateOptionalExpression(auth_query->password_, &evaluator);
Callback callback;
@@ -309,7 +324,19 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
// If the license is not valid we create users with admin access
if (!valid_enterprise_license) {
spdlog::warn("Granting all the privileges to {}.", username);
auth->GrantPrivilege(username, kPrivilegesAll);
auth->GrantPrivilege(username, kPrivilegesAll
#ifdef MG_ENTERPRISE
,
{{{AuthQuery::FineGrainedPrivilege::CREATE_DELETE, {auth::kAsterisk}}}},
{
{
{
AuthQuery::FineGrainedPrivilege::CREATE_DELETE, { auth::kAsterisk }
}
}
}
#endif
);
}
return std::vector<std::vector<TypedValue>>();
@@ -384,8 +411,18 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
};
return callback;
case AuthQuery::Action::GRANT_PRIVILEGE:
callback.fn = [auth, user_or_role, privileges] {
auth->GrantPrivilege(user_or_role, privileges);
callback.fn = [auth, user_or_role, privileges
#ifdef MG_ENTERPRISE
,
label_privileges, edge_type_privileges
#endif
] {
auth->GrantPrivilege(user_or_role, privileges
#ifdef MG_ENTERPRISE
,
label_privileges, edge_type_privileges
#endif
);
return std::vector<std::vector<TypedValue>>();
};
return callback;
@@ -396,8 +433,18 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
};
return callback;
case AuthQuery::Action::REVOKE_PRIVILEGE: {
callback.fn = [auth, user_or_role, privileges] {
auth->RevokePrivilege(user_or_role, privileges);
callback.fn = [auth, user_or_role, privileges
#ifdef MG_ENTERPRISE
,
label_privileges, edge_type_privileges
#endif
] {
auth->RevokePrivilege(user_or_role, privileges
#ifdef MG_ENTERPRISE
,
label_privileges, edge_type_privileges
#endif
);
return std::vector<std::vector<TypedValue>>();
};
return callback;
@@ -429,7 +476,7 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
default:
break;
}
}
} // namespace
Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &parameters,
InterpreterContext *interpreter_context, DbAccessor *db_accessor,
@@ -791,6 +838,37 @@ Callback HandleStreamQuery(StreamQuery *stream_query, const Parameters &paramete
}
}
Callback HandleConfigQuery() {
Callback callback;
callback.header = {"name", "default_value", "current_value", "description"};
callback.fn = [] {
std::vector<GFLAGS_NAMESPACE::CommandLineFlagInfo> flags;
GetAllFlags(&flags);
std::vector<std::vector<TypedValue>> results;
for (const auto &flag : flags) {
if (flag.hidden ||
// These flags are not defined with gflags macros but are specified in config/flags.yaml
flag.name == "help" || flag.name == "help_xml" || flag.name == "version") {
continue;
}
std::vector<TypedValue> current_fields;
current_fields.emplace_back(flag.name);
current_fields.emplace_back(flag.default_value);
current_fields.emplace_back(flag.current_value);
current_fields.emplace_back(flag.description);
results.emplace_back(std::move(current_fields));
}
return results;
};
return callback;
}
Callback HandleSettingQuery(SettingQuery *setting_query, const Parameters &parameters, DbAccessor *db_accessor) {
Frame frame(0);
SymbolTable symbol_table;
@@ -897,7 +975,7 @@ struct PullPlanVector {
struct PullPlan {
explicit PullPlan(std::shared_ptr<CachedPlan> plan, const Parameters &parameters, bool is_profile_query,
DbAccessor *dba, InterpreterContext *interpreter_context, utils::MemoryResource *execution_memory,
TriggerContextCollector *trigger_context_collector = nullptr,
std::optional<std::string> username, TriggerContextCollector *trigger_context_collector = nullptr,
std::optional<size_t> memory_limit = {});
std::optional<plan::ProfilingStatsWithTotalTime> Pull(AnyStream *stream, std::optional<int> n,
const std::vector<Symbol> &output_symbols,
@@ -926,7 +1004,8 @@ struct PullPlan {
PullPlan::PullPlan(const std::shared_ptr<CachedPlan> plan, const Parameters &parameters, const bool is_profile_query,
DbAccessor *dba, InterpreterContext *interpreter_context, utils::MemoryResource *execution_memory,
TriggerContextCollector *trigger_context_collector, const std::optional<size_t> memory_limit)
std::optional<std::string> username, TriggerContextCollector *trigger_context_collector,
const std::optional<size_t> memory_limit)
: plan_(plan),
cursor_(plan->plan().MakeCursor(execution_memory)),
frame_(plan->symbol_table().max_position(), execution_memory),
@@ -937,6 +1016,11 @@ PullPlan::PullPlan(const std::shared_ptr<CachedPlan> plan, const Parameters &par
ctx_.evaluation_context.parameters = parameters;
ctx_.evaluation_context.properties = NamesToProperties(plan->ast_storage().properties_, dba);
ctx_.evaluation_context.labels = NamesToLabels(plan->ast_storage().labels_, dba);
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && username.has_value() && dba) {
ctx_.auth_checker = interpreter_context->auth_checker->GetFineGrainedAuthChecker(*username, dba);
}
#endif
if (interpreter_context->config.execution_timeout_sec > 0) {
ctx_.timer = utils::AsyncTimer{interpreter_context->config.execution_timeout_sec};
}
@@ -1110,6 +1194,7 @@ PreparedQuery Interpreter::PrepareTransactionQuery(std::string_view query_upper)
PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string, TypedValue> *summary,
InterpreterContext *interpreter_context, DbAccessor *dba,
utils::MemoryResource *execution_memory, std::vector<Notification> *notifications,
const std::string *username,
TriggerContextCollector *trigger_context_collector = nullptr) {
auto *cypher_query = utils::Downcast<CypherQuery>(parsed_query.query);
@@ -1118,6 +1203,7 @@ PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string,
EvaluationContext evaluation_context;
evaluation_context.timestamp = QueryTimestamp();
evaluation_context.parameters = parsed_query.parameters;
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, dba, storage::View::OLD);
const auto memory_limit = EvaluateMemoryLimit(&evaluator, cypher_query->memory_limit_, cypher_query->memory_scale_);
if (memory_limit) {
@@ -1153,8 +1239,9 @@ PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string,
header.push_back(
utils::FindOr(parsed_query.stripped_query.named_expressions(), symbol.token_position(), symbol.name()).first);
}
auto pull_plan = std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context,
execution_memory, trigger_context_collector, memory_limit);
auto pull_plan =
std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context, execution_memory,
StringPointerToOptional(username), trigger_context_collector, memory_limit);
return PreparedQuery{std::move(header), std::move(parsed_query.required_privileges),
[pull_plan = std::move(pull_plan), output_symbols = std::move(output_symbols), summary](
AnyStream *stream, std::optional<int> n) -> std::optional<QueryHandlerResult> {
@@ -1214,7 +1301,8 @@ PreparedQuery PrepareExplainQuery(ParsedQuery parsed_query, std::map<std::string
PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
std::map<std::string, TypedValue> *summary, InterpreterContext *interpreter_context,
DbAccessor *dba, utils::MemoryResource *execution_memory) {
DbAccessor *dba, utils::MemoryResource *execution_memory,
const std::string *username) {
const std::string kProfileQueryStart = "profile ";
MG_ASSERT(utils::StartsWith(utils::ToLowerCase(parsed_query.stripped_query.query()), kProfileQueryStart),
@@ -1264,12 +1352,14 @@ PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_tra
parsed_inner_query.stripped_query.hash(), std::move(parsed_inner_query.ast_storage), cypher_query,
parsed_inner_query.parameters, parsed_inner_query.is_cacheable ? &interpreter_context->plan_cache : nullptr, dba);
auto rw_type_checker = plan::ReadWriteTypeChecker();
auto optional_username = StringPointerToOptional(username);
rw_type_checker.InferRWType(const_cast<plan::LogicalOperator &>(cypher_query_plan->plan()));
return PreparedQuery{{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"},
std::move(parsed_query.required_privileges),
[plan = std::move(cypher_query_plan), parameters = std::move(parsed_inner_query.parameters),
summary, dba, interpreter_context, execution_memory, memory_limit,
summary, dba, interpreter_context, execution_memory, memory_limit, optional_username,
// We want to execute the query we are profiling lazily, so we delay
// the construction of the corresponding context.
stats_and_total_time = std::optional<plan::ProfilingStatsWithTotalTime>{},
@@ -1278,7 +1368,7 @@ PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_tra
// No output symbols are given so that nothing is streamed.
if (!stats_and_total_time) {
stats_and_total_time = PullPlan(plan, parameters, true, dba, interpreter_context,
execution_memory, nullptr, memory_limit)
execution_memory, optional_username, nullptr, memory_limit)
.Pull(stream, {}, {}, summary);
pull_plan = std::make_shared<PullPlanVector>(ProfilingStatsToTable(*stats_and_total_time));
}
@@ -1352,23 +1442,34 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans
handler = [interpreter_context, label, properties_stringified = std::move(properties_stringified),
label_name = index_query->label_.name, properties = std::move(properties),
invalidate_plan_cache = std::move(invalidate_plan_cache)](Notification &index_notification) {
if (properties.empty()) {
if (!interpreter_context->db->CreateIndex(label)) {
index_notification.code = NotificationCode::EXISTANT_INDEX;
index_notification.title =
fmt::format("Index on label {} on properties {} already exists.", label_name, properties_stringified);
}
EventCounter::IncrementCounter(EventCounter::LabelIndexCreated);
MG_ASSERT(properties.size() <= 1U);
auto maybe_index_error = properties.empty() ? interpreter_context->db->CreateIndex(label)
: interpreter_context->db->CreateIndex(label, properties[0]);
utils::OnScopeExit invalidator(invalidate_plan_cache);
if (maybe_index_error.HasError()) {
const auto &error = maybe_index_error.GetError();
std::visit(
[&index_notification, &label_name, &properties_stringified]<typename T>(T &&) {
using ErrorType = std::remove_cvref_t<T>;
if constexpr (std::is_same_v<ErrorType, storage::ReplicationError>) {
EventCounter::IncrementCounter(EventCounter::LabelIndexCreated);
throw ReplicationException(
fmt::format("At least one SYNC replica has not confirmed the creation of the index on label {} "
"on properties {}.",
label_name, properties_stringified));
} else if constexpr (std::is_same_v<ErrorType, storage::IndexDefinitionError>) {
index_notification.code = NotificationCode::EXISTENT_INDEX;
index_notification.title = fmt::format("Index on label {} on properties {} already exists.",
label_name, properties_stringified);
} else {
static_assert(kAlwaysFalse<T>, "Missing type from variant visitor");
}
},
error);
} else {
MG_ASSERT(properties.size() == 1U);
if (!interpreter_context->db->CreateIndex(label, properties[0])) {
index_notification.code = NotificationCode::EXISTANT_INDEX;
index_notification.title =
fmt::format("Index on label {} on properties {} already exists.", label_name, properties_stringified);
}
EventCounter::IncrementCounter(EventCounter::LabelPropertyIndexCreated);
EventCounter::IncrementCounter(EventCounter::LabelIndexCreated);
}
invalidate_plan_cache();
};
break;
}
@@ -1379,21 +1480,31 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans
handler = [interpreter_context, label, properties_stringified = std::move(properties_stringified),
label_name = index_query->label_.name, properties = std::move(properties),
invalidate_plan_cache = std::move(invalidate_plan_cache)](Notification &index_notification) {
if (properties.empty()) {
if (!interpreter_context->db->DropIndex(label)) {
index_notification.code = NotificationCode::NONEXISTANT_INDEX;
index_notification.title =
fmt::format("Index on label {} on properties {} doesn't exist.", label_name, properties_stringified);
}
} else {
MG_ASSERT(properties.size() == 1U);
if (!interpreter_context->db->DropIndex(label, properties[0])) {
index_notification.code = NotificationCode::NONEXISTANT_INDEX;
index_notification.title =
fmt::format("Index on label {} on properties {} doesn't exist.", label_name, properties_stringified);
}
MG_ASSERT(properties.size() <= 1U);
auto maybe_index_error = properties.empty() ? interpreter_context->db->DropIndex(label)
: interpreter_context->db->DropIndex(label, properties[0]);
utils::OnScopeExit invalidator(invalidate_plan_cache);
if (maybe_index_error.HasError()) {
const auto &error = maybe_index_error.GetError();
std::visit(
[&index_notification, &label_name, &properties_stringified]<typename T>(T &&) {
using ErrorType = std::remove_cvref_t<T>;
if constexpr (std::is_same_v<ErrorType, storage::ReplicationError>) {
throw ReplicationException(
fmt::format("At least one SYNC replica has not confirmed the dropping of the index on label {} "
"on properties {}.",
label_name, properties_stringified));
} else if constexpr (std::is_same_v<ErrorType, storage::IndexDefinitionError>) {
index_notification.code = NotificationCode::NONEXISTENT_INDEX;
index_notification.title = fmt::format("Index on label {} on properties {} doesn't exist.",
label_name, properties_stringified);
} else {
static_assert(kAlwaysFalse<T>, "Missing type from variant visitor");
}
},
error);
}
invalidate_plan_cache();
};
break;
}
@@ -1413,7 +1524,7 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans
PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
std::map<std::string, TypedValue> *summary, InterpreterContext *interpreter_context,
DbAccessor *dba, utils::MemoryResource *execution_memory) {
DbAccessor *dba, utils::MemoryResource *execution_memory, const std::string *username) {
if (in_explicit_transaction) {
throw UserModificationInMulticommandTxException();
}
@@ -1433,8 +1544,8 @@ PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transa
[fn = callback.fn](Frame *, ExecutionContext *) { return fn(); }),
0.0, AstStorage{}, symbol_table));
auto pull_plan =
std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context, execution_memory);
auto pull_plan = std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context,
execution_memory, StringPointerToOptional(username));
return PreparedQuery{
callback.header, std::move(parsed_query.required_privileges),
[pull_plan = std::move(pull_plan), callback = std::move(callback), output_symbols = std::move(output_symbols),
@@ -1521,6 +1632,28 @@ PreparedQuery PrepareFreeMemoryQuery(ParsedQuery parsed_query, const bool in_exp
RWType::NONE};
}
PreparedQuery PrepareShowConfigQuery(ParsedQuery parsed_query, const bool in_explicit_transaction) {
if (in_explicit_transaction) {
throw ShowConfigModificationInMulticommandTxException();
}
auto callback = HandleConfigQuery();
return PreparedQuery{std::move(callback.header), std::move(parsed_query.required_privileges),
[callback_fn = std::move(callback.fn), pull_plan = std::shared_ptr<PullPlanVector>{nullptr}](
AnyStream *stream, std::optional<int> n) mutable -> std::optional<QueryHandlerResult> {
if (!pull_plan) [[unlikely]] {
pull_plan = std::make_shared<PullPlanVector>(callback_fn());
}
if (pull_plan->Pull(stream, n)) {
return QueryHandlerResult::COMMIT;
}
return std::nullopt;
},
RWType::NONE};
}
TriggerEventType ToTriggerEventType(const TriggerQuery::EventType event_type) {
switch (event_type) {
case TriggerQuery::EventType::ANY:
@@ -1920,21 +2053,37 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_
handler = [interpreter_context, label, label_name = constraint_query->constraint_.label.name,
properties_stringified = std::move(properties_stringified),
properties = std::move(properties)](Notification &constraint_notification) {
auto res = interpreter_context->db->CreateExistenceConstraint(label, properties[0]);
if (res.HasError()) {
auto violation = res.GetError();
auto label_name = interpreter_context->db->LabelToName(violation.label);
MG_ASSERT(violation.properties.size() == 1U);
auto property_name = interpreter_context->db->PropertyToName(*violation.properties.begin());
throw QueryRuntimeException(
"Unable to create existence constraint :{}({}), because an "
"existing node violates it.",
label_name, property_name);
}
if (res.HasValue() && !res.GetValue()) {
constraint_notification.code = NotificationCode::EXISTANT_CONSTRAINT;
constraint_notification.title = fmt::format(
"Constraint EXISTS on label {} on properties {} already exists.", label_name, properties_stringified);
auto maybe_constraint_error = interpreter_context->db->CreateExistenceConstraint(label, properties[0]);
if (maybe_constraint_error.HasError()) {
const auto &error = maybe_constraint_error.GetError();
std::visit(
[&interpreter_context, &label_name, &properties_stringified,
&constraint_notification]<typename T>(T &&arg) {
using ErrorType = std::remove_cvref_t<T>;
if constexpr (std::is_same_v<ErrorType, storage::ConstraintViolation>) {
auto &violation = arg;
MG_ASSERT(violation.properties.size() == 1U);
auto property_name = interpreter_context->db->PropertyToName(*violation.properties.begin());
throw QueryRuntimeException(
"Unable to create existence constraint :{}({}), because an "
"existing node violates it.",
label_name, property_name);
} else if constexpr (std::is_same_v<ErrorType, storage::ConstraintDefinitionError>) {
constraint_notification.code = NotificationCode::EXISTENT_CONSTRAINT;
constraint_notification.title =
fmt::format("Constraint EXISTS on label {} on properties {} already exists.", label_name,
properties_stringified);
} else if constexpr (std::is_same_v<ErrorType, storage::ReplicationError>) {
throw ReplicationException(
"At least one SYNC replica has not confirmed the creation of the EXISTS constraint on label "
"{} on properties {}.",
label_name, properties_stringified);
} else {
static_assert(kAlwaysFalse<T>, "Missing type from variant visitor");
}
},
error);
}
};
break;
@@ -1952,21 +2101,35 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_
handler = [interpreter_context, label, label_name = constraint_query->constraint_.label.name,
properties_stringified = std::move(properties_stringified),
property_set = std::move(property_set)](Notification &constraint_notification) {
auto res = interpreter_context->db->CreateUniqueConstraint(label, property_set);
if (res.HasError()) {
auto violation = res.GetError();
auto label_name = interpreter_context->db->LabelToName(violation.label);
std::stringstream property_names_stream;
utils::PrintIterable(property_names_stream, violation.properties, ", ",
[&interpreter_context](auto &stream, const auto &prop) {
stream << interpreter_context->db->PropertyToName(prop);
});
throw QueryRuntimeException(
"Unable to create unique constraint :{}({}), because an "
"existing node violates it.",
label_name, property_names_stream.str());
auto maybe_constraint_error = interpreter_context->db->CreateUniqueConstraint(label, property_set);
if (maybe_constraint_error.HasError()) {
const auto &error = maybe_constraint_error.GetError();
std::visit(
[&interpreter_context, &label_name, &properties_stringified]<typename T>(T &&arg) {
using ErrorType = std::remove_cvref_t<T>;
if constexpr (std::is_same_v<ErrorType, storage::ConstraintViolation>) {
auto &violation = arg;
auto violation_label_name = interpreter_context->db->LabelToName(violation.label);
std::stringstream property_names_stream;
utils::PrintIterable(property_names_stream, violation.properties, ", ",
[&interpreter_context](auto &stream, const auto &prop) {
stream << interpreter_context->db->PropertyToName(prop);
});
throw QueryRuntimeException(
"Unable to create unique constraint :{}({}), because an "
"existing node violates it.",
violation_label_name, property_names_stream.str());
} else if constexpr (std::is_same_v<ErrorType, storage::ReplicationError>) {
throw ReplicationException(fmt::format(
"At least one SYNC replica has not confirmed the creation of the UNIQUE constraint: {}({}).",
label_name, properties_stringified));
} else {
static_assert(kAlwaysFalse<T>, "Missing type from variant visitor");
}
},
error);
}
switch (res.GetValue()) {
switch (maybe_constraint_error.GetValue()) {
case storage::UniqueConstraints::CreationStatus::EMPTY_PROPERTIES:
throw SyntaxException(
"At least one property must be used for unique "
@@ -1977,7 +2140,7 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_
"for unique constraints is exceeded.",
storage::kUniqueConstraintsMaxProperties);
case storage::UniqueConstraints::CreationStatus::ALREADY_EXISTS:
constraint_notification.code = NotificationCode::EXISTANT_CONSTRAINT;
constraint_notification.code = NotificationCode::EXISTENT_CONSTRAINT;
constraint_notification.title =
fmt::format("Constraint UNIQUE on label {} on properties {} already exists.", label_name,
properties_stringified);
@@ -2005,10 +2168,27 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_
handler = [interpreter_context, label, label_name = constraint_query->constraint_.label.name,
properties_stringified = std::move(properties_stringified),
properties = std::move(properties)](Notification &constraint_notification) {
if (!interpreter_context->db->DropExistenceConstraint(label, properties[0])) {
constraint_notification.code = NotificationCode::NONEXISTANT_CONSTRAINT;
constraint_notification.title = fmt::format(
"Constraint EXISTS on label {} on properties {} doesn't exist.", label_name, properties_stringified);
auto maybe_constraint_error = interpreter_context->db->DropExistenceConstraint(label, properties[0]);
if (maybe_constraint_error.HasError()) {
const auto &error = maybe_constraint_error.GetError();
std::visit(
[&label_name, &properties_stringified, &constraint_notification]<typename T>(T &&) {
using ErrorType = std::remove_cvref_t<T>;
if constexpr (std::is_same_v<ErrorType, storage::ConstraintDefinitionError>) {
constraint_notification.code = NotificationCode::NONEXISTENT_CONSTRAINT;
constraint_notification.title =
fmt::format("Constraint EXISTS on label {} on properties {} doesn't exist.", label_name,
properties_stringified);
} else if constexpr (std::is_same_v<ErrorType, storage::ReplicationError>) {
throw ReplicationException(
fmt::format("At least one SYNC replica has not confirmed the dropping of the EXISTS "
"constraint on label {} on properties {}.",
label_name, properties_stringified));
} else {
static_assert(kAlwaysFalse<T>, "Missing type from variant visitor");
}
},
error);
}
return std::vector<std::vector<TypedValue>>();
};
@@ -2027,7 +2207,24 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_
handler = [interpreter_context, label, label_name = constraint_query->constraint_.label.name,
properties_stringified = std::move(properties_stringified),
property_set = std::move(property_set)](Notification &constraint_notification) {
auto res = interpreter_context->db->DropUniqueConstraint(label, property_set);
auto maybe_constraint_error = interpreter_context->db->DropUniqueConstraint(label, property_set);
if (maybe_constraint_error.HasError()) {
const auto &error = maybe_constraint_error.GetError();
std::visit(
[&label_name, &properties_stringified]<typename T>(T &&) {
using ErrorType = std::remove_cvref_t<T>;
if constexpr (std::is_same_v<ErrorType, storage::ReplicationError>) {
throw ReplicationException(
fmt::format("At least one SYNC replica has not confirmed the dropping of the UNIQUE "
"constraint on label {} on properties {}.",
label_name, properties_stringified));
} else {
static_assert(kAlwaysFalse<T>, "Missing type from variant visitor");
}
},
error);
}
const auto &res = maybe_constraint_error.GetValue();
switch (res) {
case storage::UniqueConstraints::DeletionStatus::EMPTY_PROPERTIES:
throw SyntaxException(
@@ -2041,7 +2238,7 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_
storage::kUniqueConstraintsMaxProperties);
break;
case storage::UniqueConstraints::DeletionStatus::NOT_FOUND:
constraint_notification.code = NotificationCode::NONEXISTANT_CONSTRAINT;
constraint_notification.code = NotificationCode::NONEXISTENT_CONSTRAINT;
constraint_notification.title =
fmt::format("Constraint UNIQUE on label {} on properties {} doesn't exist.", label_name,
properties_stringified);
@@ -2146,7 +2343,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
if (utils::Downcast<CypherQuery>(parsed_query.query)) {
prepared_query = PrepareCypherQuery(std::move(parsed_query), &query_execution->summary, interpreter_context_,
&*execution_db_accessor_, &query_execution->execution_memory,
&query_execution->notifications,
&query_execution->notifications, username,
trigger_context_collector_ ? &*trigger_context_collector_ : nullptr);
} else if (utils::Downcast<ExplainQuery>(parsed_query.query)) {
prepared_query = PrepareExplainQuery(std::move(parsed_query), &query_execution->summary, interpreter_context_,
@@ -2154,7 +2351,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
} else if (utils::Downcast<ProfileQuery>(parsed_query.query)) {
prepared_query = PrepareProfileQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
interpreter_context_, &*execution_db_accessor_,
&query_execution->execution_memory_with_exception);
&query_execution->execution_memory_with_exception, username);
} else if (utils::Downcast<DumpQuery>(parsed_query.query)) {
prepared_query = PrepareDumpQuery(std::move(parsed_query), &query_execution->summary, &*execution_db_accessor_,
&query_execution->execution_memory);
@@ -2164,7 +2361,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
} else if (utils::Downcast<AuthQuery>(parsed_query.query)) {
prepared_query = PrepareAuthQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
interpreter_context_, &*execution_db_accessor_,
&query_execution->execution_memory_with_exception);
&query_execution->execution_memory_with_exception, username);
} else if (utils::Downcast<InfoQuery>(parsed_query.query)) {
prepared_query = PrepareInfoQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
interpreter_context_, interpreter_context_->db,
@@ -2181,6 +2378,8 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
&*execution_db_accessor_);
} else if (utils::Downcast<FreeMemoryQuery>(parsed_query.query)) {
prepared_query = PrepareFreeMemoryQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_);
} else if (utils::Downcast<ShowConfigQuery>(parsed_query.query)) {
prepared_query = PrepareShowConfigQuery(std::move(parsed_query), in_explicit_transaction_);
} else if (utils::Downcast<TriggerQuery>(parsed_query.query)) {
prepared_query =
PrepareTriggerQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->notifications,
@@ -2257,28 +2456,41 @@ void RunTriggersIndividually(const utils::SkipList<Trigger> &triggers, Interpret
continue;
}
auto maybe_constraint_violation = db_accessor.Commit();
if (maybe_constraint_violation.HasError()) {
const auto &constraint_violation = maybe_constraint_violation.GetError();
switch (constraint_violation.type) {
case storage::ConstraintViolation::Type::EXISTENCE: {
const auto &label_name = db_accessor.LabelToName(constraint_violation.label);
MG_ASSERT(constraint_violation.properties.size() == 1U);
const auto &property_name = db_accessor.PropertyToName(*constraint_violation.properties.begin());
spdlog::warn("Trigger '{}' failed to commit due to existence constraint violation on :{}({})", trigger.Name(),
label_name, property_name);
break;
}
case storage::ConstraintViolation::Type::UNIQUE: {
const auto &label_name = db_accessor.LabelToName(constraint_violation.label);
std::stringstream property_names_stream;
utils::PrintIterable(property_names_stream, constraint_violation.properties, ", ",
[&](auto &stream, const auto &prop) { stream << db_accessor.PropertyToName(prop); });
spdlog::warn("Trigger '{}' failed to commit due to unique constraint violation on :{}({})", trigger.Name(),
label_name, property_names_stream.str());
break;
}
}
auto maybe_commit_error = db_accessor.Commit();
if (maybe_commit_error.HasError()) {
const auto &error = maybe_commit_error.GetError();
std::visit(
[&trigger, &db_accessor]<typename T>(T &&arg) {
using ErrorType = std::remove_cvref_t<T>;
if constexpr (std::is_same_v<ErrorType, storage::ReplicationError>) {
spdlog::warn("At least one SYNC replica has not confirmed execution of the trigger '{}'.",
trigger.Name());
} else if constexpr (std::is_same_v<ErrorType, storage::ConstraintViolation>) {
const auto &constraint_violation = arg;
switch (constraint_violation.type) {
case storage::ConstraintViolation::Type::EXISTENCE: {
const auto &label_name = db_accessor.LabelToName(constraint_violation.label);
MG_ASSERT(constraint_violation.properties.size() == 1U);
const auto &property_name = db_accessor.PropertyToName(*constraint_violation.properties.begin());
spdlog::warn("Trigger '{}' failed to commit due to existence constraint violation on: {}({}) ",
trigger.Name(), label_name, property_name);
}
case storage::ConstraintViolation::Type::UNIQUE: {
const auto &label_name = db_accessor.LabelToName(constraint_violation.label);
std::stringstream property_names_stream;
utils::PrintIterable(
property_names_stream, constraint_violation.properties, ", ",
[&](auto &stream, const auto &prop) { stream << db_accessor.PropertyToName(prop); });
spdlog::warn("Trigger '{}' failed to commit due to unique constraint violation on :{}({})",
trigger.Name(), label_name, property_names_stream.str());
}
}
} else {
static_assert(kAlwaysFalse<T>, "Missing type from variant visitor");
}
},
error);
}
}
}
@@ -2319,32 +2531,45 @@ void Interpreter::Commit() {
db_accessor_.reset();
trigger_context_collector_.reset();
};
utils::OnScopeExit members_reseter(reset_necessary_members);
auto maybe_constraint_violation = db_accessor_->Commit();
if (maybe_constraint_violation.HasError()) {
const auto &constraint_violation = maybe_constraint_violation.GetError();
switch (constraint_violation.type) {
case storage::ConstraintViolation::Type::EXISTENCE: {
auto label_name = execution_db_accessor_->LabelToName(constraint_violation.label);
MG_ASSERT(constraint_violation.properties.size() == 1U);
auto property_name = execution_db_accessor_->PropertyToName(*constraint_violation.properties.begin());
reset_necessary_members();
throw QueryException("Unable to commit due to existence constraint violation on :{}({})", label_name,
property_name);
break;
}
case storage::ConstraintViolation::Type::UNIQUE: {
auto label_name = execution_db_accessor_->LabelToName(constraint_violation.label);
std::stringstream property_names_stream;
utils::PrintIterable(
property_names_stream, constraint_violation.properties, ", ",
[this](auto &stream, const auto &prop) { stream << execution_db_accessor_->PropertyToName(prop); });
reset_necessary_members();
throw QueryException("Unable to commit due to unique constraint violation on :{}({})", label_name,
property_names_stream.str());
break;
}
}
auto commit_confirmed_by_all_sync_repplicas = true;
auto maybe_commit_error = db_accessor_->Commit();
if (maybe_commit_error.HasError()) {
const auto &error = maybe_commit_error.GetError();
std::visit(
[&execution_db_accessor = execution_db_accessor_,
&commit_confirmed_by_all_sync_repplicas]<typename T>(T &&arg) {
using ErrorType = std::remove_cvref_t<T>;
if constexpr (std::is_same_v<ErrorType, storage::ReplicationError>) {
commit_confirmed_by_all_sync_repplicas = false;
} else if constexpr (std::is_same_v<ErrorType, storage::ConstraintViolation>) {
const auto &constraint_violation = arg;
auto &label_name = execution_db_accessor->LabelToName(constraint_violation.label);
switch (constraint_violation.type) {
case storage::ConstraintViolation::Type::EXISTENCE: {
MG_ASSERT(constraint_violation.properties.size() == 1U);
auto &property_name = execution_db_accessor->PropertyToName(*constraint_violation.properties.begin());
throw QueryException("Unable to commit due to existence constraint violation on :{}({})", label_name,
property_name);
}
case storage::ConstraintViolation::Type::UNIQUE: {
std::stringstream property_names_stream;
utils::PrintIterable(property_names_stream, constraint_violation.properties, ", ",
[&execution_db_accessor](auto &stream, const auto &prop) {
stream << execution_db_accessor->PropertyToName(prop);
});
throw QueryException("Unable to commit due to unique constraint violation on :{}({})", label_name,
property_names_stream.str());
}
}
} else {
static_assert(kAlwaysFalse<T>, "Missing type from variant visitor");
}
},
error);
}
// The ordered execution of after commit triggers is heavily depending on the exclusiveness of db_accessor_->Commit():
@@ -2363,9 +2588,10 @@ void Interpreter::Commit() {
});
}
reset_necessary_members();
SPDLOG_DEBUG("Finished committing the transaction");
if (!commit_confirmed_by_all_sync_repplicas) {
throw ReplicationException("At least one SYNC replica has not confirmed committing last transaction.");
}
}
void Interpreter::AdvanceCommand() {

View File

@@ -99,14 +99,33 @@ class AuthQueryHandler {
virtual std::vector<std::vector<TypedValue>> GetPrivileges(const std::string &user_or_role) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual void GrantPrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges) = 0;
virtual void GrantPrivilege(
const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges
#ifdef MG_ENTERPRISE
,
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
&label_privileges,
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
&edge_type_privileges
#endif
) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual void DenyPrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual void RevokePrivilege(const std::string &user_or_role,
const std::vector<AuthQuery::Privilege> &privileges) = 0;
virtual void RevokePrivilege(
const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges
#ifdef MG_ENTERPRISE
,
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
&label_privileges,
const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
&edge_type_privileges
#endif
) = 0;
};
enum class QueryHandlerResult { COMMIT, ABORT, NOTHING };
@@ -173,6 +192,13 @@ struct InterpreterContext {
storage::Storage *db;
// ANTLR has singleton instance that is shared between threads. It is
// protected by locks inside of ANTLR. Unfortunately, they are not protected
// in a very good way. Once we have ANTLR version without race conditions we
// can remove this lock. This will probably never happen since ANTLR
// developers introduce more bugs in each version. Fortunately, we have
// cache so this lock probably won't impact performance much...
utils::SpinLock antlr_lock;
std::optional<double> tsc_frequency{utils::GetTSCFrequency()};
std::atomic<bool> is_shutting_down{false};

View File

@@ -52,15 +52,15 @@ constexpr std::string_view GetCodeString(const NotificationCode code) {
return "DropStream"sv;
case NotificationCode::DROP_TRIGGER:
return "DropTrigger"sv;
case NotificationCode::EXISTANT_CONSTRAINT:
case NotificationCode::EXISTENT_CONSTRAINT:
return "ConstraintAlreadyExists"sv;
case NotificationCode::EXISTANT_INDEX:
case NotificationCode::EXISTENT_INDEX:
return "IndexAlreadyExists"sv;
case NotificationCode::LOAD_CSV_TIP:
return "LoadCSVTip"sv;
case NotificationCode::NONEXISTANT_INDEX:
case NotificationCode::NONEXISTENT_INDEX:
return "IndexDoesNotExist"sv;
case NotificationCode::NONEXISTANT_CONSTRAINT:
case NotificationCode::NONEXISTENT_CONSTRAINT:
return "ConstraintDoesNotExist"sv;
case NotificationCode::REGISTER_REPLICA:
return "RegisterReplica"sv;
@@ -114,4 +114,4 @@ std::string ExecutionStatsKeyToString(const ExecutionStats::Key key) {
}
}
} // namespace memgraph::query
} // namespace memgraph::query

View File

@@ -34,11 +34,11 @@ enum class NotificationCode : uint8_t {
DROP_REPLICA,
DROP_STREAM,
DROP_TRIGGER,
EXISTANT_INDEX,
EXISTANT_CONSTRAINT,
EXISTENT_INDEX,
EXISTENT_CONSTRAINT,
LOAD_CSV_TIP,
NONEXISTANT_INDEX,
NONEXISTANT_CONSTRAINT,
NONEXISTENT_INDEX,
NONEXISTENT_CONSTRAINT,
REPLICA_PORT_WARNING,
REGISTER_REPLICA,
SET_REPLICA,

View File

@@ -31,6 +31,12 @@ class Path {
/** Allocator type so that STL containers are aware that we need one */
using allocator_type = utils::Allocator<char>;
/**
* Create the path with no elements
* Allocations are done using the given MemoryResource.
*/
explicit Path(utils::MemoryResource *memory) : vertices_(memory), edges_(memory) {}
/**
* Create the path starting with the given vertex.
* Allocations are done using the given MemoryResource.

View File

@@ -25,12 +25,15 @@
#include <cppitertools/chain.hpp>
#include <cppitertools/imap.hpp>
#include "spdlog/spdlog.h"
#include "query/auth_checker.hpp"
#include "query/context.hpp"
#include "query/db_accessor.hpp"
#include "query/exceptions.hpp"
#include "query/frontend/ast/ast.hpp"
#include "query/frontend/semantic/symbol_table.hpp"
#include "query/graph.hpp"
#include "query/interpret/eval.hpp"
#include "query/path.hpp"
#include "query/plan/scoped_profile.hpp"
@@ -38,14 +41,17 @@
#include "query/procedure/mg_procedure_impl.hpp"
#include "query/procedure/module.hpp"
#include "storage/v2/property_value.hpp"
#include "storage/v2/view.hpp"
#include "utils/algorithm.hpp"
#include "utils/csv_parsing.hpp"
#include "utils/event_counter.hpp"
#include "utils/exceptions.hpp"
#include "utils/fnv.hpp"
#include "utils/license.hpp"
#include "utils/likely.hpp"
#include "utils/logging.hpp"
#include "utils/memory.hpp"
#include "utils/pmr/list.hpp"
#include "utils/pmr/unordered_map.hpp"
#include "utils/pmr/unordered_set.hpp"
#include "utils/pmr/vector.hpp"
@@ -235,6 +241,13 @@ CreateNode::CreateNodeCursor::CreateNodeCursor(const CreateNode &self, utils::Me
bool CreateNode::CreateNodeCursor::Pull(Frame &frame, ExecutionContext &context) {
SCOPED_PROFILE_OP("CreateNode");
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(self_.node_info_.labels,
memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
throw QueryRuntimeException("Vertex not created due to not having enough permission!");
}
#endif
if (input_cursor_->Pull(frame, context)) {
auto created_vertex = CreateLocalVertex(self_.node_info_, &frame, context);
@@ -320,6 +333,21 @@ bool CreateExpand::CreateExpandCursor::Pull(Frame &frame, ExecutionContext &cont
if (!input_cursor_->Pull(frame, context)) return false;
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast()) {
const auto fine_grained_permission = self_.existing_node_
? memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE
: memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE;
if (context.auth_checker &&
!(context.auth_checker->Has(self_.edge_info_.edge_type,
memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE) &&
context.auth_checker->Has(self_.node_info_.labels, fine_grained_permission))) {
throw QueryRuntimeException("Edge not created due to not having enough permission!");
}
}
#endif
// get the origin vertex
TypedValue &vertex_value = frame[self_.input_symbol_];
ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex);
@@ -404,12 +432,31 @@ class ScanAllCursor : public Cursor {
vertices_.emplace(std::move(next_vertices.value()));
vertices_it_.emplace(vertices_.value().begin());
}
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!FindNextVertex(context)) {
return false;
}
#endif
frame[output_symbol_] = *vertices_it_.value();
++vertices_it_.value();
return true;
}
#ifdef MG_ENTERPRISE
bool FindNextVertex(const ExecutionContext &context) {
while (vertices_it_.value() != vertices_.value().end()) {
if (context.auth_checker->Has(*vertices_it_.value(), memgraph::storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ)) {
return true;
}
++vertices_it_.value();
}
return false;
}
#endif
void Shutdown() override { input_cursor_->Shutdown(); }
void Reset() override {
@@ -683,6 +730,15 @@ bool Expand::ExpandCursor::Pull(Frame &frame, ExecutionContext &context) {
// attempt to get a value from the incoming edges
if (in_edges_ && *in_edges_it_ != in_edges_->end()) {
auto edge = *(*in_edges_it_)++;
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge.From(), self_.view_,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
continue;
}
#endif
frame[self_.common_.edge_symbol] = edge;
pull_node(edge, EdgeAtom::Direction::IN);
return true;
@@ -695,6 +751,14 @@ bool Expand::ExpandCursor::Pull(Frame &frame, ExecutionContext &context) {
// we should do only one expansion for cycles, and it was
// already done in the block above
if (self_.common_.direction == EdgeAtom::Direction::BOTH && edge.IsCycle()) continue;
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge.To(), self_.view_,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
continue;
}
#endif
frame[self_.common_.edge_symbol] = edge;
pull_node(edge, EdgeAtom::Direction::OUT);
return true;
@@ -787,9 +851,9 @@ ExpandVariable::ExpandVariable(const std::shared_ptr<LogicalOperator> &input, Sy
weight_lambda_(weight_lambda),
total_weight_(total_weight) {
DMG_ASSERT(type_ == EdgeAtom::Type::DEPTH_FIRST || type_ == EdgeAtom::Type::BREADTH_FIRST ||
type_ == EdgeAtom::Type::WEIGHTED_SHORTEST_PATH,
"ExpandVariable can only be used with breadth first, depth first or "
"weighted shortest path type");
type_ == EdgeAtom::Type::WEIGHTED_SHORTEST_PATH || type_ == EdgeAtom::Type::ALL_SHORTEST_PATHS,
"ExpandVariable can only be used with breadth first, depth first, "
"weighted shortest path or all shortest paths type");
DMG_ASSERT(!(type_ == EdgeAtom::Type::BREADTH_FIRST && is_reverse), "Breadth first expansion can't be reversed");
}
@@ -832,6 +896,7 @@ auto ExpandFromVertex(const VertexAccessor &vertex, EdgeAtom::Direction directio
chain_elements.emplace_back(wrapper(EdgeAtom::Direction::IN, std::move(edges)));
}
}
if (direction != EdgeAtom::Direction::IN) {
auto edges = UnwrapEdgesResult(vertex.OutEdges(view, edge_types));
if (edges.begin() != edges.end()) {
@@ -1015,16 +1080,23 @@ class ExpandVariableCursor : public Cursor {
// if we are here, we have a valid stack,
// get the edge, increase the relevant iterator
auto current_edge = *edges_it_.back()++;
// Check edge-uniqueness.
bool found_existing =
std::any_of(edges_on_frame.begin(), edges_on_frame.end(),
[&current_edge](const TypedValue &edge) { return current_edge.first == edge.ValueEdge(); });
if (found_existing) continue;
AppendEdge(current_edge.first, &edges_on_frame);
VertexAccessor current_vertex =
current_edge.second == EdgeAtom::Direction::IN ? current_edge.first.From() : current_edge.first.To();
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(current_edge.first, memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(current_vertex, storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
continue;
}
#endif
AppendEdge(current_edge.first, &edges_on_frame);
if (!self_.common_.existing_node) {
frame[self_.common_.node_symbol] = current_vertex;
@@ -1185,6 +1257,15 @@ class STShortestPathCursor : public query::plan::Cursor {
if (self_.common_.direction != EdgeAtom::Direction::IN) {
auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::View::OLD, self_.common_.edge_types));
for (const auto &edge : out_edges) {
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge.To(), storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
continue;
}
#endif
if (ShouldExpand(edge.To(), edge, frame, evaluator) && !Contains(in_edge, edge.To())) {
in_edge.emplace(edge.To(), edge);
if (Contains(out_edge, edge.To())) {
@@ -1202,6 +1283,15 @@ class STShortestPathCursor : public query::plan::Cursor {
if (self_.common_.direction != EdgeAtom::Direction::OUT) {
auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::View::OLD, self_.common_.edge_types));
for (const auto &edge : in_edges) {
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge.From(), storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
continue;
}
#endif
if (ShouldExpand(edge.From(), edge, frame, evaluator) && !Contains(in_edge, edge.From())) {
in_edge.emplace(edge.From(), edge);
if (Contains(out_edge, edge.From())) {
@@ -1233,6 +1323,14 @@ class STShortestPathCursor : public query::plan::Cursor {
if (self_.common_.direction != EdgeAtom::Direction::OUT) {
auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::View::OLD, self_.common_.edge_types));
for (const auto &edge : out_edges) {
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge.To(), storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
continue;
}
#endif
if (ShouldExpand(vertex, edge, frame, evaluator) && !Contains(out_edge, edge.To())) {
out_edge.emplace(edge.To(), edge);
if (Contains(in_edge, edge.To())) {
@@ -1250,6 +1348,14 @@ class STShortestPathCursor : public query::plan::Cursor {
if (self_.common_.direction != EdgeAtom::Direction::IN) {
auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::View::OLD, self_.common_.edge_types));
for (const auto &edge : in_edges) {
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge.From(), storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
continue;
}
#endif
if (ShouldExpand(vertex, edge, frame, evaluator) && !Contains(out_edge, edge.From())) {
out_edge.emplace(edge.From(), edge);
if (Contains(in_edge, edge.From())) {
@@ -1296,10 +1402,17 @@ class SingleSourceShortestPathCursor : public query::plan::Cursor {
// for the given (edge, vertex) pair checks if they satisfy the
// "where" condition. if so, places them in the to_visit_ structure.
auto expand_pair = [this, &evaluator, &frame](EdgeAccessor edge, VertexAccessor vertex) {
auto expand_pair = [this, &evaluator, &frame, &context](EdgeAccessor edge, VertexAccessor vertex) {
// if we already processed the given vertex it doesn't get expanded
if (processed_.find(vertex) != processed_.end()) return;
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(vertex, storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
return;
}
#endif
frame[self_.filter_lambda_.inner_edge_symbol] = edge;
frame[self_.filter_lambda_.inner_node_symbol] = vertex;
@@ -1362,6 +1475,7 @@ class SingleSourceShortestPathCursor : public query::plan::Cursor {
const auto &vertex = vertex_value.ValueVertex();
processed_.emplace(vertex, std::nullopt);
expand_from_vertex(vertex);
// go back to loop start and see if we expanded anything
@@ -1429,6 +1543,28 @@ class SingleSourceShortestPathCursor : public query::plan::Cursor {
utils::pmr::vector<std::pair<EdgeAccessor, VertexAccessor>> to_visit_next_;
};
namespace {
void CheckWeightType(TypedValue current_weight, utils::MemoryResource *memory) {
if (!current_weight.IsNumeric() && !current_weight.IsDuration()) {
throw QueryRuntimeException("Calculated weight must be numeric or a Duration, got {}.", current_weight.type());
}
const auto is_valid_numeric = [&] {
return current_weight.IsNumeric() && (current_weight >= TypedValue(0, memory)).ValueBool();
};
const auto is_valid_duration = [&] {
return current_weight.IsDuration() && (current_weight >= TypedValue(utils::Duration(0), memory)).ValueBool();
};
if (!is_valid_numeric() && !is_valid_duration()) {
throw QueryRuntimeException("Calculated weight must be non-negative!");
}
}
} // namespace
class ExpandWeightedShortestPathCursor : public query::plan::Cursor {
public:
ExpandWeightedShortestPathCursor(const ExpandVariable &self, utils::MemoryResource *mem)
@@ -1451,9 +1587,18 @@ class ExpandWeightedShortestPathCursor : public query::plan::Cursor {
// For the given (edge, vertex, weight, depth) tuple checks if they
// satisfy the "where" condition. if so, places them in the priority
// queue.
auto expand_pair = [this, &evaluator, &frame, &create_state](const EdgeAccessor &edge, const VertexAccessor &vertex,
const TypedValue &total_weight, int64_t depth) {
auto expand_pair = [this, &evaluator, &frame, &create_state, &context](
const EdgeAccessor &edge, const VertexAccessor &vertex, const TypedValue &total_weight,
int64_t depth) {
auto *memory = evaluator.GetMemoryResource();
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(vertex, storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
return;
}
#endif
if (self_.filter_lambda_.expression) {
frame[self_.filter_lambda_.inner_edge_symbol] = edge;
frame[self_.filter_lambda_.inner_node_symbol] = vertex;
@@ -1466,21 +1611,7 @@ class ExpandWeightedShortestPathCursor : public query::plan::Cursor {
TypedValue current_weight = self_.weight_lambda_->expression->Accept(evaluator);
if (!current_weight.IsNumeric() && !current_weight.IsDuration()) {
throw QueryRuntimeException("Calculated weight must be numeric or a Duration, got {}.", current_weight.type());
}
const auto is_valid_numeric = [&] {
return current_weight.IsNumeric() && (current_weight >= TypedValue(0, memory)).ValueBool();
};
const auto is_valid_duration = [&] {
return current_weight.IsDuration() && (current_weight >= TypedValue(utils::Duration(0), memory)).ValueBool();
};
if (!is_valid_numeric() && !is_valid_duration()) {
throw QueryRuntimeException("Calculated weight must be non-negative!");
}
CheckWeightType(current_weight, memory);
auto next_state = create_state(vertex, depth);
@@ -1690,6 +1821,318 @@ class ExpandWeightedShortestPathCursor : public query::plan::Cursor {
}
};
class ExpandAllShortestPathsCursor : public query::plan::Cursor {
public:
ExpandAllShortestPathsCursor(const ExpandVariable &self, utils::MemoryResource *mem)
: self_(self),
input_cursor_(self_.input_->MakeCursor(mem)),
visited_cost_(mem),
expanded_(mem),
next_edges_(mem),
traversal_stack_(mem),
pq_(mem) {}
bool Pull(Frame &frame, ExecutionContext &context) override {
SCOPED_PROFILE_OP("ExpandAllShortestPathsCursor");
ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor,
storage::View::OLD);
// For the given (edge, direction, weight, depth) tuple checks if they
// satisfy the "where" condition. if so, places them in the priority
// queue.
auto expand_vertex = [this, &evaluator, &frame](const EdgeAccessor &edge, const EdgeAtom::Direction direction,
const TypedValue &total_weight, int64_t depth) {
auto *memory = evaluator.GetMemoryResource();
auto const &next_vertex = direction == EdgeAtom::Direction::IN ? edge.From() : edge.To();
// If filter expression exists, evaluate filter
if (self_.filter_lambda_.expression) {
frame[self_.filter_lambda_.inner_edge_symbol] = edge;
frame[self_.filter_lambda_.inner_node_symbol] = next_vertex;
if (!EvaluateFilter(evaluator, self_.filter_lambda_.expression)) return;
}
// Evaluate current weight
frame[self_.weight_lambda_->inner_edge_symbol] = edge;
frame[self_.weight_lambda_->inner_node_symbol] = next_vertex;
TypedValue current_weight = self_.weight_lambda_->expression->Accept(evaluator);
CheckWeightType(current_weight, memory);
TypedValue next_weight = std::invoke([&] {
if (total_weight.IsNull()) {
return current_weight;
}
ValidateWeightTypes(current_weight, total_weight);
return TypedValue(current_weight, memory) + total_weight;
});
auto found_it = visited_cost_.find(next_vertex);
// Check if the vertex has already been processed.
if (found_it != visited_cost_.end()) {
auto weight = found_it->second;
if (weight.IsNull() || (next_weight <= weight).ValueBool()) {
// Has been visited, but now found a shorter path
visited_cost_[next_vertex] = next_weight;
} else {
// Continue and do not expand if current weight is larger
return;
}
} else {
visited_cost_[next_vertex] = next_weight;
}
DirectedEdge directed_edge = {edge, direction, next_weight};
pq_.push({next_weight, depth + 1, next_vertex, directed_edge});
};
// Populates the priority queue structure with expansions
// from the given vertex. skips expansions that don't satisfy
// the "where" condition.
auto expand_from_vertex = [this, &expand_vertex, &context](const VertexAccessor &vertex, const TypedValue &weight,
int64_t depth) {
if (self_.common_.direction != EdgeAtom::Direction::IN) {
auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::View::OLD, self_.common_.edge_types));
for (const auto &edge : out_edges) {
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(edge.To(), storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
continue;
}
#endif
expand_vertex(edge, EdgeAtom::Direction::OUT, weight, depth);
}
}
if (self_.common_.direction != EdgeAtom::Direction::OUT) {
auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::View::OLD, self_.common_.edge_types));
for (const auto &edge : in_edges) {
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(edge.From(), storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
continue;
}
#endif
expand_vertex(edge, EdgeAtom::Direction::IN, weight, depth);
}
}
};
// Check if upper bound exists
upper_bound_ = self_.upper_bound_
? EvaluateInt(&evaluator, self_.upper_bound_, "Max depth in all shortest paths expansion")
: std::numeric_limits<int64_t>::max();
// Check if upper bound is valid
if (upper_bound_ < 1) {
throw QueryRuntimeException("Maximum depth in all shortest paths expansion must be at least 1.");
}
std::optional<VertexAccessor> start_vertex;
auto *memory = context.evaluation_context.memory;
while (true) {
// Check if there is an external error.
if (MustAbort(context)) throw HintedAbortError();
// If traversal stack if filled, the DFS traversal tree is created. Traverse the tree iteratively by
// preserving the traversal state on stack.
while (!traversal_stack_.empty()) {
auto &current_level = traversal_stack_.back();
auto &edges_on_frame = frame[self_.common_.edge_symbol].ValueList();
// Clean out the current stack
if (current_level.empty()) {
if (!edges_on_frame.empty()) {
if (!self_.is_reverse_)
edges_on_frame.erase(edges_on_frame.end());
else
edges_on_frame.erase(edges_on_frame.begin());
}
traversal_stack_.pop_back();
continue;
}
auto [current_edge, current_edge_direction, current_weight] = current_level.back();
current_level.pop_back();
// Edges order depends on direction of expansion
if (!self_.is_reverse_)
edges_on_frame.emplace_back(current_edge);
else
edges_on_frame.emplace(edges_on_frame.begin(), current_edge);
auto next_vertex = current_edge_direction == EdgeAtom::Direction::IN ? current_edge.From() : current_edge.To();
frame[self_.common_.node_symbol] = next_vertex;
frame[self_.total_weight_.value()] = current_weight;
if (next_edges_.find({next_vertex, traversal_stack_.size()}) != next_edges_.end()) {
auto next_vertex_edges = next_edges_[{next_vertex, traversal_stack_.size()}];
traversal_stack_.emplace_back(std::move(next_vertex_edges));
} else {
// Signal the end of iteration
utils::pmr::list<DirectedEdge> empty(memory);
traversal_stack_.emplace_back(std::move(empty));
}
if ((current_weight > visited_cost_.at(next_vertex)).ValueBool()) continue;
return true;
}
// If priority queue is empty start new pulling stream.
if (pq_.empty()) {
// Finish if there is nothing to pull
if (!input_cursor_->Pull(frame, context)) return false;
const auto &vertex_value = frame[self_.input_symbol_];
if (vertex_value.IsNull()) continue;
start_vertex = vertex_value.ValueVertex();
if (self_.common_.existing_node) {
const auto &node = frame[self_.common_.node_symbol];
// Due to optional matching the existing node could be null.
// Skip expansion for such nodes.
if (node.IsNull()) continue;
}
// Clear existing data structures.
visited_cost_.clear();
expanded_.clear();
next_edges_.clear();
traversal_stack_.clear();
pq_.push({TypedValue(), 0, *start_vertex, std::nullopt});
visited_cost_.emplace(*start_vertex, 0);
frame[self_.common_.edge_symbol] = TypedValue::TVector(memory);
}
// Create a DFS traversal tree from the start node
while (!pq_.empty()) {
if (MustAbort(context)) throw HintedAbortError();
auto [current_weight, current_depth, current_vertex, maybe_directed_edge] = pq_.top();
pq_.pop();
// Expand only if what we've just expanded is less than max depth.
if (current_depth < upper_bound_) {
if (maybe_directed_edge) {
auto &[current_edge, direction, weight] = *maybe_directed_edge;
if (expanded_.find(current_edge) != expanded_.end()) continue;
expanded_.emplace(current_edge);
}
expand_from_vertex(current_vertex, current_weight, current_depth);
}
// if current vertex is not starting vertex, maybe_directed_edge will not be nullopt
if (maybe_directed_edge) {
auto &[current_edge, direction, weight] = *maybe_directed_edge;
// Searching for a previous vertex in the expansion
auto prev_vertex = direction == EdgeAtom::Direction::IN ? current_edge.To() : current_edge.From();
// Update the parent
if (next_edges_.find({prev_vertex, current_depth - 1}) == next_edges_.end()) {
utils::pmr::list<DirectedEdge> empty(memory);
next_edges_[{prev_vertex, current_depth - 1}] = std::move(empty);
}
next_edges_.at({prev_vertex, current_depth - 1}).emplace_back(*maybe_directed_edge);
}
}
if (start_vertex && next_edges_.find({*start_vertex, 0}) != next_edges_.end()) {
auto start_vertex_edges = next_edges_[{*start_vertex, 0}];
traversal_stack_.emplace_back(std::move(start_vertex_edges));
}
}
}
void Shutdown() override { input_cursor_->Shutdown(); }
void Reset() override {
input_cursor_->Reset();
visited_cost_.clear();
expanded_.clear();
next_edges_.clear();
traversal_stack_.clear();
ClearQueue();
}
private:
const ExpandVariable &self_;
const UniqueCursorPtr input_cursor_;
// Upper bound on the path length.
int64_t upper_bound_{-1};
struct AspStateHash {
size_t operator()(const std::pair<VertexAccessor, int64_t> &key) const {
return utils::HashCombine<VertexAccessor, int64_t>{}(key.first, key.second);
}
};
using DirectedEdge = std::tuple<EdgeAccessor, EdgeAtom::Direction, TypedValue>;
using NextEdgesState = std::pair<VertexAccessor, int64_t>;
// Maps vertices to minimum weights they got in expansion.
utils::pmr::unordered_map<VertexAccessor, TypedValue> visited_cost_;
// Marking the expanded edges to prevent multiple visits.
utils::pmr::unordered_set<EdgeAccessor> expanded_;
// Maps the vertex with the potential expansion edge.
utils::pmr::unordered_map<NextEdgesState, utils::pmr::list<DirectedEdge>, AspStateHash> next_edges_;
// Stack indicating the traversal level.
utils::pmr::list<utils::pmr::list<DirectedEdge>> traversal_stack_;
static void ValidateWeightTypes(const TypedValue &lhs, const TypedValue &rhs) {
if (!((lhs.IsNumeric() && lhs.IsNumeric()) || (rhs.IsDuration() && rhs.IsDuration()))) {
throw QueryRuntimeException(utils::MessageWithLink(
"All weights should be of the same type, either numeric or a Duration. Please update the weight "
"expression or the filter expression.",
"https://memgr.ph/wsp"));
}
}
// Priority queue comparator. Keep lowest weight on top of the queue.
class PriorityQueueComparator {
public:
bool operator()(const std::tuple<TypedValue, int64_t, VertexAccessor, std::optional<DirectedEdge>> &lhs,
const std::tuple<TypedValue, int64_t, VertexAccessor, std::optional<DirectedEdge>> &rhs) {
const auto &lhs_weight = std::get<0>(lhs);
const auto &rhs_weight = std::get<0>(rhs);
// Null defines minimum value for all types
if (lhs_weight.IsNull()) {
return false;
}
if (rhs_weight.IsNull()) {
return true;
}
ValidateWeightTypes(lhs_weight, rhs_weight);
return (lhs_weight > rhs_weight).ValueBool();
}
};
// Priority queue - core element of the algorithm.
// Stores: {weight, depth, next vertex, edge and direction}
std::priority_queue<std::tuple<TypedValue, int64_t, VertexAccessor, std::optional<DirectedEdge>>,
utils::pmr::vector<std::tuple<TypedValue, int64_t, VertexAccessor, std::optional<DirectedEdge>>>,
PriorityQueueComparator>
pq_;
void ClearQueue() {
while (!pq_.empty()) pq_.pop();
}
};
UniqueCursorPtr ExpandVariable::MakeCursor(utils::MemoryResource *mem) const {
EventCounter::IncrementCounter(EventCounter::ExpandVariableOperator);
@@ -1704,6 +2147,8 @@ UniqueCursorPtr ExpandVariable::MakeCursor(utils::MemoryResource *mem) const {
return MakeUniqueCursorPtr<ExpandVariableCursor>(mem, *this, mem);
case EdgeAtom::Type::WEIGHTED_SHORTEST_PATH:
return MakeUniqueCursorPtr<ExpandWeightedShortestPathCursor>(mem, *this, mem);
case EdgeAtom::Type::ALL_SHORTEST_PATHS:
return MakeUniqueCursorPtr<ExpandAllShortestPathsCursor>(mem, *this, mem);
case EdgeAtom::Type::SINGLE:
LOG_FATAL("ExpandVariable should not be planned for a single expansion!");
}
@@ -1919,7 +2364,16 @@ bool Delete::DeleteCursor::Pull(Frame &frame, ExecutionContext &context) {
for (TypedValue &expression_result : expression_results) {
if (MustAbort(context)) throw HintedAbortError();
if (expression_result.type() == TypedValue::Type::Edge) {
auto maybe_value = dba.RemoveEdge(&expression_result.ValueEdge());
auto &ea = expression_result.ValueEdge();
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(ea, query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE) &&
context.auth_checker->Has(ea.To(), storage::View::NEW, query::AuthQuery::FineGrainedPrivilege::UPDATE) &&
context.auth_checker->Has(ea.From(), storage::View::NEW, query::AuthQuery::FineGrainedPrivilege::UPDATE))) {
throw QueryRuntimeException("Edge not deleted due to not having enough permission!");
}
#endif
auto maybe_value = dba.RemoveEdge(&ea);
if (maybe_value.HasError()) {
switch (maybe_value.GetError()) {
case storage::Error::SERIALIZATION_ERROR:
@@ -1944,6 +2398,12 @@ bool Delete::DeleteCursor::Pull(Frame &frame, ExecutionContext &context) {
switch (expression_result.type()) {
case TypedValue::Type::Vertex: {
auto &va = expression_result.ValueVertex();
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(va, storage::View::NEW, query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
throw QueryRuntimeException("Vertex not deleted due to not having enough permission!");
}
#endif
if (self_.detach_) {
auto res = dba.DetachRemoveVertex(&va);
if (res.HasError()) {
@@ -2047,6 +2507,13 @@ bool SetProperty::SetPropertyCursor::Pull(Frame &frame, ExecutionContext &contex
switch (lhs.type()) {
case TypedValue::Type::Vertex: {
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(lhs.ValueVertex(), storage::View::NEW,
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw QueryRuntimeException("Vertex property not set due to not having enough permission!");
}
#endif
auto old_value = PropsSetChecked(&lhs.ValueVertex(), self_.property_, rhs);
context.execution_stats[ExecutionStats::Key::UPDATED_PROPERTIES] += 1;
if (context.trigger_context_collector) {
@@ -2057,10 +2524,17 @@ bool SetProperty::SetPropertyCursor::Pull(Frame &frame, ExecutionContext &contex
break;
}
case TypedValue::Type::Edge: {
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(lhs.ValueEdge(), memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw QueryRuntimeException("Edge property not set due to not having enough permission!");
}
#endif
auto old_value = PropsSetChecked(&lhs.ValueEdge(), self_.property_, rhs);
context.execution_stats[ExecutionStats::Key::UPDATED_PROPERTIES] += 1;
if (context.trigger_context_collector) {
// rhs cannot be moved because it was created with the allocator that is only valid during current pull
// rhs cannot be moved because it was created with the allocator that is only valid
// during current pull
context.trigger_context_collector->RegisterSetObjectProperty(lhs.ValueEdge(), self_.property_,
TypedValue{std::move(old_value)}, TypedValue{rhs});
}
@@ -2249,9 +2723,23 @@ bool SetProperties::SetPropertiesCursor::Pull(Frame &frame, ExecutionContext &co
switch (lhs.type()) {
case TypedValue::Type::Vertex:
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(lhs.ValueVertex(), storage::View::NEW,
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw QueryRuntimeException("Vertex properties not set due to not having enough permission!");
}
#endif
SetPropertiesOnRecord(&lhs.ValueVertex(), rhs, self_.op_, &context);
break;
case TypedValue::Type::Edge:
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(lhs.ValueEdge(), memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw QueryRuntimeException("Edge properties not set due to not having enough permission!");
}
#endif
SetPropertiesOnRecord(&lhs.ValueEdge(), rhs, self_.op_, &context);
break;
case TypedValue::Type::Null:
@@ -2289,6 +2777,13 @@ SetLabels::SetLabelsCursor::SetLabelsCursor(const SetLabels &self, utils::Memory
bool SetLabels::SetLabelsCursor::Pull(Frame &frame, ExecutionContext &context) {
SCOPED_PROFILE_OP("SetLabels");
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(self_.labels_, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
throw QueryRuntimeException("Couldn't set label due to not having enough permission!");
}
#endif
if (!input_cursor_->Pull(frame, context)) return false;
TypedValue &vertex_value = frame[self_.input_symbol_];
@@ -2296,6 +2791,15 @@ bool SetLabels::SetLabelsCursor::Pull(Frame &frame, ExecutionContext &context) {
if (vertex_value.IsNull()) return true;
ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex);
auto &vertex = vertex_value.ValueVertex();
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(vertex, storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw QueryRuntimeException("Couldn't set label due to not having enough permission!");
}
#endif
for (auto label : self_.labels_) {
auto maybe_value = vertex.AddLabel(label);
if (maybe_value.HasError()) {
@@ -2378,9 +2882,22 @@ bool RemoveProperty::RemovePropertyCursor::Pull(Frame &frame, ExecutionContext &
switch (lhs.type()) {
case TypedValue::Type::Vertex:
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(lhs.ValueVertex(), storage::View::NEW,
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw QueryRuntimeException("Vertex property not removed due to not having enough permission!");
}
#endif
remove_prop(&lhs.ValueVertex());
break;
case TypedValue::Type::Edge:
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(lhs.ValueEdge(), memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw QueryRuntimeException("Edge property not removed due to not having enough permission!");
}
#endif
remove_prop(&lhs.ValueEdge());
break;
case TypedValue::Type::Null:
@@ -2418,6 +2935,13 @@ RemoveLabels::RemoveLabelsCursor::RemoveLabelsCursor(const RemoveLabels &self, u
bool RemoveLabels::RemoveLabelsCursor::Pull(Frame &frame, ExecutionContext &context) {
SCOPED_PROFILE_OP("RemoveLabels");
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(self_.labels_, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
throw QueryRuntimeException("Couldn't remove label due to not having enough permission!");
}
#endif
if (!input_cursor_->Pull(frame, context)) return false;
TypedValue &vertex_value = frame[self_.input_symbol_];
@@ -2425,6 +2949,15 @@ bool RemoveLabels::RemoveLabelsCursor::Pull(Frame &frame, ExecutionContext &cont
if (vertex_value.IsNull()) return true;
ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex);
auto &vertex = vertex_value.ValueVertex();
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(vertex, storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw QueryRuntimeException("Couldn't remove label due to not having enough permission!");
}
#endif
for (auto label : self_.labels_) {
auto maybe_value = vertex.RemoveLabel(label);
if (maybe_value.HasError()) {
@@ -2611,6 +3144,8 @@ TypedValue DefaultAggregationOpValue(const Aggregate::Element &element, utils::M
return TypedValue(TypedValue::TVector(memory));
case Aggregation::Op::COLLECT_MAP:
return TypedValue(TypedValue::TMap(memory));
case Aggregation::Op::PROJECT:
return TypedValue(query::Graph(memory));
}
}
} // namespace
@@ -2773,7 +3308,6 @@ class AggregateCursor : public Cursor {
"Expected as much AggregationValue.counts_ as there are "
"aggregations.");
// we iterate over counts, values and aggregation info at the same time
auto count_it = agg_value->counts_.begin();
auto value_it = agg_value->values_.begin();
auto agg_elem_it = self_.aggregations_.begin();
@@ -2812,6 +3346,11 @@ class AggregateCursor : public Cursor {
case Aggregation::Op::COLLECT_LIST:
value_it->ValueList().push_back(input_value);
break;
case Aggregation::Op::PROJECT: {
EnsureOkForProject(input_value);
value_it->ValueGraph().Expand(input_value.ValuePath());
break;
}
case Aggregation::Op::COLLECT_MAP:
auto key = agg_elem_it->key->Accept(*evaluator);
if (key.type() != TypedValue::Type::String) throw QueryRuntimeException("Map key must be a string.");
@@ -2860,6 +3399,11 @@ class AggregateCursor : public Cursor {
case Aggregation::Op::COLLECT_LIST:
value_it->ValueList().push_back(input_value);
break;
case Aggregation::Op::PROJECT: {
EnsureOkForProject(input_value);
value_it->ValueGraph().Expand(input_value.ValuePath());
break;
}
case Aggregation::Op::COLLECT_MAP:
auto key = agg_elem_it->key->Accept(*evaluator);
if (key.type() != TypedValue::Type::String) throw QueryRuntimeException("Map key must be a string.");
@@ -2896,6 +3440,18 @@ class AggregateCursor : public Cursor {
throw QueryRuntimeException("Only numeric values allowed in SUM and AVG aggregations.");
}
}
/** Checks if the given TypedValue is legal in PROJECT and PROJECT_TRANSITIVE. If not
* an appropriate exception is thrown. */
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
void EnsureOkForProject(const TypedValue &value) const {
switch (value.type()) {
case TypedValue::Type::Path:
return;
default:
throw QueryRuntimeException("Only path values allowed in PROJECT aggregation.");
}
}
};
UniqueCursorPtr Aggregate::MakeCursor(utils::MemoryResource *mem) const {
@@ -3173,9 +3729,7 @@ bool Merge::MergeCursor::Pull(Frame &frame, ExecutionContext &context) {
if (pull_input_) {
// if we have just now pulled from the input
// and failed to pull from merge_match, we should create
__attribute__((unused)) bool merge_create_pull_result = merge_create_cursor_->Pull(frame, context);
DMG_ASSERT(merge_create_pull_result, "MergeCreate must never fail");
return true;
return merge_create_cursor_->Pull(frame, context);
}
// We have exhausted merge_match_cursor_ after 1 or more successful
// Pulls. Attempt next input_cursor_ pull
@@ -3708,6 +4262,18 @@ void CallCustomProcedure(const std::string_view fully_qualified_procedure_name,
for (auto *expression : args) {
args_list.emplace_back(expression->Accept(*evaluator));
}
std::optional<query::Graph> subgraph;
std::optional<query::SubgraphDbAccessor> db_acc;
if (!args_list.empty() && args_list.front().type() == TypedValue::Type::Graph) {
auto subgraph_value = args_list.front().ValueGraph();
subgraph = query::Graph(std::move(subgraph_value), subgraph_value.GetMemoryResource());
args_list.erase(args_list.begin());
db_acc = query::SubgraphDbAccessor(*std::get<query::DbAccessor *>(graph.impl), &*subgraph);
graph.impl = &*db_acc;
}
procedure::ConstructArguments(args_list, proc, fully_qualified_procedure_name, proc_args, graph);
if (memory_limit) {
SPDLOG_INFO("Running '{}' with memory limit of {}", fully_qualified_procedure_name,

View File

@@ -1082,6 +1082,7 @@ pulled.")
// that should be inaccessible (private class function won't compile)
friend class ExpandVariableCursor;
friend class ExpandWeightedShortestPathCursor;
friend class ExpandAllShortestPathCursor;
cpp<#)
(:serialize (:slk))
(:clone))

View File

@@ -65,7 +65,7 @@ std::vector<Expansion> NormalizePatterns(const SymbolTable &symbol_table, const
// Remove symbols which are bound by lambda arguments.
collector.symbols_.erase(symbol_table.at(*edge->filter_lambda_.inner_edge));
collector.symbols_.erase(symbol_table.at(*edge->filter_lambda_.inner_node));
if (edge->type_ == EdgeAtom::Type::WEIGHTED_SHORTEST_PATH) {
if (edge->type_ == EdgeAtom::Type::WEIGHTED_SHORTEST_PATH || edge->type_ == EdgeAtom::Type::ALL_SHORTEST_PATHS) {
collector.symbols_.erase(symbol_table.at(*edge->weight_lambda_.inner_edge));
collector.symbols_.erase(symbol_table.at(*edge->weight_lambda_.inner_node));
}

View File

@@ -120,6 +120,9 @@ bool PlanPrinter::PreVisit(query::plan::ExpandVariable &op) {
case Type::WEIGHTED_SHORTEST_PATH:
*out_ << "WeightedShortestPath";
break;
case Type::ALL_SHORTEST_PATHS:
*out_ << "AllShortestPaths";
break;
case Type::SINGLE:
LOG_FATAL("Unexpected ExpandVariable::type_");
}
@@ -308,6 +311,8 @@ std::string ToString(EdgeAtom::Type type) {
return "dfs";
case EdgeAtom::Type::WEIGHTED_SHORTEST_PATH:
return "wsp";
case EdgeAtom::Type::ALL_SHORTEST_PATHS:
return "asp";
case EdgeAtom::Type::SINGLE:
return "single";
}
@@ -548,7 +553,7 @@ bool PlanToJsonVisitor::PreVisit(ExpandVariable &op) {
self["filter_lambda"] = op.filter_lambda_.expression ? ToJson(op.filter_lambda_.expression) : json();
if (op.type_ == EdgeAtom::Type::WEIGHTED_SHORTEST_PATH) {
if (op.type_ == EdgeAtom::Type::WEIGHTED_SHORTEST_PATH || op.type_ == EdgeAtom::Type::ALL_SHORTEST_PATHS) {
self["weight_lambda"] = ToJson(op.weight_lambda_->expression);
self["total_weight_symbol"] = ToJson(*op.total_weight_);
}

View File

@@ -13,11 +13,12 @@
#include "utils/flag_validation.hpp"
DEFINE_VALIDATED_HIDDEN_int64(query_vertex_count_to_expand_existing, 10,
"Maximum count of indexed vertices which provoke "
"indexed lookup and then expand to existing, instead of "
"a regular expand. Default is 10, to turn off use -1.",
FLAG_IN_RANGE(-1, std::numeric_limits<std::int64_t>::max()));
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_VALIDATED_int64(query_vertex_count_to_expand_existing, 10,
"Maximum count of indexed vertices which provoke "
"indexed lookup and then expand to existing, instead of "
"a regular expand. Default is 10, to turn off use -1.",
FLAG_IN_RANGE(-1, std::numeric_limits<std::int64_t>::max()));
namespace memgraph::query::plan::impl {

View File

@@ -418,7 +418,7 @@ class RuleBasedPlanner {
std::optional<ExpansionLambda> weight_lambda;
std::optional<Symbol> total_weight;
if (edge->type_ == EdgeAtom::Type::WEIGHTED_SHORTEST_PATH) {
if (edge->type_ == EdgeAtom::Type::WEIGHTED_SHORTEST_PATH || edge->type_ == EdgeAtom::Type::ALL_SHORTEST_PATHS) {
weight_lambda.emplace(ExpansionLambda{symbol_table.at(*edge->weight_lambda_.inner_edge),
symbol_table.at(*edge->weight_lambda_.inner_node),
edge->weight_lambda_.expression});

View File

@@ -17,8 +17,9 @@
#include "utils/flag_validation.hpp"
#include "utils/logging.hpp"
DEFINE_VALIDATED_HIDDEN_uint64(query_max_plans, 1000U, "Maximum number of generated plans for a query.",
FLAG_IN_RANGE(1, std::numeric_limits<std::uint64_t>::max()));
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_VALIDATED_uint64(query_max_plans, 1000U, "Maximum number of generated plans for a query.",
FLAG_IN_RANGE(1, std::numeric_limits<std::uint64_t>::max()));
namespace memgraph::query::plan::impl {

View File

@@ -24,6 +24,7 @@
#include "mg_procedure.h"
#include "module.hpp"
#include "query/frontend/ast/ast.hpp"
#include "query/procedure/cypher_types.hpp"
#include "query/procedure/mg_procedure_helpers.hpp"
#include "query/stream/common.hpp"
@@ -31,6 +32,7 @@
#include "storage/v2/view.hpp"
#include "utils/algorithm.hpp"
#include "utils/concepts.hpp"
#include "utils/license.hpp"
#include "utils/logging.hpp"
#include "utils/math.hpp"
#include "utils/memory.hpp"
@@ -38,6 +40,9 @@
#include "utils/temporal.hpp"
#include "utils/variant_helpers.hpp"
#include <cppitertools/filter.hpp>
#include <cppitertools/imap.hpp>
// This file contains implementation of top level C API functions, but this is
// all actually part of memgraph::query::procedure. So use that namespace for simplicity.
// NOLINTNEXTLINE(google-build-using-namespace)
@@ -119,6 +124,10 @@ struct SerializationException : public memgraph::utils::BasicException {
using memgraph::utils::BasicException::BasicException;
};
struct AuthorizationException : public memgraph::utils::BasicException {
using memgraph::utils::BasicException::BasicException;
};
template <typename TFunc, typename TReturn>
concept ReturnsType = std::same_as<std::invoke_result_t<TFunc>, TReturn>;
@@ -159,6 +168,9 @@ template <typename TFunc, typename... Args>
} catch (const SerializationException &se) {
spdlog::error("Serialization error during mg API call: {}", se.what());
return mgp_error::MGP_ERROR_SERIALIZATION_ERROR;
} catch (const AuthorizationException &ae) {
spdlog::error("Authorization error during mg API call: {}", ae.what());
return mgp_error::MGP_ERROR_AUTHORIZATION_ERROR;
} catch (const std::bad_alloc &bae) {
spdlog::error("Memory allocation error during mg API call: {}", bae.what());
return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE;
@@ -290,6 +302,8 @@ mgp_value_type FromTypedValueType(memgraph::query::TypedValue::Type type) {
return MGP_VALUE_TYPE_LOCAL_DATE_TIME;
case memgraph::query::TypedValue::Type::Duration:
return MGP_VALUE_TYPE_DURATION;
case memgraph::query::TypedValue::Type::Graph:
throw std::logic_error{"mgp_value for TypedValue::Type::Graph doesn't exist."};
}
}
} // namespace
@@ -324,17 +338,17 @@ memgraph::query::TypedValue ToTypedValue(const mgp_value &val, memgraph::utils::
return memgraph::query::TypedValue(std::move(tv_map));
}
case MGP_VALUE_TYPE_VERTEX:
return memgraph::query::TypedValue(val.vertex_v->impl, memory);
return memgraph::query::TypedValue(val.vertex_v->getImpl(), memory);
case MGP_VALUE_TYPE_EDGE:
return memgraph::query::TypedValue(val.edge_v->impl, memory);
case MGP_VALUE_TYPE_PATH: {
const auto *path = val.path_v;
MG_ASSERT(!path->vertices.empty());
MG_ASSERT(path->vertices.size() == path->edges.size() + 1);
memgraph::query::Path tv_path(path->vertices[0].impl, memory);
memgraph::query::Path tv_path(path->vertices[0].getImpl(), memory);
for (size_t i = 0; i < path->edges.size(); ++i) {
tv_path.Expand(path->edges[i].impl);
tv_path.Expand(path->vertices[i + 1].impl);
tv_path.Expand(path->vertices[i + 1].getImpl());
}
return memgraph::query::TypedValue(std::move(tv_path));
}
@@ -454,12 +468,31 @@ mgp_value::mgp_value(const memgraph::query::TypedValue &tv, mgp_graph *graph, me
}
case MGP_VALUE_TYPE_VERTEX: {
memgraph::utils::Allocator<mgp_vertex> allocator(m);
vertex_v = allocator.new_object<mgp_vertex>(tv.ValueVertex(), graph);
vertex_v = std::visit(
memgraph::utils::Overloaded{
[&](memgraph::query::DbAccessor *) { return allocator.new_object<mgp_vertex>(tv.ValueVertex(), graph); },
[&](memgraph::query::SubgraphDbAccessor *impl) {
return allocator.new_object<mgp_vertex>(
memgraph::query::SubgraphVertexAccessor(tv.ValueVertex(), impl->getGraph()), graph);
}},
graph->impl);
break;
}
case MGP_VALUE_TYPE_EDGE: {
memgraph::utils::Allocator<mgp_edge> allocator(m);
edge_v = allocator.new_object<mgp_edge>(tv.ValueEdge(), graph);
edge_v = std::visit(
memgraph::utils::Overloaded{
[&tv, graph, &allocator](memgraph::query::DbAccessor *) {
return allocator.new_object<mgp_edge>(tv.ValueEdge(), graph);
},
[&tv, graph, &allocator](memgraph::query::SubgraphDbAccessor *db_impl) {
return allocator.new_object<mgp_edge>(
tv.ValueEdge(), memgraph::query::SubgraphVertexAccessor(tv.ValueEdge().From(), db_impl->getGraph()),
memgraph::query::SubgraphVertexAccessor(tv.ValueEdge().To(), db_impl->getGraph()), graph);
}},
graph->impl);
break;
}
case MGP_VALUE_TYPE_PATH: {
@@ -470,11 +503,24 @@ mgp_value::mgp_value(const memgraph::query::TypedValue &tv, mgp_graph *graph, me
mgp_path tmp_path(m);
tmp_path.vertices.reserve(tv.ValuePath().vertices().size());
for (const auto &v : tv.ValuePath().vertices()) {
tmp_path.vertices.emplace_back(v, graph);
std::visit(
memgraph::utils::Overloaded{
[&v, graph, &tmp_path](memgraph::query::DbAccessor *) { tmp_path.vertices.emplace_back(v, graph); },
[&v, graph, &tmp_path](memgraph::query::SubgraphDbAccessor *impl) {
tmp_path.vertices.emplace_back(memgraph::query::SubgraphVertexAccessor(v, impl->getGraph()), graph);
}},
graph->impl);
}
tmp_path.edges.reserve(tv.ValuePath().edges().size());
for (const auto &e : tv.ValuePath().edges()) {
tmp_path.edges.emplace_back(e, graph);
std::visit(memgraph::utils::Overloaded{
[&e, graph, &tmp_path](memgraph::query::DbAccessor *) { tmp_path.edges.emplace_back(e, graph); },
[&e, graph, &tmp_path](memgraph::query::SubgraphDbAccessor *db_impl) {
tmp_path.edges.emplace_back(
e, memgraph::query::SubgraphVertexAccessor(e.From(), db_impl->getGraph()),
memgraph::query::SubgraphVertexAccessor(e.To(), db_impl->getGraph()), graph);
}},
graph->impl);
}
memgraph::utils::Allocator<mgp_path> allocator(m);
path_v = allocator.new_object<mgp_path>(std::move(tmp_path));
@@ -799,7 +845,19 @@ mgp_value::mgp_value(mgp_value &&other, memgraph::utils::MemoryResource *m) : ty
mgp_value::~mgp_value() noexcept { DeleteValueMember(this); }
mgp_edge *mgp_edge::Copy(const mgp_edge &edge, mgp_memory &memory) {
return NewRawMgpObject<mgp_edge>(&memory, edge.impl, edge.from.graph);
return std::visit(
memgraph::utils::Overloaded{
[&](memgraph::query::DbAccessor *) { return NewRawMgpObject<mgp_edge>(&memory, edge.impl, edge.from.graph); },
[&](memgraph::query::SubgraphDbAccessor *db_impl) {
return NewRawMgpObject<mgp_edge>(
&memory, edge.impl, memgraph::query::SubgraphVertexAccessor(edge.impl.From(), db_impl->getGraph()),
memgraph::query::SubgraphVertexAccessor(edge.impl.To(), db_impl->getGraph()), edge.to.graph);
}},
edge.to.graph->impl);
}
mgp_error mgp_value_copy(mgp_value *val, mgp_memory *memory, mgp_value **result) {
return WrapExceptions([val, memory] { return NewRawMgpObject<mgp_value>(memory, *val); }, result);
}
void mgp_value_destroy(mgp_value *val) { DeleteRawMgpObject(val); }
@@ -917,6 +975,10 @@ mgp_error mgp_list_make_empty(size_t capacity, mgp_memory *memory, mgp_list **re
result);
}
mgp_error mgp_list_copy(mgp_list *list, mgp_memory *memory, mgp_list **result) {
return WrapExceptions([list, memory] { return NewRawMgpObject<mgp_list>(memory, *list); }, result);
}
void mgp_list_destroy(mgp_list *list) { DeleteRawMgpObject(list); }
namespace {
@@ -964,6 +1026,10 @@ mgp_error mgp_map_make_empty(mgp_memory *memory, mgp_map **result) {
return WrapExceptions([&memory] { return NewRawMgpObject<mgp_map>(memory); }, result);
}
mgp_error mgp_map_copy(mgp_map *map, mgp_memory *memory, mgp_map **result) {
return WrapExceptions([map, memory] { return NewRawMgpObject<mgp_map>(memory, *map); }, result);
}
void mgp_map_destroy(mgp_map *map) { DeleteRawMgpObject(map); }
mgp_error mgp_map_insert(mgp_map *map, const char *key, mgp_value *value) {
@@ -1065,6 +1131,7 @@ mgp_error mgp_path_expand(mgp_path *path, mgp_edge *edge) {
// the given edge.
auto *src_vertex = &path->vertices.back();
mgp_vertex *dst_vertex{nullptr};
if (edge->to == *src_vertex) {
dst_vertex = &edge->from;
} else if (edge->from == *src_vertex) {
@@ -1130,7 +1197,7 @@ mgp_error mgp_path_equal(mgp_path *p1, mgp_path *p2, int *result) {
}
const auto *start1 = Call<mgp_vertex *>(mgp_path_vertex_at, p1, 0);
const auto *start2 = Call<mgp_vertex *>(mgp_path_vertex_at, p2, 0);
static_assert(noexcept(start1->impl == start2->impl));
static_assert(noexcept(start1 == start2));
if (*start1 != *start2) {
return 0;
}
@@ -1497,9 +1564,12 @@ mgp_error mgp_properties_iterator_next(mgp_properties_iterator *it, mgp_property
return nullptr;
}
memgraph::utils::OnScopeExit clean_up([it] { it->current = std::nullopt; });
it->current.emplace(memgraph::utils::pmr::string(it->graph->impl->PropertyToName(it->current_it->first),
it->GetMemoryResource()),
mgp_value(it->current_it->second, it->GetMemoryResource()));
auto propToName = std::visit(
[it](auto *impl) {
return memgraph::utils::pmr::string(impl->PropertyToName(it->current_it->first), it->GetMemoryResource());
},
it->graph->impl);
it->current.emplace(propToName, mgp_value(it->current_it->second, it->GetMemoryResource()));
it->property.name = it->current->first.c_str();
it->property.value = &it->current->second;
clean_up.Disable();
@@ -1509,7 +1579,9 @@ mgp_error mgp_properties_iterator_next(mgp_properties_iterator *it, mgp_property
}
mgp_error mgp_vertex_get_id(mgp_vertex *v, mgp_vertex_id *result) {
return WrapExceptions([v] { return mgp_vertex_id{.as_int = v->impl.Gid().AsInt()}; }, result);
return WrapExceptions(
[v] { return mgp_vertex_id{.as_int = std::visit([](auto &impl) { return impl.Gid().AsInt(); }, v->impl)}; },
result);
}
mgp_error mgp_vertex_underlying_graph_is_mutable(mgp_vertex *v, int *result) {
@@ -1578,11 +1650,25 @@ memgraph::storage::PropertyValue ToPropertyValue(const mgp_value &value) {
mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_name, mgp_value *property_value) {
return WrapExceptions([=] {
auto *ctx = v->graph->ctx;
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker &&
!ctx->auth_checker->Has(v->getImpl(), v->graph->view,
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw AuthorizationException{"Insufficient permissions for setting a property on vertex!"};
}
#endif
if (!MgpVertexIsMutable(*v)) {
throw ImmutableObjectException{"Cannot set a property on an immutable vertex!"};
}
const auto prop_key = v->graph->impl->NameToProperty(property_name);
const auto result = v->impl.SetProperty(prop_key, ToPropertyValue(*property_value));
const auto prop_key =
std::visit([property_name](auto *impl) { return impl->NameToProperty(property_name); }, v->graph->impl);
const auto result = std::visit(
[prop_key, property_value](auto &impl) { return impl.SetProperty(prop_key, ToPropertyValue(*property_value)); },
v->impl);
if (result.HasError()) {
switch (result.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@@ -1597,8 +1683,6 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam
}
}
auto &ctx = v->graph->ctx;
ctx->execution_stats[memgraph::query::ExecutionStats::Key::UPDATED_PROPERTIES] += 1;
auto *trigger_ctx_collector = ctx->trigger_context_collector;
@@ -1608,21 +1692,33 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam
}
const auto old_value = memgraph::query::TypedValue(*result);
if (property_value->type == mgp_value_type::MGP_VALUE_TYPE_NULL) {
trigger_ctx_collector->RegisterRemovedObjectProperty(v->impl, prop_key, old_value);
trigger_ctx_collector->RegisterRemovedObjectProperty(v->getImpl(), 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);
trigger_ctx_collector->RegisterSetObjectProperty(v->getImpl(), prop_key, old_value, new_value);
});
}
mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) {
return WrapExceptions([=] {
auto *ctx = v->graph->ctx;
const auto label_id = std::visit([label](auto *impl) { return impl->NameToLabel(label.name); }, v->graph->impl);
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker &&
!(ctx->auth_checker->Has(v->getImpl(), v->graph->view,
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE) &&
ctx->auth_checker->Has({label_id}, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE))) {
throw AuthorizationException{"Insufficient permissions for adding a label to vertex!"};
}
#endif
if (!MgpVertexIsMutable(*v)) {
throw ImmutableObjectException{"Cannot add a label to an immutable vertex!"};
}
const auto label_id = v->graph->impl->NameToLabel(label.name);
const auto result = v->impl.AddLabel(label_id);
const auto result = std::visit([label_id](auto &impl) { return impl.AddLabel(label_id); }, v->impl);
if (result.HasError()) {
switch (result.GetError()) {
@@ -1638,23 +1734,33 @@ mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) {
}
}
auto &ctx = v->graph->ctx;
if (ctx) {
ctx->execution_stats[memgraph::query::ExecutionStats::Key::CREATED_LABELS] += 1;
ctx->execution_stats[memgraph::query::ExecutionStats::Key::CREATED_LABELS] += 1;
if (ctx->trigger_context_collector) {
ctx->trigger_context_collector->RegisterSetVertexLabel(v->impl, label_id);
if (ctx->trigger_context_collector) {
ctx->trigger_context_collector->RegisterSetVertexLabel(v->getImpl(), label_id);
}
}
});
}
mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) {
return WrapExceptions([=] {
auto *ctx = v->graph->ctx;
const auto label_id = std::visit([&label](auto *impl) { return impl->NameToLabel(label.name); }, v->graph->impl);
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker &&
!(ctx->auth_checker->Has(v->getImpl(), v->graph->view,
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE) &&
ctx->auth_checker->Has({label_id}, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE))) {
throw AuthorizationException{"Insufficient permissions for removing a label from vertex!"};
}
#endif
if (!MgpVertexIsMutable(*v)) {
throw ImmutableObjectException{"Cannot remove a label from an immutable vertex!"};
}
const auto label_id = v->graph->impl->NameToLabel(label.name);
const auto result = v->impl.RemoveLabel(label_id);
const auto result = std::visit([label_id](auto &impl) { return impl.RemoveLabel(label_id); }, v->impl);
if (result.HasError()) {
switch (result.GetError()) {
@@ -1670,12 +1776,12 @@ mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) {
}
}
auto &ctx = v->graph->ctx;
if (ctx) {
ctx->execution_stats[memgraph::query::ExecutionStats::Key::DELETED_LABELS] += 1;
ctx->execution_stats[memgraph::query::ExecutionStats::Key::DELETED_LABELS] += 1;
if (ctx->trigger_context_collector) {
ctx->trigger_context_collector->RegisterRemovedVertexLabel(v->impl, label_id);
if (ctx->trigger_context_collector) {
ctx->trigger_context_collector->RegisterRemovedVertexLabel(v->getImpl(), label_id);
}
}
});
}
@@ -1696,7 +1802,7 @@ mgp_error mgp_vertex_equal(mgp_vertex *v1, mgp_vertex *v2, int *result) {
mgp_error mgp_vertex_labels_count(mgp_vertex *v, size_t *result) {
return WrapExceptions(
[v]() -> size_t {
auto maybe_labels = v->impl.Labels(v->graph->view);
auto maybe_labels = std::visit([v](const auto &impl) { return impl.Labels(v->graph->view); }, v->impl);
if (maybe_labels.HasError()) {
switch (maybe_labels.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@@ -1718,7 +1824,7 @@ mgp_error mgp_vertex_label_at(mgp_vertex *v, size_t i, mgp_label *result) {
return WrapExceptions(
[v, i]() -> const char * {
// TODO: Maybe it's worth caching this in mgp_vertex.
auto maybe_labels = v->impl.Labels(v->graph->view);
auto maybe_labels = std::visit([v](const auto &impl) { return impl.Labels(v->graph->view); }, v->impl);
if (maybe_labels.HasError()) {
switch (maybe_labels.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@@ -1735,10 +1841,12 @@ mgp_error mgp_vertex_label_at(mgp_vertex *v, size_t i, mgp_label *result) {
throw std::out_of_range("Label cannot be retrieved, because index exceeds the number of labels!");
}
const auto &label = (*maybe_labels)[i];
static_assert(std::is_lvalue_reference_v<decltype(v->graph->impl->LabelToName(label))>,
static_assert(std::is_lvalue_reference_v<
decltype(std::get<memgraph::query::DbAccessor *>(v->graph->impl)->LabelToName(label))>,
"Expected LabelToName to return a pointer or reference, so we "
"don't have to take a copy and manage memory.");
const auto &name = v->graph->impl->LabelToName(label);
const auto &name = std::visit([label](const auto *impl) { return impl->LabelToName(label); }, v->graph->impl);
return name.c_str();
},
&result->name);
@@ -1748,9 +1856,10 @@ mgp_error mgp_vertex_has_label_named(mgp_vertex *v, const char *name, int *resul
return WrapExceptions(
[v, name] {
memgraph::storage::LabelId label;
label = v->graph->impl->NameToLabel(name);
label = std::visit([name](auto *impl) { return impl->NameToLabel(name); }, v->graph->impl);
auto maybe_has_label = v->impl.HasLabel(v->graph->view, label);
auto maybe_has_label =
std::visit([v, label](auto &impl) { return impl.HasLabel(v->graph->view, label); }, v->impl);
if (maybe_has_label.HasError()) {
switch (maybe_has_label.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@@ -1778,8 +1887,9 @@ mgp_error mgp_vertex_has_label(mgp_vertex *v, mgp_label label, int *result) {
mgp_error mgp_vertex_get_property(mgp_vertex *v, const char *name, mgp_memory *memory, mgp_value **result) {
return WrapExceptions(
[v, name, memory]() -> mgp_value * {
const auto &key = v->graph->impl->NameToProperty(name);
auto maybe_prop = v->impl.GetProperty(v->graph->view, key);
const auto &key = std::visit([name](auto *impl) { return impl->NameToProperty(name); }, v->graph->impl);
auto maybe_prop = std::visit([v, key](auto &impl) { return impl.GetProperty(v->graph->view, key); }, v->impl);
if (maybe_prop.HasError()) {
switch (maybe_prop.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@@ -1805,7 +1915,7 @@ mgp_error mgp_vertex_iter_properties(mgp_vertex *v, mgp_memory *memory, mgp_prop
// will probably require a different API in storage.
return WrapExceptions(
[v, memory] {
auto maybe_props = v->impl.Properties(v->graph->view);
auto maybe_props = std::visit([v](auto &impl) { return impl.Properties(v->graph->view); }, v->impl);
if (maybe_props.HasError()) {
switch (maybe_props.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@@ -1827,13 +1937,40 @@ mgp_error mgp_vertex_iter_properties(mgp_vertex *v, mgp_memory *memory, mgp_prop
void mgp_edges_iterator_destroy(mgp_edges_iterator *it) { DeleteRawMgpObject(it); }
#ifdef MG_ENTERPRISE
namespace {
void NextPermittedEdge(mgp_edges_iterator &it, const bool for_in) {
if (const auto *ctx = it.source_vertex.graph->ctx; !ctx || !ctx->auth_checker) return;
auto &impl_it = for_in ? it.in_it : it.out_it;
const auto end = for_in ? it.in->end() : it.out->end();
if (impl_it) {
const auto *auth_checker = it.source_vertex.graph->ctx->auth_checker.get();
const auto view = it.source_vertex.graph->view;
while (*impl_it != end) {
if (auth_checker->Has(**impl_it, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)) {
const auto &check_vertex =
it.source_vertex.getImpl() == (*impl_it)->From() ? (*impl_it)->To() : (*impl_it)->From();
if (auth_checker->Has(check_vertex, view, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)) {
break;
}
}
++*impl_it;
}
}
};
} // namespace
#endif
mgp_error mgp_vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges_iterator **result) {
return WrapExceptions(
[v, memory] {
auto it = NewMgpObject<mgp_edges_iterator>(memory, *v);
MG_ASSERT(it != nullptr);
auto maybe_edges = v->impl.InEdges(v->graph->view);
auto maybe_edges = std::visit([v](auto &impl) { return impl.InEdges(v->graph->view); }, v->impl);
if (maybe_edges.HasError()) {
switch (maybe_edges.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@@ -1850,8 +1987,26 @@ mgp_error mgp_vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges_
}
it->in.emplace(std::move(*maybe_edges));
it->in_it.emplace(it->in->begin());
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
NextPermittedEdge(*it, true);
}
#endif
if (*it->in_it != it->in->end()) {
it->current_e.emplace(**it->in_it, v->graph, it->GetMemoryResource());
std::visit(memgraph::utils::Overloaded{
[&](memgraph::query::DbAccessor *) {
it->current_e.emplace(**it->in_it, (**it->in_it).From(), (**it->in_it).To(), v->graph,
it->GetMemoryResource());
},
[&](memgraph::query::SubgraphDbAccessor *impl) {
it->current_e.emplace(
**it->in_it,
memgraph::query::SubgraphVertexAccessor((**it->in_it).From(), impl->getGraph()),
memgraph::query::SubgraphVertexAccessor((**it->in_it).To(), impl->getGraph()), v->graph,
it->GetMemoryResource());
}},
v->graph->impl);
}
return it.release();
@@ -1864,8 +2019,8 @@ mgp_error mgp_vertex_iter_out_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges
[v, memory] {
auto it = NewMgpObject<mgp_edges_iterator>(memory, *v);
MG_ASSERT(it != nullptr);
auto maybe_edges = std::visit([v](auto &impl) { return impl.OutEdges(v->graph->view); }, v->impl);
auto maybe_edges = v->impl.OutEdges(v->graph->view);
if (maybe_edges.HasError()) {
switch (maybe_edges.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@@ -1880,10 +2035,30 @@ mgp_error mgp_vertex_iter_out_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges
LOG_FATAL("Unexpected error when getting the outbound edges of a vertex.");
}
}
it->out.emplace(std::move(*maybe_edges));
it->out_it.emplace(it->out->begin());
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
NextPermittedEdge(*it, false);
}
#endif
if (*it->out_it != it->out->end()) {
it->current_e.emplace(**it->out_it, v->graph, it->GetMemoryResource());
std::visit(memgraph::utils::Overloaded{
[&](memgraph::query::DbAccessor *) {
it->current_e.emplace(**it->out_it, (**it->out_it).From(), (**it->out_it).To(), v->graph,
it->GetMemoryResource());
},
[&](memgraph::query::SubgraphDbAccessor *impl) {
it->current_e.emplace(
**it->out_it,
memgraph::query::SubgraphVertexAccessor((**it->out_it).From(), impl->getGraph()),
memgraph::query::SubgraphVertexAccessor((**it->out_it).To(), impl->getGraph()), v->graph,
it->GetMemoryResource());
}},
v->graph->impl);
}
return it.release();
@@ -1910,24 +2085,50 @@ mgp_error mgp_edges_iterator_next(mgp_edges_iterator *it, mgp_edge **result) {
return WrapExceptions(
[it] {
MG_ASSERT(it->in || it->out);
auto next = [&](auto *impl_it, const auto &end) -> mgp_edge * {
auto next = [it](const bool for_in) -> mgp_edge * {
auto &impl_it = for_in ? it->in_it : it->out_it;
const auto end = for_in ? it->in->end() : it->out->end();
if (*impl_it == end) {
MG_ASSERT(!it->current_e,
"Iteration is already done, so it->current_e "
"should have been set to std::nullopt");
return nullptr;
}
if (++(*impl_it) == end) {
++*impl_it;
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
NextPermittedEdge(*it, for_in);
}
#endif
if (*impl_it == end) {
it->current_e = std::nullopt;
return nullptr;
}
it->current_e.emplace(**impl_it, it->source_vertex.graph, it->GetMemoryResource());
std::visit(memgraph::utils::Overloaded{
[&](memgraph::query::DbAccessor *) {
it->current_e.emplace(**impl_it, (**impl_it).From(), (**impl_it).To(),
it->source_vertex.graph, it->GetMemoryResource());
},
[&](memgraph::query::SubgraphDbAccessor *impl) {
it->current_e.emplace(
**impl_it, memgraph::query::SubgraphVertexAccessor((**impl_it).From(), impl->getGraph()),
memgraph::query::SubgraphVertexAccessor((**impl_it).To(), impl->getGraph()),
it->source_vertex.graph, it->GetMemoryResource());
}},
it->source_vertex.graph->impl);
return &*it->current_e;
};
if (it->in_it) {
return next(&*it->in_it, it->in->end());
auto *result = next(true);
if (result != nullptr) {
return result;
}
}
return next(&*it->out_it, it->out->end());
return next(false);
},
result);
}
@@ -1956,10 +2157,8 @@ mgp_error mgp_edge_equal(mgp_edge *e1, mgp_edge *e2, int *result) {
mgp_error mgp_edge_get_type(mgp_edge *e, mgp_edge_type *result) {
return WrapExceptions(
[e] {
const auto &name = e->from.graph->impl->EdgeTypeToName(e->impl.EdgeType());
static_assert(std::is_lvalue_reference_v<decltype(e->from.graph->impl->EdgeTypeToName(e->impl.EdgeType()))>,
"Expected EdgeTypeToName to return a pointer or reference, so we "
"don't have to take a copy and manage memory.");
const auto &name =
std::visit([e](const auto *impl) { return impl->EdgeTypeToName(e->impl.EdgeType()); }, e->from.graph->impl);
return name.c_str();
},
&result->name);
@@ -1978,7 +2177,7 @@ mgp_error mgp_edge_get_to(mgp_edge *e, mgp_vertex **result) {
mgp_error mgp_edge_get_property(mgp_edge *e, const char *name, mgp_memory *memory, mgp_value **result) {
return WrapExceptions(
[e, name, memory] {
const auto &key = e->from.graph->impl->NameToProperty(name);
const auto &key = std::visit([name](auto *impl) { return impl->NameToProperty(name); }, e->from.graph->impl);
auto view = e->from.graph->view;
auto maybe_prop = e->impl.GetProperty(view, key);
if (maybe_prop.HasError()) {
@@ -2001,10 +2200,20 @@ mgp_error mgp_edge_get_property(mgp_edge *e, const char *name, mgp_memory *memor
mgp_error mgp_edge_set_property(struct mgp_edge *e, const char *property_name, mgp_value *property_value) {
return WrapExceptions([=] {
auto *ctx = e->from.graph->ctx;
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker &&
!ctx->auth_checker->Has(e->impl, memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw AuthorizationException{"Insufficient permissions for setting a property on edge!"};
}
#endif
if (!MgpEdgeIsMutable(*e)) {
throw ImmutableObjectException{"Cannot set a property on an immutable edge!"};
}
const auto prop_key = e->from.graph->impl->NameToProperty(property_name);
const auto prop_key =
std::visit([property_name](auto *impl) { return impl->NameToProperty(property_name); }, e->from.graph->impl);
const auto result = e->impl.SetProperty(prop_key, ToPropertyValue(*property_value));
if (result.HasError()) {
@@ -2022,8 +2231,6 @@ mgp_error mgp_edge_set_property(struct mgp_edge *e, const char *property_name, m
}
}
auto &ctx = e->from.graph->ctx;
ctx->execution_stats[memgraph::query::ExecutionStats::Key::UPDATED_PROPERTIES] += 1;
auto *trigger_ctx_collector = e->from.graph->ctx->trigger_context_collector;
@@ -2056,7 +2263,8 @@ mgp_error mgp_edge_iter_properties(mgp_edge *e, mgp_memory *memory, mgp_properti
throw DeletedObjectException{"Cannot get the properties of a deleted edge!"};
case memgraph::storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL(
"Query modules shouldn't have access to nonexistent objects when getting the properties of an edge.");
"Query modules shouldn't have access to nonexistent objects when getting the properties of an "
"edge.");
case memgraph::storage::Error::PROPERTIES_DISABLED:
case memgraph::storage::Error::VERTEX_HAS_EDGES:
case memgraph::storage::Error::SERIALIZATION_ERROR:
@@ -2071,9 +2279,22 @@ mgp_error mgp_edge_iter_properties(mgp_edge *e, mgp_memory *memory, mgp_properti
mgp_error mgp_graph_get_vertex_by_id(mgp_graph *graph, mgp_vertex_id id, mgp_memory *memory, mgp_vertex **result) {
return WrapExceptions(
[graph, id, memory]() -> mgp_vertex * {
auto maybe_vertex = graph->impl->FindVertex(memgraph::storage::Gid::FromInt(id.as_int), graph->view);
std::optional<memgraph::query::VertexAccessor> maybe_vertex = std::visit(
[graph, id](auto *impl) {
return impl->FindVertex(memgraph::storage::Gid::FromInt(id.as_int), graph->view);
},
graph->impl);
if (maybe_vertex) {
return NewRawMgpObject<mgp_vertex>(memory, *maybe_vertex, graph);
return std::visit(memgraph::utils::Overloaded{
[memory, graph, maybe_vertex](memgraph::query::DbAccessor *) {
return NewRawMgpObject<mgp_vertex>(memory, *maybe_vertex, graph);
},
[memory, graph, maybe_vertex](memgraph::query::SubgraphDbAccessor *impl) {
return NewRawMgpObject<mgp_vertex>(
memory, memgraph::query::SubgraphVertexAccessor(*maybe_vertex, impl->getGraph()),
graph);
}},
graph->impl);
}
return nullptr;
},
@@ -2087,29 +2308,59 @@ mgp_error mgp_graph_is_mutable(mgp_graph *graph, int *result) {
mgp_error mgp_graph_create_vertex(struct mgp_graph *graph, mgp_memory *memory, mgp_vertex **result) {
return WrapExceptions(
[=] {
[=]() -> mgp_vertex * {
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && graph->ctx &&
graph->ctx->auth_checker &&
!graph->ctx->auth_checker->HasGlobalPrivilegeOnVertices(
memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
throw AuthorizationException{"Insufficient permissions for creating vertices!"};
}
#endif
if (!MgpGraphIsMutable(*graph)) {
throw ImmutableObjectException{"Cannot create a vertex in an immutable graph!"};
}
auto vertex = graph->impl->InsertVertex();
auto *vertex = std::visit(
[=](auto *impl) { return NewRawMgpObject<mgp_vertex>(memory, impl->InsertVertex(), graph); }, graph->impl);
auto &ctx = graph->ctx;
ctx->execution_stats[memgraph::query::ExecutionStats::Key::CREATED_NODES] += 1;
if (ctx->trigger_context_collector) {
ctx->trigger_context_collector->RegisterCreatedObject(vertex);
ctx->trigger_context_collector->RegisterCreatedObject(vertex->getImpl());
}
return NewRawMgpObject<mgp_vertex>(memory, vertex, graph);
return vertex;
},
result);
}
mgp_error mgp_graph_delete_vertex(struct mgp_graph *graph, mgp_vertex *vertex) {
return WrapExceptions([=] {
auto *ctx = graph->ctx;
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker &&
!ctx->auth_checker->Has(vertex->getImpl(), graph->view,
memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
throw AuthorizationException{"Insufficient permissions for deleting a vertex!"};
}
#endif
if (!MgpGraphIsMutable(*graph)) {
throw ImmutableObjectException{"Cannot remove a vertex from an immutable graph!"};
}
const auto result = graph->impl->RemoveVertex(&vertex->impl);
const auto result =
std::visit(memgraph::utils::Overloaded{
[&](memgraph::query::DbAccessor *impl) {
return impl->RemoveVertex(&std::get<memgraph::query::VertexAccessor>(vertex->impl));
},
[&](memgraph::query::SubgraphDbAccessor *impl) {
return impl->RemoveVertex(&(std::get<memgraph::query::SubgraphVertexAccessor>(vertex->impl)));
}},
graph->impl);
if (result.HasError()) {
switch (result.GetError()) {
@@ -2129,8 +2380,6 @@ mgp_error mgp_graph_delete_vertex(struct mgp_graph *graph, mgp_vertex *vertex) {
return;
}
auto &ctx = graph->ctx;
ctx->execution_stats[memgraph::query::ExecutionStats::Key::DELETED_NODES] += 1;
if (ctx->trigger_context_collector) {
@@ -2141,10 +2390,27 @@ mgp_error mgp_graph_delete_vertex(struct mgp_graph *graph, mgp_vertex *vertex) {
mgp_error mgp_graph_detach_delete_vertex(struct mgp_graph *graph, mgp_vertex *vertex) {
return WrapExceptions([=] {
auto *ctx = graph->ctx;
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker &&
!ctx->auth_checker->Has(vertex->getImpl(), graph->view,
memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
throw AuthorizationException{"Insufficient permissions for deleting a vertex!"};
}
#endif
if (!MgpGraphIsMutable(*graph)) {
throw ImmutableObjectException{"Cannot remove a vertex from an immutable graph!"};
}
const auto result = graph->impl->DetachRemoveVertex(&vertex->impl);
const auto result = std::visit(
memgraph::utils::Overloaded{
[vertex](memgraph::query::DbAccessor *impl) {
return impl->DetachRemoveVertex(&std::get<memgraph::query::VertexAccessor>(vertex->impl));
},
[vertex](memgraph::query::SubgraphDbAccessor *impl) {
return impl->DetachRemoveVertex(&std::get<memgraph::query::SubgraphVertexAccessor>(vertex->impl));
}},
graph->impl);
if (result.HasError()) {
switch (result.GetError()) {
@@ -2163,8 +2429,6 @@ mgp_error mgp_graph_detach_delete_vertex(struct mgp_graph *graph, mgp_vertex *ve
return;
}
auto &ctx = graph->ctx;
ctx->execution_stats[memgraph::query::ExecutionStats::Key::DELETED_NODES] += 1;
ctx->execution_stats[memgraph::query::ExecutionStats::Key::DELETED_EDGES] +=
static_cast<int64_t>((*result)->second.size());
@@ -2187,12 +2451,33 @@ mgp_error mgp_graph_detach_delete_vertex(struct mgp_graph *graph, mgp_vertex *ve
mgp_error mgp_graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *to, mgp_edge_type type,
mgp_memory *memory, mgp_edge **result) {
return WrapExceptions(
[=] {
[=]() -> mgp_edge * {
auto *ctx = graph->ctx;
#ifdef MG_ENTERPRISE
const auto edge_id =
std::visit([type](auto *impl) { return impl->NameToEdgeType(type.name); }, from->graph->impl);
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker &&
!ctx->auth_checker->Has(edge_id, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
throw AuthorizationException{"Insufficient permissions for creating edges!"};
}
#endif
if (!MgpGraphIsMutable(*graph)) {
throw ImmutableObjectException{"Cannot create an edge in an immutable graph!"};
}
auto edge =
std::visit(memgraph::utils::Overloaded{
[from, to, type](memgraph::query::DbAccessor *impl) {
return impl->InsertEdge(&std::get<memgraph::query::VertexAccessor>(from->impl),
&std::get<memgraph::query::VertexAccessor>(to->impl),
impl->NameToEdgeType(type.name));
},
[from, to, type](memgraph::query::SubgraphDbAccessor *impl) {
return impl->InsertEdge(&std::get<memgraph::query::SubgraphVertexAccessor>(from->impl),
&std::get<memgraph::query::SubgraphVertexAccessor>(to->impl),
impl->NameToEdgeType(type.name));
}},
graph->impl);
auto edge = graph->impl->InsertEdge(&from->impl, &to->impl, from->graph->impl->NameToEdgeType(type.name));
if (edge.HasError()) {
switch (edge.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@@ -2206,25 +2491,42 @@ mgp_error mgp_graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *
throw SerializationException{"Cannot serialize creating an edge."};
}
}
auto &ctx = graph->ctx;
ctx->execution_stats[memgraph::query::ExecutionStats::Key::CREATED_EDGES] += 1;
if (ctx->trigger_context_collector) {
ctx->trigger_context_collector->RegisterCreatedObject(*edge);
}
return NewRawMgpObject<mgp_edge>(memory, edge.GetValue(), from->graph);
return std::visit(
memgraph::utils::Overloaded{
[memory, edge, from](memgraph::query::DbAccessor *) {
return NewRawMgpObject<mgp_edge>(memory->impl, edge.GetValue(), from->graph);
},
[memory, edge, from](memgraph::query::SubgraphDbAccessor *db_impl) {
const auto &v_from =
memgraph::query::SubgraphVertexAccessor(edge.GetValue().From(), db_impl->getGraph());
const auto &v_to = memgraph::query::SubgraphVertexAccessor(edge.GetValue().To(), db_impl->getGraph());
return NewRawMgpObject<mgp_edge>(memory->impl, edge.GetValue(), v_from, v_to, from->graph);
}},
graph->impl);
},
result);
}
mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, mgp_edge *edge) {
return WrapExceptions([=] {
auto *ctx = graph->ctx;
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker &&
!ctx->auth_checker->Has(edge->impl, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
throw AuthorizationException{"Insufficient permissions for deleting an edge!"};
}
#endif
if (!MgpGraphIsMutable(*graph)) {
throw ImmutableObjectException{"Cannot remove an edge from an immutable graph!"};
}
const auto result = graph->impl->RemoveEdge(&edge->impl);
const auto result = std::visit([edge](auto *impl) { return impl->RemoveEdge(&edge->impl); }, graph->impl);
if (result.HasError()) {
switch (result.GetError()) {
case memgraph::storage::Error::NONEXISTENT_OBJECT:
@@ -2241,7 +2543,6 @@ mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, mgp_edge *edge) {
if (!*result) {
return;
}
auto &ctx = graph->ctx;
ctx->execution_stats[memgraph::query::ExecutionStats::Key::DELETED_EDGES] += 1;
if (ctx->trigger_context_collector) {
@@ -2250,6 +2551,50 @@ mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, mgp_edge *edge) {
});
}
#ifdef MG_ENTERPRISE
namespace {
void NextPermitted(mgp_vertices_iterator &it) {
const auto *ctx = it.graph->ctx;
if (!ctx || !ctx->auth_checker) {
return;
}
while (it.current_it != it.vertices.end()) {
if (ctx->auth_checker->Has(*it.current_it, it.graph->view,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ)) {
break;
}
++it.current_it;
}
};
} // namespace
#endif
/// @throw anything VerticesIterable may throw
mgp_vertices_iterator::mgp_vertices_iterator(mgp_graph *graph, memgraph::utils::MemoryResource *memory)
: memory(memory),
graph(graph),
vertices(std::visit([graph](auto *impl) { return impl->Vertices(graph->view); }, graph->impl)),
current_it(vertices.begin()) {
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
NextPermitted(*this);
}
#endif
if (current_it != vertices.end()) {
std::visit(
memgraph::utils::Overloaded{
[this, graph, memory](memgraph::query::DbAccessor *) { current_v.emplace(*current_it, graph, memory); },
[this, graph, memory](memgraph::query::SubgraphDbAccessor *impl) {
current_v.emplace(memgraph::query::SubgraphVertexAccessor(*current_it, impl->getGraph()), graph, memory);
}},
graph->impl);
}
}
void mgp_vertices_iterator_destroy(mgp_vertices_iterator *it) { DeleteRawMgpObject(it); }
mgp_error mgp_graph_iter_vertices(mgp_graph *graph, mgp_memory *memory, mgp_vertices_iterator **result) {
@@ -2280,12 +2625,30 @@ mgp_error mgp_vertices_iterator_next(mgp_vertices_iterator *it, mgp_vertex **res
"should have been set to std::nullopt");
return nullptr;
}
if (++it->current_it == it->vertices.end()) {
++it->current_it;
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
NextPermitted(*it);
}
#endif
if (it->current_it == it->vertices.end()) {
it->current_v = std::nullopt;
return nullptr;
}
memgraph::utils::OnScopeExit clean_up([it] { it->current_v = std::nullopt; });
it->current_v.emplace(*it->current_it, it->graph, it->GetMemoryResource());
std::visit(memgraph::utils::Overloaded{[it](memgraph::query::DbAccessor *) {
it->current_v.emplace(*it->current_it, it->graph,
it->GetMemoryResource());
},
[it](memgraph::query::SubgraphDbAccessor *impl) {
it->current_v.emplace(memgraph::query::SubgraphVertexAccessor(
*it->current_it, impl->getGraph()),
it->graph, it->GetMemoryResource());
}},
it->graph->impl);
clean_up.Disable();
return &*it->current_v;
},
@@ -2568,6 +2931,7 @@ std::ostream &PrintValue(const TypedValue &value, std::ostream *stream) {
case TypedValue::Type::Vertex:
case TypedValue::Type::Edge:
case TypedValue::Type::Path:
case TypedValue::Type::Graph:
LOG_FATAL("value must not be a graph element");
}
}
@@ -2796,3 +3160,29 @@ mgp_error mgp_module_add_function(mgp_module *module, const char *name, mgp_func
},
result);
}
mgp_error mgp_log(const mgp_log_level log_level, const char *output) {
return WrapExceptions([=] {
switch (log_level) {
case mgp_log_level::MGP_LOG_LEVEL_TRACE:
spdlog::trace(output);
return;
case mgp_log_level::MGP_LOG_LEVEL_DEBUG:
spdlog::debug(output);
return;
case mgp_log_level::MGP_LOG_LEVEL_INFO:
spdlog::info(output);
return;
case mgp_log_level::MGP_LOG_LEVEL_WARN:
spdlog::warn(output);
return;
case mgp_log_level::MGP_LOG_LEVEL_ERROR:
spdlog::error(output);
return;
case mgp_log_level::MGP_LOG_LEVEL_CRITICAL:
spdlog::critical(output);
return;
}
throw std::invalid_argument{fmt::format("Invalid log level: {}", log_level)};
});
}

View File

@@ -32,6 +32,7 @@
#include "utils/pmr/string.hpp"
#include "utils/pmr/vector.hpp"
#include "utils/temporal.hpp"
#include "utils/variant_helpers.hpp"
/// Wraps memory resource used in custom procedures.
///
/// This should have been `using mgp_memory = memgraph::utils::MemoryResource`, but that's
@@ -442,6 +443,10 @@ struct mgp_vertex {
mgp_vertex(memgraph::query::VertexAccessor v, mgp_graph *graph, memgraph::utils::MemoryResource *memory) noexcept
: memory(memory), impl(v), graph(graph) {}
mgp_vertex(memgraph::query::SubgraphVertexAccessor v, mgp_graph *graph,
memgraph::utils::MemoryResource *memory) noexcept
: memory(memory), impl(v), graph(graph) {}
mgp_vertex(const mgp_vertex &other, memgraph::utils::MemoryResource *memory) noexcept
: memory(memory), impl(other.impl), graph(other.graph) {}
@@ -450,13 +455,21 @@ struct mgp_vertex {
mgp_vertex(mgp_vertex &&other) noexcept : memory(other.memory), impl(other.impl), graph(other.graph) {}
memgraph::query::VertexAccessor getImpl() const {
return std::visit(
memgraph::utils::Overloaded{[](memgraph::query::VertexAccessor impl) { return impl; },
[](memgraph::query::SubgraphVertexAccessor impl) { return impl.impl_; }},
this->impl);
}
/// Copy construction without memgraph::utils::MemoryResource is not allowed.
mgp_vertex(const mgp_vertex &) = delete;
mgp_vertex &operator=(const mgp_vertex &) = delete;
mgp_vertex &operator=(mgp_vertex &&) = delete;
bool operator==(const mgp_vertex &other) const noexcept { return this->impl == other.impl; }
bool operator==(const mgp_vertex &other) const noexcept { return other.getImpl() == this->getImpl(); }
bool operator!=(const mgp_vertex &other) const noexcept { return !(*this == other); };
~mgp_vertex() = default;
@@ -464,7 +477,7 @@ struct mgp_vertex {
memgraph::utils::MemoryResource *GetMemoryResource() const noexcept { return memory; }
memgraph::utils::MemoryResource *memory;
memgraph::query::VertexAccessor impl;
std::variant<memgraph::query::VertexAccessor, memgraph::query::SubgraphVertexAccessor> impl;
mgp_graph *graph;
};
@@ -484,6 +497,16 @@ struct mgp_edge {
memgraph::utils::MemoryResource *memory) noexcept
: memory(memory), impl(impl), from(impl.From(), graph, memory), to(impl.To(), graph, memory) {}
mgp_edge(const memgraph::query::EdgeAccessor &impl, const memgraph::query::VertexAccessor &from_v,
const memgraph::query::VertexAccessor &to_v, mgp_graph *graph,
memgraph::utils::MemoryResource *memory) noexcept
: memory(memory), impl(impl), from(from_v, graph, memory), to(to_v, graph, memory) {}
mgp_edge(const memgraph::query::EdgeAccessor &impl, const memgraph::query::SubgraphVertexAccessor &from_v,
const memgraph::query::SubgraphVertexAccessor &to_v, mgp_graph *graph,
memgraph::utils::MemoryResource *memory) noexcept
: memory(memory), impl(impl), from(from_v, graph, memory), to(to_v, graph, memory) {}
mgp_edge(const mgp_edge &other, memgraph::utils::MemoryResource *memory) noexcept
: memory(memory), impl(other.impl), from(other.from, memory), to(other.to, memory) {}
@@ -541,6 +564,32 @@ struct mgp_path {
memgraph::utils::pmr::vector<mgp_edge> edges;
};
struct mgp_graph {
std::variant<memgraph::query::DbAccessor *, memgraph::query::SubgraphDbAccessor *> impl;
memgraph::storage::View view;
// TODO: Merge `mgp_graph` and `mgp_memory` into a single `mgp_context`. The
// `ctx` field is out of place here.
memgraph::query::ExecutionContext *ctx;
static mgp_graph WritableGraph(memgraph::query::DbAccessor &acc, memgraph::storage::View view,
memgraph::query::ExecutionContext &ctx) {
return mgp_graph{&acc, view, &ctx};
}
static mgp_graph NonWritableGraph(memgraph::query::DbAccessor &acc, memgraph::storage::View view) {
return mgp_graph{&acc, view, nullptr};
}
static mgp_graph WritableGraph(memgraph::query::SubgraphDbAccessor &acc, memgraph::storage::View view,
memgraph::query::ExecutionContext &ctx) {
return mgp_graph{&acc, view, &ctx};
}
static mgp_graph NonWritableGraph(memgraph::query::SubgraphDbAccessor &acc, memgraph::storage::View view) {
return mgp_graph{&acc, view, nullptr};
}
};
struct mgp_result_record {
/// Result record signature as defined for mgp_proc.
const memgraph::utils::pmr::map<memgraph::utils::pmr::string,
@@ -570,23 +619,6 @@ struct mgp_func_result {
std::optional<memgraph::utils::pmr::string> error_msg;
};
struct mgp_graph {
memgraph::query::DbAccessor *impl;
memgraph::storage::View view;
// TODO: Merge `mgp_graph` and `mgp_memory` into a single `mgp_context`. The
// `ctx` field is out of place here.
memgraph::query::ExecutionContext *ctx;
static mgp_graph WritableGraph(memgraph::query::DbAccessor &acc, memgraph::storage::View view,
memgraph::query::ExecutionContext &ctx) {
return mgp_graph{&acc, view, &ctx};
}
static mgp_graph NonWritableGraph(memgraph::query::DbAccessor &acc, memgraph::storage::View view) {
return mgp_graph{&acc, view, nullptr};
}
};
// Prevents user to use ExecutionContext in writable callables
struct mgp_func_context {
memgraph::query::DbAccessor *impl;
@@ -615,8 +647,13 @@ struct mgp_properties_iterator {
mgp_properties_iterator(mgp_graph *graph, decltype(pvs) pvs, memgraph::utils::MemoryResource *memory)
: memory(memory), graph(graph), pvs(std::move(pvs)), current_it(this->pvs.begin()) {
if (current_it != this->pvs.end()) {
current.emplace(memgraph::utils::pmr::string(graph->impl->PropertyToName(current_it->first), memory),
mgp_value(current_it->second, memory));
auto value = std::visit(
[this, memory](const auto *impl) {
return memgraph::utils::pmr::string(impl->PropertyToName(current_it->first), memory);
},
graph->impl);
current.emplace(value, mgp_value(current_it->second, memory));
property.name = current->first.c_str();
property.value = &current->second;
}
@@ -635,7 +672,6 @@ struct mgp_properties_iterator {
struct mgp_edges_iterator {
using allocator_type = memgraph::utils::Allocator<mgp_edges_iterator>;
// Hopefully mgp_vertex copy constructor remains noexcept, so that we can
// have everything noexcept here.
static_assert(std::is_nothrow_constructible_v<mgp_vertex, const mgp_vertex &, memgraph::utils::MemoryResource *>);
@@ -662,9 +698,14 @@ struct mgp_edges_iterator {
memgraph::utils::MemoryResource *memory;
mgp_vertex source_vertex;
std::optional<std::remove_reference_t<decltype(*source_vertex.impl.InEdges(source_vertex.graph->view))>> in;
std::optional<std::remove_reference_t<
decltype(*std::get<memgraph::query::VertexAccessor>(source_vertex.impl).InEdges(source_vertex.graph->view))>>
in;
std::optional<decltype(in->begin())> in_it;
std::optional<std::remove_reference_t<decltype(*source_vertex.impl.OutEdges(source_vertex.graph->view))>> out;
std::optional<std::remove_reference_t<
decltype(*std::get<memgraph::query::VertexAccessor>(source_vertex.impl).OutEdges(source_vertex.graph->view))>>
out;
std::optional<decltype(out->begin())> out_it;
std::optional<mgp_edge> current_e;
};
@@ -673,18 +714,13 @@ struct mgp_vertices_iterator {
using allocator_type = memgraph::utils::Allocator<mgp_vertices_iterator>;
/// @throw anything VerticesIterable may throw
mgp_vertices_iterator(mgp_graph *graph, memgraph::utils::MemoryResource *memory)
: memory(memory), graph(graph), vertices(graph->impl->Vertices(graph->view)), current_it(vertices.begin()) {
if (current_it != vertices.end()) {
current_v.emplace(*current_it, graph, memory);
}
}
mgp_vertices_iterator(mgp_graph *graph, memgraph::utils::MemoryResource *memory);
memgraph::utils::MemoryResource *GetMemoryResource() const { return memory; }
memgraph::utils::MemoryResource *memory;
mgp_graph *graph;
decltype(graph->impl->Vertices(graph->view)) vertices;
memgraph::query::VerticesIterable vertices;
decltype(vertices.begin()) current_it;
std::optional<mgp_vertex> current_v;
};

View File

@@ -51,6 +51,7 @@ PyObject *gMgpKeyAlreadyExistsError{nullptr}; // NOLINT(cppcoreguidelines-avo
PyObject *gMgpImmutableObjectError{nullptr}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
PyObject *gMgpValueConversionError{nullptr}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
PyObject *gMgpSerializationError{nullptr}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
PyObject *gMgpAuthorizationError{nullptr}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
// Returns true if an exception is raised
bool RaiseExceptionFromErrorCode(const mgp_error error) {
@@ -101,6 +102,10 @@ bool RaiseExceptionFromErrorCode(const mgp_error error) {
PyErr_SetString(gMgpSerializationError, "Operation cannot be serialized.");
return true;
}
case mgp_error::MGP_ERROR_AUTHORIZATION_ERROR: {
PyErr_SetString(gMgpAuthorizationError, "Authorization Error. Permission Denied.");
return true;
}
}
}
@@ -2052,6 +2057,81 @@ PyObject *PyPathMakeWithStart(PyTypeObject *type, PyObject *vertex) {
return py_path;
}
// clang-format off
struct PyLogger {
PyObject_HEAD
};
// clang-format on
PyObject *PyLoggerLog(PyLogger *self, PyObject *args, const mgp_log_level level) {
MG_ASSERT(self);
const char *out = nullptr;
if (!PyArg_ParseTuple(args, "s", &out)) {
return nullptr;
}
if (RaiseExceptionFromErrorCode(mgp_log(level, out))) {
return nullptr;
}
Py_RETURN_NONE;
}
PyObject *PyLoggerLogInfo(PyLogger *self, PyObject *args) {
return PyLoggerLog(self, args, mgp_log_level::MGP_LOG_LEVEL_INFO);
}
PyObject *PyLoggerLogWarning(PyLogger *self, PyObject *args) {
return PyLoggerLog(self, args, mgp_log_level::MGP_LOG_LEVEL_WARN);
}
PyObject *PyLoggerLogError(PyLogger *self, PyObject *args) {
return PyLoggerLog(self, args, mgp_log_level::MGP_LOG_LEVEL_ERROR);
}
PyObject *PyLoggerLogCritical(PyLogger *self, PyObject *args) {
return PyLoggerLog(self, args, mgp_log_level::MGP_LOG_LEVEL_CRITICAL);
}
PyObject *PyLoggerLogTrace(PyLogger *self, PyObject *args) {
return PyLoggerLog(self, args, mgp_log_level::MGP_LOG_LEVEL_TRACE);
}
PyObject *PyLoggerLogDebug(PyLogger *self, PyObject *args) {
return PyLoggerLog(self, args, mgp_log_level::MGP_LOG_LEVEL_DEBUG);
}
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static PyMethodDef PyLoggerMethods[] = {
{"__reduce__", reinterpret_cast<PyCFunction>(DisallowPickleAndCopy), METH_NOARGS, "__reduce__ is not supported"},
{"info", reinterpret_cast<PyCFunction>(PyLoggerLogInfo), METH_VARARGS,
"Logs a message with level INFO on this logger."},
{"warning", reinterpret_cast<PyCFunction>(PyLoggerLogWarning), METH_VARARGS,
"Logs a message with level WARNNING on this logger."},
{"error", reinterpret_cast<PyCFunction>(PyLoggerLogError), METH_VARARGS,
"Logs a message with level ERROR on this logger."},
{"critical", reinterpret_cast<PyCFunction>(PyLoggerLogCritical), METH_VARARGS,
"Logs a message with level CRITICAL on this logger."},
{"trace", reinterpret_cast<PyCFunction>(PyLoggerLogTrace), METH_VARARGS,
"Logs a message with level TRACE on this logger."},
{"debug", reinterpret_cast<PyCFunction>(PyLoggerLogDebug), METH_VARARGS,
"Logs a message with level DEBUG on this logger."},
{nullptr},
};
// clang-format off
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static PyTypeObject PyLoggerType = {
PyVarObject_HEAD_INIT(nullptr, 0)
.tp_name = "_mgp.Logger",
.tp_basicsize = sizeof(PyLogger),
// NOLINTNEXTLINE(hicpp-signed-bitwise)
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "Logging API.",
.tp_methods = PyLoggerMethods,
};
// clang-format on
struct PyMgpError {
const char *name;
PyObject *&exception;
@@ -2103,6 +2183,7 @@ PyObject *PyInitMgpModule() {
if (!register_type(&PyCypherTypeType, "Type")) return nullptr;
if (!register_type(&PyMessagesType, "Messages")) return nullptr;
if (!register_type(&PyMessageType, "Message")) return nullptr;
if (!register_type(&PyLoggerType, "Logger")) return nullptr;
std::array py_mgp_errors{
PyMgpError{"_mgp.UnknownError", gMgpUnknownError, PyExc_RuntimeError, nullptr},
@@ -2116,6 +2197,7 @@ PyObject *PyInitMgpModule() {
PyMgpError{"_mgp.ImmutableObjectError", gMgpImmutableObjectError, PyExc_RuntimeError, nullptr},
PyMgpError{"_mgp.ValueConversionError", gMgpValueConversionError, PyExc_RuntimeError, nullptr},
PyMgpError{"_mgp.SerializationError", gMgpSerializationError, PyExc_RuntimeError, nullptr},
PyMgpError{"_mgp.AuthorizationError", gMgpAuthorizationError, PyExc_RuntimeError, nullptr},
};
Py_INCREF(Py_None);
@@ -2169,8 +2251,14 @@ auto WithMgpModule(mgp_module *module_def, const TFun &fun) {
"import a new module. Is some other thread also importing Python "
"modules?");
auto *py_query_module = MakePyQueryModule(module_def);
MG_ASSERT(py_query_module);
MG_ASSERT(py_mgp.SetAttr("_MODULE", py_query_module));
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
auto *py_logger = reinterpret_cast<PyObject *>(PyObject_New(PyLogger, &PyLoggerType));
MG_ASSERT(py_mgp.SetAttr("_LOGGER", py_logger));
auto ret = fun();
auto maybe_exc = py::FetchError();
MG_ASSERT(py_mgp.SetAttr("_MODULE", Py_None));

View File

@@ -22,6 +22,7 @@
#include "storage/v2/temporal.hpp"
#include "utils/exceptions.hpp"
#include "utils/fnv.hpp"
#include "utils/memory.hpp"
namespace memgraph::query {
@@ -214,6 +215,10 @@ TypedValue::TypedValue(const TypedValue &other, utils::MemoryResource *memory) :
case Type::Duration:
new (&duration_v) utils::Duration(other.duration_v);
return;
case Type::Graph:
auto *graph_ptr = utils::Allocator<Graph>(memory_).new_object<Graph>(*other.graph_v);
new (&graph_v) std::unique_ptr<Graph>(graph_ptr);
return;
}
LOG_FATAL("Unsupported TypedValue::Type");
}
@@ -263,6 +268,13 @@ TypedValue::TypedValue(TypedValue &&other, utils::MemoryResource *memory) : memo
case Type::Duration:
new (&duration_v) utils::Duration(other.duration_v);
break;
case Type::Graph:
if (other.GetMemoryResource() == memory_) {
new (&graph_v) std::unique_ptr<Graph>(std::move(other.graph_v));
} else {
auto *graph_ptr = utils::Allocator<Graph>(memory_).new_object<Graph>(std::move(*other.graph_v));
new (&graph_v) std::unique_ptr<Graph>(graph_ptr);
}
}
other.DestroyValue();
}
@@ -332,6 +344,22 @@ DEFINE_VALUE_AND_TYPE_GETTERS(utils::LocalTime, LocalTime, local_time_v)
DEFINE_VALUE_AND_TYPE_GETTERS(utils::LocalDateTime, LocalDateTime, local_date_time_v)
DEFINE_VALUE_AND_TYPE_GETTERS(utils::Duration, Duration, duration_v)
Graph &TypedValue::ValueGraph() {
if (type_ != Type::Graph) {
throw TypedValueException("TypedValue is of type '{}', not '{}'", type_, Type::Graph);
}
return *graph_v;
}
const Graph &TypedValue::ValueGraph() const {
if (type_ != Type::Graph) {
throw TypedValueException("TypedValue is of type '{}', not '{}'", type_, Type::Graph);
}
return *graph_v;
}
bool TypedValue::IsGraph() const { return type_ == Type::Graph; }
#undef DEFINE_VALUE_AND_TYPE_GETTERS
bool TypedValue::IsNull() const { return type_ == Type::Null; }
@@ -387,6 +415,8 @@ std::ostream &operator<<(std::ostream &os, const TypedValue::Type &type) {
return os << "local_date_time";
case TypedValue::Type::Duration:
return os << "duration";
case TypedValue::Type::Graph:
return os << "graph";
}
LOG_FATAL("Unsupported TypedValue::Type");
}
@@ -522,6 +552,11 @@ TypedValue &TypedValue::operator=(const TypedValue &other) {
case TypedValue::Type::Path:
new (&path_v) Path(other.path_v, memory_);
return *this;
case TypedValue::Type::Graph: {
auto *graph_ptr = utils::Allocator<Graph>(memory_).new_object<Graph>(*other.graph_v);
new (&graph_v) std::unique_ptr<Graph>(graph_ptr);
return *this;
}
case Type::Date:
new (&date_v) utils::Date(other.date_v);
return *this;
@@ -593,6 +628,14 @@ TypedValue &TypedValue::operator=(TypedValue &&other) noexcept(false) {
case Type::Duration:
new (&duration_v) utils::Duration(other.duration_v);
break;
case Type::Graph:
if (other.GetMemoryResource() == memory_) {
new (&graph_v) std::unique_ptr<Graph>(std::move(other.graph_v));
} else {
auto *graph_ptr = utils::Allocator<Graph>(memory_).new_object<Graph>(std::move(*other.graph_v));
new (&graph_v) std::unique_ptr<Graph>(graph_ptr);
}
break;
}
other.DestroyValue();
}
@@ -611,28 +654,36 @@ void TypedValue::DestroyValue() {
// we need to call destructors for non primitive types since we used
// placement new
case Type::String:
string_v.~TString();
std::destroy_at(&string_v);
break;
case Type::List:
list_v.~TVector();
std::destroy_at(&list_v);
break;
case Type::Map:
map_v.~TMap();
std::destroy_at(&map_v);
break;
case Type::Vertex:
vertex_v.~VertexAccessor();
std::destroy_at(&vertex_v);
break;
case Type::Edge:
edge_v.~EdgeAccessor();
std::destroy_at(&edge_v);
break;
case Type::Path:
path_v.~Path();
std::destroy_at(&path_v);
break;
case Type::Date:
case Type::LocalTime:
case Type::LocalDateTime:
case Type::Duration:
break;
case Type::Graph: {
auto *graph = graph_v.release();
std::destroy_at(&graph_v);
if (graph) {
utils::Allocator<Graph>(memory_).delete_object(graph);
}
break;
}
}
type_ = TypedValue::Type::Null;
@@ -792,6 +843,8 @@ TypedValue operator==(const TypedValue &a, const TypedValue &b) {
return TypedValue(a.ValueLocalDateTime() == b.ValueLocalDateTime(), a.GetMemoryResource());
case TypedValue::Type::Duration:
return TypedValue(a.ValueDuration() == b.ValueDuration(), a.GetMemoryResource());
case TypedValue::Type::Graph:
throw TypedValueException("Unsupported comparison operator");
default:
LOG_FATAL("Unhandled comparison for types");
}
@@ -1100,6 +1153,8 @@ size_t TypedValue::Hash::operator()(const TypedValue &value) const {
case TypedValue::Type::Duration:
return utils::DurationHash{}(value.ValueDuration());
break;
case TypedValue::Type::Graph:
throw TypedValueException("Unsupported hash function for Graph");
}
LOG_FATAL("Unhandled TypedValue.type() in hash function");
}

View File

@@ -21,6 +21,7 @@
#include <vector>
#include "query/db_accessor.hpp"
#include "query/graph.hpp"
#include "query/path.hpp"
#include "utils/exceptions.hpp"
#include "utils/memory.hpp"
@@ -82,7 +83,8 @@ class TypedValue {
Date,
LocalTime,
LocalDateTime,
Duration
Duration,
Graph
};
// TypedValue at this exact moment of compilation is an incomplete type, and
@@ -401,6 +403,23 @@ class TypedValue {
new (&path_v) Path(std::move(path), memory_);
}
/**
* Construct with the value of graph.
* utils::MemoryResource is obtained from graph. After the move, graph will be
* left empty.
*/
explicit TypedValue(Graph &&graph) noexcept : TypedValue(std::move(graph), graph.GetMemoryResource()) {}
/**
* Construct with the value of graph and use the given MemoryResource.
* If `*graph.GetMemoryResource() != *memory`, this call will perform an
* element-wise move and graph is not guaranteed to be empty.
*/
TypedValue(Graph &&graph, utils::MemoryResource *memory) : memory_(memory), type_(Type::Graph) {
auto *graph_ptr = utils::Allocator<Graph>(memory_).new_object<Graph>(std::move(graph));
new (&graph_v) std::unique_ptr<Graph>(graph_ptr);
}
/**
* Construct with the value of other.
* Default utils::NewDeleteResource() is used for allocations. After the move,
@@ -486,6 +505,7 @@ class TypedValue {
DECLARE_VALUE_AND_TYPE_GETTERS(utils::LocalTime, LocalTime)
DECLARE_VALUE_AND_TYPE_GETTERS(utils::LocalDateTime, LocalDateTime)
DECLARE_VALUE_AND_TYPE_GETTERS(utils::Duration, Duration)
DECLARE_VALUE_AND_TYPE_GETTERS(Graph, Graph)
#undef DECLARE_VALUE_AND_TYPE_GETTERS
@@ -528,6 +548,8 @@ class TypedValue {
utils::LocalTime local_time_v;
utils::LocalDateTime local_date_time_v;
utils::Duration duration_v;
// As the unique_ptr is not allocator aware, it requires special attention when copying or moving graphs
std::unique_ptr<Graph> graph_v;
};
/**

View File

@@ -222,23 +222,24 @@ void Storage::ReplicationClient::IfStreamingTransaction(const std::function<void
}
}
void Storage::ReplicationClient::FinalizeTransactionReplication() {
bool Storage::ReplicationClient::FinalizeTransactionReplication() {
// We can only check the state because it guarantees to be only
// valid during a single transaction replication (if the assumption
// that this and other transaction replication functions can only be
// called from a one thread stands)
if (replica_state_ != replication::ReplicaState::REPLICATING) {
return;
return false;
}
if (mode_ == replication::ReplicationMode::ASYNC) {
thread_pool_.AddTask([this] { this->FinalizeTransactionReplicationInternal(); });
thread_pool_.AddTask([this] { static_cast<void>(this->FinalizeTransactionReplicationInternal()); });
return true;
} else {
FinalizeTransactionReplicationInternal();
return FinalizeTransactionReplicationInternal();
}
}
void Storage::ReplicationClient::FinalizeTransactionReplicationInternal() {
bool Storage::ReplicationClient::FinalizeTransactionReplicationInternal() {
MG_ASSERT(replica_stream_, "Missing stream for transaction deltas");
try {
auto response = replica_stream_->Finalize();
@@ -249,6 +250,7 @@ void Storage::ReplicationClient::FinalizeTransactionReplicationInternal() {
thread_pool_.AddTask([&, this] { this->RecoverReplica(response.current_commit_timestamp); });
} else {
replica_state_.store(replication::ReplicaState::READY);
return true;
}
} catch (const rpc::RpcFailedException &) {
replica_stream_.reset();
@@ -258,6 +260,7 @@ void Storage::ReplicationClient::FinalizeTransactionReplicationInternal() {
}
HandleRpcFailure();
}
return false;
}
void Storage::ReplicationClient::RecoverReplica(uint64_t replica_commit) {

View File

@@ -103,7 +103,8 @@ class Storage::ReplicationClient {
// StartTransactionReplication, stream is created.
void IfStreamingTransaction(const std::function<void(ReplicaStream &handler)> &callback);
void FinalizeTransactionReplication();
// Return whether the transaction could be finalized on the replication client or not.
[[nodiscard]] bool FinalizeTransactionReplication();
// Transfer the snapshot file.
// @param path Path of the snapshot file.
@@ -125,7 +126,7 @@ class Storage::ReplicationClient {
Storage::TimestampInfo GetTimestampInfo();
private:
void FinalizeTransactionReplicationInternal();
[[nodiscard]] bool FinalizeTransactionReplicationInternal();
void RecoverReplica(uint64_t replica_commit);

View File

@@ -495,14 +495,14 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *
spdlog::trace(" Create label index on :{}", delta.operation_label.label);
// Need to send the timestamp
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
if (!storage_->CreateIndex(storage_->NameToLabel(delta.operation_label.label), timestamp))
if (storage_->CreateIndex(storage_->NameToLabel(delta.operation_label.label), timestamp).HasError())
throw utils::BasicException("Invalid transaction!");
break;
}
case durability::WalDeltaData::Type::LABEL_INDEX_DROP: {
spdlog::trace(" Drop label index on :{}", delta.operation_label.label);
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
if (!storage_->DropIndex(storage_->NameToLabel(delta.operation_label.label), timestamp))
if (storage_->DropIndex(storage_->NameToLabel(delta.operation_label.label), timestamp).HasError())
throw utils::BasicException("Invalid transaction!");
break;
}
@@ -510,8 +510,10 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *
spdlog::trace(" Create label+property index on :{} ({})", delta.operation_label_property.label,
delta.operation_label_property.property);
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
if (!storage_->CreateIndex(storage_->NameToLabel(delta.operation_label_property.label),
storage_->NameToProperty(delta.operation_label_property.property), timestamp))
if (storage_
->CreateIndex(storage_->NameToLabel(delta.operation_label_property.label),
storage_->NameToProperty(delta.operation_label_property.property), timestamp)
.HasError())
throw utils::BasicException("Invalid transaction!");
break;
}
@@ -519,8 +521,10 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *
spdlog::trace(" Drop label+property index on :{} ({})", delta.operation_label_property.label,
delta.operation_label_property.property);
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
if (!storage_->DropIndex(storage_->NameToLabel(delta.operation_label_property.label),
storage_->NameToProperty(delta.operation_label_property.property), timestamp))
if (storage_
->DropIndex(storage_->NameToLabel(delta.operation_label_property.label),
storage_->NameToProperty(delta.operation_label_property.property), timestamp)
.HasError())
throw utils::BasicException("Invalid transaction!");
break;
}
@@ -531,16 +535,17 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *
auto ret = storage_->CreateExistenceConstraint(
storage_->NameToLabel(delta.operation_label_property.label),
storage_->NameToProperty(delta.operation_label_property.property), timestamp);
if (!ret.HasValue() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!");
if (ret.HasError()) throw utils::BasicException("Invalid transaction!");
break;
}
case durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: {
spdlog::trace(" Drop existence constraint on :{} ({})", delta.operation_label_property.label,
delta.operation_label_property.property);
if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!");
if (!storage_->DropExistenceConstraint(storage_->NameToLabel(delta.operation_label_property.label),
storage_->NameToProperty(delta.operation_label_property.property),
timestamp))
if (storage_
->DropExistenceConstraint(storage_->NameToLabel(delta.operation_label_property.label),
storage_->NameToProperty(delta.operation_label_property.property), timestamp)
.HasError())
throw utils::BasicException("Invalid transaction!");
break;
}
@@ -570,7 +575,8 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *
}
auto ret = storage_->DropUniqueConstraint(storage_->NameToLabel(delta.operation_label_properties.label),
properties, timestamp);
if (ret != UniqueConstraints::DeletionStatus::SUCCESS) throw utils::BasicException("Invalid transaction!");
if (ret.HasError() || ret.GetValue() != UniqueConstraints::DeletionStatus::SUCCESS)
throw utils::BasicException("Invalid transaction!");
break;
}
}

View File

@@ -46,6 +46,7 @@
#include "storage/v2/replication/replication_client.hpp"
#include "storage/v2/replication/replication_server.hpp"
#include "storage/v2/replication/rpc.hpp"
#include "storage/v2/storage_error.hpp"
namespace memgraph::storage {
@@ -846,11 +847,13 @@ EdgeTypeId Storage::Accessor::NameToEdgeType(const std::string_view name) { retu
void Storage::Accessor::AdvanceCommand() { ++transaction_.command_id; }
utils::BasicResult<ConstraintViolation, void> Storage::Accessor::Commit(
utils::BasicResult<StorageDataManipulationError, void> Storage::Accessor::Commit(
const std::optional<uint64_t> desired_commit_timestamp) {
MG_ASSERT(is_transaction_active_, "The transaction is already terminated!");
MG_ASSERT(!transaction_.must_abort, "The transaction can't be committed!");
auto could_replicate_all_sync_replicas = true;
if (transaction_.deltas.empty()) {
// We don't have to update the commit timestamp here because no one reads
// it.
@@ -869,7 +872,7 @@ utils::BasicResult<ConstraintViolation, void> Storage::Accessor::Commit(
auto validation_result = ValidateExistenceConstraints(*prev.vertex, storage_->constraints_);
if (validation_result) {
Abort();
return *validation_result;
return StorageDataManipulationError{*validation_result};
}
}
@@ -926,7 +929,7 @@ utils::BasicResult<ConstraintViolation, void> Storage::Accessor::Commit(
// Replica can log only the write transaction received from Main
// so the Wal files are consistent
if (storage_->replication_role_ == ReplicationRole::MAIN || desired_commit_timestamp.has_value()) {
storage_->AppendToWal(transaction_, *commit_timestamp_);
could_replicate_all_sync_replicas = storage_->AppendToWalDataManipulation(transaction_, *commit_timestamp_);
}
// Take committed_transactions lock while holding the engine lock to
@@ -954,11 +957,15 @@ utils::BasicResult<ConstraintViolation, void> Storage::Accessor::Commit(
if (unique_constraint_violation) {
Abort();
return *unique_constraint_violation;
return StorageDataManipulationError{*unique_constraint_violation};
}
}
is_transaction_active_ = false;
if (!could_replicate_all_sync_replicas) {
return StorageDataManipulationError{ReplicationError{}};
}
return {};
}
@@ -1157,46 +1164,82 @@ EdgeTypeId Storage::NameToEdgeType(const std::string_view name) {
return EdgeTypeId::FromUint(name_id_mapper_.NameToId(name));
}
bool Storage::CreateIndex(LabelId label, const std::optional<uint64_t> desired_commit_timestamp) {
utils::BasicResult<StorageIndexDefinitionError, void> Storage::CreateIndex(
LabelId label, const std::optional<uint64_t> desired_commit_timestamp) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
if (!indices_.label_index.CreateIndex(label, vertices_.access())) return false;
if (!indices_.label_index.CreateIndex(label, vertices_.access())) {
return StorageIndexDefinitionError{IndexDefinitionError{}};
}
const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp);
AppendToWal(durability::StorageGlobalOperation::LABEL_INDEX_CREATE, label, {}, commit_timestamp);
const auto success =
AppendToWalDataDefinition(durability::StorageGlobalOperation::LABEL_INDEX_CREATE, label, {}, commit_timestamp);
commit_log_->MarkFinished(commit_timestamp);
last_commit_timestamp_ = commit_timestamp;
return true;
if (success) {
return {};
}
return StorageIndexDefinitionError{ReplicationError{}};
}
bool Storage::CreateIndex(LabelId label, PropertyId property, const std::optional<uint64_t> desired_commit_timestamp) {
utils::BasicResult<StorageIndexDefinitionError, void> Storage::CreateIndex(
LabelId label, PropertyId property, const std::optional<uint64_t> desired_commit_timestamp) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
if (!indices_.label_property_index.CreateIndex(label, property, vertices_.access())) return false;
if (!indices_.label_property_index.CreateIndex(label, property, vertices_.access())) {
return StorageIndexDefinitionError{IndexDefinitionError{}};
}
const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp);
AppendToWal(durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE, label, {property}, commit_timestamp);
auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE, label,
{property}, commit_timestamp);
commit_log_->MarkFinished(commit_timestamp);
last_commit_timestamp_ = commit_timestamp;
return true;
if (success) {
return {};
}
return StorageIndexDefinitionError{ReplicationError{}};
}
bool Storage::DropIndex(LabelId label, const std::optional<uint64_t> desired_commit_timestamp) {
utils::BasicResult<StorageIndexDefinitionError, void> Storage::DropIndex(
LabelId label, const std::optional<uint64_t> desired_commit_timestamp) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
if (!indices_.label_index.DropIndex(label)) return false;
if (!indices_.label_index.DropIndex(label)) {
return StorageIndexDefinitionError{IndexDefinitionError{}};
}
const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp);
AppendToWal(durability::StorageGlobalOperation::LABEL_INDEX_DROP, label, {}, commit_timestamp);
auto success =
AppendToWalDataDefinition(durability::StorageGlobalOperation::LABEL_INDEX_DROP, label, {}, commit_timestamp);
commit_log_->MarkFinished(commit_timestamp);
last_commit_timestamp_ = commit_timestamp;
return true;
if (success) {
return {};
}
return StorageIndexDefinitionError{ReplicationError{}};
}
bool Storage::DropIndex(LabelId label, PropertyId property, const std::optional<uint64_t> desired_commit_timestamp) {
utils::BasicResult<StorageIndexDefinitionError, void> Storage::DropIndex(
LabelId label, PropertyId property, const std::optional<uint64_t> desired_commit_timestamp) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
if (!indices_.label_property_index.DropIndex(label, property)) return false;
if (!indices_.label_property_index.DropIndex(label, property)) {
return StorageIndexDefinitionError{IndexDefinitionError{}};
}
// For a description why using `timestamp_` is correct, see
// `CreateIndex(LabelId label)`.
const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp);
AppendToWal(durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP, label, {property}, commit_timestamp);
auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP, label,
{property}, commit_timestamp);
commit_log_->MarkFinished(commit_timestamp);
last_commit_timestamp_ = commit_timestamp;
return true;
if (success) {
return {};
}
return StorageIndexDefinitionError{ReplicationError{}};
}
IndicesInfo Storage::ListAllIndices() const {
@@ -1204,55 +1247,92 @@ IndicesInfo Storage::ListAllIndices() const {
return {indices_.label_index.ListIndices(), indices_.label_property_index.ListIndices()};
}
utils::BasicResult<ConstraintViolation, bool> Storage::CreateExistenceConstraint(
utils::BasicResult<StorageExistenceConstraintDefinitionError, void> Storage::CreateExistenceConstraint(
LabelId label, PropertyId property, const std::optional<uint64_t> desired_commit_timestamp) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
auto ret = storage::CreateExistenceConstraint(&constraints_, label, property, vertices_.access());
if (ret.HasError() || !ret.GetValue()) return ret;
if (ret.HasError()) {
return StorageExistenceConstraintDefinitionError{ret.GetError()};
}
if (!ret.GetValue()) {
return StorageExistenceConstraintDefinitionError{ConstraintDefinitionError{}};
}
const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp);
AppendToWal(durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE, label, {property}, commit_timestamp);
auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE, label,
{property}, commit_timestamp);
commit_log_->MarkFinished(commit_timestamp);
last_commit_timestamp_ = commit_timestamp;
return true;
if (success) {
return {};
}
return StorageExistenceConstraintDefinitionError{ReplicationError{}};
}
bool Storage::DropExistenceConstraint(LabelId label, PropertyId property,
const std::optional<uint64_t> desired_commit_timestamp) {
utils::BasicResult<StorageExistenceConstraintDroppingError, void> Storage::DropExistenceConstraint(
LabelId label, PropertyId property, const std::optional<uint64_t> desired_commit_timestamp) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
if (!storage::DropExistenceConstraint(&constraints_, label, property)) return false;
const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp);
AppendToWal(durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP, label, {property}, commit_timestamp);
commit_log_->MarkFinished(commit_timestamp);
last_commit_timestamp_ = commit_timestamp;
return true;
}
utils::BasicResult<ConstraintViolation, UniqueConstraints::CreationStatus> Storage::CreateUniqueConstraint(
LabelId label, const std::set<PropertyId> &properties, const std::optional<uint64_t> desired_commit_timestamp) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
auto ret = constraints_.unique_constraints.CreateConstraint(label, properties, vertices_.access());
if (ret.HasError() || ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS) {
return ret;
if (!storage::DropExistenceConstraint(&constraints_, label, property)) {
return StorageExistenceConstraintDroppingError{ConstraintDefinitionError{}};
}
const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp);
AppendToWal(durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE, label, properties, commit_timestamp);
auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP, label,
{property}, commit_timestamp);
commit_log_->MarkFinished(commit_timestamp);
last_commit_timestamp_ = commit_timestamp;
return UniqueConstraints::CreationStatus::SUCCESS;
if (success) {
return {};
}
return StorageExistenceConstraintDroppingError{ReplicationError{}};
}
UniqueConstraints::DeletionStatus Storage::DropUniqueConstraint(
LabelId label, const std::set<PropertyId> &properties, const std::optional<uint64_t> desired_commit_timestamp) {
utils::BasicResult<StorageUniqueConstraintDefinitionError, UniqueConstraints::CreationStatus>
Storage::CreateUniqueConstraint(LabelId label, const std::set<PropertyId> &properties,
const std::optional<uint64_t> desired_commit_timestamp) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
auto ret = constraints_.unique_constraints.CreateConstraint(label, properties, vertices_.access());
if (ret.HasError()) {
return StorageUniqueConstraintDefinitionError{ret.GetError()};
}
if (ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS) {
return ret.GetValue();
}
const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp);
auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE, label,
properties, commit_timestamp);
commit_log_->MarkFinished(commit_timestamp);
last_commit_timestamp_ = commit_timestamp;
if (success) {
return UniqueConstraints::CreationStatus::SUCCESS;
}
return StorageUniqueConstraintDefinitionError{ReplicationError{}};
}
utils::BasicResult<StorageUniqueConstraintDroppingError, UniqueConstraints::DeletionStatus>
Storage::DropUniqueConstraint(LabelId label, const std::set<PropertyId> &properties,
const std::optional<uint64_t> desired_commit_timestamp) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
auto ret = constraints_.unique_constraints.DropConstraint(label, properties);
if (ret != UniqueConstraints::DeletionStatus::SUCCESS) {
return ret;
}
const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp);
AppendToWal(durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP, label, properties, commit_timestamp);
auto success = AppendToWalDataDefinition(durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP, label,
properties, commit_timestamp);
commit_log_->MarkFinished(commit_timestamp);
last_commit_timestamp_ = commit_timestamp;
return UniqueConstraints::DeletionStatus::SUCCESS;
if (success) {
return UniqueConstraints::DeletionStatus::SUCCESS;
}
return StorageUniqueConstraintDroppingError{ReplicationError{}};
}
ConstraintsInfo Storage::ListAllConstraints() const {
@@ -1605,8 +1685,10 @@ void Storage::FinalizeWalFile() {
}
}
void Storage::AppendToWal(const Transaction &transaction, uint64_t final_commit_timestamp) {
if (!InitializeWalFile()) return;
bool Storage::AppendToWalDataManipulation(const Transaction &transaction, uint64_t final_commit_timestamp) {
if (!InitializeWalFile()) {
return true;
}
// Traverse deltas and append them to the WAL file.
// A single transaction will always be contained in a single WAL file.
auto current_commit_timestamp = transaction.commit_timestamp->load(std::memory_order_acquire);
@@ -1775,17 +1857,28 @@ void Storage::AppendToWal(const Transaction &transaction, uint64_t final_commit_
FinalizeWalFile();
auto finalized_on_all_replicas = true;
replication_clients_.WithLock([&](auto &clients) {
for (auto &client : clients) {
client->IfStreamingTransaction([&](auto &stream) { stream.AppendTransactionEnd(final_commit_timestamp); });
client->FinalizeTransactionReplication();
const auto finalized = client->FinalizeTransactionReplication();
if (client->Mode() == replication::ReplicationMode::SYNC) {
finalized_on_all_replicas = finalized && finalized_on_all_replicas;
}
}
});
return finalized_on_all_replicas;
}
void Storage::AppendToWal(durability::StorageGlobalOperation operation, LabelId label,
const std::set<PropertyId> &properties, uint64_t final_commit_timestamp) {
if (!InitializeWalFile()) return;
bool Storage::AppendToWalDataDefinition(durability::StorageGlobalOperation operation, LabelId label,
const std::set<PropertyId> &properties, uint64_t final_commit_timestamp) {
if (!InitializeWalFile()) {
return true;
}
auto finalized_on_all_replicas = true;
wal_file_->AppendOperation(operation, label, properties, final_commit_timestamp);
{
if (replication_role_.load() == ReplicationRole::MAIN) {
@@ -1794,12 +1887,17 @@ void Storage::AppendToWal(durability::StorageGlobalOperation operation, LabelId
client->StartTransactionReplication(wal_file_->SequenceNumber());
client->IfStreamingTransaction(
[&](auto &stream) { stream.AppendOperation(operation, label, properties, final_commit_timestamp); });
client->FinalizeTransactionReplication();
const auto finalized = client->FinalizeTransactionReplication();
if (client->Mode() == replication::ReplicationMode::SYNC) {
finalized_on_all_replicas = finalized && finalized_on_all_replicas;
}
}
});
}
}
FinalizeWalFile();
return finalized_on_all_replicas;
}
utils::BasicResult<Storage::CreateSnapshotError> Storage::CreateSnapshot() {

View File

@@ -48,6 +48,7 @@
#include "storage/v2/replication/enums.hpp"
#include "storage/v2/replication/rpc.hpp"
#include "storage/v2/replication/serialization.hpp"
#include "storage/v2/storage_error.hpp"
namespace memgraph::storage {
@@ -309,11 +310,14 @@ class Storage final {
void AdvanceCommand();
/// Commit returns `ConstraintViolation` if the changes made by this
/// transaction violate an existence or unique constraint. In that case the
/// transaction is automatically aborted. Otherwise, void is returned.
/// Returns void if the transaction has been committed.
/// Returns `StorageDataManipulationError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// * `ConstraintViolation`: the changes made by this transaction violate an existence or unique constraint. In this
/// case the transaction is automatically aborted.
/// @throw std::bad_alloc
utils::BasicResult<ConstraintViolation, void> Commit(std::optional<uint64_t> desired_commit_timestamp = {});
utils::BasicResult<StorageDataManipulationError, void> Commit(
std::optional<uint64_t> desired_commit_timestamp = {});
/// @throw std::bad_alloc
void Abort();
@@ -352,54 +356,83 @@ class Storage final {
/// @throw std::bad_alloc if unable to insert a new mapping
EdgeTypeId NameToEdgeType(std::string_view name);
/// Create an index.
/// Returns void if the index has been created.
/// Returns `StorageIndexDefinitionError` if an error occures. Error can be:
/// * `IndexDefinitionError`: the index already exists.
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// @throw std::bad_alloc
bool CreateIndex(LabelId label, std::optional<uint64_t> desired_commit_timestamp = {});
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(
LabelId label, std::optional<uint64_t> desired_commit_timestamp = {});
/// Create an index.
/// Returns void if the index has been created.
/// Returns `StorageIndexDefinitionError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// * `IndexDefinitionError`: the index already exists.
/// @throw std::bad_alloc
bool CreateIndex(LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {});
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {});
bool DropIndex(LabelId label, std::optional<uint64_t> desired_commit_timestamp = {});
/// Drop an existing index.
/// Returns void if the index has been dropped.
/// Returns `StorageIndexDefinitionError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// * `IndexDefinitionError`: the index does not exist.
utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(
LabelId label, std::optional<uint64_t> desired_commit_timestamp = {});
bool DropIndex(LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {});
/// Drop an existing index.
/// Returns void if the index has been dropped.
/// Returns `StorageIndexDefinitionError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// * `IndexDefinitionError`: the index does not exist.
utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {});
IndicesInfo ListAllIndices() const;
/// Creates an existence constraint. Returns true if the constraint was
/// successfuly added, false if it already exists and a `ConstraintViolation`
/// if there is an existing vertex violating the constraint.
///
/// Returns void if the existence constraint has been created.
/// Returns `StorageExistenceConstraintDefinitionError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// * `ConstraintViolation`: there is already a vertex existing that would break this new constraint.
/// * `ConstraintDefinitionError`: the constraint already exists.
/// @throw std::bad_alloc
/// @throw std::length_error
utils::BasicResult<ConstraintViolation, bool> CreateExistenceConstraint(
utils::BasicResult<StorageExistenceConstraintDefinitionError, void> CreateExistenceConstraint(
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {});
/// Removes an existence constraint. Returns true if the constraint was
/// removed, and false if it doesn't exist.
bool DropExistenceConstraint(LabelId label, PropertyId property,
std::optional<uint64_t> desired_commit_timestamp = {});
/// Drop an existing existence constraint.
/// Returns void if the existence constraint has been dropped.
/// Returns `StorageExistenceConstraintDroppingError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// * `ConstraintDefinitionError`: the constraint did not exists.
utils::BasicResult<StorageExistenceConstraintDroppingError, void> DropExistenceConstraint(
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {});
/// Creates a unique constraint. In the case of two vertices violating the
/// constraint, it returns `ConstraintViolation`. Otherwise returns a
/// `UniqueConstraints::CreationStatus` enum with the following possibilities:
/// * `SUCCESS` if the constraint was successfully created,
/// * `ALREADY_EXISTS` if the constraint already existed,
/// * `EMPTY_PROPERTIES` if the property set is empty, or
// * `PROPERTIES_SIZE_LIMIT_EXCEEDED` if the property set exceeds the
// limit of maximum number of properties.
///
/// Create an unique constraint.
/// Returns `StorageUniqueConstraintDefinitionError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// * `ConstraintViolation`: there are already vertices violating the constraint.
/// Returns `UniqueConstraints::CreationStatus` otherwise. Value can be:
/// * `SUCCESS` if the constraint was successfully created,
/// * `ALREADY_EXISTS` if the constraint already existed,
/// * `EMPTY_PROPERTIES` if the property set is empty, or
/// * `PROPERTIES_SIZE_LIMIT_EXCEEDED` if the property set exceeds the limit of maximum number of properties.
/// @throw std::bad_alloc
utils::BasicResult<ConstraintViolation, UniqueConstraints::CreationStatus> CreateUniqueConstraint(
utils::BasicResult<StorageUniqueConstraintDefinitionError, UniqueConstraints::CreationStatus> CreateUniqueConstraint(
LabelId label, const std::set<PropertyId> &properties, std::optional<uint64_t> desired_commit_timestamp = {});
/// Removes a unique constraint. Returns `UniqueConstraints::DeletionStatus`
/// enum with the following possibilities:
/// * `SUCCESS` if constraint was successfully removed,
/// * `NOT_FOUND` if the specified constraint was not found,
/// * `EMPTY_PROPERTIES` if the property set is empty, or
/// * `PROPERTIES_SIZE_LIMIT_EXCEEDED` if the property set exceeds the
// limit of maximum number of properties.
UniqueConstraints::DeletionStatus DropUniqueConstraint(LabelId label, const std::set<PropertyId> &properties,
std::optional<uint64_t> desired_commit_timestamp = {});
/// Removes an existing unique constraint.
/// Returns `StorageUniqueConstraintDroppingError` if an error occures. Error can be:
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
/// Returns `UniqueConstraints::DeletionStatus` otherwise. Value can be:
/// * `SUCCESS` if constraint was successfully removed,
/// * `NOT_FOUND` if the specified constraint was not found,
/// * `EMPTY_PROPERTIES` if the property set is empty, or
/// * `PROPERTIES_SIZE_LIMIT_EXCEEDED` if the property set exceeds the limit of maximum number of properties.
utils::BasicResult<StorageUniqueConstraintDroppingError, UniqueConstraints::DeletionStatus> DropUniqueConstraint(
LabelId label, const std::set<PropertyId> &properties, std::optional<uint64_t> desired_commit_timestamp = {});
ConstraintsInfo ListAllConstraints() const;
@@ -474,9 +507,11 @@ class Storage final {
bool InitializeWalFile();
void FinalizeWalFile();
void AppendToWal(const Transaction &transaction, uint64_t final_commit_timestamp);
void AppendToWal(durability::StorageGlobalOperation operation, LabelId label, const std::set<PropertyId> &properties,
uint64_t final_commit_timestamp);
/// Return true in all cases excepted if any sync replicas have not sent confirmation.
[[nodiscard]] bool AppendToWalDataManipulation(const Transaction &transaction, uint64_t final_commit_timestamp);
/// Return true in all cases excepted if any sync replicas have not sent confirmation.
[[nodiscard]] bool AppendToWalDataDefinition(durability::StorageGlobalOperation operation, LabelId label,
const std::set<PropertyId> &properties, uint64_t final_commit_timestamp);
uint64_t CommitTimestamp(std::optional<uint64_t> desired_commit_timestamp = {});

View File

@@ -0,0 +1,38 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include "storage/v2/constraints.hpp"
#include <variant>
namespace memgraph::storage {
struct ReplicationError {};
using StorageDataManipulationError = std::variant<ConstraintViolation, ReplicationError>;
struct IndexDefinitionError {};
using StorageIndexDefinitionError = std::variant<IndexDefinitionError, ReplicationError>;
struct ConstraintDefinitionError {};
using StorageExistenceConstraintDefinitionError =
std::variant<ConstraintViolation, ConstraintDefinitionError, ReplicationError>;
using StorageExistenceConstraintDroppingError = std::variant<ConstraintDefinitionError, ReplicationError>;
using StorageUniqueConstraintDefinitionError = std::variant<ConstraintViolation, ReplicationError>;
using StorageUniqueConstraintDroppingError = std::variant<ReplicationError>;
} // namespace memgraph::storage

View File

@@ -59,6 +59,8 @@ void Telemetry::AddCollector(const std::string &name, const std::function<const
collectors_.emplace_back(name, func);
}
std::string Telemetry::GetRunId() const { return uuid_; }
Telemetry::~Telemetry() {
scheduler_.Stop();
CollectData("shutdown");

View File

@@ -39,6 +39,9 @@ class Telemetry final {
void AddCollector(const std::string &name, const std::function<const nlohmann::json(void)> &func);
/// Required to expose run_id to Bolt server.
std::string GetRunId() const;
~Telemetry();
Telemetry(const Telemetry &) = delete;

View File

@@ -45,7 +45,7 @@ class ExpansionBenchFixture : public benchmark::Fixture {
MG_ASSERT(!dba.Commit().HasError());
}
MG_ASSERT(db->CreateIndex(label));
MG_ASSERT(!db->CreateIndex(label).HasError());
interpreter_context.emplace(&*db, memgraph::query::InterpreterConfig{}, data_directory);
interpreter.emplace(&*interpreter_context);

View File

@@ -83,7 +83,7 @@ static void AddStarGraph(memgraph::storage::Storage *db, int spoke_count, int de
}
MG_ASSERT(!dba.Commit().HasError());
}
MG_ASSERT(db->CreateIndex(db->NameToLabel(kStartLabel)));
MG_ASSERT(!db->CreateIndex(db->NameToLabel(kStartLabel)).HasError());
}
static void AddTree(memgraph::storage::Storage *db, int vertex_count) {
@@ -105,7 +105,7 @@ static void AddTree(memgraph::storage::Storage *db, int vertex_count) {
}
MG_ASSERT(!dba.Commit().HasError());
}
MG_ASSERT(db->CreateIndex(db->NameToLabel(kStartLabel)));
MG_ASSERT(!db->CreateIndex(db->NameToLabel(kStartLabel)).HasError());
}
static memgraph::query::CypherQuery *ParseCypherQuery(const std::string &query_string,

View File

@@ -95,7 +95,7 @@ static memgraph::query::CypherQuery *AddIndexedMatches(int num_matches, const st
static auto CreateIndexedVertices(int index_count, int vertex_count, memgraph::storage::Storage *db) {
auto label = db->NameToLabel("label");
auto prop = db->NameToProperty("prop");
db->CreateIndex(label, prop);
[[maybe_unused]] auto _ = db->CreateIndex(label, prop);
auto dba = db->Access();
for (int vi = 0; vi < vertex_count; ++vi) {
for (int index = 0; index < index_count; ++index) {

View File

@@ -16,6 +16,7 @@
#include <gtest/gtest.h>
#include "storage/v2/storage.hpp"
#include "storage/v2/storage_error.hpp"
#include "utils/thread.hpp"
const uint64_t kNumVerifiers = 5;
@@ -29,7 +30,7 @@ TEST(Storage, LabelIndex) {
auto store = memgraph::storage::Storage();
auto label = store.NameToLabel("label");
ASSERT_TRUE(store.CreateIndex(label));
ASSERT_FALSE(store.CreateIndex(label).HasError());
std::vector<std::thread> verifiers;
verifiers.reserve(kNumVerifiers);
@@ -111,7 +112,7 @@ TEST(Storage, LabelPropertyIndex) {
auto label = store.NameToLabel("label");
auto prop = store.NameToProperty("prop");
ASSERT_TRUE(store.CreateIndex(label, prop));
ASSERT_FALSE(store.CreateIndex(label, prop).HasError());
std::vector<std::thread> verifiers;
verifiers.reserve(kNumVerifiers);

View File

@@ -29,6 +29,7 @@ function(copy_e2e_cpp_files TARGET_PREFIX FILE_NAME)
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${FILE_NAME})
endfunction()
add_subdirectory(fine_grained_access)
add_subdirectory(server)
add_subdirectory(replication)
add_subdirectory(memory)
@@ -37,9 +38,11 @@ add_subdirectory(isolation_levels)
add_subdirectory(streams)
add_subdirectory(temporal_types)
add_subdirectory(write_procedures)
add_subdirectory(configuration)
add_subdirectory(magic_functions)
add_subdirectory(module_file_manager)
add_subdirectory(monitoring_server)
add_subdirectory(lba_procedures)
copy_e2e_python_files(pytest_runner pytest_runner.sh "")
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/memgraph-selfsigned.crt DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

View File

@@ -0,0 +1,6 @@
function(copy_configuration_check_e2e_python_files FILE_NAME)
copy_e2e_python_files(write_procedures ${FILE_NAME})
endfunction()
copy_configuration_check_e2e_python_files(default_config.py)
copy_configuration_check_e2e_python_files(configuration_check.py)

View File

@@ -0,0 +1,46 @@
# Copyright 2022 Memgraph Ltd.
#
# Use of this software is governed by the Business Source License
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
# License, and you may not use this file except in compliance with the Business Source License.
#
# As of the Change Date specified in that file, in accordance with
# the Business Source License, use of this software will be governed
# by the Apache License, Version 2.0, included in the file
# licenses/APL.txt.
import sys
import mgclient
import pytest
import default_config
def test_does_default_config_match():
connection = mgclient.connect(host="localhost", port=7687)
connection.autocommit = True
cursor = connection.cursor()
cursor.execute("SHOW CONFIG")
config = cursor.fetchall()
assert len(config) == len(default_config.startup_config_dict)
for flag in config:
flag_name = flag[0]
# The default value of these is dependent on the given machine.
machine_dependent_configurations = ["bolt_num_workers", "data_directory", "log_file"]
if flag_name in machine_dependent_configurations:
continue
# default_value
assert default_config.startup_config_dict[flag_name][0] == flag[1]
# current_value
assert default_config.startup_config_dict[flag_name][1] == flag[2]
# description
assert default_config.startup_config_dict[flag_name][2] == flag[3]
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

@@ -0,0 +1,166 @@
# Copyright 2022 Memgraph Ltd.
#
# Use of this software is governed by the Business Source License
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
# License, and you may not use this file except in compliance with the Business Source License.
#
# As of the Change Date specified in that file, in accordance with
# the Business Source License, use of this software will be governed
# by the Apache License, Version 2.0, included in the file
# licenses/APL.txt.
# In order to check the working correctness of the SHOW CONFIG command, a couple of configuration flags has been passed to the testing instance. These are:
# "--log-level=TRACE", "--storage-properties-on-edges=True", "--storage-snapshot-interval-sec", "300", "--storage-wal-enabled=True"
# If you wish to modify these, update the startup_config_dict and workloads.yaml !
startup_config_dict = {
"auth_module_create_missing_role": ("true", "true", "Set to false to disable creation of missing roles."),
"auth_module_create_missing_user": ("true", "true", "Set to false to disable creation of missing users."),
"auth_module_executable": ("", "", "Absolute path to the auth module executable that should be used."),
"auth_module_manage_roles": (
"true",
"true",
"Set to false to disable management of roles through the auth module.",
),
"auth_module_timeout_ms": (
"10000",
"10000",
"Timeout (in milliseconds) used when waiting for a response from the auth module.",
),
"auth_password_permit_null": ("true", "true", "Set to false to disable null passwords."),
"auth_password_strength_regex": (
".+",
".+",
"The regular expression that should be used to match the entire entered password to ensure its strength.",
),
"allow_load_csv": ("true", "true", "Controls whether LOAD CSV clause is allowed in queries."),
"audit_buffer_flush_interval_ms": (
"200",
"200",
"Interval (in milliseconds) used for flushing the audit log buffer.",
),
"audit_buffer_size": ("100000", "100000", "Maximum number of items in the audit log buffer."),
"audit_enabled": ("false", "false", "Set to true to enable audit logging."),
"auth_user_or_role_name_regex": (
"[a-zA-Z0-9_.+-@]+",
"[a-zA-Z0-9_.+-@]+",
"Set to the regular expression that each user or role name must fulfill.",
),
"bolt_address": ("0.0.0.0", "0.0.0.0", "IP address on which the Bolt server should listen."),
"bolt_cert_file": ("", "", "Certificate file which should be used for the Bolt server."),
"bolt_key_file": ("", "", "Key file which should be used for the Bolt server."),
"bolt_num_workers": (
"12",
"12",
"Number of workers used by the Bolt server. By default, this will be the number of processing units available on the machine.",
),
"bolt_port": ("7687", "7687", "Port on which the Bolt server should listen."),
"bolt_server_name_for_init": (
"",
"",
"Server name which the database should send to the client in the Bolt INIT message.",
),
"bolt_session_inactivity_timeout": (
"1800",
"1800",
"Time in seconds after which inactive Bolt sessions will be closed.",
),
"data_directory": ("mg_data", "mg_data", "Path to directory in which to save all permanent data."),
"isolation_level": (
"SNAPSHOT_ISOLATION",
"SNAPSHOT_ISOLATION",
"Default isolation level used for the transactions. Allowed values: SNAPSHOT_ISOLATION, READ_COMMITTED, READ_UNCOMMITTED",
),
"kafka_bootstrap_servers": (
"",
"",
"List of default Kafka brokers as a comma separated list of broker host or host:port.",
),
"log_file": ("", "", "Path to where the log should be stored."),
"log_level": (
"WARNING",
"TRACE",
"Minimum log level. Allowed values: TRACE, DEBUG, INFO, WARNING, ERROR, CRITICAL",
),
"memory_limit": (
"0",
"0",
"Total memory limit in MiB. Set to 0 to use the default values which are 100% of the phyisical memory if the swap is enabled and 90% of the physical memory otherwise.",
),
"memory_warning_threshold": (
"1024",
"1024",
"Memory warning threshold, in MB. If Memgraph detects there is less available RAM it will log a warning. Set to 0 to disable.",
),
"monitoring_address": (
"0.0.0.0",
"0.0.0.0",
"IP address on which the websocket server for Memgraph monitoring should listen.",
),
"monitoring_port": ("7444", "7444", "Port on which the websocket server for Memgraph monitoring should listen."),
"pulsar_service_url": ("", "", "Default URL used while connecting to Pulsar brokers."),
"query_execution_timeout_sec": (
"600",
"600",
"Maximum allowed query execution time. Queries exceeding this limit will be aborted. Value of 0 means no limit.",
),
"query_modules_directory": (
"",
"",
"Directory where modules with custom query procedures are stored. NOTE: Multiple comma-separated directories can be defined.",
),
"replication_replica_check_frequency_sec": (
"1",
"1",
"The time duration between two replica checks/pings. If < 1, replicas will NOT be checked at all. NOTE: The MAIN instance allocates a new thread for each REPLICA.",
),
"storage_gc_cycle_sec": ("30", "30", "Storage garbage collector interval (in seconds)."),
"storage_properties_on_edges": ("false", "true", "Controls whether edges have properties."),
"storage_recover_on_startup": (
"false",
"false",
"Controls whether the storage recovers persisted data on startup.",
),
"storage_snapshot_interval_sec": (
"0",
"300",
"Storage snapshot creation interval (in seconds). Set to 0 to disable periodic snapshot creation.",
),
"storage_snapshot_on_exit": ("false", "false", "Controls whether the storage creates another snapshot on exit."),
"storage_snapshot_retention_count": ("3", "3", "The number of snapshots that should always be kept."),
"storage_wal_enabled": (
"false",
"true",
"Controls whether the storage uses write-ahead-logging. To enable WAL periodic snapshots must be enabled.",
),
"storage_wal_file_flush_every_n_tx": (
"100000",
"100000",
"Issue a 'fsync' call after this amount of transactions are written to the WAL file. Set to 1 for fully synchronous operation.",
),
"storage_wal_file_size_kib": ("20480", "20480", "Minimum file size of each WAL file."),
"stream_transaction_conflict_retries": (
"30",
"30",
"Number of times to retry when a stream transformation fails to commit because of conflicting transactions",
),
"stream_transaction_retry_interval": (
"500",
"500",
"Retry interval in milliseconds when a stream transformation fails to commit because of conflicting transactions",
),
"telemetry_enabled": (
"false",
"false",
"Set to true to enable telemetry. We collect information about the running system (CPU and memory information) and information about the database runtime (vertex and edge counts and resource usage) to allow for easier improvement of the product.",
),
"query_cost_planner": ("true", "true", "Use the cost-estimating query planner."),
"query_plan_cache_ttl": ("60", "60", "Time to live for cached query plans, in seconds."),
"query_vertex_count_to_expand_existing": (
"10",
"10",
"Maximum count of indexed vertices which provoke indexed lookup and then expand to existing, instead of a regular expand. Default is 10, to turn off use -1.",
),
"query_max_plans": ("1000", "1000", "Maximum number of generated plans for a query."),
"flag_file": ("", "", "load flags from file"),
}

View File

@@ -0,0 +1,13 @@
template_cluster: &template_cluster
cluster:
main:
args: ["--log-level=TRACE", "--storage-properties-on-edges=True", "--storage-snapshot-interval-sec", "300", "--storage-wal-enabled=True"]
log_file: "configuration-check-e2e.log"
setup_queries: []
validation_queries: []
workloads:
- name: "Configuration check"
binary: "tests/e2e/pytest_runner.sh"
args: ["configuration/configuration_check.py"]
<<: *template_cluster

View File

@@ -0,0 +1,8 @@
function(copy_fine_grained_access_e2e_python_files FILE_NAME)
copy_e2e_python_files(fine_grained_access ${FILE_NAME})
endfunction()
copy_fine_grained_access_e2e_python_files(common.py)
copy_fine_grained_access_e2e_python_files(create_delete_filtering_tests.py)
copy_fine_grained_access_e2e_python_files(edge_type_filtering_tests.py)
copy_fine_grained_access_e2e_python_files(path_filtering_tests.py)

View File

@@ -0,0 +1,31 @@
# 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.
import mgclient
def reset_and_prepare(admin_cursor):
execute_and_fetch_all(admin_cursor, "REVOKE LABELS * FROM user;")
execute_and_fetch_all(admin_cursor, "REVOKE EDGE_TYPES * FROM user;")
execute_and_fetch_all(admin_cursor, "MATCH(n) DETACH DELETE n;")
execute_and_fetch_all(admin_cursor, "CREATE (n:test_delete {name: 'test1'});")
execute_and_fetch_all(admin_cursor, "CREATE (n:test_delete_1)-[r:edge_type_delete]->(m:test_delete_2);")
def execute_and_fetch_all(cursor, query):
cursor.execute(query)
return cursor.fetchall()
def connect(**kwargs):
connection = mgclient.connect(host="localhost", port=7687, **kwargs)
connection.autocommit = True
return connection

View File

@@ -0,0 +1,517 @@
# Copyright 2022 Memgraph Ltd.
#
# Use of this software is governed by the Business Source License
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
# License, and you may not use this file except in compliance with the Business Source License.
#
# As of the Change Date specified in that file, in accordance with
# the Business Source License, use of this software will be governed
# by the Apache License, Version 2.0, included in the file
# licenses/APL.txt.
import pytest
import sys
from mgclient import DatabaseError
import common
def test_create_node_all_labels_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "CREATE (n:label1) RETURN n;")
assert len(results) == 1
def test_create_node_all_labels_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(user_connnection.cursor(), "CREATE (n:label1) RETURN n;")
def test_create_node_specific_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS :label1 TO user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "CREATE (n:label1) RETURN n;")
assert len(results) == 1
def test_create_node_specific_label_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :label1 TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(user_connnection.cursor(), "CREATE (n:label1) RETURN n;")
def test_delete_node_all_labels_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n:test_delete) DELETE n;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n:test_delete) RETURN n;")
assert len(results) == 0
def test_delete_node_all_labels_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n:test_delete) DELETE n")
def test_delete_node_specific_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS :test_delete TO user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n:test_delete) DELETE n;")
results = common.execute_and_fetch_all(admin_connection.cursor(), "MATCH (n:test_delete) RETURN n;")
assert len(results) == 0
def test_delete_node_specific_label_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :test_delete TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n:test_delete) DELETE n;")
def test_create_edge_all_labels_all_edge_types_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON EDGE_TYPES * TO user;")
results = common.execute_and_fetch_all(
user_connnection.cursor(),
"CREATE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
assert len(results) == 1
def test_create_edge_all_labels_all_edge_types_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON EDGE_TYPES * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"CREATE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_create_edge_all_labels_denied_all_edge_types_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON EDGE_TYPES * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"CREATE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_create_edge_all_labels_granted_all_edge_types_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON EDGE_TYPES * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"CREATE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_create_edge_all_labels_granted_specific_edge_types_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT UPDATE ON EDGE_TYPES :edge_type TO user;",
)
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"CREATE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_create_edge_first_node_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS :label1 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :label2 TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT CREATE_DELETE ON EDGE_TYPES :edge_type TO user;",
)
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"CREATE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_create_edge_second_node_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS :label2 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :label1 TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT CREATE_DELETE ON EDGE_TYPES :edge_type TO user;",
)
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"CREATE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_delete_edge_all_labels_denied_all_edge_types_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON EDGE_TYPES * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH (n:test_delete_1)-[r:edge_type_delete]->(m:test_delete_2) DELETE r",
)
def test_delete_edge_all_labels_granted_all_edge_types_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON EDGE_TYPES * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH (n:test_delete_1)-[r:edge_type_delete]->(m:test_delete_2) DELETE r",
)
def test_delete_edge_all_labels_granted_specific_edge_types_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT UPDATE ON EDGE_TYPES :edge_type_delete TO user;",
)
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH (n:test_delete_1)-[r:edge_type_delete]->(m:test_delete_2) DELETE r",
)
def test_delete_edge_first_node_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :test_delete_1 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS :test_delete_2 TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT CREATE_DELETE ON EDGE_TYPES :edge_type_delete TO user;",
)
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH (n:test_delete_1)-[r:edge_type_delete]->(m:test_delete_2) DELETE r",
)
def test_delete_edge_second_node_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :test_delete_2 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS :test_delete_1 TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT CREATE_DELETE ON EDGE_TYPES :edge_type_delete TO user;",
)
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH (n:test_delete_1)-[r:edge_type_delete]->(m:test_delete_2) DELETE r",
)
def test_delete_node_with_edge_label_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT UPDATE ON LABELS :test_delete_1 TO user;",
)
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n) DETACH DELETE n;")
def test_delete_node_with_edge_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT CREATE_DELETE ON LABELS :test_delete_1 TO user;",
)
common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n) DETACH DELETE n;")
results = common.execute_and_fetch_all(admin_connection.cursor(), "MATCH (n:test_delete_1) RETURN n;")
assert len(results) == 0
def test_merge_node_all_labels_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MERGE (n:label1) RETURN n;")
assert len(results) == 1
def test_merge_node_all_labels_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(user_connnection.cursor(), "MERGE (n:label1) RETURN n;")
def test_merge_node_specific_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS :label1 TO user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MERGE (n:label1) RETURN n;")
assert len(results) == 1
def test_merge_node_specific_label_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :label1 TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(user_connnection.cursor(), "MERGE (n:label1) RETURN n;")
def test_merge_edge_all_labels_all_edge_types_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON EDGE_TYPES * TO user;")
results = common.execute_and_fetch_all(
user_connnection.cursor(),
"MERGE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
assert len(results) == 1
def test_merge_edge_all_labels_all_edge_types_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON EDGE_TYPES * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MERGE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_merge_edge_all_labels_denied_all_edge_types_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON EDGE_TYPES * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MERGE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_merge_edge_all_labels_granted_all_edge_types_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON EDGE_TYPES * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MERGE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_merge_edge_all_labels_granted_specific_edge_types_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT UPDATE ON EDGE_TYPES :edge_type TO user;",
)
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MERGE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_merge_edge_first_node_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS :label1 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :label2 TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT CREATE_DELETE ON EDGE_TYPES :edge_type TO user;",
)
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MERGE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_merge_edge_second_node_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS :label2 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :label1 TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT CREATE_DELETE ON EDGE_TYPES :edge_type TO user;",
)
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MERGE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_set_label_when_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS :update_label_2 TO user;")
common.execute_and_fetch_all(user_connection.cursor(), "MATCH (p:test_delete) SET p:update_label_2;")
def test_set_label_when_label_denied():
admin_connection = common.connect(username="admin", password="test")
user_connection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :update_label_2 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS :test_delete TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(user_connection.cursor(), "MATCH (p:test_delete) SET p:update_label_2;")
def test_remove_label_when_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS :test_delete TO user;")
common.execute_and_fetch_all(user_connection.cursor(), "MATCH (p:test_delete) REMOVE p:test_delete;")
def test_remove_label_when_label_denied():
admin_connection = common.connect(username="admin", password="test")
user_connection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :update_label_2 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS :test_delete TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(user_connection.cursor(), "MATCH (p:test_delete) REMOVE p:test_delete;")
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

@@ -0,0 +1,94 @@
import common
import sys
import pytest
def test_all_edge_types_all_labels_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n)-[r]->(m) RETURN n,r,m;")
assert len(results) == 3
def test_deny_all_edge_types_and_all_labels():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES * TO user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n)-[r]->(m) RETURN n,r,m;")
assert len(results) == 0
def test_revoke_all_edge_types_and_all_labels():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n)-[r]->(m) RETURN n,r,m;")
assert len(results) == 0
def test_deny_edge_type():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS :label1, :label2, :label3 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES :edgeType2 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES :edgeType1 TO user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n)-[r]->(m) RETURN n,r,m;")
assert len(results) == 2
def test_denied_node_label():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS :label1,:label3 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES :edgeType1, :edgeType2 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label2 TO user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n)-[r]->(m) RETURN n,r,m;")
assert len(results) == 2
def test_denied_one_of_node_label():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS :label1,:label2 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES :edgeType1, :edgeType2 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label3 TO user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n)-[r]->(m) RETURN n,r,m;")
assert len(results) == 1
def test_revoke_all_labels():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n)-[r]->(m) RETURN n,r,m;")
assert len(results) == 0
def test_revoke_all_edge_types():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n)-[r]->(m) RETURN n,r,m;")
assert len(results) == 0
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

@@ -0,0 +1,717 @@
import common
import sys
import pytest
def test_weighted_shortest_path_all_edge_types_all_labels_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
total_paths_results = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n)-[r *wShortest (r, n | r.weight)]->(m) RETURN extract( node in nodes(p) | node.id);",
)
path_result = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *wShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length,nodes(p);",
)
expected_path = [0, 1, 3, 4, 5]
expected_all_paths = [
[0, 1],
[0, 1, 2],
[0, 1, 3],
[0, 1, 3, 4],
[0, 1, 3, 4, 5],
[1, 2],
[1, 3],
[1, 3, 4],
[1, 3, 4, 5],
[2, 1],
[2, 3],
[2, 3, 4],
[2, 3, 4, 5],
[3, 4],
[3, 4, 5],
[4, 3],
[4, 5],
]
assert len(total_paths_results) == 16
assert all(path[0] in expected_all_paths for path in total_paths_results)
assert path_result[0][0] == 20
assert all(node.id in expected_path for node in path_result[0][1])
def test_weighted_shortest_path_all_edge_types_all_labels_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES * TO user;")
results = common.execute_and_fetch_all(
user_connnection.cursor(), "MATCH p=(n)-[r *wShortest (r, n | r.weight)]->(m) RETURN p;"
)
assert len(results) == 0
def test_weighted_shortest_path_denied_start():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label1, :label2, :label3, :label4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label0 TO user;")
path_length_result = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *wShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length;",
)
assert len(path_length_result) == 0
def test_weighted_shortest_path_denied_destination():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label1, :label2, :label3 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label4 TO user;")
path_length_result = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *wShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length;",
)
assert len(path_length_result) == 0
def test_weighted_shortest_path_denied_label_1():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label2, :label3, :label4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label1 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
total_paths_results = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n)-[r *wShortest (r, n | r.weight)]->(m) RETURN extract( node in nodes(p) | node.id);",
)
path_result = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *wShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length, nodes(p);",
)
expected_path = [0, 2, 3, 4, 5]
expected_all_paths = [
[0, 2],
[0, 2, 3],
[0, 2, 3, 4],
[0, 2, 3, 4, 5],
[2, 3],
[2, 3, 4],
[2, 3, 4, 5],
[3, 4],
[3, 4, 5],
[4, 3],
[4, 5],
]
assert len(total_paths_results) == 11
assert all(path[0] in expected_all_paths for path in total_paths_results)
assert path_result[0][0] == 30
assert all(node.id in expected_path for node in path_result[0][1])
def test_weighted_shortest_path_denied_edge_type_3():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON EDGE_TYPES :edge_type_1, :edge_type_2, :edge_type_4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES :edge_type_3 TO user;")
path_result = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *wShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length, nodes(p);",
)
total_paths_results = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n)-[r *wShortest (r, n | r.weight)]->(m) RETURN extract( node in nodes(p) | node.id);",
)
expected_path = [0, 1, 2, 3, 5]
expected_all_paths = [
[0, 1],
[0, 1, 2],
[0, 1, 2, 4],
[0, 1, 2, 4, 3],
[0, 1, 2, 4, 5],
[1, 2, 4, 3],
[1, 2],
[1, 2, 4],
[1, 2, 4, 5],
[2, 1],
[2, 4, 3],
[2, 4],
[2, 4, 5],
[3, 4],
[3, 4, 5],
[4, 3],
[4, 5],
]
assert len(total_paths_results) == 16
assert all(path[0] in expected_all_paths for path in total_paths_results)
assert path_result[0][0] == 25
assert all(node.id in expected_path for node in path_result[0][1])
def test_dfs_all_edge_types_all_labels_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
source_destination_paths = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH path=(n:label0)-[* 1..3]->(m:label4) RETURN extract( node in nodes(path) | node.id);",
)
expected_paths = [[0, 1, 3, 5], [0, 2, 3, 5], [0, 2, 4, 5]]
assert len(source_destination_paths) == 3
assert all(path[0] in expected_paths for path in source_destination_paths)
def test_dfs_all_edge_types_all_labels_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES * TO user;")
total_paths_results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH p=(n)-[*]->(m) RETURN p;")
assert len(total_paths_results) == 0
def test_dfs_denied_start():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label1, :label2, :label3, :label4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label0 TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(), "MATCH p=(n:label0)-[*]->(m:label4) RETURN p;"
)
assert len(source_destination_path) == 0
def test_dfs_denied_destination():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label1, :label2, :label3 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label4 TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(), "MATCH p=(n:label0)-[*]->(m:label4) RETURN p;"
)
assert len(source_destination_path) == 0
def test_dfs_denied_label_1():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label2, :label3, :label4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label1 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
source_destination_paths = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[* 1..3]->(m:label4) RETURN extract( node in nodes(p) | node.id);",
)
expected_paths = [[0, 2, 3, 5], [0, 2, 4, 5]]
assert len(source_destination_paths) == 2
assert all(path[0] in expected_paths for path in source_destination_paths)
def test_dfs_denied_edge_type_3():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON EDGE_TYPES :edge_type_1, :edge_type_2, :edge_type_4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES :edge_type_3 TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r * 1..3]->(m:label4) RETURN extract( node in nodes(p) | node.id);",
)
expected_path = [0, 2, 4, 5]
assert len(source_destination_path) == 1
assert source_destination_path[0][0] == expected_path
def test_bfs_sts_all_edge_types_all_labels_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH (n), (m) WITH n, m MATCH p=(n:label0)-[r *BFS]->(m:label4) RETURN extract( node in nodes(p) | node.id);",
)
expected_path = [0, 1, 3, 5]
assert len(source_destination_path) == 1
assert source_destination_path[0][0] == expected_path
def test_bfs_sts_all_edge_types_all_labels_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES * TO user;")
total_paths_results = common.execute_and_fetch_all(
user_connnection.cursor(), "MATCH (n), (m) WITH n, m MATCH p=(n)-[r *BFS]->(m) RETURN p;"
)
assert len(total_paths_results) == 0
def test_bfs_sts_denied_start():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label1, :label2, :label3, :label4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label0 TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(), "MATCH (n), (m) WITH n, m MATCH p=(n:label0)-[r *BFS]->(m:label4) RETURN p;"
)
assert len(source_destination_path) == 0
def test_bfs_sts_denied_destination():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label1, :label2, :label3 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label4 TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(), "MATCH (n), (m) WITH n, m MATCH p=(n:label0)-[r *BFS]->(m:label4) RETURN p;"
)
assert len(source_destination_path) == 0
def test_bfs_sts_denied_label_1():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label2, :label3, :label4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label1 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH (n), (m) WITH n, m MATCH p=(n:label0)-[r *BFS]->(m:label4) RETURN extract( node in nodes(p) | node.id);",
)
expected_path = [0, 2, 4, 5]
assert len(source_destination_path) == 1
assert source_destination_path[0][0] == expected_path
def test_bfs_sts_denied_edge_type_3():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON EDGE_TYPES :edge_type_1, :edge_type_2, :edge_type_4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES :edge_type_3 TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH (n), (m) WITH n, m MATCH p=(n:label0)-[r *BFS]->(m:label4) RETURN extract( node in nodes(p) | node.id);",
)
expected_path = [0, 2, 4, 5]
assert len(source_destination_path) == 1
assert source_destination_path[0][0] == expected_path
def test_bfs_single_source_all_edge_types_all_labels_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *BFS]->(m:label4) RETURN extract( node in nodes(p) | node.id);",
)
expected_path = [0, 2, 3, 5]
assert len(source_destination_path) == 1
assert source_destination_path[0][0] == expected_path
def test_bfs_single_source_all_edge_types_all_labels_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES * TO user;")
total_paths_results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH p=(n)-[r *BFS]->(m) RETURN p;")
assert len(total_paths_results) == 0
def test_bfs_single_source_denied_start():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label1, :label2, :label3, :label4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label0 TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(), "MATCH p=(n:label0)-[r *BFS]->(m:label4) RETURN p;"
)
assert len(source_destination_path) == 0
def test_bfs_single_source_denied_destination():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label1, :label2, :label3 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label4 TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(), "MATCH p=(n:label0)-[r *BFS]->(m:label4) RETURN p;"
)
assert len(source_destination_path) == 0
def test_bfs_single_source_denied_label_1():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label2, :label3, :label4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label1 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *BFS]->(m:label4) RETURN extract( node in nodes(p) | node.id);",
)
expected_path = [0, 2, 3, 5]
assert len(source_destination_path) == 1
assert source_destination_path[0][0] == expected_path
def test_bfs_single_source_denied_edge_type_3():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON EDGE_TYPES :edge_type_1, :edge_type_2, :edge_type_4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES :edge_type_3 TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *BFS]->(m:label4) RETURN extract( node in nodes(p) | node.id);",
)
expected_path = [0, 2, 4, 5]
assert len(source_destination_path) == 1
assert source_destination_path[0][0] == expected_path
def test_all_shortest_paths_when_all_edge_types_all_labels_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
total_paths_results = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n)-[r *allShortest (r, n | r.weight)]->(m) RETURN extract( node in nodes(p) | node.id);",
)
path_result = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *allShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length,nodes(p);",
)
expected_path = [0, 1, 3, 4, 5]
expected_all_paths = [
[0, 1],
[0, 1, 2],
[0, 1, 3],
[0, 1, 3, 4],
[0, 1, 3, 4, 5],
[1, 2],
[1, 3],
[1, 3, 4],
[1, 3, 4, 5],
[2, 1],
[2, 3],
[2, 3, 4],
[2, 3, 4, 5],
[3, 4],
[3, 4, 5],
[4, 3],
[4, 5],
]
assert len(total_paths_results) == 16
assert all(path[0] in expected_all_paths for path in total_paths_results)
assert path_result[0][0] == 20
assert all(node.id in expected_path for node in path_result[0][1])
def test_all_shortest_paths_when_all_edge_types_all_labels_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES * TO user;")
results = common.execute_and_fetch_all(
user_connnection.cursor(), "MATCH p=(n)-[r *allShortest (r, n | r.weight)]->(m) RETURN p;"
)
assert len(results) == 0
def test_all_shortest_paths_when_denied_start():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label1, :label2, :label3, :label4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label0 TO user;")
path_length_result = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *allShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length;",
)
assert len(path_length_result) == 0
def test_all_shortest_paths_when_denied_destination():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label1, :label2, :label3 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label4 TO user;")
path_length_result = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *allShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length;",
)
assert len(path_length_result) == 0
def test_all_shortest_paths_when_denied_label_1():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label2, :label3, :label4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label1 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
total_paths_results = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n)-[r *allShortest (r, n | r.weight)]->(m) RETURN extract( node in nodes(p) | node.id);",
)
path_result = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *allShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length, nodes(p);",
)
expected_path = [0, 2, 3, 4, 5]
expected_all_paths = [
[0, 2],
[0, 2, 3],
[0, 2, 3, 4],
[0, 2, 3, 4, 5],
[2, 3],
[2, 3, 4],
[2, 3, 4, 5],
[3, 4],
[3, 4, 5],
[4, 3],
[4, 5],
]
assert len(total_paths_results) == 11
assert all(path[0] in expected_all_paths for path in total_paths_results)
assert path_result[0][0] == 30
assert all(node.id in expected_path for node in path_result[0][1])
def test_all_shortest_paths_when_denied_edge_type_3():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON EDGE_TYPES :edge_type_1, :edge_type_2, :edge_type_4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES :edge_type_3 TO user;")
path_result = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *allShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length, nodes(p);",
)
total_paths_results = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n)-[r *allShortest (r, n | r.weight)]->(m) RETURN extract( node in nodes(p) | node.id);",
)
expected_path = [0, 1, 2, 3, 5]
expected_all_paths = [
[0, 1],
[0, 1, 2],
[0, 1, 2, 4],
[0, 1, 2, 4, 3],
[0, 1, 2, 4, 5],
[1, 2, 4, 3],
[1, 2],
[1, 2, 4],
[1, 2, 4, 5],
[2, 1],
[2, 4, 3],
[2, 4],
[2, 4, 5],
[3, 4],
[3, 4, 5],
[4, 3],
[4, 5],
]
assert len(total_paths_results) == 16
assert all(path[0] in expected_all_paths for path in total_paths_results)
assert path_result[0][0] == 25
assert all(node.id in expected_path for node in path_result[0][1])
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

@@ -0,0 +1,74 @@
bolt_port: &bolt_port "7687"
create_delete_filtering_cluster: &create_delete_filtering_cluster
cluster:
main:
args: ["--bolt-port", "7687", "--log-level=TRACE"]
log_file: "fine_grained_access.log"
setup_queries:
[
"CREATE USER admin IDENTIFIED BY 'test';",
"CREATE USER user IDENTIFIED BY 'test';",
"GRANT ALL PRIVILEGES TO admin;",
"GRANT ALL PRIVILEGES TO user;",
]
edge_type_filtering_cluster: &edge_type_filtering_cluster
cluster:
main:
args: ["--bolt-port", *bolt_port, "--log-level=TRACE"]
log_file: "fine_grained_access.log"
setup_queries:
[
"CREATE USER admin IDENTIFIED BY 'test';",
"CREATE USER user IDENTIFIED BY 'test';",
"GRANT ALL PRIVILEGES TO admin;",
"GRANT ALL PRIVILEGES TO user;",
"GRANT CREATE_DELETE ON LABELS * TO admin;",
"GRANT CREATE_DELETE ON EDGE_TYPES * TO admin;",
"MERGE (l1:label1 {name: 'test1'});",
"MERGE (l2:label2 {name: 'test2'});",
"MATCH (l1:label1),(l2:label2) WHERE l1.name = 'test1' AND l2.name = 'test2' CREATE (l1)-[r:edgeType1]->(l2);",
"MERGE (l3:label3 {name: 'test3'});",
"MATCH (l1:label1),(l3:label3) WHERE l1.name = 'test1' AND l3.name = 'test3' CREATE (l1)-[r:edgeType2]->(l3);",
"MERGE (mix:label3:label1 {name: 'test4'});",
"MATCH (l1:label1),(mix:label3) WHERE l1.name = 'test1' AND mix.name = 'test4' CREATE (l1)-[r:edgeType2]->(mix);",
]
validation_queries: []
path_filtering_cluster: &path_filtering_cluster
cluster:
main:
args: ["--bolt-port", "7687", "--log-level=TRACE"]
log_file: "fine_grained_access.log"
setup_queries:
[
"CREATE USER admin IDENTIFIED BY 'test';",
"CREATE USER user IDENTIFIED BY 'test';",
"GRANT ALL PRIVILEGES TO admin;",
"GRANT ALL PRIVILEGES TO user;",
"MERGE (a:label0 {id: 0}) MERGE (b:label1 {id: 1}) CREATE (a)-[:edge_type_1 {weight: 6}]->(b);",
"MERGE (a:label0 {id: 0}) MERGE (b:label2 {id: 2}) CREATE (a)-[:edge_type_1 {weight: 14}]->(b);",
"MERGE (a:label1 {id: 1}) MERGE (b:label2 {id: 2}) CREATE (a)-[:edge_type_2 {weight: 1}]->(b);",
"MERGE (a:label2 {id: 2}) MERGE (b:label3 {id: 4}) CREATE (a)-[:edge_type_2 {weight: 10}]->(b);",
"MERGE (a:label1 {id: 1}) MERGE (b:label3 {id: 3}) CREATE (a)-[:edge_type_3 {weight: 5}]->(b);",
"MERGE (a:label2 {id: 2}) MERGE (b:label3 {id: 3}) CREATE (a)-[:edge_type_3 {weight: 7}]->(b);",
"MERGE (a:label3 {id: 3}) MERGE (b:label3 {id: 4}) CREATE (a)-[:edge_type_4 {weight: 1}]->(b);",
"MERGE (a:label3 {id: 4}) MERGE (b:label3 {id: 3}) CREATE (a)-[:edge_type_4 {weight: 1}]->(b);",
"MERGE (a:label3 {id: 3}) MERGE (b:label4 {id: 5}) CREATE (a)-[:edge_type_4 {weight: 14}]->(b);",
"MERGE (a:label3 {id: 4}) MERGE (b:label4 {id: 5}) CREATE (a)-[:edge_type_4 {weight: 8}]->(b);",
]
workloads:
- name: "Create delete filtering"
binary: "tests/e2e/pytest_runner.sh"
args: ["fine_grained_access/create_delete_filtering_tests.py"]
<<: *create_delete_filtering_cluster
- name: "EdgeType filtering"
binary: "tests/e2e/pytest_runner.sh"
args: ["fine_grained_access/edge_type_filtering_tests.py"]
<<: *edge_type_filtering_cluster
- name: "Path filtering"
binary: "tests/e2e/pytest_runner.sh"
args: ["fine_grained_access/path_filtering_tests.py"]
<<: *path_filtering_cluster

View File

@@ -0,0 +1,13 @@
function(copy_lba_procedures_e2e_python_files FILE_NAME)
copy_e2e_python_files(lba_procedures ${FILE_NAME})
endfunction()
copy_lba_procedures_e2e_python_files(common.py)
copy_lba_procedures_e2e_python_files(show_privileges.py)
copy_lba_procedures_e2e_python_files(read_query_modules.py)
copy_lba_procedures_e2e_python_files(update_query_modules.py)
copy_lba_procedures_e2e_python_files(create_delete_query_modules.py)
copy_lba_procedures_e2e_python_files(read_permission_queries.py)
copy_lba_procedures_e2e_python_files(update_permission_queries.py)
add_subdirectory(procedures)

View File

@@ -0,0 +1,69 @@
# 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.
import mgclient
import typing
def execute_and_fetch_all(cursor: mgclient.Cursor, query: str, params: dict = {}) -> typing.List[tuple]:
cursor.execute(query, params)
return cursor.fetchall()
def connect(**kwargs) -> mgclient.Connection:
connection = mgclient.connect(host="localhost", port=7687, **kwargs)
connection.autocommit = True
return connection
def reset_permissions(admin_cursor: mgclient.Cursor, create_index: bool = False):
execute_and_fetch_all(admin_cursor, "REVOKE LABELS * FROM user;")
execute_and_fetch_all(admin_cursor, "REVOKE EDGE_TYPES * FROM user;")
execute_and_fetch_all(admin_cursor, "MATCH(n) DETACH DELETE n;")
execute_and_fetch_all(admin_cursor, "DROP INDEX ON :read_label(prop);")
execute_and_fetch_all(admin_cursor, "DROP INDEX ON :read_label;")
execute_and_fetch_all(admin_cursor, "CREATE (n:read_label {prop: 5});")
execute_and_fetch_all(
admin_cursor, "CREATE (n:read_label_1 {prop: 5})-[r:read_edge_type]->(m:read_label_2 {prop: 5});"
)
if create_index:
execute_and_fetch_all(admin_cursor, "CREATE INDEX ON :read_label;")
execute_and_fetch_all(admin_cursor, "CREATE INDEX ON :read_label(prop);")
def reset_update_permissions(admin_cursor: mgclient.Cursor):
execute_and_fetch_all(admin_cursor, "REVOKE LABELS * FROM user;")
execute_and_fetch_all(admin_cursor, "REVOKE EDGE_TYPES * FROM user;")
execute_and_fetch_all(admin_cursor, "MATCH (n) DETACH DELETE n;")
execute_and_fetch_all(admin_cursor, "CREATE (n:update_label {prop: 1});")
execute_and_fetch_all(
admin_cursor,
"CREATE (n:update_label_1)-[r:update_edge_type {prop: 1}]->(m:update_label_2);",
)
def reset_create_delete_permissions(admin_cursor: mgclient.Cursor):
execute_and_fetch_all(admin_cursor, "REVOKE LABELS * FROM user;")
execute_and_fetch_all(admin_cursor, "REVOKE EDGE_TYPES * FROM user;")
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS * TO user;")
execute_and_fetch_all(admin_cursor, "GRANT READ ON EDGE_TYPES * TO user;")
execute_and_fetch_all(admin_cursor, "MATCH (n) DETACH DELETE n;")
execute_and_fetch_all(admin_cursor, "CREATE (n:create_delete_label);")
execute_and_fetch_all(
admin_cursor,
"CREATE (n:create_delete_label_1)-[r:create_delete_edge_type]->(m:create_delete_label_2);",
)

View File

@@ -0,0 +1,299 @@
# Copyright 2022 Memgraph Ltd.
#
# Use of this software is governed by the Business Source License
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
# License, and you may not use this file except in compliance with the Business Source License.
#
# As of the Change Date specified in that file, in accordance with
# the Business Source License, use of this software will be governed
# by the Apache License, Version 2.0, included in the file
# licenses/APL.txt.
import pytest
import sys
from common import (
connect,
execute_and_fetch_all,
mgclient,
reset_create_delete_permissions,
)
AUTHORIZATION_ERROR_IDENTIFIER = "AuthorizationError"
create_vertex_query = "CALL create_delete.create_vertex() YIELD created_node RETURN labels(created_node);"
remove_label_vertex_query = "CALL create_delete.remove_label('create_delete_label') YIELD node RETURN labels(node);"
set_label_vertex_query = "CALL create_delete.set_label('new_create_delete_label') YIELD node RETURN labels(node);"
create_edge_query = "MATCH (n:create_delete_label_1), (m:create_delete_label_2) CALL create_delete.create_edge(n, m) YIELD nr_of_edges RETURN nr_of_edges;"
delete_edge_query = "CALL create_delete.delete_edge() YIELD * RETURN *;"
def test_can_not_create_vertex_when_given_nothing():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, create_vertex_query)
def test_can_create_vertex_when_given_global_create_delete():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, create_vertex_query)
len(result[0][0]) == 1
def test_can_not_create_vertex_when_given_global_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, create_vertex_query)
def test_can_not_create_vertex_when_given_global_update():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON LABELS :create_delete_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, create_vertex_query)
def test_can_add_vertex_label_when_given_create_delete():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor,
"GRANT CREATE_DELETE ON LABELS :new_create_delete_label, UPDATE ON LABELS :create_delete_label TO user;",
)
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, set_label_vertex_query)
assert "create_delete_label" in result[0][0]
assert "new_create_delete_label" in result[0][0]
def test_can_not_add_vertex_label_when_given_update():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor, "GRANT UPDATE ON LABELS :new_create_delete_label, :create_delete_label TO user;"
)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, set_label_vertex_query)
def test_can_not_add_vertex_label_when_given_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor, "GRANT READ ON LABELS :new_create_delete_label, UPDATE ON LABELS :create_delete_label TO user;"
)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, set_label_vertex_query)
def test_can_remove_vertex_label_when_given_create_delete():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON LABELS :create_delete_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, remove_label_vertex_query)
assert result[0][0] != ":create_delete_label"
def test_can_remove_vertex_label_when_given_global_create_delete():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, remove_label_vertex_query)
assert result[0][0] != ":create_delete_label"
def test_can_not_remove_vertex_label_when_given_update():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON LABELS :create_delete_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, remove_label_vertex_query)
def test_can_not_remove_vertex_label_when_given_global_update():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, remove_label_vertex_query)
def test_can_not_remove_vertex_label_when_given_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :create_delete_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, remove_label_vertex_query)
def test_can_not_remove_vertex_label_when_given_global_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, remove_label_vertex_query)
def test_can_not_create_edge_when_given_nothing():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, create_edge_query)
def test_can_not_create_edge_when_given_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON EDGE_TYPES :new_create_delete_edge_type TO user")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, create_edge_query)
def test_can_not_create_edge_when_given_update():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON EDGE_TYPES :new_create_delete_edge_type TO user")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, create_edge_query)
def test_can_create_edge_when_given_create_delete():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor,
"GRANT CREATE_DELETE ON EDGE_TYPES :new_create_delete_edge_type TO user",
)
test_cursor = connect(username="user", password="test").cursor()
no_of_edges = execute_and_fetch_all(test_cursor, create_edge_query)
assert no_of_edges[0][0] == 2
def test_can_not_delete_edge_when_given_nothing():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, delete_edge_query)
def test_can_not_delete_edge_when_given_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor,
"GRANT READ ON EDGE_TYPES :create_delete_edge_type TO user",
)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, delete_edge_query)
def test_can_not_delete_edge_when_given_update():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor,
"GRANT UPDATE ON EDGE_TYPES :create_delete_edge_type TO user",
)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, delete_edge_query)
def test_can_delete_edge_when_given_create_delete():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor,
"GRANT CREATE_DELETE ON EDGE_TYPES :create_delete_edge_type TO user",
)
test_cursor = connect(username="user", password="test").cursor()
no_of_edges = execute_and_fetch_all(test_cursor, delete_edge_query)
assert no_of_edges[0][0] == 0
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

Some files were not shown because too many files have changed in this diff Show More