Skip to content

Commit bed71fc

Browse files
chkuang-ga-maurice
authored andcommitted
Properly sort nodes to calculate hash for RunTransaction()
Implement the sorting based on Android implementation. The original implementation only sort the key based on alphanumeric order, as the default order for map<Variant, Variant>. cl/274064076 only consider when the map can be converted into an vector. This fix considers all possible combination of integer, integer w/ leading zeroes, string, MIN_KEY and MAX_KEY. Also changed ParseInteger() behavior to not consider leading zeroes. PiperOrigin-RevId: 275511085
1 parent 2d0ab9b commit bed71fc

File tree

2 files changed

+68
-24
lines changed

2 files changed

+68
-24
lines changed

database/src/desktop/util_desktop.cc

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -434,17 +434,13 @@ bool HasVector(const Variant& variant) {
434434
return false;
435435
}
436436

437-
bool ParseInteger(const char* str, int32_t* output) {
437+
bool ParseInteger(const char* str, int64_t* output) {
438438
assert(output);
439439
assert(str);
440-
// Integers must not have leading zeroes.
441-
if (str[0] == '0' && str[1] != '\0') {
442-
return false;
443-
}
444440
// Check if the key is numeric
445441
bool is_int = false;
446442
char* end_ptr = nullptr;
447-
int32_t parse_value = strtol(str, &end_ptr, 10); // NOLINT
443+
int64_t parse_value = strtoll(str, &end_ptr, 10); // NOLINT
448444
if (end_ptr != nullptr && end_ptr != str && *end_ptr == '\0') {
449445
is_int = true;
450446
*output = parse_value;
@@ -454,7 +450,7 @@ bool ParseInteger(const char* str, int32_t* output) {
454450

455451
// A Variant map can be converted into a Variant vector if:
456452
// 1. map is not empty and
457-
// 2. All the key are numeric and
453+
// 2. All the key are integer (no leading 0) and
458454
// 3. If less or equal to half of the keys in the array is missing.
459455
// Return whether the map can be converted into a vector, and output
460456
// max_index_out as the highest numeric key found in the map.
@@ -464,8 +460,14 @@ bool CanConvertVariantMapToVector(const Variant& variant,
464460

465461
int64_t max_index = -1;
466462
for (auto& it_child : variant.map()) {
463+
assert(it_child.first.is_string());
464+
// Integers must not have leading zeroes.
465+
if (it_child.first.string_value()[0] == '0' &&
466+
it_child.first.string_value()[1] != '\0') {
467+
return false;
468+
}
467469
// Check if the key is numeric
468-
int32_t parse_value = 0;
470+
int64_t parse_value = 0;
469471
bool is_number = ParseInteger(it_child.first.string_value(), &parse_value);
470472
if (!is_number || parse_value < 0) {
471473
// If any one of the key is not numeric, there is no need to verify
@@ -835,26 +837,55 @@ UtilLeafType GetLeafType(Variant::Type type) {
835837
// Store the pointers to the key and the value Variant in a map for sorting
836838
typedef std::pair<const Variant*, const Variant*> NodeSortingData;
837839

838-
// Comparer for std::sort to sort nodes by key as numeric value
839-
// Return true if right node is greater than left node
840-
bool KeyAsNumberSortingFunction(const NodeSortingData& left,
841-
const NodeSortingData& right) {
842-
return left.first->AsInt64().int64_value() <
843-
right.first->AsInt64().int64_value();
840+
int ChildKeyCompareTo(const Variant& left, const Variant& right) {
841+
const Variant kMinChildKey(QueryParamsComparator::kMinKey);
842+
const Variant kMaxChildKey(QueryParamsComparator::kMaxKey);
843+
844+
FIREBASE_DEV_ASSERT(left.is_string());
845+
FIREBASE_DEV_ASSERT(right.is_string());
846+
847+
if (left == right) {
848+
return 0;
849+
} else if (left == kMinChildKey || right == kMaxChildKey) {
850+
return -1;
851+
} else if (right == kMinChildKey || left == kMaxChildKey) {
852+
return 1;
853+
} else {
854+
int64_t left_int_key = -1;
855+
bool left_is_int = ParseInteger(left.string_value(), &left_int_key);
856+
int64_t right_int_key = -1;
857+
bool right_is_int = ParseInteger(right.string_value(), &right_int_key);
858+
if (left_is_int) {
859+
if (right_is_int) {
860+
int cmp = left_int_key - right_int_key;
861+
return cmp == 0 ? (strlen(left.string_value()) -
862+
strlen(right.string_value()))
863+
: cmp;
864+
} else {
865+
return -1;
866+
}
867+
} else if (right_is_int) {
868+
return 1;
869+
} else {
870+
return left > right ? 1 : -1;
871+
}
872+
}
844873
}
845874

846875
// Private function to serialize all child nodes
847876
void ProcessChildNodes(std::stringstream* ss,
848-
std::vector<NodeSortingData>* nodes, bool saw_priority,
849-
bool can_convert_vector) {
877+
std::vector<NodeSortingData>* nodes, bool saw_priority) {
850878
// If any node has priority, sort using priority.
851879
if (saw_priority) {
852880
QueryParams params;
853881
assert(params.order_by == QueryParams::kOrderByPriority);
854882
std::sort(nodes->begin(), nodes->end(), QueryParamsLesser(&params));
855-
} else if (can_convert_vector) {
856-
// If the nodes can be converted to a vector, sort using key as number.
857-
std::sort(nodes->begin(), nodes->end(), KeyAsNumberSortingFunction);
883+
} else {
884+
// Otherwise, use default sorting function.
885+
std::sort(nodes->begin(), nodes->end(),
886+
[](const NodeSortingData& left, const NodeSortingData& right) {
887+
return ChildKeyCompareTo(*left.first, *right.first) < 0;
888+
});
858889
}
859890

860891
// Serialize each child with its key and its hashed value
@@ -888,16 +919,15 @@ void AppendHashRepAsContainer(std::stringstream* ss, const Variant& data) {
888919
saw_priority =
889920
saw_priority || !GetVariantPriority(data.vector()[i]).is_null();
890921
}
891-
ProcessChildNodes(ss, &nodes, saw_priority, true);
922+
ProcessChildNodes(ss, &nodes, saw_priority);
892923
} else if (data.is_map()) {
893924
bool saw_priority = false;
894925
for (auto& it_child : data.map()) {
895926
nodes.push_back(NodeSortingData(&it_child.first, &it_child.second));
896927
saw_priority =
897928
saw_priority || !GetVariantPriority(it_child.second).is_null();
898929
}
899-
bool can_convert_vector = CanConvertVariantMapToVector(data, nullptr);
900-
ProcessChildNodes(ss, &nodes, saw_priority, can_convert_vector);
930+
ProcessChildNodes(ss, &nodes, saw_priority);
901931
}
902932
}
903933

database/src/desktop/util_desktop.h

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,11 +188,11 @@ size_t GetEffectiveChildren(const Variant& variant,
188188
// Check if the variant or any of its children is vector.
189189
bool HasVector(const Variant& variant);
190190

191-
// Parse a base-ten input string into 32-bit integer. Strings are parsed as
191+
// Parse a base-ten input string into 64-bit integer. Strings are parsed as
192192
// integers if they do not have leading 0's - if they do they are simply treated
193193
// as strings. This is done to match the behavior of the other database
194194
// implementations.
195-
bool ParseInteger(const char* str, int32_t* output);
195+
bool ParseInteger(const char* str, int64_t* output);
196196

197197
// Prune the priorities and convert map into vector if applicable, to the
198198
// variant and its children. This function assumes the variant or its children
@@ -254,6 +254,20 @@ bool VariantsAreEquivalent(const Variant& a, const Variant& b);
254254
// using the input string.
255255
const std::string& GetBase64SHA1(const std::string& input, std::string* output);
256256

257+
// Compare two Variant as child keys for sorting.
258+
// Expect both Variants are string.
259+
// Returns 0 if both Variants are equal.
260+
// Returns greater than 0 if left is greater than right.
261+
// Returns less than 0 if left is less than right.
262+
// Child key comparison is based on the following rules.
263+
// 1. "[MAX_KEY]" is greater than everything
264+
// 2. "[MIN_KEY]" is less than everything
265+
// 3. Integer key is less than string key
266+
// 4. If both keys are integer and are equal, ex. "1" and "001", one with the
267+
// shorter length as a string is less than the other.
268+
// 5. Otherwise, compare as a string.
269+
int ChildKeyCompareTo(const Variant& left, const Variant& right);
270+
257271
// Returns serialized string of a Variant to be used for GetHash() function.
258272
// The implementation is based on Node.java and its derived classes from Android
259273
// SDK

0 commit comments

Comments
 (0)