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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

147 statements  

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 

15import math 

16from enum import Enum 

17from typing import Any 

18 

19from google.cloud.firestore_v1._helpers import decode_value 

20from google.cloud.firestore_v1._helpers import GeoPoint 

21 

22 

23class TypeOrder(Enum): 

24 """The supported Data Type. 

25 

26 Note: The Enum value does not imply the sort order. 

27 """ 

28 

29 NULL = 0 

30 BOOLEAN = 1 

31 NUMBER = 2 

32 TIMESTAMP = 3 

33 STRING = 4 

34 BLOB = 5 

35 REF = 6 

36 GEO_POINT = 7 

37 ARRAY = 8 

38 OBJECT = 9 

39 VECTOR = 10 

40 

41 @staticmethod 

42 def from_value(value) -> Any: 

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

44 lut = { 

45 "null_value": TypeOrder.NULL, 

46 "boolean_value": TypeOrder.BOOLEAN, 

47 "integer_value": TypeOrder.NUMBER, 

48 "double_value": TypeOrder.NUMBER, 

49 "timestamp_value": TypeOrder.TIMESTAMP, 

50 "string_value": TypeOrder.STRING, 

51 "bytes_value": TypeOrder.BLOB, 

52 "reference_value": TypeOrder.REF, 

53 "geo_point_value": TypeOrder.GEO_POINT, 

54 "array_value": TypeOrder.ARRAY, 

55 "map_value": TypeOrder.OBJECT, 

56 } 

57 

58 if v not in lut: 

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

60 

61 if v == "map_value": 

62 if ( 

63 "__type__" in value.map_value.fields 

64 and value.map_value.fields["__type__"].string_value == "__vector__" 

65 ): 

66 return TypeOrder.VECTOR 

67 return lut[v] 

68 

69 

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

71_TYPE_ORDER_MAP = { 

72 TypeOrder.NULL: 0, 

73 TypeOrder.BOOLEAN: 1, 

74 TypeOrder.NUMBER: 2, 

75 TypeOrder.TIMESTAMP: 3, 

76 TypeOrder.STRING: 4, 

77 TypeOrder.BLOB: 5, 

78 TypeOrder.REF: 6, 

79 TypeOrder.GEO_POINT: 7, 

80 TypeOrder.ARRAY: 8, 

81 TypeOrder.VECTOR: 9, 

82 TypeOrder.OBJECT: 10, 

83} 

84 

85 

86class Order(object): 

87 """ 

88 Order implements the ordering semantics of the backend. 

89 """ 

90 

91 @classmethod 

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

93 """ 

94 Main comparison function for all Firestore types. 

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

96 """ 

97 # First compare the types. 

98 leftType = TypeOrder.from_value(left) 

99 rightType = TypeOrder.from_value(right) 

100 if leftType != rightType: 

101 if _TYPE_ORDER_MAP[leftType] < _TYPE_ORDER_MAP[rightType]: 

102 return -1 

103 else: 

104 return 1 

105 

106 if leftType == TypeOrder.NULL: 

107 return 0 # nulls are all equal 

108 elif leftType == TypeOrder.BOOLEAN: 

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

110 elif leftType == TypeOrder.NUMBER: 

111 return cls.compare_numbers(left, right) 

112 elif leftType == TypeOrder.TIMESTAMP: 

113 return cls.compare_timestamps(left, right) 

114 elif leftType == TypeOrder.STRING: 

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

116 elif leftType == TypeOrder.BLOB: 

117 return cls.compare_blobs(left, right) 

118 elif leftType == TypeOrder.REF: 

119 return cls.compare_resource_paths(left, right) 

120 elif leftType == TypeOrder.GEO_POINT: 

121 return cls.compare_geo_points(left, right) 

122 elif leftType == TypeOrder.ARRAY: 

123 return cls.compare_arrays(left, right) 

124 elif leftType == TypeOrder.VECTOR: 

125 # ARRAYs < VECTORs < MAPs 

126 return cls.compare_vectors(left, right) 

127 elif leftType == TypeOrder.OBJECT: 

128 return cls.compare_objects(left, right) 

129 else: 

130 raise ValueError(f"Unknown TypeOrder {leftType}") 

131 

132 @staticmethod 

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

134 left_bytes = left.bytes_value 

135 right_bytes = right.bytes_value 

136 

137 return Order._compare_to(left_bytes, right_bytes) 

138 

139 @staticmethod 

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

141 left = left._pb.timestamp_value 

142 right = right._pb.timestamp_value 

143 

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

145 if seconds != 0: 

146 return seconds 

147 

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

149 

150 @staticmethod 

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

152 left_value = decode_value(left, None) 

153 right_value = decode_value(right, None) 

154 if not isinstance(left_value, GeoPoint) or not isinstance( 

155 right_value, GeoPoint 

156 ): 

157 raise AttributeError("invalid geopoint encountered") 

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

159 left_value.latitude < right_value.latitude 

160 ) 

161 

162 if cmp != 0: 

163 return cmp 

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

165 left_value.longitude < right_value.longitude 

166 ) 

167 

168 @staticmethod 

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

170 left = left.reference_value 

171 right = right.reference_value 

172 

173 left_segments = left.split("/") 

174 right_segments = right.split("/") 

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

176 # compare segments 

177 for i in range(shorter): 

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

179 return -1 

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

181 return 1 

182 

183 left_length = len(left) 

184 right_length = len(right) 

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

186 

187 @staticmethod 

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

189 l_values = left.array_value.values 

190 r_values = right.array_value.values 

191 

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

193 for i in range(length): 

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

195 if cmp != 0: 

196 return cmp 

197 

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

199 

200 @staticmethod 

201 def compare_vectors(left, right) -> int: 

202 # First compare the size of vector. 

203 l_values = left.map_value.fields["value"] 

204 r_values = right.map_value.fields["value"] 

205 

206 left_length = len(l_values.array_value.values) 

207 right_length = len(r_values.array_value.values) 

208 

209 if left_length != right_length: 

210 return Order._compare_to(left_length, right_length) 

211 

212 # Compare element if the size matches. 

213 return Order.compare_arrays(l_values, r_values) 

214 

215 @staticmethod 

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

217 left_fields = left.map_value.fields 

218 right_fields = right.map_value.fields 

219 

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

221 keyCompare = Order._compare_to(left_key, right_key) 

222 if keyCompare != 0: 

223 return keyCompare 

224 

225 value_compare = Order.compare( 

226 left_fields[left_key], right_fields[right_key] 

227 ) 

228 if value_compare != 0: 

229 return value_compare 

230 

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

232 

233 @staticmethod 

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

235 left_value = decode_value(left, None) 

236 right_value = decode_value(right, None) 

237 return Order.compare_doubles(left_value, right_value) 

238 

239 @staticmethod 

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

241 if math.isnan(left): 

242 if math.isnan(right): 

243 return 0 

244 return -1 

245 if math.isnan(right): 

246 return 1 

247 

248 return Order._compare_to(left, right) 

249 

250 @staticmethod 

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

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

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

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

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