@@ -46,7 +46,8 @@ cdef class RangeCodec(BaseCodec):
4646
4747 return codec
4848
49- cdef encode(self , WriteBuffer buf, object obj):
49+ @staticmethod
50+ cdef encode_range(WriteBuffer buf, object obj, BaseCodec sub_codec):
5051 cdef:
5152 uint8_t flags = 0
5253 WriteBuffer sub_data
@@ -56,10 +57,10 @@ cdef class RangeCodec(BaseCodec):
5657 bint inc_upper = obj.inc_upper
5758 bint empty = obj.is_empty()
5859
59- if not isinstance (self . sub_codec, ScalarCodec):
60+ if not isinstance (sub_codec, ScalarCodec):
6061 raise TypeError (
6162 ' only scalar ranges are supported (got type {!r})' .format(
62- type (self . sub_codec).__name__
63+ type (sub_codec).__name__
6364 )
6465 )
6566
@@ -78,14 +79,14 @@ cdef class RangeCodec(BaseCodec):
7879 sub_data = WriteBuffer.new()
7980 if lower is not None :
8081 try :
81- self . sub_codec.encode(sub_data, lower)
82+ sub_codec.encode(sub_data, lower)
8283 except TypeError as e:
8384 raise ValueError (
8485 ' invalid range lower bound: {}' .format(
8586 e.args[0 ])) from None
8687 if upper is not None :
8788 try :
88- self . sub_codec.encode(sub_data, upper)
89+ sub_codec.encode(sub_data, upper)
8990 except TypeError as e:
9091 raise ValueError (
9192 ' invalid range upper bound: {}' .format(
@@ -95,7 +96,8 @@ cdef class RangeCodec(BaseCodec):
9596 buf.write_byte(< int8_t> flags)
9697 buf.write_buffer(sub_data)
9798
98- cdef decode(self , FRBuffer * buf):
99+ @staticmethod
100+ cdef decode_range(FRBuffer * buf, BaseCodec sub_codec):
99101 cdef:
100102 uint8_t flags = < uint8_t> frb_read(buf, 1 )[0 ]
101103 bint empty = (flags & RANGE_EMPTY) != 0
@@ -107,7 +109,6 @@ cdef class RangeCodec(BaseCodec):
107109 object upper = None
108110 int32_t sub_len
109111 FRBuffer sub_buf
110- BaseCodec sub_codec = self .sub_codec
111112
112113 if has_lower:
113114 sub_len = hton.unpack_int32(frb_read(buf, 4 ))
@@ -137,6 +138,12 @@ cdef class RangeCodec(BaseCodec):
137138 empty = empty,
138139 )
139140
141+ cdef encode(self , WriteBuffer buf, object obj):
142+ RangeCodec.encode_range(buf, obj, self .sub_codec)
143+
144+ cdef decode(self , FRBuffer * buf):
145+ return RangeCodec.decode_range(buf, self .sub_codec)
146+
140147 cdef dump(self , int level = 0 ):
141148 return f' {level * " "}{self.name}\n {self.sub_codec.dump(level + 1)}'
142149
@@ -146,3 +153,104 @@ cdef class RangeCodec(BaseCodec):
146153 name = self .type_name,
147154 value_type = self .sub_codec.make_type(describe_context),
148155 )
156+
157+
158+ @cython.final
159+ cdef class MultiRangeCodec(BaseCodec):
160+
161+ def __cinit__ (self ):
162+ self .sub_codec = None
163+
164+ @staticmethod
165+ cdef BaseCodec new(bytes tid, BaseCodec sub_codec):
166+ cdef:
167+ MultiRangeCodec codec
168+
169+ codec = MultiRangeCodec.__new__ (MultiRangeCodec)
170+
171+ codec.tid = tid
172+ codec.name = ' MultiRange'
173+ codec.sub_codec = sub_codec
174+
175+ return codec
176+
177+ cdef encode(self , WriteBuffer buf, object obj):
178+ cdef:
179+ WriteBuffer elem_data
180+ Py_ssize_t objlen
181+ Py_ssize_t elem_data_len
182+
183+ if not isinstance (self .sub_codec, ScalarCodec):
184+ raise TypeError (
185+ f' only scalar multiranges are supported (got type '
186+ f' {type(self.sub_codec).__name__!r})'
187+ )
188+
189+ if not _is_array_iterable(obj):
190+ raise TypeError (
191+ f' a sized iterable container expected (got type '
192+ f' {type(obj).__name__!r})'
193+ )
194+
195+ objlen = len (obj)
196+ if objlen > _MAXINT32:
197+ raise ValueError (' too many elements in multirange value' )
198+
199+ elem_data = WriteBuffer.new()
200+ for item in obj:
201+ try :
202+ RangeCodec.encode_range(elem_data, item, self .sub_codec)
203+ except TypeError as e:
204+ raise ValueError (
205+ f' invalid multirange element: {e.args[0]}' ) from None
206+
207+ elem_data_len = elem_data.len()
208+ if elem_data_len > _MAXINT32 - 4 :
209+ raise OverflowError (
210+ f' size of encoded multirange datum exceeds the maximum '
211+ f' allowed {_MAXINT32 - 4} bytes' )
212+
213+ # Datum length
214+ buf.write_int32(4 + < int32_t> elem_data_len)
215+ # Number of elements in multirange
216+ buf.write_int32(< int32_t> objlen)
217+ buf.write_buffer(elem_data)
218+
219+ cdef decode(self , FRBuffer * buf):
220+ cdef:
221+ Py_ssize_t elem_count = < Py_ssize_t>< uint32_t> hton.unpack_int32(
222+ frb_read(buf, 4 ))
223+ object result
224+ Py_ssize_t i
225+ int32_t elem_len
226+ FRBuffer elem_buf
227+
228+ result = cpython.PyList_New(elem_count)
229+ for i in range (elem_count):
230+ elem_len = hton.unpack_int32(frb_read(buf, 4 ))
231+ if elem_len == - 1 :
232+ raise RuntimeError (
233+ ' unexpected NULL element in multirange value' )
234+ else :
235+ frb_slice_from(& elem_buf, buf, elem_len)
236+ elem = RangeCodec.decode_range(& elem_buf, self .sub_codec)
237+ if frb_get_len(& elem_buf):
238+ raise RuntimeError (
239+ f' unexpected trailing data in buffer after '
240+ f' multirange element decoding: '
241+ f' {frb_get_len(&elem_buf)}' )
242+
243+ cpython.Py_INCREF(elem)
244+ cpython.PyList_SET_ITEM(result, i, elem)
245+
246+ return range_mod.MultiRange(result)
247+
248+ cdef dump(self , int level = 0 ):
249+ return f' {level * " "}{self.name}\n {self.sub_codec.dump(level + 1)}'
250+
251+ def make_type (self , describe_context ):
252+ return describe.MultiRangeType(
253+ desc_id = uuid.UUID(bytes = self .tid),
254+ name = self .type_name,
255+ value_type = self .sub_codec.make_type(describe_context),
256+ )
0 commit comments