1457 lines
55 KiB
Python
1457 lines
55 KiB
Python
|
|
# ----------------------------------------------------------------------------------------------------------------------
|
|||
|
|
# 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 <value expression>'
|
|||
|
|
: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 <value expression> is not
|
|||
|
|
displayed on the screen. Instead, the result is stored in an object with the name <identifier>. 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 <identifier> = <value expression>'
|
|||
|
|
: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 <identifier> = <value expression>'
|
|||
|
|
: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 <identifier> := <value expression>'
|
|||
|
|
: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 <identifier> 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 <identifier> = <type expression>'
|
|||
|
|
: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 <identifier> = <type expression>'
|
|||
|
|
: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 <identifier> 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.')
|