Merge branch 'master' into fix-valuetype-function-on-all-data-types

This commit is contained in:
Josip Mrden 2024-02-19 13:59:42 +01:00
commit a4f2035943
15 changed files with 642 additions and 61 deletions

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

@ -447,6 +447,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);
@ -1332,6 +1355,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;
} }
bool PropertyStore::HasProperty(PropertyId property) const { uint64_t PropertyStore::PropertySize(PropertyId property) const {
uint64_t size; auto data_size_localbuffer = GetBufferInfo(buffer_);
const uint8_t *data; Reader reader(data_size_localbuffer.data, data_size_localbuffer.size);
std::tie(size, data) = GetSizeData(buffer_);
if (size % 8 != 0) { uint64_t property_size = 0;
// We are storing the data in the local buffer. if (FindSpecificPropertySize(&reader, property, property_size) != ExpectedPropertyStatus::EQUAL) return 0;
size = sizeof(buffer_) - 1; return property_size;
data = &buffer_[1];
} }
Reader reader(data, size);
bool PropertyStore::HasProperty(PropertyId property) const {
BufferInfo buffer_info = GetBufferInfo(buffer_);
Reader reader(buffer_info.data, buffer_info.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

@ -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