tests/manual/query_engine.cpp work in compiler in progress
This commit is contained in:
parent
84db4b6657
commit
c8440b4671
@ -59,9 +59,12 @@ endif (UNIX)
|
|||||||
|
|
||||||
# external dependencies
|
# external dependencies
|
||||||
|
|
||||||
set(build_include_dir ${CMAKE_BINARY_DIR}/include)
|
|
||||||
set(src_dir ${CMAKE_SOURCE_DIR}/src)
|
set(src_dir ${CMAKE_SOURCE_DIR}/src)
|
||||||
set(libs_dir ${CMAKE_SOURCE_DIR}/libs)
|
set(libs_dir ${CMAKE_SOURCE_DIR}/libs)
|
||||||
|
set(include_dir ${CMAKE_SOURCE_DIR}/include)
|
||||||
|
set(build_include_dir ${CMAKE_BINARY_DIR}/include)
|
||||||
|
set(test_include_dir ${CMAKE_BINARY_DIR}/tests/include)
|
||||||
|
set(test_src_dir ${CMAKE_BINARY_DIR}/tests/src)
|
||||||
|
|
||||||
# setup external dependencies
|
# setup external dependencies
|
||||||
|
|
||||||
@ -104,18 +107,28 @@ SET(cypher_build_include_dir ${build_include_dir}/cypher)
|
|||||||
FILE(MAKE_DIRECTORY ${cypher_build_include_dir})
|
FILE(MAKE_DIRECTORY ${cypher_build_include_dir})
|
||||||
FILE(RENAME ${CMAKE_BINARY_DIR}/cypher.h ${cypher_build_include_dir}/cypher.h)
|
FILE(RENAME ${CMAKE_BINARY_DIR}/cypher.h ${cypher_build_include_dir}/cypher.h)
|
||||||
|
|
||||||
|
# prepare template and destination folders for query engine (tests)
|
||||||
# copy query_engine's templates file
|
# copy query_engine's templates file
|
||||||
FILE(COPY ${src_dir}/query_engine/template DESTINATION ${CMAKE_BINARY_DIR})
|
FILE(COPY ${src_dir}/query_engine/template DESTINATION ${CMAKE_BINARY_DIR}/tests)
|
||||||
# create destination folder for compiled queries
|
# create destination folder for compiled queries
|
||||||
FILE(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/compiled/cpu)
|
FILE(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/tests/compiled/cpu)
|
||||||
|
|
||||||
# TODO: filter header files, all files don't need to be copied
|
# TODO: filter header files, all files don't need to be copied
|
||||||
# they are all copied because query engine needs header files during
|
# they are all copied because query engine needs header files during
|
||||||
# query compilation
|
# query compilation
|
||||||
|
# TODO: make a function (REMOVE copy pasted part)
|
||||||
SUBDIRLIST(source_folders ${src_dir})
|
SUBDIRLIST(source_folders ${src_dir})
|
||||||
foreach(source_folder ${source_folders})
|
foreach(source_folder ${source_folders})
|
||||||
file(COPY ${src_dir}/${source_folder} DESTINATION ${build_include_dir})
|
file(COPY ${src_dir}/${source_folder} DESTINATION ${build_include_dir})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
SUBDIRLIST(source_folders ${src_dir})
|
||||||
|
foreach(source_folder ${source_folders})
|
||||||
|
file(COPY ${src_dir}/${source_folder} DESTINATION ${test_src_dir})
|
||||||
|
endforeach()
|
||||||
|
SUBDIRLIST(source_folders ${include_dir})
|
||||||
|
foreach(source_foler ${source_folders})
|
||||||
|
file(COPY ${include_dir}/${source_folder} DESTINATION ${test_include_dir})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
# compiler options
|
# compiler options
|
||||||
SET(COMPILE_OPTIONS "-O2 -Wall -Werror -fmessage-length=0")
|
SET(COMPILE_OPTIONS "-O2 -Wall -Werror -fmessage-length=0")
|
||||||
@ -199,6 +212,7 @@ set(memgraph_src_files
|
|||||||
${src_dir}/storage/locking/record_lock.cpp
|
${src_dir}/storage/locking/record_lock.cpp
|
||||||
${src_dir}/storage/vertex_accessor.cpp
|
${src_dir}/storage/vertex_accessor.cpp
|
||||||
${src_dir}/transactions/transaction.cpp
|
${src_dir}/transactions/transaction.cpp
|
||||||
|
${src_dir}/template_engine/engine.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# hard coded implementation of queries
|
# hard coded implementation of queries
|
||||||
|
3
release/beta.sh
Executable file
3
release/beta.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "TODO: build beta version of Memgraph"
|
@ -1,36 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <ostream>
|
|
||||||
#include "utils/total_ordering.hpp"
|
|
||||||
|
|
||||||
class EdgeType : public TotalOrdering<EdgeType>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
EdgeType() {}
|
|
||||||
EdgeType(const std::string& id) : id(id) {}
|
|
||||||
EdgeType(std::string&& id) : id(std::move(id)) {}
|
|
||||||
|
|
||||||
friend bool operator<(const EdgeType& lhs, const EdgeType& rhs)
|
|
||||||
{
|
|
||||||
return lhs.id < rhs.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
friend bool operator==(const EdgeType& lhs, const EdgeType& rhs)
|
|
||||||
{
|
|
||||||
return lhs.id == rhs.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
friend std::ostream& operator<<(std::ostream& stream, const EdgeType& type)
|
|
||||||
{
|
|
||||||
return stream << type.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
operator const std::string&() const
|
|
||||||
{
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string id;
|
|
||||||
};
|
|
@ -12,22 +12,22 @@ public:
|
|||||||
CreExp() = default;
|
CreExp() = default;
|
||||||
CreExp(T cre, T exp) : cre_(cre), exp_(exp) {}
|
CreExp(T cre, T exp) : cre_(cre), exp_(exp) {}
|
||||||
|
|
||||||
T cre(std::memory_order order = std::memory_order_acquire) const
|
T cre(std::memory_order order = std::memory_order_seq_cst) const
|
||||||
{
|
{
|
||||||
return cre_.load(order);
|
return cre_.load(order);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cre(T value, std::memory_order order = std::memory_order_release)
|
void cre(T value, std::memory_order order = std::memory_order_seq_cst)
|
||||||
{
|
{
|
||||||
cre_.store(value, order);
|
cre_.store(value, order);
|
||||||
}
|
}
|
||||||
|
|
||||||
T exp(std::memory_order order = std::memory_order_acquire) const
|
T exp(std::memory_order order = std::memory_order_seq_cst) const
|
||||||
{
|
{
|
||||||
return exp_.load(order);
|
return exp_.load(order);
|
||||||
}
|
}
|
||||||
|
|
||||||
void exp(T value, std::memory_order order = std::memory_order_release)
|
void exp(T value, std::memory_order order = std::memory_order_seq_cst)
|
||||||
{
|
{
|
||||||
exp_.store(value, order);
|
exp_.store(value, order);
|
||||||
}
|
}
|
||||||
|
@ -54,17 +54,17 @@ private:
|
|||||||
uint8_t bits;
|
uint8_t bits;
|
||||||
};
|
};
|
||||||
|
|
||||||
Value load(std::memory_order order = std::memory_order_acquire)
|
Value load(std::memory_order order = std::memory_order_seq_cst)
|
||||||
{
|
{
|
||||||
return Value { bits.load(order) };
|
return Value { bits.load(order) };
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_committed(std::memory_order order = std::memory_order_release)
|
void set_committed(std::memory_order order = std::memory_order_seq_cst)
|
||||||
{
|
{
|
||||||
bits.fetch_or(COMMITTED, order);
|
bits.fetch_or(COMMITTED, order);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_aborted(std::memory_order order = std::memory_order_release)
|
void set_aborted(std::memory_order order = std::memory_order_seq_cst)
|
||||||
{
|
{
|
||||||
bits.fetch_or(ABORTED, order);
|
bits.fetch_or(ABORTED, order);
|
||||||
}
|
}
|
||||||
@ -97,7 +97,7 @@ public:
|
|||||||
Exp::Value exp;
|
Exp::Value exp;
|
||||||
};
|
};
|
||||||
|
|
||||||
HintBits load(std::memory_order order = std::memory_order_acquire)
|
HintBits load(std::memory_order order = std::memory_order_seq_cst)
|
||||||
{
|
{
|
||||||
return HintBits { bits.load(order) };
|
return HintBits { bits.load(order) };
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ public:
|
|||||||
|
|
||||||
T* find(const tx::Transaction& t)
|
T* find(const tx::Transaction& t)
|
||||||
{
|
{
|
||||||
auto r = head.load(std::memory_order_acquire);
|
auto r = head.load(std::memory_order_seq_cst);
|
||||||
|
|
||||||
// nullptr
|
// nullptr
|
||||||
// |
|
// |
|
||||||
@ -78,7 +78,7 @@ public:
|
|||||||
// [VerList] ----+ version, or you reach the end of the list
|
// [VerList] ----+ version, or you reach the end of the list
|
||||||
//
|
//
|
||||||
while(r != nullptr && !r->visible(t))
|
while(r != nullptr && !r->visible(t))
|
||||||
r = r->next(std::memory_order_acquire);
|
r = r->next(std::memory_order_seq_cst);
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
@ -94,7 +94,7 @@ public:
|
|||||||
// mark the record as created by the transaction t
|
// mark the record as created by the transaction t
|
||||||
v1->mark_created(t);
|
v1->mark_created(t);
|
||||||
|
|
||||||
head.store(v1, std::memory_order_release);
|
head.store(v1, std::memory_order_seq_cst);
|
||||||
|
|
||||||
return v1;
|
return v1;
|
||||||
}
|
}
|
||||||
@ -122,8 +122,8 @@ public:
|
|||||||
updated->mark_created(t);
|
updated->mark_created(t);
|
||||||
record->mark_deleted(t);
|
record->mark_deleted(t);
|
||||||
|
|
||||||
updated->next(record, std::memory_order_release);
|
updated->next(record, std::memory_order_seq_cst);
|
||||||
head.store(updated, std::memory_order_release);
|
head.store(updated, std::memory_order_seq_cst);
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
#include "utils/string/join.hpp"
|
#include "utils/string/join.hpp"
|
||||||
|
|
||||||
|
// TODO: all libraries have to be compiled in the server compile time
|
||||||
|
// TODO: generate copile command
|
||||||
class CodeCompiler
|
class CodeCompiler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -12,10 +14,12 @@ public:
|
|||||||
auto compile_command =
|
auto compile_command =
|
||||||
utils::prints("clang++",
|
utils::prints("clang++",
|
||||||
// "-std=c++1y -O2 -DNDEBUG", // compile flags
|
// "-std=c++1y -O2 -DNDEBUG", // compile flags
|
||||||
"-std=c++1y", // compile flags
|
"-std=c++1y -DDEBUG", // compile flags
|
||||||
in_file, // input file
|
in_file, // input file
|
||||||
"-o", out_file, // ouput file
|
"-o", out_file, // ouput file
|
||||||
"-I./include", // include paths (TODO: parameter)
|
"-I./include", // include paths (TODO: parameter)
|
||||||
|
"-I./src",
|
||||||
|
"-I../../libs/fmt",
|
||||||
"-shared -fPIC" // shared library flags
|
"-shared -fPIC" // shared library flags
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -16,9 +16,7 @@ class ProgramExecutor
|
|||||||
public:
|
public:
|
||||||
auto execute(QueryProgram &program)
|
auto execute(QueryProgram &program)
|
||||||
{
|
{
|
||||||
auto result = program.code->run(db, program.stripped.arguments);
|
return program.code->run(db, program.stripped.arguments);
|
||||||
PRINT_PROPS(*result->data["n"]->data[0]);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -37,7 +37,7 @@ public:
|
|||||||
auto hash_string = std::to_string(stripped.hash);
|
auto hash_string = std::to_string(stripped.hash);
|
||||||
LOG_INFO("query_hash=" + hash_string);
|
LOG_INFO("query_hash=" + hash_string);
|
||||||
|
|
||||||
auto code_lib_iter = code_libs.find(stripped_hash);
|
auto code_lib_iter = code_libs.find(stripped.hash);
|
||||||
|
|
||||||
// code is already compiled and loaded, just return runnable
|
// code is already compiled and loaded, just return runnable
|
||||||
// instance
|
// instance
|
||||||
@ -50,7 +50,7 @@ public:
|
|||||||
// TODO load output path from config
|
// TODO load output path from config
|
||||||
auto base_path = config::Config::instance()[config::COMPILE_CPU_PATH];
|
auto base_path = config::Config::instance()[config::COMPILE_CPU_PATH];
|
||||||
auto path_cpp = base_path + hash_string + ".cpp";
|
auto path_cpp = base_path + hash_string + ".cpp";
|
||||||
code_generator.generate_cpp(query, stripped_hash, path_cpp);
|
code_generator.generate_cpp(query, stripped.hash, path_cpp);
|
||||||
|
|
||||||
// TODO compile generated code
|
// TODO compile generated code
|
||||||
auto path_so = base_path + hash_string + ".so";
|
auto path_so = base_path + hash_string + ".so";
|
||||||
@ -58,7 +58,7 @@ public:
|
|||||||
|
|
||||||
// loads dynamic lib and store it
|
// loads dynamic lib and store it
|
||||||
auto code_lib = load_code_lib(path_so);
|
auto code_lib = load_code_lib(path_so);
|
||||||
code_libs.insert({{stripped_hash, code_lib}});
|
code_libs.insert({{stripped.hash, code_lib}});
|
||||||
|
|
||||||
// return instance of runnable code (ICodeCPU)
|
// return instance of runnable code (ICodeCPU)
|
||||||
return QueryProgram(code_lib->instance(), std::move(stripped));
|
return QueryProgram(code_lib->instance(), std::move(stripped));
|
||||||
|
@ -4,6 +4,10 @@
|
|||||||
#include "query_engine/i_code_cpu.hpp"
|
#include "query_engine/i_code_cpu.hpp"
|
||||||
#include "storage/model/properties/all.hpp"
|
#include "storage/model/properties/all.hpp"
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
#include "query_engine/debug.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
// TODO generate with the template engine
|
// TODO generate with the template engine
|
||||||
// #include "storage/model/properties/jsonwriter.hpp"
|
// #include "storage/model/properties/jsonwriter.hpp"
|
||||||
|
|
||||||
|
@ -29,14 +29,16 @@ public:
|
|||||||
|
|
||||||
void visit(ast::Return& ret) override
|
void visit(ast::Return& ret) override
|
||||||
{
|
{
|
||||||
|
#ifdef DEBUG
|
||||||
|
// TODO: remove from here
|
||||||
|
code += line("PRINT_PROPS(vertex_accessor.properties());");
|
||||||
|
code += line("cout << \"LABELS:\" << endl;");
|
||||||
|
code += line("for (auto label_ref : vertex_accessor.labels()) {");
|
||||||
|
code += line("cout << label_ref.get() << endl;");
|
||||||
|
code += line("}");
|
||||||
|
#endif
|
||||||
|
|
||||||
code += line("t.commit();");
|
code += line("t.commit();");
|
||||||
// code += line("auto &properties = vertex_accessor.properties();");
|
code += line("return std::make_shared<QueryResult>();");
|
||||||
// code += line("ResultList::data_t data = {&properties};");
|
|
||||||
// code += line("auto result_data = "
|
|
||||||
// "std::make_shared<ResultList>(std::move(data));");
|
|
||||||
// code += line("QueryResult::data_t query_data = {{\"" +
|
|
||||||
// ret.return_list->value->name + "\", result_data}};");
|
|
||||||
// code += line("return std::make_shared<QueryResult>"
|
|
||||||
// "(std::move(query_data));");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,22 +1,23 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <typeinfo>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <typeinfo>
|
||||||
|
|
||||||
#include "cypher/visitor/traverser.hpp"
|
#include "cypher/visitor/traverser.hpp"
|
||||||
#include "query_engine/util.hpp"
|
#include "query_engine/util.hpp"
|
||||||
|
|
||||||
|
|
||||||
class WriteTraverser : public Traverser
|
class WriteTraverser : public Traverser
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
uint32_t index{0};
|
uint32_t index{0};
|
||||||
|
std::vector<std::string> labels;
|
||||||
|
bool collecting_labels{false};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::string code;
|
std::string code;
|
||||||
|
|
||||||
void visit(ast::Create& create) override
|
void visit(ast::Create &create) override
|
||||||
{
|
{
|
||||||
code += line("auto& t = db.tx_engine.begin();");
|
code += line("auto& t = db.tx_engine.begin();");
|
||||||
code += line("auto vertex_accessor = db.graph.vertices.insert(t);");
|
code += line("auto vertex_accessor = db.graph.vertices.insert(t);");
|
||||||
@ -24,7 +25,31 @@ public:
|
|||||||
Traverser::visit(create);
|
Traverser::visit(create);
|
||||||
};
|
};
|
||||||
|
|
||||||
void visit(ast::Property& property) override
|
void visit(ast::LabelList &label_list) override
|
||||||
|
{
|
||||||
|
// TODO: dummy approach -> discussion
|
||||||
|
if (!collecting_labels) collecting_labels = true;
|
||||||
|
|
||||||
|
Traverser::visit(label_list);
|
||||||
|
|
||||||
|
if (collecting_labels) {
|
||||||
|
for (auto label : labels) {
|
||||||
|
code += line("auto &" + label +
|
||||||
|
" = db.graph.label_store.find_or_create(\"" +
|
||||||
|
label + "\");");
|
||||||
|
code += line("vertex_accessor.add_label(" + label + ");");
|
||||||
|
}
|
||||||
|
labels.clear();
|
||||||
|
collecting_labels = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void visit(ast::Identifier &identifier) override
|
||||||
|
{
|
||||||
|
if (collecting_labels) labels.push_back(identifier.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void visit(ast::Property &property) override
|
||||||
{
|
{
|
||||||
auto key = property.idn->name;
|
auto key = property.idn->name;
|
||||||
|
|
||||||
@ -35,16 +60,14 @@ public:
|
|||||||
++index;
|
++index;
|
||||||
}
|
}
|
||||||
|
|
||||||
void visit(ast::Return& ret) override
|
void visit(ast::Return &ret) override
|
||||||
{
|
{
|
||||||
|
#ifdef DEBUG
|
||||||
|
// TODO: remove from here
|
||||||
|
code += line("PRINT_PROPS(vertex_accessor.properties());");
|
||||||
|
#endif
|
||||||
|
|
||||||
code += line("t.commit();");
|
code += line("t.commit();");
|
||||||
// code += line("auto &properties = vertex_accessor.properties();");
|
code += line("return std::make_shared<QueryResult>();");
|
||||||
// code += line("ResultList::data_t data = {&properties};");
|
|
||||||
// code += line("auto result_data = "
|
|
||||||
// "std::make_shared<ResultList>(std::move(data));");
|
|
||||||
// code += line("QueryResult::data_t query_data = {{\"" +
|
|
||||||
// ret.return_list->value->name + "\", result_data}};");
|
|
||||||
// code += line("return std::make_shared<QueryResult>"
|
|
||||||
// "(std::move(query_data));");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -49,9 +49,19 @@ foreach(test ${concurrency_test_names})
|
|||||||
set_property(TARGET ${test_name} PROPERTY CXX_STANDARD 14)
|
set_property(TARGET ${test_name} PROPERTY CXX_STANDARD 14)
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
## INTEGRATION TESTS
|
||||||
|
|
||||||
# build integration tests
|
# build integration tests
|
||||||
# message(STATUS ${memgraph_src_files})
|
# message(STATUS ${memgraph_src_files})
|
||||||
add_executable(integration_queries integration/queries.cpp ${memgraph_src_files})
|
add_executable(integration_queries integration/queries.cpp ${memgraph_src_files})
|
||||||
target_link_libraries(integration_queries ${fmt_static_lib})
|
target_link_libraries(integration_queries ${fmt_static_lib})
|
||||||
add_test(NAME integration_queries COMMAND integration_queries)
|
add_test(NAME integration_queries COMMAND integration_queries)
|
||||||
set_property(TARGET integration_queries PROPERTY CXX_STANDARD 14)
|
set_property(TARGET integration_queries PROPERTY CXX_STANDARD 14)
|
||||||
|
|
||||||
|
## MANUAL TESTS
|
||||||
|
|
||||||
|
add_executable(manual_query_engine manual/query_engine.cpp ${memgraph_src_files})
|
||||||
|
target_link_libraries(manual_query_engine ${fmt_static_lib})
|
||||||
|
target_link_libraries(manual_query_engine dl)
|
||||||
|
target_link_libraries(manual_query_engine cypher_lib)
|
||||||
|
set_property(TARGET manual_query_engine PROPERTY CXX_STANDARD 14)
|
||||||
|
@ -1 +1 @@
|
|||||||
CREATE (n:ACCOUNT {id: 2322, name: "TEST", country: "Croatia", created_at: 2352352}) RETURN n
|
CREATE (n:ACCOUNT:PERSON {id: 2322, name: "TEST", country: "Croatia", created_at: 2352352}) RETURN n
|
||||||
|
27
tests/manual/query_engine.cpp
Normal file
27
tests/manual/query_engine.cpp
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#define DEBUG 1
|
||||||
|
|
||||||
|
#include "utils/command_line/arguments.hpp"
|
||||||
|
#include "cypher/common.hpp"
|
||||||
|
#include "query_engine/query_engine.hpp"
|
||||||
|
#include "utils/time/timer.hpp"
|
||||||
|
|
||||||
|
using std::cout;
|
||||||
|
using std::endl;
|
||||||
|
using std::cin;
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
QueryEngine engine;
|
||||||
|
|
||||||
|
std::string command;
|
||||||
|
cout << "-- Memgraph query engine --" << endl;
|
||||||
|
do {
|
||||||
|
cout << "> ";
|
||||||
|
std::getline(cin, command);
|
||||||
|
engine.execute(command);
|
||||||
|
} while (command != "quit");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user