diff --git a/src/query/db_accessor.hpp b/src/query/db_accessor.hpp index 71b997d9e..e10102ee5 100644 --- a/src/query/db_accessor.hpp +++ b/src/query/db_accessor.hpp @@ -54,6 +54,10 @@ class EdgeAccessor final { return impl_.GetProperty(key, view); } + storage::Result GetPropertySize(storage::PropertyId key, storage::View view) const { + return impl_.GetPropertySize(key, view); + } + storage::Result SetProperty(storage::PropertyId key, const storage::PropertyValue &value) { return impl_.SetProperty(key, value); } @@ -129,6 +133,10 @@ class VertexAccessor final { return impl_.GetProperty(key, view); } + storage::Result GetPropertySize(storage::PropertyId key, storage::View view) const { + return impl_.GetPropertySize(key, view); + } + storage::Result SetProperty(storage::PropertyId key, const storage::PropertyValue &value) { return impl_.SetProperty(key, value); } @@ -268,6 +276,10 @@ class SubgraphVertexAccessor final { return impl_.GetProperty(view, key); } + storage::Result GetPropertySize(storage::PropertyId key, storage::View view) const { + return impl_.GetPropertySize(key, view); + } + storage::Gid Gid() const noexcept { return impl_.Gid(); } storage::Result 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); } + std::optional NameToPropertyIfExists(std::string_view name) const { + return accessor_->NameToPropertyIfExists(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); } diff --git a/src/query/interpret/awesome_memgraph_functions.cpp b/src/query/interpret/awesome_memgraph_functions.cpp index c6961b42f..8e144e06b 100644 --- a/src/query/interpret/awesome_memgraph_functions.cpp +++ b/src/query/interpret/awesome_memgraph_functions.cpp @@ -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>("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(property_size), ctx.memory); +} + TypedValue StartNode(const TypedValue *args, int64_t nargs, const FunctionContext &ctx) { FType>("startNode", args, nargs); if (args[0].IsNull()) return TypedValue(ctx.memory); @@ -1332,6 +1355,7 @@ std::function EdgeAccessor::GetProperty(PropertyId property, View view) return *std::move(value); } +Result 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> EdgeAccessor::Properties(View view) const { if (!storage_->config_.salient.items.properties_on_edges) return std::map{}; bool exists = true; diff --git a/src/storage/v2/edge_accessor.hpp b/src/storage/v2/edge_accessor.hpp index 83a3e549d..6b76ddbe8 100644 --- a/src/storage/v2/edge_accessor.hpp +++ b/src/storage/v2/edge_accessor.hpp @@ -82,6 +82,9 @@ class EdgeAccessor final { /// @throw std::bad_alloc Result GetProperty(PropertyId property, View view) const; + /// Returns the size of the encoded edge property in bytes. + Result GetPropertySize(PropertyId property, View view) const; + /// @throw std::bad_alloc Result> Properties(View view) const; diff --git a/src/storage/v2/name_id_mapper.hpp b/src/storage/v2/name_id_mapper.hpp index bb91e3647..d1e8293f9 100644 --- a/src/storage/v2/name_id_mapper.hpp +++ b/src/storage/v2/name_id_mapper.hpp @@ -83,6 +83,18 @@ class NameIdMapper { 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 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 // `std::string` to avoid making unnecessary copies of the string. // Usually, this wouldn't be correct because the accessor to the diff --git a/src/storage/v2/property_store.cpp b/src/storage/v2/property_store.cpp index 427998fbe..e6e4dbbaf 100644 --- a/src/storage/v2/property_store.cpp +++ b/src/storage/v2/property_store.cpp @@ -93,6 +93,19 @@ enum class Size : uint8_t { 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 // used to store two `Size` values as described in the comment above. enum class Type : uint8_t { @@ -486,6 +499,27 @@ std::optional DecodeTemporalData(Reader &reader) { return TemporalData{static_cast(*type_value), *microseconds_value}; } +std::optional 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 // Function used to decode a PropertyValue from a byte stream. @@ -572,6 +606,92 @@ std::optional 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. // // @sa ComparePropertyValue @@ -788,6 +908,27 @@ enum class ExpectedPropertyStatus { : 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. // 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 // are sorted (by ID) in the buffer. // @@ -983,6 +1131,31 @@ std::pair GetSizeData(const uint8_t *buffer) { return {size, data}; } +struct BufferInfo { + uint64_t size; + uint8_t *data{nullptr}; + bool in_local_buffer; +}; + +template +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(data); + + return {size, non_const_data, in_local_buffer}; +} + void SetSizeData(uint8_t *buffer, uint64_t size, uint8_t *data) { memcpy(buffer, &size, sizeof(uint64_t)); memcpy(buffer + sizeof(uint64_t), &data, sizeof(uint8_t *)); @@ -1023,30 +1196,27 @@ PropertyStore::~PropertyStore() { } PropertyValue PropertyStore::GetProperty(PropertyId property) const { - uint64_t size; - const uint8_t *data; - 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); + BufferInfo buffer_info = GetBufferInfo(buffer_); + Reader reader(buffer_info.data, buffer_info.size); + PropertyValue value; if (FindSpecificProperty(&reader, property, value) != ExpectedPropertyStatus::EQUAL) return {}; 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 { - uint64_t size; - const uint8_t *data; - 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); + BufferInfo buffer_info = GetBufferInfo(buffer_); + Reader reader(buffer_info.data, buffer_info.size); + return ExistsSpecificProperty(&reader, property) == ExpectedPropertyStatus::EQUAL; } @@ -1081,32 +1251,20 @@ std::optional> PropertyStore::ExtractPropertyValues( } bool PropertyStore::IsPropertyEqual(PropertyId property, const PropertyValue &value) const { - uint64_t size; - const uint8_t *data; - 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); + BufferInfo buffer_info = GetBufferInfo(buffer_); + Reader reader(buffer_info.data, buffer_info.size); + auto info = FindSpecificPropertyAndBufferInfo(&reader, property); 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; return prop_reader.GetPosition() == info.property_size; } std::map PropertyStore::Properties() const { - uint64_t size; - const uint8_t *data; - 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); + BufferInfo buffer_info = GetBufferInfo(buffer_); + Reader reader(buffer_info.data, buffer_info.size); + std::map props; while (true) { PropertyValue value; @@ -1340,33 +1498,20 @@ bool PropertyStore::InitProperties(std::vector(data[i]); + BufferInfo buffer_info = GetBufferInfo(buffer_); + + std::string arr(buffer_info.size, ' '); + for (uint i = 0; i < buffer_info.size; ++i) { + arr[i] = static_cast(buffer_info.data[i]); } return arr; } diff --git a/src/storage/v2/property_store.hpp b/src/storage/v2/property_store.hpp index c217cbd81..eee83f5df 100644 --- a/src/storage/v2/property_store.hpp +++ b/src/storage/v2/property_store.hpp @@ -45,6 +45,11 @@ class PropertyStore { /// @throw std::bad_alloc 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 /// complexity of this function is O(n). bool HasProperty(PropertyId property) const; diff --git a/src/storage/v2/storage.hpp b/src/storage/v2/storage.hpp index a096f27fd..722867f74 100644 --- a/src/storage/v2/storage.hpp +++ b/src/storage/v2/storage.hpp @@ -250,6 +250,10 @@ class Storage { PropertyId NameToProperty(std::string_view name) { return storage_->NameToProperty(name); } + std::optional NameToPropertyIfExists(std::string_view name) const { + return storage_->NameToPropertyIfExists(name); + } + EdgeTypeId NameToEdgeType(std::string_view name) { return storage_->NameToEdgeType(name); } StorageMode GetCreationStorageMode() const noexcept; @@ -318,6 +322,14 @@ class Storage { return PropertyId::FromUint(name_id_mapper_->NameToId(name)); } + std::optional 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 { return EdgeTypeId::FromUint(name_id_mapper_->NameToId(name)); } diff --git a/src/storage/v2/vertex_accessor.cpp b/src/storage/v2/vertex_accessor.cpp index ff5062444..ef0a6ab3e 100644 --- a/src/storage/v2/vertex_accessor.cpp +++ b/src/storage/v2/vertex_accessor.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 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 @@ -438,6 +438,26 @@ Result VertexAccessor::GetProperty(PropertyId property, View view return std::move(value); } +Result 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> VertexAccessor::Properties(View view) const { bool exists = true; bool deleted = false; diff --git a/src/storage/v2/vertex_accessor.hpp b/src/storage/v2/vertex_accessor.hpp index 0e5972d14..18fad3dcc 100644 --- a/src/storage/v2/vertex_accessor.hpp +++ b/src/storage/v2/vertex_accessor.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 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 @@ -80,6 +80,9 @@ class VertexAccessor final { /// @throw std::bad_alloc Result GetProperty(PropertyId property, View view) const; + /// Returns the size of the encoded vertex property in bytes. + Result GetPropertySize(PropertyId property, View view) const; + /// @throw std::bad_alloc Result> Properties(View view) const; diff --git a/tests/e2e/CMakeLists.txt b/tests/e2e/CMakeLists.txt index 7e555398e..b8fee9940 100644 --- a/tests/e2e/CMakeLists.txt +++ b/tests/e2e/CMakeLists.txt @@ -76,6 +76,7 @@ add_subdirectory(queries) add_subdirectory(query_modules_storage_modes) add_subdirectory(garbage_collection) add_subdirectory(query_planning) +add_subdirectory(awesome_functions) if (MG_EXPERIMENTAL_HIGH_AVAILABILITY) add_subdirectory(high_availability_experimental) diff --git a/tests/e2e/awesome_functions/CMakeLists.txt b/tests/e2e/awesome_functions/CMakeLists.txt new file mode 100644 index 000000000..9d6e0143b --- /dev/null +++ b/tests/e2e/awesome_functions/CMakeLists.txt @@ -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) diff --git a/tests/e2e/awesome_functions/awesome_functions.py b/tests/e2e/awesome_functions/awesome_functions.py new file mode 100644 index 000000000..9761708ed --- /dev/null +++ b/tests/e2e/awesome_functions/awesome_functions.py @@ -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"])) diff --git a/tests/e2e/awesome_functions/common.py b/tests/e2e/awesome_functions/common.py new file mode 100644 index 000000000..14f272c23 --- /dev/null +++ b/tests/e2e/awesome_functions/common.py @@ -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"] diff --git a/tests/e2e/awesome_functions/workloads.yaml b/tests/e2e/awesome_functions/workloads.yaml new file mode 100644 index 000000000..37e5e8813 --- /dev/null +++ b/tests/e2e/awesome_functions/workloads.yaml @@ -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