b15eeffd48
Summary: Session specifics have been move out of the Bolt `executing` state, and are accessed via pure virtual Session type. Our server is templated on the session and we are setting the concrete type, so there should be no virtual call overhead. Abstract Session is used to indicate the interface, this could have also been templated, but the explicit interface definition makes it clearer. Specific session implementation for running Memgraph is now implemented in memgraph_bolt, which instantiates the concrete session type. This may not be 100% appropriate place, but Memgraph specific session isn't needed anywhere else. Bolt/communication tests now use a dummy session and depend only on communication, which significantly improves test run times. All these changes make the communication a library which doesn't depend on storage nor the database. Only shared connection points, which aren't part of the base communication library are: * glue/conversion -- which converts between storage and bolt types, and * communication/result_stream_faker -- templated, but used in tests and query/repl Depends on D1453 Reviewers: mferencevic, buda, mtomic, msantl Reviewed By: mferencevic, mtomic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D1456
259 lines
8.2 KiB
C++
259 lines
8.2 KiB
C++
#include "bolt_common.hpp"
|
|
#include "bolt_testdata.hpp"
|
|
|
|
#include "communication/bolt/v1/encoder/encoder.hpp"
|
|
#include "database/graph_db.hpp"
|
|
#include "database/graph_db_accessor.hpp"
|
|
#include "glue/conversion.hpp"
|
|
|
|
using communication::bolt::DecodedValue;
|
|
|
|
/**
|
|
* TODO (mferencevic): document
|
|
*/
|
|
|
|
constexpr const int SIZE = 131072;
|
|
uint8_t data[SIZE];
|
|
|
|
uint64_t GetBigEndianInt(std::vector<uint8_t> &v, uint8_t len,
|
|
uint8_t offset = 1) {
|
|
uint64_t ret = 0;
|
|
v.erase(v.begin(), v.begin() + offset);
|
|
for (int i = 0; i < len; ++i) {
|
|
ret <<= 8;
|
|
ret += v[i];
|
|
}
|
|
v.erase(v.begin(), v.begin() + len);
|
|
return ret;
|
|
}
|
|
|
|
void CheckTypeSize(std::vector<uint8_t> &v, int typ, uint64_t size) {
|
|
uint64_t len;
|
|
if ((v[0] & 0xF0) == type_tiny_magic[typ]) {
|
|
len = v[0] & 0x0F;
|
|
v.erase(v.begin(), v.begin() + 1);
|
|
} else if (v[0] == type_8_magic[typ]) {
|
|
len = GetBigEndianInt(v, 1);
|
|
} else if (v[0] == type_16_magic[typ]) {
|
|
len = GetBigEndianInt(v, 2);
|
|
} else if (v[0] == type_32_magic[typ]) {
|
|
len = GetBigEndianInt(v, 4);
|
|
} else {
|
|
FAIL() << "Got wrong marker!";
|
|
}
|
|
ASSERT_EQ(len, size);
|
|
}
|
|
|
|
void CheckInt(std::vector<uint8_t> &output, int64_t value) {
|
|
TestOutputStream output_stream;
|
|
TestBuffer encoder_buffer(output_stream);
|
|
communication::bolt::BaseEncoder<TestBuffer> bolt_encoder(encoder_buffer);
|
|
std::vector<uint8_t> &encoded = output_stream.output;
|
|
bolt_encoder.WriteInt(value);
|
|
CheckOutput(output, encoded.data(), encoded.size(), false);
|
|
}
|
|
|
|
void CheckRecordHeader(std::vector<uint8_t> &v, uint64_t size) {
|
|
CheckOutput(v, (const uint8_t *)"\xB1\x71", 2, false);
|
|
CheckTypeSize(v, LIST, size);
|
|
}
|
|
|
|
TestOutputStream output_stream;
|
|
TestBuffer encoder_buffer(output_stream);
|
|
communication::bolt::Encoder<TestBuffer> bolt_encoder(encoder_buffer);
|
|
std::vector<uint8_t> &output = output_stream.output;
|
|
|
|
TEST(BoltEncoder, NullAndBool) {
|
|
output.clear();
|
|
std::vector<DecodedValue> vals;
|
|
vals.push_back(DecodedValue());
|
|
vals.push_back(DecodedValue(true));
|
|
vals.push_back(DecodedValue(false));
|
|
bolt_encoder.MessageRecord(vals);
|
|
CheckRecordHeader(output, 3);
|
|
CheckOutput(output, (const uint8_t *)"\xC0\xC3\xC2", 3);
|
|
}
|
|
|
|
TEST(BoltEncoder, Int) {
|
|
int N = 28;
|
|
output.clear();
|
|
std::vector<DecodedValue> vals;
|
|
for (int i = 0; i < N; ++i) vals.push_back(DecodedValue(int_decoded[i]));
|
|
bolt_encoder.MessageRecord(vals);
|
|
CheckRecordHeader(output, N);
|
|
for (int i = 0; i < N; ++i)
|
|
CheckOutput(output, int_encoded[i], int_encoded_len[i], false);
|
|
CheckOutput(output, nullptr, 0);
|
|
}
|
|
|
|
TEST(BoltEncoder, Double) {
|
|
int N = 4;
|
|
output.clear();
|
|
std::vector<DecodedValue> vals;
|
|
for (int i = 0; i < N; ++i) vals.push_back(DecodedValue(double_decoded[i]));
|
|
bolt_encoder.MessageRecord(vals);
|
|
CheckRecordHeader(output, N);
|
|
for (int i = 0; i < N; ++i) CheckOutput(output, double_encoded[i], 9, false);
|
|
CheckOutput(output, nullptr, 0);
|
|
}
|
|
|
|
TEST(BoltEncoder, String) {
|
|
output.clear();
|
|
std::vector<DecodedValue> vals;
|
|
for (uint64_t i = 0; i < sizes_num; ++i)
|
|
vals.push_back(DecodedValue(std::string((const char *)data, sizes[i])));
|
|
bolt_encoder.MessageRecord(vals);
|
|
CheckRecordHeader(output, vals.size());
|
|
for (uint64_t i = 0; i < sizes_num; ++i) {
|
|
CheckTypeSize(output, STRING, sizes[i]);
|
|
CheckOutput(output, data, sizes[i], false);
|
|
}
|
|
CheckOutput(output, nullptr, 0);
|
|
}
|
|
|
|
TEST(BoltEncoder, List) {
|
|
output.clear();
|
|
std::vector<DecodedValue> vals;
|
|
for (uint64_t i = 0; i < sizes_num; ++i) {
|
|
std::vector<DecodedValue> val;
|
|
for (uint64_t j = 0; j < sizes[i]; ++j)
|
|
val.push_back(DecodedValue(std::string((const char *)&data[j], 1)));
|
|
vals.push_back(DecodedValue(val));
|
|
}
|
|
bolt_encoder.MessageRecord(vals);
|
|
CheckRecordHeader(output, vals.size());
|
|
for (uint64_t i = 0; i < sizes_num; ++i) {
|
|
CheckTypeSize(output, LIST, sizes[i]);
|
|
for (uint64_t j = 0; j < sizes[i]; ++j) {
|
|
CheckTypeSize(output, STRING, 1);
|
|
CheckOutput(output, &data[j], 1, false);
|
|
}
|
|
}
|
|
CheckOutput(output, nullptr, 0);
|
|
}
|
|
|
|
TEST(BoltEncoder, Map) {
|
|
output.clear();
|
|
std::vector<DecodedValue> vals;
|
|
uint8_t buff[10];
|
|
for (int i = 0; i < sizes_num; ++i) {
|
|
std::map<std::string, DecodedValue> val;
|
|
for (int j = 0; j < sizes[i]; ++j) {
|
|
sprintf((char *)buff, "%05X", j);
|
|
std::string tmp((char *)buff, 5);
|
|
val.insert(std::make_pair(tmp, DecodedValue(tmp)));
|
|
}
|
|
vals.push_back(DecodedValue(val));
|
|
}
|
|
bolt_encoder.MessageRecord(vals);
|
|
CheckRecordHeader(output, vals.size());
|
|
for (int i = 0; i < sizes_num; ++i) {
|
|
CheckTypeSize(output, MAP, sizes[i]);
|
|
for (int j = 0; j < sizes[i]; ++j) {
|
|
sprintf((char *)buff, "%05X", j);
|
|
CheckTypeSize(output, STRING, 5);
|
|
CheckOutput(output, buff, 5, false);
|
|
CheckTypeSize(output, STRING, 5);
|
|
CheckOutput(output, buff, 5, false);
|
|
}
|
|
}
|
|
CheckOutput(output, nullptr, 0);
|
|
}
|
|
|
|
TEST(BoltEncoder, VertexAndEdge) {
|
|
output.clear();
|
|
|
|
// create vertex
|
|
database::SingleNode db;
|
|
database::GraphDbAccessor db_accessor(db);
|
|
auto va1 = db_accessor.InsertVertex();
|
|
auto va2 = db_accessor.InsertVertex();
|
|
auto l1 = db_accessor.Label("label1");
|
|
auto l2 = db_accessor.Label("label2");
|
|
va1.add_label(l1);
|
|
va1.add_label(l2);
|
|
auto p1 = db_accessor.Property("prop1");
|
|
auto p2 = db_accessor.Property("prop2");
|
|
PropertyValue pv1(12), pv2(200);
|
|
va1.PropsSet(p1, pv1);
|
|
va1.PropsSet(p2, pv2);
|
|
|
|
// create edge
|
|
auto et = db_accessor.EdgeType("edgetype");
|
|
auto ea = db_accessor.InsertEdge(va1, va2, et);
|
|
auto p3 = db_accessor.Property("prop3");
|
|
auto p4 = db_accessor.Property("prop4");
|
|
PropertyValue pv3(42), pv4(1234);
|
|
ea.PropsSet(p3, pv3);
|
|
ea.PropsSet(p4, pv4);
|
|
|
|
// check everything
|
|
std::vector<DecodedValue> vals;
|
|
vals.push_back(glue::ToDecodedValue(va1));
|
|
vals.push_back(glue::ToDecodedValue(va2));
|
|
vals.push_back(glue::ToDecodedValue(ea));
|
|
bolt_encoder.MessageRecord(vals);
|
|
|
|
// The vertexedge_encoded testdata has hardcoded zeros for IDs,
|
|
// and Memgraph now encodes IDs so we need to check the output
|
|
// part by part.
|
|
CheckOutput(output, vertexedge_encoded, 5, false);
|
|
CheckInt(output, va1.gid());
|
|
CheckOutput(output, vertexedge_encoded + 6, 34, false);
|
|
CheckInt(output, va2.gid());
|
|
CheckOutput(output, vertexedge_encoded + 41, 4, false);
|
|
CheckInt(output, ea.gid());
|
|
CheckInt(output, va1.gid());
|
|
CheckInt(output, va2.gid());
|
|
CheckOutput(output, vertexedge_encoded + 48, 26);
|
|
}
|
|
|
|
TEST(BoltEncoder, BoltV1ExampleMessages) {
|
|
// this test checks example messages from: http://boltprotocol.org/v1/
|
|
|
|
output.clear();
|
|
|
|
// record message
|
|
std::vector<DecodedValue> rvals;
|
|
for (int i = 1; i < 4; ++i) rvals.push_back(DecodedValue(i));
|
|
bolt_encoder.MessageRecord(rvals);
|
|
CheckOutput(output, (const uint8_t *)"\xB1\x71\x93\x01\x02\x03", 6);
|
|
|
|
// success message
|
|
std::string sv1("name"), sv2("age"), sk("fields");
|
|
std::vector<DecodedValue> svec;
|
|
svec.push_back(DecodedValue(sv1));
|
|
svec.push_back(DecodedValue(sv2));
|
|
DecodedValue slist(svec);
|
|
std::map<std::string, DecodedValue> svals;
|
|
svals.insert(std::make_pair(sk, slist));
|
|
bolt_encoder.MessageSuccess(svals);
|
|
CheckOutput(output,
|
|
(const uint8_t *) "\xB1\x70\xA1\x86\x66\x69\x65\x6C\x64\x73\x92\x84\x6E\x61\x6D\x65\x83\x61\x67\x65",
|
|
20);
|
|
|
|
// failure message
|
|
std::string fv1("Neo.ClientError.Statement.SyntaxError"),
|
|
fv2("Invalid syntax.");
|
|
std::string fk1("code"), fk2("message");
|
|
DecodedValue ftv1(fv1), ftv2(fv2);
|
|
std::map<std::string, DecodedValue> fvals;
|
|
fvals.insert(std::make_pair(fk1, ftv1));
|
|
fvals.insert(std::make_pair(fk2, ftv2));
|
|
bolt_encoder.MessageFailure(fvals);
|
|
CheckOutput(output,
|
|
(const uint8_t *) "\xB1\x7F\xA2\x84\x63\x6F\x64\x65\xD0\x25\x4E\x65\x6F\x2E\x43\x6C\x69\x65\x6E\x74\x45\x72\x72\x6F\x72\x2E\x53\x74\x61\x74\x65\x6D\x65\x6E\x74\x2E\x53\x79\x6E\x74\x61\x78\x45\x72\x72\x6F\x72\x87\x6D\x65\x73\x73\x61\x67\x65\x8F\x49\x6E\x76\x61\x6C\x69\x64\x20\x73\x79\x6E\x74\x61\x78\x2E",
|
|
71);
|
|
|
|
// ignored message
|
|
bolt_encoder.MessageIgnored();
|
|
CheckOutput(output, (const uint8_t *)"\xB0\x7E", 2);
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
InitializeData(data, SIZE);
|
|
google::InitGoogleLogging(argv[0]);
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|