@@ -2,7 +2,7 @@ import functools
2
2
import uuid
3
3
4
4
from libc.stdint cimport uint64_t, uint8_t, int8_t
5
- from libc.string cimport memcpy
5
+ from libc.string cimport memcpy, strncmp
6
6
7
7
8
8
# A more efficient UUID type implementation
@@ -40,14 +40,6 @@ cdef inline char i64_to_hex(uint64_t num, char *s):
40
40
return 0
41
41
42
42
43
- cdef pg_uuid_from_buf(const char * buf):
44
- cdef:
45
- PgBaseUUID u = UUID.__new__ (UUID)
46
- memcpy(u._data, buf, 16 )
47
- u._int = None
48
- return u
49
-
50
-
51
43
cdef pg_uuid_bytes_from_str(str u, char * out):
52
44
cdef:
53
45
char * orig_buf
@@ -95,11 +87,27 @@ cdef pg_uuid_bytes_from_str(str u, char *out):
95
87
f' invalid UUID {u!r}: decodes to less than 16 bytes' )
96
88
97
89
98
- cdef class PgBaseUUID:
90
+ cdef class __UUIDReplaceMe:
91
+ pass
92
+
93
+
94
+ cdef pg_uuid_from_buf(const char * buf):
95
+ cdef:
96
+ UUID u = UUID.__new__ (UUID)
97
+ memcpy(u._data, buf, 16 )
98
+ return u
99
+
100
+
101
+ @cython.final
102
+ @cython.no_gc_clear
103
+ @ cython.freelist (128 )
104
+ cdef class UUID(__UUIDReplaceMe):
99
105
100
106
cdef:
101
107
char _data[16 ]
102
108
object _int
109
+ object _hash
110
+ object __weakref__
103
111
104
112
def __init__ (self , inp ):
105
113
cdef:
@@ -117,19 +125,21 @@ cdef class PgBaseUUID:
117
125
else :
118
126
raise TypeError (f' a bytes or str object expected, got {inp!r}' )
119
127
120
- self ._int = None
128
+ @property
129
+ def bytes (self ):
130
+ return cpython.PyBytes_FromStringAndSize(self ._data, 16 )
121
131
122
- property bytes :
123
- def __get__ (self ):
124
- return cpython.PyBytes_FromStringAndSize(self ._data, 16 )
132
+ @property
133
+ def int (self ):
134
+ if self ._int is None :
135
+ # The cache is important because `self.int` can be
136
+ # used multiple times by __hash__ etc.
137
+ self ._int = int .from_bytes(self .bytes, ' big' )
138
+ return self ._int
125
139
126
- property int :
127
- def __get__ (self ):
128
- if self ._int is None :
129
- # The cache is important because `self.int` can be
130
- # used multiple times by __hash__ etc.
131
- self ._int = int .from_bytes(self .bytes, ' big' )
132
- return self ._int
140
+ @property
141
+ def is_safe (self ):
142
+ return uuid.SafeUUID.unknown
133
143
134
144
def __str__ (self ):
135
145
cdef:
@@ -158,9 +168,172 @@ cdef class PgBaseUUID:
158
168
def __reduce__ (self ):
159
169
return (type (self ), (self .bytes,))
160
170
171
+ def __eq__ (self , other ):
172
+ if type (other) is UUID:
173
+ return strncmp(self ._data, (< UUID> other)._data, 16 ) == 0
174
+ if isinstance (other, uuid.UUID):
175
+ return self .int == other.int
176
+ return NotImplemented
177
+
178
+ def __ne__ (self , other ):
179
+ if type (other) is UUID:
180
+ return strncmp(self ._data, (< UUID> other)._data, 16 ) != 0
181
+ if isinstance (other, uuid.UUID):
182
+ return self .int != other.int
183
+ return NotImplemented
184
+
185
+ def __lt__ (self , other ):
186
+ if type (other) is UUID:
187
+ return strncmp(self ._data, (< UUID> other)._data, 16 ) < 0
188
+ if isinstance (other, uuid.UUID):
189
+ return self .int < other.int
190
+ return NotImplemented
191
+
192
+ def __gt__ (self , other ):
193
+ if type (other) is UUID:
194
+ return strncmp(self ._data, (< UUID> other)._data, 16 ) > 0
195
+ if isinstance (other, uuid.UUID):
196
+ return self .int > other.int
197
+ return NotImplemented
198
+
199
+ def __le__ (self , other ):
200
+ if type (other) is UUID:
201
+ return strncmp(self ._data, (< UUID> other)._data, 16 ) <= 0
202
+ if isinstance (other, uuid.UUID):
203
+ return self .int <= other.int
204
+ return NotImplemented
205
+
206
+ def __ge__ (self , other ):
207
+ if type (other) is UUID:
208
+ return strncmp(self ._data, (< UUID> other)._data, 16 ) >= 0
209
+ if isinstance (other, uuid.UUID):
210
+ return self .int >= other.int
211
+ return NotImplemented
212
+
213
+ def __hash__ (self ):
214
+ # In EdgeDB every schema object has a uuid and there are
215
+ # huge hash-maps of them. We want UUID.__hash__ to be
216
+ # as fast as possible.
217
+ if self ._hash is not None :
218
+ return self ._hash
219
+
220
+ self ._hash = hash (self .int )
221
+ return self ._hash
222
+
223
+ def __int__ (self ):
224
+ return self .int
225
+
226
+ @property
227
+ def bytes_le (self ):
228
+ bytes = self .bytes
229
+ return (bytes[4 - 1 ::- 1 ] + bytes[6 - 1 :4 - 1 :- 1 ] + bytes[8 - 1 :6 - 1 :- 1 ] +
230
+ bytes[8 :])
231
+
232
+ @property
233
+ def fields (self ):
234
+ return (self .time_low, self .time_mid, self .time_hi_version,
235
+ self .clock_seq_hi_variant, self .clock_seq_low, self .node)
236
+
237
+ @property
238
+ def time_low (self ):
239
+ return self .int >> 96
240
+
241
+ @property
242
+ def time_mid (self ):
243
+ return (self .int >> 80 ) & 0xffff
244
+
245
+ @property
246
+ def time_hi_version (self ):
247
+ return (self .int >> 64 ) & 0xffff
248
+
249
+ @property
250
+ def clock_seq_hi_variant (self ):
251
+ return (self .int >> 56 ) & 0xff
252
+
253
+ @property
254
+ def clock_seq_low (self ):
255
+ return (self .int >> 48 ) & 0xff
256
+
257
+ @property
258
+ def time (self ):
259
+ return (((self .time_hi_version & 0x0fff ) << 48 ) |
260
+ (self .time_mid << 32 ) | self .time_low)
261
+
262
+ @property
263
+ def clock_seq (self ):
264
+ return (((self .clock_seq_hi_variant & 0x3f ) << 8 ) |
265
+ self .clock_seq_low)
266
+
267
+ @property
268
+ def node (self ):
269
+ return self .int & 0xffffffffffff
270
+
271
+ @property
272
+ def hex (self ):
273
+ cdef:
274
+ uint64_t u
275
+ char out[32 ]
161
276
162
- class UUID (PgBaseUUID , uuid.UUID ):
163
- __slots__ = ()
277
+ u = < uint64_t> hton.unpack_int64(self ._data)
278
+ i64_to_hex(u, out)
279
+ u = < uint64_t> hton.unpack_int64(self ._data + 8 )
280
+ i64_to_hex(u, out + 16 )
281
+
282
+ return cpythonx.PyUnicode_FromKindAndData(
283
+ cpythonx.PyUnicode_1BYTE_KIND, < void * > out, 32 )
284
+
285
+ @property
286
+ def urn (self ):
287
+ return ' urn:uuid:' + str (self )
288
+
289
+ @property
290
+ def variant (self ):
291
+ if not self .int & (0x8000 << 48 ):
292
+ return uuid.RESERVED_NCS
293
+ elif not self .int & (0x4000 << 48 ):
294
+ return uuid.RFC_4122
295
+ elif not self .int & (0x2000 << 48 ):
296
+ return uuid.RESERVED_MICROSOFT
297
+ else :
298
+ return uuid.RESERVED_FUTURE
299
+
300
+ @property
301
+ def version (self ):
302
+ # The version bits are only meaningful for RFC 4122 UUIDs.
303
+ if self .variant == uuid.RFC_4122:
304
+ return int ((self .int >> 76 ) & 0xf )
305
+
306
+
307
+ # <hack>
308
+ # In order for `isinstance(pgproto.UUID, uuid.UUID)` to work,
309
+ # patch __bases__ and __mro__ by injecting `uuid.UUID`.
310
+ #
311
+ # We apply brute-force here because the following pattern stopped
312
+ # working with Python 3.8:
313
+ #
314
+ # cdef class OurUUID:
315
+ # ...
316
+ #
317
+ # class UUID(OurUUID, uuid.UUID):
318
+ # ...
319
+ #
320
+ # With Python 3.8 it now produces
321
+ #
322
+ # "TypeError: multiple bases have instance lay-out conflict"
323
+ #
324
+ # error. Maybe it's possible to fix this some other way, but
325
+ # the best solution possible would be to just contribute our
326
+ # faster UUID to the standard library and not have this problem
327
+ # at all. For now this hack is pretty safe and should be
328
+ # compatible with future Pythons for long enough.
329
+ #
330
+ assert UUID.__bases__ [0 ] is __UUIDReplaceMe
331
+ assert UUID.__mro__[1 ] is __UUIDReplaceMe
332
+ cpython.Py_INCREF(uuid.UUID)
333
+ cpython.PyTuple_SET_ITEM(UUID.__bases__ , 0 , uuid.UUID)
334
+ cpython.Py_INCREF(uuid.UUID)
335
+ cpython.PyTuple_SET_ITEM(UUID.__mro__, 1 , uuid.UUID)
336
+ # </hack>
164
337
165
338
166
339
cdef pg_UUID = UUID
0 commit comments