Major properties and storage refactoring in progress. UNSTABLE STATE
This commit is contained in:
parent
ee523d5080
commit
9adf5699d9
@ -52,9 +52,9 @@ FILE(COPY ${include_dir}/transactions/engine.hpp DESTINATION ${build_include_dir
|
|||||||
FILE(COPY ${include_dir}/transactions/transaction_store.hpp DESTINATION ${build_include_dir}/transactions)
|
FILE(COPY ${include_dir}/transactions/transaction_store.hpp DESTINATION ${build_include_dir}/transactions)
|
||||||
FILE(COPY ${include_dir}/transactions/transaction_read.hpp DESTINATION ${build_include_dir}/transactions)
|
FILE(COPY ${include_dir}/transactions/transaction_read.hpp DESTINATION ${build_include_dir}/transactions)
|
||||||
|
|
||||||
FILE(COPY ${include_dir}/storage/typed_value.hpp DESTINATION ${build_include_dir}/storage/model)
|
FILE(COPY ${include_dir}/storage/typed_value.hpp DESTINATION ${build_include_dir}/storage)
|
||||||
FILE(COPY ${include_dir}/storage/typed_value_store.hpp DESTINATION ${build_include_dir}/storage/model)
|
FILE(COPY ${include_dir}/storage/typed_value_store.hpp DESTINATION ${build_include_dir}/storage)
|
||||||
FILE(COPY ${include_dir}/storage/typed_value_utils.hpp DESTINATION ${build_include_dir}/storage/model)
|
FILE(COPY ${include_dir}/storage/typed_value_utils.hpp DESTINATION ${build_include_dir}/storage)
|
||||||
|
|
||||||
FILE(COPY ${include_dir}/storage/garbage/delete_sensitive.hpp DESTINATION ${build_include_dir}/storage/garbage)
|
FILE(COPY ${include_dir}/storage/garbage/delete_sensitive.hpp DESTINATION ${build_include_dir}/storage/garbage)
|
||||||
FILE(COPY ${include_dir}/storage/garbage/garbage.hpp DESTINATION ${build_include_dir}/storage/garbage)
|
FILE(COPY ${include_dir}/storage/garbage/garbage.hpp DESTINATION ${build_include_dir}/storage/garbage)
|
||||||
|
@ -6,25 +6,14 @@
|
|||||||
#include "storage/edge_accessor.hpp"
|
#include "storage/edge_accessor.hpp"
|
||||||
#include "storage/vertex_accessor.hpp"
|
#include "storage/vertex_accessor.hpp"
|
||||||
|
|
||||||
#include "storage/edge_type/edge_type.hpp"
|
#include "storage/typed_value.hpp"
|
||||||
#include "storage/label/label.hpp"
|
|
||||||
#include "storage/model/properties/all.hpp"
|
|
||||||
#include "storage/model/properties/properties.hpp"
|
|
||||||
#include "storage/vertex_record.hpp"
|
|
||||||
|
|
||||||
namespace bolt
|
namespace bolt {
|
||||||
{
|
|
||||||
|
|
||||||
template <class Stream>
|
template<class Stream>
|
||||||
class BoltSerializer
|
class BoltSerializer {
|
||||||
{
|
|
||||||
friend class Property;
|
|
||||||
|
|
||||||
// TODO: here shoud be friend but it doesn't work
|
public:
|
||||||
// template <class Handler>
|
|
||||||
// friend void accept(const Property &property, Handler &h);
|
|
||||||
|
|
||||||
public:
|
|
||||||
BoltSerializer(Stream &stream) : encoder(stream) {}
|
BoltSerializer(Stream &stream) : encoder(stream) {}
|
||||||
|
|
||||||
/** Serializes the vertex accessor into the packstream format
|
/** Serializes the vertex accessor into the packstream format
|
||||||
@ -36,38 +25,9 @@ public:
|
|||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void write(const VertexAccessor &vertex)
|
void write(const VertexAccessor &vertex);
|
||||||
{
|
|
||||||
// write signatures for the node struct and node data type
|
|
||||||
encoder.write_struct_header(3);
|
|
||||||
encoder.write(underlying_cast(pack::Node));
|
|
||||||
|
|
||||||
// IMPORTANT: here we write a hardcorded 0 because we don't
|
/** Serializes the edge accessor into the packstream format
|
||||||
// use internal IDs, but need to give something to Bolt
|
|
||||||
// note that OpenCypther has no id(x) function, so the client
|
|
||||||
// should not be able to do anything with this value anyway
|
|
||||||
encoder.write_integer(0);
|
|
||||||
|
|
||||||
// write the list of labels
|
|
||||||
auto labels = vertex.labels();
|
|
||||||
|
|
||||||
encoder.write_list_header(labels.size());
|
|
||||||
|
|
||||||
for (auto &label : labels)
|
|
||||||
encoder.write_string(label.get());
|
|
||||||
|
|
||||||
// write the property map
|
|
||||||
auto props = vertex.properties();
|
|
||||||
|
|
||||||
encoder.write_map_header(props.size());
|
|
||||||
|
|
||||||
for (auto &prop : props) {
|
|
||||||
write(prop.key.family_name());
|
|
||||||
prop.accept(*this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Serializes the vertex accessor into the packstream format
|
|
||||||
*
|
*
|
||||||
* struct[size = 5] Edge [signature = 0x52] {
|
* struct[size = 5] Edge [signature = 0x52] {
|
||||||
* Integer edge_id; // IMPORTANT: always 0 since we don't do IDs
|
* Integer edge_id; // IMPORTANT: always 0 since we don't do IDs
|
||||||
@ -80,57 +40,18 @@ public:
|
|||||||
*/
|
*/
|
||||||
void write(const EdgeAccessor &edge);
|
void write(const EdgeAccessor &edge);
|
||||||
|
|
||||||
void write_null() { encoder.write_null(); }
|
// TODO document
|
||||||
|
void write_failure(const std::map<std::string, std::string> &data);
|
||||||
|
|
||||||
void write(const Null &) { encoder.write_null(); }
|
/**
|
||||||
|
* Writes a TypedValue (typically a property value in the edge or vertex).
|
||||||
|
*
|
||||||
|
* @param value The value to write.
|
||||||
|
*/
|
||||||
|
void write(const TypedValue& value);
|
||||||
|
|
||||||
void write(const Bool &prop) { encoder.write_bool(prop.value()); }
|
protected:
|
||||||
|
|
||||||
void write(const Float &prop) { encoder.write_double(prop.value()); }
|
|
||||||
|
|
||||||
void write(const Double &prop) { encoder.write_double(prop.value()); }
|
|
||||||
|
|
||||||
void write(const Int32 &prop) { encoder.write_integer(prop.value()); }
|
|
||||||
|
|
||||||
void write(const Int64 &prop) { encoder.write_integer(prop.value()); }
|
|
||||||
|
|
||||||
void write(const String &value) { encoder.write_string(value.value()); }
|
|
||||||
|
|
||||||
// Not yet implemented
|
|
||||||
void write(const ArrayBool &) { assert(false); }
|
|
||||||
|
|
||||||
// Not yet implemented
|
|
||||||
void write(const ArrayInt32 &) { assert(false); }
|
|
||||||
|
|
||||||
// Not yet implemented
|
|
||||||
void write(const ArrayInt64 &) { assert(false); }
|
|
||||||
|
|
||||||
// Not yet implemented
|
|
||||||
void write(const ArrayFloat &) { assert(false); }
|
|
||||||
|
|
||||||
// Not yet implemented
|
|
||||||
void write(const ArrayDouble &) { assert(false); }
|
|
||||||
|
|
||||||
// Not yet implemented
|
|
||||||
void write(const ArrayString &) { assert(false); }
|
|
||||||
|
|
||||||
void write_failure(const std::map<std::string, std::string> &data)
|
|
||||||
{
|
|
||||||
encoder.message_failure();
|
|
||||||
encoder.write_map_header(data.size());
|
|
||||||
for (auto const &kv : data) {
|
|
||||||
write(kv.first);
|
|
||||||
write(kv.second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class T>
|
|
||||||
void handle(const T &prop)
|
|
||||||
{
|
|
||||||
write(prop);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
Stream &encoder;
|
Stream &encoder;
|
||||||
};
|
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -7,83 +7,73 @@
|
|||||||
|
|
||||||
#include "logging/default.hpp"
|
#include "logging/default.hpp"
|
||||||
|
|
||||||
namespace bolt
|
namespace bolt {
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* compiled queries have to use this class in order to return results
|
* compiled queries have to use this class in order to return results
|
||||||
* query code should not know about bolt protocol
|
* query code should not know about bolt protocol
|
||||||
*/
|
*/
|
||||||
template <class Socket>
|
template<class Socket>
|
||||||
class RecordStream
|
class RecordStream {
|
||||||
{
|
public:
|
||||||
public:
|
RecordStream(Socket &socket) : socket(socket) {
|
||||||
RecordStream(Socket &socket) : socket(socket)
|
logger = logging::log->logger("Record Stream");
|
||||||
{
|
|
||||||
logger = logging::log->logger("Record Stream");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~RecordStream() = default;
|
~RecordStream() = default;
|
||||||
|
|
||||||
// TODO: create apstract methods that are not bolt specific ---------------
|
// TODO: create apstract methods that are not bolt specific ---------------
|
||||||
void write_success()
|
void write_success() {
|
||||||
{
|
logger.trace("write_success");
|
||||||
logger.trace("write_success");
|
bolt_encoder.message_success();
|
||||||
bolt_encoder.message_success();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_success_empty()
|
void write_success_empty() {
|
||||||
{
|
logger.trace("write_success_empty");
|
||||||
logger.trace("write_success_empty");
|
bolt_encoder.message_success_empty();
|
||||||
bolt_encoder.message_success_empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_ignored()
|
void write_ignored() {
|
||||||
{
|
logger.trace("write_ignored");
|
||||||
logger.trace("write_ignored");
|
bolt_encoder.message_ignored();
|
||||||
bolt_encoder.message_ignored();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_empty_fields()
|
void write_empty_fields() {
|
||||||
{
|
bolt_encoder.message_success();
|
||||||
bolt_encoder.message_success();
|
bolt_encoder.write_map_header(1);
|
||||||
bolt_encoder.write_map_header(1);
|
bolt_encoder.write_string("fields");
|
||||||
bolt_encoder.write_string("fields");
|
write_list_header(0);
|
||||||
write_list_header(0);
|
chunk();
|
||||||
chunk();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_fields(const std::vector<std::string> &fields)
|
void write_fields(const std::vector<std::string> &fields) {
|
||||||
{
|
// TODO: that should be one level below?
|
||||||
// TODO: that should be one level below?
|
bolt_encoder.message_success();
|
||||||
bolt_encoder.message_success();
|
|
||||||
|
|
||||||
bolt_encoder.write_map_header(1);
|
bolt_encoder.write_map_header(1);
|
||||||
bolt_encoder.write_string("fields");
|
bolt_encoder.write_string("fields");
|
||||||
write_list_header(fields.size());
|
write_list_header(fields.size());
|
||||||
|
|
||||||
for (auto &name : fields) {
|
for (auto &name : fields) {
|
||||||
bolt_encoder.write_string(name);
|
bolt_encoder.write_string(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
chunk();
|
chunk();
|
||||||
send();
|
send();
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_field(const std::string &field)
|
void write_field(const std::string &field) {
|
||||||
{
|
bolt_encoder.message_success();
|
||||||
bolt_encoder.message_success();
|
bolt_encoder.write_map_header(1);
|
||||||
bolt_encoder.write_map_header(1);
|
bolt_encoder.write_string("fields");
|
||||||
bolt_encoder.write_string("fields");
|
write_list_header(1);
|
||||||
write_list_header(1);
|
bolt_encoder.write_string(field);
|
||||||
bolt_encoder.write_string(field);
|
chunk();
|
||||||
chunk();
|
send();
|
||||||
send();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_list_header(size_t size)
|
void write_list_header(size_t size) {
|
||||||
{
|
bolt_encoder.write_list_header(size);
|
||||||
bolt_encoder.write_list_header(size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_record() { bolt_encoder.message_record(); }
|
void write_record() { bolt_encoder.message_record(); }
|
||||||
@ -92,94 +82,73 @@ public:
|
|||||||
// TODO: write whole implementation (currently, only type is supported)
|
// TODO: write whole implementation (currently, only type is supported)
|
||||||
// { "stats": { "nodes created": 1, "properties set": 1},
|
// { "stats": { "nodes created": 1, "properties set": 1},
|
||||||
// "type": "r" | "rw" | ...
|
// "type": "r" | "rw" | ...
|
||||||
void write_meta(const std::string &type)
|
void write_meta(const std::string &type) {
|
||||||
{
|
bolt_encoder.message_success();
|
||||||
bolt_encoder.message_success();
|
bolt_encoder.write_map_header(1);
|
||||||
bolt_encoder.write_map_header(1);
|
bolt_encoder.write_string("type");
|
||||||
bolt_encoder.write_string("type");
|
bolt_encoder.write_string(type);
|
||||||
bolt_encoder.write_string(type);
|
chunk();
|
||||||
chunk();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_failure(const std::map<std::string, std::string> &data)
|
void write_failure(const std::map<std::string, std::string> &data) {
|
||||||
{
|
serializer.write_failure(data);
|
||||||
serializer.write_failure(data);
|
chunk();
|
||||||
chunk();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_count(const size_t count)
|
void write_count(const size_t count) {
|
||||||
{
|
write_record();
|
||||||
write_record();
|
write_list_header(1);
|
||||||
write_list_header(1);
|
write(count);
|
||||||
write(Int64(count));
|
chunk();
|
||||||
chunk();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void write(const VertexAccessor &vertex) { serializer.write(vertex); }
|
void write(const VertexAccessor &vertex) { serializer.write(vertex); }
|
||||||
void write_vertex_record(const VertexAccessor& va)
|
|
||||||
{
|
void write_vertex_record(const VertexAccessor &va) {
|
||||||
write_record();
|
write_record();
|
||||||
write_list_header(1);
|
write_list_header(1);
|
||||||
write(va);
|
write(va);
|
||||||
chunk();
|
chunk();
|
||||||
}
|
}
|
||||||
|
|
||||||
void write(const EdgeAccessor &edge) { serializer.write(edge); }
|
void write(const EdgeAccessor &edge) { serializer.write(edge); }
|
||||||
void write_edge_record(const EdgeAccessor& ea)
|
|
||||||
{
|
void write_edge_record(const EdgeAccessor &ea) {
|
||||||
write_record();
|
write_record();
|
||||||
write_list_header(1);
|
write_list_header(1);
|
||||||
write(ea);
|
write(ea);
|
||||||
chunk();
|
chunk();
|
||||||
}
|
}
|
||||||
|
|
||||||
void write(const StoredProperty<TypeGroupEdge> &prop)
|
void write(const TypedValue& value) {
|
||||||
{
|
serializer.write(value);
|
||||||
prop.accept(serializer);
|
|
||||||
}
|
}
|
||||||
void write(const StoredProperty<TypeGroupVertex> &prop)
|
|
||||||
{
|
|
||||||
prop.accept(serializer);
|
|
||||||
}
|
|
||||||
void write(const Null &prop) { serializer.write(prop); }
|
|
||||||
void write(const Bool &prop) { serializer.write(prop); }
|
|
||||||
void write(const Float &prop) { serializer.write(prop); }
|
|
||||||
void write(const Int32 &prop) { serializer.write(prop); }
|
|
||||||
void write(const Int64 &prop) { serializer.write(prop); }
|
|
||||||
void write(const Double &prop) { serializer.write(prop); }
|
|
||||||
void write(const String &prop) { serializer.write(prop); }
|
|
||||||
void write(const ArrayBool &prop) { serializer.write(prop); }
|
|
||||||
void write(const ArrayInt32 &prop) { serializer.write(prop); }
|
|
||||||
void write(const ArrayInt64 &prop) { serializer.write(prop); }
|
|
||||||
void write(const ArrayFloat &prop) { serializer.write(prop); }
|
|
||||||
void write(const ArrayDouble &prop) { serializer.write(prop); }
|
|
||||||
void write(const ArrayString &prop) { serializer.write(prop); }
|
|
||||||
|
|
||||||
void send() { chunked_buffer.flush(); }
|
void send() { chunked_buffer.flush(); }
|
||||||
|
|
||||||
void chunk() { chunked_encoder.write_chunk(); }
|
void chunk() { chunked_encoder.write_chunk(); }
|
||||||
|
|
||||||
void _write_test()
|
// TODO WTF is this test doing here?
|
||||||
{
|
void _write_test() {
|
||||||
logger.trace("write_test");
|
logger.trace("write_test");
|
||||||
|
|
||||||
write_fields({{"name"}});
|
write_fields({{"name"}});
|
||||||
|
|
||||||
write_record();
|
write_record();
|
||||||
write_list_header(1);
|
write_list_header(1);
|
||||||
write(String("max"));
|
bolt_encoder.write("max");
|
||||||
|
|
||||||
write_record();
|
write_record();
|
||||||
write_list_header(1);
|
write_list_header(1);
|
||||||
write(String("paul"));
|
bolt_encoder.write("paul");
|
||||||
|
|
||||||
write_success_empty();
|
write_success_empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Logger logger;
|
Logger logger;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using socket_t = SocketStream<Socket>;
|
using socket_t = SocketStream<Socket>;
|
||||||
using buffer_t = ChunkedBuffer<socket_t>;
|
using buffer_t = ChunkedBuffer<socket_t>;
|
||||||
using chunked_encoder_t = ChunkedEncoder<buffer_t>;
|
using chunked_encoder_t = ChunkedEncoder<buffer_t>;
|
||||||
@ -191,5 +160,5 @@ private:
|
|||||||
chunked_encoder_t chunked_encoder{chunked_buffer};
|
chunked_encoder_t chunked_encoder{chunked_buffer};
|
||||||
bolt_encoder_t bolt_encoder{chunked_encoder};
|
bolt_encoder_t bolt_encoder{chunked_encoder};
|
||||||
bolt_serializer_t serializer{bolt_encoder};
|
bolt_serializer_t serializer{bolt_encoder};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -12,12 +12,10 @@
|
|||||||
|
|
||||||
#include "logging/default.hpp"
|
#include "logging/default.hpp"
|
||||||
|
|
||||||
namespace bolt
|
namespace bolt {
|
||||||
{
|
|
||||||
|
|
||||||
class Session : public io::tcp::Stream<io::Socket>
|
class Session : public io::tcp::Stream<io::Socket> {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
using Decoder = BoltDecoder;
|
using Decoder = BoltDecoder;
|
||||||
using OutputStream = communication::OutputStream;
|
using OutputStream = communication::OutputStream;
|
||||||
|
|
||||||
@ -26,10 +24,12 @@ public:
|
|||||||
bool alive() const;
|
bool alive() const;
|
||||||
|
|
||||||
void execute(const byte *data, size_t len);
|
void execute(const byte *data, size_t len);
|
||||||
|
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
Bolt &bolt;
|
Bolt &bolt;
|
||||||
Db &active_db();
|
|
||||||
|
GraphDb &active_db();
|
||||||
|
|
||||||
Decoder decoder;
|
Decoder decoder;
|
||||||
OutputStream output_stream{socket};
|
OutputStream output_stream{socket};
|
||||||
@ -37,7 +37,7 @@ public:
|
|||||||
bool connected{false};
|
bool connected{false};
|
||||||
State *state;
|
State *state;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Logger logger;
|
Logger logger;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include "transactions/engine.hpp"
|
#include "transactions/engine.hpp"
|
||||||
#include "mvcc/version_list.hpp"
|
#include "mvcc/version_list.hpp"
|
||||||
#include "utils/pass_key.hpp"
|
#include "utils/pass_key.hpp"
|
||||||
|
#include "data_structures/concurrent/concurrent_set.hpp"
|
||||||
#include "storage/unique_object_store.hpp"
|
#include "storage/unique_object_store.hpp"
|
||||||
|
|
||||||
// forward declaring Edge and Vertex because they use
|
// forward declaring Edge and Vertex because they use
|
||||||
@ -26,9 +27,9 @@ class GraphDb {
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
// definitions for what data types are used for a Label, Property, EdgeType
|
// definitions for what data types are used for a Label, Property, EdgeType
|
||||||
using Label = uint32_t;
|
using Label = std::string*;
|
||||||
using EdgeType = uint32_t;
|
using EdgeType = std::string*;
|
||||||
using Property = uint32_t;
|
using Property = std::string*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This constructor will create a database with the name "default"
|
* This constructor will create a database with the name "default"
|
||||||
@ -83,7 +84,7 @@ public:
|
|||||||
SkipList<mvcc::VersionList<Vertex>*> vertices_;
|
SkipList<mvcc::VersionList<Vertex>*> vertices_;
|
||||||
|
|
||||||
// unique object stores
|
// unique object stores
|
||||||
UniqueObjectStore<std::string, Label> labels_;
|
ConcurrentSet<std::string> labels_;
|
||||||
UniqueObjectStore<std::string, EdgeType> edge_types_;
|
ConcurrentSet<std::string> edge_types_;
|
||||||
UniqueObjectStore<std::string, Property> properties_;
|
ConcurrentSet<std::string> properties_;
|
||||||
};
|
};
|
||||||
|
@ -3,14 +3,14 @@
|
|||||||
// Created by Florijan Stamenkovic on 03.02.17.
|
// Created by Florijan Stamenkovic on 03.02.17.
|
||||||
//
|
//
|
||||||
|
|
||||||
#pragma
|
#pragma once
|
||||||
|
|
||||||
#include "graph_db.hpp"
|
#include "graph_db.hpp"
|
||||||
#include "transactions/transaction.hpp"
|
#include "transactions/transaction.hpp"
|
||||||
|
|
||||||
|
|
||||||
class GraphDbAccessor {
|
class GraphDbAccessor {
|
||||||
GraphDbAccessor(GraphDb& db) : db_(db), transaction_(db.tx_engine.begin()) {}
|
GraphDbAccessor(GraphDb& db);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
@ -36,24 +36,48 @@ public:
|
|||||||
*/
|
*/
|
||||||
GraphDb::Label label(const std::string& label_name);
|
GraphDb::Label label(const std::string& label_name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the label name (a string) for the given label.
|
||||||
|
*
|
||||||
|
* @param label a Label.
|
||||||
|
* @return See above.
|
||||||
|
*/
|
||||||
|
std::string& label_name(const GraphDb::Label label) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains the EdgeType for it's name.
|
* Obtains the EdgeType for it's name.
|
||||||
* @return See above.
|
* @return See above.
|
||||||
*/
|
*/
|
||||||
GraphDb::EdgeType edge_type(const std::string& edge_type_name);
|
GraphDb::EdgeType edge_type(const std::string& edge_type_name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the edge type name (a string) for the given edge type.
|
||||||
|
*
|
||||||
|
* @param edge_type an EdgeType.
|
||||||
|
* @return See above.
|
||||||
|
*/
|
||||||
|
std::string& edge_type_name(const GraphDb::EdgeType edge_type) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains the Property for it's name.
|
* Obtains the Property for it's name.
|
||||||
* @return See above.
|
* @return See above.
|
||||||
*/
|
*/
|
||||||
GraphDb::Property property(const std::string& property_name);
|
GraphDb::Property property(const std::string& property_name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the property name (a string) for the given property.
|
||||||
|
*
|
||||||
|
* @param property a Property.
|
||||||
|
* @return See above.
|
||||||
|
*/
|
||||||
|
std::string& property_name(const GraphDb::Property property) const;
|
||||||
|
|
||||||
/** The current transaction */
|
/** The current transaction */
|
||||||
tx::Transaction const transaction_;
|
tx::Transaction transaction_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GraphDb& db_;
|
GraphDb& db_;
|
||||||
|
|
||||||
// for privileged access to some RecordAccessor functionality (and similar)
|
// for privileged access to some RecordAccessor functionality (and similar)
|
||||||
const PassKey<GraphDb> pass_key;
|
const PassKey<GraphDbAccessor> pass_key;
|
||||||
};
|
};
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "communication/communication.hpp"
|
#include "communication/communication.hpp"
|
||||||
#include "database/graph_db.hpp"
|
#include "database/graph_db_accessor.hpp"
|
||||||
#include "database/db_accessor.hpp"
|
|
||||||
#include "query/strip/stripped.hpp"
|
#include "query/strip/stripped.hpp"
|
||||||
|
|
||||||
template <typename Stream>
|
template<typename Stream>
|
||||||
class IPlanCPU
|
class IPlanCPU {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
virtual bool run(Db &db, plan_args_t &args, Stream &stream) = 0;
|
virtual bool run(GraphDbAccessor &db_accessor, TypedValueStore<>& args, Stream &stream) = 0;
|
||||||
virtual ~IPlanCPU() {}
|
|
||||||
|
virtual ~IPlanCPU() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Stream>
|
template<typename Stream>
|
||||||
using produce_t = IPlanCPU<Stream> *(*)();
|
using produce_t = IPlanCPU<Stream> *(*)();
|
||||||
|
|
||||||
template <typename Stream>
|
template<typename Stream>
|
||||||
using destruct_t = void (*)(IPlanCPU<Stream> *);
|
using destruct_t = void (*)(IPlanCPU<Stream> *);
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
#include "config/config.hpp"
|
#include "config/config.hpp"
|
||||||
#include "logging/default.hpp"
|
#include "logging/default.hpp"
|
||||||
#include "query/backend/cpp_old/cypher.hpp"
|
|
||||||
#include "query/dynamic_lib.hpp"
|
#include "query/dynamic_lib.hpp"
|
||||||
#include "query/frontend/cypher.hpp"
|
#include "query/frontend/cypher.hpp"
|
||||||
#include "query/plan/compiler.hpp"
|
#include "query/plan/compiler.hpp"
|
||||||
@ -99,9 +98,7 @@ private:
|
|||||||
QueryPreprocessor preprocessor;
|
QueryPreprocessor preprocessor;
|
||||||
|
|
||||||
// TODO: compile time switch between frontends and backends
|
// TODO: compile time switch between frontends and backends
|
||||||
using frontend_t = cypher::Frontend;
|
PlanGenerator<cypher::Frontend, CypherBackend<Stream>> plan_generator;
|
||||||
using backend_t = CypherBackend<Stream>;
|
|
||||||
PlanGenerator<frontend_t, backend_t> plan_generator;
|
|
||||||
|
|
||||||
PlanCompiler plan_compiler;
|
PlanCompiler plan_compiler;
|
||||||
|
|
||||||
|
@ -2,12 +2,7 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "storage/model/properties/property.hpp"
|
#include "storage/typed_value_store.hpp"
|
||||||
|
|
||||||
/*
|
|
||||||
* Query Plan Arguments Type
|
|
||||||
*/
|
|
||||||
using plan_args_t = std::vector<Property>;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* QueryStripped contains:
|
* QueryStripped contains:
|
||||||
@ -15,18 +10,17 @@ using plan_args_t = std::vector<Property>;
|
|||||||
* * plan arguments stripped from query
|
* * plan arguments stripped from query
|
||||||
* * hash of stripped query
|
* * hash of stripped query
|
||||||
*/
|
*/
|
||||||
struct QueryStripped
|
struct QueryStripped {
|
||||||
{
|
|
||||||
QueryStripped(const std::string &&query, plan_args_t &&arguments,
|
|
||||||
uint64_t hash)
|
|
||||||
: query(std::forward<const std::string>(query)),
|
|
||||||
arguments(std::forward<plan_args_t>(arguments)), hash(hash)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
QueryStripped(QueryStripped &other) = delete;
|
|
||||||
QueryStripped(QueryStripped &&other) = default;
|
|
||||||
|
|
||||||
std::string query;
|
QueryStripped(const std::string &&query, const TypedValueStore<> &&arguments, uint64_t hash)
|
||||||
plan_args_t arguments;
|
: query(std::forward<const std::string>(query)),
|
||||||
uint64_t hash;
|
arguments(std::forward<const TypedValueStore<>>(arguments)),
|
||||||
|
hash(hash) {}
|
||||||
|
|
||||||
|
QueryStripped(QueryStripped &other) = delete;
|
||||||
|
QueryStripped(QueryStripped &&other) = default;
|
||||||
|
|
||||||
|
std::string query;
|
||||||
|
TypedValueStore<> arguments;
|
||||||
|
uint64_t hash;
|
||||||
};
|
};
|
||||||
|
@ -10,136 +10,114 @@
|
|||||||
#include "logging/loggable.hpp"
|
#include "logging/loggable.hpp"
|
||||||
#include "query/language/cypher/tokenizer/cypher_lexer.hpp"
|
#include "query/language/cypher/tokenizer/cypher_lexer.hpp"
|
||||||
#include "query/strip/stripped.hpp"
|
#include "query/strip/stripped.hpp"
|
||||||
#include "storage/model/properties/all.hpp"
|
#include "storage/typed_value_store.hpp"
|
||||||
#include "utils/hashing/fnv.hpp"
|
#include "utils/hashing/fnv.hpp"
|
||||||
#include "utils/string/transform.hpp"
|
#include "utils/string/transform.hpp"
|
||||||
#include "utils/variadic/variadic.hpp"
|
#include "utils/variadic/variadic.hpp"
|
||||||
|
|
||||||
// TODO: Maybe std::move(v) is faster, but it must be cheked for validity.
|
|
||||||
template <class T, class V>
|
|
||||||
void store_query_param(plan_args_t &arguments, V &&v)
|
|
||||||
{
|
|
||||||
arguments.emplace_back(Property(T(std::move(v)), T::type));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... Ts>
|
template<typename... Ts>
|
||||||
class QueryStripper : public Loggable
|
class QueryStripper : public Loggable {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
QueryStripper(Ts &&... strip_types)
|
QueryStripper(Ts &&... strip_types)
|
||||||
: Loggable("QueryStripper"),
|
: Loggable("QueryStripper"),
|
||||||
strip_types(std::make_tuple(std::forward<Ts>(strip_types)...)),
|
strip_types(std::make_tuple(std::forward<Ts>(strip_types)...)),
|
||||||
lexer(std::make_unique<CypherLexer>())
|
lexer(std::make_unique<CypherLexer>()) {
|
||||||
{
|
}
|
||||||
}
|
|
||||||
|
|
||||||
QueryStripper(QueryStripper &other) = delete;
|
QueryStripper(QueryStripper &other) = delete;
|
||||||
|
|
||||||
QueryStripper(QueryStripper &&other)
|
QueryStripper(QueryStripper &&other)
|
||||||
: Loggable("QueryStripper"), strip_types(std::move(other.strip_types)),
|
: Loggable("QueryStripper"), strip_types(std::move(other.strip_types)),
|
||||||
lexer(std::move(other.lexer))
|
lexer(std::move(other.lexer)) {
|
||||||
{
|
}
|
||||||
}
|
|
||||||
|
|
||||||
auto strip_space(const std::string &query) { return strip(query, " "); }
|
auto strip_space(const std::string &query) { return strip(query, " "); }
|
||||||
|
|
||||||
auto strip(const std::string &query, const std::string &separator = "")
|
auto strip(const std::string &query, const std::string &separator = "") {
|
||||||
{
|
// -------------------------------------------------------------------
|
||||||
// -------------------------------------------------------------------
|
// TODO: write speed tests and then optimize, because this
|
||||||
// TODO: write speed tests and then optimize, because this
|
// function is called before every query execution !
|
||||||
// function is called before every query execution !
|
// -------------------------------------------------------------------
|
||||||
// -------------------------------------------------------------------
|
|
||||||
|
|
||||||
// TODO write this more optimal (resplace string
|
// TODO write this more optimal (resplace string
|
||||||
// concatenation with something smarter)
|
// concatenation with something smarter)
|
||||||
// TODO: in place substring replacement
|
// TODO: in place substring replacement
|
||||||
|
|
||||||
auto tokenizer = lexer->tokenize(query);
|
auto tokenizer = lexer->tokenize(query);
|
||||||
|
|
||||||
// TMP size of supported token types
|
// TMP size of supported token types
|
||||||
constexpr auto size = std::tuple_size<decltype(strip_types)>::value;
|
constexpr auto size = std::tuple_size<decltype(strip_types)>::value;
|
||||||
|
|
||||||
int counter = 0;
|
TypedValueStore<> stripped_arguments;
|
||||||
plan_args_t stripped_arguments;
|
std::string stripped_query;
|
||||||
std::string stripped_query;
|
stripped_query.reserve(query.size());
|
||||||
stripped_query.reserve(query.size());
|
|
||||||
|
|
||||||
while (auto token = tokenizer.lookup())
|
int counter = 0; // how many arguments have we processed so far
|
||||||
{
|
while (auto token = tokenizer.lookup()) {
|
||||||
if (_or(token.id, strip_types, std::make_index_sequence<size>{}))
|
if (_or(token.id, strip_types, std::make_index_sequence < size > {})) {
|
||||||
{
|
switch (token.id) {
|
||||||
auto index = counter++;
|
case TK_LONG:
|
||||||
switch (token.id)
|
stripped_arguments.set(counter, std::stoi(token.value));
|
||||||
{
|
break;
|
||||||
case TK_LONG:
|
case TK_STR:
|
||||||
store_query_param<Int64>(stripped_arguments,
|
// TODO: remove quotes view lexertl
|
||||||
std::stol(token.value));
|
token.value.erase(0, 1);
|
||||||
break;
|
token.value.erase(token.value.length() - 1, 1);
|
||||||
case TK_STR:
|
// TODO: remove
|
||||||
// TODO: remove quotes view lexertl
|
stripped_arguments.set(counter, token.value);
|
||||||
token.value.erase(0, 1);
|
break;
|
||||||
token.value.erase(token.value.length() - 1, 1);
|
case TK_BOOL: {
|
||||||
// TODO: remove
|
bool value = token.value[0] == 'T' || token.value[0] == 't';
|
||||||
store_query_param<String>(stripped_arguments, token.value);
|
stripped_arguments.set(counter, value);
|
||||||
break;
|
break;
|
||||||
case TK_BOOL:
|
}
|
||||||
{
|
case TK_FLOAT:
|
||||||
bool value = token.value[0] == 'T' || token.value[0] == 't';
|
stripped_arguments.set(counter, std::stof(token.value));
|
||||||
store_query_param<Bool>(stripped_arguments, value);
|
break;
|
||||||
break;
|
default:
|
||||||
}
|
// TODO: other properties
|
||||||
case TK_FLOAT:
|
assert(false);
|
||||||
store_query_param<Float>(stripped_arguments,
|
|
||||||
std::stof(token.value));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// TODO: other properties
|
|
||||||
assert(false);
|
|
||||||
}
|
|
||||||
stripped_query += std::to_string(index) + separator;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// if token is keyword then lowercase because query hash
|
|
||||||
// should be the same
|
|
||||||
// TODO: probably we shoud do the lowercase before
|
|
||||||
// or during the tokenization (SPEED TESTS)
|
|
||||||
if (token.id == TK_OR || token.id == TK_AND ||
|
|
||||||
token.id == TK_NOT || token.id == TK_WITH ||
|
|
||||||
token.id == TK_SET || token.id == TK_CREATE ||
|
|
||||||
token.id == TK_MERGE || token.id == TK_MATCH ||
|
|
||||||
token.id == TK_DELETE || token.id == TK_DETACH ||
|
|
||||||
token.id == TK_WHERE || token.id == TK_RETURN ||
|
|
||||||
token.id == TK_DISTINCT || token.id == TK_COUNT ||
|
|
||||||
token.id == TK_LABELS)
|
|
||||||
{
|
|
||||||
std::transform(token.value.begin(), token.value.end(),
|
|
||||||
token.value.begin(), ::tolower);
|
|
||||||
}
|
|
||||||
stripped_query += token.value + separator;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
stripped_query += std::to_string(counter++) + separator;
|
||||||
// TODO: hash function should be a template parameter
|
} else {
|
||||||
auto hash = fnv(stripped_query);
|
// if token is keyword then lowercase because query hash
|
||||||
return QueryStripped(std::move(stripped_query),
|
// should be the same
|
||||||
std::move(stripped_arguments), hash);
|
// TODO: probably we shoud do the lowercase before
|
||||||
|
// or during the tokenization (SPEED TESTS)
|
||||||
|
if (token.id == TK_OR || token.id == TK_AND ||
|
||||||
|
token.id == TK_NOT || token.id == TK_WITH ||
|
||||||
|
token.id == TK_SET || token.id == TK_CREATE ||
|
||||||
|
token.id == TK_MERGE || token.id == TK_MATCH ||
|
||||||
|
token.id == TK_DELETE || token.id == TK_DETACH ||
|
||||||
|
token.id == TK_WHERE || token.id == TK_RETURN ||
|
||||||
|
token.id == TK_DISTINCT || token.id == TK_COUNT ||
|
||||||
|
token.id == TK_LABELS) {
|
||||||
|
std::transform(token.value.begin(), token.value.end(),
|
||||||
|
token.value.begin(), ::tolower);
|
||||||
|
}
|
||||||
|
stripped_query += token.value + separator;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: hash function should be a template parameter
|
||||||
|
auto hash = fnv(stripped_query);
|
||||||
|
return QueryStripped(std::move(stripped_query),
|
||||||
|
std::move(stripped_arguments), hash);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::tuple<Ts...> strip_types;
|
std::tuple<Ts...> strip_types;
|
||||||
CypherLexer::uptr lexer;
|
CypherLexer::uptr lexer;
|
||||||
|
|
||||||
template <typename Value, typename Tuple, std::size_t... index>
|
template<typename Value, typename Tuple, std::size_t... index>
|
||||||
bool _or(Value &&value, Tuple &&tuple, std::index_sequence<index...>)
|
bool _or(Value &&value, Tuple &&tuple, std::index_sequence<index...>) {
|
||||||
{
|
return utils::or_vargs(std::forward<Value>(value),
|
||||||
return utils::or_vargs(std::forward<Value>(value),
|
std::get<index>(std::forward<Tuple>(tuple))...);
|
||||||
std::get<index>(std::forward<Tuple>(tuple))...);
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename... Ts>
|
template<typename... Ts>
|
||||||
decltype(auto) make_query_stripper(Ts &&... ts)
|
decltype(auto) make_query_stripper(Ts &&... ts) {
|
||||||
{
|
return QueryStripper<Ts...>(std::forward<Ts>(ts)...);
|
||||||
return QueryStripper<Ts...>(std::forward<Ts>(ts)...);
|
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,6 @@
|
|||||||
|
|
||||||
#include "fmt/format.h"
|
#include "fmt/format.h"
|
||||||
#include "logging/default.hpp"
|
#include "logging/default.hpp"
|
||||||
#include "storage/model/properties/properties.hpp"
|
|
||||||
#include "storage/model/properties/property.hpp"
|
|
||||||
#include "storage/model/properties/json_writer.hpp"
|
|
||||||
#include "utils/types/byte.hpp"
|
|
||||||
#include "utils/exceptions/basic_exception.hpp"
|
#include "utils/exceptions/basic_exception.hpp"
|
||||||
|
|
||||||
using std::cout;
|
using std::cout;
|
||||||
@ -20,76 +16,54 @@ using std::endl;
|
|||||||
// headers because it will create a unique namespace for each compilation unit
|
// headers because it will create a unique namespace for each compilation unit
|
||||||
// http://stackoverflow.com/questions/2727582/multiple-definition-in-header-file
|
// http://stackoverflow.com/questions/2727582/multiple-definition-in-header-file
|
||||||
// but sometimes that might be a problem
|
// but sometimes that might be a problem
|
||||||
namespace
|
namespace {
|
||||||
{
|
|
||||||
|
|
||||||
class CodeLineFormatException : public BasicException
|
class CodeLineFormatException : public BasicException {
|
||||||
{
|
public:
|
||||||
public:
|
|
||||||
using BasicException::BasicException;
|
using BasicException::BasicException;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename... Args>
|
template<typename... Args>
|
||||||
std::string format(const std::string &format_str, const Args &... args)
|
std::string format(const std::string &format_str, const Args &... args) {
|
||||||
{
|
|
||||||
return fmt::format(format_str, args...);
|
return fmt::format(format_str, args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... Args>
|
template<typename... Args>
|
||||||
std::string code_line(const std::string &format_str, const Args &... args)
|
std::string code_line(const std::string &format_str, const Args &... args) {
|
||||||
{
|
|
||||||
try {
|
try {
|
||||||
return "\t" + format(format_str, args...) + "\n";
|
return "\t" + format(format_str, args...) + "\n";
|
||||||
} catch (std::runtime_error &e) {
|
} catch (std::runtime_error &e) {
|
||||||
throw CodeLineFormatException(std::string(e.what()) + " " + format_str);
|
throw CodeLineFormatException(std::string(e.what()) + " " + format_str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using name_properties_t = std::vector<std::pair<std::string, Property>>;
|
class CoutSocket {
|
||||||
using indices_t = std::map<std::string, long>;
|
public:
|
||||||
|
|
||||||
auto query_properties(indices_t &indices, properties_t &values)
|
|
||||||
{
|
|
||||||
name_properties_t properties;
|
|
||||||
for (auto &property_index : indices) {
|
|
||||||
properties.push_back(
|
|
||||||
std::make_pair(std::move(property_index.first),
|
|
||||||
std::move(values[property_index.second])));
|
|
||||||
}
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
class CoutSocket
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CoutSocket() : logger(logging::log->logger("Cout Socket")) {}
|
CoutSocket() : logger(logging::log->logger("Cout Socket")) {}
|
||||||
|
|
||||||
int write(const std::string &str)
|
int write(const std::string &str) {
|
||||||
{
|
logger.info(str);
|
||||||
logger.info(str);
|
return str.size();
|
||||||
return str.size();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int write(const char *data, size_t len)
|
int write(const char *data, size_t len) {
|
||||||
{
|
logger.info(std::string(data, len));
|
||||||
logger.info(std::string(data, len));
|
return len;
|
||||||
return len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int write(const byte *data, size_t len)
|
int write(const byte *data, size_t len) {
|
||||||
{
|
std::stringstream ss;
|
||||||
std::stringstream ss;
|
for (int i = 0; i < len; i++) {
|
||||||
for (int i = 0; i < len; i++) {
|
ss << data[i];
|
||||||
ss << data[i];
|
}
|
||||||
}
|
std::string output(ss.str());
|
||||||
std::string output(ss.str());
|
cout << output << endl;
|
||||||
cout << output << endl;
|
logger.info(output);
|
||||||
logger.info(output);
|
return len;
|
||||||
return len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Logger logger;
|
Logger logger;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,5 +13,5 @@ public:
|
|||||||
mvcc::VersionList<Vertex>* from_;
|
mvcc::VersionList<Vertex>* from_;
|
||||||
mvcc::VersionList<Vertex>* to_;
|
mvcc::VersionList<Vertex>* to_;
|
||||||
GraphDb::EdgeType edge_type_;
|
GraphDb::EdgeType edge_type_;
|
||||||
TypedValueStore properties_;
|
TypedValueStore<GraphDb::Property> properties_;
|
||||||
};
|
};
|
||||||
|
@ -1,49 +1,37 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "mvcc/version_list.hpp"
|
#include "mvcc/version_list.hpp"
|
||||||
#include "transactions/transaction.hpp"
|
|
||||||
#include "storage/typed_value.hpp"
|
#include "storage/typed_value.hpp"
|
||||||
#include "database/graph_db.hpp"
|
#include "database/graph_db.hpp"
|
||||||
#include "database/graph_db_accessor.hpp"
|
#include "database/graph_db_accessor.hpp"
|
||||||
#include "utils/pass_key.hpp"
|
#include "utils/pass_key.hpp"
|
||||||
|
|
||||||
template <typename TRecord, typename TDerived>
|
template<typename TRecord, typename TDerived>
|
||||||
class RecordAccessor {
|
class RecordAccessor {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
RecordAccessor(mvcc::VersionList<TRecord>* vlist, tx::Transaction &trans)
|
RecordAccessor(mvcc::VersionList<TRecord> *vlist, GraphDbAccessor *db_accessor)
|
||||||
: vlist_(vlist), trans_(trans) {
|
: vlist_(vlist), db_accessor_(db_accessor) {
|
||||||
record_ = vlist->find(trans_);
|
record_ = vlist->find(db_accessor->transaction_);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
template<typename TValue>
|
||||||
* Indicates if this record is visible to the current transaction.
|
void PropsSet(GraphDb::Property key, TValue value) {
|
||||||
*
|
update()->props_.set(key, value);
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
bool is_visible() const {
|
|
||||||
return record_ != nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TypedValue at(GraphDb::Property key) const {
|
size_t PropsErase(GraphDb::Property key) {
|
||||||
return record_->props_.at(key);
|
return update()->props_.erase(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename TValue>
|
const TypedValueStore<GraphDb::Property> &Properties() const {
|
||||||
void set(GraphDb::Property key, TValue value) {
|
return view().properties_;
|
||||||
update();
|
|
||||||
record_->props_.set(key, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t erase(GraphDb::Property key) {
|
void PropertiesAccept(std::function<void(const GraphDb::Property key, const TypedValue &prop)> handler,
|
||||||
update();
|
std::function<void()> finish = {}) const {
|
||||||
return record_->props_.erase(key);
|
view()->props_.Accept(handler, finish);
|
||||||
}
|
|
||||||
|
|
||||||
void Accept(std::function<void(const GraphDb::Property key, const TypedValue& prop)> handler,
|
|
||||||
std::function<void()> finish = {}) const {
|
|
||||||
record_->props_.Accept(handler, finish);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assumes same transaction
|
// Assumes same transaction
|
||||||
@ -64,29 +52,57 @@ public:
|
|||||||
* @param pass_key Ignored.
|
* @param pass_key Ignored.
|
||||||
* @return The version list of this accessor.
|
* @return The version list of this accessor.
|
||||||
*/
|
*/
|
||||||
mvcc::VersionList<TRecord>* vlist(PassKey<GraphDbAccessor> pass_key) const {
|
mvcc::VersionList<TRecord> *vlist(PassKey<GraphDbAccessor> pass_key) const {
|
||||||
return vlist_;
|
return vlist_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a GraphDB accessor of this record accessor.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
const GraphDbAccessor &db_accessor() const {
|
||||||
|
return db_accessor_;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures this accessor is fit for updating functions.
|
* Returns the update-ready version of the record.
|
||||||
*
|
*
|
||||||
* IMPORTANT: This function should be called from any
|
* @return See above.
|
||||||
* method that will change the record (in terms of the
|
|
||||||
* property graph data).
|
|
||||||
*/
|
*/
|
||||||
void update() {
|
TRecord *update() {
|
||||||
// TODO consider renaming this to something more indicative
|
// TODO consider renaming this to something more indicative
|
||||||
// of the underlying MVCC functionality (like "new_version" or so)
|
// of the underlying MVCC functionality (like "new_version" or so)
|
||||||
if (record_->is_visible_write(trans_))
|
|
||||||
return;
|
if (!record_->is_visible_write(db_accessor_->transaction_))
|
||||||
else
|
record_ = vlist_->update(db_accessor_->transaction_);
|
||||||
record_ = vlist_->update(trans_);
|
|
||||||
|
return record_;
|
||||||
}
|
}
|
||||||
|
|
||||||
mvcc::VersionList<TRecord>* vlist_;
|
/**
|
||||||
tx::Transaction& trans_;
|
* Returns a version of the record that is only for viewing.
|
||||||
TRecord* record_;
|
*
|
||||||
|
* @return See above.
|
||||||
|
*/
|
||||||
|
const TRecord *view() const {
|
||||||
|
return record_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The record (edge or vertex) this accessor provides access to.
|
||||||
|
mvcc::VersionList<TRecord> *vlist_;
|
||||||
|
|
||||||
|
// The database accessor for which this record accessor is created
|
||||||
|
// Provides means of getting to the transaction and database functions.
|
||||||
|
GraphDbAccessor *db_accessor_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/* The version of the record currently used in this transaction. Defaults to the
|
||||||
|
* latest viewable version (set in the constructor). After the first update done
|
||||||
|
* through this accessor a new, editable version, is created for this transaction,
|
||||||
|
* and set as the value of this variable.
|
||||||
|
*/
|
||||||
|
TRecord *record_;
|
||||||
};
|
};
|
||||||
|
@ -10,14 +10,14 @@
|
|||||||
* using a key of type Properties::TKey.
|
* using a key of type Properties::TKey.
|
||||||
*
|
*
|
||||||
* The underlying implementation is not necessarily std::map.
|
* The underlying implementation is not necessarily std::map.
|
||||||
|
*
|
||||||
|
* @tparam TKey The type of key used in this value store.
|
||||||
*/
|
*/
|
||||||
|
template <typename TKey = uint32_t>
|
||||||
class TypedValueStore {
|
class TypedValueStore {
|
||||||
public:
|
public:
|
||||||
using sptr = std::shared_ptr<TypedValueStore>;
|
using sptr = std::shared_ptr<TypedValueStore>;
|
||||||
|
|
||||||
/** The type of key used to get and set properties */
|
|
||||||
using TKey = uint32_t;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a TypedValue (by reference) at the given key.
|
* Returns a TypedValue (by reference) at the given key.
|
||||||
* If the key does not exist, the Null property is returned.
|
* If the key does not exist, the Null property is returned.
|
||||||
@ -65,7 +65,7 @@ public:
|
|||||||
/**
|
/**
|
||||||
* @return The number of Properties in this collection.
|
* @return The number of Properties in this collection.
|
||||||
*/
|
*/
|
||||||
size_t size();
|
size_t size() const;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,5 +14,5 @@ public:
|
|||||||
std::vector<mvcc::VersionList<Edge> *> out_;
|
std::vector<mvcc::VersionList<Edge> *> out_;
|
||||||
std::vector<mvcc::VersionList<Edge> *> in_;
|
std::vector<mvcc::VersionList<Edge> *> in_;
|
||||||
std::set<GraphDb::Label> labels_;
|
std::set<GraphDb::Label> labels_;
|
||||||
TypedValueStore properties_;
|
TypedValueStore<GraphDb::Property> properties_;
|
||||||
};
|
};
|
||||||
|
@ -20,7 +20,7 @@ class Transaction : public TransactionRead
|
|||||||
public:
|
public:
|
||||||
Transaction(const Id &id, const Snapshot<Id> &snapshot, Engine &engine);
|
Transaction(const Id &id, const Snapshot<Id> &snapshot, Engine &engine);
|
||||||
Transaction(const Transaction &) = delete;
|
Transaction(const Transaction &) = delete;
|
||||||
Transaction(Transaction &&) = delete;
|
Transaction(Transaction &&) = default;
|
||||||
|
|
||||||
// Returns copy of transaction_read
|
// Returns copy of transaction_read
|
||||||
TransactionRead transaction_read();
|
TransactionRead transaction_read();
|
||||||
|
@ -5,32 +5,92 @@
|
|||||||
#include "communication/bolt/v1/transport/socket_stream.hpp"
|
#include "communication/bolt/v1/transport/socket_stream.hpp"
|
||||||
#include "io/network/socket.hpp"
|
#include "io/network/socket.hpp"
|
||||||
|
|
||||||
template <class Stream>
|
template<class Stream>
|
||||||
void bolt::BoltSerializer<Stream>::write(const EdgeAccessor &edge)
|
void bolt::BoltSerializer<Stream>::write(const VertexAccessor &vertex) {
|
||||||
{
|
|
||||||
// write signatures for the edge struct and edge data type
|
|
||||||
encoder.write_struct_header(5);
|
|
||||||
encoder.write(underlying_cast(pack::Relationship));
|
|
||||||
|
|
||||||
// write the identifier for the node
|
// write signatures for the node struct and node data type
|
||||||
encoder.write_integer(edge.id());
|
encoder.write_struct_header(3);
|
||||||
|
encoder.write(underlying_cast(pack::Node));
|
||||||
|
|
||||||
encoder.write_integer(edge.from().id());
|
// IMPORTANT: here we write a hardcoded 0 because we don't
|
||||||
encoder.write_integer(edge.to().id());
|
// use internal IDs, but need to give something to Bolt
|
||||||
|
// note that OpenCypher has no id(x) function, so the client
|
||||||
|
// should not be able to do anything with this value anyway
|
||||||
|
encoder.write_integer(0); // uID
|
||||||
|
|
||||||
// write the type of the edge
|
// write the list of labels
|
||||||
encoder.write_string(edge.edge_type());
|
auto labels = vertex.labels();
|
||||||
|
encoder.write_list_header(labels.size());
|
||||||
|
for (auto label : labels)
|
||||||
|
encoder.write_string(vertex.db_accessor().label_name(label));
|
||||||
|
|
||||||
// write the property map
|
// write the properties
|
||||||
auto props = edge.properties();
|
const TypedValueStore &props = vertex.Properties();
|
||||||
|
encoder.write_map_header(props.size());
|
||||||
encoder.write_map_header(props.size());
|
props.Accept([&vertex](const TypedValueStore::TKey &prop_name, const TypedValue &value) {
|
||||||
|
write(vertex.db_accessor().property_name(prop_name));
|
||||||
for (auto &prop : props) {
|
write(value);
|
||||||
write(prop.key.family_name());
|
});
|
||||||
prop.accept(*this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template class bolt::BoltSerializer<bolt::BoltEncoder<
|
template<class Stream>
|
||||||
|
void bolt::BoltSerializer<Stream>::write(const EdgeAccessor &edge) {
|
||||||
|
// write signatures for the edge struct and edge data type
|
||||||
|
encoder.write_struct_header(5);
|
||||||
|
encoder.write(underlying_cast(pack::Relationship));
|
||||||
|
|
||||||
|
// IMPORTANT: here we write a hardcoded 0 because we don't
|
||||||
|
// use internal IDs, but need to give something to Bolt
|
||||||
|
// note that OpenCypher has no id(x) function, so the client
|
||||||
|
// should not be able to do anything with this value anyway
|
||||||
|
encoder.write_integer(0);
|
||||||
|
encoder.write_integer(0);
|
||||||
|
encoder.write_integer(0);
|
||||||
|
|
||||||
|
// write the type of the edge
|
||||||
|
encoder.write_string(edge.edge_type());
|
||||||
|
|
||||||
|
// write the property map
|
||||||
|
const TypedValueStore& props = edge.Properties();
|
||||||
|
encoder.write_map_header(props.size());
|
||||||
|
props.Accept([&edge](const TypedValueStore::TKey &prop_name, const TypedValue &value) {
|
||||||
|
write(edge.db_accessor().property_name(prop_name));
|
||||||
|
write(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Stream>
|
||||||
|
void bolt::BoltSerializer<Stream>::write(const TypedValue& value) {
|
||||||
|
|
||||||
|
switch (value.type_) {
|
||||||
|
case TypedValue::Type::Null:
|
||||||
|
encoder.write_null();
|
||||||
|
return;
|
||||||
|
case TypedValue::Type::Bool:
|
||||||
|
encoder.write_bool(value.Value<bool>());
|
||||||
|
return;
|
||||||
|
case TypedValue::Type::String:
|
||||||
|
encoder.write_string(value.Value<std::string>());
|
||||||
|
return;
|
||||||
|
case TypedValue::Type::Int:
|
||||||
|
encoder.write_integer(value.Value<int>());
|
||||||
|
return;
|
||||||
|
case TypedValue::Type::Float:
|
||||||
|
encoder.write_double(value.Value<float>());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Stream>
|
||||||
|
void bolt::BoltSerializer<Stream>::write_failure(const std::map<std::string, std::string> &data) {
|
||||||
|
encoder.message_failure();
|
||||||
|
encoder.write_map_header(data.size());
|
||||||
|
for (auto const &kv : data) {
|
||||||
|
write(kv.first);
|
||||||
|
write(kv.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template
|
||||||
|
class bolt::BoltSerializer<bolt::BoltEncoder<
|
||||||
bolt::ChunkedEncoder<bolt::ChunkedBuffer<bolt::SocketStream<io::Socket>>>>>;
|
bolt::ChunkedEncoder<bolt::ChunkedBuffer<bolt::SocketStream<io::Socket>>>>>;
|
||||||
|
@ -43,5 +43,5 @@ void Session::close()
|
|||||||
bolt.close(this);
|
bolt.close(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
Db &Session::active_db() { return bolt.dbms.active(); }
|
GraphDb &Session::active_db() { return bolt.dbms.active(); }
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
|
#include <database/creation_exception.hpp>
|
||||||
#include "database/graph_db_accessor.hpp"
|
#include "database/graph_db_accessor.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
GraphDbAccessor::GraphDbAccessor(GraphDb& db) : db_(db), transaction_(std::move(db.tx_engine.begin())) {}
|
||||||
|
|
||||||
VertexAccessor GraphDbAccessor::insert_vertex() {
|
VertexAccessor GraphDbAccessor::insert_vertex() {
|
||||||
auto vertex_vlist = new mvcc::VersionList<Vertex>();
|
auto vertex_vlist = new mvcc::VersionList<Vertex>();
|
||||||
vertex_vlist->insert(transaction_);
|
vertex_vlist->insert(transaction_);
|
||||||
@ -32,8 +35,6 @@ EdgeAccessor GraphDbAccessor::insert_edge(
|
|||||||
// TODO connect the vertices to edge
|
// TODO connect the vertices to edge
|
||||||
from.add_to_out(edge_vlist, pass_key);
|
from.add_to_out(edge_vlist, pass_key);
|
||||||
to.add_to_in(edge_vlist, pass_key);
|
to.add_to_in(edge_vlist, pass_key);
|
||||||
// from.vlist(pass_key).out_.emplace(edge_vlist);
|
|
||||||
// to.vlist(pass_key).in_.emplace(edge_vlist);
|
|
||||||
|
|
||||||
// TODO make this configurable
|
// TODO make this configurable
|
||||||
for (int i = 0; i < 5; ++i) {
|
for (int i = 0; i < 5; ++i) {
|
||||||
@ -47,13 +48,25 @@ EdgeAccessor GraphDbAccessor::insert_edge(
|
|||||||
}
|
}
|
||||||
|
|
||||||
GraphDb::Label GraphDbAccessor::label(const std::string& label_name) {
|
GraphDb::Label GraphDbAccessor::label(const std::string& label_name) {
|
||||||
return db_.labels_.GetKey(label_name);
|
return &(*db_.labels_.access().insert(label_name).first);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string& GraphDbAccessor::label_name(const GraphDb::Label label) const {
|
||||||
|
return *label;
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphDb::EdgeType GraphDbAccessor::edge_type(const std::string& edge_type_name){
|
GraphDb::EdgeType GraphDbAccessor::edge_type(const std::string& edge_type_name){
|
||||||
return db_.edge_types_.GetKey(edge_type_name);
|
return &(*db_.edge_types_.access().insert(edge_type_name).first);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string& GraphDbAccessor::edge_type_name(const GraphDb::EdgeType edge_type) const {
|
||||||
|
return *edge_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphDb::Property GraphDbAccessor::property(const std::string& property_name) {
|
GraphDb::Property GraphDbAccessor::property(const std::string& property_name) {
|
||||||
return db_.properties_.GetKey(property_name);
|
return &(*db_.properties_.access().insert(property_name).first);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string& GraphDbAccessor::property_name(const GraphDb::Property property) const {
|
||||||
|
return *property;
|
||||||
}
|
}
|
||||||
|
@ -2,37 +2,35 @@
|
|||||||
#include "storage/vertex_accessor.hpp"
|
#include "storage/vertex_accessor.hpp"
|
||||||
|
|
||||||
void EdgeAccessor::set_edge_type(GraphDb::EdgeType edge_type) {
|
void EdgeAccessor::set_edge_type(GraphDb::EdgeType edge_type) {
|
||||||
this->record_->edge_type_ = edge_type;
|
this->update()->edge_type_ = edge_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphDb::EdgeType EdgeAccessor::edge_type() const {
|
GraphDb::EdgeType EdgeAccessor::edge_type() const {
|
||||||
return this->record_->edge_type_;
|
return this->view()->edge_type_;
|
||||||
}
|
}
|
||||||
|
|
||||||
VertexAccessor EdgeAccessor::from() const {
|
VertexAccessor EdgeAccessor::from() const {
|
||||||
return VertexAccessor(this->record_->from_, this->trans_);
|
return VertexAccessor(this->view()->from_, this->db_accessor_->transaction_);
|
||||||
}
|
}
|
||||||
|
|
||||||
VertexAccessor EdgeAccessor::to() const {
|
VertexAccessor EdgeAccessor::to() const {
|
||||||
return VertexAccessor(this->record_->to_, this->trans_);
|
return VertexAccessor(this->view()->to_, this->db_accessor_->transaction_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EdgeAccessor::remove() {
|
void EdgeAccessor::remove() {
|
||||||
// remove this edge's reference from the "from" vertex
|
// remove this edge's reference from the "from" vertex
|
||||||
auto vertex_from = from();
|
auto vertex_from = from().update();
|
||||||
vertex_from.update();
|
std::remove(vertex_from->out_.begin(),
|
||||||
std::remove(vertex_from.record_->out_.begin(),
|
vertex_from->out_.end(),
|
||||||
vertex_from.record_->out_.end(),
|
|
||||||
vlist_);
|
vlist_);
|
||||||
|
|
||||||
// remove this edge's reference from the "to" vertex
|
// remove this edge's reference from the "to" vertex
|
||||||
auto vertex_to = to();
|
auto vertex_to = to().update();
|
||||||
vertex_to.update();
|
std::remove(vertex_to->in_.begin(),
|
||||||
std::remove(vertex_to.record_->in_.begin(),
|
vertex_to->in_.end(),
|
||||||
vertex_to.record_->in_.end(),
|
|
||||||
vlist_);
|
vlist_);
|
||||||
|
|
||||||
// remove this record from the database via MVCC
|
// remove this record from the database via MVCC
|
||||||
vlist_->remove(record_, trans_);
|
vlist_->remove(update(), db_accessor_->transaction_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ void TypedValueStore::set(const TKey &key, const char *value) {
|
|||||||
set(key, std::string(value));
|
set(key, std::string(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t TypedValueStore::erase(const TKey &key) {
|
size_t TypedValueStore::erase(const TKey &key) const {
|
||||||
auto found = std::find_if(props_.begin(), props_.end(), [&key](std::pair<TKey, TypedValue> &kv){return kv.first == key;});
|
auto found = std::find_if(props_.begin(), props_.end(), [&key](std::pair<TKey, TypedValue> &kv){return kv.first == key;});
|
||||||
if (found != props_.end()) {
|
if (found != props_.end()) {
|
||||||
props_.erase(found);
|
props_.erase(found);
|
||||||
|
@ -2,30 +2,28 @@
|
|||||||
#include "storage/vertex_accessor.hpp"
|
#include "storage/vertex_accessor.hpp"
|
||||||
|
|
||||||
size_t VertexAccessor::out_degree() const {
|
size_t VertexAccessor::out_degree() const {
|
||||||
return this->record_->out_.size();
|
return this->view()->out_.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t VertexAccessor::in_degree() const {
|
size_t VertexAccessor::in_degree() const {
|
||||||
return this->record_->in_.size();
|
return this->view()->in_.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VertexAccessor::add_label(GraphDb::Label label) {
|
bool VertexAccessor::add_label(GraphDb::Label label) {
|
||||||
update();
|
return this->update()->labels_.emplace(label).second;
|
||||||
return this->record_->labels_.emplace(label).second;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t VertexAccessor::remove_label(GraphDb::Label label) {
|
size_t VertexAccessor::remove_label(GraphDb::Label label) {
|
||||||
update();
|
return this->update()->labels_.erase(label);
|
||||||
return this->record_->labels_.erase(label);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VertexAccessor::has_label(GraphDb::Label label) const {
|
bool VertexAccessor::has_label(GraphDb::Label label) const {
|
||||||
auto &label_set = this->record_->labels_;
|
auto &label_set = this->view()->labels_;
|
||||||
return label_set.find(label) != label_set.end();
|
return label_set.find(label) != label_set.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::set<GraphDb::Label> &VertexAccessor::labels() const {
|
const std::set<GraphDb::Label>& VertexAccessor::labels() const {
|
||||||
return this->record_->labels_;
|
return this->view()->labels_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VertexAccessor::remove() {
|
bool VertexAccessor::remove() {
|
||||||
@ -33,7 +31,7 @@ bool VertexAccessor::remove() {
|
|||||||
if (out_degree() > 0 || in_degree() > 0)
|
if (out_degree() > 0 || in_degree() > 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
vlist_->remove(record_, trans_);
|
vlist_->remove(view(), db_accessor_->transaction_);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,21 +39,19 @@ void VertexAccessor::detach_remove() {
|
|||||||
// removing edges via accessors is both safe
|
// removing edges via accessors is both safe
|
||||||
// and it should remove all the pointers in the relevant
|
// and it should remove all the pointers in the relevant
|
||||||
// vertices (including this one)
|
// vertices (including this one)
|
||||||
for (auto edge_vlist : record_->out_)
|
for (auto edge_vlist : view()->out_)
|
||||||
EdgeAccessor(edge_vlist, trans_).remove();
|
EdgeAccessor(edge_vlist, db_accessor_->transaction_).remove();
|
||||||
|
|
||||||
for (auto edge_vlist : record_->in_)
|
for (auto edge_vlist : view()->in_)
|
||||||
EdgeAccessor(edge_vlist, trans_).remove();
|
EdgeAccessor(edge_vlist, db_accessor_->transaction_).remove();
|
||||||
|
|
||||||
vlist_->remove(record_, trans_);
|
vlist_->remove(view(), db_accessor_->transaction_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VertexAccessor::attach_in(mvcc::VersionList<Edge>* edge_vlist, PassKey<GraphDb>) {
|
void VertexAccessor::attach_in(mvcc::VersionList<Edge>* edge_vlist, PassKey<GraphDb>) {
|
||||||
update();
|
this->update()->in_.emplace_back(edge_vlist);
|
||||||
this->record_->in_.emplace_back(edge_vlist);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VertexAccessor::attach_out(mvcc::VersionList<Edge>* edge_vlist, PassKey<GraphDb>) {
|
void VertexAccessor::attach_out(mvcc::VersionList<Edge>* edge_vlist, PassKey<GraphDb>) {
|
||||||
update();
|
this->update()->out_.emplace_back(edge_vlist);
|
||||||
this->record_->out_.emplace_back(edge_vlist);
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user