Compare commits

..

11 Commits

Author SHA1 Message Date
Andi Skrgat
c66eb5c207 Add cpp23 2024-02-20 08:28:58 +01:00
Andi Skrgat
47cdca4f6e Add cpp23 2024-02-20 08:24:43 +01:00
Josipmrden
bae3e8a6d3 Add function for property sizes (#1557)
Add function for property sizes
2024-02-19 13:56:01 +01:00
Andi
f3574012c5 Add cpp23 support (#1726) 2024-02-19 10:36:51 +00:00
Gareth Andrew Lloyd
33c400fcc1 Fixup memory e2e tests (#1715)
- Remove the e2e that did concurrent mgp_* calls on the same transaction
  (ATM this is unsupported)
- Fix up the concurrent mgp_global_alloc test to be testing it more precisely
- Reduce the memory limit on detach delete test due to recent memory
  optimizations around deltas.
- No longer throw from hook, through jemalloc C, to our C++ on other
  side. This cause mutex unlocks to not happen.
- No longer allocate error messages while inside the hook. This caused
  recursive entry back inside jamalloc which would try to relock a
  non-recursive mutex.
2024-02-16 15:35:08 +00:00
Marko Budiselić
5ac938a6c9 Remove default assignees from issue-bug template (#1730) 2024-02-16 14:41:53 +01:00
Andi
3e3224f0a2 Forbid having multiple mains in the cluster (#1727) 2024-02-16 11:41:15 +00:00
Antonio Filipovic
bfc756c092 HA: Polish flow for replicas from coordinator (#1711) 2024-02-16 10:58:01 +01:00
Marko Barišić
5f2e3f01d0 Turn e2e tests back on for release build workflows (#1725) 2024-02-15 16:20:04 +01:00
Marko Barišić
2c774ff09b Add rules for rc workflows (#1722) 2024-02-15 15:33:14 +01:00
Andi
20b47845f0 Forbid writing to cluster-managed main on restart (#1717) 2024-02-15 14:07:04 +01:00
25 changed files with 676 additions and 96 deletions

View File

@@ -2,7 +2,7 @@
BasedOnStyle: Google BasedOnStyle: Google
--- ---
Language: Cpp Language: Cpp
Standard: "c++20" Standard: c++20
UseTab: Never UseTab: Never
DerivePointerAlignment: false DerivePointerAlignment: false
PointerAlignment: Right PointerAlignment: Right

View File

@@ -3,7 +3,6 @@ name: Bug report
about: Create a report to help us improve about: Create a report to help us improve
title: "" title: ""
labels: bug labels: bug
assignees: gitbuda
--- ---
**Memgraph version** **Memgraph version**

View File

@@ -17,9 +17,10 @@ repos:
name: isort (python) name: isort (python)
args: ["--profile", "black"] args: ["--profile", "black"]
- repo: https://github.com/pre-commit/mirrors-clang-format - repo: https://github.com/pre-commit/mirrors-clang-format
rev: v13.0.0 rev: v17.0.6
hooks: hooks:
- id: clang-format - id: clang-format
types_or: [c++, c]
# - repo: local # - repo: local
# hooks: # hooks:
# - id: clang-tidy # - id: clang-tidy

View File

@@ -64,7 +64,7 @@ option(MG_ENTERPRISE "Build Memgraph Enterprise Edition" ON)
# Set the current version here to override the automatic version detection. The # Set the current version here to override the automatic version detection. The
# version must be specified as `X.Y.Z`. Primarily used when building new patch # version must be specified as `X.Y.Z`. Primarily used when building new patch
# versions. # versions.
set(MEMGRAPH_OVERRIDE_VERSION "2.15.0") set(MEMGRAPH_OVERRIDE_VERSION "")
# Custom suffix that this version should have. The suffix can be any arbitrary # Custom suffix that this version should have. The suffix can be any arbitrary
# string. Primarily used when building a version for a specific customer. # string. Primarily used when building a version for a specific customer.
@@ -189,7 +189,7 @@ add_custom_target(clean_all
# is easier debugging of compilation and linker flags. # is easier debugging of compilation and linker flags.
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
# c99-designator is disabled because of required mixture of designated and # c99-designator is disabled because of required mixture of designated and
# non-designated initializers in Python Query Module code (`py_module.cpp`). # non-designated initializers in Python Query Module code (`py_module.cpp`).

2
init
View File

@@ -120,7 +120,7 @@ fi
# develop on them -> pre-commit hook not required -> we can use latest # develop on them -> pre-commit hook not required -> we can use latest
# packages. # packages.
if [ "${DISTRO}" != "centos-7" ] && [ "$DISTRO" != "debian-10" ] && [ "${DISTRO}" != "ubuntu-18.04" ] && [ "${DISTRO}" != "amzn-2" ]; then if [ "${DISTRO}" != "centos-7" ] && [ "$DISTRO" != "debian-10" ] && [ "${DISTRO}" != "ubuntu-18.04" ] && [ "${DISTRO}" != "amzn-2" ]; then
python3 -m pip install pre-commit python3 -m pip install pre-commit==3.5.*
python3 -m pre_commit install python3 -m pre_commit install
# Install py format tools for usage during the development. # Install py format tools for usage during the development.
echo "Install black formatter" echo "Install black formatter"

View File

@@ -36,7 +36,7 @@ ADDITIONAL USE GRANT: You may use the Licensed Work in accordance with the
3. using the Licensed Work to create a work or solution 3. using the Licensed Work to create a work or solution
which competes (or might reasonably be expected to which competes (or might reasonably be expected to
compete) with the Licensed Work. compete) with the Licensed Work.
CHANGE DATE: 2028-28-02 CHANGE DATE: 2028-21-01
CHANGE LICENSE: Apache License, Version 2.0 CHANGE LICENSE: Apache License, Version 2.0
For information about alternative licensing arrangements, please visit: https://memgraph.com/legal. For information about alternative licensing arrangements, please visit: https://memgraph.com/legal.

View File

@@ -54,6 +54,10 @@ class EdgeAccessor final {
return impl_.GetProperty(key, view); return impl_.GetProperty(key, view);
} }
storage::Result<uint64_t> GetPropertySize(storage::PropertyId key, storage::View view) const {
return impl_.GetPropertySize(key, view);
}
storage::Result<storage::PropertyValue> SetProperty(storage::PropertyId key, const storage::PropertyValue &value) { storage::Result<storage::PropertyValue> SetProperty(storage::PropertyId key, const storage::PropertyValue &value) {
return impl_.SetProperty(key, value); return impl_.SetProperty(key, value);
} }
@@ -129,6 +133,10 @@ class VertexAccessor final {
return impl_.GetProperty(key, view); return impl_.GetProperty(key, view);
} }
storage::Result<uint64_t> GetPropertySize(storage::PropertyId key, storage::View view) const {
return impl_.GetPropertySize(key, view);
}
storage::Result<storage::PropertyValue> SetProperty(storage::PropertyId key, const storage::PropertyValue &value) { storage::Result<storage::PropertyValue> SetProperty(storage::PropertyId key, const storage::PropertyValue &value) {
return impl_.SetProperty(key, value); return impl_.SetProperty(key, value);
} }
@@ -268,6 +276,10 @@ class SubgraphVertexAccessor final {
return impl_.GetProperty(view, key); return impl_.GetProperty(view, key);
} }
storage::Result<uint64_t> GetPropertySize(storage::PropertyId key, storage::View view) const {
return impl_.GetPropertySize(key, view);
}
storage::Gid Gid() const noexcept { return impl_.Gid(); } storage::Gid Gid() const noexcept { return impl_.Gid(); }
storage::Result<size_t> InDegree(storage::View view) const { return impl_.InDegree(view); } storage::Result<size_t> InDegree(storage::View view) const { return impl_.InDegree(view); }
@@ -529,6 +541,10 @@ class DbAccessor final {
storage::PropertyId NameToProperty(const std::string_view name) { return accessor_->NameToProperty(name); } storage::PropertyId NameToProperty(const std::string_view name) { return accessor_->NameToProperty(name); }
std::optional<storage::PropertyId> NameToPropertyIfExists(std::string_view name) const {
return accessor_->NameToPropertyIfExists(name);
}
storage::LabelId NameToLabel(const std::string_view name) { return accessor_->NameToLabel(name); } storage::LabelId NameToLabel(const std::string_view name) { return accessor_->NameToLabel(name); }
storage::EdgeTypeId NameToEdgeType(const std::string_view name) { return accessor_->NameToEdgeType(name); } storage::EdgeTypeId NameToEdgeType(const std::string_view name) { return accessor_->NameToEdgeType(name); }

View File

@@ -442,6 +442,29 @@ TypedValue Size(const TypedValue *args, int64_t nargs, const FunctionContext &ct
} }
} }
TypedValue PropertySize(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
FType<Or<Null, Vertex, Edge>, Or<String>>("propertySize", args, nargs);
auto *dba = ctx.db_accessor;
const auto &property_name = args[1].ValueString();
const auto maybe_property_id = dba->NameToPropertyIfExists(property_name);
if (!maybe_property_id) {
return TypedValue(0, ctx.memory);
}
uint64_t property_size = 0;
const auto &graph_entity = args[0];
if (graph_entity.IsVertex()) {
property_size = graph_entity.ValueVertex().GetPropertySize(*maybe_property_id, ctx.view).GetValue();
} else if (graph_entity.IsEdge()) {
property_size = graph_entity.ValueEdge().GetPropertySize(*maybe_property_id, ctx.view).GetValue();
}
return TypedValue(static_cast<int64_t>(property_size), ctx.memory);
}
TypedValue StartNode(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) { TypedValue StartNode(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) {
FType<Or<Null, Edge>>("startNode", args, nargs); FType<Or<Null, Edge>>("startNode", args, nargs);
if (args[0].IsNull()) return TypedValue(ctx.memory); if (args[0].IsNull()) return TypedValue(ctx.memory);
@@ -1325,6 +1348,7 @@ std::function<TypedValue(const TypedValue *, int64_t, const FunctionContext &ctx
if (function_name == "PROPERTIES") return Properties; if (function_name == "PROPERTIES") return Properties;
if (function_name == "RANDOMUUID") return RandomUuid; if (function_name == "RANDOMUUID") return RandomUuid;
if (function_name == "SIZE") return Size; if (function_name == "SIZE") return Size;
if (function_name == "PROPERTYSIZE") return PropertySize;
if (function_name == "STARTNODE") return StartNode; if (function_name == "STARTNODE") return StartNode;
if (function_name == "TIMESTAMP") return Timestamp; if (function_name == "TIMESTAMP") return Timestamp;
if (function_name == "TOBOOLEAN") return ToBoolean; if (function_name == "TOBOOLEAN") return ToBoolean;

View File

@@ -17,6 +17,7 @@
#include "storage/v2/delta.hpp" #include "storage/v2/delta.hpp"
#include "storage/v2/mvcc.hpp" #include "storage/v2/mvcc.hpp"
#include "storage/v2/property_store.hpp"
#include "storage/v2/property_value.hpp" #include "storage/v2/property_value.hpp"
#include "storage/v2/result.hpp" #include "storage/v2/result.hpp"
#include "storage/v2/storage.hpp" #include "storage/v2/storage.hpp"
@@ -264,6 +265,27 @@ Result<PropertyValue> EdgeAccessor::GetProperty(PropertyId property, View view)
return *std::move(value); return *std::move(value);
} }
Result<uint64_t> EdgeAccessor::GetPropertySize(PropertyId property, View view) const {
if (!storage_->config_.salient.items.properties_on_edges) return 0;
auto guard = std::shared_lock{edge_.ptr->lock};
Delta *delta = edge_.ptr->delta;
if (!delta) {
return edge_.ptr->properties.PropertySize(property);
}
auto property_result = this->GetProperty(property, view);
if (property_result.HasError()) {
return property_result.GetError();
}
auto property_store = storage::PropertyStore();
property_store.SetProperty(property, *property_result);
return property_store.PropertySize(property);
};
Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::Properties(View view) const { Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::Properties(View view) const {
if (!storage_->config_.salient.items.properties_on_edges) return std::map<PropertyId, PropertyValue>{}; if (!storage_->config_.salient.items.properties_on_edges) return std::map<PropertyId, PropertyValue>{};
bool exists = true; bool exists = true;

View File

@@ -82,6 +82,9 @@ class EdgeAccessor final {
/// @throw std::bad_alloc /// @throw std::bad_alloc
Result<PropertyValue> GetProperty(PropertyId property, View view) const; Result<PropertyValue> GetProperty(PropertyId property, View view) const;
/// Returns the size of the encoded edge property in bytes.
Result<uint64_t> GetPropertySize(PropertyId property, View view) const;
/// @throw std::bad_alloc /// @throw std::bad_alloc
Result<std::map<PropertyId, PropertyValue>> Properties(View view) const; Result<std::map<PropertyId, PropertyValue>> Properties(View view) const;

View File

@@ -83,6 +83,18 @@ class NameIdMapper {
return id; return id;
} }
/// This method unlike NameToId does not insert the new property id if not found
/// but just returns either std::nullopt or the value of the property id if it
/// finds it.
virtual std::optional<uint64_t> NameToIdIfExists(const std::string_view name) {
auto name_to_id_acc = name_to_id_.access();
auto found = name_to_id_acc.find(name);
if (found == name_to_id_acc.end()) {
return std::nullopt;
}
return found->id;
}
// NOTE: Currently this function returns a `const std::string &` instead of a // NOTE: Currently this function returns a `const std::string &` instead of a
// `std::string` to avoid making unnecessary copies of the string. // `std::string` to avoid making unnecessary copies of the string.
// Usually, this wouldn't be correct because the accessor to the // Usually, this wouldn't be correct because the accessor to the

View File

@@ -93,6 +93,19 @@ enum class Size : uint8_t {
INT64 = 0x03, INT64 = 0x03,
}; };
uint64_t SizeToByteSize(Size size) {
switch (size) {
case Size::INT8:
return 1;
case Size::INT16:
return 2;
case Size::INT32:
return 4;
case Size::INT64:
return 8;
}
}
// All of these values must have the lowest 4 bits set to zero because they are // All of these values must have the lowest 4 bits set to zero because they are
// used to store two `Size` values as described in the comment above. // used to store two `Size` values as described in the comment above.
enum class Type : uint8_t { enum class Type : uint8_t {
@@ -486,6 +499,27 @@ std::optional<TemporalData> DecodeTemporalData(Reader &reader) {
return TemporalData{static_cast<TemporalType>(*type_value), *microseconds_value}; return TemporalData{static_cast<TemporalType>(*type_value), *microseconds_value};
} }
std::optional<uint64_t> DecodeTemporalDataSize(Reader &reader) {
uint64_t temporal_data_size = 0;
auto metadata = reader.ReadMetadata();
if (!metadata || metadata->type != Type::TEMPORAL_DATA) return std::nullopt;
temporal_data_size += 1;
auto type_value = reader.ReadUint(metadata->id_size);
if (!type_value) return std::nullopt;
temporal_data_size += SizeToByteSize(metadata->id_size);
auto microseconds_value = reader.ReadInt(metadata->payload_size);
if (!microseconds_value) return std::nullopt;
temporal_data_size += SizeToByteSize(metadata->payload_size);
return temporal_data_size;
}
} // namespace } // namespace
// Function used to decode a PropertyValue from a byte stream. // Function used to decode a PropertyValue from a byte stream.
@@ -572,6 +606,92 @@ std::optional<TemporalData> DecodeTemporalData(Reader &reader) {
} }
} }
[[nodiscard]] bool DecodePropertyValueSize(Reader *reader, Type type, Size payload_size, uint64_t &property_size) {
switch (type) {
case Type::EMPTY: {
return false;
}
case Type::NONE:
case Type::BOOL: {
return true;
}
case Type::INT: {
reader->ReadInt(payload_size);
property_size += SizeToByteSize(payload_size);
return true;
}
case Type::DOUBLE: {
reader->ReadDouble(payload_size);
property_size += SizeToByteSize(payload_size);
return true;
}
case Type::STRING: {
auto size = reader->ReadUint(payload_size);
if (!size) return false;
property_size += SizeToByteSize(payload_size);
std::string str_v(*size, '\0');
if (!reader->SkipBytes(*size)) return false;
property_size += *size;
return true;
}
case Type::LIST: {
auto size = reader->ReadUint(payload_size);
if (!size) return false;
uint64_t list_property_size = SizeToByteSize(payload_size);
for (uint64_t i = 0; i < *size; ++i) {
auto metadata = reader->ReadMetadata();
if (!metadata) return false;
list_property_size += 1;
if (!DecodePropertyValueSize(reader, metadata->type, metadata->payload_size, list_property_size)) return false;
}
property_size += list_property_size;
return true;
}
case Type::MAP: {
auto size = reader->ReadUint(payload_size);
if (!size) return false;
uint64_t map_property_size = SizeToByteSize(payload_size);
for (uint64_t i = 0; i < *size; ++i) {
auto metadata = reader->ReadMetadata();
if (!metadata) return false;
map_property_size += 1;
auto key_size = reader->ReadUint(metadata->id_size);
if (!key_size) return false;
map_property_size += SizeToByteSize(metadata->id_size);
std::string key(*key_size, '\0');
if (!reader->ReadBytes(key.data(), *key_size)) return false;
map_property_size += *key_size;
if (!DecodePropertyValueSize(reader, metadata->type, metadata->payload_size, map_property_size)) return false;
}
property_size += map_property_size;
return true;
}
case Type::TEMPORAL_DATA: {
const auto maybe_temporal_data_size = DecodeTemporalDataSize(*reader);
if (!maybe_temporal_data_size) return false;
property_size += *maybe_temporal_data_size;
return true;
}
}
}
// Function used to skip a PropertyValue from a byte stream. // Function used to skip a PropertyValue from a byte stream.
// //
// @sa ComparePropertyValue // @sa ComparePropertyValue
@@ -788,6 +908,27 @@ enum class ExpectedPropertyStatus {
: ExpectedPropertyStatus::GREATER; : ExpectedPropertyStatus::GREATER;
} }
[[nodiscard]] ExpectedPropertyStatus DecodeExpectedPropertySize(Reader *reader, PropertyId expected_property,
uint64_t &size) {
auto metadata = reader->ReadMetadata();
if (!metadata) return ExpectedPropertyStatus::MISSING_DATA;
auto property_id = reader->ReadUint(metadata->id_size);
if (!property_id) return ExpectedPropertyStatus::MISSING_DATA;
if (*property_id == expected_property.AsUint()) {
// Add one byte for reading metadata + add the number of bytes for the property key
size += (1 + SizeToByteSize(metadata->id_size));
if (!DecodePropertyValueSize(reader, metadata->type, metadata->payload_size, size))
return ExpectedPropertyStatus::MISSING_DATA;
return ExpectedPropertyStatus::EQUAL;
}
// Don't load the value if this isn't the expected property.
if (!SkipPropertyValue(reader, metadata->type, metadata->payload_size)) return ExpectedPropertyStatus::MISSING_DATA;
return (*property_id < expected_property.AsUint()) ? ExpectedPropertyStatus::SMALLER
: ExpectedPropertyStatus::GREATER;
}
// Function used to check a property exists (PropertyId) from a byte stream. // Function used to check a property exists (PropertyId) from a byte stream.
// It will skip the encoded PropertyValue. // It will skip the encoded PropertyValue.
// //
@@ -875,6 +1016,13 @@ enum class ExpectedPropertyStatus {
} }
} }
[[nodiscard]] ExpectedPropertyStatus FindSpecificPropertySize(Reader *reader, PropertyId property, uint64_t &size) {
ExpectedPropertyStatus ret = ExpectedPropertyStatus::SMALLER;
while ((ret = DecodeExpectedPropertySize(reader, property, size)) == ExpectedPropertyStatus::SMALLER) {
}
return ret;
}
// Function used to find if property is set. It relies on the fact that the properties // Function used to find if property is set. It relies on the fact that the properties
// are sorted (by ID) in the buffer. // are sorted (by ID) in the buffer.
// //
@@ -983,6 +1131,31 @@ std::pair<uint64_t, uint8_t *> GetSizeData(const uint8_t *buffer) {
return {size, data}; return {size, data};
} }
struct BufferInfo {
uint64_t size;
uint8_t *data{nullptr};
bool in_local_buffer;
};
template <size_t N>
BufferInfo GetBufferInfo(const uint8_t (&buffer)[N]) {
uint64_t size = 0;
const uint8_t *data = nullptr;
bool in_local_buffer = false;
std::tie(size, data) = GetSizeData(buffer);
if (size % 8 != 0) {
// We are storing the data in the local buffer.
size = sizeof(buffer) - 1;
data = &buffer[1];
in_local_buffer = true;
}
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
auto *non_const_data = const_cast<uint8_t *>(data);
return {size, non_const_data, in_local_buffer};
}
void SetSizeData(uint8_t *buffer, uint64_t size, uint8_t *data) { void SetSizeData(uint8_t *buffer, uint64_t size, uint8_t *data) {
memcpy(buffer, &size, sizeof(uint64_t)); memcpy(buffer, &size, sizeof(uint64_t));
memcpy(buffer + sizeof(uint64_t), &data, sizeof(uint8_t *)); memcpy(buffer + sizeof(uint64_t), &data, sizeof(uint8_t *));
@@ -1023,30 +1196,27 @@ PropertyStore::~PropertyStore() {
} }
PropertyValue PropertyStore::GetProperty(PropertyId property) const { PropertyValue PropertyStore::GetProperty(PropertyId property) const {
uint64_t size; BufferInfo buffer_info = GetBufferInfo(buffer_);
const uint8_t *data; Reader reader(buffer_info.data, buffer_info.size);
std::tie(size, data) = GetSizeData(buffer_);
if (size % 8 != 0) {
// We are storing the data in the local buffer.
size = sizeof(buffer_) - 1;
data = &buffer_[1];
}
Reader reader(data, size);
PropertyValue value; PropertyValue value;
if (FindSpecificProperty(&reader, property, value) != ExpectedPropertyStatus::EQUAL) return {}; if (FindSpecificProperty(&reader, property, value) != ExpectedPropertyStatus::EQUAL) return {};
return value; return value;
} }
uint64_t PropertyStore::PropertySize(PropertyId property) const {
auto data_size_localbuffer = GetBufferInfo(buffer_);
Reader reader(data_size_localbuffer.data, data_size_localbuffer.size);
uint64_t property_size = 0;
if (FindSpecificPropertySize(&reader, property, property_size) != ExpectedPropertyStatus::EQUAL) return 0;
return property_size;
}
bool PropertyStore::HasProperty(PropertyId property) const { bool PropertyStore::HasProperty(PropertyId property) const {
uint64_t size; BufferInfo buffer_info = GetBufferInfo(buffer_);
const uint8_t *data; Reader reader(buffer_info.data, buffer_info.size);
std::tie(size, data) = GetSizeData(buffer_);
if (size % 8 != 0) {
// We are storing the data in the local buffer.
size = sizeof(buffer_) - 1;
data = &buffer_[1];
}
Reader reader(data, size);
return ExistsSpecificProperty(&reader, property) == ExpectedPropertyStatus::EQUAL; return ExistsSpecificProperty(&reader, property) == ExpectedPropertyStatus::EQUAL;
} }
@@ -1081,32 +1251,20 @@ std::optional<std::vector<PropertyValue>> PropertyStore::ExtractPropertyValues(
} }
bool PropertyStore::IsPropertyEqual(PropertyId property, const PropertyValue &value) const { bool PropertyStore::IsPropertyEqual(PropertyId property, const PropertyValue &value) const {
uint64_t size; BufferInfo buffer_info = GetBufferInfo(buffer_);
const uint8_t *data; Reader reader(buffer_info.data, buffer_info.size);
std::tie(size, data) = GetSizeData(buffer_);
if (size % 8 != 0) {
// We are storing the data in the local buffer.
size = sizeof(buffer_) - 1;
data = &buffer_[1];
}
Reader reader(data, size);
auto info = FindSpecificPropertyAndBufferInfo(&reader, property); auto info = FindSpecificPropertyAndBufferInfo(&reader, property);
if (info.property_size == 0) return value.IsNull(); if (info.property_size == 0) return value.IsNull();
Reader prop_reader(data + info.property_begin, info.property_size); Reader prop_reader(buffer_info.data + info.property_begin, info.property_size);
if (!CompareExpectedProperty(&prop_reader, property, value)) return false; if (!CompareExpectedProperty(&prop_reader, property, value)) return false;
return prop_reader.GetPosition() == info.property_size; return prop_reader.GetPosition() == info.property_size;
} }
std::map<PropertyId, PropertyValue> PropertyStore::Properties() const { std::map<PropertyId, PropertyValue> PropertyStore::Properties() const {
uint64_t size; BufferInfo buffer_info = GetBufferInfo(buffer_);
const uint8_t *data; Reader reader(buffer_info.data, buffer_info.size);
std::tie(size, data) = GetSizeData(buffer_);
if (size % 8 != 0) {
// We are storing the data in the local buffer.
size = sizeof(buffer_) - 1;
data = &buffer_[1];
}
Reader reader(data, size);
std::map<PropertyId, PropertyValue> props; std::map<PropertyId, PropertyValue> props;
while (true) { while (true) {
PropertyValue value; PropertyValue value;
@@ -1340,33 +1498,20 @@ bool PropertyStore::InitProperties(std::vector<std::pair<storage::PropertyId, st
} }
bool PropertyStore::ClearProperties() { bool PropertyStore::ClearProperties() {
bool in_local_buffer = false; BufferInfo buffer_info = GetBufferInfo(buffer_);
uint64_t size;
uint8_t *data; if (!buffer_info.size) return false;
std::tie(size, data) = GetSizeData(buffer_); if (!buffer_info.in_local_buffer) delete[] buffer_info.data;
if (size % 8 != 0) {
// We are storing the data in the local buffer.
size = sizeof(buffer_) - 1;
data = &buffer_[1];
in_local_buffer = true;
}
if (!size) return false;
if (!in_local_buffer) delete[] data;
SetSizeData(buffer_, 0, nullptr); SetSizeData(buffer_, 0, nullptr);
return true; return true;
} }
std::string PropertyStore::StringBuffer() const { std::string PropertyStore::StringBuffer() const {
uint64_t size = 0; BufferInfo buffer_info = GetBufferInfo(buffer_);
const uint8_t *data = nullptr;
std::tie(size, data) = GetSizeData(buffer_); std::string arr(buffer_info.size, ' ');
if (size % 8 != 0) { // We are storing the data in the local buffer. for (uint i = 0; i < buffer_info.size; ++i) {
size = sizeof(buffer_) - 1; arr[i] = static_cast<char>(buffer_info.data[i]);
data = &buffer_[1];
}
std::string arr(size, ' ');
for (uint i = 0; i < size; ++i) {
arr[i] = static_cast<char>(data[i]);
} }
return arr; return arr;
} }

View File

@@ -45,6 +45,11 @@ class PropertyStore {
/// @throw std::bad_alloc /// @throw std::bad_alloc
PropertyValue GetProperty(PropertyId property) const; PropertyValue GetProperty(PropertyId property) const;
/// Returns the size of the encoded property in bytes.
/// Returns 0 if the property does not exist.
/// The time complexity of this function is O(n).
uint64_t PropertySize(PropertyId property) const;
/// Checks whether the property `property` exists in the store. The time /// Checks whether the property `property` exists in the store. The time
/// complexity of this function is O(n). /// complexity of this function is O(n).
bool HasProperty(PropertyId property) const; bool HasProperty(PropertyId property) const;

View File

@@ -250,6 +250,10 @@ class Storage {
PropertyId NameToProperty(std::string_view name) { return storage_->NameToProperty(name); } PropertyId NameToProperty(std::string_view name) { return storage_->NameToProperty(name); }
std::optional<PropertyId> NameToPropertyIfExists(std::string_view name) const {
return storage_->NameToPropertyIfExists(name);
}
EdgeTypeId NameToEdgeType(std::string_view name) { return storage_->NameToEdgeType(name); } EdgeTypeId NameToEdgeType(std::string_view name) { return storage_->NameToEdgeType(name); }
StorageMode GetCreationStorageMode() const noexcept; StorageMode GetCreationStorageMode() const noexcept;
@@ -318,6 +322,14 @@ class Storage {
return PropertyId::FromUint(name_id_mapper_->NameToId(name)); return PropertyId::FromUint(name_id_mapper_->NameToId(name));
} }
std::optional<PropertyId> NameToPropertyIfExists(std::string_view name) const {
const auto id = name_id_mapper_->NameToIdIfExists(name);
if (!id) {
return std::nullopt;
}
return PropertyId::FromUint(*id);
}
EdgeTypeId NameToEdgeType(const std::string_view name) const { EdgeTypeId NameToEdgeType(const std::string_view name) const {
return EdgeTypeId::FromUint(name_id_mapper_->NameToId(name)); return EdgeTypeId::FromUint(name_id_mapper_->NameToId(name));
} }

View File

@@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd. // Copyright 2024 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -438,6 +438,26 @@ Result<PropertyValue> VertexAccessor::GetProperty(PropertyId property, View view
return std::move(value); return std::move(value);
} }
Result<uint64_t> VertexAccessor::GetPropertySize(PropertyId property, View view) const {
{
auto guard = std::shared_lock{vertex_->lock};
Delta *delta = vertex_->delta;
if (!delta) {
return vertex_->properties.PropertySize(property);
}
}
auto property_result = this->GetProperty(property, view);
if (property_result.HasError()) {
return property_result.GetError();
}
auto property_store = storage::PropertyStore();
property_store.SetProperty(property, *property_result);
return property_store.PropertySize(property);
};
Result<std::map<PropertyId, PropertyValue>> VertexAccessor::Properties(View view) const { Result<std::map<PropertyId, PropertyValue>> VertexAccessor::Properties(View view) const {
bool exists = true; bool exists = true;
bool deleted = false; bool deleted = false;

View File

@@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd. // Copyright 2024 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -80,6 +80,9 @@ class VertexAccessor final {
/// @throw std::bad_alloc /// @throw std::bad_alloc
Result<PropertyValue> GetProperty(PropertyId property, View view) const; Result<PropertyValue> GetProperty(PropertyId property, View view) const;
/// Returns the size of the encoded vertex property in bytes.
Result<uint64_t> GetPropertySize(PropertyId property, View view) const;
/// @throw std::bad_alloc /// @throw std::bad_alloc
Result<std::map<PropertyId, PropertyValue>> Properties(View view) const; Result<std::map<PropertyId, PropertyValue>> Properties(View view) const;

View File

@@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd. // Copyright 2024 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -15,6 +15,8 @@
#include "utils/flag_validation.hpp" #include "utils/flag_validation.hpp"
#include <type_traits>
// NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables) // NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
DEFINE_VALIDATED_uint64(delta_chain_cache_threshold, 128, DEFINE_VALIDATED_uint64(delta_chain_cache_threshold, 128,
"The threshold for when to cache long delta chains. This is used for heavy read + write " "The threshold for when to cache long delta chains. This is used for heavy read + write "
@@ -31,6 +33,7 @@ auto FetchHelper(VertexInfoCache const &caches, Func &&getCache, View view, Keys
// check empty first, cheaper than the relative cost of doing an actual hash + find // check empty first, cheaper than the relative cost of doing an actual hash + find
if (cache.empty()) return std::nullopt; if (cache.empty()) return std::nullopt;
return std::nullopt;
// defer building the key, maybe a cost at construction // defer building the key, maybe a cost at construction
using key_type = typename std::remove_cvref_t<decltype(cache)>::key_type; using key_type = typename std::remove_cvref_t<decltype(cache)>::key_type;
auto const it = cache.find(key_type{std::forward<Keys>(keys)...}); auto const it = cache.find(key_type{std::forward<Keys>(keys)...});
@@ -76,8 +79,8 @@ void VertexInfoCache::Invalidate(Vertex const *vertex) {
new_.outDegreeCache_.erase(vertex); new_.outDegreeCache_.erase(vertex);
// aggressive cache invalidation, TODO: be smarter // aggressive cache invalidation, TODO: be smarter
new_.hasLabelCache_.clear(); // new_.hasLabelCache_.clear();
new_.propertyValueCache_.clear(); // new_.propertyValueCache_.clear();
new_.inEdgesCache_.clear(); new_.inEdgesCache_.clear();
new_.outEdgesCache_.clear(); new_.outEdgesCache_.clear();
} }
@@ -87,36 +90,38 @@ auto VertexInfoCache::GetLabels(View view, Vertex const *vertex) const
return FetchHelper<std::vector<LabelId>>(*this, std::mem_fn(&VertexInfoCache::Caches::labelCache_), view, vertex); return FetchHelper<std::vector<LabelId>>(*this, std::mem_fn(&VertexInfoCache::Caches::labelCache_), view, vertex);
} }
void VertexInfoCache::StoreLabels(View view, Vertex const *vertex, const std::vector<LabelId> &res) { void VertexInfoCache::StoreLabels(View view, Vertex const *vertex, const std::vector<LabelId> &res) {
Store(res, *this, std::mem_fn(&Caches::labelCache_), view, vertex); // Store(res, *this, std::mem_fn(&Caches::labelCache_), view, vertex);
} }
auto VertexInfoCache::GetHasLabel(View view, Vertex const *vertex, LabelId label) const -> std::optional<bool> { auto VertexInfoCache::GetHasLabel(View view, Vertex const *vertex, LabelId label) const -> std::optional<bool> {
return FetchHelper<bool>(*this, std::mem_fn(&Caches::hasLabelCache_), view, vertex, label); // return FetchHelper<bool>(*this, std::mem_fn(&Caches::hasLabelCache_), view, vertex, label);
return std::nullopt;
} }
void VertexInfoCache::StoreHasLabel(View view, Vertex const *vertex, LabelId label, bool res) { void VertexInfoCache::StoreHasLabel(View view, Vertex const *vertex, LabelId label, bool res) {
Store(res, *this, std::mem_fn(&Caches::hasLabelCache_), view, vertex, label); // Store(res, *this, std::mem_fn(&Caches::hasLabelCache_), view, vertex, label);
} }
void VertexInfoCache::Invalidate(Vertex const *vertex, LabelId label) { void VertexInfoCache::Invalidate(Vertex const *vertex, LabelId label) {
new_.labelCache_.erase(vertex); new_.labelCache_.erase(vertex);
new_.hasLabelCache_.erase(std::tuple{vertex, label}); // new_.hasLabelCache_.erase(std::tuple{vertex, label});
} }
auto VertexInfoCache::GetProperty(View view, Vertex const *vertex, PropertyId property) const auto VertexInfoCache::GetProperty(View view, Vertex const *vertex, PropertyId property) const
-> std::optional<std::reference_wrapper<PropertyValue const>> { -> std::optional<std::reference_wrapper<PropertyValue const>> {
return FetchHelper<PropertyValue>(*this, std::mem_fn(&Caches::propertyValueCache_), view, vertex, property); // return FetchHelper<PropertyValue>(*this, std::mem_fn(&Caches::propertyValueCache_), view, vertex, property);
return std::nullopt;
} }
void VertexInfoCache::StoreProperty(View view, Vertex const *vertex, PropertyId property, PropertyValue value) { void VertexInfoCache::StoreProperty(View view, Vertex const *vertex, PropertyId property, PropertyValue value) {
Store(std::move(value), *this, std::mem_fn(&Caches::propertyValueCache_), view, vertex, property); // Store(std::move(value), *this, std::mem_fn(&Caches::propertyValueCache_), view, vertex, property);
} }
auto VertexInfoCache::GetProperties(View view, Vertex const *vertex) const auto VertexInfoCache::GetProperties(View view, Vertex const *vertex) const
-> std::optional<std::reference_wrapper<std::map<PropertyId, PropertyValue> const>> { -> std::optional<std::reference_wrapper<std::map<PropertyId, PropertyValue> const>> {
return FetchHelper<std::map<PropertyId, PropertyValue>>(*this, std::mem_fn(&Caches::propertiesCache_), view, vertex); return FetchHelper<std::map<PropertyId, PropertyValue>>(*this, std::mem_fn(&Caches::propertiesCache_), view, vertex);
} }
void VertexInfoCache::StoreProperties(View view, Vertex const *vertex, std::map<PropertyId, PropertyValue> properties) { void VertexInfoCache::StoreProperties(View view, Vertex const *vertex, std::map<PropertyId, PropertyValue> properties) {
Store(std::move(properties), *this, std::mem_fn(&Caches::propertiesCache_), view, vertex); // Store(std::move(properties), *this, std::mem_fn(&Caches::propertiesCache_), view, vertex);
} }
void VertexInfoCache::Invalidate(Vertex const *vertex, PropertyId property_key) { void VertexInfoCache::Invalidate(Vertex const *vertex, PropertyId property_key) {
new_.propertiesCache_.erase(vertex); new_.propertiesCache_.erase(vertex);
new_.propertyValueCache_.erase(std::tuple{vertex, property_key}); // new_.propertyValueCache_.erase(std::tuple{vertex, property_key});
} }
auto VertexInfoCache::GetInEdges(View view, Vertex const *src_vertex, Vertex const *dst_vertex, auto VertexInfoCache::GetInEdges(View view, Vertex const *src_vertex, Vertex const *dst_vertex,
@@ -126,8 +131,8 @@ auto VertexInfoCache::GetInEdges(View view, Vertex const *src_vertex, Vertex con
} }
void VertexInfoCache::StoreInEdges(View view, Vertex const *src_vertex, Vertex const *dst_vertex, void VertexInfoCache::StoreInEdges(View view, Vertex const *src_vertex, Vertex const *dst_vertex,
std::vector<EdgeTypeId> edge_types, EdgeStore in_edges) { std::vector<EdgeTypeId> edge_types, EdgeStore in_edges) {
Store(std::move(in_edges), *this, std::mem_fn(&Caches::inEdgesCache_), view, src_vertex, dst_vertex, // Store(std::move(in_edges), *this, std::mem_fn(&Caches::inEdgesCache_), view, src_vertex, dst_vertex,
std::move(edge_types)); // std::move(edge_types));
} }
auto VertexInfoCache::GetOutEdges(View view, Vertex const *src_vertex, Vertex const *dst_vertex, auto VertexInfoCache::GetOutEdges(View view, Vertex const *src_vertex, Vertex const *dst_vertex,
const std::vector<EdgeTypeId> &edge_types) const const std::vector<EdgeTypeId> &edge_types) const
@@ -136,8 +141,8 @@ auto VertexInfoCache::GetOutEdges(View view, Vertex const *src_vertex, Vertex co
} }
void VertexInfoCache::StoreOutEdges(View view, Vertex const *src_vertex, Vertex const *dst_vertex, void VertexInfoCache::StoreOutEdges(View view, Vertex const *src_vertex, Vertex const *dst_vertex,
std::vector<EdgeTypeId> edge_types, EdgeStore out_edges) { std::vector<EdgeTypeId> edge_types, EdgeStore out_edges) {
Store(std::move(out_edges), *this, std::mem_fn(&Caches::outEdgesCache_), view, src_vertex, dst_vertex, // Store(std::move(out_edges), *this, std::mem_fn(&Caches::outEdgesCache_), view, src_vertex, dst_vertex,
std::move(edge_types)); // std::move(edge_types));
} }
auto VertexInfoCache::GetInDegree(View view, Vertex const *vertex) const -> std::optional<std::size_t> { auto VertexInfoCache::GetInDegree(View view, Vertex const *vertex) const -> std::optional<std::size_t> {
@@ -145,7 +150,7 @@ auto VertexInfoCache::GetInDegree(View view, Vertex const *vertex) const -> std:
} }
void VertexInfoCache::StoreInDegree(View view, Vertex const *vertex, std::size_t in_degree) { void VertexInfoCache::StoreInDegree(View view, Vertex const *vertex, std::size_t in_degree) {
Store(in_degree, *this, std::mem_fn(&Caches::inDegreeCache_), view, vertex); // Store(in_degree, *this, std::mem_fn(&Caches::inDegreeCache_), view, vertex);
} }
auto VertexInfoCache::GetOutDegree(View view, Vertex const *vertex) const -> std::optional<std::size_t> { auto VertexInfoCache::GetOutDegree(View view, Vertex const *vertex) const -> std::optional<std::size_t> {
@@ -153,7 +158,7 @@ auto VertexInfoCache::GetOutDegree(View view, Vertex const *vertex) const -> std
} }
void VertexInfoCache::StoreOutDegree(View view, Vertex const *vertex, std::size_t out_degree) { void VertexInfoCache::StoreOutDegree(View view, Vertex const *vertex, std::size_t out_degree) {
Store(out_degree, *this, std::mem_fn(&Caches::outDegreeCache_), view, vertex); // Store(out_degree, *this, std::mem_fn(&Caches::outDegreeCache_), view, vertex);
} }
void VertexInfoCache::Invalidate(Vertex const *vertex, EdgeTypeId /*unused*/, EdgeDirection direction) { void VertexInfoCache::Invalidate(Vertex const *vertex, EdgeTypeId /*unused*/, EdgeDirection direction) {
@@ -175,8 +180,8 @@ void VertexInfoCache::Clear() {
void VertexInfoCache::Caches::Clear() { void VertexInfoCache::Caches::Clear() {
existsCache_.clear(); existsCache_.clear();
deletedCache_.clear(); deletedCache_.clear();
hasLabelCache_.clear(); // hasLabelCache_.clear();
propertyValueCache_.clear(); // propertyValueCache_.clear();
labelCache_.clear(); labelCache_.clear();
propertiesCache_.clear(); propertiesCache_.clear();
inEdgesCache_.clear(); inEdgesCache_.clear();

View File

@@ -76,6 +76,7 @@ add_subdirectory(queries)
add_subdirectory(query_modules_storage_modes) add_subdirectory(query_modules_storage_modes)
add_subdirectory(garbage_collection) add_subdirectory(garbage_collection)
add_subdirectory(query_planning) add_subdirectory(query_planning)
add_subdirectory(awesome_functions)
if (MG_EXPERIMENTAL_HIGH_AVAILABILITY) if (MG_EXPERIMENTAL_HIGH_AVAILABILITY)
add_subdirectory(high_availability_experimental) add_subdirectory(high_availability_experimental)

View File

@@ -0,0 +1,6 @@
function(copy_awesome_functions_e2e_python_files FILE_NAME)
copy_e2e_python_files(awesome_functions ${FILE_NAME})
endfunction()
copy_awesome_functions_e2e_python_files(common.py)
copy_awesome_functions_e2e_python_files(awesome_functions.py)

View File

@@ -0,0 +1,269 @@
# Copyright 2023 Memgraph Ltd.
#
# Use of this software is governed by the Business Source License
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
# License, and you may not use this file except in compliance with the Business Source License.
#
# As of the Change Date specified in that file, in accordance with
# the Business Source License, use of this software will be governed
# by the Apache License, Version 2.0, included in the file
# licenses/APL.txt.
import sys
import pytest
from common import get_bytes, memgraph
def test_property_size_on_null_prop(memgraph):
memgraph.execute(
"""
CREATE (n:Node)
SET n.null_prop = null;
"""
)
null_bytes = get_bytes(memgraph, "null_prop")
# No property stored, no bytes allocated
assert null_bytes == 0
def test_property_size_on_bool_prop(memgraph):
memgraph.execute(
"""
CREATE (n:Node)
SET n.bool_prop = True;
"""
)
bool_bytes = get_bytes(memgraph, "bool_prop")
# 1 byte metadata, 1 byte prop id, but value is encoded in the metadata
assert bool_bytes == 2
def test_property_size_on_one_byte_int_prop(memgraph):
memgraph.execute(
"""
CREATE (n:Node)
SET n.S_int_prop = 4;
"""
)
s_int_bytes = get_bytes(memgraph, "S_int_prop")
# 1 byte metadata, 1 byte prop id + payload size 1 byte to store the int
assert s_int_bytes == 3
def test_property_size_on_two_byte_int_prop(memgraph):
memgraph.execute(
"""
CREATE (n:Node)
SET n.M_int_prop = 500;
"""
)
m_int_bytes = get_bytes(memgraph, "M_int_prop")
# 1 byte metadata, 1 byte prop id + payload size 2 bytes to store the int
assert m_int_bytes == 4
def test_property_size_on_four_byte_int_prop(memgraph):
memgraph.execute(
"""
CREATE (n:Node)
SET n.L_int_prop = 1000000000;
"""
)
l_int_bytes = get_bytes(memgraph, "L_int_prop")
# 1 byte metadata, 1 byte prop id + payload size 4 bytes to store the int
assert l_int_bytes == 6
def test_property_size_on_eight_byte_int_prop(memgraph):
memgraph.execute(
"""
CREATE (n:Node)
SET n.XL_int_prop = 1000000000000;
"""
)
xl_int_bytes = get_bytes(memgraph, "XL_int_prop")
# 1 byte metadata, 1 byte prop id + payload size 8 bytes to store the int
assert xl_int_bytes == 10
def test_property_size_on_float_prop(memgraph):
memgraph.execute(
"""
CREATE (n:Node)
SET n.float_prop = 4.0;
"""
)
float_bytes = get_bytes(memgraph, "float_prop")
# 1 byte metadata, 1 byte prop id + payload size 8 bytes to store the float
assert float_bytes == 10
def test_property_size_on_string_prop(memgraph):
memgraph.execute(
"""
CREATE (n:Node)
SET n.str_prop = 'str_value';
"""
)
str_bytes = get_bytes(memgraph, "str_prop")
# 1 byte metadata
# 1 byte prop id
# - the payload size contains the amount of bytes stored for the size in the next sequence
# X bytes for the length of the string (1, 2, 4 or 8 bytes) -> "str_value" has 1 byte for the length of 9
# Y bytes for the string content -> 9 bytes for "str_value"
assert str_bytes == 12
def test_property_size_on_list_prop(memgraph):
memgraph.execute(
"""
CREATE (n:Node)
SET n.list_prop = [1, 2, 3];
"""
)
list_bytes = get_bytes(memgraph, "list_prop")
# 1 byte metadata
# 1 byte prop id
# - the payload size contains the amount of bytes stored for the size of the list
# X bytes for the size of the list (1, 2, 4 or 8 bytes)
# for each list element:
# - 1 byte for the metadata
# - the amount of bytes for the payload of the type (a small int is 1 additional byte)
# in this case 1 + 1 + 3 * (1 + 1)
assert list_bytes == 9
def test_property_size_on_map_prop(memgraph):
memgraph.execute(
"""
CREATE (n:Node)
SET n.map_prop = {key1: 'value', key2: 4};
"""
)
map_bytes = get_bytes(memgraph, "map_prop")
# 1 byte metadata
# 1 byte prop id
# - the payload size contains the amount of bytes stored for the size of the map
# X bytes for the size of the map (1, 2, 4 or 8 bytes - in this case 1)
# for every map element:
# - 1 byte for metadata
# - 1, 2, 4 or 8 bytes for the key length (read from the metadata payload) -> this case 1
# - Y bytes for the key content -> this case 4
# - Z amount of bytes for the type
# - for 'value' -> 1 byte for size and 5 for length
# - for 4 -> 1 byte for content read from payload
# total: 1 + 1 + (1 + 1 + 4 + (1 + 5)) + (1 + 1 + 4 + (1))
assert map_bytes == 22
def test_property_size_on_date_prop(memgraph):
memgraph.execute(
"""
CREATE (n:Node)
SET n.date_prop = date('2023-01-01');
"""
)
date_bytes = get_bytes(memgraph, "date_prop")
# 1 byte metadata (to see that it's temporal data)
# 1 byte prop id
# 1 byte metadata
# - type is again the same
# - id field contains the length of the specific temporal type (1, 2, 4 or 8 bytes) -> probably always 1
# - payload field contains the length of the microseconds (1, 2, 4, or 8 bytes) -> probably always 8
assert date_bytes == 12
def test_property_size_on_local_time_prop(memgraph):
memgraph.execute(
"""
CREATE (n:Node)
SET n.localtime_prop = localtime('23:00:00');
"""
)
local_time_bytes = get_bytes(memgraph, "localtime_prop")
# 1 byte metadata (to see that it's temporal data)
# 1 byte prop id
# 1 byte metadata
# - type is again the same
# - id field contains the length of the specific temporal type (1, 2, 4 or 8 bytes) -> probably always 1
# - payload field contains the length of the microseconds (1, 2, 4, or 8 bytes) -> probably always 8
assert local_time_bytes == 12
def test_property_size_on_local_date_time_prop(memgraph):
memgraph.execute(
"""
CREATE (n:Node)
SET n.localdatetime_prop = localdatetime('2022-01-01T23:00:00');
"""
)
local_date_time_bytes = get_bytes(memgraph, "localdatetime_prop")
# 1 byte metadata (to see that it's temporal data)
# 1 byte prop id
# 1 byte metadata
# - type is again the same
# - id field contains the length of the specific temporal type (1, 2, 4 or 8 bytes) -> probably always 1
# - payload field contains the length of the microseconds (1, 2, 4, or 8 bytes) -> probably always 8
assert local_date_time_bytes == 12
def test_property_size_on_duration_prop(memgraph):
memgraph.execute(
"""
CREATE (n:Node)
SET n.duration_prop = duration('P5DT2M2.33S');
"""
)
duration_bytes = get_bytes(memgraph, "duration_prop")
# 1 byte metadata (to see that it's temporal data)
# 1 byte prop id
# 1 byte metadata
# - type is again the same
# - id field contains the length of the specific temporal type (1, 2, 4 or 8 bytes) -> probably always 1
# - payload field contains the length of the microseconds (1, 2, 4, or 8 bytes) -> probably always 8
assert duration_bytes == 12
def test_property_size_on_nonexistent_prop(memgraph):
memgraph.execute(
"""
CREATE (n:Node);
"""
)
nonexistent_bytes = get_bytes(memgraph, "nonexistent_prop")
assert nonexistent_bytes == 0
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

@@ -0,0 +1,29 @@
# Copyright 2023 Memgraph Ltd.
#
# Use of this software is governed by the Business Source License
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
# License, and you may not use this file except in compliance with the Business Source License.
#
# As of the Change Date specified in that file, in accordance with
# the Business Source License, use of this software will be governed
# by the Apache License, Version 2.0, included in the file
# licenses/APL.txt.
import pytest
from gqlalchemy import Memgraph
@pytest.fixture
def memgraph(**kwargs) -> Memgraph:
memgraph = Memgraph()
yield memgraph
memgraph.drop_indexes()
memgraph.ensure_constraints([])
memgraph.drop_database()
def get_bytes(memgraph, prop_name):
res = list(memgraph.execute_and_fetch(f"MATCH (n) RETURN propertySize(n, '{prop_name}') AS size"))
return res[0]["size"]

View File

@@ -0,0 +1,14 @@
awesome_functions_cluster: &awesome_functions_cluster
cluster:
main:
args: ["--bolt-port", "7687", "--log-level=TRACE"]
log_file: "awesome_functions.log"
setup_queries: []
validation_queries: []
workloads:
- name: "Awesome Functions"
binary: "tests/e2e/pytest_runner.sh"
args: ["awesome_functions/awesome_functions.py"]
<<: *awesome_functions_cluster

View File

@@ -1,4 +1,4 @@
// Copyright 2023 Memgraph Ltd. // Copyright 2024 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -113,7 +113,6 @@ struct ConsumerTest : public ::testing::Test {
void SeedTopicWithInt(const std::string &topic_name, int value) { void SeedTopicWithInt(const std::string &topic_name, int value) {
std::array<char, sizeof(int)> int_as_char{}; std::array<char, sizeof(int)> int_as_char{};
std::memcpy(int_as_char.data(), &value, int_as_char.size()); std::memcpy(int_as_char.data(), &value, int_as_char.size());
cluster.SeedTopic(topic_name, int_as_char); cluster.SeedTopic(topic_name, int_as_char);
} }

View File

@@ -1,4 +1,4 @@
// Copyright 2022 Memgraph Ltd. // Copyright 2024 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -78,10 +78,6 @@ void KafkaClusterMock::CreateTopic(const std::string &topic_name) {
} }
} }
void KafkaClusterMock::SeedTopic(const std::string &topic_name, std::string_view message) {
SeedTopic(topic_name, std::span{message.data(), message.size()});
}
void KafkaClusterMock::SeedTopic(const std::string &topic_name, std::span<const char> message) { void KafkaClusterMock::SeedTopic(const std::string &topic_name, std::span<const char> message) {
char errstr[256] = {'\0'}; char errstr[256] = {'\0'};
std::string bootstraps_servers = Bootstraps(); std::string bootstraps_servers = Bootstraps();

View File

@@ -1,4 +1,4 @@
// Copyright 2021 Memgraph Ltd. // Copyright 2024 Memgraph Ltd.
// //
// Use of this software is governed by the Business Source License // Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -41,7 +41,6 @@ class KafkaClusterMock {
std::string Bootstraps() const; std::string Bootstraps() const;
void CreateTopic(const std::string &topic_name); void CreateTopic(const std::string &topic_name);
void SeedTopic(const std::string &topic_name, std::span<const char> message); void SeedTopic(const std::string &topic_name, std::span<const char> message);
void SeedTopic(const std::string &topic_name, std::string_view message);
private: private:
RdKafkaUniquePtr rk_{nullptr}; RdKafkaUniquePtr rk_{nullptr};