├── .gitmodules
├── tests
├── dataset_benchmark
│ ├── .gitignore
│ └── run.sh
├── input_output
│ ├── prepare.cypher
│ ├── input
│ │ ├── escaping.txt
│ │ ├── multiple_columns.txt
│ │ ├── multiple_queries_per_line.txt
│ │ ├── query_per_line.txt
│ │ ├── enum.txt
│ │ ├── spatial.txt
│ │ ├── unfinished_query.txt
│ │ ├── quote.txt
│ │ ├── multiline_query.txt
│ │ ├── multiline_query_with_comments.txt
│ │ └── temporal.txt
│ ├── output_csv
│ │ ├── escaping.txt
│ │ ├── multiple_columns.txt
│ │ ├── multiple_queries_per_line.txt
│ │ ├── query_per_line.txt
│ │ ├── enum.txt
│ │ ├── spatial.txt
│ │ ├── quote.txt
│ │ ├── unfinished_query.txt
│ │ ├── multiline_query.txt
│ │ ├── multiline_query_with_comments.txt
│ │ └── temporal.txt
│ ├── output_tabular
│ │ ├── escaping.txt
│ │ ├── multiple_columns.txt
│ │ ├── multiple_queries_per_line.txt
│ │ ├── query_per_line.txt
│ │ ├── enum.txt
│ │ ├── spatial.txt
│ │ ├── quote.txt
│ │ ├── unfinished_query.txt
│ │ ├── multiline_query.txt
│ │ ├── multiline_query_with_comments.txt
│ │ └── temporal.txt
│ ├── CMakeLists.txt
│ └── run-tests.sh
└── CMakeLists.txt
├── .gitignore
├── .clang-format
├── Dockerfile
├── src
├── version.hpp.in
├── parsing.hpp
├── utils
│ ├── assert.hpp
│ ├── bolt.hpp
│ ├── CMakeLists.txt
│ ├── thread_pool.hpp
│ ├── bolt.cpp
│ ├── thread_pool.cpp
│ ├── notifier.hpp
│ ├── synchronized.hpp
│ ├── constants.hpp
│ ├── future.hpp
│ ├── query_type.hpp
│ └── utils.hpp
├── serial_import.hpp
├── interactive.hpp
├── batch_import.hpp
├── serial_import.cpp
├── parsing.cpp
├── CMakeLists.txt
├── interactive.cpp
├── main.cpp
└── batch_import.cpp
├── CMakeLists.txt
├── .github
└── workflows
│ ├── release.yml
│ └── ci.yml
├── README.md
└── LICENSE
/.gitmodules:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dataset_benchmark/.gitignore:
--------------------------------------------------------------------------------
1 | *.gz
2 | *.cypherl
3 |
--------------------------------------------------------------------------------
/tests/input_output/prepare.cypher:
--------------------------------------------------------------------------------
1 | CREATE ENUM Status VALUES { Good, Bad };
2 |
--------------------------------------------------------------------------------
/tests/input_output/input/escaping.txt:
--------------------------------------------------------------------------------
1 | CREATE (n:Node{tmp:"\"\\;\\"}) RETURN n;
2 |
--------------------------------------------------------------------------------
/tests/input_output/output_csv/escaping.txt:
--------------------------------------------------------------------------------
1 | "n"
2 | "(:Node {tmp: ""\""\\;\\""})"
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # build directory
2 | build/
3 |
4 | # vim temporary files
5 | *.swp
6 | *.swo
7 |
--------------------------------------------------------------------------------
/tests/input_output/input/multiple_columns.txt:
--------------------------------------------------------------------------------
1 | CREATE (n:Node)-[e:Edge]->(m:Vertex) RETURN n,e,m;
2 |
--------------------------------------------------------------------------------
/tests/input_output/output_csv/multiple_columns.txt:
--------------------------------------------------------------------------------
1 | "n","e","m"
2 | "(:Node)","[:Edge]","(:Vertex)"
3 |
--------------------------------------------------------------------------------
/tests/input_output/output_csv/multiple_queries_per_line.txt:
--------------------------------------------------------------------------------
1 | "n"
2 | "(:Node)"
3 | "n"
4 | "(:Vertex)"
5 | "n"
6 | "(:Vertex)"
7 |
--------------------------------------------------------------------------------
/tests/input_output/input/multiple_queries_per_line.txt:
--------------------------------------------------------------------------------
1 | CREATE (n:Node) RETURN n; CREATE (n:Vertex) RETURN n; MATCH (n:Vertex) RETURN n;
2 |
--------------------------------------------------------------------------------
/tests/input_output/output_csv/query_per_line.txt:
--------------------------------------------------------------------------------
1 | "n"
2 | "(:Node)"
3 | "n"
4 | "(:Node)"
5 | "n"
6 | "(:Vertex)"
7 | "n"
8 | "(:Vertex)"
9 |
--------------------------------------------------------------------------------
/tests/input_output/input/query_per_line.txt:
--------------------------------------------------------------------------------
1 | CREATE (n:Node) RETURN n;
2 | MATCH (n:Node) RETURN n;
3 | CREATE (n:Vertex) RETURN n;
4 | MATCH (n:Vertex) RETURN n;
5 |
--------------------------------------------------------------------------------
/tests/input_output/input/enum.txt:
--------------------------------------------------------------------------------
1 | SHOW ENUMS;
2 | CREATE (n :l1 {s: Status::Good}) RETURN n;
3 | CREATE (n :l2 {s: {__type: "test", __value: "test_value"}}) RETURN n;
4 | MATCH (n) return n;
--------------------------------------------------------------------------------
/tests/input_output/output_csv/enum.txt:
--------------------------------------------------------------------------------
1 | "Enum Name","Enum Values"
2 | """Status""","[""Good"", ""Bad""]"
3 | "n"
4 | "(:l1 {s: Status::Good})"
5 | "n"
6 | "(:l2 {s: {__type: ""test"", __value: ""test_value""}})"
--------------------------------------------------------------------------------
/tests/input_output/output_tabular/escaping.txt:
--------------------------------------------------------------------------------
1 | +--------------------------+
2 | | n |
3 | +--------------------------+
4 | | (:Node {tmp: "\"\\;\\"}) |
5 | +--------------------------+
6 |
--------------------------------------------------------------------------------
/tests/input_output/input/spatial.txt:
--------------------------------------------------------------------------------
1 | RETURN point({x:0, y:1}) AS point;
2 | RETURN point({latitude:0, longitude:1}) AS point;
3 | RETURN point({x:0, y:1, z:2}) AS point;
4 | RETURN point({latitude:0, longitude:1, height:2}) AS point;
5 |
--------------------------------------------------------------------------------
/tests/input_output/output_csv/spatial.txt:
--------------------------------------------------------------------------------
1 | "point"
2 | "POINT({ x:0, y:1, srid:7203 })"
3 | "point"
4 | "POINT({ x:1, y:0, srid:4326 })"
5 | "point"
6 | "POINT({ x:0, y:1, z:2, srid:9757 })"
7 | "point"
8 | "POINT({ x:1, y:0, z:2, srid:4979 })"
9 |
--------------------------------------------------------------------------------
/.clang-format:
--------------------------------------------------------------------------------
1 | ---
2 | Language: Cpp
3 | BasedOnStyle: Google
4 | Standard: "c++20"
5 | UseTab: Never
6 | DerivePointerAlignment: false
7 | PointerAlignment: Right
8 | ColumnLimit : 120
9 | IncludeBlocks: Preserve
10 | ...
11 |
--------------------------------------------------------------------------------
/tests/input_output/output_tabular/multiple_columns.txt:
--------------------------------------------------------------------------------
1 | +-----------+-----------+-----------+
2 | | n | e | m |
3 | +-----------+-----------+-----------+
4 | | (:Node) | [:Edge] | (:Vertex) |
5 | +-----------+-----------+-----------+
6 |
--------------------------------------------------------------------------------
/tests/input_output/input/unfinished_query.txt:
--------------------------------------------------------------------------------
1 | CREATE (n:Ovid{quote:"Exitus Acta Probat"}) RETURN n; MATCH (n)
2 | RETURN n;
3 | MATCH (n) RETURN n; CREATE (n:Bible{quote:"Fiat Lux"}) RETURN n; CREATE (n:Plinius{quote:"In vino veritas"}) RETURN n; MATCH
4 | (n:Plinius)
5 | RETURN
6 | n;
7 |
--------------------------------------------------------------------------------
/tests/input_output/input/quote.txt:
--------------------------------------------------------------------------------
1 | CREATE (n:Ciceron{quote:"o tempora o mores"}) RETURN n;
2 | CREATE (n:Ciceron{quote:'o tempora o mores!'}) RETURN n;
3 | CREATE (n:Ciceron{quote:"o tempora 'o mores'"}) RETURN n;
4 | CREATE (n:Ciceron{quote:'o tempora "o mores"'}) RETURN n;
5 | CREATE (n:Ciceron{quote:"o tempora \"o mores\""}) RETURN n;
6 |
--------------------------------------------------------------------------------
/tests/input_output/output_tabular/multiple_queries_per_line.txt:
--------------------------------------------------------------------------------
1 | +---------+
2 | | n |
3 | +---------+
4 | | (:Node) |
5 | +---------+
6 | +-----------+
7 | | n |
8 | +-----------+
9 | | (:Vertex) |
10 | +-----------+
11 | +-----------+
12 | | n |
13 | +-----------+
14 | | (:Vertex) |
15 | +-----------+
16 |
--------------------------------------------------------------------------------
/tests/input_output/output_csv/quote.txt:
--------------------------------------------------------------------------------
1 | "n"
2 | "(:Ciceron {quote: ""o tempora o mores""})"
3 | "n"
4 | "(:Ciceron {quote: ""o tempora o mores!""})"
5 | "n"
6 | "(:Ciceron {quote: ""o tempora \'o mores\'""})"
7 | "n"
8 | "(:Ciceron {quote: ""o tempora \""o mores\""""})"
9 | "n"
10 | "(:Ciceron {quote: ""o tempora \""o mores\""""})"
11 |
--------------------------------------------------------------------------------
/tests/input_output/output_csv/unfinished_query.txt:
--------------------------------------------------------------------------------
1 | "n"
2 | "(:Ovid {quote: ""Exitus Acta Probat""})"
3 | "n"
4 | "(:Ovid {quote: ""Exitus Acta Probat""})"
5 | "n"
6 | "(:Ovid {quote: ""Exitus Acta Probat""})"
7 | "n"
8 | "(:Bible {quote: ""Fiat Lux""})"
9 | "n"
10 | "(:Plinius {quote: ""In vino veritas""})"
11 | "n"
12 | "(:Plinius {quote: ""In vino veritas""})"
13 |
--------------------------------------------------------------------------------
/tests/input_output/input/multiline_query.txt:
--------------------------------------------------------------------------------
1 | CREATE (n:Constantine{quote:"In hoc signo vinces"})
2 | RETURN n;
3 | MATCH
4 | (n)
5 | RETURN
6 | n;
7 | CREATE (n:Erdody{quote:
8 | "Regnum regno non praescribit leges"})
9 | RETURN
10 | n;
11 | MATCH (n:Erdody) RETURN
12 | n;
13 | CREATE (n:Caesar{quote:"Alea iacta
14 | est"}) RETURN n;
15 | MATCH (n:Caesar)
16 | RETURN n;
17 |
--------------------------------------------------------------------------------
/tests/input_output/output_tabular/query_per_line.txt:
--------------------------------------------------------------------------------
1 | +---------+
2 | | n |
3 | +---------+
4 | | (:Node) |
5 | +---------+
6 | +---------+
7 | | n |
8 | +---------+
9 | | (:Node) |
10 | +---------+
11 | +-----------+
12 | | n |
13 | +-----------+
14 | | (:Vertex) |
15 | +-----------+
16 | +-----------+
17 | | n |
18 | +-----------+
19 | | (:Vertex) |
20 | +-----------+
21 |
--------------------------------------------------------------------------------
/tests/input_output/input/multiline_query_with_comments.txt:
--------------------------------------------------------------------------------
1 | CREATE (n:Constantine{quote:"In hoc signo vinces"})
2 | RETURN n;
3 | MATCH
4 | (n)
5 | // comment
6 | RETURN
7 | n;
8 | CREATE (n:Erdody{quote:
9 | "Regnum regno non praescribit leges"})
10 | RETURN
11 | n;
12 | // comment
13 | MATCH (n:Erdody) RETURN
14 | n;
15 | CREATE (n:Caesar{quote:"Alea iacta est"})
16 | RETURN n;
17 | MATCH (n:Caesar)
18 | RETURN n;
19 |
--------------------------------------------------------------------------------
/tests/input_output/output_csv/multiline_query.txt:
--------------------------------------------------------------------------------
1 | "n"
2 | "(:Constantine {quote: ""In hoc signo vinces""})"
3 | "n"
4 | "(:Constantine {quote: ""In hoc signo vinces""})"
5 | "n"
6 | "(:Erdody {quote: ""Regnum regno non praescribit leges""})"
7 | "n"
8 | "(:Erdody {quote: ""Regnum regno non praescribit leges""})"
9 | "n"
10 | "(:Caesar {quote: ""Alea iacta\nest""})"
11 | "n"
12 | "(:Caesar {quote: ""Alea iacta\nest""})"
13 |
--------------------------------------------------------------------------------
/tests/input_output/output_csv/multiline_query_with_comments.txt:
--------------------------------------------------------------------------------
1 | "n"
2 | "(:Constantine {quote: ""In hoc signo vinces""})"
3 | "n"
4 | "(:Constantine {quote: ""In hoc signo vinces""})"
5 | "n"
6 | "(:Erdody {quote: ""Regnum regno non praescribit leges""})"
7 | "n"
8 | "(:Erdody {quote: ""Regnum regno non praescribit leges""})"
9 | "n"
10 | "(:Caesar {quote: ""Alea iacta est""})"
11 | "n"
12 | "(:Caesar {quote: ""Alea iacta est""})"
13 |
--------------------------------------------------------------------------------
/tests/input_output/output_tabular/enum.txt:
--------------------------------------------------------------------------------
1 | +-----------------+-----------------+
2 | | Enum Name | Enum Values |
3 | +-----------------+-----------------+
4 | | "Status" | ["Good", "Bad"] |
5 | +-----------------+-----------------+
6 | +-------------------------+
7 | | n |
8 | +-------------------------+
9 | | (:l1 {s: Status::Good}) |
10 | +-------------------------+
11 | +----------------------------------------------------+
12 | | n |
13 | +----------------------------------------------------+
14 | | (:l2 {s: {__type: "test", __value: "test_value"}}) |
15 | +----------------------------------------------------+
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 |
2 | FROM debian:bullseye-slim AS builder
3 |
4 | ENV DEBIAN_FRONTEND=noninteractive
5 |
6 | RUN apt-get update && apt-get install -y \
7 | git \
8 | cmake \
9 | make \
10 | gcc \
11 | g++ \
12 | libssl-dev \
13 | && rm -rf /var/lib/apt/lists/*
14 |
15 | RUN git clone https://github.com/memgraph/mgconsole.git /mgconsole
16 |
17 | WORKDIR /mgconsole
18 |
19 | RUN mkdir build && cd build && \
20 | cmake -DCMAKE_BUILD_TYPE=Release .. && \
21 | make && \
22 | make install
23 |
24 | FROM debian:bullseye-slim
25 |
26 | WORKDIR /mgconsole
27 |
28 | COPY --from=builder /mgconsole/build/src/mgconsole /usr/local/bin/mgconsole
29 |
30 | ENTRYPOINT ["mgconsole"]
31 |
--------------------------------------------------------------------------------
/tests/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # mgconsole - console client for Memgraph database
2 | # Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program. If not, see .
16 |
17 | add_subdirectory(input_output)
18 |
--------------------------------------------------------------------------------
/src/version.hpp.in:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
2 | //
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License
14 | // along with this program. If not, see .
15 |
16 | #pragma once
17 |
18 | static const char *version_string = "@PROJECT_VERSION@";
19 |
--------------------------------------------------------------------------------
/tests/input_output/output_tabular/spatial.txt:
--------------------------------------------------------------------------------
1 | +--------------------------------+
2 | | point |
3 | +--------------------------------+
4 | | POINT({ x:0, y:1, srid:7203 }) |
5 | +--------------------------------+
6 | +--------------------------------+
7 | | point |
8 | +--------------------------------+
9 | | POINT({ x:1, y:0, srid:4326 }) |
10 | +--------------------------------+
11 | +-------------------------------------+
12 | | point |
13 | +-------------------------------------+
14 | | POINT({ x:0, y:1, z:2, srid:9757 }) |
15 | +-------------------------------------+
16 | +-------------------------------------+
17 | | point |
18 | +-------------------------------------+
19 | | POINT({ x:1, y:0, z:2, srid:4979 }) |
20 | +-------------------------------------+
21 |
--------------------------------------------------------------------------------
/src/parsing.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
2 | //
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License
14 | // along with this program. If not, see .
15 |
16 | #pragma once
17 |
18 | namespace mode::parsing {
19 |
20 | int Run(bool collect_parser_stats, bool print_parser_stats);
21 |
22 | } // namespace mode::parsing
23 |
--------------------------------------------------------------------------------
/tests/input_output/input/temporal.txt:
--------------------------------------------------------------------------------
1 | RETURN date("1999-05-05");
2 | RETURN date({year: 2012, month: 12, day: 5});
3 | RETURN localtime({hour: 23, minute: 56, second: 23});
4 | RETURN localtime("12:01:12");
5 | RETURN localdatetime("2000-09-12T06:21:45");
6 | RETURN localdatetime({year: 2000, day: 23, hour: 12, second: 21});
7 | RETURN duration({day: 23, hour: 100, second: 21});
8 | RETURN duration({second: 0, microsecond: -123});
9 | RETURN duration("P1DT48H61M79.123S");
10 | RETURN datetime("2024-04-21T14:15:00-07:00[America/Los_Angeles]");
11 | RETURN datetime("2024-04-21T14:15:00+07:30");
12 | RETURN datetime("2024-04-21T14:15:00-05:45");
13 | RETURN datetime("2021-04-21T14:15:00Z");
14 | RETURN datetime({year: 2024, month: 4, day: 21, hour: 14, minute: 15, timezone: "America/Los_Angeles"});
15 | RETURN datetime({year: 2021, month: 4, day: 21, hour: 14, minute: 15, timezone: -60});
16 | RETURN datetime({year: 2021, month: 4, day: 21, hour: 14, minute: 15, timezone: 90});
17 |
--------------------------------------------------------------------------------
/src/utils/assert.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
2 | //
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License
14 | // along with this program. If not, see .
15 |
16 | #pragma once
17 |
18 | // #define NDEBUG
19 | #include
20 | // Use (void) to silence unused warnings.
21 | #define MG_ASSERT(exp, msg) assert(((void)msg, exp))
22 | #define MG_FAIL(msg) assert(((void)msg, false))
23 |
--------------------------------------------------------------------------------
/src/serial_import.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
2 | //
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License
14 | // along with this program. If not, see .
15 |
16 | #pragma once
17 |
18 | #include "utils/bolt.hpp"
19 | #include "utils/utils.hpp"
20 |
21 | namespace mode::serial_import {
22 |
23 | int Run(const utils::bolt::Config &bolt_config, const format::CsvOptions &csv_opts,
24 | const format::OutputOptions &output_opts);
25 |
26 | } // namespace mode::serial_import
27 |
--------------------------------------------------------------------------------
/src/utils/bolt.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
2 | //
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License
14 | // along with this program. If not, see .
15 |
16 | #pragma once
17 |
18 | #include "utils.hpp"
19 |
20 | namespace utils::bolt {
21 |
22 | struct Config {
23 | std::string host;
24 | int port;
25 | std::string username;
26 | std::string password;
27 | bool use_ssl;
28 | };
29 |
30 | mg_memory::MgSessionPtr MakeBoltSession(const Config &config);
31 |
32 | } // namespace utils::bolt
33 |
--------------------------------------------------------------------------------
/src/interactive.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
2 | //
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License
14 | // along with this program. If not, see .
15 |
16 | #pragma once
17 |
18 | #include "utils/bolt.hpp"
19 | #include "utils/utils.hpp"
20 |
21 | namespace mode::interactive {
22 |
23 | int Run(utils::bolt::Config &bolt_config, const std::string &history, bool no_history, bool verbose_execution_info,
24 | const format::CsvOptions &csv_opts, const format::OutputOptions &output_opts);
25 |
26 | } // namespace mode::interactive
27 |
--------------------------------------------------------------------------------
/tests/input_output/output_tabular/quote.txt:
--------------------------------------------------------------------------------
1 | +-----------------------------------------+
2 | | n |
3 | +-----------------------------------------+
4 | | (:Ciceron {quote: "o tempora o mores"}) |
5 | +-----------------------------------------+
6 | +------------------------------------------+
7 | | n |
8 | +------------------------------------------+
9 | | (:Ciceron {quote: "o tempora o mores!"}) |
10 | +------------------------------------------+
11 | +---------------------------------------------+
12 | | n |
13 | +---------------------------------------------+
14 | | (:Ciceron {quote: "o tempora \'o mores\'"}) |
15 | +---------------------------------------------+
16 | +---------------------------------------------+
17 | | n |
18 | +---------------------------------------------+
19 | | (:Ciceron {quote: "o tempora \"o mores\""}) |
20 | +---------------------------------------------+
21 | +---------------------------------------------+
22 | | n |
23 | +---------------------------------------------+
24 | | (:Ciceron {quote: "o tempora \"o mores\""}) |
25 | +---------------------------------------------+
26 |
--------------------------------------------------------------------------------
/tests/input_output/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # mgconsole - console client for Memgraph database
2 | # Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program. If not, see .
16 |
17 | set(MEMGRAPH_PATH "/usr/lib/memgraph/memgraph" CACHE FILEPATH
18 | "Path to Memgraph binary for client tests")
19 |
20 | add_test(NAME mgconsole-test
21 | COMMAND ./run-tests.sh ${MEMGRAPH_PATH} ${PROJECT_BINARY_DIR}/src/mgconsole
22 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
23 |
24 | add_test(NAME mgconsole-secure-test
25 | COMMAND ./run-tests.sh --use-ssl ${MEMGRAPH_PATH} ${PROJECT_BINARY_DIR}/src/mgconsole
26 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
27 |
--------------------------------------------------------------------------------
/tests/input_output/output_tabular/unfinished_query.txt:
--------------------------------------------------------------------------------
1 | +---------------------------------------+
2 | | n |
3 | +---------------------------------------+
4 | | (:Ovid {quote: "Exitus Acta Probat"}) |
5 | +---------------------------------------+
6 | +---------------------------------------+
7 | | n |
8 | +---------------------------------------+
9 | | (:Ovid {quote: "Exitus Acta Probat"}) |
10 | +---------------------------------------+
11 | +---------------------------------------+
12 | | n |
13 | +---------------------------------------+
14 | | (:Ovid {quote: "Exitus Acta Probat"}) |
15 | +---------------------------------------+
16 | +------------------------------+
17 | | n |
18 | +------------------------------+
19 | | (:Bible {quote: "Fiat Lux"}) |
20 | +------------------------------+
21 | +---------------------------------------+
22 | | n |
23 | +---------------------------------------+
24 | | (:Plinius {quote: "In vino veritas"}) |
25 | +---------------------------------------+
26 | +---------------------------------------+
27 | | n |
28 | +---------------------------------------+
29 | | (:Plinius {quote: "In vino veritas"}) |
30 | +---------------------------------------+
31 |
--------------------------------------------------------------------------------
/tests/input_output/output_csv/temporal.txt:
--------------------------------------------------------------------------------
1 | "date(""1999-05-05"")"
2 | "1999-05-05"
3 | "date({year: 2012, month: 12, day: 5})"
4 | "2012-12-05"
5 | "localtime({hour: 23, minute: 56, second: 23})"
6 | "23:56:23.000000000"
7 | "localtime(""12:01:12"")"
8 | "12:01:12.000000000"
9 | "localdatetime(""2000-09-12T06:21:45"")"
10 | "2000-09-12 06:21:45.000000000"
11 | "localdatetime({year: 2000, day: 23, hour: 12, second: 21})"
12 | "2000-01-23 12:00:21.000000000"
13 | "duration({day: 23, hour: 100, second: 21})"
14 | "P27DT4H21S"
15 | "duration({second: 0, microsecond: -123})"
16 | "P-0.000123S"
17 | "duration(""P1DT48H61M79.123S"")"
18 | "P3DT1H2M19.123000S"
19 | "datetime(""2024-04-21T14:15:00-07:00[America/Los_Angeles]"")"
20 | "2024-04-21 14:15:00.000000000[America/Los_Angeles]"
21 | "datetime(""2024-04-21T14:15:00+07:30"")"
22 | "2024-04-21 14:15:00.000000000+07:30"
23 | "datetime(""2024-04-21T14:15:00-05:45"")"
24 | "2024-04-21 14:15:00.000000000-05:45"
25 | "datetime(""2021-04-21T14:15:00Z"")"
26 | "2021-04-21 14:15:00.000000000[Etc/UTC]"
27 | "datetime({year: 2024, month: 4, day: 21, hour: 14, minute: 15, timezone: ""America/Los_Angeles""})"
28 | "2024-04-21 14:15:00.000000000[America/Los_Angeles]"
29 | "datetime({year: 2021, month: 4, day: 21, hour: 14, minute: 15, timezone: -60})"
30 | "2021-04-21 14:15:00.000000000-01:00"
31 | "datetime({year: 2021, month: 4, day: 21, hour: 14, minute: 15, timezone: 90})"
32 | "2021-04-21 14:15:00.000000000+01:30"
33 |
--------------------------------------------------------------------------------
/src/batch_import.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
2 | //
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License
14 | // along with this program. If not, see .
15 |
16 | #pragma once
17 |
18 | #include "utils/bolt.hpp"
19 |
20 | // NOTE: Batched and parallel execution has many practical issue.
21 | // * In the transactional mode, there are many serialization errors -> check if a transaction was successfully
22 | // executed + retry are required.
23 | // * In the analytical mode, almost any query will pass (e.g. edge creation won't fail if nodes are not there) / it's
24 | // hard to detect any issue -> ordering of nodes and edges is the only way to correctly import data.
25 |
26 | namespace mode::batch_import {
27 |
28 | int Run(const utils::bolt::Config &bolt_config, int batch_size, int workers_number);
29 |
30 | } // namespace mode::batch_import
31 |
--------------------------------------------------------------------------------
/tests/input_output/output_tabular/multiline_query.txt:
--------------------------------------------------------------------------------
1 | +-----------------------------------------------+
2 | | n |
3 | +-----------------------------------------------+
4 | | (:Constantine {quote: "In hoc signo vinces"}) |
5 | +-----------------------------------------------+
6 | +-----------------------------------------------+
7 | | n |
8 | +-----------------------------------------------+
9 | | (:Constantine {quote: "In hoc signo vinces"}) |
10 | +-----------------------------------------------+
11 | +---------------------------------------------------------+
12 | | n |
13 | +---------------------------------------------------------+
14 | | (:Erdody {quote: "Regnum regno non praescribit leges"}) |
15 | +---------------------------------------------------------+
16 | +---------------------------------------------------------+
17 | | n |
18 | +---------------------------------------------------------+
19 | | (:Erdody {quote: "Regnum regno non praescribit leges"}) |
20 | +---------------------------------------------------------+
21 | +--------------------------------------+
22 | | n |
23 | +--------------------------------------+
24 | | (:Caesar {quote: "Alea iacta\nest"}) |
25 | +--------------------------------------+
26 | +--------------------------------------+
27 | | n |
28 | +--------------------------------------+
29 | | (:Caesar {quote: "Alea iacta\nest"}) |
30 | +--------------------------------------+
31 |
--------------------------------------------------------------------------------
/tests/input_output/output_tabular/multiline_query_with_comments.txt:
--------------------------------------------------------------------------------
1 | +-----------------------------------------------+
2 | | n |
3 | +-----------------------------------------------+
4 | | (:Constantine {quote: "In hoc signo vinces"}) |
5 | +-----------------------------------------------+
6 | +-----------------------------------------------+
7 | | n |
8 | +-----------------------------------------------+
9 | | (:Constantine {quote: "In hoc signo vinces"}) |
10 | +-----------------------------------------------+
11 | +---------------------------------------------------------+
12 | | n |
13 | +---------------------------------------------------------+
14 | | (:Erdody {quote: "Regnum regno non praescribit leges"}) |
15 | +---------------------------------------------------------+
16 | +---------------------------------------------------------+
17 | | n |
18 | +---------------------------------------------------------+
19 | | (:Erdody {quote: "Regnum regno non praescribit leges"}) |
20 | +---------------------------------------------------------+
21 | +-------------------------------------+
22 | | n |
23 | +-------------------------------------+
24 | | (:Caesar {quote: "Alea iacta est"}) |
25 | +-------------------------------------+
26 | +-------------------------------------+
27 | | n |
28 | +-------------------------------------+
29 | | (:Caesar {quote: "Alea iacta est"}) |
30 | +-------------------------------------+
31 |
--------------------------------------------------------------------------------
/src/utils/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | ExternalProject_Add(replxx-proj
2 | PREFIX replxx
3 | GIT_REPOSITORY https://github.com/AmokHuginnsson/replxx.git
4 | GIT_TAG release-0.0.4
5 | CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX="
6 | "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}"
7 | "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
8 | "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}"
9 | "-DREPLXX_BUILD_EXAMPLES=OFF"
10 | "-DBUILD_SHARED_LIBS=OFF"
11 | INSTALL_DIR "${PROJECT_BINARY_DIR}/replxx")
12 |
13 | if(CMAKE_BUILD_TYPE_LOWERCASE STREQUAL "debug")
14 | set(REPLXX_LIB_POSTFIX "-d")
15 | elseif(CMAKE_BUILD_TYPE_LOWERCASE STREQUAL "relwithdebinfo")
16 | set(REPLXX_LIB_POSTFIX "-rd")
17 | else()
18 | set(REPLXX_LIB_POSTFIX "")
19 | endif()
20 |
21 | ExternalProject_Get_Property(replxx-proj INSTALL_DIR)
22 | set(REPLXX_ROOT ${INSTALL_DIR})
23 | set(REPLXX_INCLUDE_DIRS ${REPLXX_ROOT}/include)
24 | set(REPLXX_LIBRARY_PATH ${REPLXX_ROOT}/${MG_INSTALL_LIB_DIR}/libreplxx${REPLXX_LIB_POSTFIX}.a)
25 | set(REPLXX_LIBRARY replxx)
26 |
27 | add_library(${REPLXX_LIBRARY} STATIC IMPORTED GLOBAL)
28 | target_compile_definitions(${REPLXX_LIBRARY} INTERFACE REPLXX_STATIC)
29 | set_target_properties(${REPLXX_LIBRARY} PROPERTIES
30 | IMPORTED_LOCATION ${REPLXX_LIBRARY_PATH})
31 |
32 | add_dependencies(${REPLXX_LIBRARY} replxx-proj)
33 | add_library(utils STATIC utils.cpp thread_pool.cpp bolt.cpp)
34 | add_dependencies(utils replxx gflags mgclient)
35 | target_compile_definitions(utils PUBLIC MGCLIENT_STATIC_DEFINE)
36 | target_include_directories(utils PUBLIC ${REPLXX_INCLUDE_DIRS} ${GFLAGS_INCLUDE_DIRS} ${MGCLIENT_INCLUDE_DIRS})
37 | target_link_libraries(utils ${REPLXX_LIBRARY})
38 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # mgconsole - console client for Memgraph database
2 | # Copyright (C) 2016-2021 Memgraph Ltd. [https://memgraph.com]
3 | #
4 | # This program is free software: you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation, either version 3 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program. If not, see .
16 |
17 | cmake_minimum_required(VERSION 3.5)
18 | project(mgconsole VERSION 1.4)
19 | include(CTest)
20 |
21 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake)
22 |
23 | set(CMAKE_C_STANDARD 11)
24 | set(CMAKE_CXX_STANDARD 20)
25 |
26 | # Set default build type to 'Release'
27 | if (NOT CMAKE_BUILD_TYPE)
28 | set(CMAKE_BUILD_TYPE "Release")
29 | endif()
30 | message(STATUS "CMake build type: ${CMAKE_BUILD_TYPE}")
31 | # Config of some libraries (e.g. replxx) depends on the build type.
32 | string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWERCASE)
33 | # Enable explicit static linking of SSL because of generic Linux binary.
34 | set(MGCONSOLE_STATIC_SSL OFF CACHE STRING "Statically link SSL")
35 |
36 | # Set default installation directory to '/usr'
37 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
38 | # '/usr' is a special case, for more details see:
39 | # https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html#special-cases
40 | set(CMAKE_INSTALL_PREFIX "/usr" CACHE PATH "install dir" FORCE)
41 | endif()
42 |
43 | add_subdirectory(src)
44 | if(BUILD_TESTING)
45 | add_subdirectory(tests)
46 | endif()
47 |
--------------------------------------------------------------------------------
/src/utils/thread_pool.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
2 | //
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License
14 | // along with this program. If not, see .
15 |
16 | #pragma once
17 |
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 |
25 | #include "synchronized.hpp"
26 |
27 | namespace utils {
28 |
29 | class ThreadPool {
30 | using TaskSignature = std::function;
31 |
32 | public:
33 | explicit ThreadPool(size_t pool_size);
34 | ThreadPool(const ThreadPool &) = delete;
35 | ThreadPool(ThreadPool &&) = delete;
36 | ThreadPool &operator=(const ThreadPool &) = delete;
37 | ThreadPool &operator=(ThreadPool &&) = delete;
38 | ~ThreadPool();
39 |
40 | void AddTask(std::function new_task);
41 | size_t UnfinishedTasksNum() const;
42 | void Shutdown();
43 |
44 | private:
45 | void ThreadLoop();
46 | std::unique_ptr PopTask();
47 |
48 | std::vector thread_pool_;
49 | std::atomic unfinished_tasks_num_{0};
50 | std::atomic terminate_pool_{false};
51 | std::atomic stopped_{false};
52 | utils::Synchronized>, std::mutex> task_queue_;
53 | std::mutex pool_lock_;
54 | std::condition_variable queue_cv_;
55 | };
56 |
57 | } // namespace utils
58 |
--------------------------------------------------------------------------------
/src/serial_import.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
2 | //
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License
14 | // along with this program. If not, see .
15 |
16 | #include "serial_import.hpp"
17 |
18 | namespace mode::serial_import {
19 |
20 | using namespace std::string_literals;
21 |
22 | int Run(const utils::bolt::Config &bolt_config, const format::CsvOptions &csv_opts,
23 | const format::OutputOptions &output_opts) {
24 | auto session = MakeBoltSession(bolt_config);
25 | if (session.get() == nullptr) {
26 | return 1;
27 | }
28 |
29 | while (true) {
30 | auto query = query::GetQuery(nullptr);
31 | if (!query) {
32 | break;
33 | }
34 | if (query->query.empty()) {
35 | continue;
36 | }
37 |
38 | try {
39 | auto ret = query::ExecuteQuery(session.get(), query->query);
40 | if (ret.records.size() > 0) {
41 | Output(ret.header, ret.records, output_opts, csv_opts);
42 | }
43 | } catch (const utils::ClientQueryException &e) {
44 | console::EchoFailure("Failed query", query->query);
45 | console::EchoFailure("Client received query exception", e.what());
46 | return 1;
47 | } catch (const utils::ClientFatalException &e) {
48 | console::EchoFailure("Client received connection exception", e.what());
49 | return 1;
50 | }
51 | }
52 |
53 | return 0;
54 | }
55 |
56 | } // namespace mode::serial_import
57 |
--------------------------------------------------------------------------------
/src/parsing.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
2 | //
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License
14 | // along with this program. If not, see .
15 |
16 | #include "parsing.hpp"
17 |
18 | #include "utils/utils.hpp"
19 |
20 | namespace mode::parsing {
21 |
22 | using namespace std::string_literals;
23 |
24 | int Run(bool collect_parsing_stats, bool print_parser_stats) {
25 | int64_t query_index = 0;
26 | while (true) {
27 | auto query = query::GetQuery(nullptr, collect_parsing_stats);
28 | if (!query) {
29 | break;
30 | }
31 | if (query->query.empty()) {
32 | continue;
33 | }
34 | if (collect_parsing_stats && print_parser_stats) {
35 | std::cout << "Line: " << query->line_number << " "
36 | << "Index: " << query->index << " "
37 | << "has_create: " << query->info->has_create << " "
38 | << "has_match: " << query->info->has_match << " "
39 | << "has_merge: " << query->info->has_merge << " "
40 | << "has_detach_delete: " << query->info->has_detach_delete << " "
41 | << "has_create_index: " << query->info->has_create_index << " "
42 | << "has_drop_index: " << query->info->has_drop_index << " "
43 | << "has_storage_mode: " << query->info->has_storage_mode << " "
44 | << "has_remove: " << query->info->has_remove << " " << std::endl;
45 | }
46 | ++query_index;
47 | }
48 | std::cout << "Parsed " << query_index << " queries" << std::endl;
49 | return 0;
50 | }
51 |
52 | } // namespace mode::parsing
53 |
--------------------------------------------------------------------------------
/src/utils/bolt.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
2 | //
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License
14 | // along with this program. If not, see .
15 |
16 | #include "bolt.hpp"
17 |
18 | #include "gflags/gflags.h"
19 |
20 | namespace utils::bolt {
21 |
22 | using namespace std::string_literals;
23 |
24 | mg_memory::MgSessionPtr MakeBoltSession(const Config &config) {
25 | std::string bolt_client_version = "mg/"s + gflags::VersionString();
26 | mg_memory::MgSessionParamsPtr params = mg_memory::MakeCustomUnique(mg_session_params_make());
27 | if (!params) {
28 | console::EchoFailure("Connection failure", "out of memory, failed to allocate `mg_session_params` struct");
29 | }
30 | mg_session_params_set_host(params.get(), config.host.c_str());
31 | mg_session_params_set_port(params.get(), config.port);
32 | if (!config.username.empty()) {
33 | mg_session_params_set_username(params.get(), config.username.c_str());
34 | mg_session_params_set_password(params.get(), config.password.c_str());
35 | }
36 | mg_session_params_set_user_agent(params.get(), bolt_client_version.c_str());
37 | mg_session_params_set_sslmode(params.get(), config.use_ssl ? MG_SSLMODE_REQUIRE : MG_SSLMODE_DISABLE);
38 | mg_memory::MgSessionPtr session = mg_memory::MakeCustomUnique(nullptr);
39 | {
40 | mg_session *session_tmp;
41 | int status = mg_connect(params.get(), &session_tmp);
42 | session = mg_memory::MakeCustomUnique(session_tmp);
43 | if (status != 0) {
44 | console::EchoFailure("Connection failure", mg_session_error(session.get()));
45 | return mg_memory::MakeCustomUnique(nullptr);
46 | }
47 | return session;
48 | }
49 | return session;
50 | }
51 |
52 | } // namespace utils::bolt
53 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Build and Release mgconsole Docker Image
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | version:
7 | description: "Version of the Docker image to publish."
8 | required: true
9 | default: "0.0.1"
10 | force_release:
11 | description: "Force release even if the version already exists."
12 | type: boolean
13 | required: false
14 | default: false
15 |
16 | jobs:
17 | build-and-push:
18 | runs-on: ubuntu-latest
19 | env:
20 | DOCKER_ORGANIZATION_NAME: memgraph
21 | DOCKER_REPOSITORY_NAME: mgconsole
22 |
23 | steps:
24 | - name: Checkout repository
25 | uses: actions/checkout@v2
26 |
27 | - name: Set up Docker Buildx
28 | uses: docker/setup-buildx-action@v2
29 |
30 | - name: Log in to Docker Hub
31 | uses: docker/login-action@v2
32 | with:
33 | username: ${{ secrets.DOCKER_USERNAME }}
34 | password: ${{ secrets.DOCKER_PASSWORD }}
35 |
36 | - name: Check if specified version is already pushed
37 | run: |
38 | EXISTS=$(docker manifest inspect ${{ env.DOCKER_ORGANIZATION_NAME }}/${{ env.DOCKER_REPOSITORY_NAME }}:${{ github.event.inputs.version }} > /dev/null; echo $?)
39 | echo $EXISTS
40 | if [[ ${EXISTS} -eq 0 ]]; then
41 | echo 'The specified version has been already released to DockerHub.'
42 | if [[ ${{ github.event.inputs.force_release }} == 'true' ]]; then
43 | echo 'Forcing the release!'
44 | else
45 | echo 'Stopping the release!'
46 | exit 1
47 | fi
48 | else
49 | echo 'All good, the specified version has not been released to DockerHub.'
50 | fi
51 |
52 | - name: Build and push Docker image
53 | uses: docker/build-push-action@v4
54 | with:
55 | context: .
56 | push: true
57 | tags: |
58 | ${{ env.DOCKER_ORGANIZATION_NAME }}/${{ env.DOCKER_REPOSITORY_NAME }}:${{ github.event.inputs.version }}
59 | ${{ env.DOCKER_ORGANIZATION_NAME }}/${{ env.DOCKER_REPOSITORY_NAME }}:latest
60 | platforms: linux/amd64,linux/arm64
61 |
62 |
63 | - name: Verify Docker image
64 | run: |
65 | docker run --rm $DOCKER_ORGANIZATION_NAME/$DOCKER_REPOSITORY_NAME:${{ github.event.inputs.version }} --version
--------------------------------------------------------------------------------
/src/utils/thread_pool.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
2 | //
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License
14 | // along with this program. If not, see .
15 |
16 | #include "thread_pool.hpp"
17 |
18 | namespace utils {
19 |
20 | ThreadPool::ThreadPool(const size_t pool_size) {
21 | for (size_t i = 0; i < pool_size; ++i) {
22 | thread_pool_.emplace_back(([this] { this->ThreadLoop(); }));
23 | }
24 | }
25 |
26 | void ThreadPool::AddTask(std::function new_task) {
27 | task_queue_.WithLock([&](auto &queue) {
28 | queue.emplace(std::make_unique(std::move(new_task)));
29 | unfinished_tasks_num_.fetch_add(1);
30 | });
31 | std::unique_lock pool_guard(pool_lock_);
32 | queue_cv_.notify_one();
33 | }
34 |
35 | void ThreadPool::Shutdown() {
36 | terminate_pool_.store(true);
37 | {
38 | std::unique_lock pool_guard(pool_lock_);
39 | queue_cv_.notify_all();
40 | }
41 |
42 | for (auto &thread : thread_pool_) {
43 | if (thread.joinable()) {
44 | thread.join();
45 | }
46 | }
47 |
48 | thread_pool_.clear();
49 | stopped_.store(true);
50 | }
51 |
52 | ThreadPool::~ThreadPool() {
53 | if (!stopped_.load()) {
54 | Shutdown();
55 | }
56 | }
57 |
58 | std::unique_ptr ThreadPool::PopTask() {
59 | return task_queue_.WithLock([](auto &queue) -> std::unique_ptr {
60 | if (queue.empty()) {
61 | return nullptr;
62 | }
63 | auto front = std::move(queue.front());
64 | queue.pop();
65 | return front;
66 | });
67 | }
68 |
69 | void ThreadPool::ThreadLoop() {
70 | std::unique_ptr task = PopTask();
71 | while (true) {
72 | while (task) {
73 | if (terminate_pool_.load()) {
74 | return;
75 | }
76 | (*task)();
77 | unfinished_tasks_num_.fetch_sub(1);
78 | task = PopTask();
79 | }
80 |
81 | std::unique_lock guard(pool_lock_);
82 | queue_cv_.wait(guard, [&] {
83 | task = PopTask();
84 | return task || terminate_pool_.load();
85 | });
86 | if (terminate_pool_.load()) {
87 | return;
88 | }
89 | }
90 | }
91 |
92 | size_t ThreadPool::UnfinishedTasksNum() const { return unfinished_tasks_num_.load(); }
93 |
94 | } // namespace utils
95 |
--------------------------------------------------------------------------------
/src/utils/notifier.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
2 | //
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License
14 | // along with this program. If not, see .
15 |
16 | #pragma once
17 |
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 |
24 | namespace utils {
25 |
26 | class ReadinessToken {
27 | size_t id_;
28 |
29 | public:
30 | explicit ReadinessToken(size_t id) : id_(id) {}
31 | size_t GetId() const { return id_; }
32 | };
33 |
34 | class Inner {
35 | std::condition_variable cv_;
36 | std::mutex mu_;
37 | std::vector ready_;
38 | std::optional> tick_simulator_;
39 |
40 | public:
41 | void Notify(ReadinessToken readiness_token) {
42 | {
43 | std::unique_lock lock(mu_);
44 | ready_.emplace_back(readiness_token);
45 | } // mutex dropped
46 |
47 | cv_.notify_all();
48 | }
49 |
50 | ReadinessToken Await() {
51 | std::unique_lock lock(mu_);
52 |
53 | while (ready_.empty()) {
54 | if (tick_simulator_) [[unlikely]] {
55 | // This avoids a deadlock in a similar way that
56 | // Future::Wait will release its mutex while
57 | // interacting with the simulator, due to
58 | // the fact that the simulator may cause
59 | // notifications that we are interested in.
60 | lock.unlock();
61 | std::invoke(tick_simulator_.value());
62 | lock.lock();
63 | } else {
64 | cv_.wait(lock);
65 | }
66 | }
67 |
68 | ReadinessToken ret = ready_.back();
69 | ready_.pop_back();
70 | return ret;
71 | }
72 |
73 | void InstallSimulatorTicker(std::function tick_simulator) {
74 | std::unique_lock lock(mu_);
75 | tick_simulator_ = tick_simulator;
76 | }
77 | };
78 |
79 | class Notifier {
80 | std::shared_ptr inner_;
81 |
82 | public:
83 | Notifier() : inner_(std::make_shared()) {}
84 | Notifier(const Notifier &) = default;
85 | Notifier &operator=(const Notifier &) = default;
86 | Notifier(Notifier &&old) = default;
87 | Notifier &operator=(Notifier &&old) = default;
88 | ~Notifier() = default;
89 |
90 | void Notify(ReadinessToken readiness_token) const { inner_->Notify(readiness_token); }
91 |
92 | ReadinessToken Await() const { return inner_->Await(); }
93 |
94 | void InstallSimulatorTicker(std::function tick_simulator) { inner_->InstallSimulatorTicker(tick_simulator); }
95 | };
96 |
97 | } // namespace utils
98 |
--------------------------------------------------------------------------------
/tests/dataset_benchmark/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | # mgconsole - console client for Memgraph database
4 | # Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
5 | #
6 | # This program is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU General Public License as published by
8 | # the Free Software Foundation, either version 3 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program. If not, see .
18 |
19 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
20 | cd "$DIR"
21 |
22 | MGCONSOLE_BINARY="${MGCONSOLE_BINARY:-$DIR/../../build/src/mgconsole}"
23 | MGCONSOLE_SETUP="${MGCONSOLE_SETUP:-STORAGE MODE IN_MEMORY_ANALYTICAL;}"
24 | MGCONSOLE_BATCH_SIZE="${MGCONSOLE_BATCH_SIZE:-1000}"
25 | MGCONSOLE_WORKERS="${MGCONSOLE_WORKERS:-32}"
26 |
27 | TIMEFORMAT=%R
28 | DATASETS=(
29 | "https://download.memgraph.com/datasets/cora-scientific-publications/cora-scientific-publications.cypherl.gz 2708 5278"
30 | "https://download.memgraph.com/datasets/marvel-cinematic-universe/marvel-cinematic-universe.cypherl.gz 21732 682943"
31 | )
32 |
33 | function check_dataset {
34 | expected_nodes=$1
35 | expected_edges=$2
36 | actual_nodes=$(echo "MATCH (n) RETURN count(n);" | $MGCONSOLE_BINARY --output-format=csv | tail -n 1 | tr -d '"')
37 | actual_edges=$(echo "MATCH (n)-[r]->(m) RETURN count(r);" | $MGCONSOLE_BINARY --output-format=csv | tail -n 1 | tr -d '"')
38 | if [[ $expected_nodes != $actual_nodes ]]; then
39 | echo "The number of nodes is wrong, expected: $expected_nodes actual: $actual_nodes"
40 | exit 1
41 | fi
42 | if [[ $expected_edges != $actual_edges ]]; then
43 | echo "The number of edges is wrong, expected: $expected_edges actual: $actual_edges"
44 | exit 1
45 | fi
46 | }
47 |
48 | measure_serial_import() {
49 | dataset_cypherl=$1
50 | nodes=$1
51 | edges=$2
52 | echo "MATCH (n) DETACH DELETE n;" | $MGCONSOLE_BINARY
53 | import_time=$( { time cat $dataset_cypherl | $MGCONSOLE_BINARY --import-mode="serial"; } 2>&1 )
54 | echo "$import_time"
55 | }
56 |
57 | measure_batched_parallel_import() {
58 | dataset_cypherl=$1
59 | nodes=$1
60 | edges=$2
61 | echo "MATCH (n) DETACH DELETE n;" | $MGCONSOLE_BINARY
62 | import_time=$( { time cat $dataset_cypherl | $MGCONSOLE_BINARY --import-mode="batched-parallel" --batch-size=$MGCONSOLE_BATCH_SIZE --workers-number=$MGCONSOLE_WORKERS; } 2>&1 )
63 | echo "$import_time"
64 | }
65 |
66 | echo "$MGCONSOLE_SETUP" | $MGCONSOLE_BINARY
67 | for dataset in "${DATASETS[@]}"; do
68 | set -- $dataset; dataset_url=$1; nodes=$2; edges=$3
69 | dataset_gz="$(basename $dataset_url)"
70 | dataset_cypherl="$(basename $dataset_gz .gz)"
71 | if [[ ! -f $dataset_cypherl ]]; then
72 | wget $dataset_url -O $dataset_gz
73 | gzip -df $dataset_gz
74 | fi
75 |
76 | echo "$dataset_cypherl serial import..."
77 | serial_import_time=$(measure_serial_import $dataset_cypherl $nodes $edges)
78 | check_dataset $nodes $edges
79 | serial_tx=$(echo "($nodes + $edges)/$serial_import_time" | bc -l)
80 |
81 | echo "$dataset_cypherl parallel import..."
82 | parallel_import_time=$(measure_batched_parallel_import $dataset_cypherl $nodes $edges)
83 | check_dataset $nodes $edges
84 | parallel_tx=$(echo "($nodes + $edges)/$parallel_import_time" | bc -l)
85 |
86 | echo "dataset | nodes | edges | serial (nodes+edges)/s | parallel (nodes+edges)/s | batch size | workers number"
87 | echo "$dataset_cypherl | $nodes | $edges | $serial_tx | $parallel_tx | $MGCONSOLE_BATCH_SIZE | $MGCONSOLE_WORKERS"
88 | done
89 |
--------------------------------------------------------------------------------
/tests/input_output/output_tabular/temporal.txt:
--------------------------------------------------------------------------------
1 | +--------------------+
2 | | date("1999-05-05") |
3 | +--------------------+
4 | | 1999-05-05 |
5 | +--------------------+
6 | +---------------------------------------+
7 | | date({year: 2012, month: 12, day: 5}) |
8 | +---------------------------------------+
9 | | 2012-12-05 |
10 | +---------------------------------------+
11 | +-----------------------------------------------+
12 | | localtime({hour: 23, minute: 56, second: 23}) |
13 | +-----------------------------------------------+
14 | | 23:56:23.000000000 |
15 | +-----------------------------------------------+
16 | +-----------------------+
17 | | localtime("12:01:12") |
18 | +-----------------------+
19 | | 12:01:12.000000000 |
20 | +-----------------------+
21 | +--------------------------------------+
22 | | localdatetime("2000-09-12T06:21:45") |
23 | +--------------------------------------+
24 | | 2000-09-12 06:21:45.000000000 |
25 | +--------------------------------------+
26 | +------------------------------------------------------------+
27 | | localdatetime({year: 2000, day: 23, hour: 12, second: 21}) |
28 | +------------------------------------------------------------+
29 | | 2000-01-23 12:00:21.000000000 |
30 | +------------------------------------------------------------+
31 | +--------------------------------------------+
32 | | duration({day: 23, hour: 100, second: 21}) |
33 | +--------------------------------------------+
34 | | P27DT4H21S |
35 | +--------------------------------------------+
36 | +------------------------------------------+
37 | | duration({second: 0, microsecond: -123}) |
38 | +------------------------------------------+
39 | | P-0.000123S |
40 | +------------------------------------------+
41 | +-------------------------------+
42 | | duration("P1DT48H61M79.123S") |
43 | +-------------------------------+
44 | | P3DT1H2M19.123000S |
45 | +-------------------------------+
46 | +------------------------------------------------------------+
47 | | datetime("2024-04-21T14:15:00-07:00[America/Los_Angeles]") |
48 | +------------------------------------------------------------+
49 | | 2024-04-21 14:15:00.000000000[America/Los_Angeles] |
50 | +------------------------------------------------------------+
51 | +---------------------------------------+
52 | | datetime("2024-04-21T14:15:00+07:30") |
53 | +---------------------------------------+
54 | | 2024-04-21 14:15:00.000000000+07:30 |
55 | +---------------------------------------+
56 | +---------------------------------------+
57 | | datetime("2024-04-21T14:15:00-05:45") |
58 | +---------------------------------------+
59 | | 2024-04-21 14:15:00.000000000-05:45 |
60 | +---------------------------------------+
61 | +----------------------------------------+
62 | | datetime("2021-04-21T14:15:00Z") |
63 | +----------------------------------------+
64 | | 2021-04-21 14:15:00.000000000[Etc/UTC] |
65 | +----------------------------------------+
66 | +--------------------------------------------------------------------------------------------------+
67 | | datetime({year: 2024, month: 4, day: 21, hour: 14, minute: 15, timezone: "America/Los_Angeles"}) |
68 | +--------------------------------------------------------------------------------------------------+
69 | | 2024-04-21 14:15:00.000000000[America/Los_Angeles] |
70 | +--------------------------------------------------------------------------------------------------+
71 | +--------------------------------------------------------------------------------+
72 | | datetime({year: 2021, month: 4, day: 21, hour: 14, minute: 15, timezone: -60}) |
73 | +--------------------------------------------------------------------------------+
74 | | 2021-04-21 14:15:00.000000000-01:00 |
75 | +--------------------------------------------------------------------------------+
76 | +-------------------------------------------------------------------------------+
77 | | datetime({year: 2021, month: 4, day: 21, hour: 14, minute: 15, timezone: 90}) |
78 | +-------------------------------------------------------------------------------+
79 | | 2021-04-21 14:15:00.000000000+01:30 |
80 | +-------------------------------------------------------------------------------+
81 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | workflow_dispatch:
6 |
7 | env:
8 | OFFICIAL: true
9 |
10 | jobs:
11 | build_and_test_ubuntu:
12 | strategy:
13 | matrix:
14 | platform: [ubuntu-22.04]
15 | mg_version:
16 | - "2.19.0"
17 | runs-on: ${{ matrix.platform }}
18 | steps:
19 | - name: Set up and check memgraph download link
20 | run: |
21 | mg_version=${{ matrix.mg_version }}
22 | mg_version_short=${mg_version%%-*}
23 | if [ "${{ env.OFFICIAL }}" = "true" ]; then
24 | mg_url="https://download.memgraph.com/memgraph/v${mg_version}/${{ matrix.platform }}/memgraph_${mg_version_short}-1_amd64.deb"
25 | else
26 | mg_url="https://s3.eu-west-1.amazonaws.com/deps.memgraph.io/memgraph/v${mg_version}/${{ matrix.platform }}/memgraph_${mg_version_short}-1_amd64.deb"
27 | fi
28 | echo "Checking Memgraph download link: $mg_url"
29 | if curl --output /dev/null --silent --head --fail $mg_url; then
30 | echo "Memgraph download link is valid"
31 | echo "MEMGRAPH_DOWNLOAD_LINK=${mg_url}" >> $GITHUB_ENV
32 | else
33 | echo "Memgraph download link is not valid"
34 | exit 1
35 | fi
36 | - name: Install dependencies (Ubuntu 22.04)
37 | if: matrix.platform == 'ubuntu-22.04'
38 | run: |
39 | sudo apt install -y git cmake make gcc g++ libssl-dev # mgconsole deps
40 | sudo apt install -y libpython3.10 python3-pip # memgraph deps
41 | mkdir ~/memgraph
42 | curl -L ${{ env.MEMGRAPH_DOWNLOAD_LINK }} > ~/memgraph/memgraph_${{ matrix.mg_version }}-1_amd64.deb
43 | sudo systemctl mask memgraph
44 | sudo dpkg -i ~/memgraph/memgraph_${{ matrix.mg_version }}-1_amd64.deb
45 | - uses: actions/checkout@v4
46 | - name: Install and test mgconsole
47 | run: |
48 | mkdir build
49 | cd build
50 | cmake ..
51 | make
52 | sudo make install
53 | ctest --verbose
54 | - name: Save mgconsole test results
55 | if: always()
56 | uses: actions/upload-artifact@v4
57 | with:
58 | name: "mgconsole_ctest.log"
59 | path: build/Testing/Temporary/LastTest.log
60 |
61 | build_windows_mingw:
62 | runs-on: windows-2022
63 | strategy:
64 | matrix:
65 | include: [
66 | { msystem: MINGW64, arch: x86_64 }
67 | ]
68 | defaults:
69 | run:
70 | shell: msys2 {0}
71 | steps:
72 | - name: Set-up repository
73 | uses: actions/checkout@v4
74 | - uses: msys2/setup-msys2@v2
75 | with:
76 | msystem: ${{ matrix.msystem }}
77 | update: true
78 | install: >-
79 | git base-devel
80 | mingw-w64-${{ matrix.arch }}-toolchain
81 | mingw-w64-${{ matrix.arch }}-cmake
82 | mingw-w64-${{ matrix.arch }}-openssl
83 | - name: Build and install mgconsole
84 | run: |
85 | mkdir build
86 | cd build
87 | cmake -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release ..
88 | cmake --build . --parallel
89 | make install
90 | - name: Save mgconsole Windows build
91 | uses: actions/upload-artifact@v4
92 | with:
93 | name: "mgconsole Windows build"
94 | path: build/src/mgconsole.exe
95 |
96 | build_apple:
97 | strategy:
98 | matrix:
99 | platform: [macos-14]
100 | runs-on: ${{ matrix.platform }}
101 | steps:
102 | - name: Set-up repository
103 | uses: actions/checkout@v4
104 | # NOTE: CI can't execute end2end tests because there is no way to run
105 | # Memgraph on CI MacOS machines.
106 | - name: Install openssl
107 | run: |
108 | brew update
109 | brew install openssl
110 | - name: Build mgconsole
111 | run: |
112 | mkdir build
113 | cd build
114 | cmake -DOPENSSL_ROOT_DIR="$(brew --prefix openssl)" -DCMAKE_BUILD_TYPE=Release ..
115 | make
116 | - name: Save mgconsole MacOS build
117 | uses: actions/upload-artifact@v4
118 | with:
119 | name: "mgconsole MacOS build"
120 | path: build/src/mgconsole
121 |
--------------------------------------------------------------------------------
/src/utils/synchronized.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
2 | //
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License
14 | // along with this program. If not, see .
15 |
16 | #pragma once
17 |
18 | #include
19 | #include
20 | #include
21 | #include
22 |
23 | namespace utils {
24 |
25 | template
26 | concept SharedMutex = requires(TMutex mutex) {
27 | mutex.lock();
28 | mutex.unlock();
29 | mutex.lock_shared();
30 | mutex.unlock_shared();
31 | };
32 |
33 | /// A simple utility for easier mutex-based concurrency (influenced by
34 | /// Facebook's Folly)
35 | ///
36 | /// Many times we have an object that is accessed from multiple threads so it
37 | /// has an associated lock:
38 | ///
39 | /// utils::SpinLock my_important_map_lock_;
40 | /// std::map my_important_map_;
41 | ///
42 | /// Whenever we want to access the object, we have to remember that we have to
43 | /// acquire the corresponding lock:
44 | ///
45 | /// std::lock_guard
46 | /// my_important_map_guard(my_important_map_lock_);
47 | /// my_important_map_[key] = value;
48 | ///
49 | /// Correctness of this approach depends on the programmer never forgetting to
50 | /// acquire the lock.
51 | ///
52 | /// Synchronized encodes that information in the type information, and it is
53 | /// much harder to use the object incorrectly.
54 | ///
55 | /// Synchronized, utils::SpinLock>
56 | /// my_important_map_;
57 | ///
58 | /// Now we have multiple ways of accessing the map:
59 | ///
60 | /// 1. Acquiring a locked pointer:
61 | /// auto my_map_ptr = my_important_map_.Lock();
62 | /// my_map_ptr->emplace(key, value);
63 | ///
64 | /// 2. Using the indirection operator:
65 | ///
66 | /// my_important_map_->emplace(key, value);
67 | ///
68 | /// 3. Using a lambda:
69 | /// my_important_map_.WithLock([](auto &my_important_map) {
70 | /// my_important_map[key] = value;
71 | /// });
72 | ///
73 | /// Approach 2 is probably the best to use for one-line operations, and
74 | /// approach 3 for multi-line ops.
75 | template
76 | class Synchronized {
77 | public:
78 | template
79 | explicit Synchronized(Args &&...args) : object_(std::forward(args)...) {}
80 |
81 | Synchronized(const Synchronized &) = delete;
82 | Synchronized(Synchronized &&) = delete;
83 | Synchronized &operator=(const Synchronized &) = delete;
84 | Synchronized &operator=(Synchronized &&) = delete;
85 | ~Synchronized() = default;
86 |
87 | class LockedPtr {
88 | private:
89 | friend class Synchronized;
90 |
91 | LockedPtr(T *object_ptr, TMutex *mutex) : object_ptr_(object_ptr), guard_(*mutex) {}
92 |
93 | public:
94 | T *operator->() { return object_ptr_; }
95 | T &operator*() { return *object_ptr_; }
96 |
97 | private:
98 | T *object_ptr_;
99 | std::lock_guard guard_;
100 | };
101 |
102 | class ReadLockedPtr {
103 | private:
104 | friend class Synchronized;
105 |
106 | ReadLockedPtr(const T *object_ptr, TMutex *mutex) : object_ptr_(object_ptr), guard_(*mutex) {}
107 |
108 | public:
109 | const T *operator->() const { return object_ptr_; }
110 | const T &operator*() const { return *object_ptr_; }
111 |
112 | private:
113 | const T *object_ptr_;
114 | std::shared_lock guard_;
115 | };
116 |
117 | LockedPtr Lock() { return LockedPtr(&object_, &mutex_); }
118 |
119 | template
120 | decltype(auto) WithLock(TCallable &&callable) {
121 | return callable(*Lock());
122 | }
123 |
124 | LockedPtr operator->() { return LockedPtr(&object_, &mutex_); }
125 |
126 | template
127 | requires SharedMutex
128 | ReadLockedPtr ReadLock() const {
129 | return ReadLockedPtr(&object_, &mutex_);
130 | }
131 |
132 | template
133 | requires SharedMutex decltype(auto)
134 | WithReadLock(TCallable &&callable) const {
135 | return callable(*ReadLock());
136 | }
137 |
138 | template
139 | requires SharedMutex
140 | ReadLockedPtr operator->() const {
141 | return ReadLockedPtr(&object_, &mutex_);
142 | }
143 |
144 | private:
145 | T object_;
146 | mutable TMutex mutex_;
147 | };
148 |
149 | } // namespace utils
150 |
--------------------------------------------------------------------------------
/tests/input_output/run-tests.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # mgconsole - console client for Memgraph database
4 | # Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
5 | #
6 | # This program is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU General Public License as published by
8 | # the Free Software Foundation, either version 3 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program. If not, see .
18 |
19 | ## Helper functions
20 |
21 | function wait_for_server {
22 | port=$1
23 | while ! nc -z -w 1 127.0.0.1 $port; do
24 | sleep 0.1
25 | done
26 | sleep 1
27 | }
28 |
29 | function echo_info { printf "\033[1;36m~~ $1 ~~\033[0m\n"; }
30 | function echo_success { printf "\033[1;32m~~ $1 ~~\033[0m\n\n"; }
31 | function echo_failure { printf "\033[1;31m~~ $1 ~~\033[0m\n\n"; }
32 |
33 | use_ssl=false
34 | if [ "$1" == "--use-ssl" ]; then
35 | use_ssl=true
36 | shift
37 | fi
38 |
39 | if [ ! $# -eq 2 ]; then
40 | echo "Usage: $0 [path to memgraph binary] [path to client binary]"
41 | exit 1
42 | fi
43 |
44 | # Find memgraph binaries.
45 | if [ ! -x $1 ]; then
46 | echo_failure "memgraph executable not found"
47 | exit 1
48 | fi
49 |
50 | # Find mgconsole binaries.
51 | if [ ! -x $2 ]; then
52 | echo_failure "mgconsole executable not found"
53 | exit 1
54 | fi
55 |
56 | memgraph_binary=$(realpath $1)
57 | client_binary=$(realpath $2)
58 |
59 | ## Environment setup
60 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
61 | cd "$DIR"
62 |
63 | # Create a temporary directory for output files
64 | tmpdir=/tmp/mgconsole/output
65 | if [ -d $tmpdir ]; then
66 | rm -rf $tmpdir
67 | fi
68 | mkdir -p $tmpdir
69 | cd $tmpdir
70 |
71 | # Check and generate SSL certificates
72 | key_file=""
73 | cert_file=""
74 | if $use_ssl; then
75 | key_file=".key.pem"
76 | cert_file=".cert.pem"
77 | openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 \
78 | -subj "/C=GB/ST=London/L=London/O=Memgraph/CN=db.memgraph.com" \
79 | -keyout $key_file -out $cert_file || exit 1
80 | fi
81 |
82 | ## Startup
83 |
84 | # Start the memgraph process and wait for it to start.
85 | echo_info "Starting memgraph"
86 | $memgraph_binary --bolt-port 7687 \
87 | --bolt-cert-file=$cert_file \
88 | --bolt-key-file=$key_file \
89 | --data-directory=$tmpdir \
90 | --storage-properties-on-edges=true \
91 | --storage-snapshot-interval-sec=0 \
92 | --storage-wal-enabled=false \
93 | --data-recovery-on-startup=false \
94 | --storage-snapshot-on-exit=false \
95 | --telemetry-enabled=false \
96 | --timezone=UTC \
97 | --log-file='' &
98 |
99 | pid=$!
100 | wait_for_server 7687
101 | echo_success "Started memgraph"
102 |
103 |
104 | ## Tests
105 |
106 | client_flags="--use-ssl=$use_ssl"
107 |
108 | echo_info "Prepare database"
109 | echo # Blank line
110 |
111 | $client_binary $client_flags < ${DIR}/prepare.cypher > $tmpdir/prepare.log
112 |
113 | echo_info "Running tests"
114 | echo # Blank line
115 |
116 | client_flags="--use-ssl=$use_ssl"
117 | test_code=0
118 | for output_dir in ${DIR}/output_*; do
119 | for filename in ${DIR}/input/*; do
120 | test_name=$(basename $filename)
121 | test_name=${test_name%.*}
122 | output_name="$test_name.txt"
123 |
124 | output_format=$(basename $output_dir)
125 | output_format=${output_format#*_}
126 | run_flags="$client_flags --output-format=$output_format"
127 |
128 | echo_info "Running test '$test_name' with $output_format output"
129 | $client_binary $run_flags < $filename > $tmpdir/$test_name
130 | diff -b $tmpdir/$test_name $output_dir/$output_name
131 | test_code=$?
132 | if [ $test_code -ne 0 ]; then
133 | echo_failure "Test '$test_name' with $output_format output failed"
134 | break
135 | else
136 | echo_success "Test '$test_name' with $output_format output passed"
137 | fi
138 |
139 | # Clear database for each test.
140 | $client_binary $client_flags <<< "MATCH (n) DETACH DELETE n;" \
141 | &> /dev/null || exit 1
142 | done
143 | if [ $test_code -ne 0 ]; then
144 | break
145 | fi
146 | done
147 |
148 |
149 | ## Cleanup
150 | echo_info "Starting test cleanup"
151 |
152 | # Shutdown the memgraph process.
153 | kill $pid
154 | wait -n
155 | code_mg=$?
156 |
157 | # Remove temporary directory
158 | rm -rf $tmpdir
159 |
160 | # Check memgraph exit code.
161 | if [ $code_mg -ne 0 ]; then
162 | echo_failure "The memgraph process didn't terminate properly!"
163 | exit $code_mg
164 | fi
165 | echo_success "Test cleanup done"
166 |
167 | exit $test_code
168 |
--------------------------------------------------------------------------------
/src/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | #
2 | # This program is free software: you can redistribute it and/or modify
3 | # it under the terms of the GNU General Public License as published by
4 | # the Free Software Foundation, either version 3 of the License, or
5 | # (at your option) any later version.
6 | #
7 | # This program is distributed in the hope that it will be useful,
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | # GNU General Public License for more details.
11 | #
12 | # You should have received a copy of the GNU General Public License
13 | # along with this program. If not, see .
14 |
15 | include(ExternalProject)
16 | include(GNUInstallDirs)
17 |
18 | # Create compile_commands.json
19 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
20 |
21 | if(WIN32)
22 | set(MGCONSOLE_ON_WINDOWS TRUE)
23 | elseif(APPLE)
24 | set(MGCONSOLE_ON_OSX TRUE)
25 | elseif(UNIX)
26 | set(MGCONSOLE_ON_LINUX TRUE)
27 | endif()
28 |
29 | find_package(Threads REQUIRED)
30 | set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
31 | set(THREADS_PREFER_PTHREAD_FLAG TRUE)
32 |
33 | if(NOT MGCONSOLE_ON_LINUX OR MGCONSOLE_STATIC_SSL)
34 | set(OPENSSL_USE_STATIC_LIBS TRUE)
35 | endif()
36 | find_package(OpenSSL REQUIRED)
37 | if(MGCONSOLE_ON_OSX)
38 | set(MACOSX_OPENSSL_ROOTDIR_FLAG "-DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR}")
39 | endif()
40 |
41 | # Handle platforms
42 | if(MGCONSOLE_ON_WINDOWS)
43 | set(CMAKE_CXX_STANDARD_LIBRARIES "-static-libgcc -static-libstdc++ -lws2_32 -lcrypt32")
44 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive")
45 | elseif(MGCONSOLE_ON_LINUX)
46 | # -no-pie -> https://stackoverflow.com/questions/46827433/g-compile-error-rodata-can-not-be-used-when-making-a-shared-object-recomp
47 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++ -no-pie")
48 | endif()
49 |
50 | if(MGCONSOLE_ON_LINUX)
51 | # On Debian, the libdir has multiarch path which we don't want while searching for
52 | # dependancy libs
53 | string(REGEX MATCH "lib[^/]*" MG_INSTALL_LIB_DIR ${CMAKE_INSTALL_LIBDIR})
54 | else()
55 | set(MG_INSTALL_LIB_DIR "lib")
56 | endif()
57 |
58 | # Setup GFlags. The GIT_TAG refers to this build:
59 | # https://github.com/gflags/gflags/tree/70c01a642f08734b7bddc9687884844ca117e080,
60 | # which is the earliest to support modern cmake.
61 | ExternalProject_Add(gflags-proj
62 | PREFIX gflags
63 | GIT_REPOSITORY https://github.com/gflags/gflags.git
64 | GIT_TAG 70c01a6
65 | CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX="
66 | "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}"
67 | "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
68 | INSTALL_DIR "${PROJECT_BINARY_DIR}/gflags")
69 |
70 | ExternalProject_Get_Property(gflags-proj install_dir)
71 | set(GFLAGS_ROOT ${install_dir})
72 | set(GFLAGS_INCLUDE_DIRS ${GFLAGS_ROOT}/include)
73 | set(GFLAGS_LIBRARY_PATH ${GFLAGS_ROOT}/lib/libgflags.a)
74 | set(GFLAGS_DEBUG_LIBRARY_PATH ${GFLAGS_ROOT}/lib/libgflags_debug.a)
75 | set(GFLAGS_LIBRARY gflags)
76 |
77 | add_library(${GFLAGS_LIBRARY} STATIC IMPORTED)
78 | target_compile_definitions(${GFLAGS_LIBRARY} INTERFACE GFLAGS_IS_A_DLL=0)
79 | set_target_properties(${GFLAGS_LIBRARY} PROPERTIES
80 | IMPORTED_LOCATION ${GFLAGS_LIBRARY_PATH}
81 | IMPORTED_LOCATION_DEBUG ${GFLAGS_DEBUG_LIBRARY_PATH}
82 | INTERFACE_LINK_LIBRARIES Threads::Threads)
83 | add_dependencies(${GFLAGS_LIBRARY} gflags-proj)
84 |
85 | ExternalProject_Add(mgclient-proj
86 | PREFIX mgclient
87 | GIT_REPOSITORY https://github.com/memgraph/mgclient.git
88 | GIT_TAG v1.5.0
89 | CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX="
90 | "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
91 | "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}"
92 | "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}"
93 | ${MACOSX_OPENSSL_ROOTDIR_FLAG}
94 | INSTALL_DIR "${PROJECT_BINARY_DIR}/mgclient")
95 |
96 | ExternalProject_Get_Property(mgclient-proj install_dir)
97 | set(MGCLIENT_ROOT ${install_dir})
98 | set(MGCLIENT_INCLUDE_DIRS ${MGCLIENT_ROOT}/include)
99 | set(MGCLIENT_LIBRARY_PATH ${MGCLIENT_ROOT}/${MG_INSTALL_LIB_DIR}/libmgclient.a)
100 | set(MGCLIENT_LIBRARY mgclient)
101 |
102 | add_library(${MGCLIENT_LIBRARY} STATIC IMPORTED)
103 | set_target_properties(${MGCLIENT_LIBRARY} PROPERTIES
104 | IMPORTED_LOCATION ${MGCLIENT_LIBRARY_PATH}
105 | INTERFACE_LINK_LIBRARIES Threads::Threads)
106 | add_dependencies(${MGCLIENT_LIBRARY} mgclient-proj)
107 |
108 | add_subdirectory(utils)
109 |
110 | add_compile_options(-Wall -Wextra -pedantic -Werror)
111 |
112 | # replxx performs narrowing conversion on Windows
113 | if(MGCONSOLE_ON_WINDOWS)
114 | add_compile_options(-Wno-narrowing)
115 | endif()
116 |
117 | add_executable(mgconsole main.cpp interactive.cpp serial_import.cpp batch_import.cpp parsing.cpp)
118 | target_compile_definitions(mgconsole PRIVATE MGCLIENT_STATIC_DEFINE)
119 | target_include_directories(mgconsole
120 | PRIVATE
121 | ${GFLAGS_INCLUDE_DIRS}
122 | ${MGCLIENT_INCLUDE_DIRS}
123 | ${REPLXX_INCLUDE_DIRS}
124 | ${CMAKE_CURRENT_BINARY_DIR})
125 | target_link_libraries(mgconsole
126 | PRIVATE
127 | ${GFLAGS_LIBRARY}
128 | utils
129 | ${MGCLIENT_LIBRARY}
130 | ${OPENSSL_LIBRARIES})
131 | if(MGCONSOLE_ON_WINDOWS)
132 | target_link_libraries(mgconsole PRIVATE shlwapi)
133 | endif()
134 |
135 | configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version.hpp.in"
136 | "${CMAKE_CURRENT_BINARY_DIR}/version.hpp")
137 | install(TARGETS mgconsole
138 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
139 |
--------------------------------------------------------------------------------
/src/interactive.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
2 | //
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License
14 | // along with this program. If not, see .
15 |
16 | #include "interactive.hpp"
17 |
18 | #include
19 |
20 | #include
21 |
22 | #include "utils/constants.hpp"
23 |
24 | namespace mode::interactive {
25 |
26 | using namespace std::string_literals;
27 |
28 | int Run(utils::bolt::Config &bolt_config, const std::string &history, bool no_history,
29 | bool verbose_execution_info, const format::CsvOptions &csv_opts, const format::OutputOptions &output_opts) {
30 | Replxx *replxx_instance = InitAndSetupReplxx();
31 |
32 | bool resources_cleaned_up = false;
33 | auto cleanup_resources = [replxx_instance, &resources_cleaned_up]() {
34 | if (!resources_cleaned_up) {
35 | replxx_end(replxx_instance);
36 | resources_cleaned_up = true;
37 | }
38 | };
39 |
40 | auto password = bolt_config.password;
41 | if (bolt_config.username.size() > 0 && password.size() == 0) {
42 | console::SetStdinEcho(false);
43 | auto password_optional = console::ReadLine(replxx_instance, "Password: ");
44 | std::cout << std::endl;
45 | if (password_optional) {
46 | bolt_config.password = *password_optional;
47 | } else {
48 | console::EchoFailure("Password not submitted", "Requested password for username " + bolt_config.username);
49 | cleanup_resources();
50 | return 1;
51 | }
52 | console::SetStdinEcho(true);
53 | }
54 |
55 | fs::path history_dir = history;
56 | if (history == (constants::kDefaultHistoryBaseDir + "/" + constants::kDefaultHistoryMemgraphDir)) {
57 | // Fetch home dir for user.
58 | history_dir = utils::GetUserHomeDir() / constants::kDefaultHistoryMemgraphDir;
59 | }
60 | if (!utils::EnsureDir(history_dir)) {
61 | console::EchoFailure("History directory doesn't exist", history_dir.string());
62 | // Should program exit here or just continue with warning message?
63 | cleanup_resources();
64 | return 1;
65 | }
66 | fs::path history_file = history_dir / constants::kHistoryFilename;
67 | // Read history file.
68 | if (fs::exists(history_file)) {
69 | auto ret = replxx_history_load(replxx_instance, history_file.string().c_str());
70 | if (ret != 0) {
71 | console::EchoFailure("Unable to read history file", history_file.string());
72 | // Should program exit here or just continue with warning message?
73 | cleanup_resources();
74 | return 1;
75 | }
76 | }
77 |
78 | // Save history function. Used to save replxx history after each query.
79 | auto save_history = [&history_file, replxx_instance, &cleanup_resources, no_history] {
80 | if (!no_history) {
81 | // If there was no history, create history file.
82 | // Otherwise, append to existing history.
83 | auto ret = replxx_history_save(replxx_instance, history_file.string().c_str());
84 | if (ret != 0) {
85 | console::EchoFailure("Unable to save history to file", history_file.string());
86 | cleanup_resources();
87 | return 1;
88 | }
89 | }
90 | return 0;
91 | };
92 |
93 | int num_retries = 3;
94 | auto session = MakeBoltSession(bolt_config);
95 | if (session.get() == nullptr) {
96 | cleanup_resources();
97 | return 1;
98 | }
99 |
100 | console::EchoInfo("mgconsole "s + gflags::VersionString());
101 | console::EchoInfo("Connected to 'memgraph://" + bolt_config.host + ":" + std::to_string(bolt_config.port) + "'");
102 | console::EchoInfo("Type :help for shell usage");
103 | console::EchoInfo("Quit the shell by typing Ctrl-D(eof) or :quit");
104 |
105 | while (true) {
106 | auto query = query::GetQuery(replxx_instance);
107 | if (!query) {
108 | console::EchoInfo("Bye");
109 | break;
110 | }
111 | if (query->query.empty()) {
112 | continue;
113 | }
114 |
115 | try {
116 | auto ret = query::ExecuteQuery(session.get(), query->query);
117 | if (ret.records.size() > 0) {
118 | Output(ret.header, ret.records, output_opts, csv_opts);
119 | }
120 | std::string summary;
121 | if (ret.records.size() == 0) {
122 | summary = "Empty set";
123 | } else if (ret.records.size() == 1) {
124 | summary = std::to_string(ret.records.size()) + " row in set";
125 | } else {
126 | summary = std::to_string(ret.records.size()) + " rows in set";
127 | }
128 | std::printf("%s (round trip in %.3lf sec)\n", summary.c_str(), ret.wall_time.count());
129 | auto history_ret = save_history();
130 | if (history_ret != 0) {
131 | cleanup_resources();
132 | return history_ret;
133 | }
134 | if (ret.notification) {
135 | console::EchoNotification(ret.notification.value());
136 | }
137 | if (ret.stats) {
138 | console::EchoStats(ret.stats.value());
139 | }
140 | if (verbose_execution_info && ret.execution_info) {
141 | console::EchoExecutionInfo(ret.execution_info.value());
142 | }
143 | } catch (const utils::ClientQueryException &e) {
144 | console::EchoFailure("Client received query exception", e.what());
145 | } catch (const utils::ClientFatalException &e) {
146 | console::EchoFailure("Client received connection exception", e.what());
147 | console::EchoInfo("Trying to reconnect...");
148 | bool is_connected = false;
149 | session.reset(nullptr);
150 | while (num_retries > 0) {
151 | --num_retries;
152 | session = utils::bolt::MakeBoltSession(bolt_config);
153 | if (session.get() == nullptr) {
154 | console::EchoFailure("Connection failure", mg_session_error(session.get()));
155 | session.reset(nullptr);
156 | } else {
157 | is_connected = true;
158 | break;
159 | }
160 | std::this_thread::sleep_for(std::chrono::seconds(1));
161 | }
162 | if (is_connected) {
163 | num_retries = 3;
164 | console::EchoInfo("Connected to 'memgraph://" + bolt_config.host + ":" + std::to_string(bolt_config.port) +
165 | "'");
166 | } else {
167 | console::EchoFailure("Couldn't connect to",
168 | "'memgraph://" + bolt_config.host + ":" + std::to_string(bolt_config.port) + "'");
169 | cleanup_resources();
170 | return 1;
171 | }
172 | }
173 | }
174 |
175 | cleanup_resources();
176 | return 0;
177 | }
178 |
179 | } // namespace mode::interactive
180 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/memgraph/mgconsole/actions)
2 |
3 | # mgconsole
4 |
5 | mgconsole is a command line interface for [Memgraph](https://memgraph.com)
6 | database.
7 |
8 |
9 |
10 |
11 | ## Running mgconsole
12 |
13 | For MacOs and Windows users, there is a prebuilt version of mgconsole available on [Memgraph download hub](https://memgraph.com/download), Linux users need to build native version.
14 |
15 | There is also a docker version of the same container available on the [Docker Hub](https://hub.docker.com/repository/docker/memgraph/mgconsole/general).
16 |
17 | You can start `mgconsole` locally by running the following command:
18 |
19 | ```
20 | docker run -it memgraph/mgconsole:latest
21 | ```
22 |
23 | ## Building and installing
24 |
25 | To build and install mgconsole from source you will need:
26 | - CMake version >= 3.4
27 | - OpenSSL version >= 1.0.2
28 | - C compiler supporting C11
29 | - C++ compiler supporting C++20
30 |
31 | To install compile dependencies on Debian / Ubuntu:
32 |
33 | ```
34 | apt-get install -y git cmake make gcc g++ libssl-dev
35 | ```
36 |
37 | On RedHat / CentOS / Fedora:
38 |
39 | ```
40 | yum install -y git cmake make gcc gcc-c++ openssl-devel libstdc++-static
41 | ```
42 |
43 | On MacOS, first make sure you have [XCode](https://developer.apple.com/xcode/) and [Homebrew](https://brew.sh) installed. Then, in the terminal, paste:
44 |
45 | ```
46 | brew install git cmake make openssl
47 | ```
48 |
49 | On Windows, you need to install the MSYS2. Just follow the [instructions](https://www.msys2.org), up to step 6.
50 | In addition, OpenSSL must be installed. You can easily install it with an
51 | [installer](https://slproweb.com/products/Win32OpenSSL.html). The Win64
52 | version is required, although the "Light" version is enough. Both EXE and MSI
53 | variants should work.
54 | Then, you'll need to install the dependencies using the MSYS2 MINGW64 terminal,
55 | which should be available from your Start menu. Just run the following command
56 | inside the MSYS2 MINGW64 terminal:
57 |
58 | ```
59 | pacman -Syu --needed base-devel git mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-openssl
60 | ```
61 |
62 | Once everything is in place, create a build directory inside the source
63 | directory and configure the build by running CMake from it as follows:
64 |
65 | * on Linux:
66 | ```
67 | mkdir build
68 | cd build
69 | cmake -DCMAKE_BUILD_TYPE=Release ..
70 | ```
71 |
72 | * on MacOS:
73 | ```
74 | mkdir build
75 | cd build
76 | cmake -DOPENSSL_ROOT_DIR="$(brew --prefix openssl)" -DCMAKE_BUILD_TYPE=Release ..
77 | ```
78 |
79 | * on Windows, from the MSYS2 MINGW64 terminal:
80 | ```
81 | mkdir build
82 | cd build
83 | cmake -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release ..
84 | ```
85 |
86 | After running CMake, you should see a Makefile in the build directory. Then you
87 | can build the project by running:
88 | ```
89 | make
90 | ```
91 |
92 | This will build the `mgconsole` binary. To install it, run:
93 | ```
94 | make install
95 | ```
96 |
97 | This will install to system default installation directory. If you want to
98 | change this location, use `-DCMAKE_INSTALL_PREFIX` option when running CMake.
99 |
100 | NOTE: If you have issues compiling `mgconsole` using your compiler, please try to use
101 | [Memgraph official toolchain](https://memgraph.notion.site/Toolchain-37c37c84382149a58d09b2ccfcb410d7).
102 | In case you encounter any problem, please create
103 | [a new GitHub issue](https://github.com/memgraph/mgconsole/issues/new).
104 |
105 | ## Example usage
106 |
107 | ```
108 | $ mgconsole --host 127.0.0.1 --port 7687 --use-ssl=false
109 | mgconsole 0.1
110 | Type :help for shell usage
111 | Quit the shell by typing Ctrl-D(eof) or :quit
112 | Connected to 'memgraph://127.0.0.1:7687'
113 | memgraph> :help
114 | In interactive mode, user can enter cypher queries and supported commands.
115 |
116 | Cypher queries can span through multiple lines and conclude with a
117 | semi-colon (;). Each query is executed in the database and the results
118 | are printed out.
119 |
120 | The following interactive commands are supported:
121 |
122 | :help Print out usage for interactive mode
123 | :quit Exit the shell
124 |
125 | memgraph>
126 | memgraph> MATCH (t:Turtle) RETURN t;
127 | +-------------------------------------------+
128 | | t |
129 | +-------------------------------------------+
130 | | (:Turtle {color: "blue", name: "Leo"}) |
131 | | (:Turtle {color: "purple", name: "Don"}) |
132 | | (:Turtle {color: "orange", name: "Mike"}) |
133 | | (:Turtle {color: "red", name: "Raph"}) |
134 | +-------------------------------------------+
135 | 4 rows in set (0.000 sec)
136 | memgraph> :quit
137 | Bye
138 | ```
139 |
140 | ## Export & import into Memgraph
141 |
142 | An interesting use-case for `mgconsole` is exporting and importing data.
143 | You can close the loop by running the following example queries:
144 |
145 | ```
146 | # Export to cypherl formatted data file
147 | echo "DUMP DATABASE;" | mgconsole --output-format=cypherl > data.cypherl
148 |
149 | # Import from cypherl file
150 | cat data.cypherl | mgconsole
151 | ```
152 |
153 | ## Batched and parallelized import (EXPERIMENTAL)
154 |
155 | Since Memgraph v2 expects vertices to come first (vertices has to exist to
156 | create an edge), and serial import can be slow, the goal with batching and
157 | parallelization is to improve the import speed when ingesting queries in the
158 | text format.
159 |
160 | To enable faster import, use `--import-mode="batched-parallel"` flag when
161 | running `mgconsole` + put Memgraph into the `STORAGE MODE
162 | IN_MEMORY_ANALYTICAL;` (could be part of the `.cypherl` file) to be able to
163 | leverage parallelism in the best possible way.
164 |
165 | ```
166 | cat data.cypherl | mgconsole --import-mode=batched-parallel
167 | # STORAGE MODE IN_MEMORY_ANALYTICAL; is optional
168 | ```
169 |
170 | IMPORTANT NOTE: Inside the import file, vertices always have to come first
171 | because `mgconsole` will read the file serially and chunk by chunk.
172 |
173 | Additional useful runtime flags are:
174 | - `--batch-size=10000`
175 | - `--workers-number=64`
176 |
177 | ### Memgraph in the TRANSACTIONAL mode
178 |
179 | In [TRANSACTIONAL
180 | mode](https://memgraph.com/docs/memgraph/reference-guide/storage-modes#transactional-storage-mode-default),
181 | batching and parallelization might help, but since there are high chances for
182 | serialization errors, the execution times might be similar or even slower
183 | compared to the serial mode.
184 |
185 | ### Memgraph in ANALYTICAL mode
186 |
187 | In [ANALYTICAL
188 | mode](https://memgraph.com/docs/memgraph/reference-guide/storage-modes#analytical-storage-mode),
189 | batching and parallelization will mostly likely help massively because
190 | serialization errors don't exist, but since Memgraph will accept any query
191 | (e.g., on edge create failure, vertices could be created multiple times),
192 | special care is required:
193 | - queries with pure create vertices have to be specified first
194 | - please use only import statements using simple MATCH, CREATE, MERGE
195 | statements.
196 |
197 | If you encounter any issue, please create a new [mgconsole GitHub issue](https://github.com/memgraph/mgconsole/issues).
198 |
--------------------------------------------------------------------------------
/src/utils/constants.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | namespace constants {
8 |
9 | // Usage strings
10 | constexpr const auto *kUsage =
11 | "Memgraph bolt client.\n"
12 | "The client can be run in interactive or non-interactive mode.\n";
13 |
14 | constexpr const std::string_view kInteractiveUsage =
15 | "In interactive mode, user can enter Cypher queries and supported "
16 | "commands.\n\n"
17 | "Cypher queries can span through multiple lines and conclude with a\n"
18 | "semi-colon (;). Each query is executed in the database and the results\n"
19 | "are printed out.\n\n"
20 | "The following interactive commands are supported:\n\n"
21 | "\t:help\t Print out usage for interactive mode\n"
22 | "\t:quit\t Exit the shell\n";
23 |
24 | constexpr const std::string_view kDocs =
25 | "If you are new to Memgraph or the Cypher query language, check out these "
26 | "resources:\n\n"
27 | "\tQuerying with Cypher: https://memgr.ph/querying\n"
28 | "\tImporting data: https://memgr.ph/importing-data\n"
29 | "\tDatabase configuration: https://memgr.ph/configuration\n\n"
30 | "Official mgconsole documentation available on: "
31 | "https://memgr.ph/mgconsole\n";
32 |
33 | constexpr const std::string_view kCommandQuit = ":quit";
34 | constexpr const std::string_view kCommandHelp = ":help";
35 | constexpr const std::string_view kCommandDocs = ":docs";
36 |
37 | // Supported formats.
38 | constexpr const std::string_view kCsvFormat = "csv";
39 | constexpr const std::string_view kTabularFormat = "tabular";
40 | constexpr const std::string_view kCypherlFormat = "cypherl";
41 |
42 | // Supported modes.
43 | constexpr const std::string_view kSerialMode = "serial";
44 | constexpr const std::string_view kBatchedParallel = "batched-parallel";
45 | constexpr const std::string_view kParserMode = "parser";
46 |
47 | // History default directory.
48 | static const std::string kDefaultHistoryBaseDir = "~";
49 | static const std::string kDefaultHistoryMemgraphDir = ".memgraph";
50 | // History filename.
51 | static const std::string kHistoryFilename = "client_history";
52 |
53 | static const std::string kPrompt = "memgraph> ";
54 | static const std::string kMultilinePrompt = " -> ";
55 |
56 | /// Memgraph and OpenCypher keywords.
57 | static const std::vector kMemgraphKeywords{
58 | "ALTER", "ASYNC", "AUTH", "BATCH", "BATCHES", "CLEAR", "CSV", "DATA", "DELIMITER", "DENY",
59 | "DROP", "FOR", "FREE", "FROM", "GRANT", "HEADER", "INFO", "IDENTIFIED", "INTERVAL", "K_TEST",
60 | "KAFKA", "LOAD", "LOCK", "MAIN", "MODE", "PASSWORD", "REPLICA", "REPLICAS", "REPLICATION", "PORT",
61 | "PRIVILEGES", "QUOTE", "REVOKE", "ROLE", "ROLES", "SIZE", "START", "STATS", "STOP", "STREAM",
62 | "STREAMS", "SYNC", "TIMEOUT", "TO", "TOPIC", "TRANSFORM", "UNLOCK", "USER", "USERS"};
63 |
64 | static const std::vector kCypherKeywords{
65 | "ALL", "AND", "ANY", "AS", "ASC", "ASCENDING", "BFS", "BY", "CASE",
66 | "CONTAINS", "COUNT", "CREATE", "CYPHERNULL", "DELETE", "DESC", "DESCENDING", "DETACH", "DISTINCT",
67 | "ELSE", "END", "ENDS", "EXTRACT", "FALSE", "FILTER", "IN", "INDEX", "IS",
68 | "LIMIT", "L_SKIP", "MATCH", "MERGE", "NONE", "NOT", "ON", "OPTIONAL", "OR",
69 | "ORDER", "REDUCE", "REMOVE", "RETURN", "SET", "SHOW", "SINGLE", "STARTS", "THEN",
70 | "TRUE", "UNION", "UNWIND", "WHEN", "WHERE", "WITH", "WSHORTEST", "XOR"};
71 |
72 | static const std::vector kAwesomeFunctions{"DEGREE",
73 | "INDEGREE",
74 | "OUTDEGREE",
75 | "ENDNODE",
76 | "HEAD",
77 | "ID",
78 | "LAST",
79 | "PROPERTIES",
80 | "SIZE",
81 | "STARTNODE",
82 | "TIMESTAMP",
83 | "TOBOOLEAN",
84 | "TOFLOAT",
85 | "TOINTEGER",
86 | "TYPE",
87 | "VALUETYPE",
88 | "KEYS",
89 | "LABELS",
90 | "NODES",
91 | "RANGE",
92 | "RELATIONSHIPS",
93 | "TAIL",
94 | "UNIFORMSAMPLE",
95 | "ABS",
96 | "CEIL",
97 | "FLOOR",
98 | "RAND",
99 | "ROUND",
100 | "SIGN",
101 | "E",
102 | "EXP",
103 | "LOG",
104 | "LOG10",
105 | "SQRT",
106 | "ACOS",
107 | "ASIN",
108 | "ATAN",
109 | "ATAN2",
110 | "COS",
111 | "PI",
112 | "SIN",
113 | "TAN",
114 | "CONTAINS",
115 | "ENDSWITH",
116 | "LEFT",
117 | "LTRIM",
118 | "REPLACE",
119 | "REVERSE",
120 | "RIGHT",
121 | "RTRIM",
122 | "SPLIT",
123 | "STARTSWITH",
124 | "SUBSTRING",
125 | "TOLOWER",
126 | "TOSTRING",
127 | "TOUPPER",
128 | "TRIM",
129 | "ASSERT",
130 | "COUNTER",
131 | "TOBYTESTRING",
132 | "FROMBYTESTRING",
133 | "DATE",
134 | "LOCALTIME",
135 | "LOCALDATETIME",
136 | "DURATION"};
137 |
138 | } // namespace constants
139 |
--------------------------------------------------------------------------------
/src/main.cpp:
--------------------------------------------------------------------------------
1 | // mgconsole - console client for Memgraph database
2 | //
3 | // Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
4 | //
5 | // This program is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU General Public License as published by
7 | // the Free Software Foundation, either version 3 of the License, or
8 | // (at your option) any later version.
9 | //
10 | // This program is distributed in the hope that it will be useful,
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | // GNU General Public License for more details.
14 | //
15 | // You should have received a copy of the GNU General Public License
16 | // along with this program. If not, see .
17 |
18 | #include
19 |
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | #ifdef _WIN32
28 |
29 | #include
30 |
31 | #else /* _WIN32 */
32 |
33 | #include
34 | #include
35 | #include
36 |
37 | #endif /* _WIN32 */
38 |
39 | #include
40 | #include
41 | #include
42 |
43 | #include "batch_import.hpp"
44 | #include "interactive.hpp"
45 | #include "parsing.hpp"
46 | #include "serial_import.hpp"
47 | #include "utils/assert.hpp"
48 | #include "utils/constants.hpp"
49 | #include "utils/utils.hpp"
50 | #include "version.hpp"
51 |
52 | using namespace std::string_literals;
53 |
54 | volatile sig_atomic_t is_shutting_down = 0;
55 |
56 | // connection
57 | DEFINE_string(host, "127.0.0.1", "Server address. It can be a DNS resolvable hostname.");
58 | DEFINE_int32(port, 7687, "Server port.");
59 | DEFINE_string(username, "", "Database username.");
60 | DEFINE_string(password, "", "Database password.");
61 | DEFINE_bool(use_ssl, false, "Use SSL when connecting to the server.");
62 |
63 | // output
64 | DEFINE_bool(fit_to_screen, false, "Fit output width to screen width.");
65 | DEFINE_bool(term_colors, false, "Use terminal colors syntax highlighting.");
66 | DEFINE_string(output_format, "tabular",
67 | "Query output format can be csv, tabular or cypherl. If output format is "
68 | "not tabular `fit-to-screen` flag is ignored.");
69 | DEFINE_bool(verbose_execution_info, false,
70 | "Output the additional information about query such as query cost, parsing, planning and execution times.");
71 | DEFINE_validator(output_format, [](const char *, const std::string &value) {
72 | if (value == constants::kCsvFormat || value == constants::kTabularFormat || value == constants::kCypherlFormat) {
73 | return true;
74 | }
75 | return false;
76 | });
77 |
78 | // CSV
79 | DEFINE_string(csv_delimiter, ",", "Character used to separate fields.");
80 | DEFINE_validator(csv_delimiter, [](const char *, const std::string &value) {
81 | if (value.size() != 1) {
82 | return false;
83 | }
84 | return true;
85 | });
86 | DEFINE_string(csv_escapechar, "", "Character used to escape the quotechar(\") if the `csv-doublequote` flag is false.");
87 | DEFINE_bool(csv_doublequote, true,
88 | "Controls how instances of quotechar(\") appearing inside a field should "
89 | "themselves be quoted. When true, the character is doubled. When false, "
90 | "the escapechar is used as a prefix to the quotechar. "
91 | "If `csv-doublequote` is false, 'csv-escapechar' must be set.");
92 |
93 | // history
94 | DEFINE_string(history, "~/.memgraph", "Use the specified directory to save history.");
95 | DEFINE_bool(no_history, false, "Do not save history.");
96 |
97 | DEFINE_string(
98 | import_mode, "serial",
99 | "Import mode defines the way how the queries will be executed. `serial` mode will try to execute queries in the "
100 | "specified input order. `batched-parallel` will batched and parallelize query execution. NOTE: batched-parallel is "
101 | "an experimental feature, the behavior might be unexpected because it depends on how the underlying database "
102 | "system is configured (e.g., in the transactional setup there might be many serialization errors, while in the "
103 | "analytical setup, ordering of nodes/edges is very important. `parser` mode will just print info about the "
104 | "provided queries. NOTE: `parser` mode won't execute any query against the underlying database system.");
105 | DEFINE_validator(import_mode, [](const char *, const std::string &value) {
106 | if (value == constants::kSerialMode || value == constants::kBatchedParallel || value == constants::kParserMode) {
107 | return true;
108 | }
109 | return false;
110 | });
111 | DEFINE_int32(batch_size, 1000, "A single batch size only when --import-mode=batched-parallel.");
112 | DEFINE_int32(workers_number, 32,
113 | "The number of threads to execute batches in parallel, only when --import-mode=batched-parallel");
114 | DEFINE_bool(collect_parser_stats, true, "Collect parsing statistics only when --import-mode=parser");
115 | DEFINE_bool(print_parser_stats, true, "Print parser statistics for each query only when --import-mode=parser");
116 |
117 | DECLARE_int32(min_log_level);
118 |
119 | int main(int argc, char **argv) {
120 | gflags::SetVersionString(version_string);
121 | gflags::SetUsageMessage(constants::kUsage);
122 |
123 | gflags::ParseCommandLineFlags(&argc, &argv, true);
124 |
125 | format::CsvOptions csv_opts{FLAGS_csv_delimiter, FLAGS_csv_escapechar, FLAGS_csv_doublequote};
126 | format::OutputOptions output_opts{FLAGS_output_format, FLAGS_fit_to_screen};
127 |
128 | if (output_opts.output_format == constants::kCsvFormat && !csv_opts.ValidateDoubleQuote()) {
129 | console::EchoFailure(
130 | "Unsupported combination of 'csv-doublequote' and 'csv-escapechar'\n"
131 | "flags",
132 | "Run '" + std::string(argv[0]) + " --help' for usage.");
133 | return 1;
134 | }
135 |
136 | if (mg_init() != 0) {
137 | console::EchoFailure("Internal error", "Couldn't initialize all the resources");
138 | return 1;
139 | }
140 |
141 | #ifdef _WIN32
142 | // ToDo(the-joksim):
143 | // - How to handle shutdown inside a shutdown on Windows? (Windows uses
144 | // messages instead of signals.)
145 | // - the double shutdown (a second signal being sent to the process while the
146 | // first
147 | // signal is being handled, both signals causing process termination) should
148 | // be a rare event (SIGTERM might be sent from other processes, such as
149 | // daemons/services, e.g. when the system is shutting down). What behavior
150 | // does the double shutdown cause, and what's the benefit in handling it?
151 | #else /* _WIN32 */
152 |
153 | auto shutdown = [](int exit_code = 0) {
154 | if (is_shutting_down) return;
155 | is_shutting_down = 1;
156 |
157 | #ifdef __APPLE__
158 |
159 | std::exit(exit_code);
160 |
161 | #else /* __APPLE__ */
162 |
163 | std::quick_exit(exit_code);
164 |
165 | #endif /*__APPLE__*/
166 | };
167 | struct sigaction action;
168 | action.sa_sigaction = nullptr;
169 | action.sa_handler = shutdown;
170 | // Prevent handling shutdown inside a shutdown. For example, SIGINT handler
171 | // being interrupted by SIGTERM before is_shutting_down is set, thus causing
172 | // double shutdown.
173 | sigemptyset(&action.sa_mask);
174 | sigaddset(&action.sa_mask, SIGTERM);
175 | sigaddset(&action.sa_mask, SIGINT);
176 | action.sa_flags = SA_RESTART;
177 | sigaction(SIGTERM, &action, nullptr);
178 | sigaction(SIGINT, &action, nullptr);
179 |
180 | #endif /* _WIN32 */
181 |
182 | utils::bolt::Config bolt_config{
183 | .host = FLAGS_host,
184 | .port = FLAGS_port,
185 | .username = FLAGS_username,
186 | .password = FLAGS_password,
187 | .use_ssl = FLAGS_use_ssl,
188 | };
189 |
190 | if (console::is_a_tty(STDIN_FILENO)) { // INTERACTIVE
191 | return mode::interactive::Run(bolt_config, FLAGS_history, FLAGS_no_history, FLAGS_verbose_execution_info, csv_opts,
192 | output_opts);
193 | } else if (FLAGS_import_mode == constants::kParserMode) {
194 | return mode::parsing::Run(FLAGS_collect_parser_stats, FLAGS_print_parser_stats);
195 | } else if (FLAGS_import_mode == constants::kBatchedParallel) {
196 | return mode::batch_import::Run(bolt_config, FLAGS_batch_size, FLAGS_workers_number);
197 | } else if (FLAGS_import_mode == constants::kSerialMode) {
198 | return mode::serial_import::Run(bolt_config, csv_opts, output_opts);
199 | } else {
200 | MG_FAIL("Unknown import mode!");
201 | }
202 |
203 | return 0;
204 | }
205 |
--------------------------------------------------------------------------------
/src/utils/future.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
2 | //
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License
14 | // along with this program. If not, see .
15 |
16 | #pragma once
17 |
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 |
26 | #include "assert.hpp"
27 |
28 | namespace utils {
29 |
30 | // Shared is in an anonymous namespace, and the only way to
31 | // construct a Promise or Future is to pass a Shared in. This
32 | // ensures that Promises and Futures can only be constructed
33 | // in this translation unit.
34 | namespace details {
35 | template
36 | class Shared {
37 | mutable std::condition_variable cv_;
38 | mutable std::mutex mu_;
39 | std::optional item_;
40 | bool consumed_ = false;
41 | bool waiting_ = false;
42 | bool filled_ = false;
43 | std::function wait_notifier_ = nullptr;
44 | std::function fill_notifier_ = nullptr;
45 |
46 | public:
47 | explicit Shared(std::function wait_notifier, std::function fill_notifier)
48 | : wait_notifier_(wait_notifier), fill_notifier_(fill_notifier) {}
49 | Shared() = default;
50 | Shared(Shared &&) = delete;
51 | Shared &operator=(Shared &&) = delete;
52 | Shared(const Shared &) = delete;
53 | Shared &operator=(const Shared &) = delete;
54 | ~Shared() = default;
55 |
56 | /// Takes the item out of our optional item_ and returns it.
57 | T Take() {
58 | MG_ASSERT(item_, "Take called without item_ being present");
59 | MG_ASSERT(!consumed_, "Take called on already-consumed Future");
60 |
61 | T ret = std::move(item_).value();
62 | item_.reset();
63 |
64 | consumed_ = true;
65 |
66 | return ret;
67 | }
68 |
69 | T Wait() {
70 | std::unique_lock lock(mu_);
71 | waiting_ = true;
72 |
73 | while (!item_) {
74 | if (wait_notifier_) [[unlikely]] {
75 | // We can't hold our own lock while notifying
76 | // the simulator because notifying the simulator
77 | // involves acquiring the simulator's mutex
78 | // to guarantee that our notification linearizes
79 | // with the simulator's condition variable.
80 | // However, the simulator may acquire our
81 | // mutex to check if we are being awaited,
82 | // while determining system quiescence,
83 | // so we have to get out of its way to avoid
84 | // a cyclical deadlock.
85 | lock.unlock();
86 | std::invoke(wait_notifier_);
87 | lock.lock();
88 | if (item_) {
89 | // item may have been filled while we
90 | // had dropped our mutex while notifying
91 | // the simulator of our waiting_ status.
92 | break;
93 | }
94 | } else {
95 | cv_.wait(lock);
96 | }
97 | MG_ASSERT(!consumed_, "Future consumed twice!");
98 | }
99 |
100 | waiting_ = false;
101 |
102 | return Take();
103 | }
104 |
105 | bool IsReady() const {
106 | std::unique_lock lock(mu_);
107 | return item_.has_value();
108 | }
109 |
110 | std::optional TryGet() {
111 | std::unique_lock lock(mu_);
112 |
113 | if (item_) {
114 | return Take();
115 | }
116 |
117 | return std::nullopt;
118 | }
119 |
120 | void Fill(T item) {
121 | {
122 | std::unique_lock lock(mu_);
123 |
124 | MG_ASSERT(!consumed_, "Promise filled after it was already consumed!");
125 | MG_ASSERT(!filled_, "Promise filled twice!");
126 |
127 | item_ = item;
128 | filled_ = true;
129 | } // lock released before condition variable notification
130 |
131 | if (fill_notifier_) {
132 | std::invoke(fill_notifier_);
133 | }
134 |
135 | cv_.notify_all();
136 | }
137 |
138 | bool IsAwaited() const {
139 | std::unique_lock lock(mu_);
140 | return waiting_;
141 | }
142 | };
143 | } // namespace details
144 |
145 | template
146 | class Future {
147 | bool consumed_or_moved_ = false;
148 | std::shared_ptr> shared_;
149 |
150 | public:
151 | explicit Future(std::shared_ptr> shared) : shared_(shared) {}
152 |
153 | Future() = delete;
154 | Future(Future &&old) noexcept {
155 | MG_ASSERT(!old.consumed_or_moved_, "Future moved from after already being moved from or consumed.");
156 | shared_ = std::move(old.shared_);
157 | consumed_or_moved_ = old.consumed_or_moved_;
158 | old.consumed_or_moved_ = true;
159 | }
160 |
161 | Future &operator=(Future &&old) noexcept {
162 | MG_ASSERT(!old.consumed_or_moved_, "Future moved from after already being moved from or consumed.");
163 | shared_ = std::move(old.shared_);
164 | consumed_or_moved_ = old.consumed_or_moved_;
165 | old.consumed_or_moved_ = true;
166 | return *this;
167 | }
168 |
169 | Future(const Future &) = delete;
170 | Future &operator=(const Future &) = delete;
171 | ~Future() = default;
172 |
173 | /// Returns true if the Future is ready to
174 | /// be consumed using TryGet or Wait (prefer Wait
175 | /// if you know it's ready, because it doesn't
176 | /// return an optional.
177 | bool IsReady() {
178 | MG_ASSERT(!consumed_or_moved_, "Called IsReady after Future already consumed!");
179 | return shared_->IsReady();
180 | }
181 |
182 | /// Non-blocking method that returns the inner
183 | /// item if it's already ready, or std::nullopt
184 | /// if it is not ready yet.
185 | std::optional TryGet() {
186 | MG_ASSERT(!consumed_or_moved_, "Called TryGet after Future already consumed!");
187 | std::optional ret = shared_->TryGet();
188 | if (ret) {
189 | consumed_or_moved_ = true;
190 | }
191 | return ret;
192 | }
193 |
194 | /// Block on the corresponding promise to be filled,
195 | /// returning the inner item when ready.
196 | T Wait() && {
197 | MG_ASSERT(!consumed_or_moved_, "Future should only be consumed with Wait once!");
198 | T ret = shared_->Wait();
199 | consumed_or_moved_ = true;
200 | return ret;
201 | }
202 |
203 | /// Marks this Future as canceled.
204 | void Cancel() {
205 | MG_ASSERT(!consumed_or_moved_, "Future::Cancel called on a future that was already moved or consumed!");
206 | consumed_or_moved_ = true;
207 | }
208 | };
209 |
210 | template
211 | class Promise {
212 | std::shared_ptr> shared_;
213 | bool filled_or_moved_{false};
214 |
215 | public:
216 | explicit Promise(std::shared_ptr> shared) : shared_(shared) {}
217 |
218 | Promise() = delete;
219 | Promise(Promise &&old) noexcept {
220 | MG_ASSERT(!old.filled_or_moved_, "Promise moved from after already being moved from or filled.");
221 | shared_ = std::move(old.shared_);
222 | old.filled_or_moved_ = true;
223 | }
224 |
225 | Promise &operator=(Promise &&old) noexcept {
226 | MG_ASSERT(!old.filled_or_moved_, "Promise moved from after already being moved from or filled.");
227 | shared_ = std::move(old.shared_);
228 | old.filled_or_moved_ = true;
229 | }
230 | Promise(const Promise &) = delete;
231 | Promise &operator=(const Promise &) = delete;
232 |
233 | // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.Branch)
234 | ~Promise() { MG_ASSERT(filled_or_moved_, "Promise destroyed before its associated Future was filled!"); }
235 |
236 | // Fill the expected item into the Future.
237 | void Fill(T item) {
238 | MG_ASSERT(!filled_or_moved_, "Promise::Fill called on a promise that is already filled or moved!");
239 | shared_->Fill(item);
240 | filled_or_moved_ = true;
241 | }
242 |
243 | bool IsAwaited() { return shared_->IsAwaited(); }
244 |
245 | /// Moves this Promise into a unique_ptr.
246 | std::unique_ptr> ToUnique() && {
247 | std::unique_ptr> up = std::make_unique>(std::move(shared_));
248 |
249 | filled_or_moved_ = true;
250 |
251 | return up;
252 | }
253 | };
254 |
255 | template
256 | std::pair, Promise> FuturePromisePair() {
257 | std::shared_ptr> shared = std::make_shared>();
258 |
259 | Future future = Future(shared);
260 | Promise promise = Promise(shared);
261 |
262 | return std::make_pair(std::move(future), std::move(promise));
263 | }
264 |
265 | template
266 | std::pair, Promise> FuturePromisePairWithNotifications(std::function wait_notifier,
267 | std::function fill_notifier) {
268 | std::shared_ptr> shared = std::make_shared>(wait_notifier, fill_notifier);
269 |
270 | Future future = Future(shared);
271 | Promise promise = Promise(shared);
272 |
273 | return std::make_pair(std::move(future), std::move(promise));
274 | }
275 |
276 | } // namespace utils
277 |
--------------------------------------------------------------------------------
/src/batch_import.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
2 | //
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License
14 | // along with this program. If not, see .
15 |
16 | #include "batch_import.hpp"
17 |
18 | #include
19 | #include
20 | #include
21 |
22 | #include
23 |
24 | #include "utils/bolt.hpp"
25 | #include "utils/constants.hpp"
26 | #include "utils/future.hpp"
27 | #include "utils/notifier.hpp"
28 | #include "utils/thread_pool.hpp"
29 | #include "utils/utils.hpp"
30 |
31 | namespace mode::batch_import {
32 |
33 | using namespace std::string_literals;
34 |
35 | struct Batches {
36 | Batches() = delete;
37 | Batches(const Batches &) = delete;
38 | Batches &operator=(const Batches &) = delete;
39 | Batches(Batches &&) = default;
40 | Batches &operator=(Batches &&) = default;
41 |
42 | explicit Batches(uint64_t batch_size, uint64_t max_batches)
43 | : batch_size(batch_size), vertices_batch(batch_size, 0), edges_batch(batch_size, 1) {
44 | batch_index = 1;
45 | vertex_batches.reserve(max_batches);
46 | edge_batches.reserve(max_batches);
47 | }
48 | bool Empty() const { return vertex_batches.empty() && edge_batches.empty(); }
49 |
50 | void AddQuery(query::Query query) {
51 | // NOTE: Take a look at what info /ref QueryInfo contains.
52 | auto is_pre_query = [](const query::Query &query) {
53 | MG_ASSERT(query.info, "QueryInfo is an empty optional");
54 | const auto &info = *query.info;
55 | return info.has_create_index || info.has_storage_mode;
56 | };
57 | auto is_vertex_query = [](const query::Query &query) {
58 | MG_ASSERT(query.info, "QueryInfo is an empty optional");
59 | const auto &info = *query.info;
60 | return info.has_create && !info.has_match && !info.has_merge && !info.has_detach_delete &&
61 | !info.has_create_index && !info.has_drop_index && !info.has_remove && !info.has_storage_mode;
62 | };
63 | // NOTE: This logic might not be correct in some cases, consider MERGE, this is one of the main reasons why
64 | // batched-parallel import mode is EXPERIMENTAL.
65 | auto is_edge_query = [](const query::Query &query) {
66 | MG_ASSERT(query.info, "QueryInfo is an empty optional");
67 | const auto &info = *query.info;
68 | return info.has_match && info.has_create;
69 | };
70 |
71 | if (is_pre_query(query)) {
72 | pre_queries.emplace_back(std::move(query));
73 | } else if (is_vertex_query(query)) {
74 | if (vertices_batch.queries.size() < batch_size) {
75 | vertices_batch.queries.emplace_back(std::move(query));
76 | } else {
77 | batch_index += 1;
78 | vertex_batches.emplace_back(std::move(vertices_batch));
79 | vertices_batch = query::Batch(batch_size, batch_index);
80 | vertices_batch.queries.emplace_back(std::move(query));
81 | }
82 | } else if (is_edge_query(query)) {
83 | if (edges_batch.queries.size() < batch_size) {
84 | edges_batch.queries.emplace_back(std::move(query));
85 | } else {
86 | batch_index += 1;
87 | edge_batches.emplace_back(std::move(edges_batch));
88 | edges_batch = query::Batch(batch_size, batch_index);
89 | edges_batch.queries.emplace_back(std::move(query));
90 | }
91 | } else {
92 | post_queries.emplace_back(std::move(query));
93 | }
94 | }
95 |
96 | // Add last batch if it's missing!
97 | void Finalize() {
98 | if (vertices_batch.queries.size() > 0 && vertices_batch.queries.size() < batch_size) {
99 | vertex_batches.emplace_back(std::move(vertices_batch));
100 | }
101 | if (edges_batch.queries.size() > 0 && edges_batch.queries.size() < batch_size) {
102 | edge_batches.emplace_back(std::move(edges_batch));
103 | }
104 | }
105 |
106 | uint64_t VertexQueryNo() const {
107 | uint64_t no = 0;
108 | for (const auto &b : vertex_batches) {
109 | no += b.queries.size();
110 | }
111 | return no;
112 | }
113 | uint64_t EdgesNo() const {
114 | uint64_t no = 0;
115 | for (const auto &b : edge_batches) {
116 | no += b.queries.size();
117 | }
118 | return no;
119 | }
120 | uint64_t TotalQueryNo() const { return VertexQueryNo() + EdgesNo(); }
121 |
122 | uint64_t batch_size;
123 | uint64_t batch_index{0};
124 |
125 | // An assumption here that there is a few setup queries.
126 | std::vector pre_queries;
127 | query::Batch vertices_batch;
128 | query::Batch edges_batch;
129 | std::vector vertex_batches;
130 | std::vector edge_batches;
131 | std::vector post_queries;
132 | };
133 |
134 | inline std::ostream &operator<<(std::ostream &os, const Batches &bs) {
135 | os << "Batches .vertex_batches " << bs.vertex_batches.size() << " .edge_batches " << bs.edge_batches.size() << '\n';
136 | os << " vertex_batches" << '\n';
137 | for (const auto &b : bs.vertex_batches) {
138 | os << " " << b.queries.size() << '\n';
139 | }
140 | os << " edge_batches" << '\n';
141 | for (const auto &b : bs.edge_batches) {
142 | os << " " << b.queries.size() << '\n';
143 | }
144 | return os;
145 | }
146 |
147 | struct BatchExecutionContext {
148 | BatchExecutionContext() = delete;
149 | BatchExecutionContext(const BatchExecutionContext &) = delete;
150 | BatchExecutionContext &operator=(const BatchExecutionContext &) = delete;
151 | BatchExecutionContext(BatchExecutionContext &&) = delete;
152 | BatchExecutionContext &operator=(BatchExecutionContext &&) = delete;
153 |
154 | BatchExecutionContext(uint64_t batch_size, uint64_t max_batches, uint64_t max_concurrent_executions,
155 | const utils::bolt::Config &bolt_config)
156 | : batch_size(batch_size),
157 | max_batches(max_batches),
158 | max_concurrent_executions(max_concurrent_executions),
159 | thread_pool(max_concurrent_executions) {
160 | sessions.reserve(max_concurrent_executions);
161 | for (uint64_t thread_i = 0; thread_i < max_concurrent_executions; ++thread_i) {
162 | sessions[thread_i] = MakeBoltSession(bolt_config);
163 | if (!sessions[thread_i].get()) {
164 | MG_FAIL("a session uninitialized");
165 | }
166 | }
167 | }
168 |
169 | /// A single batch size / number of queries in a single batch.
170 | uint64_t batch_size;
171 | /// Max number of batches loaded inside RAM at any given time.
172 | uint64_t max_batches;
173 | /// Size of the thread pool used to execute batches against the database.
174 | uint64_t max_concurrent_executions;
175 | utils::ThreadPool thread_pool{max_concurrent_executions};
176 | utils::Notifier notifier;
177 | std::vector sessions;
178 | };
179 |
180 | Batches FetchBatches(BatchExecutionContext &execution_context) {
181 | uint64_t query_number = 0;
182 | Batches batches(execution_context.batch_size, execution_context.max_batches);
183 | while (true) {
184 | if (query_number + 1 >= execution_context.batch_size * execution_context.max_batches) {
185 | break;
186 | }
187 | auto query = query::GetQuery(nullptr, true);
188 | if (!query) {
189 | break;
190 | }
191 | if (query->query.empty()) {
192 | continue;
193 | }
194 | query_number += 1;
195 | batches.AddQuery(std::move(*query));
196 | }
197 | batches.Finalize();
198 | return batches;
199 | }
200 |
201 | void ExecuteSerial(const std::vector &queries, BatchExecutionContext &context) {
202 | for (const auto &query : queries) {
203 | try {
204 | query::ExecuteQuery(context.sessions[0].get(), query.query);
205 | } catch (const utils::ClientQueryException &e) {
206 | console::EchoFailure("Client received query exception", e.what());
207 | MG_FAIL("Unable to ExecuteSerial");
208 | } catch (const utils::ClientFatalException &e) {
209 | console::EchoFailure("Client received connection exception", e.what());
210 | MG_FAIL("Unable to ExecuteSerial");
211 | }
212 | }
213 | }
214 |
215 | /// returns the number of executed batches.
216 | uint64_t ExecuteBatchesParallel(std::vector &batches, BatchExecutionContext &execution_context,
217 | const utils::bolt::Config &bolt_config) {
218 | if (batches.empty()) return 0;
219 | std::atomic executed_batches = 0;
220 | while (true) {
221 | if (executed_batches.load() >= batches.size()) {
222 | break;
223 | }
224 |
225 | std::unordered_map> f_execs;
226 | uint64_t used_threads = 0;
227 | for (uint64_t batch_i = 0; batch_i < batches.size(); ++batch_i) {
228 | if (used_threads >= execution_context.max_concurrent_executions) {
229 | break;
230 | }
231 | auto &batch = batches.at(batch_i);
232 | if (batch.is_executed) {
233 | continue;
234 | }
235 |
236 | // Schedule all batches for parallel execution.
237 | auto thread_i = used_threads;
238 | used_threads++;
239 | utils::ReadinessToken readiness_token{static_cast(batch_i)};
240 | std::function fill_notifier = [readiness_token, &execution_context]() {
241 | execution_context.notifier.Notify(readiness_token);
242 | };
243 | auto [future, promise] = utils::FuturePromisePairWithNotifications(nullptr, fill_notifier);
244 | auto shared_promise = std::make_shared(std::move(promise));
245 | execution_context.thread_pool.AddTask([&execution_context, &batches, thread_i, batch_i, &executed_batches,
246 | &bolt_config, promise = std::move(shared_promise)]() mutable {
247 | auto &batch = batches.at(batch_i);
248 | if (batch.backoff > 1) {
249 | std::this_thread::sleep_for(std::chrono::milliseconds(batch.backoff));
250 | }
251 | auto ret = query::ExecuteBatch(execution_context.sessions[thread_i].get(), batch);
252 | if (ret.is_executed) {
253 | batch.is_executed = true;
254 | executed_batches++;
255 | promise->Fill(true);
256 | } else {
257 | // NOTE: The magic numbers here are here because the idea was to avoid serialization errors in the
258 | // transactional import mode. They were picked in a specific context (playing with a specific dataset). It's
259 | // definitely possible to improve.
260 | batch.backoff *= 2;
261 | if (batch.backoff > 100) {
262 | batch.backoff = 1;
263 | }
264 | batch.attempts += 1;
265 | promise->Fill(false);
266 | }
267 | if (mg_session_status(execution_context.sessions[thread_i].get()) == MG_SESSION_BAD) {
268 | execution_context.sessions[thread_i] = MakeBoltSession(bolt_config);
269 | }
270 | });
271 | f_execs.insert_or_assign(thread_i, std::move(future));
272 | }
273 |
274 | // Wait for the execution to finish.
275 | int64_t no = used_threads;
276 | while (no > 0) {
277 | execution_context.notifier.Await();
278 | --no;
279 | }
280 | }
281 | return executed_batches.load();
282 | }
283 |
284 | int Run(const utils::bolt::Config &bolt_config, int batch_size, int workers_number) {
285 | // NOTE: In the execution context it's possible to define size of the thread pool + how many different batches are
286 | // held in RAM at any given time. For simplicity of runtime flags, these to are set to the same value
287 | // (workers_number).
288 | BatchExecutionContext execution_context(batch_size, workers_number, workers_number, bolt_config);
289 | while (true) {
290 | auto batches = FetchBatches(execution_context);
291 | if (batches.Empty()) {
292 | break;
293 | }
294 | // Stuff like CREATE INDEX.
295 | ExecuteSerial(batches.pre_queries, execution_context);
296 | // Vertices have to come first because edges depend on vertices.
297 | ExecuteBatchesParallel(batches.vertex_batches, execution_context, bolt_config);
298 | ExecuteBatchesParallel(batches.edge_batches, execution_context, bolt_config);
299 | // Any cleanup queries.
300 | ExecuteSerial(batches.post_queries, execution_context);
301 | }
302 | return 0;
303 | }
304 |
305 | } // namespace mode::batch_import
306 |
--------------------------------------------------------------------------------
/src/utils/query_type.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
2 | //
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License
14 | // along with this program. If not, see .
15 |
16 | #pragma once
17 |
18 | #include "iostream"
19 |
20 | // Let's have a party with simple state machine! The intention here is to
21 | // implement something simple to understand and fast; to experiment with the
22 | // batched and parallelization execution mode.
23 | // A more modular choice would be some lexer analyzer or Antlr, but that would
24 | // add a lot of build complexity + would likely be slower.
25 | // Consider implementing some simple but fast state machine or at least try to
26 | // be DRY (in this case makes a lot of sense).
27 |
28 | namespace query::line {
29 |
30 | struct CollectedClauses {
31 | bool has_match{false};
32 | bool has_create{false};
33 | bool has_merge{false};
34 | bool has_create_index{false};
35 | bool has_detach_delete{false};
36 | bool has_remove{false};
37 | bool has_drop_index{false};
38 | bool has_storage_mode{false};
39 | };
40 |
41 | inline CollectedClauses MergeCollectedClauses(const CollectedClauses &l, const CollectedClauses &r) {
42 | return CollectedClauses{
43 | .has_match = l.has_match || r.has_match,
44 | .has_create = l.has_create || r.has_create,
45 | .has_merge = l.has_merge || r.has_merge,
46 | .has_create_index = l.has_create_index || r.has_create_index,
47 | .has_detach_delete = l.has_detach_delete || r.has_detach_delete,
48 | .has_remove = l.has_remove || r.has_remove,
49 | .has_drop_index = l.has_drop_index || r.has_drop_index,
50 | .has_storage_mode = l.has_storage_mode || r.has_storage_mode,
51 | };
52 | }
53 |
54 | inline std::ostream &operator<<(std::ostream &os, const CollectedClauses &cc) {
55 | os << "CollectedClauses: ";
56 | if (cc.has_match) {
57 | os << "HAS_MATCH ";
58 | } else if (cc.has_create) {
59 | os << "HAS_CREATE ";
60 | } else if (cc.has_merge) {
61 | os << "HAS_MERGE ";
62 | } else if (cc.has_create_index) {
63 | os << "HAS_CREATE_INDEX ";
64 | } else if (cc.has_detach_delete) {
65 | os << "HAS_DETACH_DELETE ";
66 | } else if (cc.has_remove) {
67 | os << "HAS_REMOVE ";
68 | } else if (cc.has_drop_index) {
69 | os << "HAS_DROP_INDEX ";
70 | } else if (cc.has_storage_mode) {
71 | os << "HAS_STORAGE_MODE ";
72 | }
73 | return os;
74 | }
75 |
76 | // clang-format off
77 | enum class ClauseState {
78 | NONE, // CREATE_(
79 | C, CR, CRE, CREA, CREAT, CREATE, CREATE_, CREATE_P,
80 | CREATE_I, CREATE_IN, CREATE_IND, CREATE_INDE, CREATE_INDEX,
81 | M, MA, MAT, MATC, MATCH, MATCH_, MATCH_P,
82 | ME, MER, MERG, MERGE, MERGE_, MERGE_P,
83 | D, DE, DET, DETA, DETAC, DETACH, DETACH_, DETACH_D, DETACH_DE, DETACH_DEL, DETACH_DELE, DETACH_DELET, DETACH_DELETE,
84 | DR, DRO, DROP, DROP_, DROP_I, DROP_IN, DROP_IND, DROP_INDE, DROP_INDEX,
85 | // )_REMOVE
86 | P, P_, P_R, P_RE, P_REM, P_REMO, P_REMOV, P_REMOVE,
87 | S, ST, STO, STOR, STORA, STORAG, STORAGE, STORAGE_, STORAGE_M, STORAGE_MO, STORAGE_MOD, STORAGE_MODE,
88 | };
89 | // clang-format on
90 |
91 | inline bool IsWhitespace(char c) { return c == ' ' || c == '\t' || c == '\n'; }
92 |
93 | inline ClauseState NextState(char *quote, char c, const ClauseState state) {
94 | if (!*quote && IsWhitespace(c)) {
95 | if (state == ClauseState::CREATE) {
96 | return ClauseState::CREATE_;
97 | } else if (state == ClauseState::MATCH) {
98 | return ClauseState::MATCH_;
99 | } else if (state == ClauseState::MERGE) {
100 | return ClauseState::MERGE_;
101 | } else if (state == ClauseState::DETACH) {
102 | return ClauseState::DETACH_;
103 | } else if (state == ClauseState::DROP) {
104 | return ClauseState::DROP_;
105 | } else if (state == ClauseState::P) {
106 | return ClauseState::P_;
107 | } else if (state == ClauseState::STORAGE) {
108 | return ClauseState::STORAGE_;
109 | } else {
110 | return state;
111 | }
112 | }
113 |
114 | // CREATE
115 | if (!*quote && (c == 'C' || c == 'c') && state == ClauseState::NONE) {
116 | return ClauseState::C;
117 | } else if (!*quote && (c == 'R' || c == 'r') && state == ClauseState::C) {
118 | return ClauseState::CR;
119 | } else if (!*quote && (c == 'E' || c == 'e') && state == ClauseState::CR) {
120 | return ClauseState::CRE;
121 | } else if (!*quote && (c == 'A' || c == 'a') && state == ClauseState::CRE) {
122 | return ClauseState::CREA;
123 | } else if (!*quote && (c == 'T' || c == 't') && state == ClauseState::CREA) {
124 | return ClauseState::CREAT;
125 | } else if (!*quote && (c == 'E' || c == 'e') && state == ClauseState::CREAT) {
126 | return ClauseState::CREATE;
127 | } else if (!*quote && c == '(' && (state == ClauseState::CREATE || state == ClauseState::CREATE_)) {
128 | return ClauseState::CREATE_P;
129 |
130 | // CREATE INDEX
131 | } else if (!*quote && (c == 'I' || c == 'i') && state == ClauseState::CREATE_) {
132 | return ClauseState::CREATE_I;
133 | } else if (!*quote && (c == 'N' || c == 'n') && state == ClauseState::CREATE_I) {
134 | return ClauseState::CREATE_IN;
135 | } else if (!*quote && (c == 'D' || c == 'd') && state == ClauseState::CREATE_IN) {
136 | return ClauseState::CREATE_IND;
137 | } else if (!*quote && (c == 'E' || c == 'e') && state == ClauseState::CREATE_IND) {
138 | return ClauseState::CREATE_INDE;
139 | } else if (!*quote && (c == 'X' || c == 'x') && state == ClauseState::CREATE_INDE) {
140 | return ClauseState::CREATE_INDEX;
141 |
142 | // MATCH
143 | } else if (!*quote && (c == 'M' || c == 'm') && state == ClauseState::NONE) {
144 | return ClauseState::M;
145 | } else if (!*quote && (c == 'A' || c == 'a') && state == ClauseState::M) {
146 | return ClauseState::MA;
147 | } else if (!*quote && (c == 'T' || c == 't') && state == ClauseState::MA) {
148 | return ClauseState::MAT;
149 | } else if (!*quote && (c == 'C' || c == 'c') && state == ClauseState::MAT) {
150 | return ClauseState::MATC;
151 | } else if (!*quote && (c == 'H' || c == 'h') && state == ClauseState::MATC) {
152 | return ClauseState::MATCH;
153 | } else if (!*quote && c == '(' && (state == ClauseState::MATCH || state == ClauseState::MATCH_)) {
154 | return ClauseState::MATCH_P;
155 |
156 | // MERGE
157 | } else if (!*quote && (c == 'E' || c == 'e') && state == ClauseState::M) {
158 | return ClauseState::ME;
159 | } else if (!*quote && (c == 'R' || c == 'r') && state == ClauseState::ME) {
160 | return ClauseState::MER;
161 | } else if (!*quote && (c == 'G' || c == 'g') && state == ClauseState::MER) {
162 | return ClauseState::MERG;
163 | } else if (!*quote && (c == 'E' || c == 'e') && state == ClauseState::MERG) {
164 | return ClauseState::MERGE;
165 | } else if (!*quote && c == '(' && (state == ClauseState::MERGE || state == ClauseState::MERGE_)) {
166 | return ClauseState::MERGE_P;
167 |
168 | // DETACH DELETE
169 | } else if (!*quote && (c == 'D' || c == 'd') && state == ClauseState::NONE) {
170 | return ClauseState::D;
171 | } else if (!*quote && (c == 'E' || c == 'e') && state == ClauseState::D) {
172 | return ClauseState::DE;
173 | } else if (!*quote && (c == 'T' || c == 't') && state == ClauseState::DE) {
174 | return ClauseState::DET;
175 | } else if (!*quote && (c == 'A' || c == 'a') && state == ClauseState::DET) {
176 | return ClauseState::DETA;
177 | } else if (!*quote && (c == 'C' || c == 'c') && state == ClauseState::DETA) {
178 | return ClauseState::DETAC;
179 | } else if (!*quote && (c == 'H' || c == 'h') && state == ClauseState::DETAC) {
180 | return ClauseState::DETACH;
181 | } else if (!*quote && (c == 'D' || c == 'd') && state == ClauseState::DETACH_) {
182 | return ClauseState::DETACH_D;
183 | } else if (!*quote && (c == 'E' || c == 'e') && state == ClauseState::DETACH_D) {
184 | return ClauseState::DETACH_DE;
185 | } else if (!*quote && (c == 'L' || c == 'e') && state == ClauseState::DETACH_DE) {
186 | return ClauseState::DETACH_DEL;
187 | } else if (!*quote && (c == 'E' || c == 'e') && state == ClauseState::DETACH_DEL) {
188 | return ClauseState::DETACH_DELE;
189 | } else if (!*quote && (c == 'T' || c == 'e') && state == ClauseState::DETACH_DELE) {
190 | return ClauseState::DETACH_DELET;
191 | } else if (!*quote && (c == 'E' || c == 'e') && state == ClauseState::DETACH_DELET) {
192 | return ClauseState::DETACH_DELETE;
193 |
194 | // DROP INDEX
195 | } else if (!*quote && (c == 'R' || c == 'r') && state == ClauseState::D) {
196 | return ClauseState::DR;
197 | } else if (!*quote && (c == 'O' || c == 'o') && state == ClauseState::DR) {
198 | return ClauseState::DRO;
199 | } else if (!*quote && (c == 'P' || c == 'p') && state == ClauseState::DRO) {
200 | return ClauseState::DROP;
201 | } else if (!*quote && (c == 'I' || c == 'i') && state == ClauseState::DROP_) {
202 | return ClauseState::DROP_I;
203 | } else if (!*quote && (c == 'N' || c == 'n') && state == ClauseState::DROP_I) {
204 | return ClauseState::DROP_IN;
205 | } else if (!*quote && (c == 'D' || c == 'd') && state == ClauseState::DROP_IN) {
206 | return ClauseState::DROP_IND;
207 | } else if (!*quote && (c == 'E' || c == 'e') && state == ClauseState::DROP_IND) {
208 | return ClauseState::DROP_INDE;
209 | } else if (!*quote && (c == 'X' || c == 'x') && state == ClauseState::DROP_INDE) {
210 | return ClauseState::DROP_INDEX;
211 |
212 | // ) REMOVE
213 | } else if (!*quote && c == ')' && state == ClauseState::NONE) {
214 | return ClauseState::P;
215 | } else if (!*quote && (c == 'R' || c == 'r') && state == ClauseState::P_) {
216 | return ClauseState::P_R;
217 | } else if (!*quote && (c == 'E' || c == 'e') && state == ClauseState::P_R) {
218 | return ClauseState::P_RE;
219 | } else if (!*quote && (c == 'M' || c == 'm') && state == ClauseState::P_RE) {
220 | return ClauseState::P_REM;
221 | } else if (!*quote && (c == 'O' || c == 'o') && state == ClauseState::P_REM) {
222 | return ClauseState::P_REMO;
223 | } else if (!*quote && (c == 'V' || c == 'v') && state == ClauseState::P_REMO) {
224 | return ClauseState::P_REMOV;
225 | } else if (!*quote && (c == 'E' || c == 'e') && state == ClauseState::P_REMOV) {
226 | return ClauseState::P_REMOVE;
227 |
228 | } else if (!*quote && (c == 'S' || c == 's') && state == ClauseState::NONE) {
229 | return ClauseState::S;
230 | } else if (!*quote && (c == 'T' || c == 't') && state == ClauseState::S) {
231 | return ClauseState::ST;
232 | } else if (!*quote && (c == 'O' || c == 'o') && state == ClauseState::ST) {
233 | return ClauseState::STO;
234 | } else if (!*quote && (c == 'R' || c == 'r') && state == ClauseState::STO) {
235 | return ClauseState::STOR;
236 | } else if (!*quote && (c == 'A' || c == 'a') && state == ClauseState::STOR) {
237 | return ClauseState::STORA;
238 | } else if (!*quote && (c == 'G' || c == 'g') && state == ClauseState::STORA) {
239 | return ClauseState::STORAG;
240 | } else if (!*quote && (c == 'E' || c == 'e') && state == ClauseState::STORAG) {
241 | return ClauseState::STORAGE;
242 | } else if (!*quote && (c == 'M' || c == 'm') && state == ClauseState::STORAGE_) {
243 | return ClauseState::STORAGE_M;
244 | } else if (!*quote && (c == 'O' || c == 'o') && state == ClauseState::STORAGE_M) {
245 | return ClauseState::STORAGE_MO;
246 | } else if (!*quote && (c == 'D' || c == 'd') && state == ClauseState::STORAGE_MO) {
247 | return ClauseState::STORAGE_MOD;
248 | } else if (!*quote && (c == 'E' || c == 'e') && state == ClauseState::STORAGE_MOD) {
249 | return ClauseState::STORAGE_MODE;
250 |
251 | } else {
252 | return ClauseState::NONE;
253 | }
254 | }
255 |
256 | inline std::ostream &operator<<(std::ostream &os, const ClauseState &s) {
257 | if (s == ClauseState::CREATE_P) {
258 | os << "CREATE_(";
259 | } else if (s == ClauseState::MATCH_P) {
260 | os << "MATCH_(";
261 | } else if (s == ClauseState::MERGE_P) {
262 | os << "MERGE_(";
263 | } else if (s == ClauseState::CREATE_INDEX) {
264 | os << "CREATE_INDEX";
265 | } else if (s == ClauseState::DETACH_DELETE) {
266 | os << "DETACH_DELETE";
267 | } else if (s == ClauseState::DROP_INDEX) {
268 | os << "DROP_INDEX";
269 | } else if (s == ClauseState::P_REMOVE) {
270 | os << ")_REMOVE";
271 | } else if (s == ClauseState::STORAGE_MODE) {
272 | os << "STORAGE_MODE";
273 | } else {
274 | os << "Some ClauseState";
275 | }
276 | return os;
277 | }
278 |
279 | } // namespace query::line
280 |
--------------------------------------------------------------------------------
/src/utils/utils.hpp:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2016-2023 Memgraph Ltd. [https://memgraph.com]
2 | //
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU General Public License
14 | // along with this program. If not, see .
15 |
16 | #pragma once
17 |
18 | #include
19 | #include
20 | #include
21 | #include