Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/google/cloud/firestore_v1/order.py: 29%

130 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-09 06:27 +0000

1# Copyright 2017 Google LLC All rights reserved. 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15from enum import Enum 

16from google.cloud.firestore_v1._helpers import decode_value 

17import math 

18from typing import Any 

19 

20 

21class TypeOrder(Enum): 

22 # NOTE: This order is defined by the backend and cannot be changed. 

23 NULL = 0 

24 BOOLEAN = 1 

25 NUMBER = 2 

26 TIMESTAMP = 3 

27 STRING = 4 

28 BLOB = 5 

29 REF = 6 

30 GEO_POINT = 7 

31 ARRAY = 8 

32 OBJECT = 9 

33 

34 @staticmethod 

35 def from_value(value) -> Any: 

36 v = value._pb.WhichOneof("value_type") 

37 

38 lut = { 

39 "null_value": TypeOrder.NULL, 

40 "boolean_value": TypeOrder.BOOLEAN, 

41 "integer_value": TypeOrder.NUMBER, 

42 "double_value": TypeOrder.NUMBER, 

43 "timestamp_value": TypeOrder.TIMESTAMP, 

44 "string_value": TypeOrder.STRING, 

45 "bytes_value": TypeOrder.BLOB, 

46 "reference_value": TypeOrder.REF, 

47 "geo_point_value": TypeOrder.GEO_POINT, 

48 "array_value": TypeOrder.ARRAY, 

49 "map_value": TypeOrder.OBJECT, 

50 } 

51 

52 if v not in lut: 

53 raise ValueError(f"Could not detect value type for {v}") 

54 return lut[v] 

55 

56 

57class Order(object): 

58 """ 

59 Order implements the ordering semantics of the backend. 

60 """ 

61 

62 @classmethod 

63 def compare(cls, left, right) -> int: 

64 """ 

65 Main comparison function for all Firestore types. 

66 @return -1 is left < right, 0 if left == right, otherwise 1 

67 """ 

68 # First compare the types. 

69 leftType = TypeOrder.from_value(left).value 

70 rightType = TypeOrder.from_value(right).value 

71 

72 if leftType != rightType: 

73 if leftType < rightType: 

74 return -1 

75 return 1 

76 

77 value_type = left._pb.WhichOneof("value_type") 

78 

79 if value_type == "null_value": 

80 return 0 # nulls are all equal 

81 elif value_type == "boolean_value": 

82 return cls._compare_to(left.boolean_value, right.boolean_value) 

83 elif value_type == "integer_value": 

84 return cls.compare_numbers(left, right) 

85 elif value_type == "double_value": 

86 return cls.compare_numbers(left, right) 

87 elif value_type == "timestamp_value": 

88 return cls.compare_timestamps(left, right) 

89 elif value_type == "string_value": 

90 return cls._compare_to(left.string_value, right.string_value) 

91 elif value_type == "bytes_value": 

92 return cls.compare_blobs(left, right) 

93 elif value_type == "reference_value": 

94 return cls.compare_resource_paths(left, right) 

95 elif value_type == "geo_point_value": 

96 return cls.compare_geo_points(left, right) 

97 elif value_type == "array_value": 

98 return cls.compare_arrays(left, right) 

99 elif value_type == "map_value": 

100 return cls.compare_objects(left, right) 

101 else: 

102 raise ValueError(f"Unknown ``value_type`` {value_type}") 

103 

104 @staticmethod 

105 def compare_blobs(left, right) -> int: 

106 left_bytes = left.bytes_value 

107 right_bytes = right.bytes_value 

108 

109 return Order._compare_to(left_bytes, right_bytes) 

110 

111 @staticmethod 

112 def compare_timestamps(left, right) -> Any: 

113 left = left._pb.timestamp_value 

114 right = right._pb.timestamp_value 

115 

116 seconds = Order._compare_to(left.seconds or 0, right.seconds or 0) 

117 if seconds != 0: 

118 return seconds 

119 

120 return Order._compare_to(left.nanos or 0, right.nanos or 0) 

121 

122 @staticmethod 

123 def compare_geo_points(left, right) -> Any: 

124 left_value = decode_value(left, None) 

125 right_value = decode_value(right, None) 

126 cmp = (left_value.latitude > right_value.latitude) - ( 

127 left_value.latitude < right_value.latitude 

128 ) 

129 

130 if cmp != 0: 

131 return cmp 

132 return (left_value.longitude > right_value.longitude) - ( 

133 left_value.longitude < right_value.longitude 

134 ) 

135 

136 @staticmethod 

137 def compare_resource_paths(left, right) -> int: 

138 left = left.reference_value 

139 right = right.reference_value 

140 

141 left_segments = left.split("/") 

142 right_segments = right.split("/") 

143 shorter = min(len(left_segments), len(right_segments)) 

144 # compare segments 

145 for i in range(shorter): 

146 if left_segments[i] < right_segments[i]: 

147 return -1 

148 if left_segments[i] > right_segments[i]: 

149 return 1 

150 

151 left_length = len(left) 

152 right_length = len(right) 

153 return (left_length > right_length) - (left_length < right_length) 

154 

155 @staticmethod 

156 def compare_arrays(left, right) -> int: 

157 l_values = left.array_value.values 

158 r_values = right.array_value.values 

159 

160 length = min(len(l_values), len(r_values)) 

161 for i in range(length): 

162 cmp = Order.compare(l_values[i], r_values[i]) 

163 if cmp != 0: 

164 return cmp 

165 

166 return Order._compare_to(len(l_values), len(r_values)) 

167 

168 @staticmethod 

169 def compare_objects(left, right) -> int: 

170 left_fields = left.map_value.fields 

171 right_fields = right.map_value.fields 

172 

173 for left_key, right_key in zip(sorted(left_fields), sorted(right_fields)): 

174 keyCompare = Order._compare_to(left_key, right_key) 

175 if keyCompare != 0: 

176 return keyCompare 

177 

178 value_compare = Order.compare( 

179 left_fields[left_key], right_fields[right_key] 

180 ) 

181 if value_compare != 0: 

182 return value_compare 

183 

184 return Order._compare_to(len(left_fields), len(right_fields)) 

185 

186 @staticmethod 

187 def compare_numbers(left, right) -> int: 

188 left_value = decode_value(left, None) 

189 right_value = decode_value(right, None) 

190 return Order.compare_doubles(left_value, right_value) 

191 

192 @staticmethod 

193 def compare_doubles(left, right) -> int: 

194 if math.isnan(left): 

195 if math.isnan(right): 

196 return 0 

197 return -1 

198 if math.isnan(right): 

199 return 1 

200 

201 return Order._compare_to(left, right) 

202 

203 @staticmethod 

204 def _compare_to(left, right) -> int: 

205 # We can't just use cmp(left, right) because cmp doesn't exist 

206 # in Python 3, so this is an equivalent suggested by 

207 # https://docs.python.org/3.0/whatsnew/3.0.html#ordering-comparisons 

208 return (left > right) - (left < right)