├── .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 | [![Actions Status](https://github.com/memgraph/mgconsole/workflows/CI/badge.svg)](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 | mgconsole 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 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #ifdef _WIN32 28 | #define STDIN_FILENO 0 29 | #endif /* _WIN32 */ 30 | 31 | #include "mgclient.h" 32 | #include "replxx.h" 33 | 34 | #include "query_type.hpp" 35 | 36 | namespace fs = std::filesystem; 37 | 38 | namespace mg_memory { 39 | /// Unique pointers with custom deleters for automatic memory management of 40 | /// mg_values. 41 | 42 | template 43 | inline void CustomDelete(T *); 44 | 45 | template <> 46 | inline void CustomDelete(mg_session *session) { 47 | mg_session_destroy(session); 48 | } 49 | 50 | template <> 51 | inline void CustomDelete(mg_session_params *session_params) { 52 | mg_session_params_destroy(session_params); 53 | } 54 | 55 | template <> 56 | inline void CustomDelete(mg_list *list) { 57 | mg_list_destroy(list); 58 | } 59 | 60 | template <> 61 | inline void CustomDelete(mg_map *map) { 62 | mg_map_destroy(map); 63 | } 64 | 65 | template 66 | using CustomUniquePtr = std::unique_ptr; 67 | 68 | template 69 | CustomUniquePtr MakeCustomUnique(T *ptr) { 70 | return CustomUniquePtr(ptr, CustomDelete); 71 | } 72 | 73 | using MgSessionPtr = CustomUniquePtr; 74 | using MgSessionParamsPtr = CustomUniquePtr; 75 | using MgListPtr = CustomUniquePtr; 76 | using MgMapPtr = CustomUniquePtr; 77 | 78 | } // namespace mg_memory 79 | 80 | namespace utils { 81 | 82 | class ClientFatalException : public std::exception { 83 | public: 84 | ClientFatalException(std::string what) : what_(std::move(what)) {} 85 | const char *what() const noexcept override { return what_.c_str(); } 86 | 87 | private: 88 | std::string what_; 89 | }; 90 | 91 | class ClientQueryException : public std::exception { 92 | public: 93 | ClientQueryException(std::string what) : what_(std::move(what)) {} 94 | const char *what() const noexcept override { return what_.c_str(); } 95 | 96 | private: 97 | std::string what_; 98 | }; 99 | 100 | bool EnsureDir(const fs::path &dir) noexcept; 101 | 102 | fs::path GetUserHomeDir(); 103 | 104 | /** 105 | * return string with all uppercased characters (locale independent). 106 | */ 107 | std::string ToUpperCase(std::string s); 108 | 109 | /** 110 | * removes whitespace characters from the start and from the end of a string. 111 | * 112 | * @param str string that is going to be trimmed 113 | * 114 | * @return trimmed string 115 | */ 116 | std::string Trim(const std::string &s); 117 | 118 | /** 119 | * replaces all occurences of in with . 120 | */ 121 | // todo: this could be implemented much more efficiently. 122 | std::string Replace(std::string src, const std::string &match, const std::string &replacement); 123 | 124 | /// escapes all whitespace and quotation characters to produce a string 125 | /// which can be used as a string literal. 126 | std::string Escape(const std::string &src); 127 | 128 | /** 129 | * outputs a collection of items to the given stream, separating them with the 130 | * given delimiter. 131 | * 132 | * @param stream destination stream. 133 | * @param iterable an iterable collection of items. 134 | * @param delim delimiter that is put between items. 135 | * @param streamer function which accepts a tstream and an item and 136 | * streams the item to the stream. 137 | */ 138 | template 139 | inline void PrintIterable(tstream &stream, const titerable &iterable, const std::string &delim = ", ") { 140 | bool first = true; 141 | for (const auto &item : iterable) { 142 | if (first) 143 | first = false; 144 | else 145 | stream << delim; 146 | stream << item; 147 | } 148 | } 149 | 150 | void PrintStringUnescaped(std::ostream &os, const mg_string *str); 151 | 152 | void PrintValue(std::ostream &os, const mg_string *str); 153 | 154 | void PrintValue(std::ostream &os, const mg_map *map); 155 | 156 | void PrintValue(std::ostream &os, const mg_node *node); 157 | 158 | void PrintValue(std::ostream &os, const mg_relationship *rel); 159 | 160 | void PrintValue(std::ostream &os, const mg_unbound_relationship *rel); 161 | 162 | void PrintValue(std::ostream &os, const mg_path *path); 163 | 164 | void PrintValue(std::ostream &os, const mg_date *date); 165 | 166 | void PrintValue(std::ostream &os, const mg_local_time *local_time); 167 | 168 | void PrintValue(std::ostream &os, const mg_local_date_time *local_date_time); 169 | 170 | void PrintValue(std::ostream &os, const mg_duration *duration); 171 | 172 | void PrintValue(std::ostream &os, const mg_value *value); 173 | 174 | } // namespace utils 175 | 176 | // Unfinished query text from previous input. 177 | // e.g. Previous input was MATCH(n) RETURN n; MATCH 178 | // then default_text would be set to MATCH for next query. 179 | static std::string mgconsole_global_default_text; 180 | // The following variables are used to track the line number and index (number specifying order) of the processed query. 181 | [[maybe_unused]] static int64_t mgconsole_global_line_number{0}; 182 | [[maybe_unused]] static int64_t mgconsole_global_query_index{0}; 183 | 184 | namespace console { 185 | 186 | bool is_a_tty(int fd); 187 | 188 | void PrintHelp(); 189 | 190 | void PrintDocs(); 191 | 192 | void EchoFailure(const std::string &failure_msg, const std::string &explanation); 193 | 194 | void EchoInfo(const std::string &message); 195 | 196 | void EchoStats(const std::map &stats); 197 | 198 | void EchoNotification(const std::map ¬ification); 199 | 200 | void EchoExecutionInfo(const std::map &execution_info); 201 | 202 | /// Helper function that sets default input for 'readline' 203 | int SetDefaultText(); 204 | 205 | void SetStdinEcho(bool enable); 206 | 207 | std::optional GetLine(); 208 | 209 | struct ParseLineInfo { 210 | query::line::CollectedClauses collected_clauses; 211 | }; 212 | struct ParseLineResult { 213 | std::string line; 214 | bool is_done; 215 | // In the case when caller is interested in more info. 216 | std::optional info; 217 | }; 218 | /// Because query can span across multiple lines. 219 | inline ParseLineInfo MergeParseLineInfo(const ParseLineInfo &l, const ParseLineInfo &r) { 220 | return ParseLineInfo{ 221 | .collected_clauses = query::line::MergeCollectedClauses(l.collected_clauses, r.collected_clauses), 222 | }; 223 | } 224 | /// Helper function that parses user line input. 225 | /// @param line user input line. 226 | /// @param quote quote character or '\0'; if set line is inside quotation. 227 | /// @param escaped if set, next character should be escaped. 228 | /// @return ParseLineResult a pair of string and bool. string is parsed line and bool marks 229 | /// if query finished(Query finishes with ';') with this line. + optionally info about what line contains 230 | ParseLineResult ParseLine(const std::string &line, char *quote, bool *escaped, bool collect_info = false); 231 | 232 | /// Helper function that reads a line from the 233 | /// standard input using the 'readline' lib. 234 | /// Adds support for history and reverse-search. 235 | /// @param prompt The prompt to display. 236 | /// @return User input line, or nullopt on EOF. 237 | std::optional ReadLine(Replxx *replxx_instance, const std::string &prompt); 238 | 239 | } // namespace console 240 | 241 | namespace query { 242 | 243 | // Interesting abstraction because multiple lines can be parsed in parallel. 244 | struct Line { 245 | int64_t line_number; 246 | std::string line; 247 | }; 248 | 249 | // NOTE: In theory it's possible to merge QueryInfo and CollectedClauses because they are the same, but it's not clear 250 | // what would be the best, leaving as is. 251 | struct QueryInfo { 252 | bool has_create{false}; 253 | bool has_match{false}; 254 | bool has_merge{false}; 255 | bool has_detach_delete{false}; 256 | bool has_create_index{false}; 257 | bool has_drop_index{false}; 258 | bool has_remove{false}; 259 | bool has_storage_mode{false}; 260 | }; 261 | 262 | inline std::optional QueryInfoFromParseLineInfo(const std::optional &line_info) { 263 | // NOTE: The logic here is correct only if there is a controlled input, change to make batched and parallel import 264 | // non-experimental feature. 265 | if (line_info) { 266 | return QueryInfo{ 267 | .has_create = line_info->collected_clauses.has_create, 268 | .has_match = line_info->collected_clauses.has_match, 269 | .has_merge = line_info->collected_clauses.has_merge, 270 | .has_detach_delete = line_info->collected_clauses.has_detach_delete, 271 | .has_create_index = line_info->collected_clauses.has_create_index, 272 | .has_drop_index = line_info->collected_clauses.has_drop_index, 273 | .has_remove = line_info->collected_clauses.has_remove, 274 | .has_storage_mode = line_info->collected_clauses.has_storage_mode, 275 | }; 276 | } else { 277 | return std::nullopt; 278 | } 279 | } 280 | 281 | struct Query { 282 | int64_t line_number{0}; 283 | int64_t index{0}; 284 | std::string query{""}; 285 | std::optional info{std::nullopt}; 286 | }; 287 | void PrintQueryInfo(const Query &); 288 | 289 | struct Batch { 290 | explicit Batch(int64_t capacity, int64_t index) : capacity(capacity), index(index) { queries.reserve(capacity); } 291 | Batch() = delete; 292 | Batch(const Batch &) = delete; 293 | Batch &operator=(const Batch &) = delete; 294 | Batch(Batch &&) = default; 295 | Batch &operator=(Batch &&) = default; 296 | 297 | int64_t capacity; 298 | int64_t index; 299 | std::vector queries; 300 | bool is_executed = false; 301 | int64_t backoff = 1; 302 | int64_t attempts = 0; 303 | }; 304 | void PrintBatchesInfo(const std::vector &); 305 | 306 | struct QueryResult { 307 | std::vector header; 308 | std::vector records; 309 | std::chrono::duration wall_time; 310 | std::optional> notification; 311 | std::optional> stats; 312 | std::optional> execution_info; 313 | }; 314 | 315 | struct BatchResult { 316 | bool is_executed; 317 | std::vector results; 318 | }; 319 | 320 | // Depends on the global static string because of ...; MATCH 321 | // The extra part is preserved for the next GetQuery call 322 | std::optional GetQuery(Replxx *replxx_instance, bool collect_info = false); 323 | 324 | QueryResult ExecuteQuery(mg_session *session, const std::string &query); 325 | BatchResult ExecuteBatch(mg_session *session, const Batch &batch); 326 | 327 | } // namespace query 328 | 329 | namespace format { 330 | 331 | struct CsvOptions { 332 | CsvOptions(std::string delim, std::string escape, const bool dquote) 333 | : delimiter(std::move(delim)), escapechar(std::move(escape)), doublequote(dquote) {} 334 | 335 | bool ValidateDoubleQuote() { 336 | if (!doublequote && escapechar.size() != 1) { 337 | return false; 338 | } 339 | return true; 340 | } 341 | 342 | std::string delimiter; 343 | std::string escapechar; 344 | bool doublequote; 345 | }; 346 | 347 | struct OutputOptions { 348 | OutputOptions(std::string out_format, const bool fit_to_scr) 349 | : output_format(std::move(out_format)), fit_to_screen(fit_to_scr) {} 350 | 351 | std::string output_format; 352 | bool fit_to_screen; 353 | }; 354 | 355 | void PrintHeaderTabular(const std::vector &data, int total_width, int column_width, int num_columns, 356 | bool all_columns_fit, int margin); 357 | 358 | /// Helper function for determining maximum length of data. 359 | /// @param data List of mg_values representing row. 360 | /// @param margin Column margin width. 361 | /// @return length needed for representing max size element in @p data list. 362 | /// Plus one is added because of column start character '|'. 363 | uint64_t GetMaxColumnWidth(const mg_memory::MgListPtr &data, int margin); 364 | 365 | uint64_t GetMaxColumnWidth(const std::vector &data, int margin); 366 | 367 | void PrintRowTabular(const mg_memory::MgListPtr &data, int total_width, int column_width, int num_columns, 368 | bool all_columns_fit, int margin); 369 | 370 | void PrintTabular(const std::vector &header, const std::vector &records, 371 | const bool fit_to_screen); 372 | 373 | std::vector FormatCsvFields(const mg_memory::MgListPtr &fields, const CsvOptions &csv_opts); 374 | 375 | std::vector FormatCsvHeader(const std::vector &fields, const CsvOptions &csv_opts); 376 | 377 | void PrintCsv(const std::vector &header, const std::vector &records, 378 | const CsvOptions &csv_opts); 379 | 380 | void Output(const std::vector &header, const std::vector &records, 381 | const OutputOptions &out_opts, const CsvOptions &csv_opts); 382 | } // namespace format 383 | 384 | Replxx *InitAndSetupReplxx(); 385 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | --------------------------------------------------------------------------------