Compare commits

..

154 Commits

Author SHA1 Message Date
Andreja Tonev
3d1e55f29a Cache user to speed up PullPlan 2023-07-24 23:04:52 +02:00
Andreja Tonev
e7b1244572 Check if user has db access only if different from current 2023-07-22 09:54:49 +02:00
Andreja Tonev
5777f35939 Updated isolation level e2e test 2023-07-21 19:12:31 +02:00
Andreja Tonev
cc76e36c13 Bugfix: DB has to accessed via interpreter_context (fault when switching to on disk) 2023-07-21 17:16:35 +02:00
Andreja Tonev
07b4640288 Quickfix: e2e test fails due to low memory 2023-07-21 15:08:44 +02:00
Andreja Tonev
9ee5eb687a Merge remote-tracking branch 'origin/master' into MG-add-multi-db-support 2023-07-21 14:02:04 +02:00
Andreja Tonev
ff5e6bce08 Bugfix: Broken symlink crashes memgraph 2023-07-21 13:48:36 +02:00
Andreja Tonev
98f0bfa4e6 Updater for storage::Config; Proactively linking on-disk dirs; Tets updates 2023-07-21 12:18:25 +02:00
Andreja Tonev
8b149edba8 Bugfix: OnDisk path not handled properly 2023-07-21 11:35:12 +02:00
Andreja Tonev
a1e44fe756 Clang-tidy fix 2023-07-21 09:31:26 +02:00
Andreja Tonev
ee49edc749 Bugfix: ENTERPRISE OFF build fail 2023-07-20 16:41:24 +02:00
Andreja Tonev
c5ea780f08 Merge branch 'master' into MG-add-multi-db-support
Conflicts:
	src/http_handlers/metrics.hpp
	src/memgraph.cpp
	src/query/exceptions.hpp
	src/query/interpreter.cpp
	src/query/interpreter.hpp
	src/storage/v2/storage.cpp
	src/storage/v2/storage.hpp
	tests/e2e/isolation_levels/isolation_levels.cpp
	tests/manual/single_query.cpp
	tests/unit/interpreter_faker.hpp
	tests/unit/query_common.hpp
	tests/unit/query_trigger.cpp

 Further modified:
	src/dbms/interp_handler.hpp
	src/dbms/session_context.hpp
	src/dbms/session_context_handler.hpp
	src/dbms/storage_handler.hpp
	src/memgraph.cpp
	src/query/interpreter.cpp
	src/query/interpreter.hpp
	src/storage/v2/config.hpp
	src/storage/v2/storage.cpp
	src/storage/v2/storage.hpp
	tests/unit/dbms_interp.cpp
	tests/unit/dbms_sc_handler.cpp
	tests/unit/dbms_storage.cpp
2023-07-20 16:20:21 +02:00
Marko Budiselić
992e718a97 Fix v2.9 release build issues (#1087)
* Fix include order inside `tests/unit/query_variable_start_planner.cpp` fails on an Rhel7 based OS
2023-07-20 16:15:27 +02:00
Marko Budiselić
2ec6b7f40b Update v2.9 license date (#1081) 2023-07-20 10:56:31 +02:00
Andi
05eca46267 Fix flaky transaction_queue_multiple unit test (#1077) 2023-07-19 22:58:02 +02:00
Vlasta
fae039c215 Improve logging for the on-disk storage (#1079) 2023-07-19 22:54:42 +02:00
Marko Budiselić
3b9133fd5a Improve e2e and replication testing setup (#1061)
* Add `--replication-restore-state-on-startup` with `false` as default

Co-authored-by: Aidar Samerkhanov <aidar.samerkhanov@memgraph.io>
Co-authored-by: Andi Skrgat <andi8647@gmail.com>
2023-07-19 21:18:43 +02:00
Andreja Tonev
2a28b9feb4 Merge commit 'aa4f68a37' into MG-add-multi-db-support
Pre on-disk commit alignment
2023-07-19 09:22:23 +02:00
Andreja Tonev
ba63aaaf82 Bugfix: SyncPtr ignores specified timeout; Added tests 2023-07-14 21:52:56 +02:00
andrejtonev
506567d7a4 [E952 < ] Converted query errors into exceptions (#1076)
Converted multi-database query errors into exceptions
2023-07-14 21:11:29 +02:00
andrejtonev
fa0160c578 Remove databases fromm auth and durability list when not recovering (#1072)
Clean up auth and durability if recovery on startup is disabled
2023-07-14 21:01:54 +02:00
andrejtonev
dbddb97496 [E952 <] Added simple SyncPtr 1sec timeout (#1075)
SyncPtr has a configurable timeout

On timeout an exception is thrown. 
A DELETE DATABASE query throws a QueryRuntimeException in this case and stops blocking.
TODO: Add a flag that configures the timeout
2023-07-14 17:37:16 +02:00
andrejtonev
8716d43c15 [E952 <] Mg add multi db support remove tmp db (#1071)
Removed temporary_ database

There is only a single active database that can be defined by a cypher query or its metadata
When database is defined via metadata, cypher query is disabled
2023-07-14 17:35:05 +02:00
andrejtonev
ac2d9c0565 [E952 <] Grant/revoke database will throw if database not present (#1074)
* Grant/revoke database will throw if database not present

* Bugfix: ENTERPRISE OFF
2023-07-14 15:44:46 +02:00
andrejtonev
9ff193399b Default to memgraph for pre multi-db user (#1073) 2023-07-14 14:36:22 +02:00
andrejtonev
5391502710 [E952 <] Improvements and bugfixes (#1063)
Improvements and bugfixes

Database::Contains now prioritizes denied over granted
New query SHOW DATABASE PRIVILEGES FOR USER
Extended auth api to support the query
Bugfix: databaseNameOrStar didn't support wildcard as expected
Bugfix: missing privileges from cypher lexer
Bugfix: SHOW DATABASES crashes with more than 1000 dbs
Bugfix: granting database may clear revoked databases
Added useful logs
Throwing dbms specific exceptions, instead of std ones
SessionContextHandler clean up
Added storage_delete_on_drop flag
Added multi-db mode to some e2e tests
Added default db and updatet auth e2e tests
Removed USER keyword; Updated auth, fine access and tx e2e tests
Added database info to telemetry
Reverted symlink to the per-dir version
Added name to storage info
2023-07-12 19:00:51 +02:00
Andreja Tonev
9a03600a6c SessionContextHandler handle partial delete and new 2023-07-03 20:17:57 +02:00
Andreja Tonev
cacdb3f7b4 Improvements
Added RestoreTriggers and RestoreStreams to SessionContextHandler
Bugfix: ENTERPRISE OFF SessionHL constructor error
trigger e2e tests with mutli-db
2023-07-03 18:44:54 +02:00
andrejtonev
db01d33956 [E952 < ] Improvements to the architecture (#1050)
Simplifies the architecture

SessioHL inherits from bolt::Session
Moved back to single auth
Removed MULTI_TENANT and added MULTI_DATABASE_EDIT/USE
Decoupled dbms from glue
User can grant or revoke access to specific databases
Prepared queries are associated with a specific database
storage_recover_on_startup and tenant_recover_on_startup unified into data_recover_on_startup
storage_recover_on_startup deprecated (still works, but it's hidden)
auth directory backed up and cleared if data_recover_on_startup is false (disabled because it's a breaking change)
Query result summary sends back the db executed on
New SessionContextHandler API: SetInPlace and CallInPlace (executes user-defined handlers under read lock)
Back-compatibility symlink updated to link memgraph dir directly to root (not a good solution)
Temporary db change goes through the sc_handler (via SetInPlace and CallInPlace)
GetDirDiskUsage ignore symlinks
2023-07-03 10:22:56 +02:00
Marko Budiselić
9d056e7649 Add experimental/v1 of ON_DISK_TRANSACTIONAL storage (#850)
Co-authored-by: Andi Skrgat <andi8647@gmail.com>
Co-authored-by: Aidar Samerkhanov <aidar.samerkhanov@memgraph.io>
2023-06-29 11:44:55 +02:00
Antonio Filipovic
aa4f68a37d Add error handling on py batched module init (#1052) 2023-06-28 17:23:42 +02:00
Josipmrden
84721f7e0a Add vertex degree to index statistics (#1026)
Add graph analysis of vertex degrees when doing ANALYZE GRAPH.
2023-06-27 18:06:20 +02:00
Ante Javor
261aa4f49b Improve replication logging (#1030) 2023-06-27 17:57:51 +02:00
Josipmrden
5ce1526995 Fix map at operator returning null from a map (#1039)
Fix map returning null from a map and not throwing exception if key is not present
2023-06-27 15:04:12 +02:00
Antonio Filipovic
cb843ee664 Fix multiple include error on mgp.hpp (#1043) 2023-06-27 10:07:33 +02:00
Andi
0f1ca745e5 Improve connection handling in tests/e2e (#1012) 2023-06-26 22:43:34 +02:00
Gareth Andrew Lloyd
3b781bf525 Add HTTP+GZIP support to LOAD CSV (#1027) 2023-06-26 19:10:48 +02:00
Antonio Filipovic
d573eda8bb Add python & cpp batching option in procedures
* Add API for batching from the procedure 
* Use PoolResource for batched procedures
2023-06-26 15:46:13 +02:00
Andreja Tonev
09cf2146fc Bugfix: ENTERPRISE OFF build failed 2023-06-26 15:13:20 +02:00
Andreja Tonev
0255992c5e Clean up handler code and tests 2023-06-26 14:40:58 +02:00
Marko Budiselić
00226dee24 Improve setup when memgraph is a git submodule (#1038) 2023-06-26 12:27:58 +02:00
Marko Budiselić
546bfc0ede Fix package workflow on ARM (#1040) 2023-06-26 11:19:13 +02:00
Andreja Tonev
00df1dfb1a Bugfix: Allow generic handler in ENTERPRISE OFF 2023-06-23 18:19:18 +02:00
Andreja Tonev
3823321a29 Created a main hanlder class; Added tests for SCH 2023-06-23 17:36:49 +02:00
Gareth Andrew Lloyd
5b1ba10183 Fix IN_MEMORY_ANALYTICAL storage GC (#1025) 2023-06-23 12:50:03 +02:00
Andreja Tonev
b23c5336d6 wip 2023-06-23 11:34:42 +02:00
Vlasta
b25e9968ee Update links inside CSV import tool (#834) 2023-06-22 16:00:22 +02:00
Vlasta
bcd23fe3cb Update CSV import tool error docs 2023-06-22 14:44:46 +02:00
Katarina Supe
68e5610566 Fix replica exception message (#930) 2023-06-22 14:41:59 +02:00
Ante Javor
0ea96663ba Add check for opening snapshots (#966) 2023-06-22 13:29:49 +02:00
Marko Budiselić
da17fe92d6 Update package_docker by adding --pull (#1032)
Each release will pull the latest base image because we want to include any new
security patches.
2023-06-22 12:12:31 +02:00
Andreja Tonev
1344951416 Merge branch 'master' into MG-add-multi-db-support 2023-06-22 11:36:24 +02:00
Marko Budiselić
e73eac77a9 Improve libstdc++ dependency on RPM systems (#863) 2023-06-22 10:14:59 +02:00
Marko Budiselić
d51a61fc5f Update dependencies under environment/os (#862) 2023-06-21 23:14:37 +02:00
andrejtonev
dfea0a8086 Merge pull request #1028 from memgraph/MG-add-multi-db-support-w-recovery
[E952 < T] Support tenant recovery at startup
2023-06-21 20:30:46 +02:00
Josipmrden
b875649270 Add restoring of replication roles upon database startup (#791)
Fix replica node restoration on startup so it is restored as replica and not as main.
2023-06-21 19:08:58 +02:00
Josipmrden
05cc35bf93 Add command NULLIF for identifying nulls in LOAD CSV (#914)
Add NULLIF command which turns all row values corresponding to the string to the nullif character sequence.
2023-06-21 14:50:46 +02:00
Josipmrden
63f8298033 Fix MATCH + LOAD CSV to load CSV only once (#916)
* update profile query to use poolresource
* Optimize update of indexes
* Add ignore empty strings to load csv
* Add operator changes to support handling of nulls
* Store chunks in memory pools ordered
* Use same max block per chunks number
* Remove redundant return statement
* add hacky cached solution
* change map to set
* remove memory
* Add match load csv invalid behaviour commit
* Accept input on LOAD CSV
* Ommit changes not tied to the PR
* Add tests for match + load csv
* Add gqlalchemy installation for e2e tests
* Modify setup script to update packages
* Revert gqlalchemy to 1.3.3
* Revert gqlalchemy to 1.3.3
* Address PR review comments
* Ommit semicolon
---------

Co-authored-by: antoniofilipovic <filipovicantonio1998@gmail.com>
Co-authored-by: János Benjamin Antal <benjamin.antal@memgraph.io>
2023-06-21 11:13:40 +02:00
Andreja Tonev
7442b0f84a Added SyncPtr tests and small improvements 2023-06-21 11:01:55 +02:00
Andreja Tonev
02533abc57 Added dbms auth tests and small improvements 2023-06-21 09:55:59 +02:00
Andreja Tonev
5e7269a174 Added interpreter handler tests and small improvements 2023-06-20 19:47:47 +02:00
Josipmrden
df95775222 Fix init file startup in community edition (#974)
* Fix init file startup in community edition

* Add possibility to build binary without MG_ENTERPRISE

* Added trace spdlog for when init file is not present

* Add gqlalchemy and unit tests

* Add init data files which correspond to the right directory by the github actions
2023-06-20 17:54:50 +02:00
Andreja Tonev
f80ee4b163 SyncPtr Asan fix 2023-06-20 17:34:51 +02:00
Andreja Tonev
60eec1b2a8 Added dbms storage tests and handler.New bugfix 2023-06-20 16:29:03 +02:00
Andreja Tonev
6d526d9f2a Delete storage on drop 2023-06-20 13:59:13 +02:00
Andreja Tonev
2a4179d6ba Merge branch 'MG-add-multi-db-support' into MG-add-multi-db-support-w-recovery 2023-06-20 12:35:02 +02:00
andrejtonev
a288ed1b7c Merge pull request #1017 from memgraph/MG-add-multi-db-support-w-sync
[E952 < T] Sync on delete
2023-06-20 12:29:18 +02:00
Andreja Tonev
01d62fb7d1 Merge branch 'MG-add-multi-db-support' into MG-add-multi-db-support-w-recovery 2023-06-20 11:20:34 +02:00
Andreja Tonev
e5d39c851e Code clean up to pass clang-tidy checks 2023-06-20 10:46:25 +02:00
Josipmrden
eb22edfd35 Add any type to C++ mgp wrapper 2023-06-20 09:33:14 +02:00
Andreja Tonev
143a1caeca Revert "Small improvement: use ref"
This reverts commit 4f8c53b113.
2023-06-20 08:34:39 +02:00
Andreja Tonev
d0f13fa093 Moved multi-db directories into a separate dir 2023-06-19 19:29:50 +02:00
Andreja Tonev
b8876bca41 Tenant recovery 2023-06-19 18:37:33 +02:00
Andreja Tonev
7bc65e9c33 Support SyncPtr without TConfig 2023-06-19 16:03:40 +02:00
Andreja Tonev
0abc20083f Merge branch 'MG-add-multi-db-support' into MG-add-multi-db-support-w-sync 2023-06-19 14:45:45 +02:00
Andreja Tonev
4f8c53b113 Small improvement: use ref 2023-06-19 14:27:09 +02:00
andrejtonev
66a824f657 Merge pull request #1022 from memgraph/MG-add-multi-db-support-auth
[E952 < T] Per-db auth
2023-06-19 14:21:23 +02:00
Andreja Tonev
775e26bf39 Merge branch 'MG-add-multi-db-support' into MG-add-multi-db-support-auth 2023-06-19 11:57:45 +02:00
andrejtonev
20ffe42a7b Merge pull request #1020 from memgraph/MG-add-multi-db-support-w-enterprise
[E952 < T] Moved multi-tenancy under MG_ENTERPRISE
2023-06-19 11:13:20 +02:00
andrejtonev
31a432f368 Merge branch 'MG-add-multi-db-support' into MG-add-multi-db-support-w-enterprise 2023-06-19 11:01:47 +02:00
andrejtonev
2ede947809 Merge pull request #1021 from memgraph/MG-add-multi-db-support-disable-replica
Handle replication in v1
2023-06-19 10:57:03 +02:00
Andreja Tonev
dcb5145525 Updated privileges test 2023-06-18 17:22:40 +02:00
Andreja Tonev
b939305d67 Using auth per DB
Handling authhandler and checker in the dbms
Code cleanup
2023-06-18 15:06:30 +02:00
Andreja Tonev
34b5d56fab Added multi-db privileges
Moved db_name to Storage
2023-06-18 12:18:42 +02:00
Marko Budiselić
1b85d77e9e Add cypherl transform scripts under import/ (#773)
* Add `mglogs2cypherl.sh`
* Add `n2mg_cypherl.sh`
2023-06-18 11:57:46 +02:00
Andreja Tonev
7be7a054dd CREATE/DROP/USE/SHOW DATABASE disabled for replicas 2023-06-16 21:04:08 +02:00
Andreja Tonev
4bb6fb59a1 Moved multi-tenancy under MG_ENTERPRISE 2023-06-16 20:39:01 +02:00
Andreja Tonev
9c4b817d6d Bugfix: skipped a check for debugging 2023-06-16 18:08:35 +02:00
Andreja Tonev
35f4a8b4eb updated e2e transaction test 2023-06-16 17:47:55 +02:00
Andreja Tonev
bc0d7ea324 Audit log and RUN message add DB info 2023-06-16 17:45:12 +02:00
Andreja Tonev
cf904b3c58 Moved (and improved) SyncPtr to utils 2023-06-16 11:21:27 +02:00
Marko Budiselić
cf1a86ed13 Refactor tests/integration/run.sh (#1016) 2023-06-15 23:10:52 +02:00
Andreja Tonev
bd924a475a clang-tidy fix 2023-06-15 19:13:13 +02:00
Andreja Tonev
264253f576 Merge branch 'MG-add-multi-db-support' into MG-add-multi-db-support-w-sync 2023-06-15 18:42:47 +02:00
andrejtonev
86d6063cd4 Merge pull request #1015 from memgraph/add-show-transactions-multidb-test
Add SHOW TRANSACTIONS MultiDB test
2023-06-15 17:49:29 +02:00
Andreja Tonev
9d18baaa97 Bugfix: non-default DBs did not have auth_checker and handler 2023-06-15 17:45:16 +02:00
Andreja Tonev
44080f7837 Better back-compatibility
DeafultDB gets created at root od data directory (as before mt)
These get linked in the new directory (intead of the other way)
2023-06-15 17:12:45 +02:00
Andreja Tonev
6e3bac8f5b Storage symlinks for back-compatibility 2023-06-15 16:07:08 +02:00
Marko Budiselic
bcdef9c68c Add SHOW TRANSACTIONS MultiDB test 2023-06-15 13:36:58 +00:00
Andreja Tonev
a2b92d0bad Better USE DB error handling 2023-06-15 14:13:38 +02:00
Marko Budiselić
30f727703e Merge branch 'master' into MG-add-multi-db-support 2023-06-15 13:20:11 +02:00
Marko Budiselić
7fb3f62703 Upgrade to RocksDB 8.1.1 (#1013) 2023-06-15 11:54:24 +02:00
Marko Budiselić
cb4b71bdbd Update pull_request_template.md 2023-06-14 16:04:35 +02:00
Andreja Tonev
9075688bbf const run_id via SessionContext constructor 2023-06-14 15:51:49 +02:00
Andreja Tonev
d061b498f8 Bugfix: missing run_id
TODO: Place the run_id in the SessionContext constructor
2023-06-14 15:18:51 +02:00
Andreja Tonev
31cb6111f1 GetImpl moved to protected; Bugfix: MG_ENTERPRISE handling 2023-06-14 15:18:37 +02:00
Andreja Tonev
5e63689b9b Bugfix: missing run_id
TODO: Place the run_id in the SessionContext constructor
2023-06-14 14:19:40 +02:00
Andreja Tonev
5b7fbf11ab GetImpl moved to protected; Bugfix: MG_ENTERPRISE handling 2023-06-14 13:55:38 +02:00
Andreja Tonev
b4fe44e327 Merge branch 'MG-add-multi-db-support' into MG-add-multi-db-support-w-sync 2023-06-13 17:19:33 +02:00
Andreja Tonev
64276340cb Removed db_context_ from SessionContextHandler
Not needed after move to shared_ptr (use the ll handlers directly)
2023-06-13 15:34:06 +02:00
Andreja Tonev
b7295ec5db Code clean up and comments 2023-06-13 14:37:42 +02:00
Andreja Tonev
83888648c6 Support synced storage delete 2023-06-13 11:39:48 +02:00
Andreja Tonev
51bbafc140 Bugfix: misplaced reference 2023-06-13 09:12:00 +02:00
andrejtonev
30ec570bb9 Add Bolt v5 support (#938) 2023-06-12 18:55:15 +02:00
Andreja Tonev
9196b36823 Code clean up 2023-06-12 11:49:20 +02:00
Andreja Tonev
abd162c9c0 Bugfix: non-enterprise compiler error 2023-06-12 11:03:15 +02:00
Andreja Tonev
a449e38a7f Bugfix: Handle drop database with shared_ptrs 2023-06-12 10:37:30 +02:00
Andreja Tonev
3ff0e5051c Drop DATABASE cleans up the storage 2023-06-09 10:46:45 +02:00
Andreja Tonev
24cdc0434f Simple session-safety implemented via RWLock 2023-06-08 19:36:58 +02:00
Andreja Tonev
8b168af71b Code cleanup
renamed SessionData to SessionContext
renamed BoltSession to SessionHL
renamed sessions_ to hl_sessions_
Added multi db support to WebSocket session
2023-06-07 14:09:12 +02:00
Andreja Tonev
d13a457155 Bugfix: no synchronization in SDH 2023-06-05 17:04:25 +02:00
Andreja Tonev
7e9e8631ea Singleton SessionDataHandler 2023-06-05 14:56:16 +02:00
Andreja Tonev
f4c228b2f8 Trying to switch during pull, not after 2023-06-05 14:06:55 +02:00
Andreja Tonev
d6e4686898 Better DBMS error handling 2023-06-02 16:08:53 +02:00
Andreja Tonev
be4ddbabda Added SHOW DATABASES support 2023-06-02 15:27:21 +02:00
Andreja Tonev
019b5f34a2 Added DROP DATABASE support
Removed old hacked io
Renamed concepts
2023-06-02 14:53:40 +02:00
Andreja Tonev
dc5b29b59a Added CREATE/USE DATABASE
SessionDataHandler's metadata keeping is a bit wonky
TODO: Remove SDHandler InterpreterContext loop
2023-06-02 12:49:33 +02:00
andrejtonev
ef0d3108f1 Merge pull request #958 from memgraph/MG-952-decouple-sessions
[952 < ] Decouple BoltSession and bolt::Session
2023-06-01 17:31:58 +02:00
Andreja Tonev
007b15eafe Force the creation of the deafult DB 2023-06-01 17:16:35 +02:00
Andreja Tonev
2718fb51eb Fixed bolt test setup 2023-06-01 16:28:38 +02:00
Andreja Tonev
fd92a9e523 Decoupled BoltSession and communication::bolt::Session
BoltSession does not inherit bolt::Session
Instead, bolt::Session takes BoltSession as an implementation param
BoltSession is completely isolated and only implements the needed functions
that are guarded via a concept in bolt::Session
2023-06-01 16:13:40 +02:00
Andreja Tonev
c598420697 Bugfix: MG_ENTERPRISE handling 2023-06-01 11:58:43 +02:00
Andreja Tonev
e192d01d40 First rough multi db implementation
Using metadata to switch dbs (bad beacause it gets applied only after the query)
Focused on the server for now
Not thread safe
2023-06-01 10:32:58 +02:00
Antonio Filipovic
d917c3f0fd Fix slow IN LIST evaluation (#901) 2023-05-29 17:52:20 +02:00
andrejtonev
d842adbed3 Handle user-defined metadata and expose it with SHOW TRANSACTIONS(#945) 2023-05-29 11:40:14 +02:00
Bruno Sačarić
cdfcbc106c Update license date (#941) 2023-05-18 11:42:12 +02:00
Josipmrden
651b6f3a5a Expose system metrics over HTTP Endpoint (#940) 2023-05-18 05:10:57 +00:00
Ante Pušić
0d9bd74a8a Add support for map projection (#892) 2023-05-16 20:05:35 +02:00
andrejtonev
802f8aceda Add data directory status and (un)lock query (#933) 2023-05-16 18:36:04 +02:00
gvolfing
7ddce539fa Add return build type command (#894) 2023-05-16 16:02:03 +02:00
gvolfing
c3e4f81026 Include additional info inside storage mode info query (#883) 2023-05-16 14:25:41 +02:00
Antonio Filipovic
208705f296 Reduce memory consumption on return from python procedures (#932) 2023-05-16 10:33:09 +02:00
Ante Javor
69634a5354 Fix typo in mgbench 2023-05-10 14:02:46 +02:00
Aidar Samerkhanov
b8f282468d Update pulsar client for e2e tests 2023-05-09 12:23:28 +02:00
Ante Javor
ab38161cd2 FIix methodology links (#903)
Co-authored-by: Josip Mrden <josip.mrden@memgraph.io>
2023-05-03 16:37:36 +02:00
János Benjamin Antal
3a5f140c2b Order chunks in utils::Pool to speed up deallocation (#898) 2023-05-02 13:08:20 +02:00
Ante Javor
eead0f79fc Fix missing argument in daily benchmark (#907) 2023-05-02 11:00:44 +02:00
Antonio Filipovic
91017b7f36 Update profile query to use PoolResource for LOAD CSV (#885) 2023-04-26 18:04:13 +02:00
gvolfing
00f8d54249 Parallelize index creation (#882) 2023-04-26 16:28:02 +02:00
János Benjamin Antal
4fcdd52f88 Use correct memory resource (#900) 2023-04-26 10:02:55 +02:00
János Benjamin Antal
6c947947eb Parallelize recovery (#868)
* Parallelize edge recovery

* Load vertex labels and properties parallel

* Add parallel connectivity loading

* Add batches information to snapshot

* Introduce `items_per_batch` and `recovery_thread_count` flags

* Make possible to load snapshots with old version

* Add vertex batches to `RecoveryInfo`

* Extend durability integration tests with v15 test cases

* Add `std::vector` based `InitProperties`

* Use `InitProperties` in snapshot loading
2023-04-25 16:25:25 +02:00
Ante Javor
64fd281b2e Update benchgraph methodology (#899) 2023-04-25 09:45:25 +02:00
János Benjamin Antal
97e250129e Change AccumulateCursor to use utils::pmr::deque (#888)
* Increase performance by eliminating unnecessary `TypedValue` copies
2023-04-24 16:22:22 +02:00
Marko Budiselić
b02b201129 Improve Jepsen setup (#893) 2023-04-23 16:16:49 +02:00
491 changed files with 42986 additions and 15268 deletions

View File

@@ -1,6 +1,7 @@
---
BasedOnStyle: Google
---
Language: Cpp
BasedOnStyle: Google
Standard: "c++20"
UseTab: Never
DerivePointerAlignment: false

View File

@@ -10,5 +10,5 @@
To keep docs changelog up to date, one more thing to do:
- [ ] Write a release note here
- [ ] Write a release note here, including added/changed clauses
- [ ] Tag someone from docs team in the comments

View File

@@ -67,7 +67,7 @@ jobs:
- name: Run mgbench
run: |
cd tests/mgbench
./benchmark.py --num-workers-for-benchmark 12 --export-results benchmark_result.json pokec/medium/*/*
./benchmark.py vendor-native --num-workers-for-benchmark 12 --export-results benchmark_result.json pokec/medium/*/*
- name: Upload mgbench results
run: |

View File

@@ -196,22 +196,7 @@ jobs:
- name: Run integration tests
run: |
cd tests/integration
for name in *; do
if [ ! -d $name ]; then continue; fi
pushd $name >/dev/null
echo "Running: $name"
if [ -x prepare.sh ]; then
./prepare.sh
fi
if [ -x runner.py ]; then
./runner.py
elif [ -x runner.sh ]; then
./runner.sh
fi
echo
popd >/dev/null
done
tests/integration/run.sh
- name: Run cppcheck and clang-format
run: |
@@ -281,12 +266,11 @@ jobs:
- name: Run e2e tests
run: |
# TODO(gitbuda): Setup mgclient and pymgclient properly.
cd tests
./setup.sh
source ve3/bin/activate
cd e2e
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../../libs/mgclient/lib python runner.py --workloads-root-directory .
./run.sh
- name: Run stress test (plain)
run: |
@@ -308,7 +292,6 @@ jobs:
run: |
# Activate toolchain.
source /opt/toolchain-v4/activate
cd build
# create mgconsole
@@ -355,10 +338,8 @@ jobs:
run: |
# Activate toolchain.
source /opt/toolchain-v4/activate
# Initialize dependencies.
./init
# Build only memgraph release binarie.
cd build
cmake -DCMAKE_BUILD_TYPE=release ..
@@ -367,7 +348,7 @@ jobs:
- name: Run Jepsen tests
run: |
cd tests/jepsen
./run.sh test --binary ../../build/memgraph --run-args "test-all --node-configs resources/node-config.edn" --ignore-run-stdout-logs --ignore-run-stderr-logs
./run.sh test-all-individually --binary ../../build/memgraph --ignore-run-stdout-logs --ignore-run-stderr-logs
- name: Save Jepsen report
uses: actions/upload-artifact@v3

View File

@@ -146,22 +146,7 @@ jobs:
- name: Run integration tests
run: |
cd tests/integration
for name in *; do
if [ ! -d $name ]; then continue; fi
pushd $name >/dev/null
echo "Running: $name"
if [ -x prepare.sh ]; then
./prepare.sh
fi
if [ -x runner.py ]; then
./runner.py
elif [ -x runner.sh ]; then
./runner.sh
fi
echo
popd >/dev/null
done
tests/integration/run.sh
- name: Run cppcheck and clang-format
run: |
@@ -280,12 +265,11 @@ jobs:
- name: Run e2e tests
run: |
# TODO(gitbuda): Setup mgclient and pymgclient properly.
cd tests
./setup.sh
source ve3/bin/activate
cd e2e
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../../libs/mgclient/lib python runner.py --workloads-root-directory .
./run.sh
- name: Run stress test (plain)
run: |

View File

@@ -146,22 +146,7 @@ jobs:
- name: Run integration tests
run: |
cd tests/integration
for name in *; do
if [ ! -d $name ]; then continue; fi
pushd $name >/dev/null
echo "Running: $name"
if [ -x prepare.sh ]; then
./prepare.sh
fi
if [ -x runner.py ]; then
./runner.py
elif [ -x runner.sh ]; then
./runner.sh
fi
echo
popd >/dev/null
done
tests/integration/run.sh
- name: Run cppcheck and clang-format
run: |
@@ -279,12 +264,11 @@ jobs:
- name: Run e2e tests
run: |
# TODO(gitbuda): Setup mgclient and pymgclient properly.
cd tests
./setup.sh
source ve3/bin/activate
cd e2e
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../../libs/mgclient/lib python runner.py --workloads-root-directory .
./run.sh
- name: Run stress test (plain)
run: |
@@ -334,10 +318,8 @@ jobs:
run: |
# Activate toolchain.
source /opt/toolchain-v4/activate
# Initialize dependencies.
./init
# Build only memgraph release binary.
cd build
cmake -DCMAKE_BUILD_TYPE=release ..
@@ -346,7 +328,7 @@ jobs:
- name: Run Jepsen tests
run: |
cd tests/jepsen
./run.sh test --binary ../../build/memgraph --run-args "test-all --node-configs resources/node-config.edn" --ignore-run-stdout-logs --ignore-run-stderr-logs
./run.sh test-all-individually --binary ../../build/memgraph --ignore-run-stdout-logs --ignore-run-stderr-logs
- name: Save Jepsen report
uses: actions/upload-artifact@v3

View File

@@ -146,22 +146,7 @@ jobs:
- name: Run integration tests
run: |
cd tests/integration
for name in *; do
if [ ! -d $name ]; then continue; fi
pushd $name >/dev/null
echo "Running: $name"
if [ -x prepare.sh ]; then
./prepare.sh
fi
if [ -x runner.py ]; then
./runner.py
elif [ -x runner.sh ]; then
./runner.sh
fi
echo
popd >/dev/null
done
tests/integration/run.sh
- name: Run cppcheck and clang-format
run: |
@@ -279,12 +264,11 @@ jobs:
- name: Run e2e tests
run: |
# TODO(gitbuda): Setup mgclient and pymgclient properly.
cd tests
./setup.sh
source ve3/bin/activate
cd e2e
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../../libs/mgclient/lib python runner.py --workloads-root-directory .
./run.sh
- name: Run stress test (plain)
run: |

View File

@@ -3,6 +3,7 @@ repos:
rev: v4.4.0
hooks:
- id: check-yaml
args: [--allow-multiple-documents]
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/psf/black

View File

@@ -1,6 +1,7 @@
# MemGraph CMake configuration
cmake_minimum_required(VERSION 3.8)
cmake_minimum_required(VERSION 3.12)
cmake_policy(SET CMP0076 NEW)
# !! IMPORTANT !! run ./project_root/init.sh before cmake command
# to download dependencies
@@ -18,10 +19,12 @@ set_directory_properties(PROPERTIES CLEAN_NO_CUSTOM TRUE)
# during the code coverage process
find_program(CCACHE_FOUND ccache)
option(USE_CCACHE "ccache:" ON)
message(STATUS "CCache: ${USE_CCACHE}")
if(CCACHE_FOUND AND USE_CCACHE)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
message(STATUS "CCache: Used")
else ()
message(STATUS "CCache: Not used")
endif(CCACHE_FOUND AND USE_CCACHE)
# choose a compiler
@@ -37,7 +40,14 @@ endif()
# -----------------------------------------------------------------------------
project(memgraph)
project(memgraph LANGUAGES C CXX)
#TODO: upgrade to cmake 3.24 + CheckIPOSupported
#cmake_policy(SET CMP0138 NEW)
#include(CheckIPOSupported)
#check_ipo_supported()
#set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_Release TRUE)
#set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RelWithDebInfo TRUE)
# Install licenses.
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/licenses/
@@ -143,7 +153,9 @@ endif()
# files used can be seen here:
# https://git-scm.com/book/en/v2/Git-Internals-Git-References
set(git_directory "${CMAKE_SOURCE_DIR}/.git")
if (EXISTS "${git_directory}")
# Check for directory because if the repo is cloned as a git submodule, .git is
# a file and below code doesn't work.
if (IS_DIRECTORY "${git_directory}")
set_property(DIRECTORY APPEND PROPERTY
CMAKE_CONFIGURE_DEPENDS "${git_directory}/HEAD")
file(STRINGS "${git_directory}/HEAD" git_head_data)
@@ -158,7 +170,7 @@ endif()
# setup CMake module path, defines path for include() and find_package()
# https://cmake.org/cmake/help/latest/variable/CMAKE_MODULE_PATH.html
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
# custom function definitions
include(functions)
# -----------------------------------------------------------------------------
@@ -231,6 +243,8 @@ endif()
message(STATUS "CMake build type: ${CMAKE_BUILD_TYPE}")
# -----------------------------------------------------------------------------
add_definitions( -DCMAKE_BUILD_TYPE_NAME="${CMAKE_BUILD_TYPE}")
if (NOT MG_ARCH)
set(MG_ARCH_DESCR "Host architecture to build Memgraph on. Supported values are x86_64, ARM64.")
if (${CMAKE_HOST_SYSTEM_PROCESSOR} MATCHES "aarch64")

View File

@@ -103,6 +103,10 @@ modifications:
value: "true"
override: false
- name: "storage_parallel_index_recovery"
value: "false"
override: true
undocumented:
- "flag_file"
- "also_log_to_stderr"

View File

@@ -23,6 +23,14 @@ def wrap_text(s, initial_indent="# "):
def extract_flags(binary_path):
ret = {}
data = subprocess.run([binary_path, "--help-xml"], stdout=subprocess.PIPE).stdout.decode("utf-8")
# If something is printed out before the help output, it will break the the
# XML parsing -> filter out if something is not XML line because something
# can be logged before gflags output (e.g. during the global objects init).
# This gets called during memgraph build phase to generate default config
# file later installed under /etc/memgraph/memgraph.conf
# NOTE: Don't use \n in the gflags description strings.
# NOTE: Check here if gflags version changes because of the XML format.
data = "\n".join([line for line in data.split("\n") if line.startswith("<")])
root = ET.fromstring(data)
for child in root:
if child.tag == "usage" and child.text.lower().count("warning"):

View File

@@ -202,3 +202,29 @@ for row in csv.reader(stream, delimiter=',', doublequote=True,
For more information about the meaning of the above values, see:
https://docs.python.org/3/library/csv.html#csv.Dialect
## Errors
1. [Skipping duplicate node with ID '{}'. For more details, visit:
memgr.ph/csv-import-tool.](#error-1)
2. [Skipping bad relationship with START_ID '{}'. For more details, visit:
memgr.ph/csv-import-tool.](#error-2)
3. [Skipping bad relationship with END_ID '{}'. For more details, visit:
memgr.ph/csv-import-tool.](#error-3)
## Skipping duplicate node with ID {} {#error-1}
Duplicate nodes are nodes that have an ID that is the same as another node that
was already imported. You can instruct the importer to ignore all duplicate
nodes (instead of raising an error) by using the `--skip-duplicate-nodes` flag.
## Skipping bad relationship with START_ID {} {#error-2}
A node with the id `START_ID` doesn't exist. You can instruct the importer to
ignore all bad relationships (instead of raising an error) that refer to nodes
that don't exist in the node files by using the `--skip-bad-relationships` flag.
## Skipping bad relationship with END_ID {} {#error-3}
A node with the id `END_ID` doesn't exist. You can instruct the importer to
ignore all bad relationships (instead of raising an error) that refer to nodes
that don't exist in the node files by using the `--skip-bad-relationships` flag.

10
environment/README.md Normal file
View File

@@ -0,0 +1,10 @@
# Memgraph Operating Environments
## os
Under the `os` directory, you can find scripts to install all required system
dependencies on operating systems where Memgraph natively builds. The testing
script helps to see how to install all packages (in the case of a new package),
or make any adjustments in the overall system setup. Also, the testing script
helps check if Memgraph runs on a freshly installed operating system (with no
packages installed).

View File

@@ -1,3 +1,6 @@
*.deb
*.deb.*
*.rpm
*.rpm.*
*.tar.gz
*.tar.gz.*

View File

@@ -41,7 +41,7 @@ TOOLCHAIN_RUN_DEPS=(
MEMGRAPH_BUILD_DEPS=(
git # source code control
make # build system
make cmake # build system
wget # for downloading libs
libuuid-devel java-11-openjdk # required by antlr
readline-devel # for memgraph console
@@ -57,9 +57,18 @@ MEMGRAPH_BUILD_DEPS=(
libcurl-devel # mg-requests
rpm-build rpmlint # for RPM package building
doxygen graphviz # source documentation generators
which nodejs golang zip unzip java-11-openjdk-devel # for driver tests
which nodejs golang custom-golang1.18.9 zip unzip java-11-openjdk-devel jdk-17 custom-maven3.9.2 # for driver tests
autoconf # for jemalloc code generation
libtool # for protobuf code generation
cyrus-sasl-devel
)
MEMGRAPH_RUN_DEPS=(
logrotate openssl python3 libseccomp
)
NEW_DEPS=(
wget curl tar gzip
)
list() {
@@ -73,6 +82,18 @@ check() {
local OLD_LD_LIBRARY_PATH=${LD_LIBRARY_PATH:-""}
LD_LIBRARY_PATH=""
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
if [ ! -f "/opt/apache-maven-3.9.2/bin/mvn" ]; then
missing="$pkg $missing"
fi
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
if [ ! -f "/opt/go1.18.9/go/bin/go" ]; then
missing="$pkg $missing"
fi
continue
fi
if [ "$pkg" == "PyYAML" ]; then
if ! python3 -c "import yaml" >/dev/null 2>/dev/null; then
missing="$pkg $missing"
@@ -103,8 +124,27 @@ install() {
else
echo "NOTE: export LANG=en_US.utf8"
fi
yum update -y
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
install_custom_maven "3.9.2"
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
install_custom_golang "1.18.9"
continue
fi
if [ "$pkg" == jdk-17 ]; then
if ! yum list installed jdk-17 >/dev/null 2>/dev/null; then
wget --no-check-certificate -c --header "Cookie: oraclelicense=accept-securebackup-cookie" https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.rpm
rpm -Uvh jdk-17_linux-x64_bin.rpm
# NOTE: Set Java 11 as default.
update-alternatives --set java java-11-openjdk.x86_64
update-alternatives --set javac java-11-openjdk.x86_64
fi
continue
fi
if [ "$pkg" == libipt ]; then
if ! yum list installed libipt >/dev/null 2>/dev/null; then
yum install -y http://repo.okay.com.mx/centos/8/x86_64/release/libipt-1.6.1-8.el8.x86_64.rpm

View File

@@ -39,7 +39,7 @@ TOOLCHAIN_RUN_DEPS=(
)
MEMGRAPH_BUILD_DEPS=(
make pkgconfig # build system
make cmake pkgconfig # build system
curl wget # for downloading libs
libuuid-devel java-11-openjdk # required by antlr
readline-devel # for memgraph console
@@ -56,9 +56,19 @@ MEMGRAPH_BUILD_DEPS=(
sbcl # for custom Lisp C++ preprocessing
rpm-build rpmlint # for RPM package building
doxygen graphviz # source documentation generators
which mono-complete dotnet-sdk-3.1 golang nodejs zip unzip java-11-openjdk-devel # for driver tests
which mono-complete dotnet-sdk-3.1 golang custom-golang1.18.9 # for driver tests
nodejs zip unzip java-11-openjdk-devel jdk-17 custom-maven3.9.2 # for driver tests
autoconf # for jemalloc code generation
libtool # for protobuf code generation
cyrus-sasl-devel
)
MEMGRAPH_RUN_DEPS=(
logrotate openssl python3 libseccomp
)
NEW_DEPS=(
wget curl tar gzip
)
list() {
@@ -68,6 +78,18 @@ list() {
check() {
local missing=""
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
if [ ! -f "/opt/apache-maven-3.9.2/bin/mvn" ]; then
missing="$pkg $missing"
fi
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
if [ ! -f "/opt/go1.18.9/go/bin/go" ]; then
missing="$pkg $missing"
fi
continue
fi
if [ "$pkg" == git ]; then
if ! which "git" >/dev/null; then
missing="git $missing"
@@ -110,7 +132,25 @@ install() {
yum update -y
yum install -y wget python3 python3-pip
yum install -y git
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
install_custom_maven "3.9.2"
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
install_custom_golang "1.18.9"
continue
fi
if [ "$pkg" == jdk-17 ]; then
if ! yum list installed jdk-17 >/dev/null 2>/dev/null; then
wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.rpm
rpm -ivh jdk-17_linux-x64_bin.rpm
update-alternatives --set java java-11-openjdk.x86_64
update-alternatives --set javac java-11-openjdk.x86_64
fi
continue
fi
if [ "$pkg" == libipt ]; then
if ! yum list installed libipt >/dev/null 2>/dev/null; then
yum install -y http://repo.okay.com.mx/centos/8/x86_64/release/libipt-1.6.1-8.el8.x86_64.rpm

View File

@@ -40,7 +40,7 @@ TOOLCHAIN_RUN_DEPS=(
MEMGRAPH_BUILD_DEPS=(
git # source code control
make pkgconf-pkg-config # build system
make cmake pkgconf-pkg-config # build system
wget # for downloading libs
libuuid-devel java-11-openjdk # required by antlr
readline-devel # for memgraph console
@@ -56,10 +56,20 @@ MEMGRAPH_BUILD_DEPS=(
libcurl-devel # mg-requests
rpm-build rpmlint # for RPM package building
doxygen graphviz # source documentation generators
which nodejs golang zip unzip java-11-openjdk-devel # for driver tests
which nodejs golang custom-golang1.18.9 # for driver tests
zip unzip java-11-openjdk-devel java-17-openjdk java-17-openjdk-devel custom-maven3.9.2 # for driver tests
sbcl # for custom Lisp C++ preprocessing
autoconf # for jemalloc code generation
libtool # for protobuf code generation
cyrus-sasl-devel
)
MEMGRAPH_RUN_DEPS=(
logrotate openssl python3 libseccomp
)
NEW_DEPS=(
wget curl tar gzip
)
list() {
@@ -69,6 +79,18 @@ list() {
check() {
local missing=""
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
if [ ! -f "/opt/apache-maven-3.9.2/bin/mvn" ]; then
missing="$pkg $missing"
fi
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
if [ ! -f "/opt/go1.18.9/go/bin/go" ]; then
missing="$pkg $missing"
fi
continue
fi
if [ "$pkg" == "PyYAML" ]; then
if ! python3 -c "import yaml" >/dev/null 2>/dev/null; then
missing="$pkg $missing"
@@ -103,7 +125,16 @@ install() {
fi
yum update -y
yum install -y wget git python3 python3-pip
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
install_custom_maven "3.9.2"
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
install_custom_golang "1.18.9"
continue
fi
# Since there is no support for libipt-devel for CentOS 9 we install
# Fedoras version of same libs, they are the same version but released
# for different OS

View File

@@ -40,7 +40,7 @@ TOOLCHAIN_RUN_DEPS=(
MEMGRAPH_BUILD_DEPS=(
git # source code control
make pkg-config # build system
make cmake pkg-config # build system
curl wget # for downloading libs
uuid-dev default-jre-headless # required by antlr
libreadline-dev # for memgraph console
@@ -53,10 +53,19 @@ MEMGRAPH_BUILD_DEPS=(
libcurl4-openssl-dev # mg-requests
sbcl # for custom Lisp C++ preprocessing
doxygen graphviz # source documentation generators
mono-runtime mono-mcs zip unzip default-jdk-headless # for driver tests
dotnet-sdk-3.1 golang nodejs npm
mono-runtime mono-mcs zip unzip default-jdk-headless oracle-java17-installer custom-maven3.9.2 # for driver tests
dotnet-sdk-3.1 golang custom-golang1.18.9 nodejs npm # for driver tests
autoconf # for jemalloc code generation
libtool # for protobuf code generation
libsasl2-dev
)
MEMGRAPH_RUN_DEPS=(
logrotate openssl python3 libseccomp
)
NEW_DEPS=(
wget curl tar gzip
)
list() {
@@ -64,7 +73,28 @@ list() {
}
check() {
check_all_dpkg "$1"
local missing=""
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
if [ ! -f "/opt/apache-maven-3.9.2/bin/mvn" ]; then
missing="$pkg $missing"
fi
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
if [ ! -f "/opt/go1.18.9/go/bin/go" ]; then
missing="$pkg $missing"
fi
continue
fi
if ! dpkg -s "$pkg" >/dev/null 2>/dev/null; then
missing="$pkg $missing"
fi
done
if [ "$missing" != "" ]; then
echo "MISSING PACKAGES: $missing"
exit 1
fi
}
install() {
@@ -75,8 +105,15 @@ deb http://deb.debian.org/debian/ buster-updates main contrib non-free
deb-src http://deb.debian.org/debian/ buster-updates main contrib non-free
deb http://security.debian.org/debian-security buster/updates main contrib non-free
deb-src http://security.debian.org/debian-security buster/updates main contrib non-free
EOF
apt --allow-releaseinfo-change update
cat >/etc/apt/sources.list.d/java.list << EOF
deb http://ppa.launchpad.net/linuxuprising/java/ubuntu bionic main
deb-src http://ppa.launchpad.net/linuxuprising/java/ubuntu bionic main
EOF
cd "$DIR"
apt install -y gnupg
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys EA8CACC073C3DB2A
apt --allow-releaseinfo-change update
# If GitHub Actions runner is installed, append LANG to the environment.
# Python related tests doesn't work the LANG export.
@@ -85,8 +122,26 @@ EOF
else
echo "NOTE: export LANG=en_US.utf8"
fi
apt install -y wget
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
install_custom_maven "3.9.2"
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
install_custom_golang "1.18.9"
continue
fi
if [ "$pkg" == oracle-java17-installer ]; then
if ! dpkg -s "$pkg" 2>/dev/null >/dev/null; then
echo oracle-java17-installer shared/accepted-oracle-license-v1-3 select true | /usr/bin/debconf-set-selections
echo oracle-java17-installer shared/accepted-oracle-license-v1-3 seen true | /usr/bin/debconf-set-selections
apt install -y "$pkg"
update-alternatives --set java /usr/lib/jvm/java-11-openjdk-amd64/bin/java
update-alternatives --set javac /usr/lib/jvm/java-11-openjdk-amd64/bin/javac
fi
continue
fi
if [ "$pkg" == dotnet-sdk-3.1 ]; then
if ! dpkg -s "$pkg" 2>/dev/null >/dev/null; then
wget -nv https://packages.microsoft.com/config/debian/10/packages-microsoft-prod.deb -O packages-microsoft-prod.deb

View File

@@ -54,10 +54,19 @@ MEMGRAPH_BUILD_DEPS=(
libcurl4-openssl-dev # mg-requests
sbcl # for custom Lisp C++ preprocessing
doxygen graphviz # source documentation generators
mono-runtime mono-mcs zip unzip default-jdk-headless # for driver tests
golang nodejs npm
mono-runtime mono-mcs zip unzip default-jdk-headless openjdk-17-jdk custom-maven3.9.2 # for driver tests
golang custom-golang1.18.9 nodejs npm
autoconf # for jemalloc code generation
libtool # for protobuf code generation
libsasl2-dev
)
MEMGRAPH_RUN_DEPS=(
logrotate openssl python3 libseccomp
)
NEW_DEPS=(
wget curl tar gzip
)
list() {
@@ -65,7 +74,28 @@ list() {
}
check() {
check_all_dpkg "$1"
local missing=""
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
if [ ! -f "/opt/apache-maven-3.9.2/bin/mvn" ]; then
missing="$pkg $missing"
fi
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
if [ ! -f "/opt/go1.18.9/go/bin/go" ]; then
missing="$pkg $missing"
fi
continue
fi
if ! dpkg -s "$pkg" >/dev/null 2>/dev/null; then
missing="$pkg $missing"
fi
done
if [ "$missing" != "" ]; then
echo "MISSING PACKAGES: $missing"
exit 1
fi
}
install() {
@@ -89,7 +119,25 @@ EOF
echo "NOTE: export LANG=en_US.utf8"
fi
apt install -y wget
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
install_custom_maven "3.9.2"
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
install_custom_golang "1.18.9"
continue
fi
if [ "$pkg" == openjdk-17-jdk ]; then
if ! dpkg -s "$pkg" 2>/dev/null >/dev/null; then
apt install -y "$pkg"
# The default Java version should be Java 11
update-alternatives --set java /usr/lib/jvm/java-11-openjdk-arm64/bin/java
update-alternatives --set javac /usr/lib/jvm/java-11-openjdk-arm64/bin/javac
fi
continue
fi
apt install -y "$pkg"
done
}

View File

@@ -41,7 +41,7 @@ TOOLCHAIN_RUN_DEPS=(
MEMGRAPH_BUILD_DEPS=(
git # source code control
make pkg-config # build system
make cmake pkg-config # build system
curl wget # for downloading libs
uuid-dev default-jre-headless # required by antlr
libreadline-dev # for memgraph console
@@ -54,10 +54,19 @@ MEMGRAPH_BUILD_DEPS=(
libcurl4-openssl-dev # mg-requests
sbcl # for custom Lisp C++ preprocessing
doxygen graphviz # source documentation generators
mono-runtime mono-mcs zip unzip default-jdk-headless # for driver tests
dotnet-sdk-3.1 golang nodejs npm
mono-runtime mono-mcs zip unzip default-jdk-headless openjdk-17-jdk custom-maven3.9.2 # for driver tests
dotnet-sdk-3.1 golang custom-golang1.18.9 nodejs npm
autoconf # for jemalloc code generation
libtool # for protobuf code generation
libsasl2-dev
)
MEMGRAPH_RUN_DEPS=(
logrotate openssl python3 libseccomp
)
NEW_DEPS=(
wget curl tar gzip
)
list() {
@@ -65,7 +74,28 @@ list() {
}
check() {
check_all_dpkg "$1"
local missing=""
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
if [ ! -f "/opt/apache-maven-3.9.2/bin/mvn" ]; then
missing="$pkg $missing"
fi
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
if [ ! -f "/opt/go1.18.9/go/bin/go" ]; then
missing="$pkg $missing"
fi
continue
fi
if ! dpkg -s "$pkg" >/dev/null 2>/dev/null; then
missing="$pkg $missing"
fi
done
if [ "$missing" != "" ]; then
echo "MISSING PACKAGES: $missing"
exit 1
fi
}
install() {
@@ -89,7 +119,25 @@ EOF
echo "NOTE: export LANG=en_US.utf8"
fi
apt install -y wget
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
install_custom_maven "3.9.2"
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
install_custom_golang "1.18.9"
continue
fi
if [ "$pkg" == openjdk-17-jdk ]; then
if ! dpkg -s "$pkg" 2>/dev/null >/dev/null; then
apt install -y "$pkg"
# The default Java version should be Java 11
update-alternatives --set java /usr/lib/jvm/java-11-openjdk-amd64/bin/java
update-alternatives --set javac /usr/lib/jvm/java-11-openjdk-amd64/bin/javac
fi
continue
fi
if [ "$pkg" == dotnet-sdk-3.1 ]; then
if ! dpkg -s "$pkg" 2>/dev/null >/dev/null; then
wget -nv https://packages.microsoft.com/config/debian/10/packages-microsoft-prod.deb -O packages-microsoft-prod.deb

View File

@@ -41,7 +41,7 @@ TOOLCHAIN_RUN_DEPS=(
MEMGRAPH_BUILD_DEPS=(
git # source code control
make pkgconf-pkg-config # build system
make cmake pkgconf-pkg-config # build system
wget # for downloading libs
libuuid-devel java-11-openjdk # required by antlr
readline-devel # for memgraph console
@@ -52,10 +52,21 @@ MEMGRAPH_BUILD_DEPS=(
libcurl-devel # mg-requests
rpm-build rpmlint # for RPM package building
doxygen graphviz # source documentation generators
which nodejs golang zip unzip java-11-openjdk-devel # for driver tests
java-11-openjdk-devel java-17-openjdk-devel custom-maven3.9.2 # for driver tests
which zip unzip
nodejs golang custom-golang1.18.9 # for driver tests
sbcl # for custom Lisp C++ preprocessing
autoconf # for jemalloc code generation
libtool # for protobuf code generation
cyrus-sasl-devel
)
MEMGRAPH_RUN_DEPS=(
logrotate openssl python3 libseccomp
)
NEW_DEPS=(
wget curl tar gzip
)
list() {
@@ -64,11 +75,25 @@ list() {
check() {
local missing=""
# On Fedora yum/dnf and python10 use newer glibc which is not compatible
# with ours, so we need to momentarely disable env
local OLD_LD_LIBRARY_PATH=${LD_LIBRARY_PATH}
LD_LIBRARY_PATH=""
if [ -v LD_LIBRARY_PATH ]; then
# On Fedora yum/dnf and python10 use newer glibc which is not compatible
# with ours, so we need to momentarely disable env
local OLD_LD_LIBRARY_PATH=${LD_LIBRARY_PATH}
LD_LIBRARY_PATH=""
fi
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
if [ ! -f "/opt/apache-maven-3.9.2/bin/mvn" ]; then
missing="$pkg $missing"
fi
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
if [ ! -f "/opt/go1.18.9/go/bin/go" ]; then
missing="$pkg $missing"
fi
continue
fi
if ! dnf list installed "$pkg" >/dev/null 2>/dev/null; then
missing="$pkg $missing"
fi
@@ -77,7 +102,10 @@ check() {
echo "MISSING PACKAGES: $missing"
exit 1
fi
LD_LIBRARY_PATH=${OLD_LD_LIBRARY_PATH}
if [ -v OLD_LD_LIBRARY_PATH ]; then
echo "Restoring LD_LIBRARY_PATH..."
LD_LIBRARY_PATH=${OLD_LD_LIBRARY_PATH}
fi
}
install() {
@@ -94,7 +122,25 @@ install() {
echo "NOTE: export LANG=en_US.utf8"
fi
dnf update -y
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
install_custom_maven "3.9.2"
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
install_custom_golang "1.18.9"
continue
fi
if [ "$pkg" == java-17-openjdk-devel ]; then
if ! dnf list installed "$pkg" >/dev/null 2>/dev/null; then
dnf install -y "$pkg"
# The default Java version should be Java 11
update-alternatives --set java java-11-openjdk.x86_64
update-alternatives --set javac java-11-openjdk.x86_64
fi
continue
fi
dnf install -y "$pkg"
done
}

145
environment/os/run.sh Executable file
View File

@@ -0,0 +1,145 @@
#!/bin/bash
set -Eeuo pipefail
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
IFS=' '
# NOTE: docker_image_name could be local image build based on release/package images.
# NOTE: each line has to be under quotes, docker_container_type, script_name and docker_image_name separate with a space.
# "docker_container_type script_name docker_image_name"
OPERATING_SYSTEMS=(
"mgrun amzn-2 amazonlinux:2"
"mgrun centos-7 centos:7"
"mgrun centos-9 dokken/centos-stream-9"
"mgrun debian-10 debian:10"
"mgrun debian-11 debian:11"
"mgrun fedora-36 fedora:36"
"mgrun ubuntu-18.04 ubuntu:18.04"
"mgrun ubuntu-20.04 ubuntu:20.04"
"mgrun ubuntu-22.04 ubuntu:22.04"
# "mgbuild centos-7 package-mgbuild_centos-7"
)
if [ ! "$(docker info)" ]; then
echo "ERROR: Docker is required"
exit 1
fi
print_help () {
echo -e "$0 all\t\t\t\t => start + init all containers in the background"
echo -e "$0 check\t\t\t\t => check all containers"
echo -e "$0 delete\t\t\t\t => stop + remove all containers"
echo -e "$0 copy src_container dst_container => copy build package from src to dst container"
exit 1
}
# NOTE: This is an idempotent operation!
# TODO(gitbuda): Consider making docker_run always delete + start a new container or add a new function.
docker_run () {
cnt_name="$1"
cnt_image="$2"
if [ ! "$(docker ps -q -f name=$cnt_name)" ]; then
if [ "$(docker ps -aq -f status=exited -f name=$cnt_name)" ]; then
echo "Cleanup of the old exited container..."
docker rm $cnt_name
fi
docker run -d --volume "$SCRIPT_DIR/../../:/memgraph" --network host --name "$cnt_name" "$cnt_image" sleep infinity
fi
echo "The $cnt_image container is active under $cnt_name name!"
}
docker_exec () {
cnt_name="$1"
cnt_cmd="$2"
docker exec -it "$cnt_name" bash -c "$cnt_cmd"
}
docker_stop_and_rm () {
cnt_name="$1"
if [ "$(docker ps -q -f name=$cnt_name)" ]; then
docker stop "$1"
if [ "$(docker ps -aq -f status=exited -f name=$cnt_name)" ]; then
docker rm "$1"
fi
fi
}
# TODO(gitbuda): Make the call to `install NEW_DEPS` configurable, the question what else is useful?
start_all () {
for script_docker_pair in "${OPERATING_SYSTEMS[@]}"; do
read -a script_docker <<< "$script_docker_pair"
docker_container_type="${script_docker[0]}"
script_name="${script_docker[1]}"
docker_image="${script_docker[2]}"
docker_name="${docker_container_type}_$script_name"
echo ""
echo "~~~~ OPERATING ON $docker_image as $docker_name..."
docker_run "$docker_name" "$docker_image"
docker_exec "$docker_name" "/memgraph/environment/os/$script_name.sh install NEW_DEPS"
echo "---- DONE EVERYHING FOR $docker_image as $docker_name..."
echo ""
done
}
check_all () {
for script_docker_pair in "${OPERATING_SYSTEMS[@]}"; do
read -a script_docker <<< "$script_docker_pair"
docker_container_type="${script_docker[0]}"
script_name="${script_docker[1]}"
docker_image="${script_docker[2]}"
docker_name="${docker_container_type}_$script_name"
echo ""
echo "~~~~ OPERATING ON $docker_image as $docker_name..."
docker_exec "$docker_name" "/memgraph/environment/os/$script_name.sh check NEW_DEPS"
echo "---- DONE EVERYHING FOR $docker_image as $docker_name..."
echo ""
done
}
delete_all () {
for script_docker_pair in "${OPERATING_SYSTEMS[@]}"; do
read -a script_docker <<< "$script_docker_pair"
docker_container_type="${script_docker[0]}"
script_name="${script_docker[1]}"
docker_image="${script_docker[2]}"
docker_name="${docker_container_type}_$script_name"
docker_stop_and_rm "$docker_name"
echo "~~~~ $docker_image as $docker_name DELETED"
done
}
# TODO(gitbuda): Copy file between containers is a useful util, also delete, + consider copying of a whole folder.
# TODO(gitbuda): Add args: src_cnt dst_cnt abs_path; both file and recursive folder, always delete + copy.
copy_build_package () {
src_container="$1"
dst_container="$2"
src="$src_container:/memgraph/build/output"
tmp_dst="$SCRIPT_DIR/../../build"
mkdir -p "$tmp_dst"
rm -rf "$tmp_dst/output"
dst="$dst_container:/"
docker cp "$src" "$tmp_dst"
docker cp "$tmp_dst/output" "$dst"
}
if [ "$#" -eq 0 ]; then
print_help
else
case $1 in
all)
start_all
;;
check)
check_all
;;
delete)
delete_all
;;
copy) # src_container dst_container
if [ "$#" -ne 3 ]; then
print_help
fi
copy_build_package "$2" "$3"
;;
*)
print_help
;;
esac
fi

View File

@@ -6,6 +6,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
source "$DIR/../util.sh"
check_operating_system "todo-os-name"
check_architecture "todo-arch-name"
TOOLCHAIN_BUILD_DEPS=(
pkg
@@ -19,6 +20,16 @@ MEMGRAPH_BUILD_DEPS=(
pkg
)
MEMGRAPH_RUN_DEPS=(
pkg
)
# NEW_DEPS is useful when you won't to test the installation of a new package.
# During the test you can put here packages like wget curl tar gzip
NEW_DEPS=(
pkg
)
list() {
echo "$1"
}

View File

@@ -41,7 +41,7 @@ TOOLCHAIN_RUN_DEPS=(
MEMGRAPH_BUILD_DEPS=(
git # source code control
make pkg-config # build system
make cmake pkg-config # build system
curl wget # downloading libs
uuid-dev default-jre-headless # required by antlr
libreadline-dev # memgraph console
@@ -53,9 +53,19 @@ MEMGRAPH_BUILD_DEPS=(
libcurl4-openssl-dev # mg-requests
sbcl # custom Lisp C++ preprocessing
doxygen graphviz # source documentation generators
mono-runtime mono-mcs nodejs zip unzip default-jdk-headless # driver tests
mono-runtime mono-mcs nodejs zip unzip default-jdk-headless openjdk-17-jdk-headless custom-maven3.9.2 # driver tests
custom-golang1.18.9 # for driver tests
autoconf # for jemalloc code generation
libtool # for protobuf code generation
libsasl2-dev
)
MEMGRAPH_RUN_DEPS=(
logrotate openssl python3 libseccomp2
)
NEW_DEPS=(
wget curl tar gzip
)
list() {
@@ -63,11 +73,53 @@ list() {
}
check() {
check_all_dpkg "$1"
local missing=""
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
if [ ! -f "/opt/apache-maven-3.9.2/bin/mvn" ]; then
missing="$pkg $missing"
fi
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
if [ ! -f "/opt/go1.18.9/go/bin/go" ]; then
missing="$pkg $missing"
fi
continue
fi
if ! dpkg -s "$pkg" >/dev/null 2>/dev/null; then
missing="$pkg $missing"
fi
done
if [ "$missing" != "" ]; then
echo "MISSING PACKAGES: $missing"
exit 1
fi
}
install() {
apt install -y $1
apt update -y
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
install_custom_maven "3.9.2"
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
install_custom_golang "1.18.9"
continue
fi
if [ "$pkg" == openjdk-17-jdk-headless ]; then
if ! dpkg -s "$pkg" 2>/dev/null >/dev/null; then
apt install -y "$pkg"
# The default Java version should be Java 11
update-alternatives --set java /usr/lib/jvm/java-11-openjdk-amd64/bin/java
update-alternatives --set javac /usr/lib/jvm/java-11-openjdk-amd64/bin/javac
fi
continue
fi
apt install -y "$pkg"
done
}
deps=$2"[*]"

View File

@@ -40,7 +40,7 @@ TOOLCHAIN_RUN_DEPS=(
MEMGRAPH_BUILD_DEPS=(
git # source code control
make pkg-config # build system
make cmake pkg-config # build system
curl wget # for downloading libs
uuid-dev default-jre-headless # required by antlr
libreadline-dev # for memgraph console
@@ -53,10 +53,19 @@ MEMGRAPH_BUILD_DEPS=(
libcurl4-openssl-dev # mg-requests
sbcl # for custom Lisp C++ preprocessing
doxygen graphviz # source documentation generators
mono-runtime mono-mcs zip unzip default-jdk-headless # for driver tests
dotnet-sdk-3.1 golang nodejs npm
mono-runtime mono-mcs zip unzip default-jdk-headless openjdk-17-jdk-headless custom-maven3.9.2 # for driver tests
dotnet-sdk-3.1 golang custom-golang1.18.9 nodejs npm # for driver tests
autoconf # for jemalloc code generation
libtool # for protobuf code generation
libsasl2-dev
)
MEMGRAPH_RUN_DEPS=(
logrotate openssl python3 libseccomp2
)
NEW_DEPS=(
wget curl tar gzip
)
list() {
@@ -64,12 +73,35 @@ list() {
}
check() {
check_all_dpkg "$1"
local missing=""
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
if [ ! -f "/opt/apache-maven-3.9.2/bin/mvn" ]; then
missing="$pkg $missing"
fi
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
if [ ! -f "/opt/go1.18.9/go/bin/go" ]; then
missing="$pkg $missing"
fi
continue
fi
if ! dpkg -s "$pkg" >/dev/null 2>/dev/null; then
missing="$pkg $missing"
fi
done
if [ "$missing" != "" ]; then
echo "MISSING PACKAGES: $missing"
exit 1
fi
}
install() {
cd "$DIR"
apt update
export DEBIAN_FRONTEND=noninteractive
apt update -y
apt install -y wget
# If GitHub Actions runner is installed, append LANG to the environment.
# Python related tests doesn't work the LANG export.
if [ -d "/home/gh/actions-runner" ]; then
@@ -77,8 +109,16 @@ install() {
else
echo "NOTE: export LANG=en_US.utf8"
fi
apt install -y wget
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
install_custom_maven "3.9.2"
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
install_custom_golang "1.18.9"
continue
fi
if [ "$pkg" == dotnet-sdk-3.1 ]; then
if ! dpkg -s dotnet-sdk-3.1 2>/dev/null >/dev/null; then
wget -nv https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
@@ -88,6 +128,15 @@ install() {
fi
continue
fi
if [ "$pkg" == openjdk-17-jdk-headless ]; then
if ! dpkg -s "$pkg" 2>/dev/null >/dev/null; then
apt install -y "$pkg"
# The default Java version should be Java 11
update-alternatives --set java /usr/lib/jvm/java-11-openjdk-amd64/bin/java
update-alternatives --set javac /usr/lib/jvm/java-11-openjdk-amd64/bin/javac
fi
continue
fi
apt install -y "$pkg"
done
}

View File

@@ -40,7 +40,7 @@ TOOLCHAIN_RUN_DEPS=(
MEMGRAPH_BUILD_DEPS=(
git # source code control
make pkg-config # build system
make cmake pkg-config # build system
curl wget # for downloading libs
uuid-dev default-jre-headless # required by antlr
libreadline-dev # for memgraph console
@@ -53,10 +53,19 @@ MEMGRAPH_BUILD_DEPS=(
libcurl4-openssl-dev # mg-requests
sbcl # for custom Lisp C++ preprocessing
doxygen graphviz # source documentation generators
mono-runtime mono-mcs zip unzip default-jdk-headless # for driver tests
dotnet-sdk-6.0 golang nodejs npm
mono-runtime mono-mcs zip unzip default-jdk-headless openjdk-17-jdk-headless custom-maven3.9.2 # for driver tests
dotnet-sdk-6.0 golang custom-golang1.18.9 nodejs npm
autoconf # for jemalloc code generation
libtool # for protobuf code generation
libsasl2-dev
)
MEMGRAPH_RUN_DEPS=(
logrotate openssl python3 libseccomp2
)
NEW_DEPS=(
wget curl tar gzip
)
list() {
@@ -64,7 +73,28 @@ list() {
}
check() {
check_all_dpkg "$1"
local missing=""
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
if [ ! -f "/opt/apache-maven-3.9.2/bin/mvn" ]; then
missing="$pkg $missing"
fi
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
if [ ! -f "/opt/go1.18.9/go/bin/go" ]; then
missing="$pkg $missing"
fi
continue
fi
if ! dpkg -s "$pkg" >/dev/null 2>/dev/null; then
missing="$pkg $missing"
fi
done
if [ "$missing" != "" ]; then
echo "MISSING PACKAGES: $missing"
exit 1
fi
}
install() {
@@ -78,7 +108,16 @@ install() {
echo "NOTE: export LANG=en_US.utf8"
fi
apt install -y wget
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
install_custom_maven "3.9.2"
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
install_custom_golang "1.18.9"
continue
fi
if [ "$pkg" == dotnet-sdk-6.0 ]; then
if ! dpkg -s dotnet-sdk-6.0 2>/dev/null >/dev/null; then
wget -nv https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
@@ -88,6 +127,15 @@ install() {
fi
continue
fi
if [ "$pkg" == openjdk-17-jdk-headless ]; then
if ! dpkg -s "$pkg" 2>/dev/null >/dev/null; then
apt install -y "$pkg"
# The default Java version should be Java 11
update-alternatives --set java /usr/lib/jvm/java-11-openjdk-arm64/bin/java
update-alternatives --set javac /usr/lib/jvm/java-11-openjdk-arm64/bin/javac
fi
continue
fi
apt install -y "$pkg"
done
}

View File

@@ -40,7 +40,7 @@ TOOLCHAIN_RUN_DEPS=(
MEMGRAPH_BUILD_DEPS=(
git # source code control
make pkg-config # build system
make cmake pkg-config # build system
curl wget # for downloading libs
uuid-dev default-jre-headless # required by antlr
libreadline-dev # for memgraph console
@@ -53,10 +53,19 @@ MEMGRAPH_BUILD_DEPS=(
libcurl4-openssl-dev # mg-requests
sbcl # for custom Lisp C++ preprocessing
doxygen graphviz # source documentation generators
mono-runtime mono-mcs zip unzip default-jdk-headless # for driver tests
dotnet-sdk-6.0 golang nodejs npm
mono-runtime mono-mcs zip unzip default-jdk-headless openjdk-17-jdk-headless custom-maven3.9.2 # for driver tests
dotnet-sdk-6.0 golang custom-golang1.18.9 nodejs npm # for driver tests
autoconf # for jemalloc code generation
libtool # for protobuf code generation
libsasl2-dev
)
MEMGRAPH_RUN_DEPS=(
logrotate openssl python3 libseccomp2
)
NEW_DEPS=(
wget curl tar gzip
)
list() {
@@ -64,12 +73,34 @@ list() {
}
check() {
check_all_dpkg "$1"
local missing=""
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
if [ ! -f "/opt/apache-maven-3.9.2/bin/mvn" ]; then
missing="$pkg $missing"
fi
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
if [ ! -f "/opt/go1.18.9/go/bin/go" ]; then
missing="$pkg $missing"
fi
continue
fi
if ! dpkg -s "$pkg" >/dev/null 2>/dev/null; then
missing="$pkg $missing"
fi
done
if [ "$missing" != "" ]; then
echo "MISSING PACKAGES: $missing"
exit 1
fi
}
install() {
cd "$DIR"
apt update
apt update -y
apt install -y wget
# If GitHub Actions runner is installed, append LANG to the environment.
# Python related tests doesn't work the LANG export.
if [ -d "/home/gh/actions-runner" ]; then
@@ -77,8 +108,16 @@ install() {
else
echo "NOTE: export LANG=en_US.utf8"
fi
apt install -y wget
for pkg in $1; do
if [ "$pkg" == custom-maven3.9.2 ]; then
install_custom_maven "3.9.2"
continue
fi
if [ "$pkg" == custom-golang1.18.9 ]; then
install_custom_golang "1.18.9"
continue
fi
if [ "$pkg" == dotnet-sdk-6.0 ]; then
if ! dpkg -s dotnet-sdk-6.0 2>/dev/null >/dev/null; then
wget -nv https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
@@ -88,6 +127,15 @@ install() {
fi
continue
fi
if [ "$pkg" == openjdk-17-jdk-headless ]; then
if ! dpkg -s "$pkg" 2>/dev/null >/dev/null; then
apt install -y "$pkg"
# The default Java version should be Java 11
update-alternatives --set java /usr/lib/jvm/java-11-openjdk-amd64/bin/java
update-alternatives --set javac /usr/lib/jvm/java-11-openjdk-amd64/bin/javac
fi
continue
fi
apt install -y "$pkg"
done
}

View File

@@ -76,3 +76,26 @@ function install_all_apt() {
apt install -y "$pkg"
done
}
function install_custom_golang() {
# NOTE: The official https://go.dev/doc/manage-install doesn't seem to be working.
GOVERSION="$1"
GOINSTALLDIR="/opt/go$GOVERSION"
GOROOT="$GOINSTALLDIR/go" # GOPATH=$HOME/go
if [ ! -f "$GOROOT/bin/go" ]; then
curl -LO https://go.dev/dl/go$GOVERSION.linux-amd64.tar.gz
mkdir -p "$GOINSTALLDIR"
tar -C "$GOINSTALLDIR" -xzf go$GOVERSION.linux-amd64.tar.gz
fi
echo "go $GOVERSION installed under $GOROOT"
}
function install_custom_maven() {
MVNVERSION="$1"
MVNINSTALLDIR="/opt/apache-maven-$MVNVERSION"
if [ ! -f "$MVNINSTALLDIR/bin/mvn" ]; then
curl -LO "https://dlcdn.apache.org/maven/maven-3/$MVNVERSION/binaries/apache-maven-$MVNVERSION-bin.tar.gz"
tar -C "/opt" -xzf "apache-maven-$MVNVERSION-bin.tar.gz"
fi
echo "maven $MVNVERSION installed under $MVNINSTALLDIR"
}

26
import/mglogs2cypherl.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/bin/bash -e
COLOR_ORANGE="\e[38;5;208m"
COLOR_GREEN="\e[38;5;35m"
COLOR_RED="\e[0;31m"
COLOR_NULL="\e[0m"
print_help() {
echo -e "${COLOR_ORANGE}HOW TO RUN:${COLOR_NULL} $0 memgraph_logs_file_path cypherl_output_path"
exit 1
}
if [ "$#" -ne 2 ]; then
print_help
fi
INPUT="$1"
OUTPUT="$2"
if [ ! -f "$INPUT" ]; then
echo -e "${COLOR_RED}ERROR:${COLOR_NULL} memgraph_logs_file_path is not a file!"
print_help
fi
awk -v RS="Run] '" 'NR>1 { print $0 }' < "$INPUT" | sed -e "/^\[/d;" -e "s/'\([^']*\)$/;/g" > "$OUTPUT"
echo -e "${COLOR_GREEN}DONE!${COLOR_NULL} Please find Memgraph compatible cypherl file under $OUTPUT"
echo ""
echo "Import can be done by executing => \`cat $OUTPUT | mgconsole\`"

35
import/n2mg_cypherl.sh Executable file
View File

@@ -0,0 +1,35 @@
#!/bin/bash -e
COLOR_ORANGE="\e[38;5;208m"
COLOR_GREEN="\e[38;5;35m"
COLOR_RED="\e[0;31m"
COLOR_NULL="\e[0m"
print_help() {
echo -e "${COLOR_ORANGE}HOW TO RUN:${COLOR_NULL} $0 input_file_path output_file_path"
exit 1
}
if [ "$#" -ne 2 ]; then
print_help
fi
INPUT="$1"
OUTPUT="$2"
if [ ! -f "$INPUT" ]; then
echo -e "${COLOR_RED}ERROR:${COLOR_NULL} input_file_path is not a file!"
print_help
fi
echo -e "${COLOR_ORANGE}NOTE:${COLOR_NULL} BEGIN and COMMIT are required because variables share the same name (e.g. row)"
echo -e "${COLOR_ORANGE}NOTE:${COLOR_NULL} CONSTRAINTS are just skipped -> ${COLOR_RED}please create consraints manually if needed${COLOR_NULL}"
sed -e 's/^:begin/BEGIN/g; s/^BEGIN$/BEGIN;/g;' \
-e 's/^:commit/COMMIT/g; s/^COMMIT$/COMMIT;/g;' \
-e '/^CALL/d; /^SCHEMA AWAIT/d;' \
-e 's/CREATE RANGE INDEX FOR (n:/CREATE INDEX ON :/g;' \
-e 's/) ON (n./(/g;' \
-e '/^CREATE CONSTRAINT/d; /^DROP CONSTRAINT/d;' "$INPUT" > "$OUTPUT"
echo ""
echo -e "${COLOR_GREEN}DONE!${COLOR_NULL} Please find Memgraph compatible cypherl|.cypher file under $OUTPUT"
echo ""
echo "Please import data by executing => \`cat $OUTPUT | mgconsole\`"

View File

@@ -677,6 +677,16 @@ inline mgp_proc *module_add_write_procedure(mgp_module *module, const char *name
return MgInvoke<mgp_proc *>(mgp_module_add_write_procedure, module, name, cb);
}
inline mgp_proc *module_add_batch_read_procedure(mgp_module *module, const char *name, mgp_proc_cb cb,
mgp_proc_initializer initializer, mgp_proc_cleanup cleanup) {
return MgInvoke<mgp_proc *>(mgp_module_add_batch_read_procedure, module, name, cb, initializer, cleanup);
}
inline mgp_proc *module_add_batch_write_procedure(mgp_module *module, const char *name, mgp_proc_cb cb,
mgp_proc_initializer initializer, mgp_proc_cleanup cleanup) {
return MgInvoke<mgp_proc *>(mgp_module_add_batch_write_procedure, module, name, cb, initializer, cleanup);
}
inline void proc_add_arg(mgp_proc *proc, const char *name, mgp_type *type) {
MgInvokeVoid(mgp_proc_add_arg, proc, name, type);
}

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -1318,6 +1318,13 @@ MGP_ENUM_CLASS mgp_log_level{
/// to allocate global resources.
typedef void (*mgp_proc_cb)(struct mgp_list *, struct mgp_graph *, struct mgp_result *, struct mgp_memory *);
/// Cleanup for a query module read procedure. Can't be invoked through OpenCypher. Cleans batched stream.
typedef void (*mgp_proc_cleanup)();
/// Initializer for a query module batched read procedure. Can't be invoked through OpenCypher. Initializes batched
/// stream.
typedef void (*mgp_proc_initializer)(struct mgp_list *, struct mgp_graph *, struct mgp_memory *);
/// Register a read-only procedure to a module.
///
/// The `name` must be a sequence of digits, underscores, lowercase and
@@ -1342,6 +1349,30 @@ enum mgp_error mgp_module_add_read_procedure(struct mgp_module *module, const ch
enum mgp_error mgp_module_add_write_procedure(struct mgp_module *module, const char *name, mgp_proc_cb cb,
struct mgp_proc **result);
/// Register a readable batched procedure to a module.
///
/// The `name` must be a valid identifier, following the same rules as the
/// procedure`name` in mgp_module_add_read_procedure.
///
/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for mgp_proc.
/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid procedure name.
/// RETURN mgp_error::MGP_ERROR_LOGIC_ERROR if a procedure with the same name was already registered.
enum mgp_error mgp_module_add_batch_read_procedure(struct mgp_module *module, const char *name, mgp_proc_cb cb,
mgp_proc_initializer initializer, mgp_proc_cleanup cleanup,
struct mgp_proc **result);
/// Register a writeable batched procedure to a module.
///
/// The `name` must be a valid identifier, following the same rules as the
/// procedure`name` in mgp_module_add_read_procedure.
///
/// Return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE if unable to allocate memory for mgp_proc.
/// Return mgp_error::MGP_ERROR_INVALID_ARGUMENT if `name` is not a valid procedure name.
/// RETURN mgp_error::MGP_ERROR_LOGIC_ERROR if a procedure with the same name was already registered.
enum mgp_error mgp_module_add_batch_write_procedure(struct mgp_module *module, const char *name, mgp_proc_cb cb,
mgp_proc_initializer initializer, mgp_proc_cleanup cleanup,
struct mgp_proc **result);
/// Add a required argument to a procedure.
///
/// The order of adding arguments will correspond to the order the procedure

View File

@@ -946,6 +946,7 @@ class Duration {
/* #region Value */
enum class Type : uint8_t {
Null,
Any,
Bool,
Int,
Double,
@@ -1318,6 +1319,20 @@ inline void AddProcedure(mgp_proc_cb callback, std::string_view name, ProcedureT
std::vector<Parameter> parameters, std::vector<Return> returns, mgp_module *module,
mgp_memory *memory);
/// @brief Adds a batch procedure to the query module.
/// @param callback - procedure callback
/// @param initializer - procedure initializer
/// @param cleanup - procedure cleanup
/// @param name - procedure name
/// @param proc_type - procedure type (read/write)
/// @param parameters - procedure parameters
/// @param returns - procedure return values
/// @param module - the query module that the procedure is added to
/// @param memory - access to memory
inline void AddBatchProcedure(mgp_proc_cb callback, mgp_proc_initializer initializer, mgp_proc_cleanup cleanup,
std::string_view name, ProcedureType proc_type, std::vector<Parameter> parameters,
std::vector<Return> returns, mgp_module *module, mgp_memory *memory);
/// @brief Adds a function to the query module.
/// @param callback - function callback
/// @param name - function name
@@ -1491,6 +1506,8 @@ inline bool ValuesEqual(mgp_value *value1, mgp_value *value2) {
/// @brief Converts C++ API types to their MGP API equivalents.
inline mgp_type *ToMGPType(Type type) {
switch (type) {
case Type::Any:
return mgp::type_any();
case Type::Bool:
return mgp::type_bool();
case Type::Int:
@@ -2201,7 +2218,14 @@ inline bool Map::Empty() const { return Size() == 0; }
inline const Value Map::operator[](std::string_view key) const { return Value(mgp::map_at(ptr_, key.data())); }
inline const Value Map::At(std::string_view key) const { return Value(mgp::map_at(ptr_, key.data())); }
inline const Value Map::At(std::string_view key) const {
auto *ptr = mgp::map_at(ptr_, key.data());
if (ptr) {
return Value(ptr);
}
return Value();
}
inline Map::Iterator::Iterator(mgp_map_items_iterator *map_items_iterator) : map_items_iterator_(map_items_iterator) {
if (map_items_iterator_ == nullptr) return;
@@ -3427,14 +3451,12 @@ inline mgp_type *Return::GetMGPType() const {
return util::ToMGPType(type_);
}
void AddProcedure(mgp_proc_cb callback, std::string_view name, ProcedureType proc_type,
std::vector<Parameter> parameters, std::vector<Return> returns, mgp_module *module,
mgp_memory *memory) {
auto proc = (proc_type == ProcedureType::Read) ? mgp::module_add_read_procedure(module, name.data(), callback)
: mgp::module_add_write_procedure(module, name.data(), callback);
// do not enter
namespace detail {
inline void AddParamsReturnsToProc(mgp_proc *proc, std::vector<Parameter> &parameters,
const std::vector<Return> &returns) {
for (const auto &parameter : parameters) {
auto parameter_name = parameter.name.data();
const auto *parameter_name = parameter.name.data();
if (!parameter.optional) {
mgp::proc_add_arg(proc, parameter_name, parameter.GetMGPType());
} else {
@@ -3443,18 +3465,35 @@ void AddProcedure(mgp_proc_cb callback, std::string_view name, ProcedureType pro
}
for (const auto return_ : returns) {
auto return_name = return_.name.data();
const auto *return_name = return_.name.data();
mgp::proc_add_result(proc, return_name, return_.GetMGPType());
}
}
} // namespace detail
void AddProcedure(mgp_proc_cb callback, std::string_view name, ProcedureType proc_type,
std::vector<Parameter> parameters, std::vector<Return> returns, mgp_module *module,
mgp_memory *memory) {
auto *proc = (proc_type == ProcedureType::Read) ? mgp::module_add_read_procedure(module, name.data(), callback)
: mgp::module_add_write_procedure(module, name.data(), callback);
detail::AddParamsReturnsToProc(proc, parameters, returns);
}
void AddBatchProcedure(mgp_proc_cb callback, mgp_proc_initializer initializer, mgp_proc_cleanup cleanup,
std::string_view name, ProcedureType proc_type, std::vector<Parameter> parameters,
std::vector<Return> returns, mgp_module *module, mgp_memory *memory) {
auto *proc = (proc_type == ProcedureType::Read)
? mgp::module_add_batch_read_procedure(module, name.data(), callback, initializer, cleanup)
: mgp::module_add_batch_write_procedure(module, name.data(), callback, initializer, cleanup);
detail::AddParamsReturnsToProc(proc, parameters, returns);
}
void AddFunction(mgp_func_cb callback, std::string_view name, std::vector<Parameter> parameters, mgp_module *module,
mgp_memory *memory) {
auto func = mgp::module_add_function(module, name.data(), callback);
auto *func = mgp::module_add_function(module, name.data(), callback);
for (const auto &parameter : parameters) {
auto parameter_name = parameter.name.data();
const auto *parameter_name = parameter.name.data();
if (!parameter.optional) {
mgp::func_add_arg(func, parameter_name, parameter.GetMGPType());

View File

@@ -1402,6 +1402,13 @@ class UnsupportedTypingError(Exception):
super().__init__("Unsupported typing annotation '{}'".format(type_))
class UnequalTypesError(Exception):
"""Signals a typing annotation is not equal between types"""
def __init__(self, type1_: typing.Any, type2_: typing.Any):
super().__init__(f"Unequal typing annotation '{type1_}' and '{type2_}'")
def _typing_to_cypher_type(type_):
"""Convert typing annotation to a _mgp.CypherType instance."""
simple_types = {
@@ -1514,6 +1521,72 @@ def _typing_to_cypher_type(type_):
return parse_typing(str(type_))
def _is_typing_same(type1_, type2_):
"""Convert typing annotation to a _mgp.CypherType instance."""
simple_types = {
typing.Any: 1,
object: 2,
list: 3,
Any: 4,
bool: 5,
str: 6,
int: 7,
float: 8,
Number: 9,
Map: 10,
Vertex: 11,
Edge: 12,
Path: 13,
Date: 14,
LocalTime: 15,
LocalDateTime: 16,
Duration: 17,
}
try:
return simple_types[type1_] == simple_types[type2_]
except KeyError:
pass
if sys.version_info < (3, 8):
# skip type checks
return True
complex_type1 = typing.get_origin(type1_)
type_args1 = typing.get_args(type2_)
complex_type2 = typing.get_origin(type1_)
type_args2 = typing.get_args(type2_)
if complex_type2 != complex_type1:
raise UnequalTypesError(type1_, type2_)
if complex_type1 == typing.Union:
contains_none_arg1 = type(None) in type_args1
contains_none_arg2 = type(None) in type_args2
if contains_none_arg1 != contains_none_arg2:
raise UnequalTypesError(type1_, type2_)
if contains_none_arg1:
types1 = tuple(t for t in type_args1 if t is not type(None)) # noqa E721
types2 = tuple(t for t in type_args2 if t is not type(None)) # noqa E721
if len(types1) != len(types2):
raise UnequalTypesError(types1, types2)
if len(types1) == 1:
(type_arg1,) = types1
(type_arg2,) = types2
else:
type_arg1 = typing.Union.__getitem__(types1)
type_arg2 = typing.Union.__getitem__(types2)
return _is_typing_same(type_arg1, type_arg2)
elif complex_type1 == list:
(type_arg1,) = type_args1
(type_arg2,) = type_args2
return _is_typing_same(type_arg1, type_arg2)
# skip type checks
return True
# Procedure registration
@@ -1673,6 +1746,92 @@ def write_proc(func: typing.Callable[..., Record]):
return _register_proc(func, True)
def _register_batch_proc(
func: typing.Callable[..., Record], initializer: typing.Callable, cleanup: typing.Callable, is_write: bool
):
raise_if_does_not_meet_requirements(func)
register_func = _mgp.Module.add_batch_write_procedure if is_write else _mgp.Module.add_batch_read_procedure
func_sig = inspect.signature(func)
func_params = tuple(func_sig.parameters.values())
initializer_sig = inspect.signature(initializer)
initializer_params = tuple(initializer_sig.parameters.values())
assert (
func_params and initializer_params or not func_params and not initializer_params
), "Both function params and initializer params must exist or not exist"
assert len(func_params) == len(initializer_params), "Number of params must be same"
assert initializer_sig.return_annotation is initializer_sig.empty, "Initializer can't return anything"
if func_params and func_params[0].annotation is ProcCtx:
assert (
initializer_params and initializer_params[0].annotation is ProcCtx
), "Initializer must have mgp.ProcCtx as first parameter"
@wraps(func)
def wrapper_func(graph, args):
return func(ProcCtx(graph), *args)
@wraps(initializer)
def wrapper_initializer(graph, args):
return initializer(ProcCtx(graph), *args)
func_params = func_params[1:]
initializer_params = initializer_params[1:]
mgp_proc = register_func(_mgp._MODULE, wrapper_func, wrapper_initializer, cleanup)
else:
@wraps(func)
def wrapper_func(graph, args):
return func(*args)
@wraps(initializer)
def wrapper_initializer(graph, args):
return initializer(*args)
mgp_proc = register_func(_mgp._MODULE, wrapper_func, wrapper_initializer, cleanup)
for func_param, initializer_param in zip(func_params, initializer_params):
func_param_name = func_param.name
func_param_type_ = func_param.annotation
if func_param_type_ is func_param.empty:
func_param_type_ = object
initializer_param_type_ = initializer_param.annotation
if initializer_param.annotation is initializer_param.empty:
initializer_param_type_ = object
assert _is_typing_same(
func_param_type_, initializer_param_type_
), "Types of initializer and function must be same"
func_cypher_type = _typing_to_cypher_type(func_param_type_)
if func_param.default is func_param.empty:
mgp_proc.add_arg(func_param_name, func_cypher_type)
else:
mgp_proc.add_opt_arg(func_param_name, func_cypher_type, func_param.default)
if func_sig.return_annotation is not func_sig.empty:
record = func_sig.return_annotation
if not isinstance(record, Record):
raise TypeError("Expected '{}' to return 'mgp.Record', got '{}'".format(func.__name__, type(record)))
for name, type_ in record.fields.items():
if isinstance(type_, Deprecated):
cypher_type = _typing_to_cypher_type(type_.field_type)
mgp_proc.add_deprecated_result(name, cypher_type)
else:
mgp_proc.add_result(name, _typing_to_cypher_type(type_))
return func
def add_batch_write_proc(func: typing.Callable[..., Record], initializer: typing.Callable, cleanup: typing.Callable):
return _register_batch_proc(func, initializer, cleanup, True)
def add_batch_read_proc(func: typing.Callable[..., Record], initializer: typing.Callable, cleanup: typing.Callable):
return _register_batch_proc(func, initializer, cleanup, False)
class InvalidMessageError(Exception):
"""
Signals using a message instance outside of the registered transformation.

18
init
View File

@@ -77,7 +77,9 @@ fi
# Fix for centos 7 during release
if [ "${DISTRO}" = "centos-7" ] || [ "${DISTRO}" = "debian-11" ] || [ "${DISTRO}" = "amzn-2" ]; then
python3 -m pip uninstall -y virtualenv
if python3 -m pip show virtualenv >/dev/null 2>/dev/null; then
python3 -m pip uninstall -y virtualenv
fi
python3 -m pip install virtualenv
fi
@@ -100,11 +102,15 @@ setup_virtualenv tests/integration/ldap
echo "Done installing dependencies for Memgraph"
echo "Linking git hooks"
for hook in $(find $DIR/.githooks -type f -printf "%f\n"); do
ln -s -f "$DIR/.githooks/$hook" "$DIR/.git/hooks/$hook"
echo "Added $hook hook"
done;
echo "Linking git hooks OR skip if .git folder is not there"
if [ -d "$DIR/.git" ]; then
for hook in $(find $DIR/.githooks -type f -printf "%f\n"); do
ln -s -f "$DIR/.githooks/$hook" "$DIR/.git/hooks/$hook"
echo "Added $hook hook"
done;
else
echo "WARNING: .git folder not present, skip adding hooks"
fi
# Install precommit hook except on old operating systems because we don't
# develop on them -> pre-commit hook not required -> we can use latest

1
libs/.gitignore vendored
View File

@@ -6,3 +6,4 @@
!__main.cpp
!pulsar.patch
!antlr4.10.1.patch
!rocksdb8.1.1.patch

View File

@@ -12,6 +12,7 @@ find_package(Boost 1.78 REQUIRED)
find_package(BZip2 1.0.6 REQUIRED)
find_package(Threads REQUIRED)
set(GFLAGS_NOTHREADS OFF)
# NOTE: config/generate.py depends on the gflags help XML format.
find_package(gflags REQUIRED)
find_package(fmt 8.0.1)
find_package(Jemalloc REQUIRED)
@@ -28,6 +29,7 @@ function(import_header_library name include_dir)
set(${_upper_name}_INCLUDE_DIR ${include_dir} CACHE FILEPATH
"Path to ${name} include directory" FORCE)
mark_as_advanced(${_upper_name}_INCLUDE_DIR)
add_library(lib::${name} ALIAS ${name})
endfunction(import_header_library)
function(import_library name type location include_dir)
@@ -256,3 +258,6 @@ import_external_library(librdtsc STATIC
${CMAKE_CURRENT_SOURCE_DIR}/librdtsc/include
CMAKE_ARGS ${MG_LIBRDTSC_CMAKE_ARGS}
BUILD_COMMAND $(MAKE) rdtsc)
# setup ctre
import_header_library(ctre ${CMAKE_CURRENT_SOURCE_DIR})

13
libs/rocksdb8.1.1.patch Normal file
View File

@@ -0,0 +1,13 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 598c728..816c705 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1242,7 +1242,7 @@ if(NOT WIN32 OR ROCKSDB_INSTALL_ON_WINDOWS)
if(ROCKSDB_BUILD_SHARED)
install(
- TARGETS ${ROCKSDB_SHARED_LIB}
+ TARGETS ${ROCKSDB_SHARED_LIB} OPTIONAL
EXPORT RocksDBTargets
COMPONENT runtime
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"

View File

@@ -122,6 +122,7 @@ declare -A primary_urls=(
["protobuf"]="http://$local_cache_host/git/protobuf.git"
["pulsar"]="http://$local_cache_host/git/pulsar.git"
["librdtsc"]="http://$local_cache_host/git/librdtsc.git"
["ctre"]="http://$local_cache_host/file/hanickadot/compile-time-regular-expressions/v3.7.2/single-header/ctre.hpp"
)
# The goal of secondary urls is to have links to the "source of truth" of
@@ -147,6 +148,7 @@ declare -A secondary_urls=(
["protobuf"]="https://github.com/protocolbuffers/protobuf.git"
["pulsar"]="https://github.com/apache/pulsar.git"
["librdtsc"]="https://github.com/gabrieleara/librdtsc.git"
["ctre"]="https://raw.githubusercontent.com/hanickadot/compile-time-regular-expressions/v3.7.2/single-header/ctre.hpp"
)
# antlr
@@ -192,10 +194,10 @@ cd json
file_get_try_double "${primary_urls[nlohmann]}" "${secondary_urls[nlohmann]}"
cd ..
rocksdb_tag="v6.14.6" # (2020-10-14)
rocksdb_tag="v8.1.1" # (2023-04-21)
repo_clone_try_double "${primary_urls[rocksdb]}" "${secondary_urls[rocksdb]}" "rocksdb" "$rocksdb_tag" true
pushd rocksdb
git apply ../rocksdb.patch
git apply ../rocksdb8.1.1.patch
popd
# mgclient
@@ -238,3 +240,9 @@ repo_clone_try_double "${primary_urls[librdtsc]}" "${secondary_urls[librdtsc]}"
pushd librdtsc
git apply ../librdtsc.patch
popd
#ctre
mkdir -p ctre
cd ctre
file_get_try_double "${primary_urls[ctre]}" "${secondary_urls[ctre]}"
cd ..

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

View File

@@ -48,7 +48,7 @@ set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}
# Add `openssl` package to dependencies list. Used to generate SSL certificates.
# We also depend on `python3` because we embed it in Memgraph.
set(CPACK_DEBIAN_PACKAGE_DEPENDS "openssl (>= 1.1.0), python3 (>= 3.5.0), libstdc++6")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "openssl (>= 1.1.0), python3 (>= 3.5.0)")
# Setting arhitecture extension for rpm packages
set(MG_ARCH_EXTENSION_RPM "noarch")
@@ -69,6 +69,12 @@ set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION
set(CPACK_RPM_PACKAGE_REQUIRES_PRE "shadow-utils")
set(CPACK_RPM_USER_BINARY_SPECFILE "${CMAKE_CURRENT_SOURCE_DIR}/rpm/memgraph.spec.in")
set(CPACK_RPM_PACKAGE_LICENSE "Memgraph License")
# CPACK deduces dependency to libstdc++ which:
# * can't be easily installed on Centos 7 (the one from the toolchain,
# only required to avoid printing issue within query modules)
# * it causes issues with glibcxx 2.4
# `if(DISTRO STREQUAL "Amazon Linux" AND DISTRO_VERSION STREQUAL "2")`
set(CPACK_RPM_PACKAGE_AUTOREQ " no")
# Description formatting is important, no line must be greater than 80 characters.
set(CPACK_RPM_PACKAGE_DESCRIPTION "Contains Memgraph, the graph database.
@@ -77,13 +83,7 @@ the next generation of applications driver by real-time connected data.")
# Add `openssl` package to dependencies list. Used to generate SSL certificates.
# We also depend on `python3` because we embed it in Memgraph.
set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.0.0, curl >= 7.29.0, python3 >= 3.5.0, libstdc++ >= 3.4.29, logrotate")
# If amzn-2
if(DISTRO STREQUAL "Amazon Linux" AND DISTRO_VERSION STREQUAL "2")
# It causes issues with glibcxx 2.4
set(CPACK_RPM_PACKAGE_AUTOREQ " no")
endif()
set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.0.0, curl >= 7.29.0, python3 >= 3.5.0, logrotate")
# All variables must be set before including.
include(CPack)

View File

@@ -55,7 +55,8 @@ image_name="memgraph:${version}"
image_package_name="memgraph-${version}-docker.tar.gz"
# Build docker image.
docker build -t ${image_name} ${tag_latest} -f ${dockerfile_path} \
# NOTE: --pull is here to always pull that latest base image because of security patches.
docker build --pull -t ${image_name} ${tag_latest} -f ${dockerfile_path} \
--build-arg BINARY_NAME=${package_name} \
--build-arg EXTENSION=${extension} \
--build-arg TARGETARCH="" .

View File

@@ -17,7 +17,8 @@ ACTIVATE_TOOLCHAIN="source /opt/${TOOLCHAIN_VERSION}/activate"
HOST_OUTPUT_DIR="$PROJECT_ROOT/build/output"
print_help () {
echo "$0 init|package {os} [--for-docker|--for-platform]|docker|test"
# TODO(gitbuda): Update the release/package/run.sh help
echo "$0 init|package|docker|test {os} [--for-docker|--for-platform]"
echo ""
echo " OSs: ${SUPPORTED_OS[*]}"
exit 1
@@ -35,7 +36,7 @@ make_package () {
package_command=" cpack -G RPM --config ../CPackConfig.cmake && rpmlint --file='../../release/rpm/rpmlintrc' memgraph*.rpm "
fi
if [[ "$os" =~ ^"debian".* ]]; then
docker exec "$build_container" bash -c "apt update"
docker exec "$build_container" bash -c "apt --allow-releaseinfo-change -y update"
package_command=" cpack -G DEB --config ../CPackConfig.cmake "
fi
if [[ "$os" =~ ^"ubuntu".* ]]; then
@@ -64,6 +65,7 @@ make_package () {
git fetch origin master:master
fi
docker exec "$build_container" mkdir -p /memgraph
# TODO(gitbuda): Revisit copying the whole repo -> makese sense under CI.
docker cp "$PROJECT_ROOT/." "$build_container:/memgraph/"
container_build_dir="/memgraph/build"
@@ -74,6 +76,8 @@ make_package () {
# environment/os/{os}.sh does not come within the toolchain package. When
# migrating to the next version of toolchain do that, and remove the
# TOOLCHAIN_RUN_DEPS installation from here.
# TODO(gitbuda): On the other side, having this here allows updating deps
# wihout reruning the build containers.
echo "Installing dependencies using '/memgraph/environment/os/$os.sh' script..."
docker exec "$build_container" bash -c "/memgraph/environment/os/$os.sh install TOOLCHAIN_RUN_DEPS"
docker exec "$build_container" bash -c "/memgraph/environment/os/$os.sh install MEMGRAPH_BUILD_DEPS"
@@ -83,6 +87,7 @@ make_package () {
docker exec "$build_container" bash -c "cd /memgraph && git config --global --add safe.directory '*'"
docker exec "$build_container" bash -c "cd /memgraph && $ACTIVATE_TOOLCHAIN && ./init"
docker exec "$build_container" bash -c "cd $container_build_dir && rm -rf ./*"
# TODO(gitbuda): cmake fails locally if remote is clone via ssh because of the key -> FIX
if [[ "$os" =~ "-arm" ]]; then
docker exec "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && cmake -DCMAKE_BUILD_TYPE=release -DMG_ARCH="ARM64" $telemetry_id_override_flag .."
else
@@ -108,8 +113,13 @@ make_package () {
case "$1" in
init)
cd "$SCRIPT_DIR"
docker-compose build --build-arg TOOLCHAIN_VERSION="${TOOLCHAIN_VERSION}"
docker-compose up -d
if ! which "docker-compose" >/dev/null; then
docker_compose_cmd="docker compose"
else
docker_compose_cmd="docker-compose"
fi
$docker_compose_cmd build --build-arg TOOLCHAIN_VERSION="${TOOLCHAIN_VERSION}"
$docker_compose_cmd up -d
;;
docker)

View File

@@ -1,6 +1,7 @@
# CMake configuration for the main memgraph library and executable
# add memgraph sub libraries, ordered by dependency
add_subdirectory(csv)
add_subdirectory(utils)
add_subdirectory(requests)
add_subdirectory(io)
@@ -16,10 +17,8 @@ add_subdirectory(slk)
add_subdirectory(rpc)
add_subdirectory(license)
add_subdirectory(auth)
if(MG_ENTERPRISE)
add_subdirectory(audit)
endif()
add_subdirectory(audit)
add_subdirectory(dbms)
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
@@ -36,12 +35,7 @@ set(mg_single_node_v2_sources
)
set(mg_single_node_v2_libs stdc++fs Threads::Threads
mg-telemetry 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)
endif()
mg-telemetry mg-query mg-communication mg-memory mg-utils mg-auth mg-license mg-settings mg-glue mg-audit)
# memgraph main executable
add_executable(memgraph ${mg_single_node_v2_sources})

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
// License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
@@ -116,12 +116,12 @@ Log::~Log() {
}
void Log::Record(const std::string &address, const std::string &username, const std::string &query,
const storage::PropertyValue &params) {
const storage::PropertyValue &params, const std::string &db) {
if (!started_.load(std::memory_order_relaxed)) return;
auto timestamp =
std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch())
.count();
buffer_->emplace(Item{timestamp, address, username, query, params});
buffer_->emplace(Item{timestamp, address, username, query, params, db});
}
void Log::ReopenLog() {
@@ -136,8 +136,8 @@ void Log::Flush() {
for (uint64_t i = 0; i < buffer_size_; ++i) {
auto item = buffer_->pop();
if (!item) break;
log_.Write(fmt::format("{}.{:06d},{},{},{},{}\n", item->timestamp / 1000000, item->timestamp % 1000000,
item->address, item->username, utils::Escape(item->query),
log_.Write(fmt::format("{}.{:06d},{},{},{},{},{}\n", item->timestamp / 1000000, item->timestamp % 1000000,
item->address, item->username, item->db, utils::Escape(item->query),
utils::Escape(PropertyValueToJson(item->params).dump())));
}
log_.Sync();

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
// License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
@@ -32,6 +32,7 @@ class Log {
std::string username;
std::string query;
storage::PropertyValue params;
std::string db;
};
public:
@@ -51,7 +52,7 @@ class Log {
/// Adds an entry to the audit log. Thread-safe.
void Record(const std::string &address, const std::string &username, const std::string &query,
const storage::PropertyValue &params);
const storage::PropertyValue &params, const std::string &db);
/// Reopens the log file. Used for log file rotation. Thread-safe.
void ReopenLog();

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
// License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
@@ -314,4 +314,57 @@ std::vector<auth::User> Auth::AllUsersForRole(const std::string &rolename_orig)
return ret;
}
#ifdef MG_ENTERPRISE
bool Auth::GrantDatabaseToUser(const std::string &db, const std::string &name) {
auto user = GetUser(name);
if (user) {
if (db == "*") {
user->db_access().GrantAll();
} else {
user->db_access().Add(db);
}
SaveUser(*user);
return true;
}
return false;
}
bool Auth::RevokeDatabaseFromUser(const std::string &db, const std::string &name) {
auto user = GetUser(name);
if (user) {
if (db == "*") {
user->db_access().DenyAll();
} else {
user->db_access().Remove(db);
}
SaveUser(*user);
return true;
}
return false;
}
void Auth::DeleteDatabase(const std::string &db) {
for (auto it = storage_.begin(kUserPrefix); it != storage_.end(kUserPrefix); ++it) {
auto username = it->first.substr(kUserPrefix.size());
if (username != utils::ToLowerCase(username)) continue;
auto user = GetUser(username);
if (user) {
user->db_access().Delete(db);
}
}
}
bool Auth::SetMainDatabase(const std::string &db, const std::string &name) {
auto user = GetUser(name);
if (user) {
if (!user->db_access().SetDefault(db)) {
throw AuthException("Couldn't set default database '{}' for user '{}'!", db, name);
}
SaveUser(*user);
return true;
}
return false;
}
#endif
} // namespace memgraph::auth

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Licensed as a Memgraph Enterprise file under the Memgraph Enterprise
// License (the "License"); by using this file, you agree to be bound by the terms of the License, and you may not use
@@ -155,6 +155,46 @@ class Auth final {
*/
std::vector<User> AllUsersForRole(const std::string &rolename) const;
#ifdef MG_ENTERPRISE
/**
* @brief Revoke access to individual database for a user.
*
* @param db name of the database to revoke
* @param name user's username
* @return true on success
* @throw AuthException if unable to find or update the user
*/
bool RevokeDatabaseFromUser(const std::string &db, const std::string &name);
/**
* @brief Grant access to individual database for a user.
*
* @param db name of the database to revoke
* @param name user's username
* @return true on success
* @throw AuthException if unable to find or update the user
*/
bool GrantDatabaseToUser(const std::string &db, const std::string &name);
/**
* @brief Delete a database from all users.
*
* @param db name of the database to delete
* @throw AuthException if unable to read data
*/
void DeleteDatabase(const std::string &db);
/**
* @brief Set main database for an individual user.
*
* @param db name of the database to revoke
* @param name user's username
* @return true on success
* @throw AuthException if unable to find or update the user
*/
bool SetMainDatabase(const std::string &db, const std::string &name);
#endif
private:
// Even though the `kvstore::KVStore` class is guaranteed to be thread-safe,
// Auth is not thread-safe because modifying users and roles might require

View File

@@ -15,8 +15,10 @@
#include "auth/crypto.hpp"
#include "auth/exceptions.hpp"
#include "dbms/constants.hpp"
#include "license/license.hpp"
#include "query/constants.hpp"
#include "spdlog/spdlog.h"
#include "utils/cast.hpp"
#include "utils/logging.hpp"
#include "utils/settings.hpp"
@@ -35,18 +37,31 @@ namespace memgraph::auth {
namespace {
// Constant list of all available permissions.
const std::vector<Permission> kPermissionsAll = {Permission::MATCH, Permission::CREATE,
Permission::MERGE, Permission::DELETE,
Permission::SET, Permission::REMOVE,
Permission::INDEX, Permission::STATS,
Permission::CONSTRAINT, Permission::DUMP,
Permission::AUTH, Permission::REPLICATION,
Permission::DURABILITY, Permission::READ_FILE,
Permission::FREE_MEMORY, Permission::TRIGGER,
Permission::CONFIG, Permission::STREAM,
Permission::MODULE_READ, Permission::MODULE_WRITE,
Permission::WEBSOCKET, Permission::TRANSACTION_MANAGEMENT,
Permission::STORAGE_MODE};
const std::vector<Permission> kPermissionsAll = {Permission::MATCH,
Permission::CREATE,
Permission::MERGE,
Permission::DELETE,
Permission::SET,
Permission::REMOVE,
Permission::INDEX,
Permission::STATS,
Permission::CONSTRAINT,
Permission::DUMP,
Permission::AUTH,
Permission::REPLICATION,
Permission::DURABILITY,
Permission::READ_FILE,
Permission::FREE_MEMORY,
Permission::TRIGGER,
Permission::CONFIG,
Permission::STREAM,
Permission::MODULE_READ,
Permission::MODULE_WRITE,
Permission::WEBSOCKET,
Permission::TRANSACTION_MANAGEMENT,
Permission::STORAGE_MODE,
Permission::MULTI_DATABASE_EDIT,
Permission::MULTI_DATABASE_USE};
} // namespace
@@ -98,6 +113,10 @@ std::string PermissionToString(Permission permission) {
return "TRANSACTION_MANAGEMENT";
case Permission::STORAGE_MODE:
return "STORAGE_MODE";
case Permission::MULTI_DATABASE_EDIT:
return "MULTI_DATABASE_EDIT";
case Permission::MULTI_DATABASE_USE:
return "MULTI_DATABASE_USE";
}
}
@@ -464,6 +483,78 @@ bool operator==(const Role &first, const Role &second) {
return first.rolename_ == second.rolename_ && first.permissions_ == second.permissions_;
}
#ifdef MG_ENTERPRISE
void Databases::Add(const std::string &db) {
if (allow_all_) {
grants_dbs_.clear();
allow_all_ = false;
}
grants_dbs_.emplace(db);
denies_dbs_.erase(db);
}
void Databases::Remove(const std::string &db) {
denies_dbs_.emplace(db);
grants_dbs_.erase(db);
}
void Databases::Delete(const std::string &db) {
denies_dbs_.erase(db);
if (!allow_all_) {
grants_dbs_.erase(db);
}
}
void Databases::GrantAll() {
allow_all_ = true;
grants_dbs_.clear();
denies_dbs_.clear();
}
void Databases::DenyAll() {
allow_all_ = false;
grants_dbs_.clear();
denies_dbs_.clear();
}
bool Databases::SetDefault(const std::string &db) {
if (!Contains(db)) return false;
default_db_ = db;
return true;
}
[[nodiscard]] bool Databases::Contains(const std::string &db) const {
return !denies_dbs_.contains(db) && (allow_all_ || grants_dbs_.contains(db));
}
const std::string &Databases::GetDefault() const {
if (!Contains(default_db_)) {
throw AuthException("No access to the set default database \"{}\".", default_db_);
}
return default_db_;
}
nlohmann::json Databases::Serialize() const {
nlohmann::json data = nlohmann::json::object();
data["grants"] = grants_dbs_;
data["denies"] = denies_dbs_;
data["allow_all"] = allow_all_;
data["default"] = default_db_;
return data;
}
Databases Databases::Deserialize(const nlohmann::json &data) {
if (!data.is_object()) {
throw AuthException("Couldn't load database data!");
}
if (!data["grants"].is_structured() || !data["denies"].is_structured() || !data["allow_all"].is_boolean() ||
!data["default"].is_string()) {
throw AuthException("Couldn't load database data!");
}
return {data["allow_all"], data["grants"], data["denies"], data["default"]};
}
#endif
User::User() {}
User::User(const std::string &username) : username_(utils::ToLowerCase(username)) {}
@@ -472,11 +563,12 @@ User::User(const std::string &username, const std::string &password_hash, const
#ifdef MG_ENTERPRISE
User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions,
FineGrainedAccessHandler fine_grained_access_handler)
FineGrainedAccessHandler fine_grained_access_handler, Databases db_access)
: username_(utils::ToLowerCase(username)),
password_hash_(password_hash),
permissions_(permissions),
fine_grained_access_handler_(std::move(fine_grained_access_handler)) {}
fine_grained_access_handler_(std::move(fine_grained_access_handler)),
database_access_(db_access) {}
#endif
bool User::CheckPassword(const std::string &password) {
@@ -576,8 +668,10 @@ nlohmann::json User::Serialize() const {
#ifdef MG_ENTERPRISE
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize();
data["databases"] = database_access_.Serialize();
} else {
data["fine_grained_access_handler"] = {};
data["databases"] = {};
}
#endif
// The role shouldn't be serialized here, it is stored as a foreign key.
@@ -594,11 +688,20 @@ User User::Deserialize(const nlohmann::json &data) {
auto permissions = Permissions::Deserialize(data["permissions"]);
#ifdef MG_ENTERPRISE
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
Databases db_access;
if (data["databases"].is_structured()) {
db_access = Databases::Deserialize(data["databases"]);
} else {
// Back-compatibility
spdlog::warn("User without specified database access. Given access to the default database.");
db_access.Add(dbms::kDefaultDB);
db_access.SetDefault(dbms::kDefaultDB);
}
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};
return {data["username"], data["password_hash"], permissions, fine_grained_access_handler, db_access};
}
#endif
return {data["username"], data["password_hash"], permissions};

View File

@@ -9,10 +9,13 @@
#pragma once
#include <optional>
#include <set>
#include <string>
#include <unordered_map>
#include <json/json.hpp>
#include "dbms/constants.hpp"
#include "utils/logging.hpp"
namespace memgraph::auth {
// These permissions must have values that are applicable for usage in a
@@ -41,7 +44,9 @@ enum class Permission : uint64_t {
MODULE_WRITE = 1U << 19U,
WEBSOCKET = 1U << 20U,
TRANSACTION_MANAGEMENT = 1U << 21U,
STORAGE_MODE = 1U << 22U
STORAGE_MODE = 1U << 22U,
MULTI_DATABASE_EDIT = 1U << 23U,
MULTI_DATABASE_USE = 1U << 24U,
};
// clang-format on
@@ -237,6 +242,85 @@ class Role final {
bool operator==(const Role &first, const Role &second);
#ifdef MG_ENTERPRISE
class Databases final {
public:
Databases() : allow_all_(true), default_db_(dbms::kDefaultDB) {}
Databases(const Databases &) = default;
Databases &operator=(const Databases &) = default;
Databases(Databases &&) noexcept = default;
Databases &operator=(Databases &&) noexcept = default;
~Databases() = default;
/**
* @brief Add database to the list of granted access. @note allow_all_ will be false after execution
*
* @param db name of the database to grant access to
*/
void Add(const std::string &db);
/**
* @brief Remove database to the list of granted access.
* @note if allow_all_ is set, the flag will remain set and the
* database will be added to the set of denied databases.
*
* @param db name of the database to grant access to
*/
void Remove(const std::string &db);
/**
* @brief Called when database is dropped. Removes it from granted (if allow_all is false) and denied set.
* @note allow_all_ is not changed
*
* @param db name of the database to grant access to
*/
void Delete(const std::string &db);
/**
* @brief Set allow_all_ to true and clears grants and denied sets.
*/
void GrantAll();
/**
* @brief Set allow_all_ to false and clears grants and denied sets.
*/
void DenyAll();
/**
* @brief Set the default database.
*/
bool SetDefault(const std::string &db);
/**
* @brief Checks if access is grated to the database.
*
* @param db name of the database
* @return true if allow_all and not denied or granted
*/
bool Contains(const std::string &db) const;
bool GetAllowAll() const { return allow_all_; }
const std::set<std::string> &GetGrants() const { return grants_dbs_; }
const std::set<std::string> &GetDenies() const { return denies_dbs_; }
const std::string &GetDefault() const;
nlohmann::json Serialize() const;
/// @throw AuthException if unable to deserialize.
static Databases Deserialize(const nlohmann::json &data);
private:
Databases(bool allow_all, std::set<std::string> grant, std::set<std::string> deny,
const std::string &default_db = dbms::kDefaultDB)
: grants_dbs_(grant), denies_dbs_(deny), allow_all_(allow_all), default_db_(default_db) {}
std::set<std::string> grants_dbs_; //!< set of databases with granted access
std::set<std::string> denies_dbs_; //!< set of databases with denied access
bool allow_all_; //!< flag to allow access to everything (denied overrides this)
std::string default_db_; //!< user's default database
};
#endif
// TODO (mferencevic): Implement password expiry.
class User final {
public:
@@ -246,7 +330,7 @@ class User final {
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);
FineGrainedAccessHandler fine_grained_access_handler, Databases db_access = {});
#endif
User(const User &) = default;
User &operator=(const User &) = default;
@@ -279,6 +363,11 @@ class User final {
const Role *role() const;
#ifdef MG_ENTERPRISE
Databases &db_access() { return database_access_; }
const Databases &db_access() const { return database_access_; }
#endif
nlohmann::json Serialize() const;
/// @throw AuthException if unable to deserialize.
@@ -292,6 +381,7 @@ class User final {
Permissions permissions_;
#ifdef MG_ENTERPRISE
FineGrainedAccessHandler fine_grained_access_handler_;
Databases database_access_;
#endif
std::optional<Role> role_;
};

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -20,6 +20,8 @@ inline constexpr uint8_t kPreamble[4] = {0x60, 0x60, 0xB0, 0x17};
enum class Signature : uint8_t {
Noop = 0x00,
Init = 0x01,
LogOn = 0x6A,
LogOff = 0x6B,
AckFailure = 0x0E, // only v1
Reset = 0x0F,
Goodbye = 0x02,

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -28,7 +28,7 @@ inline constexpr size_t kChunkWholeSize = kChunkHeaderSize + kChunkMaxDataSize;
*/
inline constexpr size_t kHandshakeSize = 20;
inline constexpr uint16_t kSupportedVersions[] = {0x0100, 0x0400, 0x0401, 0x0403};
inline constexpr uint16_t kSupportedVersions[] = {0x0100, 0x0400, 0x0401, 0x0403, 0x0502};
inline constexpr int kPullAll = -1;
inline constexpr int kPullLast = -1;

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -33,7 +33,14 @@ namespace memgraph::communication::bolt {
template <typename Buffer>
class Decoder {
public:
explicit Decoder(Buffer &buffer) : buffer_(buffer) {}
explicit Decoder(Buffer &buffer) : buffer_(buffer), major_v_(0) {}
/**
* Lets the user update the version.
* This is all single thread for now. TODO: Update if ever multithreaded.
* @param major_v the major version of the Bolt protocol used.
*/
void UpdateVersion(int major_v) { major_v_ = major_v; }
/**
* Reads a Value from the available data in the buffer.
@@ -208,6 +215,10 @@ class Decoder {
protected:
Buffer &buffer_;
int major_v_; //!< Major version of the underlying Bolt protocol
// TODO: when refactoring
// Ideally the major_v would be a compile time constant. If the higher level (Bolt driver) ends up being separate
// classes, this could be just a template and each version of the driver would use the appropriate decoder.
private:
bool ReadNull(const Marker &marker, Value *data) {
@@ -370,11 +381,7 @@ class Decoder {
}
ret.emplace(std::move(dv_key.ValueString()), std::move(dv_val));
}
if (ret.size() != size) {
return false;
}
return true;
return ret.size() == size;
}
bool ReadVertex(Value *data) {
@@ -407,6 +414,14 @@ class Decoder {
}
vertex.properties = std::move(dv.ValueMap());
if (major_v_ > 4) {
// element_id introduced in v5.0
if (!ReadValue(&dv, Value::Type::String)) {
return false;
}
vertex.element_id = std::move(dv.ValueString());
}
return true;
}
@@ -445,6 +460,23 @@ class Decoder {
}
edge.properties = std::move(dv.ValueMap());
if (major_v_ > 4) {
// element_id introduced in v5.0
if (!ReadValue(&dv, Value::Type::String)) {
return false;
}
edge.element_id = std::move(dv.ValueString());
// from_element_id introduced in v5.0
if (!ReadValue(&dv, Value::Type::String)) {
return false;
}
edge.from_element_id = std::move(dv.ValueString());
// to_element_id introduced in v5.0
if (!ReadValue(&dv, Value::Type::String)) {
return false;
}
edge.to_element_id = std::move(dv.ValueString());
}
return true;
}
@@ -471,6 +503,14 @@ class Decoder {
}
edge.properties = std::move(dv.ValueMap());
if (major_v_ > 4) {
// element_id introduced in v5.0
if (!ReadValue(&dv, Value::Type::String)) {
return false;
}
edge.element_id = std::move(dv.ValueString());
}
return true;
}

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -36,7 +36,14 @@ namespace memgraph::communication::bolt {
template <typename Buffer>
class BaseEncoder {
public:
explicit BaseEncoder(Buffer &buffer) : buffer_(buffer) {}
explicit BaseEncoder(Buffer &buffer) : buffer_(buffer), major_v_(0) {}
/**
* Lets the user update the version.
* This is all single thread for now. TODO: Update if ever multithreaded.
* @param major_v the major version of the Bolt protocol used.
*/
void UpdateVersion(int major_v) { major_v_ = major_v; }
void WriteRAW(const uint8_t *data, uint64_t len) { buffer_.Write(data, len); }
@@ -116,7 +123,8 @@ class BaseEncoder {
}
void WriteVertex(const Vertex &vertex) {
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct) + 3);
int struct_n = 3 + 1 * int(major_v_ > 4); // element_id introduced from v5
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct) + struct_n);
WriteRAW(utils::UnderlyingCast(Signature::Node));
WriteInt(vertex.id.AsInt());
@@ -132,10 +140,16 @@ class BaseEncoder {
WriteString(prop.first);
WriteValue(prop.second);
}
if (major_v_ > 4) {
// element_id introduced in v5.0
WriteString(vertex.element_id);
}
}
void WriteEdge(const Edge &edge, bool unbound = false) {
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct) + (unbound ? 3 : 5));
int struct_n = (unbound ? 3 + 1 * int(major_v_ > 4) : 5 + 3 * int(major_v_ > 4)); // element_id introduced from v5
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct) + struct_n);
WriteRAW(utils::UnderlyingCast(unbound ? Signature::UnboundRelationship : Signature::Relationship));
WriteInt(edge.id.AsInt());
@@ -152,10 +166,22 @@ class BaseEncoder {
WriteString(prop.first);
WriteValue(prop.second);
}
if (major_v_ > 4) {
// element_id introduced in v5.0
WriteString(edge.element_id);
if (!unbound) {
// from_element_id introduced in v5.0
WriteString(edge.from_element_id);
// to_element_id introduced in v5.0
WriteString(edge.to_element_id);
}
}
}
void WriteEdge(const UnboundedEdge &edge) {
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct) + 3);
const int struct_n = 3 + 1 * int(major_v_ > 4); // element_id introduced from v5
WriteRAW(utils::UnderlyingCast(Marker::TinyStruct) + struct_n);
WriteRAW(utils::UnderlyingCast(Signature::UnboundRelationship));
WriteInt(edge.id.AsInt());
@@ -168,6 +194,11 @@ class BaseEncoder {
WriteString(prop.first);
WriteValue(prop.second);
}
if (major_v_ > 4) {
// element_id introduced in v5.0
WriteString(edge.element_id);
}
}
void WritePath(const Path &path) {
@@ -264,6 +295,7 @@ class BaseEncoder {
protected:
Buffer &buffer_;
int major_v_; //!< Major version of the underlying Bolt protocol (TODO: Think about reimplementing the versioning)
private:
template <class T>

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -41,6 +41,8 @@ class ClientEncoder : private BaseEncoder<Buffer> {
public:
ClientEncoder(Buffer &buffer) : BaseEncoder<Buffer>(buffer) {}
using BaseEncoder<Buffer>::UpdateVersion;
/**
* Writes a Init message.
*

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -34,6 +34,8 @@ class Encoder : private BaseEncoder<Buffer> {
public:
Encoder(Buffer &buffer) : BaseEncoder<Buffer>(buffer) {}
using BaseEncoder<Buffer>::UpdateVersion;
/**
* Sends a Record message.
*

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -11,6 +11,8 @@
#pragma once
#include <concepts>
#include <cstddef>
#include <optional>
#include <thread>
@@ -24,8 +26,12 @@
#include "communication/bolt/v1/states/executing.hpp"
#include "communication/bolt/v1/states/handshake.hpp"
#include "communication/bolt/v1/states/init.hpp"
#include "communication/bolt/v1/value.hpp"
#include "dbms/constants.hpp"
#include "dbms/global.hpp"
#include "utils/exceptions.hpp"
#include "utils/logging.hpp"
#include "utils/uuid.hpp"
namespace memgraph::communication::bolt {
@@ -48,14 +54,31 @@ class SessionException : public utils::BasicException {
* @tparam TOutputStream type of output stream that will be used
*/
template <typename TInputStream, typename TOutputStream>
class Session {
#ifdef MG_ENTERPRISE
class Session : public dbms::SessionInterface
#else
class Session
#endif
{
public:
using TEncoder = Encoder<ChunkedEncoderBuffer<TOutputStream>>;
/**
* @brief Construct a new Session object
*
* @param input_stream stream to read from
* @param output_stream stream to write to
* @param impl a default high-level implementation to use (has to be defined)
*/
Session(TInputStream *input_stream, TOutputStream *output_stream)
: input_stream_(*input_stream), output_stream_(*output_stream) {}
: input_stream_(*input_stream), output_stream_(*output_stream), session_uuid_(utils::GenerateUUID()) {}
virtual ~Session() {}
virtual ~Session() = default;
Session(const Session &) = delete;
Session &operator=(const Session &) = delete;
Session(Session &&) noexcept = delete;
Session &operator=(Session &&) noexcept = delete;
/**
* Process the given `query` with `params`.
@@ -63,7 +86,10 @@ class Session {
* if an explicit transaction was started.
*/
virtual std::pair<std::vector<std::string>, std::optional<int>> Interpret(
const std::string &query, const std::map<std::string, Value> &params) = 0;
const std::string &query, const std::map<std::string, Value> &params,
const std::map<std::string, memgraph::communication::bolt::Value> &metadata) = 0;
virtual void Configure(const std::map<std::string, memgraph::communication::bolt::Value> &run_time_info) = 0;
/**
* Put results of the processed query in the `encoder`.
@@ -85,7 +111,7 @@ class Session {
*/
virtual std::map<std::string, Value> Discard(std::optional<int> n, std::optional<int> qid) = 0;
virtual void BeginTransaction() = 0;
virtual void BeginTransaction(const std::map<std::string, memgraph::communication::bolt::Value> &params) = 0;
virtual void CommitTransaction() = 0;
virtual void RollbackTransaction() = 0;
@@ -98,7 +124,6 @@ class Session {
/** Return the name of the server that should be used for the Bolt INIT
* message. */
virtual std::optional<std::string> GetServerNameForInit() = 0;
/**
* Executes the session after data has been read into the buffer.
* Goes through the bolt states in order to execute commands from the client.
@@ -120,6 +145,9 @@ class Session {
return;
}
handshake_done_ = true;
// Update the decoder's Bolt version (v5 has changed the undelying structure)
decoder_.UpdateVersion(version_.major);
encoder_.UpdateVersion(version_.major);
}
ChunkState chunk_state;
@@ -157,8 +185,7 @@ class Session {
}
}
// TODO: Rethink if there is a way to hide some members. At the momement all
// of them are public.
// TODO: Rethink if there is a way to hide some members. At the momement all of them are public.
TInputStream &input_stream_;
TOutputStream &output_stream_;
@@ -178,6 +205,11 @@ class Session {
Version version_;
virtual std::string GetID() const = 0;
std::string UUID() const { return session_uuid_; }
// virtual dbms::SetForResult SwitchDB(const std::string &db_name) = 0;
private:
void ClientFailureInvalidData() {
// Set the state to Close.
@@ -193,6 +225,8 @@ class Session {
// of the session to trigger session cleanup and socket close.
throw SessionException("Something went wrong during session execution!");
}
const std::string session_uuid_; //!< unique identifier of the session (auto generated)
};
} // namespace memgraph::communication::bolt

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -22,10 +22,15 @@
#include "communication/bolt/v1/state.hpp"
#include "communication/bolt/v1/states/handlers.hpp"
#include "communication/bolt/v1/value.hpp"
#include "utils/event_counter.hpp"
#include "utils/likely.hpp"
#include "utils/logging.hpp"
#include "utils/message.hpp"
namespace memgraph::metrics {
extern const Event BoltMessages;
} // namespace memgraph::metrics
namespace memgraph::communication::bolt {
template <typename TSession>
@@ -86,6 +91,37 @@ State RunHandlerV4(Signature signature, TSession &session, State state, Marker m
}
}
template <typename TSession>
State RunHandlerV5(Signature signature, TSession &session, State state, Marker marker) {
switch (signature) {
case Signature::Run:
return HandleRunV5<TSession>(session, state, marker);
case Signature::Pull:
return HandlePullV5<TSession>(session, state, marker);
case Signature::Discard:
return HandleDiscardV5<TSession>(session, state, marker);
case Signature::Reset:
return HandleReset<TSession>(session, marker);
case Signature::Begin:
return HandleBegin<TSession>(session, state, marker);
case Signature::Commit:
return HandleCommit<TSession>(session, state, marker);
case Signature::Goodbye:
return HandleGoodbye<TSession>();
case Signature::Rollback:
return HandleRollback<TSession>(session, state, marker);
case Signature::Noop:
return HandleNoop<TSession>(state);
case Signature::Route:
return HandleRoute<TSession>(session, marker);
case Signature::LogOff:
return HandleLogOff<TSession>();
default:
spdlog::trace("Unrecognized signature received (0x{:02X})!", utils::UnderlyingCast(signature));
return State::Close;
}
}
/**
* Executor state run function
* This function executes an initialized Bolt session.
@@ -103,8 +139,10 @@ State StateExecutingRun(TSession &session, State state) {
switch (session.version_.major) {
case 1:
memgraph::metrics::IncrementCounter(memgraph::metrics::BoltMessages);
return RunHandlerV1(signature, session, state, marker);
case 4: {
memgraph::metrics::IncrementCounter(memgraph::metrics::BoltMessages);
if (session.version_.minor >= 3) {
return RunHandlerV4<TSession, 3>(signature, session, state, marker);
}
@@ -113,6 +151,8 @@ State StateExecutingRun(TSession &session, State state) {
}
return RunHandlerV4<TSession>(signature, session, state, marker);
}
case 5:
return RunHandlerV5<TSession>(signature, session, state, marker);
default:
spdlog::trace("Unsupported bolt version:{}.{})!", session.version_.major, session.version_.minor);
return State::Close;

View File

@@ -11,7 +11,9 @@
#pragma once
#include <exception>
#include <map>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
@@ -22,6 +24,7 @@
#include "communication/bolt/v1/state.hpp"
#include "communication/bolt/v1/value.hpp"
#include "communication/exceptions.hpp"
#include "storage/v2/property_value.hpp"
#include "utils/logging.hpp"
#include "utils/message.hpp"
@@ -71,6 +74,23 @@ inline std::pair<std::string, std::string> ExceptionToErrorMessage(const std::ex
"should be in database logs."};
}
namespace helpers {
/** Extracts metadata from the extras field.
* NOTE: In order to avoid a copy, the metadata in moved.
* TODO: Update if extra field is used for anything else.
*/
inline std::map<std::string, Value> ConsumeMetadata(Value &extra) {
std::map<std::string, Value> md;
auto &md_tv = extra.ValueMap()["tx_metadata"];
if (md_tv.IsMap()) {
md = std::move(md_tv.ValueMap());
}
return md;
}
} // namespace helpers
namespace details {
template <bool is_pull, typename TSession>
@@ -205,11 +225,15 @@ State HandleRunV1(TSession &session, const State state, const Marker marker) {
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
#ifdef MG_ENTERPRISE
spdlog::debug("[Run - {}] '{}'", session.GetID(), query.ValueString());
#else
spdlog::debug("[Run] '{}'", query.ValueString());
#endif
try {
// Interpret can throw.
const auto [header, qid] = session.Interpret(query.ValueString(), params.ValueMap());
const auto [header, qid] = session.Interpret(query.ValueString(), params.ValueMap(), {});
// Convert std::string to Value
std::vector<Value> vec;
std::map<std::string, Value> data;
@@ -250,6 +274,7 @@ State HandleRunV4(TSession &session, const State state, const Marker marker) {
// Even though this part seems unnecessary it is needed to move the buffer
if (!session.decoder_.ReadValue(&extra, Value::Type::Map)) {
spdlog::trace("Couldn't read extra field!");
return State::Close;
}
if (state != State::Idle) {
@@ -262,11 +287,22 @@ State HandleRunV4(TSession &session, const State state, const Marker marker) {
DMG_ASSERT(!session.encoder_buffer_.HasData(), "There should be no data to write in this state");
try {
session.Configure(extra.ValueMap());
} catch (const std::exception &e) {
return HandleFailure(session, e);
}
#ifdef MG_ENTERPRISE
spdlog::debug("[Run - {}] '{}'", session.GetID(), query.ValueString());
#else
spdlog::debug("[Run] '{}'", query.ValueString());
#endif
try {
// Interpret can throw.
const auto [header, qid] = session.Interpret(query.ValueString(), params.ValueMap());
const auto [header, qid] =
session.Interpret(query.ValueString(), params.ValueMap(), helpers::ConsumeMetadata(extra));
// Convert std::string to Value
std::vector<Value> vec;
std::map<std::string, Value> data;
@@ -288,6 +324,12 @@ State HandleRunV4(TSession &session, const State state, const Marker marker) {
}
}
template <typename TSession>
State HandleRunV5(TSession &session, const State state, const Marker marker) {
// Using V4 on purpose
return HandleRunV4<TSession>(session, state, marker);
}
template <typename TSession>
State HandlePullV1(TSession &session, const State state, const Marker marker) {
return details::HandlePullDiscardV1<true>(session, state, marker);
@@ -298,6 +340,12 @@ State HandlePullV4(TSession &session, const State state, const Marker marker) {
return details::HandlePullDiscardV4<true>(session, state, marker);
}
template <typename TSession>
State HandlePullV5(TSession &session, const State state, const Marker marker) {
// Using V4 on purpose
return HandlePullV4<TSession>(session, state, marker);
}
template <typename TSession>
State HandleDiscardV1(TSession &session, const State state, const Marker marker) {
return details::HandlePullDiscardV1<false>(session, state, marker);
@@ -308,6 +356,12 @@ State HandleDiscardV4(TSession &session, const State state, const Marker marker)
return details::HandlePullDiscardV4<false>(session, state, marker);
}
template <typename TSession>
State HandleDiscardV5(TSession &session, const State state, const Marker marker) {
// Using V4 on purpose
return HandleDiscardV4<TSession>(session, state, marker);
}
template <typename TSession>
State HandleReset(TSession &session, const Marker marker) {
// IMPORTANT: This implementation of the Bolt RESET command isn't fully
@@ -360,7 +414,7 @@ State HandleBegin(TSession &session, const State state, const Marker marker) {
}
try {
session.BeginTransaction();
session.BeginTransaction(helpers::ConsumeMetadata(extra));
} catch (const std::exception &e) {
return HandleFailure(session, e);
}
@@ -465,4 +519,10 @@ State HandleRoute(TSession &session, const Marker marker) {
}
return State::Error;
}
template <typename TSession>
State HandleLogOff() {
// No arguments sent, the user just needs to reauthenticate
return State::Init;
}
} // namespace memgraph::communication::bolt

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -18,6 +18,7 @@
#include "communication/bolt/v1/state.hpp"
#include "communication/bolt/v1/value.hpp"
#include "communication/exceptions.hpp"
#include "spdlog/spdlog.h"
#include "utils/likely.hpp"
#include "utils/logging.hpp"
@@ -27,17 +28,25 @@ namespace details {
template <typename TSession>
std::optional<State> AuthenticateUser(TSession &session, Value &metadata) {
// Get authentication data.
// From neo4j driver v4.4, fields that have a default value are not sent.
// In order to have back-compatibility, the missing fields will be added.
auto &data = metadata.ValueMap();
if (!data.count("scheme")) {
spdlog::warn("The client didn't supply authentication information!");
return State::Close;
if (data.empty()) { // Special case auth=None
spdlog::warn("The client didn't supply the authentication scheme! Trying with \"none\"...");
data["scheme"] = "none";
}
std::string username;
std::string password;
if (data["scheme"].ValueString() == "basic") {
if (!data.count("principal") || !data.count("credentials")) {
spdlog::warn("The client didn't supply authentication information!");
return State::Close;
if (!data.count("principal")) { // Special case principal = ""
spdlog::warn("The client didn't supply the principal field! Trying with \"\"...");
data["principal"] = "";
}
if (!data.count("credentials")) { // Special case credentials = ""
spdlog::warn("The client didn't supply the credentials field! Trying with \"\"...");
data["credentials"] = "";
}
username = data["principal"].ValueString();
password = data["credentials"].ValueString();
@@ -106,6 +115,30 @@ std::optional<Value> GetMetadataV4(TSession &session, const Marker marker) {
return std::nullopt;
}
auto &data = metadata.ValueMap();
if (!data.count("user_agent")) {
spdlog::warn("The client didn't supply the user agent!");
return std::nullopt;
}
spdlog::info("Client connected '{}'", data.at("user_agent").ValueString());
return metadata;
}
template <typename TSession>
std::optional<Value> GetInitDataV5(TSession &session, const Marker marker) {
if (marker != Marker::TinyStruct1) [[unlikely]] {
spdlog::trace("Expected TinyStruct1 marker, but received 0x{:02X}!", utils::UnderlyingCast(marker));
return std::nullopt;
}
Value metadata;
if (!session.decoder_.ReadValue(&metadata, Value::Type::Map)) {
spdlog::trace("Couldn't read metadata!");
return std::nullopt;
}
const auto &data = metadata.ValueMap();
if (!data.count("user_agent")) {
spdlog::warn("The client didn't supply the user agent!");
@@ -117,6 +150,22 @@ std::optional<Value> GetMetadataV4(TSession &session, const Marker marker) {
return metadata;
}
template <typename TSession>
std::optional<Value> GetAuthDataV5(TSession &session, const Marker marker) {
if (marker != Marker::TinyStruct1) [[unlikely]] {
spdlog::trace("Expected TinyStruct1 marker, but received 0x{:02X}!", utils::UnderlyingCast(marker));
return std::nullopt;
}
Value metadata;
if (!session.decoder_.ReadValue(&metadata, Value::Type::Map)) {
spdlog::trace("Couldn't read metadata!");
return std::nullopt;
}
return metadata;
}
template <typename TSession>
State SendSuccessMessage(TSession &session) {
// Neo4j's Java driver 4.1.1+ requires connection_id.
@@ -180,6 +229,57 @@ State StateInitRunV4(TSession &session, Marker marker, Signature signature) {
return SendSuccessMessage(session);
}
template <typename TSession>
State StateInitRunV5(TSession &session, Marker marker, Signature signature) {
if (signature == Signature::Noop) [[unlikely]] {
SPDLOG_DEBUG("Received NOOP message");
return State::Init;
}
if (signature == Signature::Init) {
auto maybeMetadata = GetInitDataV5(session, marker);
if (!maybeMetadata) {
return State::Close;
}
if (SendSuccessMessage(session) == State::Close) {
return State::Close;
}
// Stay in Init
return State::Init;
}
if (signature == Signature::LogOn) {
if (marker != Marker::TinyStruct1) [[unlikely]] {
spdlog::trace("Expected TinyStruct1 marker, but received 0x{:02X}!", utils::UnderlyingCast(marker));
spdlog::trace(
"The client sent malformed data, but we are continuing "
"because the official Neo4j Java driver sends malformed "
"data. D'oh!");
return State::Close;
}
auto maybeMetadata = GetAuthDataV5(session, marker);
if (!maybeMetadata) {
return State::Close;
}
auto result = AuthenticateUser(session, *maybeMetadata);
if (result) {
spdlog::trace("Failed to authenticate, closing connection...");
return State::Close;
}
if (SendSuccessMessage(session) == State::Close) {
return State::Close;
}
return State::Idle;
}
spdlog::trace("Expected Init signature, but received 0x{:02X}!", utils::UnderlyingCast(signature));
return State::Close;
}
} // namespace details
/**
@@ -208,6 +308,9 @@ State StateInitRun(TSession &session) {
}
return details::StateInitRunV4<TSession>(session, marker, signature);
}
case 5: {
return details::StateInitRunV5<TSession>(session, marker, signature);
}
}
spdlog::trace("Unsupported bolt version:{}.{})!", session.version_.major, session.version_.minor);
return State::Close;

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -342,6 +342,9 @@ std::ostream &operator<<(std::ostream &os, const Vertex &vertex) {
[&](auto &stream, const auto &pair) { stream << pair.first << ": " << pair.second; });
os << "}";
}
if (!vertex.element_id.empty()) {
os << " element_id: " << vertex.element_id;
}
return os << ")";
}

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -57,6 +57,7 @@ struct Vertex {
Id id;
std::vector<std::string> labels;
std::map<std::string, Value> properties;
std::string element_id;
};
/**
@@ -69,6 +70,9 @@ struct Edge {
Id to;
std::string type;
std::map<std::string, Value> properties;
std::string element_id;
std::string from_element_id;
std::string to_element_id;
};
/**
@@ -79,6 +83,7 @@ struct UnboundedEdge {
Id id;
std::string type;
std::map<std::string, Value> properties;
std::string element_id;
};
/**

View File

@@ -0,0 +1,109 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <list>
#include <memory>
#include <spdlog/spdlog.h>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp>
#include <boost/beast/core.hpp>
#include "communication/context.hpp"
#include "communication/http/session.hpp"
#include "utils/spin_lock.hpp"
#include "utils/synchronized.hpp"
namespace memgraph::communication::http {
template <class TRequestHandler, typename TSessionContext>
class Listener final : public std::enable_shared_from_this<Listener<TRequestHandler, TSessionContext>> {
using tcp = boost::asio::ip::tcp;
using SessionHandler = Session<TRequestHandler, TSessionContext>;
using std::enable_shared_from_this<Listener<TRequestHandler, TSessionContext>>::shared_from_this;
public:
Listener(const Listener &) = delete;
Listener(Listener &&) = delete;
Listener &operator=(const Listener &) = delete;
Listener &operator=(Listener &&) = delete;
~Listener() {}
template <typename... Args>
static std::shared_ptr<Listener> Create(Args &&...args) {
return std::shared_ptr<Listener>{new Listener(std::forward<Args>(args)...)};
}
// Start accepting incoming connections
void Run() { DoAccept(); }
tcp::endpoint GetEndpoint() const { return acceptor_.local_endpoint(); }
private:
Listener(boost::asio::io_context &ioc, TSessionContext *session_context, ServerContext *context,
tcp::endpoint endpoint)
: ioc_(ioc), session_context_(session_context), context_(context), acceptor_(ioc) {
boost::beast::error_code ec;
// Open the acceptor
acceptor_.open(endpoint.protocol(), ec);
if (ec) {
LogError(ec, "open");
return;
}
// Allow address reuse
acceptor_.set_option(boost::asio::socket_base::reuse_address(true), ec);
if (ec) {
LogError(ec, "set_option");
return;
}
// Bind to the server address
acceptor_.bind(endpoint, ec);
if (ec) {
LogError(ec, "bind");
return;
}
acceptor_.listen(boost::asio::socket_base::max_listen_connections, ec);
if (ec) {
LogError(ec, "listen");
return;
}
spdlog::info("HTTP server is listening on {}:{}", endpoint.address(), endpoint.port());
}
void DoAccept() {
acceptor_.async_accept(ioc_, [shared_this = shared_from_this()](auto ec, auto socket) {
shared_this->OnAccept(ec, std::move(socket));
});
}
void OnAccept(boost::beast::error_code ec, tcp::socket socket) {
if (ec) {
return LogError(ec, "accept");
}
SessionHandler::Create(std::move(socket), session_context_, *context_)->Run();
DoAccept();
}
boost::asio::io_context &ioc_;
TSessionContext *session_context_;
ServerContext *context_;
tcp::acceptor acceptor_;
};
} // namespace memgraph::communication::http

View File

@@ -0,0 +1,66 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <thread>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include "communication/http/listener.hpp"
#include "io/network/endpoint.hpp"
namespace memgraph::communication::http {
template <class TRequestHandler, typename TSessionContext>
class Server final {
using tcp = boost::asio::ip::tcp;
public:
explicit Server(io::network::Endpoint endpoint, TSessionContext *session_context, ServerContext *context)
: listener_{Listener<TRequestHandler, TSessionContext>::Create(
ioc_, session_context, context,
tcp::endpoint{boost::asio::ip::make_address(endpoint.address), endpoint.port})} {}
Server(const Server &) = delete;
Server(Server &&) = delete;
Server &operator=(const Server &) = delete;
Server &operator=(Server &&) = delete;
~Server() {
MG_ASSERT(!background_thread_ || (ioc_.stopped() && !background_thread_->joinable()),
"Server wasn't shutdown properly");
}
void Start() {
MG_ASSERT(!background_thread_, "The server was already started!");
listener_->Run();
background_thread_.emplace([this] { ioc_.run(); });
}
void Shutdown() { ioc_.stop(); }
void AwaitShutdown() {
if (background_thread_ && background_thread_->joinable()) {
background_thread_->join();
}
}
bool IsRunning() const { return background_thread_ && !ioc_.stopped(); }
tcp::endpoint GetEndpoint() const { return listener_->GetEndpoint(); }
private:
boost::asio::io_context ioc_;
std::shared_ptr<Listener<TRequestHandler, TSessionContext>> listener_;
std::optional<std::thread> background_thread_;
};
} // namespace memgraph::communication::http

View File

@@ -0,0 +1,193 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <deque>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <variant>
#include <spdlog/spdlog.h>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp>
#include <boost/beast/core/buffers_to_string.hpp>
#include <boost/beast/core/stream_traits.hpp>
#include <boost/beast/core/tcp_stream.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/beast/version.hpp>
#include <json/json.hpp>
#include "communication/context.hpp"
#include "utils/logging.hpp"
#include "utils/variant_helpers.hpp"
namespace memgraph::communication::http {
inline constexpr uint16_t kSSLExpirySeconds = 30;
inline void LogError(boost::beast::error_code ec, const std::string_view what) {
spdlog::warn("HTTP session failed on {}: {}", what, ec.message());
}
template <class TRequestHandler, typename TSessionContext>
class Session : public std::enable_shared_from_this<Session<TRequestHandler, TSessionContext>> {
using tcp = boost::asio::ip::tcp;
using std::enable_shared_from_this<Session<TRequestHandler, TSessionContext>>::shared_from_this;
public:
template <typename... Args>
static std::shared_ptr<Session> Create(Args &&...args) {
return std::shared_ptr<Session>{new Session{std::forward<Args>(args)...}};
}
void Run() {
if (auto *ssl = std::get_if<SSLSocket>(&stream_); ssl != nullptr) {
try {
boost::beast::get_lowest_layer(*ssl).expires_after(std::chrono::seconds(kSSLExpirySeconds));
ssl->handshake(boost::asio::ssl::stream_base::server);
} catch (const boost::system::system_error &e) {
spdlog::warn("Failed on SSL handshake: {}", e.what());
return;
}
}
// run on the strand
boost::asio::dispatch(strand_, [shared_this = shared_from_this()] { shared_this->DoRead(); });
}
private:
using PlainSocket = boost::beast::tcp_stream;
using SSLSocket = boost::beast::ssl_stream<boost::beast::tcp_stream>;
explicit Session(tcp::socket &&socket, TSessionContext *data, ServerContext &context)
: stream_(CreateSocket(std::move(socket), context)),
handler_(data),
strand_{boost::asio::make_strand(GetExecutor())} {}
std::variant<PlainSocket, SSLSocket> CreateSocket(tcp::socket &&socket, ServerContext &context) {
if (context.use_ssl()) {
ssl_context_.emplace(context.context_clone());
return Session::SSLSocket{std::move(socket), *ssl_context_};
}
return Session::PlainSocket{std::move(socket)};
}
void OnWrite(boost::beast::error_code ec, size_t bytes_transferred) {
boost::ignore_unused(bytes_transferred);
if (ec) {
close_ = true;
return LogError(ec, "write");
}
if (close_) {
DoClose();
return;
}
res_ = nullptr;
DoRead();
}
void DoRead() {
req_ = {};
ExecuteForStream([this](auto &&stream) {
boost::beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(kSSLExpirySeconds));
boost::beast::http::async_read(
stream, buffer_, req_,
boost::asio::bind_executor(strand_, std::bind_front(&Session::OnRead, shared_from_this())));
});
}
void OnRead(boost::beast::error_code ec, size_t bytes_transferred) {
boost::ignore_unused(bytes_transferred);
if (ec == boost::beast::http::error::end_of_stream) {
DoClose();
return;
}
if (ec) {
return LogError(ec, "read");
}
auto async_write = [this](boost::beast::http::response<boost::beast::http::string_body> msg) {
ExecuteForStream([this, &msg](auto &&stream) {
// The lifetime of the message has to extend
// for the duration of the async operation so
// we use a shared_ptr to manage it.
auto sp = std::make_shared<boost::beast::http::response<boost::beast::http::string_body>>(std::move(msg));
// Store a type-erased version of the shared
// pointer in the class to keep it alive.
res_ = sp;
// Write the response
boost::beast::http::async_write(
stream, *sp, boost::asio::bind_executor(strand_, std::bind_front(&Session::OnWrite, shared_from_this())));
});
};
// handle request
handler_.HandleRequest(std::move(req_), async_write);
}
void DoClose() {
std::visit(utils::Overloaded{[this](SSLSocket &stream) {
boost::beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(30));
// Perform the SSL shutdown
stream.async_shutdown(
boost::beast::bind_front_handler(&Session::OnClose, shared_from_this()));
},
[](PlainSocket &stream) {
boost::beast::error_code ec;
stream.socket().shutdown(tcp::socket::shutdown_send, ec);
}},
stream_);
}
void OnClose(boost::beast::error_code ec) {
if (ec) {
LogError(ec, "close");
}
// At this point the connection is closed gracefully
}
auto GetExecutor() {
return std::visit(utils::Overloaded{[](auto &&stream) { return stream.get_executor(); }}, stream_);
}
template <typename F>
decltype(auto) ExecuteForStream(F &&fn) {
return std::visit(utils::Overloaded{std::forward<F>(fn)}, stream_);
}
std::optional<std::reference_wrapper<boost::asio::ssl::context>> ssl_context_;
std::variant<PlainSocket, SSLSocket> stream_;
boost::beast::flat_buffer buffer_;
TRequestHandler handler_;
boost::beast::http::request<boost::beast::http::string_body> req_;
std::shared_ptr<void> res_;
boost::asio::strand<boost::beast::tcp_stream::executor_type> strand_;
bool close_{false};
};
} // namespace memgraph::communication::http

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -39,7 +39,7 @@ namespace memgraph::communication {
* second, checks all sessions for expiration and shuts them down if they have
* expired.
*/
template <class TSession, class TSessionData>
template <class TSession, class TSessionContext>
class Listener final {
private:
// The maximum number of events handled per execution thread is 1. This is
@@ -48,10 +48,10 @@ class Listener final {
// can take a long time.
static const int kMaxEvents = 1;
using SessionHandler = Session<TSession, TSessionData>;
using SessionHandler = Session<TSession, TSessionContext>;
public:
Listener(TSessionData *data, ServerContext *context, int inactivity_timeout_sec, const std::string &service_name,
Listener(TSessionContext *data, ServerContext *context, int inactivity_timeout_sec, const std::string &service_name,
size_t workers_count)
: data_(data),
alive_(false),
@@ -259,7 +259,7 @@ class Listener final {
io::network::Epoll epoll_;
TSessionData *data_;
TSessionContext *data_;
utils::SpinLock lock_;
std::vector<std::unique_ptr<SessionHandler>> sessions_;

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -46,10 +46,10 @@ namespace memgraph::communication {
* @tparam TSession the server can handle different Sessions, each session
* represents a different protocol so the same network infrastructure
* can be used for handling different protocols
* @tparam TSessionData the class with objects that will be forwarded to the
* @tparam TSessionContext the class with objects that will be forwarded to the
* session
*/
template <typename TSession, typename TSessionData>
template <typename TSession, typename TSessionContext>
class Server final {
public:
using Socket = io::network::Socket;
@@ -58,12 +58,12 @@ class Server final {
* Constructs and binds server to endpoint, operates on session data and
* invokes workers_count workers
*/
Server(const io::network::Endpoint &endpoint, TSessionData *session_data, ServerContext *context,
Server(const io::network::Endpoint &endpoint, TSessionContext *session_context, ServerContext *context,
int inactivity_timeout_sec, const std::string &service_name,
size_t workers_count = std::thread::hardware_concurrency())
: alive_(false),
endpoint_(endpoint),
listener_(session_data, context, inactivity_timeout_sec, service_name, workers_count),
listener_(session_context, context, inactivity_timeout_sec, service_name, workers_count),
service_name_(service_name) {}
~Server() {
@@ -156,7 +156,7 @@ class Server final {
Socket socket_;
io::network::Endpoint endpoint_;
Listener<TSession, TSessionData> listener_;
Listener<TSession, TSessionContext> listener_;
const std::string service_name_;
};

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -69,10 +69,10 @@ class OutputStream final {
* sessions. It handles socket ownership, inactivity timeout and protocol
* wrapping.
*/
template <class TSession, class TSessionData>
template <class TSession, class TSessionContext>
class Session final {
public:
Session(io::network::Socket &&socket, TSessionData *data, ServerContext *context, int inactivity_timeout_sec)
Session(io::network::Socket &&socket, TSessionContext *data, ServerContext *context, int inactivity_timeout_sec)
: socket_(std::move(socket)),
output_stream_([this](const uint8_t *data, size_t len, bool have_more) { return Write(data, len, have_more); }),
session_(data, socket_.endpoint(), input_buffer_.read_end(), &output_stream_),

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -30,16 +30,17 @@
#include "communication/context.hpp"
#include "communication/v2/pool.hpp"
#include "communication/v2/session.hpp"
#include "utils/message.hpp"
#include "utils/spin_lock.hpp"
#include "utils/synchronized.hpp"
namespace memgraph::communication::v2 {
template <class TSession, class TSessionData>
class Listener final : public std::enable_shared_from_this<Listener<TSession, TSessionData>> {
template <class TSession, class TSessionContext>
class Listener final : public std::enable_shared_from_this<Listener<TSession, TSessionContext>> {
using tcp = boost::asio::ip::tcp;
using SessionHandler = Session<TSession, TSessionData>;
using std::enable_shared_from_this<Listener<TSession, TSessionData>>::shared_from_this;
using SessionHandler = Session<TSession, TSessionContext>;
using std::enable_shared_from_this<Listener<TSession, TSessionContext>>::shared_from_this;
public:
Listener(const Listener &) = delete;
@@ -58,10 +59,10 @@ class Listener final : public std::enable_shared_from_this<Listener<TSession, TS
bool IsRunning() const noexcept { return alive_.load(std::memory_order_relaxed); }
private:
Listener(boost::asio::io_context &io_context, TSessionData *data, ServerContext *server_context,
Listener(boost::asio::io_context &io_context, TSessionContext *session_context, ServerContext *server_context,
tcp::endpoint &endpoint, const std::string_view service_name, const uint64_t inactivity_timeout_sec)
: io_context_(io_context),
data_(data),
session_context_(session_context),
server_context_(server_context),
acceptor_(io_context_),
endpoint_{endpoint},
@@ -110,8 +111,8 @@ class Listener final : public std::enable_shared_from_this<Listener<TSession, TS
return OnError(ec, "accept");
}
auto session = SessionHandler::Create(std::move(socket), data_, *server_context_, endpoint_, inactivity_timeout_,
service_name_);
auto session = SessionHandler::Create(std::move(socket), session_context_, *server_context_, endpoint_,
inactivity_timeout_, service_name_);
session->Start();
DoAccept();
}
@@ -122,7 +123,7 @@ class Listener final : public std::enable_shared_from_this<Listener<TSession, TS
}
boost::asio::io_context &io_context_;
TSessionData *data_;
TSessionContext *session_context_;
ServerContext *server_context_;
tcp::acceptor acceptor_;

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -60,27 +60,27 @@ using ServerEndpoint = boost::asio::ip::tcp::endpoint;
* @tparam TSession the server can handle different Sessions, each session
* represents a different protocol so the same network infrastructure
* can be used for handling different protocols
* @tparam TSessionData the class with objects that will be forwarded to the
* @tparam TSessionContext the class with objects that will be forwarded to the
* session
*/
template <typename TSession, typename TSessionData>
template <typename TSession, typename TSessionContext>
class Server final {
using ServerHandler = Server<TSession, TSessionData>;
using ServerHandler = Server<TSession, TSessionContext>;
public:
/**
* Constructs and binds server to endpoint, operates on session data and
* invokes workers_count workers
*/
Server(ServerEndpoint &endpoint, TSessionData *session_data, ServerContext *server_context,
Server(ServerEndpoint &endpoint, TSessionContext *session_context, ServerContext *server_context,
const int inactivity_timeout_sec, const std::string_view service_name,
size_t workers_count = std::thread::hardware_concurrency())
: endpoint_{endpoint},
service_name_{service_name},
context_thread_pool_{workers_count},
listener_{Listener<TSession, TSessionData>::Create(context_thread_pool_.GetIOContext(), session_data,
server_context, endpoint_, service_name_,
inactivity_timeout_sec)} {}
listener_{Listener<TSession, TSessionContext>::Create(context_thread_pool_.GetIOContext(), session_context,
server_context, endpoint_, service_name_,
inactivity_timeout_sec)} {}
~Server() { MG_ASSERT(!IsRunning(), "Server wasn't shutdown properly"); }
@@ -122,7 +122,7 @@ class Server final {
std::string service_name_;
IOContextThreadPool context_thread_pool_;
std::shared_ptr<Listener<TSession, TSessionData>> listener_;
std::shared_ptr<Listener<TSession, TSessionContext>> listener_;
};
} // namespace memgraph::communication::v2

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -16,10 +16,12 @@
#include <cstdint>
#include <cstring>
#include <deque>
#include <exception>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <variant>
@@ -41,11 +43,23 @@
#include <boost/beast/websocket/rfc6455.hpp>
#include <boost/system/detail/error_code.hpp>
#include "communication/bolt/v1/session.hpp"
#include "communication/buffer.hpp"
#include "communication/context.hpp"
#include "communication/exceptions.hpp"
#include "dbms/global.hpp"
#include "utils/event_counter.hpp"
#include "utils/logging.hpp"
#include "utils/on_scope_exit.hpp"
#include "utils/variant_helpers.hpp"
namespace memgraph::metrics {
extern const Event ActiveSessions;
extern const Event ActiveTCPSessions;
extern const Event ActiveSSLSessions;
extern const Event ActiveWebSocketSessions;
} // namespace memgraph::metrics
namespace memgraph::communication::v2 {
/**
@@ -85,10 +99,10 @@ class OutputStream final {
* Websocket Sessions. It handles socket ownership, inactivity timeout and protocol
* wrapping.
*/
template <typename TSession, typename TSessionData>
class WebsocketSession : public std::enable_shared_from_this<WebsocketSession<TSession, TSessionData>> {
template <typename TSession, typename TSessionContext>
class WebsocketSession : public std::enable_shared_from_this<WebsocketSession<TSession, TSessionContext>> {
using WebSocket = boost::beast::websocket::stream<boost::beast::tcp_stream>;
using std::enable_shared_from_this<WebsocketSession<TSession, TSessionData>>::shared_from_this;
using std::enable_shared_from_this<WebsocketSession<TSession, TSessionContext>>::shared_from_this;
public:
template <typename... Args>
@@ -96,9 +110,20 @@ class WebsocketSession : public std::enable_shared_from_this<WebsocketSession<TS
return std::shared_ptr<WebsocketSession>(new WebsocketSession(std::forward<Args>(args)...));
}
#ifdef MG_ENTERPRISE
~WebsocketSession() { session_context_->Delete(session_); }
WebsocketSession(const WebsocketSession &) = delete;
WebsocketSession &operator=(const WebsocketSession &) = delete;
WebsocketSession(WebsocketSession &&) noexcept = delete;
WebsocketSession &operator=(WebsocketSession &&) noexcept = delete;
#endif
// Start the asynchronous accept operation
template <class Body, class Allocator>
void DoAccept(boost::beast::http::request<Body, boost::beast::http::basic_fields<Allocator>> req) {
memgraph::metrics::IncrementCounter(memgraph::metrics::ActiveWebSocketSessions);
execution_active_ = true;
// Set suggested timeout settings for the websocket
ws_.set_option(boost::beast::websocket::stream_base::timeout::suggested(boost::beast::role_type::server));
@@ -139,15 +164,20 @@ class WebsocketSession : public std::enable_shared_from_this<WebsocketSession<TS
private:
// Take ownership of the socket
explicit WebsocketSession(tcp::socket &&socket, TSessionData *data, tcp::endpoint endpoint,
explicit WebsocketSession(tcp::socket &&socket, TSessionContext *session_context, tcp::endpoint endpoint,
std::string_view service_name)
: ws_(std::move(socket)),
strand_{boost::asio::make_strand(ws_.get_executor())},
output_stream_([this](const uint8_t *data, size_t len, bool /*have_more*/) { return Write(data, len); }),
session_(data, endpoint, input_buffer_.read_end(), &output_stream_),
session_{*session_context, endpoint, input_buffer_.read_end(), &output_stream_},
session_context_{session_context},
endpoint_{endpoint},
remote_endpoint_{ws_.next_layer().socket().remote_endpoint()},
service_name_{service_name} {}
service_name_{service_name} {
#ifdef MG_ENTERPRISE
session_context_->Register(session_);
#endif
}
void OnAccept(boost::beast::error_code ec) {
if (ec) {
@@ -213,6 +243,10 @@ class WebsocketSession : public std::enable_shared_from_this<WebsocketSession<TS
if (!IsConnected()) {
return;
}
memgraph::metrics::DecrementCounter(memgraph::metrics::ActiveSessions);
memgraph::metrics::DecrementCounter(memgraph::metrics::ActiveWebSocketSessions);
if (ec) {
return OnError(ec, "close");
}
@@ -226,6 +260,7 @@ class WebsocketSession : public std::enable_shared_from_this<WebsocketSession<TS
communication::Buffer input_buffer_;
OutputStream output_stream_;
TSession session_;
TSessionContext *session_context_;
tcp::endpoint endpoint_;
tcp::endpoint remote_endpoint_;
std::string_view service_name_;
@@ -237,11 +272,11 @@ class WebsocketSession : public std::enable_shared_from_this<WebsocketSession<TS
* Sessions. It handles socket ownership, inactivity timeout and protocol
* wrapping.
*/
template <typename TSession, typename TSessionData>
class Session final : public std::enable_shared_from_this<Session<TSession, TSessionData>> {
template <typename TSession, typename TSessionContext>
class Session final : public std::enable_shared_from_this<Session<TSession, TSessionContext>> {
using TCPSocket = tcp::socket;
using SSLSocket = boost::asio::ssl::stream<TCPSocket>;
using std::enable_shared_from_this<Session<TSession, TSessionData>>::shared_from_this;
using std::enable_shared_from_this<Session<TSession, TSessionContext>>::shared_from_this;
public:
template <typename... Args>
@@ -249,22 +284,32 @@ class Session final : public std::enable_shared_from_this<Session<TSession, TSes
return std::shared_ptr<Session>(new Session(std::forward<Args>(args)...));
}
#ifdef MG_ENTERPRISE
~Session() { session_context_->Delete(session_); }
Session(const Session &) = delete;
Session(Session &&) = delete;
Session &operator=(const Session &) = delete;
Session &operator=(Session &&) = delete;
~Session() = default;
#endif
bool Start() {
if (execution_active_) {
return false;
}
memgraph::metrics::IncrementCounter(memgraph::metrics::ActiveSessions);
execution_active_ = true;
timeout_timer_.async_wait(boost::asio::bind_executor(strand_, std::bind(&Session::OnTimeout, shared_from_this())));
if (std::holds_alternative<SSLSocket>(socket_)) {
utils::OnScopeExit increment_counter(
[] { memgraph::metrics::IncrementCounter(memgraph::metrics::ActiveSSLSessions); });
boost::asio::dispatch(strand_, [shared_this = shared_from_this()] { shared_this->DoHandshake(); });
} else {
utils::OnScopeExit increment_counter(
[] { memgraph::metrics::IncrementCounter(memgraph::metrics::ActiveTCPSessions); });
boost::asio::dispatch(strand_, [shared_this = shared_from_this()] { shared_this->DoRead(); });
}
return true;
@@ -311,18 +356,22 @@ class Session final : public std::enable_shared_from_this<Session<TSession, TSes
}
private:
explicit Session(tcp::socket &&socket, TSessionData *data, ServerContext &server_context, tcp::endpoint endpoint,
const std::chrono::seconds inactivity_timeout_sec, std::string_view service_name)
explicit Session(tcp::socket &&socket, TSessionContext *session_context, ServerContext &server_context,
tcp::endpoint endpoint, const std::chrono::seconds inactivity_timeout_sec,
std::string_view service_name)
: socket_(CreateSocket(std::move(socket), server_context)),
strand_{boost::asio::make_strand(GetExecutor())},
output_stream_([this](const uint8_t *data, size_t len, bool have_more) { return Write(data, len, have_more); }),
session_(data, endpoint, input_buffer_.read_end(), &output_stream_),
data_{data},
session_{*session_context, endpoint, input_buffer_.read_end(), &output_stream_},
session_context_{session_context},
endpoint_{endpoint},
remote_endpoint_{GetRemoteEndpoint()},
service_name_{service_name},
timeout_seconds_(inactivity_timeout_sec),
timeout_timer_(GetExecutor()) {
#ifdef MG_ENTERPRISE
session_context_->Register(session_);
#endif
ExecuteForSocket([](auto &&socket) {
socket.lowest_layer().set_option(tcp::no_delay(true)); // enable PSH
socket.lowest_layer().set_option(boost::asio::socket_base::keep_alive(true)); // enable SO_KEEPALIVE
@@ -373,7 +422,8 @@ class Session final : public std::enable_shared_from_this<Session<TSession, TSes
spdlog::info("Switching {} to websocket connection", remote_endpoint_);
if (std::holds_alternative<TCPSocket>(socket_)) {
auto sock = std::get<TCPSocket>(std::move(socket_));
WebsocketSession<TSession, TSessionData>::Create(std::move(sock), data_, endpoint_, service_name_)
WebsocketSession<TSession, TSessionContext>::Create(std::move(sock), session_context_, endpoint_,
service_name_)
->DoAccept(parser.release());
execution_active_ = false;
return;
@@ -450,6 +500,14 @@ class Session final : public std::enable_shared_from_this<Session<TSession, TSes
}
void OnClose(const boost::system::error_code &ec) {
if (ssl_context_.has_value()) {
memgraph::metrics::DecrementCounter(memgraph::metrics::ActiveSSLSessions);
} else {
memgraph::metrics::DecrementCounter(memgraph::metrics::ActiveTCPSessions);
}
memgraph::metrics::DecrementCounter(memgraph::metrics::ActiveSessions);
if (ec) {
return OnError(ec);
}
@@ -465,7 +523,7 @@ class Session final : public std::enable_shared_from_this<Session<TSession, TSes
if (timeout_timer_.expiry() <= boost::asio::steady_timer::clock_type::now()) {
// The deadline has passed. Stop the session. The other actors will
// terminate as soon as possible.
spdlog::info("Shutting down session after {} of inactivity", timeout_seconds_);
spdlog::info("Shutting down session after {} seconds of inactivity", timeout_seconds_.count());
DoShutdown();
} else {
// Put the actor back to sleep.
@@ -504,7 +562,7 @@ class Session final : public std::enable_shared_from_this<Session<TSession, TSes
communication::Buffer input_buffer_;
OutputStream output_stream_;
TSession session_;
TSessionData *data_;
TSessionContext *session_context_;
tcp::endpoint endpoint_;
tcp::endpoint remote_endpoint_;
std::string_view service_name_;

18
src/csv/CMakeLists.txt Normal file
View File

@@ -0,0 +1,18 @@
add_library(mg-csv STATIC)
add_library(mg::csv ALIAS mg-csv)
target_sources(mg-csv
PUBLIC
include/csv/parsing.hpp
PRIVATE
parsing.cpp
)
target_include_directories(mg-csv PUBLIC include)
find_package(Boost REQUIRED COMPONENTS iostreams)
target_link_libraries(mg-csv
PUBLIC mg-utils
PRIVATE lib::ctre mg-requests Boost::iostreams
)
add_subdirectory(fuzz)

View File

@@ -0,0 +1,5 @@
add_executable(fuzz_csv EXCLUDE_FROM_ALL)
target_sources(fuzz_csv PRIVATE fuzz_reader.cpp)
target_link_libraries(fuzz_csv PRIVATE mg::csv)
target_compile_options(fuzz_csv PRIVATE -fsanitize=fuzzer)
target_link_libraries(fuzz_csv PRIVATE -fsanitize=fuzzer)

View File

@@ -0,0 +1,78 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include <cstdint>
#include <optional>
#include <sstream>
#include "csv/parsing.hpp"
#include "utils/string.hpp"
namespace mg = memgraph;
namespace csv = mg::csv;
using pmr_str = mg::utils::pmr::string;
extern "C" int LLVMFuzzerTestOneInput(std::uint8_t const *data, std::size_t size) {
// need to parse a config
if (size < 4) return 0;
auto const with_header = bool(data[0]);
auto const ignore_bad = bool(data[1]);
auto const delim_size = data[2];
auto const quote_size = data[3];
// 0 will be nullopt, everything else will be one smaller
// 0 , 1, 2, 3, 4
// nullopt, 0, 1, 2, 3
auto const delim_real_size = delim_size == 0 ? 0 : delim_size - 1;
auto const quote_real_size = quote_size == 0 ? 0 : quote_size - 1;
// not worth testing if too large
if (delim_real_size > 3 || quote_real_size > 3) return 0;
// is there enough space for them to exist
if (size < 4 + delim_real_size + quote_real_size) return 0;
auto const delim_start = 4;
auto const delim_end = delim_start + delim_real_size;
auto const quote_start = delim_end;
auto const quote_end = quote_start + quote_real_size;
auto *mem = mg::utils::NewDeleteResource();
auto delim = delim_size == 0 ? std::optional<pmr_str>{} : pmr_str(&data[delim_start], &data[delim_end], mem);
auto quote = quote_size == 0 ? std::optional<pmr_str>{} : pmr_str(&data[quote_start], &data[quote_end], mem);
auto const remaining = static_cast<int64_t>(size) - quote_end;
if (remaining < 0) __builtin_trap(); // if this hits, above parsing is wrong
// #############################################################################################################
// build Config
auto cfg = csv::Reader::Config{with_header, ignore_bad, std::move(delim), std::move(quote)};
// build CSV source
auto ss = std::stringstream{};
ss.write(reinterpret_cast<char const *>(&data[quote_end]), static_cast<std::streamsize>(remaining));
auto source = csv::StreamCsvSource{std::move(ss)};
// #############################################################################################################
try {
auto reader = memgraph::csv::Reader(std::move(source), std::move(cfg));
auto const header = reader.GetHeader();
asm volatile("" : : "g"(header) : "memory");
while (true) {
auto row = reader.GetNextRow(mem);
if (!row) break;
asm volatile("" : : "g"(row) : "memory");
}
} catch (csv::CsvReadException const &) {
// CsvReadException is ok
}
return 0;
}

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -23,6 +23,7 @@
#include <fstream>
#include <optional>
#include <string>
#include <variant>
#include <vector>
#include "utils/exceptions.hpp"
@@ -36,13 +37,53 @@ class CsvReadException : public utils::BasicException {
using utils::BasicException::BasicException;
};
class FileCsvSource {
public:
explicit FileCsvSource(std::filesystem::path path);
std::istream &GetStream();
private:
std::filesystem::path path_;
std::ifstream stream_;
};
class StreamCsvSource {
public:
StreamCsvSource(std::stringstream stream) : stream_{std::move(stream)} {}
std::istream &GetStream() { return stream_; }
private:
std::stringstream stream_;
};
class UrlCsvSource : public StreamCsvSource {
public:
UrlCsvSource(char const *url);
};
class CsvSource {
public:
static auto Create(utils::pmr::string const &csv_location) -> CsvSource;
CsvSource(FileCsvSource source) : source_{std::move(source)} {}
CsvSource(StreamCsvSource source) : source_{std::move(source)} {}
CsvSource(UrlCsvSource source) : source_{std::move(source)} {}
std::istream &GetStream();
private:
std::variant<FileCsvSource, UrlCsvSource, StreamCsvSource> source_;
};
class Reader {
public:
struct Config {
Config() = default;
Config(const bool with_header, const bool ignore_bad, std::optional<utils::pmr::string> delim,
std::optional<utils::pmr::string> qt)
: with_header(with_header), ignore_bad(ignore_bad), delimiter(std::move(delim)), quote(std::move(qt)) {}
: with_header(with_header), ignore_bad(ignore_bad), delimiter(std::move(delim)), quote(std::move(qt)) {
// delimiter + quote can not be empty
if (delimiter && delimiter->empty()) delimiter.reset();
if (quote && quote->empty()) quote.reset();
}
bool with_header{false};
bool ignore_bad{false};
@@ -53,16 +94,7 @@ class Reader {
using Row = utils::pmr::vector<utils::pmr::string>;
using Header = utils::pmr::vector<utils::pmr::string>;
Reader() = default;
explicit Reader(std::filesystem::path path, Config cfg, utils::MemoryResource *mem = utils::NewDeleteResource())
: memory_(mem), path_(std::move(path)) {
read_config_.with_header = cfg.with_header;
read_config_.ignore_bad = cfg.ignore_bad;
read_config_.delimiter = cfg.delimiter ? std::move(*cfg.delimiter) : utils::pmr::string{",", memory_};
read_config_.quote = cfg.quote ? std::move(*cfg.quote) : utils::pmr::string{"\"", memory_};
InitializeStream();
TryInitializeHeader();
}
explicit Reader(CsvSource source, Config cfg, utils::MemoryResource *mem = utils::NewDeleteResource());
Reader(const Reader &) = delete;
Reader &operator=(const Reader &) = delete;
@@ -81,28 +113,18 @@ class Reader {
};
using ParsingResult = utils::BasicResult<ParseError, Row>;
[[nodiscard]] bool HasHeader() const;
const Header &GetHeader() const;
std::optional<Row> GetNextRow(utils::MemoryResource *mem);
bool HasHeader() const;
auto GetHeader() const -> Header const &;
auto GetNextRow(utils::MemoryResource *mem) -> std::optional<Row>;
private:
utils::MemoryResource *memory_;
std::filesystem::path path_;
std::ifstream csv_stream_;
Config read_config_;
uint64_t line_count_{1};
uint16_t number_of_columns_{0};
Header header_{memory_};
void InitializeStream();
void TryInitializeHeader();
std::optional<utils::pmr::string> GetNextLine(utils::MemoryResource *mem);
ParsingResult ParseHeader();
ParsingResult ParseRow(utils::MemoryResource *mem);
// Some implementation issues that need clearing up, but this is mainly because
// I don't want `boost/iostreams/filtering_stream.hpp` included in this header file
// Because it causes issues when combined with antlr headers
// When we have C++20 modules this can be fixed
struct impl;
std::unique_ptr<impl, void (*)(impl *)> pimpl;
};
} // namespace memgraph::csv

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -9,33 +9,132 @@
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "utils/csv_parsing.hpp"
#include "csv/parsing.hpp"
#include <string_view>
#include <boost/iostreams/filter/bzip2.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <ctre/ctre.hpp>
#include "requests/requests.hpp"
#include "utils/file.hpp"
#include "utils/on_scope_exit.hpp"
#include "utils/string.hpp"
using PlainStream = boost::iostreams::filtering_istream;
namespace memgraph::csv {
using ParseError = Reader::ParseError;
void Reader::InitializeStream() {
if (!std::filesystem::exists(path_)) {
throw CsvReadException("CSV file not found: {}", path_.string());
}
csv_stream_.open(path_);
if (!csv_stream_.good()) {
throw CsvReadException("CSV file {} couldn't be opened!", path_.string());
}
struct Reader::impl {
impl(CsvSource source, Reader::Config cfg, utils::MemoryResource *mem);
[[nodiscard]] bool HasHeader() const { return read_config_.with_header; }
[[nodiscard]] auto Header() const -> Header const & { return header_; }
auto GetNextRow(utils::MemoryResource *mem) -> std::optional<Reader::Row>;
private:
void InitializeStream();
void TryInitializeHeader();
std::optional<utils::pmr::string> GetNextLine(utils::MemoryResource *mem);
ParsingResult ParseHeader();
ParsingResult ParseRow(utils::MemoryResource *mem);
utils::MemoryResource *memory_;
std::filesystem::path path_;
CsvSource source_;
PlainStream csv_stream_;
Config read_config_;
uint64_t line_count_{1};
uint16_t number_of_columns_{0};
Reader::Header header_{memory_};
};
Reader::impl::impl(CsvSource source, Reader::Config cfg, utils::MemoryResource *mem)
: memory_(mem), source_(std::move(source)) {
read_config_.with_header = cfg.with_header;
read_config_.ignore_bad = cfg.ignore_bad;
read_config_.delimiter = cfg.delimiter ? std::move(*cfg.delimiter) : utils::pmr::string{",", memory_};
read_config_.quote = cfg.quote ? std::move(*cfg.quote) : utils::pmr::string{"\"", memory_};
InitializeStream();
TryInitializeHeader();
}
std::optional<utils::pmr::string> Reader::GetNextLine(utils::MemoryResource *mem) {
enum class CompressionMethod : uint8_t {
NONE,
GZip,
BZip2,
};
/// Detect compression based on magic sequences
auto DetectCompressionMethod(std::istream &is) -> CompressionMethod {
// Ensure stream is reset
auto const on_exit = utils::OnScopeExit{[&]() { is.seekg(std::ios::beg); }};
// Note we must use bytes for comparison, not char
//
std::byte c{}; // this gets reused
auto const next_byte = [&](std::byte &b) { return bool(is.get(reinterpret_cast<char &>(b))); };
if (!next_byte(c)) return CompressionMethod::NONE;
auto const as_bytes = []<typename... Args>(Args... args) {
return std::array<std::byte, sizeof...(Args)>{std::byte(args)...};
};
// Gzip - 0x1F8B
constexpr static auto gzip_seq = as_bytes(0x1F, 0x8B);
if (c == gzip_seq[0]) {
if (!next_byte(c) || c != gzip_seq[1]) return CompressionMethod::NONE;
return CompressionMethod::GZip;
}
// BZip2 - 0x425A68
constexpr static auto bzip_seq = as_bytes(0x42, 0x5A, 0x68);
if (c == bzip_seq[0]) {
if (!next_byte(c) || c != bzip_seq[1]) return CompressionMethod::NONE;
if (!next_byte(c) || c != bzip_seq[2]) return CompressionMethod::NONE;
return CompressionMethod::BZip2;
}
return CompressionMethod::NONE;
}
Reader::Reader(CsvSource source, Reader::Config cfg, utils::MemoryResource *mem)
: pimpl{new impl{std::move(source), std::move(cfg), mem}, [](impl *p) { delete p; }} {}
void Reader::impl::InitializeStream() {
auto &source = source_.GetStream();
auto const method = DetectCompressionMethod(source);
switch (method) {
case CompressionMethod::GZip:
csv_stream_.push(boost::iostreams::gzip_decompressor{});
break;
case CompressionMethod::BZip2:
csv_stream_.push(boost::iostreams::bzip2_decompressor{});
break;
default:
break;
}
csv_stream_.push(source);
MG_ASSERT(csv_stream_.auto_close(), "Should be 'auto close' for correct operation");
MG_ASSERT(csv_stream_.is_complete(), "Should be 'complete' for correct operation");
}
std::optional<utils::pmr::string> Reader::impl::GetNextLine(utils::MemoryResource *mem) {
utils::pmr::string line(mem);
if (!std::getline(csv_stream_, line)) {
// reached end of file or an I/0 error occurred
if (!csv_stream_.good()) {
csv_stream_.close();
csv_stream_.reset(); // this will close the file_stream_ and clear the chain
}
return std::nullopt;
}
@@ -43,13 +142,13 @@ std::optional<utils::pmr::string> Reader::GetNextLine(utils::MemoryResource *mem
return std::move(line);
}
Reader::ParsingResult Reader::ParseHeader() {
Reader::ParsingResult Reader::impl::ParseHeader() {
// header must be the very first line in the file
MG_ASSERT(line_count_ == 1, "Invalid use of {}", __func__);
return ParseRow(memory_);
}
void Reader::TryInitializeHeader() {
void Reader::impl::TryInitializeHeader() {
if (!HasHeader()) {
return;
}
@@ -67,16 +166,16 @@ void Reader::TryInitializeHeader() {
header_ = std::move(*header);
}
[[nodiscard]] bool Reader::HasHeader() const { return read_config_.with_header; }
[[nodiscard]] bool Reader::HasHeader() const { return pimpl->HasHeader(); }
const Reader::Header &Reader::GetHeader() const { return header_; }
const Reader::Header &Reader::GetHeader() const { return pimpl->Header(); }
namespace {
enum class CsvParserState : uint8_t { INITIAL_FIELD, NEXT_FIELD, QUOTING, EXPECT_DELIMITER, DONE };
} // namespace
Reader::ParsingResult Reader::ParseRow(utils::MemoryResource *mem) {
Reader::ParsingResult Reader::impl::ParseRow(utils::MemoryResource *mem) {
utils::pmr::vector<utils::pmr::string> row(mem);
if (number_of_columns_ != 0) {
row.reserve(number_of_columns_);
@@ -140,9 +239,10 @@ Reader::ParsingResult Reader::ParseRow(utils::MemoryResource *mem) {
break;
}
case CsvParserState::QUOTING: {
const auto quote_size = read_config_.quote->size();
const auto quote_now = utils::StartsWith(line_string_view, *read_config_.quote);
const auto quote_next =
utils::StartsWith(line_string_view.substr(read_config_.quote->size()), *read_config_.quote);
const auto quote_next = quote_size <= line_string_view.size() &&
utils::StartsWith(line_string_view.substr(quote_size), *read_config_.quote);
if (quote_now && quote_next) {
// This is an escaped quote character.
column += *read_config_.quote;
@@ -216,12 +316,7 @@ Reader::ParsingResult Reader::ParseRow(utils::MemoryResource *mem) {
return std::move(row);
}
// Returns Reader::Row if the read row if valid;
// Returns std::nullopt if end of file is reached or an error occurred
// making it unreadable;
// @throws CsvReadException if a bad row is encountered, and the ignore_bad is set
// to 'true' in the Reader::Config.
std::optional<Reader::Row> Reader::GetNextRow(utils::MemoryResource *mem) {
std::optional<Reader::Row> Reader::impl::GetNextRow(utils::MemoryResource *mem) {
auto row = ParseRow(mem);
if (row.HasError()) {
@@ -245,4 +340,44 @@ std::optional<Reader::Row> Reader::GetNextRow(utils::MemoryResource *mem) {
return std::move(*row);
}
// Returns Reader::Row if the read row if valid;
// Returns std::nullopt if end of file is reached or an error occurred
// making it unreadable;
// @throws CsvReadException if a bad row is encountered, and the ignore_bad is set
// to 'true' in the Reader::Config.
std::optional<Reader::Row> Reader::GetNextRow(utils::MemoryResource *mem) { return pimpl->GetNextRow(mem); }
FileCsvSource::FileCsvSource(std::filesystem::path path) : path_(std::move(path)) {
if (!std::filesystem::exists(path_)) {
throw CsvReadException("CSV file not found: {}", path_.string());
}
stream_.open(path_);
if (!stream_.good()) {
throw CsvReadException("CSV file {} couldn't be opened!", path_.string());
}
}
std::istream &FileCsvSource::GetStream() { return stream_; }
std::istream &CsvSource::GetStream() {
return *std::visit([](auto &&source) { return std::addressof(source.GetStream()); }, source_);
}
auto CsvSource::Create(const utils::pmr::string &csv_location) -> CsvSource {
constexpr auto protocol_matcher = ctre::starts_with<"(https?|ftp)://">;
if (protocol_matcher(csv_location)) {
return csv::UrlCsvSource{csv_location.c_str()};
}
return csv::FileCsvSource{csv_location};
}
// Helper for UrlCsvSource
auto urlToStringStream(const char *url) -> std::stringstream {
auto ss = std::stringstream{};
if (!requests::DownloadToStream(url, ss)) {
throw CsvReadException("CSV was unable to be fetched from {}", url);
}
return ss;
};
UrlCsvSource::UrlCsvSource(const char *url) : StreamCsvSource{urlToStringStream(url)} {}
} // namespace memgraph::csv

3
src/dbms/CMakeLists.txt Normal file
View File

@@ -0,0 +1,3 @@
add_library(mg-dbms INTERFACE)
target_include_directories(mg-dbms INTERFACE .)
# target_link_libraries(mg-dbms INTERFACE mg-glue)

66
src/dbms/auth_handler.hpp Normal file
View File

@@ -0,0 +1,66 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#if 0 /* Disabled for now; we moved back to a single auth source */
#include <filesystem>
#include <memory>
#include <optional>
#include <unordered_map>
#include "auth/auth.hpp"
#include "global.hpp"
#include "glue/auth_checker.hpp"
#include "glue/auth_handler.hpp"
#include "utils/result.hpp"
#include "utils/rw_lock.hpp"
#include "utils/sync_ptr.hpp"
#include "handler.hpp"
namespace memgraph::dbms {
struct AuthContext {
explicit AuthContext(const std::filesystem::path &data_directory, const std::string &ah_flag = "")
: auth(data_directory / "auth"), auth_handler(&auth, ah_flag), auth_checker(&auth) {}
utils::Synchronized<auth::Auth, utils::WritePrioritizedRWLock> auth;
glue::AuthQueryHandler auth_handler;
glue::AuthChecker auth_checker;
};
struct AuthConfig {
std::filesystem::path storage_dir;
std::string ah_flag;
};
class AuthContextHandler : public Handler<AuthContext, AuthConfig> {
public:
using HandlerT = Handler<AuthContext, AuthConfig>;
typename HandlerT::NewResult New(const std::string &name, const std::filesystem::path &data_directory,
const std::string &ah_flag = "") {
// Check if compatible with the existing auth
if (std::any_of(cbegin(), cend(),
[&](const auto &elem) { return elem.second.config().storage_dir == data_directory; })) {
// LOG
return NewError::EXISTS;
}
return HandlerT::New(name, std::forward_as_tuple(data_directory, ah_flag),
std::forward_as_tuple(data_directory, ah_flag));
}
};
} // namespace memgraph::dbms
#endif

18
src/dbms/constants.hpp Normal file
View File

@@ -0,0 +1,18 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
namespace memgraph::dbms {
constexpr static const char *kDefaultDB = "memgraph"; //!< Name of the default database
} // namespace memgraph::dbms

117
src/dbms/global.hpp Normal file
View File

@@ -0,0 +1,117 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <concepts>
#include <cstdint>
#include <string>
#include "utils/exceptions.hpp"
namespace memgraph::dbms {
enum class DeleteError : uint8_t {
DEFAULT_DB,
USING,
NON_EXISTENT,
FAIL,
DISK_FAIL,
};
enum class NewError : uint8_t {
NO_CONFIGS,
EXISTS,
DEFUNCT,
GENERIC,
};
enum class SetForResult : uint8_t {
SUCCESS,
ALREADY_SET,
FAIL,
};
/**
* UnknownUser Exception
*
* Used to indicate that an unknown user was used.
*/
class UnknownUser : public utils::BasicException {
public:
using utils::BasicException::BasicException;
};
/**
* UnknownSession Exception
*
* Used to indicate that an unknown session was used.
*/
class UnknownSession : public utils::BasicException {
public:
using utils::BasicException::BasicException;
};
/**
* UnknownDatabase Exception
*
* Used to indicate that an unknown database was used.
*/
class UnknownDatabase : public utils::BasicException {
public:
using utils::BasicException::BasicException;
};
#ifdef MG_ENTERPRISE
/**
* @brief Session interface used by the DBMS to handle the the active sessions.
*/
class SessionInterface {
public:
SessionInterface() = default;
virtual ~SessionInterface() = default;
SessionInterface(const SessionInterface &) = default;
SessionInterface &operator=(const SessionInterface &) = default;
SessionInterface(SessionInterface &&) noexcept = default;
SessionInterface &operator=(SessionInterface &&) noexcept = default;
/**
* @brief Return the unique string identifying the session.
*
* @return std::string
*/
virtual std::string UUID() const = 0;
/**
* @brief Return the currently active database.
*
* @return std::string
*/
virtual std::string GetID() const = 0;
/**
* @brief Gets called on database change.
*
* @return true on success
*/
virtual dbms::SetForResult OnChange(const std::string &) = 0;
/**
* @brief Gets called on database delete (drop).
*
* @return true on success
*/
virtual bool OnDelete(const std::string &) = 0;
};
#endif
} // namespace memgraph::dbms

140
src/dbms/handler.hpp Normal file
View File

@@ -0,0 +1,140 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <filesystem>
#include <memory>
#include <optional>
#include <string_view>
#include <unordered_map>
#include "global.hpp"
#include "utils/result.hpp"
#include "utils/sync_ptr.hpp"
namespace memgraph::dbms {
/**
* @brief Generic multi-database content handler.
*
* @tparam TContext
* @tparam TConfig
*/
template <typename TContext, typename TConfig>
class Handler {
public:
using NewResult = utils::BasicResult<NewError, std::shared_ptr<TContext>>;
/**
* @brief Empty Handler constructor.
*
*/
Handler() {}
/**
* @brief Generate a new context and corresponding configuration.
*
* @tparam T1 Variadic template of context constructor arguments
* @tparam T2 Variadic template of config constructor arguments
* @param name Name associated with the new context/config pair
* @param args1 Arguments passed (as a tuple) to the context constructor
* @param args2 Arguments passed (as a tuple) to the config constructor
* @return NewResult
*/
template <typename... T1, typename... T2>
NewResult New(std::string name, std::tuple<T1...> args1, std::tuple<T2...> args2) {
return New_(name, args1, args2, std::make_index_sequence<sizeof...(T1)>{},
std::make_index_sequence<sizeof...(T2)>{});
}
/**
* @brief Get pointer to context.
*
* @param name Name associated with the wanted context
* @return std::optional<std::shared_ptr<TContext>>
*/
std::optional<std::shared_ptr<TContext>> Get(const std::string &name) {
if (auto search = items_.find(name); search != items_.end()) {
return search->second.get();
}
return {};
}
/**
* @brief Get the config.
*
* @param name Name associated with the wanted config
* @return std::optional<TConfig>
*/
std::optional<TConfig> GetConfig(const std::string &name) const {
if (auto search = items_.find(name); search != items_.end()) {
return search->second.config();
}
return {};
}
/**
* @brief Delete the context/config pair associated with the name.
*
* @param name Name associated with the context/config pair to delete
* @return true on success
*/
bool Delete(const std::string &name) {
if (auto itr = items_.find(name); itr != items_.end()) {
itr->second.DestroyAndSync();
items_.erase(itr);
return true;
}
return false;
}
/**
* @brief Check if a name is already used.
*
* @param name Name to check
* @return true if a context/config pair is already associated with the name
*/
bool Has(const std::string &name) const { return items_.find(name) != items_.end(); }
auto begin() { return items_.begin(); }
auto end() { return items_.end(); }
auto cbegin() const { return items_.cbegin(); }
auto cend() const { return items_.cend(); }
private:
/**
* @brief Lower level handler that hides some ugly code.
*
* @tparam T1 Variadic template of context constructor arguments
* @tparam T2 Variadic template of config constructor arguments
* @tparam I1 List of indexes associated with the first tuple
* @tparam I2 List of indexes associated with the second tuple
*/
template <typename... T1, typename... T2, std::size_t... I1, std::size_t... I2>
NewResult New_(std::string name, std::tuple<T1...> &args1, std::tuple<T2...> &args2,
std::integer_sequence<std::size_t, I1...> /*not-used*/,
std::integer_sequence<std::size_t, I2...> /*not-used*/) {
// Make sure the emplace will succeed, since we don't want to create temporary objects that could break something
if (!Has(name)) {
auto [itr, _] = items_.emplace(std::piecewise_construct, std::forward_as_tuple(name),
std::forward_as_tuple(TConfig{std::forward<T1>(std::get<I1>(args1))...},
std::forward<T2>(std::get<I2>(args2))...));
return itr->second.get();
}
spdlog::info("Item with name \"{}\" already exists.", name);
return NewError::EXISTS;
}
std::unordered_map<std::string, utils::SyncPtr<TContext, TConfig>> items_; //!< map to all active items
};
} // namespace memgraph::dbms

106
src/dbms/interp_handler.hpp Normal file
View File

@@ -0,0 +1,106 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#ifdef MG_ENTERPRISE
#include "global.hpp"
#include "query/auth_checker.hpp"
#include "query/config.hpp"
#include "query/interpreter.hpp"
#include "storage/v2/storage.hpp"
#include "handler.hpp"
namespace memgraph::dbms {
/**
* @brief Simple class that adds useful information to the query's InterpreterContext
*
* @tparam T Multi-database handler type
*/
template <typename T>
class ExpandedInterpContext : public query::InterpreterContext {
public:
template <typename... TArgs>
explicit ExpandedInterpContext(T &ref, TArgs &&...args)
: query::InterpreterContext(std::forward<TArgs>(args)...), sc_handler_(ref) {}
T &sc_handler_; //!< Multi-database/SessionContext handler (used in some queries)
};
/**
* @brief Simple structure that expands on the query's InterpreterConfig
*
*/
struct ExpandedInterpConfig {
storage::Config storage_config; //!< Storage configuration
query::InterpreterConfig interp_config; //!< Interpreter configuration
};
/**
* @brief Multi-database interpreter context handler
*
* @tparam TSCHandler High-level multi-database/SessionContext handler type
*/
template <typename TSCHandler>
class InterpContextHandler : public Handler<ExpandedInterpContext<TSCHandler>, ExpandedInterpConfig> {
public:
using InterpContextT = ExpandedInterpContext<TSCHandler>;
using HandlerT = Handler<InterpContextT, ExpandedInterpConfig>;
/**
* @brief Generate a new interpreter context associated with the passed name.
*
* @param name Name associating the new interpreter context
* @param sc_handler Multi-database/SessionContext handler used (some queries might use it)
* @param db Storage associated with the interpreter context
* @param config Interpreter's configuration
* @param dir Directory used by the interpreter
* @param auth_handler AuthQueryHandler used
* @param auth_checker AuthChecker used
* @return HandlerT::NewResult
*/
typename HandlerT::NewResult New(const std::string &name, TSCHandler &sc_handler, storage::Config storage_config,
const query::InterpreterConfig &interpreter_config,
query::AuthQueryHandler &auth_handler, query::AuthChecker &auth_checker) {
// Check if compatible with the existing interpreters
if (std::any_of(HandlerT::cbegin(), HandlerT::cend(), [&](const auto &elem) {
const auto &config = elem.second.config().storage_config;
return config.durability.storage_directory == storage_config.durability.storage_directory;
})) {
spdlog::info("Tried to generate a new context using claimed directory and/or storage.");
return NewError::EXISTS;
}
const auto dir = storage_config.durability.storage_directory;
storage_config.name = name; // Set storage id via config
return HandlerT::New(
name, std::forward_as_tuple(storage_config, interpreter_config),
std::forward_as_tuple(sc_handler, storage_config, interpreter_config, dir, &auth_handler, &auth_checker));
}
/**
* @brief All currently active storage.
*
* @return std::vector<std::string>
*/
std::vector<std::string> All() const {
std::vector<std::string> res;
res.reserve(std::distance(HandlerT::cbegin(), HandlerT::cend()));
std::for_each(HandlerT::cbegin(), HandlerT::cend(), [&](const auto &elem) { res.push_back(elem.first); });
return res;
}
};
} // namespace memgraph::dbms
#endif

View File

@@ -0,0 +1,65 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include "auth/auth.hpp"
#include "auth_handler.hpp"
#include "query/interpreter.hpp"
#include "storage/v2/storage.hpp"
#include "utils/synchronized.hpp"
#if MG_ENTERPRISE
#include "audit/log.hpp"
#endif
namespace memgraph::dbms {
/**
* @brief Structure encapsulating storage and interpreter context.
*
* @note Each session contains a copy.
*/
struct SessionContext {
// Explicit constructor here to ensure that pointers to all objects are
// supplied.
SessionContext(std::shared_ptr<memgraph::query::InterpreterContext> interpreter_context, std::string run,
// std::shared_ptr<AuthContext> auth_context
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth
#ifdef MG_ENTERPRISE
,
memgraph::audit::Log *audit_log
#endif
)
: interpreter_context(interpreter_context),
run_id(run),
// auth_context(auth_context),
// auth(&auth_context->auth)
auth(auth)
#ifdef MG_ENTERPRISE
,
audit_log(audit_log)
#endif
{
}
std::shared_ptr<memgraph::query::InterpreterContext> interpreter_context;
std::string run_id;
// std::shared_ptr<AuthContext> auth_context;
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth;
#ifdef MG_ENTERPRISE
memgraph::audit::Log *audit_log;
#endif
};
} // namespace memgraph::dbms

View File

@@ -0,0 +1,599 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <algorithm>
#include <concepts>
#include <cstdint>
#include <filesystem>
#include <memory>
#include <mutex>
#include <optional>
#include <ostream>
#include <stdexcept>
#include <system_error>
#include <unordered_map>
#include "constants.hpp"
#include "global.hpp"
#include "interp_handler.hpp"
#include "query/auth_checker.hpp"
#include "query/config.hpp"
#include "query/interpreter.hpp"
#include "session_context.hpp"
#include "spdlog/spdlog.h"
#include "storage/v2/durability/durability.hpp"
#include "storage/v2/durability/paths.hpp"
#include "utils/exceptions.hpp"
#include "utils/file.hpp"
#include "utils/logging.hpp"
#include "utils/result.hpp"
#include "utils/rw_lock.hpp"
#include "utils/synchronized.hpp"
#include "utils/uuid.hpp"
#include "handler.hpp"
namespace memgraph::dbms {
#ifdef MG_ENTERPRISE
using DeleteResult = utils::BasicResult<DeleteError>;
/**
* @brief Multi-database session contexts handler.
*/
class SessionContextHandler {
public:
using StorageT = storage::Storage;
using StorageConfigT = storage::Config;
// using InterpT = decltype(interp_handler_)::ExpandedInterpContext;
// using InterpConfigT = query::InterpreterConfig;
using LockT = utils::RWLock;
using NewResultT = utils::BasicResult<NewError, SessionContext>;
struct Config {
StorageConfigT storage_config; //!< Storage configuration
query::InterpreterConfig interp_config; //!< Interpreter context configuration
// std::string ah_flags; //!< glue::AuthHandler setup flags
std::function<void(utils::Synchronized<auth::Auth, utils::WritePrioritizedRWLock> *,
std::unique_ptr<query::AuthQueryHandler> &, std::unique_ptr<query::AuthChecker> &)>
glue_auth;
};
/**
* @brief Initialize the handler.
*
* @param audit_log pointer to the audit logger (ENTERPRISE only)
* @param configs storage and interpreter configurations
* @param recovery_on_startup restore databases (and its content) and authentication data
*/
SessionContextHandler(memgraph::audit::Log &audit_log, Config configs, bool recovery_on_startup, bool delete_on_drop)
: lock_{utils::RWLock::Priority::READ},
default_configs_(configs),
run_id_{utils::GenerateUUID()},
audit_log_(&audit_log),
delete_on_drop_(delete_on_drop) {
const auto &root = configs.storage_config.durability.storage_directory;
utils::EnsureDirOrDie(root);
// Verify that the user that started the process is the same user that is
// the owner of the storage directory.
storage::durability::VerifyStorageDirectoryOwnerAndProcessUserOrDie(root);
// Create the lock file and open a handle to it. This will crash the
// database if it can't open the file for writing or if any other process is
// holding the file opened.
lock_file_path_ = root / ".lock";
lock_file_handle_.Open(lock_file_path_, utils::OutputFile::Mode::OVERWRITE_EXISTING);
MG_ASSERT(lock_file_handle_.AcquireLock(),
"Couldn't acquire lock on the storage directory {}"
"!\nAnother Memgraph process is currently running with the same "
"storage directory, please stop it first before starting this "
"process!",
root);
// TODO: Figure out if this is needed/wanted
// Clear auth database since we are not recovering
// if (!recovery_on_startup) {
// const auto &auth_dir = root / "auth";
// // Backup if auth present
// if (utils::DirExists(auth_dir)) {
// auto backup_dir = root / storage::durability::kBackupDirectory;
// std::error_code error_code;
// utils::EnsureDirOrDie(backup_dir);
// std::error_code ec;
// const auto now = std::chrono::system_clock::now();
// std::ostringstream os;
// os << now.time_since_epoch().count();
// std::filesystem::rename(auth_dir, backup_dir / ("auth-" + os.str()), ec);
// MG_ASSERT(!ec, "Couldn't backup auth directory because of: {}", ec.message());
// spdlog::warn(
// "Since Memgraph was not supposed to recover on startup the authentication files will be "
// "overwritten. To prevent important data loss, Memgraph has stored those files into .backup directory "
// "inside the storage directory.");
// }
// // Clear
// if (std::filesystem::exists(auth_dir)) {
// std::filesystem::remove_all(auth_dir);
// }
// }
// Lazy initialization of auth_
auth_ = std::make_unique<utils::Synchronized<auth::Auth, utils::WritePrioritizedRWLock>>(root / "auth");
configs.glue_auth(auth_.get(), auth_handler_, auth_checker_);
// TODO: Decouple storage config from dbms config
// TODO: Save individual db configs inside the kvstore and restore from there
storage::UpdatePaths(default_configs_->storage_config,
default_configs_->storage_config.durability.storage_directory / "databases");
const auto &db_dir = default_configs_->storage_config.durability.storage_directory;
const auto durability_dir = db_dir / ".durability";
utils::EnsureDirOrDie(db_dir);
utils::EnsureDirOrDie(durability_dir);
durability_ = std::make_unique<kvstore::KVStore>(durability_dir);
// Generate the default database
MG_ASSERT(!NewDefault_().HasError(), "Failed while creating the default DB.");
// Recover previous databases
if (recovery_on_startup) {
for (const auto &[name, _] : *durability_) {
if (name == kDefaultDB) continue; // Already set
spdlog::info("Restoring database {}.", name);
MG_ASSERT(!New_(name).HasError(), "Failed while creating database {}.", name);
spdlog::info("Database {} restored.", name);
}
} else { // Clear databases from the durability list and auth
auto locked_auth = auth_->Lock();
for (const auto &[name, _] : *durability_) {
if (name == kDefaultDB) continue;
locked_auth->DeleteDatabase(name);
durability_->Delete(name);
}
}
}
void Shutdown() {
for (auto &ic : interp_handler_) memgraph::query::Shutdown(ic.second.get().get());
}
/**
* @brief Create a new SessionContext associated with the "name" database
*
* @param name name of the database
* @return NewResultT context on success, error on failure
*/
NewResultT New(const std::string &name) {
std::lock_guard<LockT> wr(lock_);
return New_(name, name);
}
/**
* @brief Get the context associated with the "name" database
*
* @param name
* @return SessionContext
* @throw UnknownDatabase if getting unknown database
*/
SessionContext Get(const std::string &name) {
std::shared_lock<LockT> rd(lock_);
return Get_(name);
}
/**
* @brief Set the undelying database for a particular session.
*
* @param uuid unique session identifier
* @param db_name unique database name
* @return SetForResult enum
* @throws UnknownDatabase, UnknownSession or anything OnChange throws
*/
SetForResult SetFor(const std::string &uuid, const std::string &db_name) {
std::shared_lock<LockT> rd(lock_);
(void)Get_(
db_name); // throws if db doesn't exist (TODO: Better to pass it via OnChange - but injecting dependency)
try {
auto &s = sessions_.at(uuid);
return s.OnChange(db_name);
} catch (std::out_of_range &) {
throw UnknownSession("Unknown session \"{}\"", uuid);
}
}
/**
* @brief Set the undelying database from a session itself. SessionContext handler.
*
* @param db_name unique database name
* @param handler function that gets called in place with the appropriate SessionContext
* @return SetForResult enum
*/
template <typename THandler>
requires std::invocable<THandler, SessionContext> SetForResult SetInPlace(const std::string &db_name,
THandler handler) {
std::shared_lock<LockT> rd(lock_);
return handler(Get_(db_name));
}
/**
* @brief Call void handler under a shared lock.
*
* @param handler function that gets called in place
*/
template <typename THandler>
requires std::invocable<THandler>
void CallInPlace(THandler handler) {
std::shared_lock<LockT> rd(lock_);
handler();
}
/**
* @brief Register an active session (used to handle callbacks).
*
* @param session
* @return true on success
*/
bool Register(SessionInterface &session) {
std::lock_guard<LockT> wr(lock_);
auto [_, success] = sessions_.emplace(session.UUID(), session);
return success;
}
/**
* @brief Delete a session.
*
* @param session
*/
bool Delete(const SessionInterface &session) {
std::lock_guard<LockT> wr(lock_);
return sessions_.erase(session.UUID()) > 0;
}
/**
* @brief Delete database.
*
* @param db_name database name
* @return DeleteResult error on failure
*/
DeleteResult Delete(const std::string &db_name) {
std::lock_guard<LockT> wr(lock_);
if (db_name == kDefaultDB) {
// MSG cannot delete the default db
return DeleteError::DEFAULT_DB;
}
// Check if db exists
try {
auto sc = Get_(db_name);
// Check if a session is using the db
if (!sc.interpreter_context->interpreters->empty()) {
return DeleteError::USING;
}
} catch (UnknownDatabase &) {
return DeleteError::NON_EXISTENT;
}
// High level handlers
for (auto &[_, s] : sessions_) {
if (!s.OnDelete(db_name)) {
spdlog::error("Partial failure while deleting database \"{}\".", db_name);
defunct_dbs_.emplace(db_name);
return DeleteError::FAIL;
}
}
// Low level handlers
const auto storage_path = StorageDir_(db_name);
MG_ASSERT(storage_path, "Missing storage for {}", db_name);
if (!interp_handler_.Delete(db_name)) {
spdlog::error("Partial failure while deleting database \"{}\".", db_name);
defunct_dbs_.emplace(db_name);
return DeleteError::FAIL;
}
// Remove from auth
auth_->Lock()->DeleteDatabase(db_name);
// Remove from durability list
if (durability_) durability_->Delete(db_name);
// Delete disk storage
if (delete_on_drop_) {
std::error_code ec;
(void)std::filesystem::remove_all(*storage_path, ec);
if (ec) {
spdlog::error("Failed to clean disk while deleting database \"{}\".", db_name);
defunct_dbs_.emplace(db_name);
return DeleteError::DISK_FAIL;
}
}
// Delete from defunct_dbs_ (in case a second delete call was successful)
defunct_dbs_.erase(db_name);
return {}; // Success
}
/**
* @brief Set the default configurations.
*
* @param configs storage, interpreter and authorization configurations
*/
void SetDefaultConfigs(Config configs) {
std::lock_guard<LockT> wr(lock_);
default_configs_ = configs;
}
/**
* @brief Get the default configurations.
*
* @return std::optional<Config>
*/
std::optional<Config> GetDefaultConfigs() const {
std::shared_lock<LockT> rd(lock_);
return default_configs_;
}
/**
* @brief Return all active databases.
*
* @return std::vector<std::string>
*/
std::vector<std::string> All() const {
std::shared_lock<LockT> rd(lock_);
return interp_handler_.All();
}
/**
* @brief Return the number of vertex across all databases.
*
* @return uint64_t
*/
std::tuple<uint64_t, uint64_t, uint64_t> Info() const {
// TODO: Handle overflow
uint64_t nv = 0;
uint64_t ne = 0;
std::shared_lock<LockT> rd(lock_);
const uint64_t ndb = std::distance(interp_handler_.cbegin(), interp_handler_.cend());
for (auto ic = interp_handler_.cbegin(); ic != interp_handler_.cend(); ++ic) {
const auto &info = ic->second.get()->db->GetInfo();
nv += info.vertex_count;
ne += info.edge_count;
}
return {nv, ne, ndb};
}
/**
* @brief Return the currently active database for a particular session.
*
* @param uuid session's unique identifier
* @return std::string name of the database
* @throw
*/
std::string Current(const std::string &uuid) const {
std::shared_lock<LockT> rd(lock_);
return sessions_.at(uuid).GetID();
}
/**
* @brief Restore triggers for all currently defined databases.
* @note: Triggers can execute query procedures, so we need to reload the modules first and then the triggers
*/
void RestoreTriggers() {
std::lock_guard<LockT> wr(lock_);
for (auto &ic_itr : interp_handler_) {
auto ic = ic_itr.second.get();
spdlog::debug("Restoring trigger for database \"{}\"", ic->db->id());
auto storage_accessor = ic->db->Access();
auto dba = memgraph::query::DbAccessor{storage_accessor.get()};
ic->trigger_store.RestoreTriggers(&ic->ast_cache, &dba, ic->config.query, ic->auth_checker);
}
}
/**
* @brief Restore streams of all currently defined databases.
* @note: Stream transformations are using modules, they have to be restored after the query modules are loaded.
*/
void RestoreStreams() {
std::lock_guard<LockT> wr(lock_);
for (auto &ic_itr : interp_handler_) {
auto ic = ic_itr.second.get();
spdlog::debug("Restoring streams for database \"{}\"", ic->db->id());
ic->streams.RestoreStreams();
}
}
private:
std::optional<std::filesystem::path> StorageDir_(const std::string &name) const {
const auto conf = interp_handler_.GetConfig(name);
if (conf) {
return conf->storage_config.durability.storage_directory;
}
spdlog::debug("Failed to find storage dir for database \"{}\"", name);
return {};
}
/**
* @brief Create a new SessionContext associated with the "name" database
*
* @param name name of the database
* @return NewResultT context on success, error on failure
*/
NewResultT New_(const std::string &name) { return New_(name, name); }
/**
* @brief Create a new SessionContext associated with the "name" database
*
* @param name name of the database
* @param storage_subdir undelying RocksDB directory
* @return NewResultT context on success, error on failure
*/
NewResultT New_(const std::string &name, std::filesystem::path storage_subdir) {
if (default_configs_) {
auto storage = default_configs_->storage_config;
storage::UpdatePaths(storage, storage.durability.storage_directory / storage_subdir);
return New_(name, storage, default_configs_->interp_config);
}
spdlog::info("Trying to generate session context without any configurations.");
return NewError::NO_CONFIGS;
}
/**
* @brief Create a new SessionContext associated with the "name" database
*
* @param name name of the database
* @param storage_config storage configuration
* @param inter_config interpreter configuration
* @return NewResultT context on success, error on failure
*/
NewResultT New_(const std::string &name, StorageConfigT &storage_config, query::InterpreterConfig &inter_config/*,
const std::string &ah_flags*/) {
MG_ASSERT(auth_handler_, "No high level AuthQueryHandler has been supplied.");
MG_ASSERT(auth_checker_, "No high level AuthChecker has been supplied.");
if (defunct_dbs_.contains(name)) {
spdlog::warn("Failed to generate database due to the unknown state of the previously defunct database \"{}\".",
name);
return NewError::DEFUNCT;
}
auto new_interp = interp_handler_.New(name, *this, storage_config, inter_config, *auth_handler_, *auth_checker_);
if (new_interp.HasValue()) {
// Success
if (durability_) durability_->Put(name, "ok");
return SessionContext{new_interp.GetValue(), run_id_, auth_.get(), audit_log_};
}
return new_interp.GetError();
}
/**
* @brief Create a new SessionContext associated with the default database
*
* @return NewResultT context on success, error on failure
*/
NewResultT NewDefault_() {
// Create the default DB in the root (this is how it was done pre multi-tenancy)
auto res = New_(kDefaultDB, "..");
if (res.HasValue()) {
// For back-compatibility...
// Recreate the dbms layout for the default db and symlink to the root
const auto dir = StorageDir_(kDefaultDB);
MG_ASSERT(dir, "Failed to find storage path.");
const auto main_dir = *dir / "databases" / kDefaultDB;
if (!std::filesystem::exists(main_dir)) {
std::filesystem::create_directory(main_dir);
}
// Force link on-disk directories
const auto conf = interp_handler_.GetConfig(kDefaultDB);
MG_ASSERT(conf, "No configuration for the default database.");
const auto &tmp_conf = conf->storage_config.disk;
std::vector<std::filesystem::path> to_link{
tmp_conf.main_storage_directory, tmp_conf.label_index_directory,
tmp_conf.label_property_index_directory, tmp_conf.unique_constraints_directory,
tmp_conf.name_id_mapper_directory, tmp_conf.id_name_mapper_directory,
tmp_conf.durability_directory, tmp_conf.wal_directory,
};
// Add in-memory paths
// Some directories are redundant (skip those)
const std::vector<std::string> skip{".lock", "audit_log", "auth", "databases", "internal_modules", "settings"};
for (auto const &item : std::filesystem::directory_iterator{*dir}) {
const auto dir_name = std::filesystem::relative(item.path(), item.path().parent_path());
if (std::find(skip.begin(), skip.end(), dir_name) != skip.end()) continue;
to_link.push_back(item.path());
}
// Symlink to root dir
for (auto const &item : to_link) {
const auto dir_name = std::filesystem::relative(item, item.parent_path());
const auto link = main_dir / dir_name;
const auto to = std::filesystem::relative(item, main_dir);
if (!std::filesystem::is_symlink(link) && !std::filesystem::exists(link)) {
std::filesystem::create_directory_symlink(to, link);
} else { // Check existing link
std::error_code ec;
const auto test_link = std::filesystem::read_symlink(link, ec);
if (ec || test_link != to) {
MG_ASSERT(false,
"Memgraph storage directory incompatible with new version.\n"
"Please use a clean directory or remove \"{}\" and try again.",
link.string());
}
}
}
}
return res;
}
/**
* @brief Get the context associated with the "name" database
*
* @param name
* @return SessionContext
* @throw UnknownDatabase if trying to get unknown database
*/
SessionContext Get_(const std::string &name) {
auto interp = interp_handler_.Get(name);
if (interp) {
return SessionContext{*interp, run_id_, auth_.get(), audit_log_};
}
throw UnknownDatabase("Tried to retrieve an unknown database \"{}\".", name);
}
// Should storage objects ever be deleted?
mutable LockT lock_; //!< protective lock
std::filesystem::path lock_file_path_; //!< Lock file protecting the main storage
utils::OutputFile lock_file_handle_; //!< Handler the lock (crash if already open)
InterpContextHandler<SessionContextHandler> interp_handler_; //!< multi-tenancy interpreter handler
// AuthContextHandler auth_handler_; //!< multi-tenancy authorization handler (currently we use a single global
// auth)
std::unique_ptr<utils::Synchronized<auth::Auth, utils::WritePrioritizedRWLock>> auth_;
std::unique_ptr<query::AuthQueryHandler> auth_handler_;
std::unique_ptr<query::AuthChecker> auth_checker_;
std::optional<Config> default_configs_; //!< default storage and interpreter configurations
const std::string run_id_; //!< run's unique identifier (auto generated)
memgraph::audit::Log *audit_log_; //!< pointer to the audit logger
std::unordered_map<std::string, SessionInterface &> sessions_; //!< map of active/registered sessions
std::unique_ptr<kvstore::KVStore> durability_; //!< list of active dbs (pointer so we can postpone its creation)
std::set<std::string> defunct_dbs_; //!< Databases that are in an unknown state due to various failures
bool delete_on_drop_; //!< Flag defining if dropping storage also deletes its directory
public:
static SessionContextHandler &ExtractSCH(query::InterpreterContext *interpreter_context) {
return static_cast<typename decltype(interp_handler_)::InterpContextT *>(interpreter_context)->sc_handler_;
}
};
#else
/**
* @brief Initialize the handler.
*
* @param auth pointer to the authenticator
* @param configs storage and interpreter configurations
*/
static inline SessionContext Init(storage::Config &storage_config, query::InterpreterConfig &interp_config,
utils::Synchronized<auth::Auth, utils::WritePrioritizedRWLock> *auth,
query::AuthQueryHandler *auth_handler, query::AuthChecker *auth_checker) {
MG_ASSERT(auth, "Passed a nullptr auth");
MG_ASSERT(auth_handler, "Passed a nullptr auth_handler");
MG_ASSERT(auth_checker, "Passed a nullptr auth_checker");
auto interp_context = std::make_shared<query::InterpreterContext>(
storage_config, interp_config, storage_config.durability.storage_directory, auth_handler, auth_checker);
MG_ASSERT(interp_context, "Failed to construct main interpret context.");
return SessionContext{interp_context, utils::GenerateUUID(), auth};
}
#endif
} // namespace memgraph::dbms

View File

@@ -0,0 +1,77 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#if 0
#pragma once
#ifdef MG_ENTERPRISE
#include <algorithm>
#include <filesystem>
#include <iterator>
#include <memory>
#include <optional>
#include <string_view>
#include <unordered_map>
#include "global.hpp"
#include "storage/v2/storage.hpp"
#include "utils/result.hpp"
#include "handler.hpp"
namespace memgraph::dbms {
/**
* @brief Multi-database storage handler
*
*/
class StorageHandler : public Handler<storage::Storage, storage::Config> {
public:
using HandlerT = Handler<storage::Storage, storage::Config>;
/**
* @brief Generate new storage associated with the passed name.
*
* @param name Name associating the new interpreter context
* @param config Storage configuration
* @return HandlerT::NewResult
*/
HandlerT::NewResult New(const std::string &name, const storage::Config &config) {
// Control that no one is using the same data directory
if (std::any_of(cbegin(), cend(), [&](const auto &elem) {
return elem.second.config().durability.storage_directory == config.durability.storage_directory;
})) {
spdlog::info("Tried to generate new storage using a claimed directory.");
return NewError::EXISTS;
}
return HandlerT::New(name, std::forward_as_tuple(config), std::forward_as_tuple(config, name));
}
/**
* @brief All currently active storage.
*
* @return std::vector<std::string>
*/
std::vector<std::string> All() const {
std::vector<std::string> res;
res.reserve(std::distance(cbegin(), cend()));
std::for_each(cbegin(), cend(), [&](const auto &elem) { res.push_back(elem.first); });
return res;
}
};
} // namespace memgraph::dbms
#endif
#endif

View File

@@ -62,6 +62,10 @@ auth::Permission PrivilegeToPermission(query::AuthQuery::Privilege privilege) {
return auth::Permission::STORAGE_MODE;
case query::AuthQuery::Privilege::TRANSACTION_MANAGEMENT:
return auth::Permission::TRANSACTION_MANAGEMENT;
case query::AuthQuery::Privilege::MULTI_DATABASE_EDIT:
return auth::Permission::MULTI_DATABASE_EDIT;
case query::AuthQuery::Privilege::MULTI_DATABASE_USE:
return auth::Permission::MULTI_DATABASE_USE;
}
}

View File

@@ -71,19 +71,21 @@ AuthChecker::AuthChecker(
: 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;
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
const std::string &db_name) const {
{
auto locked_auth = auth_->ReadLock();
if (!locked_auth->HasUsers()) {
return true;
}
if (username.has_value()) {
maybe_user = locked_auth->GetUser(*username);
if (username.has_value() && username != user_.username()) {
const auto maybe_user = locked_auth->GetUser(*username);
if (!maybe_user) return false;
user_ = *maybe_user;
}
}
return maybe_user.has_value() && IsUserAuthorized(*maybe_user, privileges);
return IsUserAuthorized(user_, privileges, db_name);
}
#ifdef MG_ENTERPRISE
@@ -94,12 +96,14 @@ std::unique_ptr<memgraph::query::FineGrainedAuthChecker> AuthChecker::GetFineGra
}
try {
auto locked_auth = auth_->Lock();
auto user = locked_auth->GetUser(username);
if (!user) {
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
if (username != user_.username()) {
auto maybe_user = locked_auth->GetUser(username);
if (!maybe_user) {
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
}
user_ = *maybe_user;
}
return std::make_unique<memgraph::glue::FineGrainedAuthChecker>(std::move(*user), dba);
return std::make_unique<memgraph::glue::FineGrainedAuthChecker>(user_, dba);
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
@@ -108,7 +112,13 @@ std::unique_ptr<memgraph::query::FineGrainedAuthChecker> AuthChecker::GetFineGra
#endif
bool AuthChecker::IsUserAuthorized(const memgraph::auth::User &user,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) {
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
const std::string &db_name) { // NOLINT
#ifdef MG_ENTERPRISE
if (!db_name.empty() && !user.db_access().Contains(db_name)) {
return false;
}
#endif
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)) ==

View File

@@ -25,7 +25,8 @@ class AuthChecker : public query::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;
const std::vector<query::AuthQuery::Privilege> &privileges,
const std::string &db_name) const override;
#ifdef MG_ENTERPRISE
std::unique_ptr<memgraph::query::FineGrainedAuthChecker> GetFineGrainedAuthChecker(
@@ -33,10 +34,12 @@ class AuthChecker : public query::AuthChecker {
#endif
[[nodiscard]] static bool IsUserAuthorized(const memgraph::auth::User &user,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges);
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
const std::string &db_name = "");
private:
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
mutable auth::User user_;
};
#ifdef MG_ENTERPRISE
class FineGrainedAuthChecker : public query::FineGrainedAuthChecker {

View File

@@ -122,6 +122,29 @@ std::vector<std::vector<memgraph::query::TypedValue>> ShowRolePrivileges(
}
#ifdef MG_ENTERPRISE
std::vector<std::vector<memgraph::query::TypedValue>> ShowDatabasePrivileges(
const std::optional<memgraph::auth::User> &user) {
if (!memgraph::license::global_license_checker.IsEnterpriseValidFast() || !user) {
return {};
}
const auto &db = user->db_access();
const auto &allows = db.GetAllowAll();
const auto &grants = db.GetGrants();
const auto &denies = db.GetDenies();
std::vector<memgraph::query::TypedValue> res; // First element is a list of granted databases, second of revoked ones
if (allows) {
res.emplace_back("*");
} else {
std::vector<memgraph::query::TypedValue> grants_vec(grants.cbegin(), grants.cend());
res.emplace_back(grants_vec);
}
std::vector<memgraph::query::TypedValue> denies_vec(denies.cbegin(), denies.cend());
res.emplace_back(denies_vec);
return {res};
}
std::vector<FineGrainedPermissionForPrivilegeResult> GetFineGrainedPermissionForPrivilegeForUserOrRole(
const memgraph::auth::FineGrainedAccessPermissions &permissions, const std::string &permission_type,
const std::string &user_or_role) {
@@ -319,6 +342,67 @@ bool AuthQueryHandler::CreateRole(const std::string &rolename) {
}
}
#ifdef MG_ENTERPRISE
bool AuthQueryHandler::RevokeDatabaseFromUser(const std::string &db, const std::string &username) {
if (!std::regex_match(username, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user name.");
}
try {
auto locked_auth = auth_->Lock();
auto user = locked_auth->GetUser(username);
if (!user) return false;
return locked_auth->RevokeDatabaseFromUser(db, username);
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
bool AuthQueryHandler::GrantDatabaseToUser(const std::string &db, const std::string &username) {
if (!std::regex_match(username, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user name.");
}
try {
auto locked_auth = auth_->Lock();
auto user = locked_auth->GetUser(username);
if (!user) return false;
return locked_auth->GrantDatabaseToUser(db, username);
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
std::vector<std::vector<memgraph::query::TypedValue>> AuthQueryHandler::GetDatabasePrivileges(
const std::string &username) {
if (!std::regex_match(username, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user or role name.");
}
try {
auto locked_auth = auth_->ReadLock();
auto user = locked_auth->GetUser(username);
if (!user) {
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist.", username);
}
return ShowDatabasePrivileges(user);
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
bool AuthQueryHandler::SetMainDatabase(const std::string &db, const std::string &username) {
if (!std::regex_match(username, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user name.");
}
try {
auto locked_auth = auth_->Lock();
auto user = locked_auth->GetUser(username);
if (!user) return false;
return locked_auth->SetMainDatabase(db, username);
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
#endif
bool AuthQueryHandler::DropRole(const std::string &rolename) {
if (!std::regex_match(rolename, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid role name.");
@@ -463,6 +547,7 @@ std::vector<std::vector<memgraph::query::TypedValue>> AuthQueryHandler::GetPrivi
std::vector<std::vector<memgraph::query::TypedValue>> grants;
#ifdef MG_ENTERPRISE
std::vector<std::vector<memgraph::query::TypedValue>> fine_grained_grants;
std::vector<std::vector<memgraph::query::TypedValue>> database_grants;
#endif
auto user = locked_auth->GetUser(user_or_role);
auto role = locked_auth->GetRole(user_or_role);

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -38,6 +38,16 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
void SetPassword(const std::string &username, const std::optional<std::string> &password) override;
#ifdef MG_ENTERPRISE
bool RevokeDatabaseFromUser(const std::string &db, const std::string &username) override;
bool GrantDatabaseToUser(const std::string &db, const std::string &username) override;
std::vector<std::vector<memgraph::query::TypedValue>> GetDatabasePrivileges(const std::string &username) override;
bool SetMainDatabase(const std::string &db, const std::string &username) override;
#endif
bool CreateRole(const std::string &rolename) override;
bool DropRole(const std::string &rolename) override;

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -150,7 +150,9 @@ storage::Result<communication::bolt::Vertex> ToBoltVertex(const storage::VertexA
for (const auto &prop : *maybe_properties) {
properties[db.PropertyToName(prop.first)] = ToBoltValue(prop.second);
}
return communication::bolt::Vertex{id, labels, properties};
// Introduced in Bolt v5 (for now just send the ID)
const auto element_id = std::to_string(id.AsInt());
return communication::bolt::Vertex{id, labels, properties, element_id};
}
storage::Result<communication::bolt::Edge> ToBoltEdge(const storage::EdgeAccessor &edge, const storage::Storage &db,
@@ -165,7 +167,11 @@ storage::Result<communication::bolt::Edge> ToBoltEdge(const storage::EdgeAccesso
for (const auto &prop : *maybe_properties) {
properties[db.PropertyToName(prop.first)] = ToBoltValue(prop.second);
}
return communication::bolt::Edge{id, from, to, type, properties};
// Introduced in Bolt v5 (for now just send the ID)
const auto element_id = std::to_string(id.AsInt());
const auto from_element_id = std::to_string(from.AsInt());
const auto to_element_id = std::to_string(to.AsInt());
return communication::bolt::Edge{id, from, to, type, properties, element_id, from_element_id, to_element_id};
}
storage::Result<communication::bolt::Path> ToBoltPath(const query::Path &path, const storage::Storage &db,

View File

@@ -0,0 +1,4 @@
set(mg_http_handlers_sources)
add_library(mg-http-handlers STATIC ${mg_http_handlers_sources})
target_link_libraries(mg-http-handlers mg-query mg-storage-v2)

View File

@@ -0,0 +1,211 @@
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include <atomic>
#include <tuple>
#include <vector>
#include <spdlog/spdlog.h>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <json/json.hpp>
#include <utils/event_counter.hpp>
#include <utils/event_gauge.hpp>
#include "storage/v2/storage.hpp"
#include "utils/event_gauge.hpp"
#include "utils/event_histogram.hpp"
namespace memgraph::http {
struct MetricsResponse {
uint64_t vertex_count;
uint64_t edge_count;
double average_degree;
uint64_t memory_usage;
uint64_t disk_usage;
// Storage of all the counter values throughout the system
// e.g. number of active transactions
std::vector<std::tuple<std::string, std::string, uint64_t>> event_counters{};
// Storage of all the current values throughout the system
std::vector<std::tuple<std::string, std::string, uint64_t>> event_gauges{};
// Storage of all the percentile values across the histograms in the system
// e.g. query latency percentiles, snapshot recovery duration percentiles, etc.
std::vector<std::tuple<std::string, std::string, uint64_t>> event_histograms{};
};
template <typename TSessionContext>
class MetricsService {
public:
explicit MetricsService(TSessionContext *session_context) : db_(session_context->interpreter_context->db.get()) {}
nlohmann::json GetMetricsJSON() {
auto response = GetMetrics();
return AsJson(response);
}
private:
const storage::Storage *db_;
MetricsResponse GetMetrics() {
auto info = db_->GetInfo();
return MetricsResponse{.vertex_count = info.vertex_count,
.edge_count = info.edge_count,
.average_degree = info.average_degree,
.memory_usage = info.memory_usage,
.disk_usage = info.disk_usage,
.event_counters = GetEventCounters(),
.event_gauges = GetEventGauges(),
.event_histograms = GetEventHistograms()};
}
nlohmann::json AsJson(MetricsResponse response) {
auto metrics_response = nlohmann::json();
const auto *general_type = "General";
metrics_response[general_type]["vertex_count"] = response.vertex_count;
metrics_response[general_type]["edge_count"] = response.edge_count;
metrics_response[general_type]["average_degree"] = response.average_degree;
metrics_response[general_type]["memory_usage"] = response.memory_usage;
metrics_response[general_type]["disk_usage"] = response.disk_usage;
for (const auto &[name, type, value] : response.event_counters) {
metrics_response[type][name] = value;
}
for (const auto &[name, type, value] : response.event_gauges) {
metrics_response[type][name] = value;
}
for (const auto &[name, type, value] : response.event_histograms) {
metrics_response[type][name] = value;
}
return metrics_response;
}
auto GetEventCounters() {
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
std::vector<std::tuple<std::string, std::string, uint64_t>> event_counters{};
for (auto i = 0; i < memgraph::metrics::CounterEnd(); i++) {
event_counters.emplace_back(memgraph::metrics::GetCounterName(i), memgraph::metrics::GetCounterType(i),
memgraph::metrics::global_counters[i].load(std::memory_order_acquire));
}
return event_counters;
}
auto GetEventGauges() {
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
std::vector<std::tuple<std::string, std::string, uint64_t>> event_gauges{};
for (auto i = 0; i < memgraph::metrics::GaugeEnd(); i++) {
event_gauges.emplace_back(memgraph::metrics::GetGaugeName(i), memgraph::metrics::GetGaugeType(i),
memgraph::metrics::global_gauges[i].load(std::memory_order_acquire));
}
return event_gauges;
}
auto GetEventHistograms() {
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
std::vector<std::tuple<std::string, std::string, uint64_t>> event_histograms{};
for (auto i = 0; i < memgraph::metrics::HistogramEnd(); i++) {
const auto *name = memgraph::metrics::GetHistogramName(i);
auto &histogram = memgraph::metrics::global_histograms[i];
for (auto &[percentile, value] : histogram.YieldPercentiles()) {
auto metric_name = std::string(name) + "_" + std::to_string(percentile) + "p";
event_histograms.emplace_back(metric_name, memgraph::metrics::GetHistogramType(i), value);
}
}
return event_histograms;
}
};
template <typename TSessionContext>
class MetricsRequestHandler final {
public:
explicit MetricsRequestHandler(TSessionContext *session_context) : service_(session_context) {
spdlog::info("Basic request handler started!");
}
MetricsRequestHandler(const MetricsRequestHandler &) = delete;
MetricsRequestHandler(MetricsRequestHandler &&) = delete;
MetricsRequestHandler &operator=(const MetricsRequestHandler &) = delete;
MetricsRequestHandler &operator=(MetricsRequestHandler &&) = delete;
~MetricsRequestHandler() = default;
template <class Body, class Allocator>
// NOLINTNEXTLINE(misc-unused-parameters)
void HandleRequest(boost::beast::http::request<Body, boost::beast::http::basic_fields<Allocator>> &&req,
std::function<void(boost::beast::http::response<boost::beast::http::string_body>)> &&send) {
auto response_json = nlohmann::json();
// Returns a bad request response
auto const bad_request = [&req, &response_json](const auto why) {
response_json["error"] = std::string(why);
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
boost::beast::http::response<boost::beast::http::string_body> res{boost::beast::http::status::bad_request,
req.version()};
res.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(boost::beast::http::field::content_type, "application/json");
res.keep_alive(req.keep_alive());
res.body() = response_json.dump();
res.prepare_payload();
return res;
};
// Make sure we can handle the method
if (req.method() != boost::beast::http::verb::get) {
return send(bad_request("Unknown HTTP-method"));
}
// Request path must be absolute and not contain "..".
if (req.target().empty() || req.target()[0] != '/' || req.target().find("..") != boost::beast::string_view::npos) {
return send(bad_request("Illegal request-target"));
}
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
boost::beast::http::string_body::value_type body;
auto service_response = service_.GetMetricsJSON();
body.append(service_response.dump());
// Cache the size since we need it after the move
const auto size = body.size();
// Respond to GET request
// NOLINTNEXTLINE(cppcoreguidelines-init-variables)
boost::beast::http::response<boost::beast::http::string_body> res{
std::piecewise_construct, std::make_tuple(std::move(body)),
std::make_tuple(boost::beast::http::status::ok, req.version())};
res.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(boost::beast::http::field::content_type, "application/json");
res.content_length(size);
res.keep_alive(req.keep_alive());
return send(std::move(res));
}
private:
MetricsService<TSessionContext> service_;
};
} // namespace memgraph::http

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -9,6 +9,8 @@
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include <iostream>
#include <rocksdb/db.h>
#include <rocksdb/options.h>
@@ -36,7 +38,13 @@ KVStore::KVStore(std::filesystem::path storage) : pimpl_(std::make_unique<impl>(
pimpl_->db.reset(db);
}
KVStore::~KVStore() {}
KVStore::~KVStore() {
spdlog::debug("Destroying KVStore at {}", pimpl_->storage.string());
const auto sync = pimpl_->db->SyncWAL();
if (!sync.ok()) spdlog::error("KVStore sync failed!");
const auto close = pimpl_->db->Close();
if (!close.ok()) spdlog::error("KVStore close failed!");
}
KVStore::KVStore(KVStore &&other) { pimpl_ = std::move(other.pimpl_); }

View File

@@ -34,13 +34,19 @@
#include <spdlog/sinks/dist_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include "audit/log.hpp"
#include "auth/models.hpp"
#include "communication/bolt/v1/constants.hpp"
#include "communication/http/server.hpp"
#include "communication/websocket/auth.hpp"
#include "communication/websocket/server.hpp"
#include "dbms/constants.hpp"
#include "dbms/global.hpp"
#include "dbms/session_context.hpp"
#include "glue/auth_checker.hpp"
#include "glue/auth_handler.hpp"
#include "helpers.hpp"
#include "http_handlers/metrics.hpp"
#include "license/license.hpp"
#include "license/license_sender.hpp"
#include "py/py.hpp"
@@ -53,6 +59,9 @@
#include "query/procedure/module.hpp"
#include "query/procedure/py_module.hpp"
#include "requests/requests.hpp"
#include "storage/v2/config.hpp"
#include "storage/v2/disk/storage.hpp"
#include "storage/v2/inmemory/storage.hpp"
#include "storage/v2/isolation_level.hpp"
#include "storage/v2/storage.hpp"
#include "storage/v2/view.hpp"
@@ -91,15 +100,12 @@
#include "communication/init.hpp"
#include "communication/v2/server.hpp"
#include "communication/v2/session.hpp"
#include "dbms/session_context_handler.hpp"
#include "glue/communication.hpp"
#include "auth/auth.hpp"
#include "glue/auth.hpp"
#ifdef MG_ENTERPRISE
#include "audit/log.hpp"
#endif
constexpr const char *kMgUser = "MEMGRAPH_USER";
constexpr const char *kMgPassword = "MEMGRAPH_PASSWORD";
constexpr const char *kMgPassfile = "MEMGRAPH_PASSFILE";
@@ -113,6 +119,9 @@ DEFINE_string(bolt_address, "0.0.0.0", "IP address on which the Bolt server shou
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_string(monitoring_address, "0.0.0.0",
"IP address on which the websocket server for Memgraph monitoring should listen.");
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_string(metrics_address, "0.0.0.0",
"IP address on which the Memgraph server for exposing metrics should listen.");
DEFINE_VALIDATED_int32(bolt_port, 7687, "Port on which the Bolt server should listen.",
FLAG_IN_RANGE(0, std::numeric_limits<uint16_t>::max()));
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
@@ -120,6 +129,9 @@ DEFINE_VALIDATED_int32(monitoring_port, 7444,
"Port on which the websocket server for Memgraph monitoring should listen.",
FLAG_IN_RANGE(0, std::numeric_limits<uint16_t>::max()));
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_VALIDATED_int32(metrics_port, 9091, "Port on which the Memgraph server for exposing metrics should listen.",
FLAG_IN_RANGE(0, std::numeric_limits<uint16_t>::max()));
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_VALIDATED_int32(bolt_num_workers, std::max(std::thread::hardware_concurrency(), 1U),
"Number of workers used by the Bolt server. By default, this will be the "
"number of processing units available on the machine.",
@@ -148,6 +160,10 @@ DEFINE_string(init_data_file, "", "Path to cypherl file that is used for creatin
// `mg_import_csv`. If you change it, make sure to change it there as well.
// 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_bool(data_recover_on_startup, false, "Controls whether the database recovers persisted data on startup.");
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_uint64(memory_warning_threshold, 1024,
"Memory warning threshold, in MB. If Memgraph detects there is "
@@ -165,8 +181,11 @@ DEFINE_VALIDATED_uint64(storage_gc_cycle_sec, 30, "Storage garbage collector int
// `mg_import_csv`. If you change it, make sure to change it there as well.
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_bool(storage_properties_on_edges, false, "Controls whether edges have properties.");
// storage_recover_on_startup deprecated; use data_recover_on_startup instead
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_bool(storage_recover_on_startup, false, "Controls whether the storage recovers persisted data on startup.");
DEFINE_HIDDEN_bool(storage_recover_on_startup, false,
"Controls whether the storage recovers persisted data on startup.");
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_VALIDATED_uint64(storage_snapshot_interval_sec, 0,
"Storage snapshot creation interval (in seconds). Set "
@@ -192,6 +211,26 @@ DEFINE_VALIDATED_uint64(storage_wal_file_flush_every_n_tx,
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_bool(storage_snapshot_on_exit, false, "Controls whether the storage creates another snapshot on exit.");
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_uint64(storage_items_per_batch, memgraph::storage::Config::Durability().items_per_batch,
"The number of edges and vertices stored in a batch in a snapshot file.");
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_bool(storage_parallel_index_recovery, false,
"Controls whether the index creation can be done in a multithreaded fashion.");
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_uint64(storage_recovery_thread_count,
std::max(static_cast<uint64_t>(std::thread::hardware_concurrency()),
memgraph::storage::Config::Durability().recovery_thread_count),
"The number of threads used to recover persisted data from disk.");
#ifdef MG_ENTERPRISE
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_bool(storage_delete_on_drop, true,
"If set to true the query 'DROP DATABASE x' will delete the underlying storage as well.");
#endif
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_bool(telemetry_enabled, false,
"Set to true to enable telemetry. We collect information about the "
@@ -239,6 +278,8 @@ DEFINE_double(query_execution_timeout_sec, 600,
DEFINE_uint64(replication_replica_check_frequency_sec, 1,
"The time duration between two replica checks/pings. If < 1, replicas will NOT be checked at all. NOTE: "
"The MAIN instance allocates a new thread for each REPLICA.");
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_bool(replication_restore_state_on_startup, false, "Restore replication state on startup, e.g. recover replica");
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_uint64(
@@ -416,122 +457,219 @@ void AddLoggerSink(spdlog::sink_ptr new_sink) {
DEFINE_HIDDEN_string(license_key, "", "License key for Memgraph Enterprise.");
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_HIDDEN_string(organization_name, "", "Organization name.");
/// Encapsulates Dbms and Interpreter that are passed through the network server
/// and worker to the session.
struct SessionData {
// Explicit constructor here to ensure that pointers to all objects are
// supplied.
#if MG_ENTERPRISE
SessionData(memgraph::storage::Storage *db, memgraph::query::InterpreterContext *interpreter_context,
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
memgraph::audit::Log *audit_log)
: db(db), interpreter_context(interpreter_context), auth(auth), audit_log(audit_log) {}
memgraph::storage::Storage *db;
memgraph::query::InterpreterContext *interpreter_context;
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth;
memgraph::audit::Log *audit_log;
#else
SessionData(memgraph::storage::Storage *db, memgraph::query::InterpreterContext *interpreter_context,
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth)
: db(db), interpreter_context(interpreter_context), auth(auth) {}
memgraph::storage::Storage *db;
memgraph::query::InterpreterContext *interpreter_context;
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;
};
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_string(auth_user_or_role_name_regex, memgraph::glue::kDefaultUserRoleRegex.data(),
"Set to the regular expression that each user or role name must fulfill.");
void InitFromCypherlFile(memgraph::query::InterpreterContext &ctx, std::string cypherl_file_path
#ifdef MG_ENTERPRISE
,
memgraph::audit::Log *audit_log
#endif
) {
void InitFromCypherlFile(memgraph::query::InterpreterContext &ctx, std::string cypherl_file_path,
memgraph::audit::Log *audit_log = nullptr) {
memgraph::query::Interpreter interpreter(&ctx);
std::ifstream file(cypherl_file_path);
if (file.is_open()) {
std::string line;
while (std::getline(file, line)) {
if (!line.empty()) {
auto results = interpreter.Prepare(line, {}, {});
memgraph::query::DiscardValueResultStream stream;
interpreter.Pull(&stream, {}, results.qid);
#ifdef MG_ENTERPRISE
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
audit_log->Record("", "", line, {});
}
#endif
if (!file.is_open()) {
spdlog::trace("Could not find init file {}", cypherl_file_path);
return;
}
std::string line;
while (std::getline(file, line)) {
if (!line.empty()) {
auto results = interpreter.Prepare(line, {}, {});
memgraph::query::DiscardValueResultStream stream;
interpreter.Pull(&stream, {}, results.qid);
if (audit_log) {
audit_log->Record("", "", line, {}, memgraph::dbms::kDefaultDB);
}
}
file.close();
}
file.close();
}
class BoltSession final : public memgraph::communication::bolt::Session<memgraph::communication::v2::InputStream,
memgraph::communication::v2::OutputStream> {
namespace memgraph::metrics {
extern const Event ActiveBoltSessions;
} // namespace memgraph::metrics
class SessionHL final : public memgraph::communication::bolt::Session<memgraph::communication::v2::InputStream,
memgraph::communication::v2::OutputStream> {
public:
BoltSession(SessionData *data, const memgraph::communication::v2::ServerEndpoint &endpoint,
memgraph::communication::v2::InputStream *input_stream,
memgraph::communication::v2::OutputStream *output_stream)
struct ContextWrapper {
explicit ContextWrapper(memgraph::dbms::SessionContext sc)
: session_context(sc),
interpreter(std::make_unique<memgraph::query::Interpreter>(session_context.interpreter_context.get())),
defunct_(false) {
session_context.interpreter_context->interpreters.WithLock(
[this](auto &interpreters) { interpreters.insert(interpreter.get()); });
}
~ContextWrapper() { Defunct(); }
void Defunct() {
if (!defunct_) {
session_context.interpreter_context->interpreters.WithLock(
[this](auto &interpreters) { interpreters.erase(interpreter.get()); });
defunct_ = true;
}
}
ContextWrapper(const ContextWrapper &) = delete;
ContextWrapper &operator=(const ContextWrapper &) = delete;
ContextWrapper(ContextWrapper &&in) noexcept
: session_context(std::move(in.session_context)),
interpreter(std::move(in.interpreter)),
defunct_(in.defunct_) {
in.defunct_ = true;
}
ContextWrapper &operator=(ContextWrapper &&in) noexcept {
if (this != &in) {
Defunct();
session_context = std::move(in.session_context);
interpreter = std::move(in.interpreter);
defunct_ = in.defunct_;
in.defunct_ = true;
}
return *this;
}
auto *ic() { return session_context.interpreter_context.get(); }
auto *interp() { return interpreter.get(); }
auto *auth() const { return session_context.auth; }
#ifdef MG_ENTERPRISE
auto *audit_log() const { return session_context.audit_log; }
#endif
auto run_id() const { return session_context.run_id; }
bool defunct() const { return defunct_; }
private:
memgraph::dbms::SessionContext session_context;
std::unique_ptr<memgraph::query::Interpreter> interpreter;
bool defunct_;
};
SessionHL(
#ifdef MG_ENTERPRISE
memgraph::dbms::SessionContextHandler &sc_handler,
#else
memgraph::dbms::SessionContext sc,
#endif
const memgraph::communication::v2::ServerEndpoint &endpoint,
memgraph::communication::v2::InputStream *input_stream, memgraph::communication::v2::OutputStream *output_stream,
const std::string &default_db = memgraph::dbms::kDefaultDB) // NOLINT
: memgraph::communication::bolt::Session<memgraph::communication::v2::InputStream,
memgraph::communication::v2::OutputStream>(input_stream, output_stream),
db_(data->db),
interpreter_context_(data->interpreter_context),
interpreter_(data->interpreter_context),
auth_(data->auth),
#if MG_ENTERPRISE
audit_log_(data->audit_log),
#ifdef MG_ENTERPRISE
sc_handler_(sc_handler),
current_(sc_handler_.Get(default_db)),
#else
current_(sc),
#endif
interpreter_context_(current_.ic()),
interpreter_(current_.interp()),
auth_(current_.auth()),
#ifdef MG_ENTERPRISE
audit_log_(current_.audit_log()),
#endif
endpoint_(endpoint),
run_id_(data->run_id) {
interpreter_context_->interpreters.WithLock([this](auto &interpreters) { interpreters.insert(&interpreter_); });
run_id_(current_.run_id()) {
memgraph::metrics::IncrementCounter(memgraph::metrics::ActiveBoltSessions);
}
~BoltSession() override {
interpreter_context_->interpreters.WithLock([this](auto &interpreters) { interpreters.erase(&interpreter_); });
~SessionHL() override { memgraph::metrics::DecrementCounter(memgraph::metrics::ActiveBoltSessions); }
SessionHL(const SessionHL &) = delete;
SessionHL &operator=(const SessionHL &) = delete;
SessionHL(SessionHL &&) = delete;
SessionHL &operator=(SessionHL &&) = delete;
void Configure(const std::map<std::string, memgraph::communication::bolt::Value> &run_time_info) override {
#ifdef MG_ENTERPRISE
std::string db;
bool update = false;
// Check if user explicitly defined the database to use
if (run_time_info.contains("db")) {
const auto &db_info = run_time_info.at("db");
if (!db_info.IsString()) {
throw memgraph::communication::bolt::ClientError("Malformed database name.");
}
db = db_info.ValueString();
update = db != current_.ic()->db->id();
interpreter_->in_explicit_db_ = true;
} else if (interpreter_->in_explicit_db_) { // Just on a switch
db = GetDefaultDB();
update = db != current_.ic()->db->id();
interpreter_->in_explicit_db_ = false;
}
// Check if the underlying database needs to be updated
if (update) {
sc_handler_.SetInPlace(db, [this](auto new_sc) mutable {
const auto &db_name = new_sc.interpreter_context->db->id();
MultiDatabaseAuth(db_name);
try {
Update(ContextWrapper(new_sc));
return memgraph::dbms::SetForResult::SUCCESS;
} catch (memgraph::dbms::UnknownDatabase &e) {
throw memgraph::communication::bolt::ClientError("No database named \"{}\" found!", db_name);
}
});
}
#endif
}
using memgraph::communication::bolt::Session<memgraph::communication::v2::InputStream,
memgraph::communication::v2::OutputStream>::TEncoder;
using TEncoder = memgraph::communication::bolt::Encoder<
memgraph::communication::bolt::ChunkedEncoderBuffer<memgraph::communication::v2::OutputStream>>;
void BeginTransaction() override { interpreter_.BeginTransaction(); }
void BeginTransaction(const std::map<std::string, memgraph::communication::bolt::Value> &metadata) override {
std::map<std::string, memgraph::storage::PropertyValue> metadata_pv;
for (const auto &[key, bolt_value] : metadata) {
metadata_pv.emplace(key, memgraph::glue::ToPropertyValue(bolt_value));
}
interpreter_->BeginTransaction(metadata_pv);
}
void CommitTransaction() override { interpreter_.CommitTransaction(); }
void CommitTransaction() override { interpreter_->CommitTransaction(); }
void RollbackTransaction() override { interpreter_.RollbackTransaction(); }
void RollbackTransaction() override { interpreter_->RollbackTransaction(); }
std::pair<std::vector<std::string>, std::optional<int>> Interpret(
const std::string &query, const std::map<std::string, memgraph::communication::bolt::Value> &params) override {
const std::string &query, const std::map<std::string, memgraph::communication::bolt::Value> &params,
const std::map<std::string, memgraph::communication::bolt::Value> &metadata) override {
std::map<std::string, memgraph::storage::PropertyValue> params_pv;
for (const auto &kv : params) params_pv.emplace(kv.first, memgraph::glue::ToPropertyValue(kv.second));
std::map<std::string, memgraph::storage::PropertyValue> metadata_pv;
for (const auto &[key, bolt_param] : params) {
params_pv.emplace(key, memgraph::glue::ToPropertyValue(bolt_param));
}
for (const auto &[key, bolt_md] : metadata) {
metadata_pv.emplace(key, memgraph::glue::ToPropertyValue(bolt_md));
}
const std::string *username{nullptr};
if (user_) {
username = &user_->username();
}
#ifdef MG_ENTERPRISE
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
audit_log_->Record(endpoint_.address().to_string(), user_ ? *username : "", query,
memgraph::storage::PropertyValue(params_pv));
memgraph::storage::PropertyValue(params_pv), interpreter_context_->db->id());
}
#endif
try {
auto result = interpreter_.Prepare(query, params_pv, username);
if (user_ && !memgraph::glue::AuthChecker::IsUserAuthorized(*user_, result.privileges)) {
interpreter_.Abort();
auto result = interpreter_->Prepare(query, params_pv, username, metadata_pv, UUID());
const std::string &db_name = result.db ? *result.db : "";
if (user_ && !memgraph::glue::AuthChecker::IsUserAuthorized(*user_, result.privileges, db_name)) {
interpreter_->Abort();
if (db_name.empty()) {
throw memgraph::communication::bolt::ClientError(
"You are not authorized to execute this query! Please contact your database administrator.");
}
throw memgraph::communication::bolt::ClientError(
"You are not authorized to execute this query! Please contact "
"your database administrator.");
"You are not authorized to execute this query on database \"{}\"! Please contact your database "
"administrator.",
db_name);
}
return {result.headers, result.qid};
@@ -546,7 +684,7 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
std::map<std::string, memgraph::communication::bolt::Value> Pull(TEncoder *encoder, std::optional<int> n,
std::optional<int> qid) override {
TypedValueResultStream stream(encoder, db_);
TypedValueResultStream stream(encoder, interpreter_context_);
return PullResults(stream, n, qid);
}
@@ -556,14 +694,26 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
return PullResults(stream, n, qid);
}
void Abort() override { interpreter_.Abort(); }
void Abort() override { interpreter_->Abort(); }
// Called during Init
// During Init, the user cannot choose the landing DB (switch is done during query execution)
bool Authenticate(const std::string &username, const std::string &password) override {
auto locked_auth = auth_->Lock();
if (!locked_auth->HasUsers()) {
return true;
}
user_ = locked_auth->Authenticate(username, password);
#ifdef MG_ENTERPRISE
if (user_.has_value()) {
const auto &db = user_->db_access().GetDefault();
// Check if the underlying database needs to be updated
if (db != current_.ic()->db->id()) {
const auto &res = sc_handler_.SetFor(UUID(), db);
return res == memgraph::dbms::SetForResult::SUCCESS || res == memgraph::dbms::SetForResult::ALREADY_SET;
}
}
#endif
return user_.has_value();
}
@@ -572,15 +722,35 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
return FLAGS_bolt_server_name_for_init;
}
#ifdef MG_ENTERPRISE
memgraph::dbms::SetForResult OnChange(const std::string &db_name) override {
MultiDatabaseAuth(db_name);
if (db_name != current_.ic()->db->id()) {
UpdateAndDefunct(db_name); // Done during Pull, so we cannot just replace the current db
return memgraph::dbms::SetForResult::SUCCESS;
}
return memgraph::dbms::SetForResult::ALREADY_SET;
}
bool OnDelete(const std::string &db_name) override {
MG_ASSERT(current_.ic()->db->id() != db_name && (!defunct_ || defunct_->defunct()),
"Trying to delete a database while still in use.");
return true;
}
#endif
std::string GetID() const override { return interpreter_context_->db->id(); }
private:
template <typename TStream>
std::map<std::string, memgraph::communication::bolt::Value> PullResults(TStream &stream, std::optional<int> n,
std::optional<int> qid) {
try {
const auto &summary = interpreter_.Pull(&stream, n, qid);
const auto &summary = interpreter_->Pull(&stream, n, qid);
std::map<std::string, memgraph::communication::bolt::Value> decoded_summary;
for (const auto &kv : summary) {
auto maybe_value = memgraph::glue::ToBoltValue(kv.second, *db_, memgraph::storage::View::NEW);
auto maybe_value =
memgraph::glue::ToBoltValue(kv.second, *interpreter_context_->db, memgraph::storage::View::NEW);
if (maybe_value.HasError()) {
switch (maybe_value.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@@ -601,6 +771,11 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
decoded_summary.emplace("run_id", *run_id);
}
// Clean up previous session (session gets defunct when switching between databases)
if (defunct_) {
defunct_.reset();
}
return decoded_summary;
} catch (const memgraph::query::QueryException &e) {
// Wrap QueryException into ClientError, because we want to allow the
@@ -609,17 +784,70 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
}
}
#ifdef MG_ENTERPRISE
/**
* @brief Update setup to the new database.
*
* @param db_name name of the target database
* @throws UnknownDatabase if handler cannot get it
*/
void UpdateAndDefunct(const std::string &db_name) { UpdateAndDefunct(ContextWrapper(sc_handler_.Get(db_name))); }
void UpdateAndDefunct(ContextWrapper &&cntxt) {
defunct_.emplace(std::move(current_));
Update(std::forward<ContextWrapper>(cntxt));
defunct_->Defunct();
}
void Update(const std::string &db_name) {
ContextWrapper tmp(sc_handler_.Get(db_name));
Update(std::move(tmp));
}
void Update(ContextWrapper &&cntxt) {
current_ = std::move(cntxt);
interpreter_ = current_.interp();
interpreter_context_ = current_.ic();
}
/**
* @brief Authenticate user on passed database.
*
* @param db database to check against
* @throws bolt::ClientError when user is not authorized
*/
void MultiDatabaseAuth(const std::string &db) {
if (user_ && !memgraph::glue::AuthChecker::IsUserAuthorized(*user_, {}, db)) {
throw memgraph::communication::bolt::ClientError(
"You are not authorized on the database \"{}\"! Please contact your database administrator.", db);
}
}
/**
* @brief Get the user's default database
*
* @return std::string
*/
std::string GetDefaultDB() {
if (user_.has_value()) {
return user_->db_access().GetDefault();
}
return memgraph::dbms::kDefaultDB;
}
#endif
/// Wrapper around TEncoder which converts TypedValue to Value
/// before forwarding the calls to original TEncoder.
class TypedValueResultStream {
public:
TypedValueResultStream(TEncoder *encoder, const memgraph::storage::Storage *db) : encoder_(encoder), db_(db) {}
TypedValueResultStream(TEncoder *encoder, memgraph::query::InterpreterContext *ic)
: encoder_(encoder), interpreter_context_(ic) {}
void Result(const std::vector<memgraph::query::TypedValue> &values) {
std::vector<memgraph::communication::bolt::Value> decoded_values;
decoded_values.reserve(values.size());
for (const auto &v : values) {
auto maybe_value = memgraph::glue::ToBoltValue(v, *db_, memgraph::storage::View::NEW);
auto maybe_value = memgraph::glue::ToBoltValue(v, *interpreter_context_->db, memgraph::storage::View::NEW);
if (maybe_value.HasError()) {
switch (maybe_value.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
@@ -640,13 +868,17 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
private:
TEncoder *encoder_;
// NOTE: Needed only for ToBoltValue conversions
const memgraph::storage::Storage *db_;
memgraph::query::InterpreterContext *interpreter_context_;
};
// NOTE: Needed only for ToBoltValue conversions
const memgraph::storage::Storage *db_;
#ifdef MG_ENTERPRISE
memgraph::dbms::SessionContextHandler &sc_handler_;
#endif
ContextWrapper current_;
std::optional<ContextWrapper> defunct_;
memgraph::query::InterpreterContext *interpreter_context_;
memgraph::query::Interpreter interpreter_;
memgraph::query::Interpreter *interpreter_;
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
std::optional<memgraph::auth::User> user_;
#ifdef MG_ENTERPRISE
@@ -657,7 +889,14 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
std::optional<std::string> run_id_;
};
using ServerT = memgraph::communication::v2::Server<BoltSession, SessionData>;
#ifdef MG_ENTERPRISE
using ServerT = memgraph::communication::v2::Server<SessionHL, memgraph::dbms::SessionContextHandler>;
#else
using ServerT = memgraph::communication::v2::Server<SessionHL, memgraph::dbms::SessionContext>;
#endif
using MonitoringServerT =
memgraph::communication::http::Server<memgraph::http::MetricsRequestHandler<memgraph::dbms::SessionContext>,
memgraph::dbms::SessionContext>;
using memgraph::communication::ServerContext;
// Needed to correctly handle memgraph destruction from a signal handler.
@@ -820,10 +1059,6 @@ int main(int argc, char **argv) {
// Begin enterprise features initialization
// Auth
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> auth{data_directory /
"auth"};
#ifdef MG_ENTERPRISE
// Audit log
memgraph::audit::Log audit_log{data_directory / "audit", FLAGS_audit_buffer_size,
@@ -847,13 +1082,24 @@ int main(int argc, char **argv) {
.interval = std::chrono::seconds(FLAGS_storage_gc_cycle_sec)},
.items = {.properties_on_edges = FLAGS_storage_properties_on_edges},
.durability = {.storage_directory = FLAGS_data_directory,
.recover_on_startup = FLAGS_storage_recover_on_startup,
.recover_on_startup = FLAGS_storage_recover_on_startup || FLAGS_data_recover_on_startup,
.snapshot_retention_count = FLAGS_storage_snapshot_retention_count,
.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 = true},
.transaction = {.isolation_level = ParseIsolationLevel()}};
.restore_replication_state_on_startup = FLAGS_replication_restore_state_on_startup,
.items_per_batch = FLAGS_storage_items_per_batch,
.recovery_thread_count = FLAGS_storage_recovery_thread_count,
.allow_parallel_index_creation = FLAGS_storage_parallel_index_recovery},
.transaction = {.isolation_level = ParseIsolationLevel()},
.disk = {.main_storage_directory = FLAGS_data_directory + "/rocksdb_main_storage",
.label_index_directory = FLAGS_data_directory + "/rocksdb_label_index",
.label_property_index_directory = FLAGS_data_directory + "/rocksdb_label_property_index",
.unique_constraints_directory = FLAGS_data_directory + "/rocksdb_unique_constraints",
.name_id_mapper_directory = FLAGS_data_directory + "/rocksdb_name_id_mapper",
.id_name_mapper_directory = FLAGS_data_directory + "/rocksdb_id_name_mapper",
.durability_directory = FLAGS_data_directory + "/rocksdb_durability",
.wal_directory = FLAGS_data_directory + "/rocksdb_wal"}};
if (FLAGS_storage_snapshot_interval_sec == 0) {
if (FLAGS_storage_wal_enabled) {
LOG_FATAL(
@@ -872,43 +1118,53 @@ int main(int argc, char **argv) {
}
db_config.durability.snapshot_interval = std::chrono::seconds(FLAGS_storage_snapshot_interval_sec);
}
memgraph::storage::Storage db(db_config);
memgraph::query::InterpreterContext interpreter_context{
&db,
{.query = {.allow_load_csv = FLAGS_allow_load_csv},
.execution_timeout_sec = FLAGS_query_execution_timeout_sec,
.replication_replica_check_frequency = std::chrono::seconds(FLAGS_replication_replica_check_frequency_sec),
.default_kafka_bootstrap_servers = FLAGS_kafka_bootstrap_servers,
.default_pulsar_service_url = FLAGS_pulsar_service_url,
.stream_transaction_conflict_retries = FLAGS_stream_transaction_conflict_retries,
.stream_transaction_retry_interval = std::chrono::milliseconds(FLAGS_stream_transaction_retry_interval)},
FLAGS_data_directory};
// Default interpreter configuration
memgraph::query::InterpreterConfig interp_config{
.query = {.allow_load_csv = FLAGS_allow_load_csv},
.execution_timeout_sec = FLAGS_query_execution_timeout_sec,
.replication_replica_check_frequency = std::chrono::seconds(FLAGS_replication_replica_check_frequency_sec),
.default_kafka_bootstrap_servers = FLAGS_kafka_bootstrap_servers,
.default_pulsar_service_url = FLAGS_pulsar_service_url,
.stream_transaction_conflict_retries = FLAGS_stream_transaction_conflict_retries,
.stream_transaction_retry_interval = std::chrono::milliseconds(FLAGS_stream_transaction_retry_interval)};
#ifdef MG_ENTERPRISE
SessionData session_data{&db, &interpreter_context, &auth, &audit_log};
// SessionContext handler (multi-tenancy)
memgraph::dbms::SessionContextHandler sc_handler(
audit_log,
{db_config, interp_config,
[flag = FLAGS_auth_user_or_role_name_regex](
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth,
std::unique_ptr<memgraph::query::AuthQueryHandler> &ah, std::unique_ptr<memgraph::query::AuthChecker> &ac) {
// Glue high level auth implementations to the query side
ah = std::make_unique<memgraph::glue::AuthQueryHandler>(auth, flag);
ac = std::make_unique<memgraph::glue::AuthChecker>(auth);
// Handle users passed via arguments
auto *maybe_username = std::getenv(kMgUser);
auto *maybe_password = std::getenv(kMgPassword);
auto *maybe_pass_file = std::getenv(kMgPassfile);
if (maybe_username && maybe_password) {
ah->CreateUser(maybe_username, maybe_password);
} else if (maybe_pass_file) {
const auto [username, password] = LoadUsernameAndPassword(maybe_pass_file);
if (!username.empty() && !password.empty()) {
ah->CreateUser(username, password);
}
}
}},
FLAGS_storage_recover_on_startup || FLAGS_data_recover_on_startup, FLAGS_storage_delete_on_drop);
// Just for current support... TODO remove
auto session_context = sc_handler.Get(memgraph::dbms::kDefaultDB);
#else
SessionData session_data{&db, &interpreter_context, &auth};
#endif
memgraph::query::procedure::gModuleRegistry.SetModulesDirectory(query_modules_directories, FLAGS_data_directory);
memgraph::query::procedure::gModuleRegistry.UnloadAndLoadModulesFromDirectories();
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;
if (!FLAGS_init_file.empty()) {
spdlog::info("Running init file.");
#ifdef MG_ENTERPRISE
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
InitFromCypherlFile(interpreter_context, FLAGS_init_file, &audit_log);
}
#else
InitFromCypherlFile(interpreter_context, FLAGS_init_file);
#endif
}
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> auth_{data_directory /
"auth"};
memgraph::glue::AuthQueryHandler auth_handler(&auth_, FLAGS_auth_user_or_role_name_regex);
memgraph::glue::AuthChecker auth_checker{&auth_};
auto session_context = memgraph::dbms::Init(db_config, interp_config, &auth_, &auth_handler, &auth_checker);
// Handle users passed via arguments
auto *maybe_username = std::getenv(kMgUser);
auto *maybe_password = std::getenv(kMgPassword);
auto *maybe_pass_file = std::getenv(kMgPassfile);
@@ -920,18 +1176,43 @@ int main(int argc, char **argv) {
auth_handler.CreateUser(username, password);
}
}
#endif
auto *auth = session_context.auth;
auto &interpreter_context = *session_context.interpreter_context; // TODO remove
memgraph::query::procedure::gModuleRegistry.SetModulesDirectory(query_modules_directories, FLAGS_data_directory);
memgraph::query::procedure::gModuleRegistry.UnloadAndLoadModulesFromDirectories();
if (!FLAGS_init_file.empty()) {
spdlog::info("Running init file...");
#ifdef MG_ENTERPRISE
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
InitFromCypherlFile(interpreter_context, FLAGS_init_file, &audit_log);
} else {
InitFromCypherlFile(interpreter_context, FLAGS_init_file);
}
#else
InitFromCypherlFile(interpreter_context, FLAGS_init_file);
#endif
}
#ifdef MG_ENTERPRISE
sc_handler.RestoreTriggers();
sc_handler.RestoreStreams();
#else
{
// Triggers can execute query procedures, so we need to reload the modules first and then
// the triggers
auto storage_accessor = interpreter_context.db->Access();
auto dba = memgraph::query::DbAccessor{&storage_accessor};
auto dba = memgraph::query::DbAccessor{storage_accessor.get()};
interpreter_context.trigger_store.RestoreTriggers(
&interpreter_context.ast_cache, &dba, interpreter_context.config.query, interpreter_context.auth_checker);
}
// As the Stream transformations are using modules, they have to be restored after the query modules are loaded.
interpreter_context.streams.RestoreStreams();
#endif
ServerContext context;
std::string service_name = "Bolt";
@@ -943,29 +1224,43 @@ int main(int argc, char **argv) {
spdlog::warn(
memgraph::utils::MessageWithLink("Using non-secure Bolt connection (without SSL).", "https://memgr.ph/ssl"));
}
auto server_endpoint = memgraph::communication::v2::ServerEndpoint{
boost::asio::ip::address::from_string(FLAGS_bolt_address), static_cast<uint16_t>(FLAGS_bolt_port)};
ServerT server(server_endpoint, &session_data, &context, FLAGS_bolt_session_inactivity_timeout, service_name,
#ifdef MG_ENTERPRISE
ServerT server(server_endpoint, &sc_handler, &context, FLAGS_bolt_session_inactivity_timeout, service_name,
FLAGS_bolt_num_workers);
#else
ServerT server(server_endpoint, &session_context, &context, FLAGS_bolt_session_inactivity_timeout, service_name,
FLAGS_bolt_num_workers);
#endif
const auto run_id = memgraph::utils::GenerateUUID();
const auto machine_id = memgraph::utils::GetMachineId();
session_data.run_id = run_id;
const auto run_id = session_context.run_id; // For current compatibility
// Setup telemetry
static constexpr auto telemetry_server{"https://telemetry.memgraph.com/88b5e7e8-746a-11e8-9f85-538a9e9690cc/"};
std::optional<memgraph::telemetry::Telemetry> telemetry;
if (FLAGS_telemetry_enabled) {
telemetry.emplace(telemetry_server, data_directory / "telemetry", run_id, machine_id, std::chrono::minutes(10));
telemetry->AddCollector("storage", [&db]() -> nlohmann::json {
auto info = db.GetInfo();
#ifdef MG_ENTERPRISE
telemetry->AddCollector("storage", [&sc_handler]() -> nlohmann::json {
auto info = sc_handler.Info();
const auto &vertex_count = std::get<0>(info);
const auto &edge_count = std::get<1>(info);
const auto &db_count = std::get<2>(info);
return {{"vertices", vertex_count}, {"edges", edge_count}, {"databases", db_count}};
});
#else
telemetry->AddCollector("storage", [&interpreter_context]() -> nlohmann::json {
auto info = interpreter_context.db->GetInfo();
return {{"vertices", info.vertex_count}, {"edges", info.edge_count}};
});
#endif
telemetry->AddCollector("event_counters", []() -> nlohmann::json {
nlohmann::json ret;
for (size_t i = 0; i < EventCounter::End(); ++i) {
ret[EventCounter::GetName(i)] = EventCounter::global_counters[i].load(std::memory_order_relaxed);
for (size_t i = 0; i < memgraph::metrics::CounterEnd(); ++i) {
ret[memgraph::metrics::GetCounterName(i)] =
memgraph::metrics::global_counters[i].load(std::memory_order_relaxed);
}
return ret;
});
@@ -976,11 +1271,48 @@ int main(int argc, char **argv) {
memgraph::license::LicenseInfoSender license_info_sender(telemetry_server, run_id, machine_id, memory_limit,
memgraph::license::global_license_checker.GetLicenseInfo());
memgraph::communication::websocket::SafeAuth websocket_auth{&auth};
memgraph::communication::websocket::SafeAuth websocket_auth{auth};
memgraph::communication::websocket::Server websocket_server{
{FLAGS_monitoring_address, static_cast<uint16_t>(FLAGS_monitoring_port)}, &context, websocket_auth};
AddLoggerSink(websocket_server.GetLoggingSink());
MonitoringServerT metrics_server{
{FLAGS_metrics_address, static_cast<uint16_t>(FLAGS_metrics_port)}, &session_context, &context};
#ifdef MG_ENTERPRISE
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
// Handler for regular termination signals
auto shutdown = [&metrics_server, &websocket_server, &server, &sc_handler] {
// Server needs to be shutdown first and then the database. This prevents
// a race condition when a transaction is accepted during server shutdown.
server.Shutdown();
// After the server is notified to stop accepting and processing
// connections we tell the execution engine to stop processing all pending
// queries.
sc_handler.Shutdown();
websocket_server.Shutdown();
metrics_server.Shutdown();
};
InitSignalHandlers(shutdown);
} else {
// Handler for regular termination signals
auto shutdown = [&websocket_server, &server, &interpreter_context] {
// Server needs to be shutdown first and then the database. This prevents
// a race condition when a transaction is accepted during server shutdown.
server.Shutdown();
// After the server is notified to stop accepting and processing
// connections we tell the execution engine to stop processing all pending
// queries.
memgraph::query::Shutdown(&interpreter_context);
websocket_server.Shutdown();
};
InitSignalHandlers(shutdown);
}
#else
// Handler for regular termination signals
auto shutdown = [&websocket_server, &server, &interpreter_context] {
// Server needs to be shutdown first and then the database. This prevents
@@ -990,19 +1322,29 @@ int main(int argc, char **argv) {
// connections we tell the execution engine to stop processing all pending
// queries.
memgraph::query::Shutdown(&interpreter_context);
websocket_server.Shutdown();
};
InitSignalHandlers(shutdown);
#endif
MG_ASSERT(server.Start(), "Couldn't start the Bolt server!");
websocket_server.Start();
#ifdef MG_ENTERPRISE
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
metrics_server.Start();
}
#endif
if (!FLAGS_init_data_file.empty()) {
spdlog::info("Running init data file.");
#ifdef MG_ENTERPRISE
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
InitFromCypherlFile(interpreter_context, FLAGS_init_data_file, &audit_log);
} else {
InitFromCypherlFile(interpreter_context, FLAGS_init_data_file);
}
#else
InitFromCypherlFile(interpreter_context, FLAGS_init_data_file);
@@ -1011,6 +1353,11 @@ int main(int argc, char **argv) {
server.AwaitShutdown();
websocket_server.AwaitShutdown();
#ifdef MG_ENTERPRISE
if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) {
metrics_server.AwaitShutdown();
}
#endif
memgraph::query::procedure::gModuleRegistry.UnloadAllModules();

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd.
// Copyright 2023 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -20,7 +20,8 @@
#include <unordered_map>
#include "helpers.hpp"
#include "storage/v2/storage.hpp"
#include "storage/v2/edge_accessor.hpp"
#include "storage/v2/inmemory/storage.hpp"
#include "utils/exceptions.hpp"
#include "utils/logging.hpp"
#include "utils/message.hpp"
@@ -421,7 +422,7 @@ void ProcessNodeRow(memgraph::storage::Storage *store, const std::vector<std::st
std::unordered_map<NodeId, memgraph::storage::Gid> *node_id_map) {
std::optional<NodeId> id;
auto acc = store->Access();
auto node = acc.CreateVertex();
auto node = acc->CreateVertex();
for (size_t i = 0; i < row.size(); ++i) {
const auto &field = fields[i];
const auto &value = row[i];
@@ -436,7 +437,7 @@ void ProcessNodeRow(memgraph::storage::Storage *store, const std::vector<std::st
if (it != node_id_map->end()) {
if (FLAGS_skip_duplicate_nodes) {
spdlog::warn(memgraph::utils::MessageWithLink("Skipping duplicate node with ID '{}'.", node_id,
"https://memgr.ph/csv"));
"https://memgr.ph/csv-import-tool"));
return;
} else {
throw LoadException("Node with ID '{}' already exists", node_id);
@@ -450,29 +451,29 @@ void ProcessNodeRow(memgraph::storage::Storage *store, const std::vector<std::st
} else {
pv_id = memgraph::storage::PropertyValue(node_id.id);
}
auto old_node_property = node.SetProperty(acc.NameToProperty(field.name), pv_id);
auto old_node_property = node.SetProperty(acc->NameToProperty(field.name), pv_id);
if (!old_node_property.HasValue()) throw LoadException("Couldn't add property '{}' to the node", field.name);
if (!old_node_property->IsNull()) throw LoadException("The property '{}' already exists", field.name);
}
id = node_id;
} else if (field.type == "LABEL") {
for (const auto &label : memgraph::utils::Split(value, FLAGS_array_delimiter)) {
auto node_label = node.AddLabel(acc.NameToLabel(label));
auto node_label = node.AddLabel(acc->NameToLabel(label));
if (!node_label.HasValue()) throw LoadException("Couldn't add label '{}' to the node", label);
if (!*node_label) throw LoadException("The label '{}' already exists", label);
}
} else if (field.type != "IGNORE") {
auto old_node_property = node.SetProperty(acc.NameToProperty(field.name), StringToValue(value, field.type));
auto old_node_property = node.SetProperty(acc->NameToProperty(field.name), StringToValue(value, field.type));
if (!old_node_property.HasValue()) throw LoadException("Couldn't add property '{}' to the node", field.name);
if (!old_node_property->IsNull()) throw LoadException("The property '{}' already exists", field.name);
}
}
for (const auto &label : additional_labels) {
auto node_label = node.AddLabel(acc.NameToLabel(label));
auto node_label = node.AddLabel(acc->NameToLabel(label));
if (!node_label.HasValue()) throw LoadException("Couldn't add label '{}' to the node", label);
if (!*node_label) throw LoadException("The label '{}' already exists", label);
}
if (acc.Commit().HasError()) throw LoadException("Couldn't store the node");
if (acc->Commit().HasError()) throw LoadException("Couldn't store the node");
}
void ProcessNodes(memgraph::storage::Storage *store, const std::string &nodes_path,
@@ -529,7 +530,7 @@ void ProcessRelationshipsRow(memgraph::storage::Storage *store, const std::vecto
if (it == node_id_map.end()) {
if (FLAGS_skip_bad_relationships) {
spdlog::warn(memgraph::utils::MessageWithLink("Skipping bad relationship with START_ID '{}'.", node_id,
"https://memgr.ph/csv"));
"https://memgr.ph/csv-import-tool"));
return;
} else {
throw LoadException("Node with ID '{}' does not exist", node_id);
@@ -547,7 +548,7 @@ void ProcessRelationshipsRow(memgraph::storage::Storage *store, const std::vecto
if (it == node_id_map.end()) {
if (FLAGS_skip_bad_relationships) {
spdlog::warn(memgraph::utils::MessageWithLink("Skipping bad relationship with END_ID '{}'.", node_id,
"https://memgr.ph/csv"));
"https://memgr.ph/csv-import-tool"));
return;
} else {
throw LoadException("Node with ID '{}' does not exist", node_id);
@@ -567,16 +568,16 @@ void ProcessRelationshipsRow(memgraph::storage::Storage *store, const std::vecto
if (!relationship_type) throw LoadException("Relationship TYPE must be set");
auto acc = store->Access();
auto from_node = acc.FindVertex(*start_id, memgraph::storage::View::NEW);
auto from_node = acc->FindVertex(*start_id, memgraph::storage::View::NEW);
if (!from_node) throw LoadException("From node must be in the storage");
auto to_node = acc.FindVertex(*end_id, memgraph::storage::View::NEW);
auto to_node = acc->FindVertex(*end_id, memgraph::storage::View::NEW);
if (!to_node) throw LoadException("To node must be in the storage");
auto relationship = acc.CreateEdge(&*from_node, &*to_node, acc.NameToEdgeType(*relationship_type));
auto relationship = acc->CreateEdge(&from_node.value(), &to_node.value(), acc->NameToEdgeType(*relationship_type));
if (!relationship.HasValue()) throw LoadException("Couldn't create the relationship");
for (const auto &property : properties) {
auto ret = relationship->SetProperty(acc.NameToProperty(property.first), property.second);
auto ret = relationship.GetValue().SetProperty(acc->NameToProperty(property.first), property.second);
if (!ret.HasValue()) {
if (ret.GetError() != memgraph::storage::Error::PROPERTIES_DISABLED) {
throw LoadException("Couldn't add property '{}' to the relationship", property.first);
@@ -589,7 +590,7 @@ void ProcessRelationshipsRow(memgraph::storage::Storage *store, const std::vecto
}
}
if (acc.Commit().HasError()) throw LoadException("Couldn't store the relationship");
if (acc->Commit().HasError()) throw LoadException("Couldn't store the relationship");
}
void ProcessRelationships(memgraph::storage::Storage *store, const std::string &relationships_path,
@@ -699,13 +700,13 @@ int main(int argc, char *argv[]) {
}
std::unordered_map<NodeId, memgraph::storage::Gid> node_id_map;
memgraph::storage::Storage store{{
std::unique_ptr<memgraph::storage::Storage> store{new memgraph::storage::InMemoryStorage{{
.items = {.properties_on_edges = FLAGS_storage_properties_on_edges},
.durability = {.storage_directory = FLAGS_data_directory,
.recover_on_startup = false,
.snapshot_wal_mode = memgraph::storage::Config::Durability::SnapshotWalMode::DISABLED,
.snapshot_on_exit = true},
}};
}}};
memgraph::utils::Timer load_timer;
@@ -715,7 +716,7 @@ int main(int argc, char *argv[]) {
std::optional<std::vector<Field>> header;
for (const auto &nodes_file : files) {
spdlog::info("Loading {}", nodes_file);
ProcessNodes(&store, nodes_file, &header, &node_id_map, additional_labels);
ProcessNodes(store.get(), nodes_file, &header, &node_id_map, additional_labels);
}
}
@@ -725,7 +726,7 @@ int main(int argc, char *argv[]) {
std::optional<std::vector<Field>> header;
for (const auto &relationships_file : files) {
spdlog::info("Loading {}", relationships_file);
ProcessRelationships(&store, relationships_file, type, &header, node_id_map);
ProcessRelationships(store.get(), relationships_file, type, &header, node_id_map);
}
}

View File

@@ -37,12 +37,9 @@ set(mg_query_sources
graph.cpp
db_accessor.cpp)
find_package(Boost REQUIRED)
add_library(mg-query STATIC ${mg_query_sources})
target_include_directories(mg-query PUBLIC ${CMAKE_SOURCE_DIR}/include)
target_link_libraries(mg-query dl cppitertools Boost::headers)
target_link_libraries(mg-query mg-integrations-pulsar mg-integrations-kafka mg-storage-v2 mg-license mg-utils mg-kvstore mg-memory)
target_link_libraries(mg-query PUBLIC dl cppitertools Python3::Python mg-integrations-pulsar mg-integrations-kafka mg-storage-v2 mg-license mg-utils mg-kvstore mg-memory mg::csv)
if(NOT "${MG_PYTHON_PATH}" STREQUAL "")
set(Python3_ROOT_DIR "${MG_PYTHON_PATH}")
endif()
@@ -51,7 +48,6 @@ if("${MG_PYTHON_VERSION}" STREQUAL "")
else()
find_package(Python3 "${MG_PYTHON_VERSION}" EXACT REQUIRED COMPONENTS Development)
endif()
target_link_libraries(mg-query Python3::Python)
# Generate Antlr openCypher parser
@@ -94,4 +90,4 @@ add_library(antlr_opencypher_parser_lib STATIC ${antlr_opencypher_generated_src}
add_dependencies(antlr_opencypher_parser_lib generate_opencypher_parser)
target_link_libraries(antlr_opencypher_parser_lib antlr4)
target_link_libraries(mg-query antlr_opencypher_parser_lib)
target_link_libraries(mg-query PUBLIC antlr_opencypher_parser_lib)

View File

@@ -24,7 +24,8 @@ class AuthChecker {
virtual ~AuthChecker() = default;
[[nodiscard]] virtual bool IsUserAuthorized(const std::optional<std::string> &username,
const std::vector<query::AuthQuery::Privilege> &privileges) const = 0;
const std::vector<query::AuthQuery::Privilege> &privileges,
const std::string &db_name) const = 0;
#ifdef MG_ENTERPRISE
[[nodiscard]] virtual std::unique_ptr<FineGrainedAuthChecker> GetFineGrainedAuthChecker(
@@ -92,7 +93,8 @@ class AllowEverythingFineGrainedAuthChecker final : public query::FineGrainedAut
class AllowEverythingAuthChecker final : public query::AuthChecker {
public:
bool IsUserAuthorized(const std::optional<std::string> & /*username*/,
const std::vector<query::AuthQuery::Privilege> & /*privileges*/) const override {
const std::vector<query::AuthQuery::Privilege> & /*privileges*/,
const std::string & /*db*/) const override {
return true;
}

View File

@@ -14,8 +14,6 @@
#include <string>
namespace memgraph::query {
inline constexpr uint16_t kDefaultReplicationPort = 10000;
inline constexpr auto *kDefaultReplicationServerIp = "0.0.0.0";
inline const std::string kAsterisk = "*";
inline constexpr uint16_t kDeleteStatisticsNumResults = 6;
inline constexpr uint16_t kComputeStatisticsNumResults = 7;
} // namespace memgraph::query

View File

@@ -22,6 +22,8 @@
#include "query/trigger.hpp"
#include "utils/async_timer.hpp"
#include "query/frame_change.hpp"
namespace memgraph::query {
enum class TransactionStatus {
@@ -82,6 +84,7 @@ struct ExecutionContext {
plan::ProfilingStats *stats_root{nullptr};
ExecutionStats execution_stats;
TriggerContextCollector *trigger_context_collector{nullptr};
FrameChangeCollector *frame_change_collector{nullptr};
utils::AsyncTimer timer;
#ifdef MG_ENTERPRISE
std::unique_ptr<FineGrainedAuthChecker> auth_checker{nullptr};

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