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
« 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.
15from enum import Enum
16from google.cloud.firestore_v1._helpers import decode_value
17import math
18from typing import Any
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
34 @staticmethod
35 def from_value(value) -> Any:
36 v = value._pb.WhichOneof("value_type")
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 }
52 if v not in lut:
53 raise ValueError(f"Could not detect value type for {v}")
54 return lut[v]
57class Order(object):
58 """
59 Order implements the ordering semantics of the backend.
60 """
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
72 if leftType != rightType:
73 if leftType < rightType:
74 return -1
75 return 1
77 value_type = left._pb.WhichOneof("value_type")
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}")
104 @staticmethod
105 def compare_blobs(left, right) -> int:
106 left_bytes = left.bytes_value
107 right_bytes = right.bytes_value
109 return Order._compare_to(left_bytes, right_bytes)
111 @staticmethod
112 def compare_timestamps(left, right) -> Any:
113 left = left._pb.timestamp_value
114 right = right._pb.timestamp_value
116 seconds = Order._compare_to(left.seconds or 0, right.seconds or 0)
117 if seconds != 0:
118 return seconds
120 return Order._compare_to(left.nanos or 0, right.nanos or 0)
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 )
130 if cmp != 0:
131 return cmp
132 return (left_value.longitude > right_value.longitude) - (
133 left_value.longitude < right_value.longitude
134 )
136 @staticmethod
137 def compare_resource_paths(left, right) -> int:
138 left = left.reference_value
139 right = right.reference_value
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
151 left_length = len(left)
152 right_length = len(right)
153 return (left_length > right_length) - (left_length < right_length)
155 @staticmethod
156 def compare_arrays(left, right) -> int:
157 l_values = left.array_value.values
158 r_values = right.array_value.values
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
166 return Order._compare_to(len(l_values), len(r_values))
168 @staticmethod
169 def compare_objects(left, right) -> int:
170 left_fields = left.map_value.fields
171 right_fields = right.map_value.fields
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
178 value_compare = Order.compare(
179 left_fields[left_key], right_fields[right_key]
180 )
181 if value_compare != 0:
182 return value_compare
184 return Order._compare_to(len(left_fields), len(right_fields))
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)
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
201 return Order._compare_to(left, right)
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)