Beispiel #1
0
    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
Beispiel #2
0
 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
Beispiel #3
0
    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)
Beispiel #4
0
    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
Beispiel #5
0
    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)
Beispiel #6
0
 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
Beispiel #7
0
    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
Beispiel #8
0
 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
Beispiel #9
0
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")
Beispiel #10
0
    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)
Beispiel #11
0
 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
Beispiel #12
0
    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
Beispiel #13
0
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)
Beispiel #14
0
    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)
Beispiel #15
0
    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()