def test_request_message_use_last_session_id(self): connection = Connection("localhost", 30015, "Fuu", "Bar") connection.session_id = 3 msg1 = RequestMessage.new(connection) assert msg1.session_id == connection.session_id connection.session_id = 5 msg2 = RequestMessage.new(connection) assert msg2.session_id == connection.session_id
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 _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 connect(self): with self._socket_lock: if self._socket is not None: # Socket already established return self._open_socket_and_init_protocoll() # Perform the authenication handshake and get the part # with the agreed authentication data agreed_auth_part = self._auth_manager.perform_handshake() request = RequestMessage.new( self, RequestSegment( message_types.CONNECT, ( agreed_auth_part, ClientId( "pyhdb-%s@%s" % (os.getpid(), socket.getfqdn()) ), ConnectOptions(DEFAULT_CONNECTION_OPTIONS) ) ) ) reply = self.send_request(request, no_reconnect=True) for segment in reply.segments: for part in segment.parts: if part.kind == part_kinds.CONNECTOPTIONS: self.connect_options = part.options
def prepare(self, statement): """Prepare SQL statement in HANA and cache it :param statement; a valid SQL statement :returns: statement_id (of prepared and cached statement) """ self._check_closed() self._column_types = None statement_id = params_metadata = result_metadata_part = None request = RequestMessage.new( self.connection, RequestSegment(message_types.PREPARE, Command(statement))) response = self.connection.send_request(request) for part in response.segments[0].parts: if part.kind == part_kinds.STATEMENTID: statement_id = part.statement_id elif part.kind == part_kinds.PARAMETERMETADATA: params_metadata = part.values elif part.kind == part_kinds.RESULTSETMETADATA: result_metadata_part = part # Check that both variables have been set in previous loop, we need them: assert statement_id is not None assert params_metadata is not None # cache statement: self._prepared_statements[statement_id] = PreparedStatement( self.connection, statement_id, params_metadata, result_metadata_part) return statement_id
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 test_request_message_init_with_multiple_segments_as_tuple(self): connection = Connection("localhost", 30015, "Fuu", "Bar") request_seg_1 = RequestSegment(0) request_seg_2 = RequestSegment(1) msg = RequestMessage.new(connection, (request_seg_1, request_seg_2)) assert msg.segments == (request_seg_1, request_seg_2)
def prepare(self, statement): """Prepare SQL statement in HANA and cache it :param statement; a valid SQL statement :returns: statement_id (of prepared and cached statement) """ self._check_closed() self._column_types = None statement_id = params_metadata = result_metadata_part = None request = RequestMessage.new( self.connection, RequestSegment( message_types.PREPARE, Command(statement) ) ) response = self.connection.send_request(request) for part in response.segments[0].parts: if part.kind == part_kinds.STATEMENTID: statement_id = part.statement_id elif part.kind == part_kinds.PARAMETERMETADATA: params_metadata = part.values elif part.kind == part_kinds.RESULTSETMETADATA: result_metadata_part = part # Check that both variables have been set in previous loop, we need them: assert statement_id is not None assert params_metadata is not None # cache statement: self._prepared_statements[statement_id] = PreparedStatement(self.connection, statement_id, params_metadata, result_metadata_part) return statement_id
def test_pack(): connection = Connection("localhost", 30015, "Fuu", "Bar") msg = RequestMessage.new(connection, [DummySegment(None)]) payload = msg.pack() packed = payload.getvalue() assert isinstance(packed, bytes) # Session id assert packed[0:8] == b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" # Packet count assert packed[8:12] == b"\x00\x00\x00\x00" # var part length assert packed[12:16] == b"\x0A\x00\x00\x00" # var part size assert packed[16:20] == b"\xE0\xFF\x01\x00" # no of segments assert packed[20:22] == b"\x01\x00" # reserved assert packed[22:32] == b"\x00" * 10 # payload assert packed[32:42] == b"\x00" * 10
def fetchmany(self, size=None): """Fetch many rows from select result set. :param size: Number of rows to return. :returns: list of row records (tuples) """ self._check_closed() if not self._executed: raise ProgrammingError("Require execute() first") if size is None: size = self.arraysize result = [] cnt = 0 while cnt != size: try: result.append(next(self._buffer)) cnt += 1 except StopIteration: break if cnt == size or self._received_last_resultset_part: # No rows are missing or there are no additional rows return result request = RequestMessage.new( self.connection, RequestSegment(message_types.FETCHNEXT, (ResultSetId(self._resultset_id), FetchSize(size - cnt))), ) response = self.connection.send_request(request) resultset_part = response.segments[0].parts[1] if resultset_part.attribute & 1: self._received_last_resultset_part = True result.extend(resultset_part.unpack_rows(self._column_types, self.connection)) return result
def test_request_message_init_with_multiple_segments_as_tuple(): connection = Connection("localhost", 30015, "Fuu", "Bar") request_seg_1 = RequestSegment(0) request_seg_2 = RequestSegment(1) msg = RequestMessage.new(connection, (request_seg_1, request_seg_2)) assert msg.segments == (request_seg_1, request_seg_2)
def connect(self): with self._socket_lock: if self._socket is not None: # Socket already established return self._open_socket_and_init_protocoll() # Perform the authenication handshake and get the part # with the agreed authentication data agreed_auth_part = self._auth_manager.perform_handshake() request = RequestMessage.new( self, RequestSegment( message_types.CONNECT, ( agreed_auth_part, ClientId( "pyhdb-%s@%s" % (os.getpid(), socket.getfqdn()) ), ConnectOptions(DEFAULT_CONNECTION_OPTIONS) ) ) ) self.send_request(request)
def test_pack(self): connection = Connection("localhost", 30015, "Fuu", "Bar") msg = RequestMessage.new(connection, [DummySegment(None)]) payload = msg.pack() packed = payload.getvalue() assert isinstance(packed, bytes) # Session id assert packed[0:8] == b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" # Packet count assert packed[8:12] == b"\x00\x00\x00\x00" # var part length assert packed[12:16] == b"\x0A\x00\x00\x00" # var part size assert packed[16:20] == b"\xE0\xFF\x01\x00" # no of segments assert packed[20:22] == b"\x01\x00" # reserved assert packed[22:32] == b"\x00" * 10 # payload assert packed[32:42] == b"\x00" * 10
def fetchmany(self, size=None): self._check_closed() if not self._executed: raise ProgrammingError("Require execute() first") if size is None: size = self.arraysize _result = [] _missing = size while bool(self._buffer) and _missing > 0: _result.append(self._buffer.popleft()) _missing -= 1 if _missing == 0 or self._received_last_resultset_part: # No rows are missing or there are no additional rows return _result request = RequestMessage.new( self.connection, RequestSegment( message_types.FETCHNEXT, (ResultSetId(self._resultset_id), FetchSize(_missing)) ) ) response = self.connection.send_request(request) if response.segments[0].parts[1].attribute & 1: self._received_last_resultset_part = True resultset_part = response.segments[0].parts[1] for row in self._unpack_rows(resultset_part.payload, resultset_part.rows): _result.append(row) return _result
def test_payload_pack(self, autocommit): connection = Connection("localhost", 30015, "Fuu", "Bar", autocommit=autocommit) msg = RequestMessage.new(connection, [DummySegment(None)]) payload = BytesIO() msg.build_payload(payload) assert payload.getvalue() == b"\x00" * 10
def test_invalid_request(connection): request = RequestMessage.new( connection, RequestSegment(2) ) with pytest.raises(DatabaseError): connection.send_request(request)
def commit(self): self._check_closed() request = RequestMessage.new( self, RequestSegment(message_types.COMMIT) ) self.send_request(request)
def rollback(self): self._check_closed() request = RequestMessage.new( self, RequestSegment(message_types.ROLLBACK) ) self.send_request(request)
def test_request_message_use_last_session_id(): connection = Connection("localhost", 30015, "Fuu", "Bar") connection.session_id = 1 msg = RequestMessage.new(connection) assert msg.session_id == connection.session_id connection.session_id = 5 assert msg.session_id == connection.session_id
def test_request_message_keep_packet_count(self, get_next_packet_count): connection = Connection("localhost", 30015, "Fuu", "Bar") msg = RequestMessage.new(connection) assert msg.packet_count == 0 # Check two time packet count of the message # but the get_next_packet_count method of connection # should only called once. assert msg.packet_count == 0 get_next_packet_count.assert_called_once_with()
def _perform_lob_write_requests(self, unwritten_lobs): """After sending incomplete LOB data during an INSERT or UPDATE this method will be called. It sends missing LOB data possibly in multiple LOBWRITE requests for all LOBs. :param unwritten_lobs: A deque list of LobBuffer instances containing LOB data. Those buffers have been assembled in the parts.Parameter.pack_lob_data() method. """ while unwritten_lobs: request = RequestMessage.new( self.connection, RequestSegment(message_types.WRITELOB, WriteLobRequest(unwritten_lobs)) ) self.connection.send_request(request)
def _perform_lob_write_requests(self, unwritten_lobs): """After sending incomplete LOB data during an INSERT or UPDATE this method will be called. It sends missing LOB data possibly in multiple LOBWRITE requests for all LOBs. :param unwritten_lobs: A deque list of LobBuffer instances containing LOB data. Those buffers have been assembled in the parts.Parameter.pack_lob_data() method. """ while unwritten_lobs: request = RequestMessage.new( self.connection, RequestSegment(message_types.WRITELOB, WriteLobRequest(unwritten_lobs))) self.connection.send_request(request)
def close(self): with self._socket_lock: if self._socket is None: raise Error("Connection already closed") try: request = RequestMessage.new(self, RequestSegment(message_types.DISCONNECT)) reply = self.send_request(request) if reply.segments[0].function_code != function_codes.DISCONNECT: raise Error("Connection wasn't closed correctly") finally: self._socket.close() self._socket = None
def close_result(connection, _resultset_id): request = RequestMessage.new( connection, RequestSegment(message_types.CLOSERESULTSET, (ResultSetId(_resultset_id)))) response = connection.send_request(request) # no exception... # no result check... # never failed... (what if connection issue...) return
def close(self): with self._socket_lock: if self._socket is None: raise Error("Connection already closed") try: request = RequestMessage.new( self, RequestSegment(message_types.DISCONNECT)) reply = self.send_request(request) if reply.segments[0].function_code != \ function_codes.DISCONNECT: raise Error("Connection wasn't closed correctly") finally: self._socket.close() self._socket = None
def _make_read_lob_request(self, readoffset, readlength): """Make low level request to HANA database (READLOBREQUEST). Compose request message with proper parameters and read lob data from second part object of reply. """ self._connection._check_closed() request = RequestMessage.new( self._connection, RequestSegment(message_types.READLOB, (ReadLobRequest( self._lob_header.locator_id, readoffset, readlength), ))) response = self._connection.send_request(request) # The segment of the message contains two parts. # 1) StatementContext -> ignored for now # 2) ReadLobReply -> contains some header information and actual LOB data data_part = response.segments[0].parts[1] # return actual lob container (BytesIO/TextIO): return data_part.data
def drop_statement(connection, statement_id): log('psid to drop --> %s' % (hextostr(statement_id)), 4) if statement_id is None or connection is None: return t0 = time.time() request = RequestMessage.new( connection, RequestSegment(message_types.DROPSTATEMENTID, StatementId(statement_id))) response = connection.send_request(request) t1 = time.time() log('psid drop took %s' % (str(round(t1 - t0, 3))), 4)
def perform_handshake(self): request = RequestMessage.new( self.connection, RequestSegment( message_types.AUTHENTICATE, Authentication(self.user, {self.method: self.client_key}))) response = self.connection.send_request(request, no_reconnect=True) auth_part = response.segments[0].parts[0] if self.method not in auth_part.methods: raise Exception( "Only unknown authentication methods available: %s" % b",".join(auth_part.methods.keys())) salt, server_key = Fields.unpack_data( BytesIO(auth_part.methods[self.method])) self.client_proof = self.calculate_client_proof([salt], server_key) return Authentication(self.user, {'SCRAMSHA256': self.client_proof})
def fetchmany(self, size=None): """Fetch many rows from select result set. :param size: Number of rows to return. :returns: list of row records (tuples) """ self._check_closed() if not self._executed: raise ProgrammingError("Require execute() first") if size is None: size = self.arraysize column_names = [column[0] for column in self.description ] if self.description else [] result = [] cnt = 0 while cnt != size: try: result.append(ResultRow(column_names, next(self._buffer))) cnt += 1 except StopIteration: break if cnt == size or self._received_last_resultset_part: # No rows are missing or there are no additional rows return result request = RequestMessage.new( self.connection, RequestSegment( message_types.FETCHNEXT, (ResultSetId(self._resultset_id), FetchSize(size - cnt)))) response = self.connection.send_request(request) resultset_part = response.segments[0].parts[1] if resultset_part.attribute & 1: self._received_last_resultset_part = True result_parts = resultset_part.unpack_rows(self._column_types, self.connection) for result_part in result_parts: result.append(ResultRow(column_names, result_part)) return result
def _make_read_lob_request(self, readoffset, readlength): """Make low level request to HANA database (READLOBREQUEST). Compose request message with proper parameters and read lob data from second part object of reply. """ self._connection._check_closed() request = RequestMessage.new( self._connection, RequestSegment( message_types.READLOB, (ReadLobRequest(self._lob_header.locator_id, readoffset, readlength),) ), ) response = self._connection.send_request(request) # The segment of the message contains two parts. # 1) StatementContext -> ignored for now # 2) ReadLobReply -> contains some header information and actual LOB data data_part = response.segments[0].parts[1] # return actual lob container (BytesIO/TextIO): return data_part.data
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 perform_handshake(self): request = RequestMessage.new( self.connection, RequestSegment( message_types.AUTHENTICATE, Authentication(self.user, {self.method: self.client_key}) ) ) response = self.connection.send_request(request, no_reconnect=True) auth_part = response.segments[0].parts[0] if self.method not in auth_part.methods: raise Exception( "Only unknown authentication methods available: %s" % b",".join(auth_part.methods.keys()) ) salt, server_key = Fields.unpack_data( BytesIO(auth_part.methods[self.method]) ) self.client_proof = self.calculate_client_proof([salt], server_key) return Authentication(self.user, {'SCRAMSHA256': self.client_proof})
def test_invalid_request(connection): request = RequestMessage.new(connection, RequestSegment(2)) with pytest.raises(DatabaseError): connection.send_request(request)
def test_request_message_init_without_segment(self): connection = Connection("localhost", 30015, "Fuu", "Bar") msg = RequestMessage.new(connection) assert msg.segments == ()
def test_request_message_init_with_single_segment(self): connection = Connection("localhost", 30015, "Fuu", "Bar") request_seg = RequestSegment(0) msg = RequestMessage.new(connection, request_seg) assert msg.segments == (request_seg, )
def test_request_message_init_without_segment(): connection = Connection("localhost", 30015, "Fuu", "Bar") msg = RequestMessage.new(connection) assert msg.segments == []
def test_request_message_init_with_single_segment(): connection = Connection("localhost", 30015, "Fuu", "Bar") request_seg = RequestSegment(0) msg = RequestMessage.new(connection, request_seg) assert msg.segments == [request_seg]