def unpack_from(cls, payload, expected_parts): """Unpack parts from payload""" for num_part in iter_range(expected_parts): hdr = payload.read(cls.header_size) try: part_header = PartHeader(*cls.header_struct.unpack(hdr)) except struct.error: raise InterfaceError("No valid part header") if part_header.payload_size % 8 != 0: part_payload_size = part_header.payload_size + 8 - (part_header.payload_size % 8) else: part_payload_size = part_header.payload_size pl = payload.read(part_payload_size) part_payload = io.BytesIO(pl) try: _PartClass = PART_MAPPING[part_header.part_kind] except KeyError: raise InterfaceError("Unknown part kind %s" % part_header.part_kind) debug('%s (%d/%d): %s', _PartClass.__name__, num_part+1, expected_parts, str(part_header)) debug('Read %d bytes payload for part %d', part_payload_size, num_part + 1) init_arguments = _PartClass.unpack_data(part_header.argument_count, part_payload) debug('Part data: %s', init_arguments) part = _PartClass(*init_arguments) part.header = part_header part.attribute = part_header.part_attributes part.source = 'server' if pyhdb.tracing: part.trace_header = humanhexlify(hdr[:part_header.payload_size]) part.trace_payload = humanhexlify(pl, 30) yield part
def _handle_dbproc_call(self, parts, parameters_metadata): """Handle reply messages from STORED PROCEDURE statements""" for part in parts: if part.kind == part_kinds.ROWSAFFECTED: self.rowcount = part.values[0] elif part.kind == part_kinds.TRANSACTIONFLAGS: pass elif part.kind == part_kinds.STATEMENTCONTEXT: pass elif part.kind == part_kinds.OUTPUTPARAMETERS: self._buffer = part.unpack_rows(parameters_metadata, self.connection) self._received_last_resultset_part = True self._executed = True elif part.kind == part_kinds.RESULTSETMETADATA: self.description, self._column_types = self._handle_result_metadata( part) elif part.kind == part_kinds.RESULTSETID: self._resultset_id = part.value elif part.kind == part_kinds.RESULTSET: self._buffer = part.unpack_rows(self._column_types, self.connection) self._received_last_resultset_part = part.attribute & 1 self._executed = True else: raise InterfaceError( "Stored procedure call, unexpected part kind %d." % part.kind) self._executed = True
def _handle_select(self, parts, result_metadata=None): """Handle reply messages from SELECT statements""" self.rowcount = -1 if result_metadata is not None: # Select was prepared and we can use the already received metadata self.description, self._column_types = self._handle_result_metadata( result_metadata) for part in parts: if part.kind == part_kinds.RESULTSETID: self._resultset_id = part.value elif part.kind == part_kinds.RESULTSETMETADATA: self.description, self._column_types = self._handle_result_metadata( part) elif part.kind == part_kinds.RESULTSET: self._buffer = part.unpack_rows(self._column_types, self.connection) self._received_last_resultset_part = part.attribute & 1 self._executed = True elif part.kind in (part_kinds.STATEMENTCONTEXT, part_kinds.TRANSACTIONFLAGS, part_kinds.PARAMETERMETADATA): pass else: raise InterfaceError( "Prepared select statement response, unexpected part kind %d." % part.kind)
def _handle_upsert(self, parts, unwritten_lobs=()): """Handle reply messages from INSERT or UPDATE statements""" self.description = None self._received_last_resultset_part = True # set to 'True' so that cursor.fetch*() returns just empty list for part in parts: if part.kind == part_kinds.ROWSAFFECTED: self.rowcount = part.values[0] elif part.kind in (part_kinds.TRANSACTIONFLAGS, part_kinds.STATEMENTCONTEXT, part_kinds.PARAMETERMETADATA): pass elif part.kind == part_kinds.WRITELOBREPLY: # This part occurrs after lobs have been submitted not at all or only partially during an insert. # In this case the parameter part of the Request message contains a list called 'unwritten_lobs' # with LobBuffer instances. # Those instances are in the same order as 'locator_ids' received in the reply message. These IDs # are then used to deliver the missing LOB data to the server via WRITE_LOB_REQUESTs. for lob_buffer, lob_locator_id in izip(unwritten_lobs, part.locator_ids): # store locator_id in every lob buffer instance for later reference: lob_buffer.locator_id = lob_locator_id self._perform_lob_write_requests(unwritten_lobs) else: raise InterfaceError( "Prepared insert statement response, unexpected part kind %d." % part.kind) self._executed = True
def _execute_direct(self, operation): """Execute statements which are not going through 'prepare_statement' (aka 'direct execution'). Either their have no parameters, or Python's string expansion has been applied to the SQL statement. :param operation: """ request = RequestMessage.new( self.connection, RequestSegment(message_types.EXECUTEDIRECT, Command(operation))) reply = self.connection.send_request(request) parts = reply.segments[0].parts function_code = reply.segments[0].function_code if function_code == function_codes.SELECT: self._handle_select(parts) elif function_code in function_codes.DML: self._handle_upsert(parts) elif function_code == function_codes.DDL: # No additional handling is required pass elif function_code in (function_codes.DBPROCEDURECALL, function_codes.DBPROCEDURECALLWITHRESULT): self._handle_dbproc_call(parts, None) else: raise InterfaceError( "Invalid or unsupported function code received: %d" % function_code)
def _add_type_to_type_code_mapping(type_class, type_code): if not 0 <= type_code <= 127: raise InterfaceError( "%s type type_code must be between 0 and 127" % type_class.__name__ ) by_type_code[type_code] = type_class
def pack_data(self, remaining_size): payload = b"" arguments = 0 for option, value in self.options.items(): try: key, typ = self.option_definition[option] except KeyError: raise InterfaceError("Unknown option identifier %s" % option) if value is None: continue if typ == 1: value = struct.pack('B', value) elif typ == 2: value = struct.pack('h', value) elif typ == 3: value = struct.pack('i', value) elif typ == 4: value = struct.pack('q', value) elif typ == 28: value = struct.pack('?', value) elif typ == 29 or typ == 30: value = value.encode('utf-8') value = struct.pack('h', len(value)) + value else: raise Exception("Unknown option type %s" % typ) arguments += 1 payload += struct.pack('bb', key, typ) + value return arguments, payload
def __new__(mcs, name, bases, attrs): part_class = super(PartMeta, mcs).__new__(mcs, name, bases, attrs) if part_class.kind: if not -128 <= part_class.kind <= 127: raise InterfaceError("%s part kind must be between -128 and 127" % part_class.__name__) # Register new part class is registry dictionary for later lookup: PART_MAPPING[part_class.kind] = part_class return part_class
def escape_values(values): """ Escape multiple values from a list, tuple or dict. """ if isinstance(values, (tuple, list)): return tuple([escape(value) for value in values]) elif isinstance(values, dict): return dict([(key, escape(value)) for (key, value) in values.items()]) else: raise InterfaceError("escape_values expects list, tuple or dict")
def _handle_result_metadata(self, result_metadata): description = [] column_types = [] for column in result_metadata.columns: description.append((column[8], column[1], None, column[3], column[2], None, column[0] & 0b10)) if column[1] not in by_type_code: raise InterfaceError("Unknown column data type: %s" % column[1]) column_types.append(by_type_code[column[1]]) return tuple(description), tuple(column_types)
def get_length(payload): length_indicator = struct.unpack('B', payload.read(1))[0] if length_indicator <= 245: length = length_indicator elif length_indicator == 246: length = struct.unpack('h', payload.read(2))[0] elif length_indicator == 247: length = struct.unpack('i', payload.read(4))[0] elif length_indicator == 255: return None else: raise InterfaceError("Unknown length inidcator") return length
def to_daydate(cls, *argv): """ Convert date to Julian day (DAYDATE) """ argc = len(argv) if argc == 3: year, month, day = argv elif argc == 1: dval = argv[0] try: year = dval.year month = dval.month day = dval.day except AttributeError: raise InterfaceError("Unsupported python date input: %s (%s)" % (str(dval), dval.__class__)) else: raise InterfaceError( "Date.to_datetime does not support %d arguments." % argc) TURN_OF_ERAS = 1721424 if month < 3: year -= 1 month += 12 if ((year > 1582) or (year == 1582 and month > 10) or (year == 1582 and month == 10 and day >= 15)): A = int(year / 100) B = int(A / 4) C = 2 - A + B else: C = 0 E = int(365.25 * (year + 4716)) F = int(30.6001 * (month + 1)) Z = C + day + E + F - 1524 return Z + 1 - TURN_OF_ERAS
def escape(value): """ Escape a single value. """ if isinstance(value, (tuple, list)): return "(" + ", ".join([escape(arg) for arg in value]) + ")" else: typ = by_python_type.get(value.__class__) if typ is None: raise InterfaceError("Unsupported python input: %s (%s)" % (value, value.__class__)) return typ.to_sql(value)
def execute_prepared(self, prepared_statement, multi_row_parameters): """ :param prepared_statement: A PreparedStatement instance :param multi_row_parameters: A list/tuple containing list/tuples of parameters (for multiple rows) """ self._check_closed() # Convert parameters into a generator producing lists with parameters as named tuples (incl. some meta data): parameters = prepared_statement.prepare_parameters( multi_row_parameters) while parameters: request = RequestMessage.new( self.connection, RequestSegment(message_types.EXECUTE, (StatementId( prepared_statement.statement_id), Parameters(parameters)))) reply = self.connection.send_request(request) parts = reply.segments[0].parts function_code = reply.segments[0].function_code if function_code == function_codes.SELECT: self._handle_select(parts, prepared_statement.result_metadata_part) elif function_code in function_codes.DML: self._handle_upsert( parts, request.segments[0].parts[1].unwritten_lobs) elif function_code == function_codes.DDL: # No additional handling is required pass elif function_code in (function_codes.DBPROCEDURECALL, function_codes.DBPROCEDURECALLWITHRESULT): self._handle_dbproc_call(parts, prepared_statement._params_metadata ) # resultset metadata set in prepare else: raise InterfaceError( "Invalid or unsupported function code received: %d" % function_code)
def pack_data(self, remaining_size): payload = io.BytesIO() num_rows = 0 for row_parameters in self.parameters: # Loop over all input row parameters. # Memorize start position of row in buffer if it has to be removed in case that # the maximum message size will be exceeded (see below) row_header_start_pos = payload.tell() row_lobs = [] row_lob_size_sum = 0 for parameter in row_parameters: # 'parameter' is a named tuple, created in PreparedStatement.prepare_parameters() type_code, value = parameter.type_code, parameter.value try: _DataType = types.by_type_code[type_code] except KeyError: raise InterfaceError("Prepared statement parameter datatype not supported: %d" % type_code) if value is None: pfield = types.NoneType.prepare(type_code) elif type_code in types.String.type_code: pfield = _DataType.prepare(value, type_code) else: pfield = _DataType.prepare(value) if type_code in pyhdb.protocol.lobs.LOB_TYPE_CODE_MAP: # In case of value being a lob its actual data is not yet included in 'pfield' generated above. # Instead the lob data needs to be appended at the end of the packed row data. # Memorize the position of the lob header data (the 'pfield'): lob_header_pos = payload.tell() lob_buffer = LobBuffer(value, _DataType, lob_header_pos) # Add length of lob data to the sum so we can see whether all data fits into a segment below: row_lob_size_sum += lob_buffer.encoded_lob_size # Append lob data so it can be appended once all data for the row is packed: row_lobs.append(lob_buffer) payload.write(pfield) if payload.tell() >= remaining_size: # Last row (even without lobs) does not fit anymore into the current message! Remove it from payload # by resetting payload pointer to former position and truncate away last row data: payload.seek(row_header_start_pos) payload.truncate() self.parameters.back() # make generator to go one step back, so same item will be delivered again # Check for case that a row does not fit at all into a part block (i.e. it is the first one): if num_rows == 0: raise DataError('Parameter row too large to fit into execute statement.' 'Got: %d bytes, allowed: %d bytes' % (payload.tell() + row_lob_size_sum, remaining_size)) break # jump out of loop - no more rows to be added! else: # Keep row data. num_rows += 1 # Now append as much as possible of actual binary lob data after the end of all parameters of this row. # All those LOBs which were not or only partially written to the payload will be collected in # 'unwritten_lobs' for further LOBWRITEREQUESTs. self.unwritten_lobs = self.pack_lob_data(remaining_size, payload, row_header_start_pos, row_lobs) if payload.tell() >= remaining_size: # all the rest of the segment is filled with lob data, no more rows can be added: break return num_rows, payload.getvalue()