@@ -179,7 +179,7 @@ def _parse_success(self, conn: Connection, buffer: bytearray, fields: list):
179179 )
180180 fields += body_class ._fields_ + [
181181 ('data' , data_class ),
182- ('more' , ctypes .c_bool ),
182+ ('more' , ctypes .c_byte ),
183183 ]
184184
185185 def _create_parse_result (self , conn : Connection , header_class , fields : list , buffer : bytearray ):
@@ -226,3 +226,193 @@ def to_python(self, ctype_object, *args, **kwargs):
226226 )
227227 result ['data' ].append (row )
228228 return result
229+
230+
231+ @attr .s
232+ class Response130 :
233+ following = attr .ib (type = list , factory = list )
234+ _response_header = None
235+
236+ def __attrs_post_init__ (self ):
237+ # replace None with empty list
238+ self .following = self .following or []
239+
240+ @classmethod
241+ def build_header (cls ):
242+ if cls ._response_header is None :
243+ cls ._response_header = type (
244+ 'ResponseHeader' ,
245+ (ctypes .LittleEndianStructure ,),
246+ {
247+ '_pack_' : 1 ,
248+ '_fields_' : [
249+ ('length' , ctypes .c_int ),
250+ ('query_id' , ctypes .c_longlong ),
251+ ('status_code' , ctypes .c_int ),
252+ ],
253+ },
254+ )
255+ return cls ._response_header
256+
257+ def parse (self , client : 'Client' ):
258+ header_class = self .build_header ()
259+ buffer = client .recv (ctypes .sizeof (header_class ))
260+ header = header_class .from_buffer_copy (buffer )
261+ fields = []
262+
263+ if header .status_code == OP_SUCCESS :
264+ for name , ignite_type in self .following :
265+ c_type , buffer_fragment = ignite_type .parse (client )
266+ buffer += buffer_fragment
267+ fields .append ((name , c_type ))
268+ else :
269+ c_type , buffer_fragment = String .parse (client )
270+ buffer += buffer_fragment
271+ fields .append (('error_message' , c_type ))
272+
273+ response_class = type (
274+ 'Response' ,
275+ (header_class ,),
276+ {
277+ '_pack_' : 1 ,
278+ '_fields_' : fields ,
279+ }
280+ )
281+ return response_class , buffer
282+
283+ def to_python (self , ctype_object , * args , ** kwargs ):
284+ result = OrderedDict ()
285+
286+ for name , c_type in self .following :
287+ result [name ] = c_type .to_python (
288+ getattr (ctype_object , name ),
289+ * args , ** kwargs
290+ )
291+
292+ return result if result else None
293+
294+
295+ @attr .s
296+ class SQLResponse130 (Response130 ):
297+ """
298+ The response class of SQL functions is special in the way the row-column
299+ data is counted in it. Basically, Ignite thin client API is following a
300+ “counter right before the counted objects” rule in most of its parts.
301+ SQL ops are breaking this rule.
302+ """
303+ include_field_names = attr .ib (type = bool , default = False )
304+ has_cursor = attr .ib (type = bool , default = False )
305+
306+ def fields_or_field_count (self ):
307+ if self .include_field_names :
308+ return 'fields' , StringArray
309+ return 'field_count' , Int
310+
311+ def parse (self , client : 'Client' ):
312+ header_class = self .build_header ()
313+ buffer = client .recv (ctypes .sizeof (header_class ))
314+ header = header_class .from_buffer_copy (buffer )
315+ fields = []
316+
317+ if header .status_code == OP_SUCCESS :
318+ following = [
319+ self .fields_or_field_count (),
320+ ('row_count' , Int ),
321+ ]
322+ if self .has_cursor :
323+ following .insert (0 , ('cursor' , Long ))
324+ body_struct = Struct (following )
325+ body_class , body_buffer = body_struct .parse (client )
326+ body = body_class .from_buffer_copy (body_buffer )
327+
328+ if self .include_field_names :
329+ field_count = body .fields .length
330+ else :
331+ field_count = body .field_count
332+
333+ data_fields = []
334+ data_buffer = b''
335+ for i in range (body .row_count ):
336+ row_fields = []
337+ row_buffer = b''
338+ for j in range (field_count ):
339+ field_class , field_buffer = AnyDataObject .parse (client )
340+ row_fields .append (('column_{}' .format (j ), field_class ))
341+ row_buffer += field_buffer
342+
343+ row_class = type (
344+ 'SQLResponseRow' ,
345+ (ctypes .LittleEndianStructure ,),
346+ {
347+ '_pack_' : 1 ,
348+ '_fields_' : row_fields ,
349+ }
350+ )
351+ data_fields .append (('row_{}' .format (i ), row_class ))
352+ data_buffer += row_buffer
353+
354+ data_class = type (
355+ 'SQLResponseData' ,
356+ (ctypes .LittleEndianStructure ,),
357+ {
358+ '_pack_' : 1 ,
359+ '_fields_' : data_fields ,
360+ }
361+ )
362+ fields += body_class ._fields_ + [
363+ ('data' , data_class ),
364+ ('more' , ctypes .c_byte ),
365+ ]
366+ buffer += body_buffer + data_buffer
367+ else :
368+ c_type , buffer_fragment = String .parse (client )
369+ buffer += buffer_fragment
370+ fields .append (('error_message' , c_type ))
371+
372+ final_class = type (
373+ 'SQLResponse' ,
374+ (header_class ,),
375+ {
376+ '_pack_' : 1 ,
377+ '_fields_' : fields ,
378+ }
379+ )
380+ buffer += client .recv (ctypes .sizeof (final_class ) - len (buffer ))
381+ return final_class , buffer
382+
383+ def to_python (self , ctype_object , * args , ** kwargs ):
384+ if ctype_object .status_code == 0 :
385+ result = {
386+ 'more' : Bool .to_python (
387+ ctype_object .more , * args , ** kwargs
388+ ),
389+ 'data' : [],
390+ }
391+ if hasattr (ctype_object , 'fields' ):
392+ result ['fields' ] = StringArray .to_python (
393+ ctype_object .fields , * args , ** kwargs
394+ )
395+ else :
396+ result ['field_count' ] = Int .to_python (
397+ ctype_object .field_count , * args , ** kwargs
398+ )
399+ if hasattr (ctype_object , 'cursor' ):
400+ result ['cursor' ] = Long .to_python (
401+ ctype_object .cursor , * args , ** kwargs
402+ )
403+ for row_item in ctype_object .data ._fields_ :
404+ row_name = row_item [0 ]
405+ row_object = getattr (ctype_object .data , row_name )
406+ row = []
407+ for col_item in row_object ._fields_ :
408+ col_name = col_item [0 ]
409+ col_object = getattr (row_object , col_name )
410+ row .append (
411+ AnyDataObject .to_python (col_object , * args , ** kwargs )
412+ )
413+ result ['data' ].append (row )
414+ return result
415+
416+
417+ Response120 = Response130
418+ SQLResponse120 = SQLResponse130
0 commit comments