# ---------------------------------------------------------------------------------------------------------------------- # The Secondo Python API (pySecondo) # Victor Silva (victor.silva@posteo.de) # October 2019 # ---------------------------------------------------------------------------------------------------------------------- # Module # ---------------------------------------------------------------------------------------------------------------------- # Secondo API # secondoapi.py # ---------------------------------------------------------------------------------------------------------------------- """ The module Secondo API implements the Application Programming Interface for Python for the interaction with a |sec| server. The API implements the Python Database API 2.0 Specification (PEP 249). """ from socket import socket, AF_INET, SOCK_STREAM, TCP_NODELAY, IPPROTO_TCP import secondodb.api.support.secondoparser as parser import secondodb.api.support.secondomessages as messages import secondodb.api.support.secondocommands as com apilevel = '2.0' threadsafety = 1 paramstyle = 'pyformat' encoding = 'latin-1' error_encoding = 'ignore' def connect(host, port, username='username', passwd='passwd', database=''): """ Constructor for creating a connection to the |sec| server. If further data is provided (database and login data), a connection to an existing database (if available on the server) will be established. Returns a Connection Object. :param host: The host of the |sec| server as IP-address or as qualified name (localhost). :param port: The port of the |sec| server. :param username: The username for the connection (optional). :param passwd: The password for the connection (optional). :param database: The name of the database (optional). :return: A Connection Object. """ # Check if minimum parameters for the connection are available and valid if host == '': raise InterfaceError('The host is not valid.') if port == '': raise InterfaceError('The port is not valid.') if not parser.check_port(port): raise InterfaceError('The port has invalid characters.') if not parser.check_identifier(username): raise InterfaceError('The username is not valid.') if not parser.check_identifier(passwd): raise InterfaceError('The password is not valid.') if database != '' and not parser.check_identifier(database): raise InterfaceError('The database name is not valid.') port = int(port) # Create Connection object connection = Connection(host, port, username, passwd, database) if connection.initialized: return connection else: raise InterfaceError('Connection refused.') # --- Exceptions --- class Warning(Exception): """ Exception raised for important warnings like data truncations while inserting, etc. """ def __init__(self, message, *args): self.message = message super(Warning, self).__init__(message, *args) class Error(Exception): """ Exception that is the base class of all other error exceptions. You can use this to catch all errors with one single except statement. Warnings are not considered errors and thus should not use this class as base. """ def __init__(self, message, *args): self.message = message super(Error, self).__init__(message, *args) class InterfaceError(Error): """ Exception raised for errors that are related to the database interface rather than the database itself. """ def __init__(self, message, *args): self.message = message super(InterfaceError, self).__init__(message, *args) class DatabaseError(Error): """ Exception raised for errors that are related to the database. """ def __init__(self, message, *args): self.message = message super(DatabaseError, self).__init__(message, *args) class DataError(DatabaseError): """ Exception raised for errors that are due to problems with the processed data like division by zero, numeric value out of range, etc. """ def __init__(self, message, *args): self.message = message super(DataError, self).__init__(message, *args) class OperationalError(DatabaseError): """ Exception raised for errors that are related to the database's operation and not necessarily under the control of the programmer, e.g. an unexpected disconnect occurs, the data source name is not found, a transaction could not be processed, a memory allocation error occurred during processing, etc. """ def __init__(self, message, *args): self.message = message super(OperationalError, self).__init__(message, *args) class IntegrityError(DatabaseError): """ Exception raised when the relational integrity of the database is affected, e.g. a foreign key check fails. """ def __init__(self, message, *args): self.message = message super(IntegrityError, self).__init__(message, *args) class InternalError(DatabaseError): """ Exception raised when the database encounters an internal error, e.g. the cursor is not valid anymore, the transaction is out of sync, etc. """ def __init__(self, message, *args): self.message = message super(InternalError, self).__init__(message, *args) class ProgrammingError(DatabaseError): """ Exception raised for programming errors, e.g. table not found or already exists, syntax error in the SQL statement, wrong number of parameters specified, etc. """ def __init__(self, message, *args): self.message = message super(ProgrammingError, self).__init__(message, *args) class NotSupportedError(DatabaseError): """ Exception raised in case a method or database API was used which is not supported by the database, e.g. requesting a .rollback() on a connection that does not support transaction or has transactions turned off. """ def __init__(self, message, *args): self.message = message super(NotSupportedError, self).__init__(message, *args) # --- Connection Objects --- class Connection: """ This class implements the connection object of the |sec| API. The connection object manages all operations at server level and provides access to the current connection instance with the server. The connection object provides the cursor() method to create a cursor for the execution of operations at the database level. """ def __init__(self, host, port, username, passwd, database): """ Constructor of the Connection object. :param host: The host of the |sec| Server as IP-address or as qualified name (localhost). :param port: The port of the |sec| Server. :param username: The username for the connection. :param passwd: The password for the connection. :param database: The name of the database. """ self.host = host self.port = port self.username = username self.passwd = passwd self.database = database self.socket_object = socket(AF_INET, SOCK_STREAM) self.socket_object.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) self.initialized = False self.server_mode_only = False self.transaction_init = False self.__initialize() def __initialize(self): """ Initializes the connection to the |sec| server. :return: None """ # Connect to socket using the host and the port try: self.socket_object.connect((self.host, self.port)) except ConnectionRefusedError as e: self.socket_object.close() raise InterfaceError(e.args[1]) except OSError as e: self.socket_object.close() raise InterfaceError(e.args[1] + ' - Check connection parameters.') # Receive initial data from socket intro_string = '' try: intro_string, ok_message = parser.receive_response(self.socket_object) except OSError as e: raise InterfaceError(e.args[1]) except OperationalError: raise if intro_string == messages.SECONDO_OK: # If Secondo accepted the token, proceed with the connection to the server conn_string = messages.SECONDO_CONNECT_START + self.username + '\n' \ + self.passwd + '\n' \ + messages.SECONDO_CONNECT_END self.socket_object.sendall(conn_string.encode()) # Get response list from socket try: intro_string, success_message = parser.receive_response(self.socket_object) except OperationalError: raise # Check intro string, print success message from the server if intro_string == messages.SECONDO_INTRO_START: self.initialized = True print(success_message) # If a database name was provided, connect to database if self.database != '': try: self.open_database(self.database) except OperationalError: self.server_mode_only = True self.database = '' self.username = '' self.passwd = '' self.socket_object.close() raise except InterfaceError: raise else: self.server_mode_only = False return True else: self.server_mode_only = True return True else: raise OperationalError('Error: Bad server response.') else: try: self.socket_object.close() except OSError as e: raise InterfaceError(e.args[1]) else: self.socket_object = None self.initialized = False raise InterfaceError('Error: No response from Secondo server') def close(self): """ Close the connection now. The connection will be unusable from this point forward; an InternalError exception will be raised if any operation is attempted with the connection. The same applies to all cursor objects trying to use the connection. Note that closing a connection without committing the changes first will cause an implicit rollback to be performed. :return: True, if the connection was closed successfully. """ if self.initialized: # Any pending transaction will be rolled back if self.transaction_init: try: self.rollback() except OperationalError: raise except InterfaceError: raise close_string = messages.SECONDO_DISCONNECT_END # Close open databases if not self.server_mode_only: try: self.close_database() except InterfaceError: raise try: self.socket_object.sendall(close_string.encode()) except OSError as e: raise OperationalError(e.args[1]) else: self.socket_object.close() self.socket_object = None self.initialized = False return True else: raise InterfaceError('Error: The connection object is not initialized.') def start_transaction(self): """ Starts a transaction in the database. :return: True, if the transaction was started successfully. """ if self.initialized: if self.server_mode_only: raise OperationalError('No connection to a database available.') command_level = 1 operation = com.SECONDO_COM_BEGIN_TR command_string = messages.SECONDO_COMMAND_START + str(command_level) + '\n' \ + operation + '\n' \ + messages.SECONDO_COMMAND_END self.socket_object.sendall(command_string.encode()) try: response_string, receive_list, error_dict = parser.receive_response(self.socket_object) except OperationalError: raise if error_dict['code'] != 0: if error_dict['code'] == 100: raise InterfaceError('Secondo: Unexpected fatal system error.') else: raise InterfaceError(error_dict['message']) else: self.transaction_init = True return True else: raise InterfaceError('Error: The connection object is not initialized.') def commit(self): """ Commit any pending transaction to the database. :return: True, if the current transaction was commit successfully. """ if self.initialized: if self.server_mode_only: raise InterfaceError('No connection to a database available.') if self.transaction_init: command_level = 1 operation = com.SECONDO_COM_COMMIT_TR command_string = messages.SECONDO_COMMAND_START + str(command_level) + '\n' \ + operation + '\n' \ + messages.SECONDO_COMMAND_END self.socket_object.sendall(command_string.encode()) try: response_string, receive_list, error_dict = parser.receive_response(self.socket_object) except OperationalError: raise if error_dict['code'] != 0: if error_dict['code'] == 100: raise InterfaceError('Secondo: Unexpected fatal system error.') else: raise InterfaceError(error_dict['message']) else: self.transaction_init = False return True else: raise InternalError('No transactions were initiated.') else: raise InterfaceError('Error: The connection object is not initialized.') def rollback(self): """ In case a database does provide transactions this method causes the database to roll back to the start of any pending transaction. Closing a connection without committing the changes first will cause an implicit rollback to be performed. :return: True, if the current transaction was rolled back successfully. """ if self.initialized: if self.server_mode_only: raise InterfaceError('No connection to a database available.') if self.transaction_init: command_level = 1 operation = com.SECONDO_COM_ABORT_TR command_string = messages.SECONDO_COMMAND_START + str(command_level) + '\n' \ + operation + '\n' \ + messages.SECONDO_COMMAND_END self.socket_object.sendall(command_string.encode()) try: response_string, receive_list, error_dict = parser.receive_response(self.socket_object) except OperationalError: raise if error_dict['code'] != 0: if error_dict['code'] == 100: raise InterfaceError('Secondo: Unexpected fatal system error.') else: raise InterfaceError(error_dict['message']) else: self.transaction_init = False return True else: raise OperationalError('No transactions were initiated.') else: raise InterfaceError('Error: The connection object is not initialized.') def cursor(self): """ Return a new Cursor Object using the connection. If the database does not provide a direct cursor concept, the module will have to emulate cursors using other means to the extent needed by this specification. :return: A Cursor object. """ cursor = Cursor(self) return cursor def create_database(self, database_name): """ Creates a database on the |sec| server. The database remains open after creation. :param database_name: The name of the new database. :return: True, if the database was created successfully. """ if self.initialized: command_level = 1 database_name.lower() if parser.check_identifier(database_name): operation = com.SECONDO_COM_CREATE_DB.format(database_name) command_string = messages.SECONDO_COMMAND_START + str(command_level) + '\n' \ + operation + '\n' \ + messages.SECONDO_COMMAND_END self.socket_object.sendall(command_string.encode()) try: response_string, receive_list, error_dict = parser.receive_response(self.socket_object) except OperationalError: raise if error_dict['code'] != 0: if error_dict['code'] == 701: raise OperationalError('The database ' + database_name.upper() + ' already exists.') if error_dict['code'] == 100: raise InterfaceError('Secondo: Unexpected fatal system error.') else: raise OperationalError(error_dict['message']) else: return True else: raise OperationalError('The database name has invalid characters.') else: raise InterfaceError('The connection has not been initialized.') def restore_database(self, database_name, database_location): """ Restores a database from a specific location into the |sec| server. After restoring the database will be available for establish a connection. :param database_name: The name of the database. :param database_location: The location of the database. :return: True, if the database was successfully restored. """ if self.initialized: command_level = 1 if parser.check_identifier(database_name): operation = com.SECONDO_COM_RESTORE_DB_FROM.format(database_name, database_location) command_string = messages.SECONDO_COMMAND_START + str(command_level) + '\n' \ + operation + '\n' \ + messages.SECONDO_COMMAND_END self.socket_object.sendall(command_string.encode()) try: response_string, receive_list, error_dict = parser.receive_response(self.socket_object) except OperationalError: raise if error_dict['code'] != 0: if error_dict['code'] == 100: raise InterfaceError('Secondo: Unexpected fatal system error.') else: raise InterfaceError(error_dict['message']) else: try: self.close_database() except InterfaceError: raise else: return True else: raise OperationalError('The database name has invalid characters.') else: raise InterfaceError('The connection has not been initialized.') def delete_database(self, database_name): """ Deletes a database on the |sec| server. Before deletion, all opened databases on the Secondo server will be closed. The connection object will be set to server-only mode. :param database_name: The name of the database. :return: True, if the database was deleted successfully. """ if self.initialized: # Close databases, if any of them are open if not self.server_mode_only: try: self.close_database() except InterfaceError: raise if parser.check_identifier(database_name): command_level = 1 database_name.lower() operation = com.SECONDO_COM_DELETE_DB.format(database_name) command_string = messages.SECONDO_COMMAND_START + str(command_level) + '\n' \ + operation + '\n' \ + messages.SECONDO_COMMAND_END self.socket_object.sendall(command_string.encode()) try: response_string, receive_list, error_dict = parser.receive_response(self.socket_object) except OperationalError: raise if error_dict['code'] != 0: if error_dict['code'] == 100: raise InterfaceError('Secondo: Unexpected fatal system error.') else: raise InterfaceError(error_dict['message']) else: return True else: raise OperationalError('The database name has invalid characters.') else: raise InterfaceError('The connection has not been initialized.') def get_list_databases(self): """ Returns a list of the databases in the |sec| server. :return: A Python list with the names of the available databases on the |sec| server. """ if self.initialized: command_level = 1 operation = com.SECONDO_COM_LIST_DB command_string = messages.SECONDO_COMMAND_START + str(command_level) + '\n' \ + operation + '\n' \ + messages.SECONDO_COMMAND_END self.socket_object.sendall(command_string.encode()) try: response_string, receive_list, error_dict = parser.receive_response(self.socket_object) except OperationalError: raise if error_dict['code'] != 0: if error_dict['code'] == 100: raise InterfaceError('Secondo: Unexpected fatal system error.') else: raise InterfaceError(error_dict['message']) else: databases = parser.parse_inquiry_databases(receive_list) return databases else: raise InterfaceError('The connection has not been initialized.') def get_list_objects(self): """ Returns a list of the objects in the |sec| server. :return: A Python list with the names available objects of the currently opened database. """ if self.initialized: command_level = 1 operation = com.SECONDO_COM_LIST_OBJECTS command_string = messages.SECONDO_COMMAND_START + str(command_level) + '\n' \ + operation + '\n' \ + messages.SECONDO_COMMAND_END self.socket_object.sendall(command_string.encode()) try: response_string, receive_list, error_dict = parser.receive_response(self.socket_object) except OperationalError: raise if error_dict['code'] != 0: if error_dict['code'] == 100: raise InterfaceError('Secondo: Unexpected fatal system error.') else: raise InterfaceError(error_dict['message']) else: objects = parser.parse_inquiry_objects(receive_list) return objects else: raise InterfaceError('The connection has not been initialized.') def get_list_types(self): """ Returns a list of the types available in the current open database. :return: A list expression object with the available types of the currently opened database. """ if self.initialized: command_level = 1 operation = com.SECONDO_COM_LIST_TYPES command_string = messages.SECONDO_COMMAND_START + str(command_level) + '\n' \ + operation + '\n' \ + messages.SECONDO_COMMAND_END self.socket_object.sendall(command_string.encode()) try: response_string, receive_list, error_dict = parser.receive_response(self.socket_object) except OperationalError: raise if error_dict['code'] != 0: if error_dict['code'] == 100: raise InterfaceError('Secondo: Unexpected fatal system error.') else: raise InterfaceError(error_dict['message']) else: types = parser.parse_inquiry_types(receive_list) return types else: raise InterfaceError('The connection has not been initialized.') def get_list_type_constructors(self): """ Returns a list of the type constructors available in the |sec| server. :return: A Python list with the constructors of the available types of the currently opened database. """ if self.initialized: command_level = 1 operation = com.SECONDO_COM_LIST_TYPE_CONS command_string = messages.SECONDO_COMMAND_START + str(command_level) + '\n' \ + operation + '\n' \ + messages.SECONDO_COMMAND_END self.socket_object.sendall(command_string.encode()) try: response_string, receive_list, error_dict = parser.receive_response(self.socket_object) except OperationalError: raise if error_dict['code'] != 0: if error_dict['code'] == 100: raise InterfaceError('Secondo: Unexpected fatal system error.') else: raise InterfaceError(error_dict['message']) else: type_constructors = parser.parse_inquiry_type_constructors(receive_list) return type_constructors else: raise InterfaceError('The connection has not been initialized.') def get_list_algebras(self): """ Returns a list of the available algebras in the |sec| server. :return: A Python list with the available algebras_1 on the |sec| server. """ if self.initialized: command_level = 1 operation = com.SECONDO_COM_LIST_ALGEBRAS command_string = messages.SECONDO_COMMAND_START + str(command_level) + '\n' \ + operation + '\n' \ + messages.SECONDO_COMMAND_END self.socket_object.sendall(command_string.encode()) try: response_string, receive_list, error_dict = parser.receive_response(self.socket_object) except OperationalError: raise if error_dict['code'] != 0: if error_dict['code'] == 100: raise InterfaceError('Secondo: Unexpected fatal system error.') else: raise InterfaceError(error_dict['message']) else: algebras = parser.parse_inquiry_algebras(receive_list) return algebras else: raise InterfaceError('The connection has not been initialized.') def get_algebra(self, algebra_name): """ Returns the details for an algebra from the |sec| server. :return: A Python list with the available algebras on the |sec| server. """ if self.initialized: command_level = 1 operation = com.SECONDO_COM_LIST_ALGEBRA.format(algebra_name) command_string = messages.SECONDO_COMMAND_START + str(command_level) + '\n' \ + operation + '\n' \ + messages.SECONDO_COMMAND_END self.socket_object.sendall(command_string.encode()) try: response_string, receive_list, error_dict = parser.receive_response(self.socket_object) except OperationalError: raise if error_dict['code'] != 0: if error_dict['code'] == 100: raise InterfaceError('Secondo: Unexpected fatal system error.') else: raise InterfaceError(error_dict['message']) else: algebra = parser.parse_inquiry_algebra(receive_list) return algebra else: raise InterfaceError('The connection has not been initialized.') def open_database(self, database_name): """ Opens a database on the |sec| server. The server-only mode will be set to False. :param database_name: The name of the database. :return: True, if the database was opened successfully. """ if self.initialized: command_level = 1 operation = com.SECONDO_COM_OPEN_DB.format(database_name) command_string = messages.SECONDO_COMMAND_START + str(command_level) + '\n' \ + operation + '\n' \ + messages.SECONDO_COMMAND_END self.socket_object.sendall(command_string.encode()) try: response_string, receive_list, error_dict = parser.receive_response(self.socket_object) except OperationalError: raise if error_dict['code'] != 0: if error_dict['code'] == 100: raise InterfaceError('Secondo: Unexpected fatal system error.') else: raise InterfaceError(error_dict['message']) else: self.server_mode_only = False self.database = database_name return True else: raise InterfaceError('The connection has not been initialized.') def close_database(self): """ Closes the currently opened database on the |sec| server. The connection object returns to the server-only mode. :return: True, if the database was closed successfully. """ if self.initialized: command_level = 1 operation = com.SECONDO_COM_CLOSE_DB command_string = messages.SECONDO_COMMAND_START + str(command_level) + '\n' \ + operation + '\n' \ + messages.SECONDO_COMMAND_END self.socket_object.sendall(command_string.encode()) try: response_string, receive_list, error_dict = parser.receive_response(self.socket_object) except OperationalError: raise if error_dict['code'] != 0: if error_dict['code'] == 100: self.server_mode_only = True raise InterfaceError('Secondo: Unexpected fatal system error.') else: raise InterfaceError(error_dict['message']) else: self.server_mode_only = True self.database = '' return True else: raise InterfaceError('The connection has not been initialized.') class Cursor: """ This class implements the cursor object of the |sec| API. A cursor implements functions to operate within a specific database connection. The main method of the class is the execute method, which implements the programm logic towards the execution of CRUD operations at database level. """ description = None rowcount = 0 def __init__(self, connection): """ Constructor of the Cursor object. The database can be initialized during the initialization of the Connection object or alternatively in the Cursor object through this constructor. It is important to set previously the variable database in the Connection object, otherwise the database won´t be initialized. In that case, an InterfaceError will be raised. :param connection: A Connection object. """ self.connection = connection self.initialized = False self.result = [] # If the connection to the database is not existent, connect to provided database if self.connection.server_mode_only: if self.connection.database != '': try: self.connection.open_database(self.connection.database) except OperationalError: raise else: self.initialized = True else: raise OperationalError("No database provided or initialized. The cursor couldn´t be created.") else: self.initialized = True def close(self): """ Close the cursor now (rather than whenever __del__ is called). The cursor will be unusable from this point forward; an InternalError exception will be raised if any operation is attempted with the cursor. :return: True, if the cursor was closed successfully. """ if self.initialized: try: self.connection.close_database() except InterfaceError: raise else: self.connection.database = '' self.initialized = False return True else: raise OperationalError("The cursor has not been initialized") def execute(self, operation, parameters=None): """ Prepare and execute a database operation (query or command). Parameters may be provided as sequence or mapping and will be bound to variables in the operation. The placeholders in the operation must use the following format: {i}, where i is an integer, which specifies the position of the parameter in the list of parameters. Example 1: Simple Query Operation = query {0} Parameter list = ['mehringdamm'] Formatted operation = query mehringdamm Example 2: Inserting Single Tuples Operation = query {0} inserttuple[{1}, {2}] count; Parameter list = ['myfirstrel','"Anna"', '27'] Formatted operation = query myfirstrel inserttuple["Anna", 27] count; :param operation: A string with a |sec| command. :param parameters: Further parameters for the execution of the command (currently not in use). :return: A |sec| object, a list with |sec| objects or a |sec| response. """ self.description = '' self.rowcount = 0 # Apply parameters, if some are supplied if parameters is not None: operation = com.apply_parameters_to_operation(operation, parameters) # Process operation if self.initialized: command_level = 1 command_string = messages.SECONDO_COMMAND_START + str(command_level) + '\n' \ + operation + '\n' \ + messages.SECONDO_COMMAND_END try: self.connection.socket_object.sendall(command_string.encode(encoding, error_encoding)) except UnicodeEncodeError as e: raise ProgrammingError(e.args[0]) else: try: response_string, receive_list, error_dict = parser.receive_response(self.connection.socket_object) except OperationalError: raise if error_dict['code'] != 0: if error_dict['code'] == 6: # No database open raise InterfaceError(error_dict['message']) else: raise ProgrammingError(error_dict['message']) else: if receive_list.get_list_length() > 1: try: query_response, object_type = parser.parse_query(receive_list) except InterfaceError as e: raise InterfaceError(e.args[0]) else: self.rowcount = 1 self.description = object_type string_output = str(receive_list) self.result.append([query_response, object_type, string_output]) return query_response, object_type, string_output else: return 'Success', '', 'Success' else: raise InternalError('The cursor has not been initialized or is already closed.') def executemany(self, operation, seq_of_parameters): """ Prepare a database operation (query or command) and then execute it against all parameter sequences or mappings found in the sequence seq_of_parameters. Every entry of the list seq_of_parameters must be declared as a list, even when just one parameter is being used. The placeholders in the operation must use the following format: {i}, where i is an integer number, which specifies the position of the parameter in the list of parameters. Example 1: Simple Query Operation = query {0} Parameter list = ['mehringdamm'] Formatted operation = query mehringdamm Example 2: Inserting Single Tuples Operation = query {0} inserttuple[{1}, {2}] count; Parameter list = ['myfirstrel','"Anna"', '27'] Formatted operation = query myfirstrel inserttuple["Anna", 27] count; :param operation: A |sec| command with placeholders. :param seq_of_parameters: A list with further parameters for the execution of the commands of the list. :return: A list with entries. Each entry is a list with an |sec| object, the type of the response and the list expression as string. """ response_list = [] self.description = '' self.rowcount = 0 # Apply parameters to operation for parameter_list in seq_of_parameters: response = self.execute(operation=operation, parameters=parameter_list) response_list.append(response) self.rowcount += 1 self.description = 'response list' return response_list def fetchone(self): """ Fetch the next row of a query result set, returning a single sequence, or None when no more data is available. An InterfaceError exception is raised if the previous call to .execute*() did not produce any result set or no call was issued yet. :return: A single row of a query result set. """ if self.initialized: if self.rowcount >= 1: try: one_row = self.result.pop(0) return one_row except IndexError: return None else: raise InterfaceError('Error: No rows in cursor available.') else: raise InternalError('The cursor has not been initialized or is already closed.') def fetchmany(self, size=1): """ Fetch the next set of rows of a query result, returning a sequence of sequences (e.g. a list of tuples). An empty sequence is returned when no more rows are available. If there are not enough rows to fulfil the size parameter, only the available rows will be returned. :param size: The number of the rows to be returned from the cursor. :return: A list with the fetched elements. """ if self.initialized: if size > 0: if self.rowcount >= 1: qty_elements = len(self.result) if qty_elements >= size: try: response_set = self.result[0:size] del self.result[0:size] except IndexError: return None else: return response_set else: try: response_set = self.result[0:qty_elements] del self.result[0:qty_elements] except IndexError: return None else: return response_set else: raise InternalError('Error: No rows in cursor available.') else: raise InterfaceError('Error: Invalid set size.') else: raise InternalError('The cursor has not been initialized or is already closed.') def fetchall(self): """ Fetch all (remaining) rows of a query result, returning them as a sequence of sequences (e.g. a list of tuples). An empty sequence is returned when no more rows are available. An InterfaceError exception is raised if the previous call to .execute*() did not produce any result set or no call was issued yet. :return: A list with the fetched elements. """ if self.initialized: qty_elements = len(self.result) if self.rowcount >= 1: if qty_elements > 0: return self.fetchmany(qty_elements) else: return [] else: raise InternalError('Error: No rows in cursor available.') else: raise InternalError('The cursor has not been initialized or is already closed.') def execute_create_empty_relation(self, relation_name: str, list_attributes: []): """ Creates an empty relation in the database. The list of the attributes must follow the following format: list_attributes = [attr] where attr = [Name, type] IMPORTANT: The name of the attributes must begin with an upper case character. Example: attr1 = ['Att1', 'string'] attr2 = ['Att2', 'string'] list_attributes = [attr1, attr2] :param relation_name: The name of the relation. :param list_attributes: A list with the attributes of the relation (please be aware of the format). :return: The string 'Success', if the relation was successfully created, otherwise None. """ if self.initialized: if len(list_attributes) > 0: str_attr_list = '[' counter = 0 for attr in list_attributes: if attr[0] is not None: str_attr_list = str_attr_list + attr[0] + ' : ' + attr[1] if counter < len(list_attributes) - 1: str_attr_list = str_attr_list + ', ' counter += 1 else: raise ProgrammingError('The attribute at position ' + str(counter) + ' has no valid identifier.') str_attr_list = str_attr_list + ']' command_string = com.SECONDO_COM_REL_CREATE_EMPTY.format(relation_name, str_attr_list) try: response = self.execute(command_string) return response[0] except ProgrammingError: raise else: raise ProgrammingError('Error: List of attributes is empty.') else: raise InternalError('The cursor has not been initialized or is already closed.') def execute_insert_tuple_into_relation(self, relation_name: str, single_tuple: []): """ Inserts a list of values into a relation. :param relation_name: The name of the relation. :param single_tuple: A single tuple of the relation. The single tuple is a list must contain two elements: the first element is a list with the values of the tuple, and the second element is a list with the types of the relation. :return: The string 'Success', if the tuples were successfully added, otherwise None. """ if self.initialized: if len(single_tuple) > 0: counter = 0 str_value_list = [] for column in range(len(single_tuple[0])): field_type = single_tuple[1][column] value = single_tuple[0][column] const_value = com.SECONDO_COM_REL_CONST_VALUE.format(field_type, value) str_value_list.append(const_value) if counter < len(single_tuple[0]) - 1: str_value_list.append(', ') # str_value_list = str_value_list + const_value # if counter < len(single_tuple[0]) - 1: # str_value_list = str_value_list + ', ' counter += 1 str_value = "".join(str_value_list) command_string = com.SECONDO_COM_REL_INSERT_TUPLE.format(relation_name, str_value) try: response = self.execute(command_string) return response[0] except ProgrammingError: raise else: raise ProgrammingError('Error: List of values is empty.') else: raise InternalError('The cursor has not been initialized or is already closed.') def execute_simple_query(self, value_expression): """ Evaluates the given value expression and displays the result object. :param value_expression: A value expression for the command 'query ' :return: A list expression object with the response from the |sec| server. """ if self.initialized: command_string = com.SECONDO_COM_QUERY.format(value_expression) try: response = self.execute(command_string) except ProgrammingError: raise else: return response[0] else: raise InternalError('The cursor has not been initialized or is already closed.') def execute_let(self, identifier, value_expression): """ This command does almost the same as the query command. In contrast, the result of the is not displayed on the screen. Instead, the result is stored in an object with the name . The command only runs successfully if the object does not exist yet in the database; otherwise, an error message is displayed. :param identifier: The identifier of the object. :param value_expression: A value expression for the command 'let = ' :return: A list expression object with the response from the |sec| server. """ if self.initialized: if not parser.check_identifier(identifier): raise OperationalError('Invalid identifier.') command_string = com.SECONDO_COM_LET.format(identifier, value_expression) try: response = self.execute(command_string) except ProgrammingError: raise else: return response[0] else: raise InternalError('The cursor has not been initialized or is already closed.') def execute_derive(self, identifier, value_expression): """ This works basically in the same way as the let command. The difference is the handling of the created objects during creating and restoring a database dump. The derive command should be used for objects that have no external representation, e.g., indexes. :param identifier: The identifier of the object. :param value_expression: A value expression for the command 'derive = ' :return: None """ if self.initialized: if not parser.check_identifier(identifier): raise OperationalError('Invalid identifier.') command_string = com.SECONDO_COM_DERIVE.format(identifier, value_expression) try: response = self.execute(command_string) except ProgrammingError: raise else: return response[0] else: raise InternalError('The cursor has not been initialized or is already closed.') def execute_update(self, identifier, value_expression): """ Assigns the result of the value expression to an existing object in the database. :param identifier: The identifier of the object. :param value_expression: A value expression for the command 'update := ' :return: The string Success, if the object was updated successfully. """ if self.initialized: if not parser.check_identifier(identifier): raise OperationalError('Invalid identifier.') command_string = com.SECONDO_COM_UPDATE.format(identifier, value_expression) try: response = self.execute(command_string) except ProgrammingError: raise else: return response[0] else: raise InternalError('The cursor has not been initialized or is already closed.') def execute_delete(self, identifier): """ Deletes the object with the name from the currently opened database. :param identifier: The identifier of the object. :return: The string Success, if the object was deleted successfully. """ if self.initialized: if not parser.check_identifier(identifier): raise OperationalError('Invalid identifier.') command_string = com.SECONDO_COM_DELETE.format(identifier) try: response = self.execute(command_string) except ProgrammingError: raise else: return response[0] else: raise InternalError('The cursor has not been initialized or is already closed.') def execute_create_type(self, identifier, type_expression): """ Creates a named type in the database. :param identifier: The identifier of the type to be created. :param type_expression: A type expression for the command 'type = ' :return: The string Success, if the named type was created successfully. """ if self.initialized: if not parser.check_identifier(identifier): raise OperationalError('Invalid identifier.') command_string = com.SECONDO_COM_TYPE.format(identifier, type_expression) try: response = self.execute(command_string) except ProgrammingError: raise else: return response[0] else: raise InternalError('The cursor has not been initialized or is already closed.') def execute_delete_type(self, identifier): """ Deletes a named type from the database. :param identifier: The identifier of the type to be deleted. :return: The string Success, if the named type was deleted successfully. """ if self.initialized: if not parser.check_identifier(identifier): raise OperationalError('Invalid identifier.') command_string = com.SECONDO_COM_DELETE_TYPE.format(identifier) try: response = self.execute(command_string) except ProgrammingError: raise else: return response[0] else: raise InternalError('The cursor has not been initialized or is already closed.') def execute_create(self, identifier, type_expression): """ Creates an object of the given type with undefined value. :param identifier: The identifier of the object to be created. :param type_expression: A type expression for the command 'create = ' :return: The string Success, if the object was created successfully. """ if self.initialized: if not parser.check_identifier(identifier): raise OperationalError('Invalid identifier.') command_string = com.SECONDO_COM_CREATE.format(identifier, type_expression) try: response = self.execute(command_string) except ProgrammingError: raise else: return response[0] else: raise InternalError('The cursor has not been initialized or is already closed.') def execute_kill(self, identifier): """ Removes the object with the name from the opened database catalog without removing its data structures. Generally, the delete command should be used to remove database objects. The kill command should only be used if the delete command crashes the database due to corrupted persistent data structures for this object. :param identifier: The identifier of the object to be "killed". :return: The string Success, if the object was "killed" successfully. """ if self.initialized: if not parser.check_identifier(identifier): raise OperationalError('Invalid identifier.') command_string = com.SECONDO_COM_KILL.format(identifier) try: response = self.execute(command_string) except ProgrammingError: raise else: return response[0] else: raise InternalError('The cursor has not been initialized or is already closed.')