Add the create and drop feature

The table, specified by the user, can now be dropped and created in the
csv import dialog.
This commit is contained in:
Lea Laux 2021-02-18 14:06:17 +01:00 committed by KDV Admin
parent 7906123f47
commit ef9d6e6a20
2 changed files with 213 additions and 60 deletions

View File

@ -1,4 +1,5 @@
import copy import copy
import logging
import os import os
import csv import csv
import re import re
@ -90,20 +91,31 @@ class CSVImporter:
# Check the data type of the current column. # Check the data type of the current column.
for check_column in range(len(current_row)): for check_column in range(len(current_row)):
# Get the old/previous data type for comparison. # Try to parse and assume the data types. This process could fail caused by a wrong delimiter for the
old_data_type = self.data_types[check_column] # csv file, so the list of data types has a completely different length, causing an error.
# If the data type is TEXT, break, because there is nothing to change. This data type works in every try:
# case. # Get the old/previous data type for comparison.
if old_data_type != "TEXT": old_data_type = self.data_types[check_column]
# Get the current value. # If the data type is TEXT, break, because there is nothing to change. This data type works in every
value = current_row[check_column] # case.
# Get the data type of the current value. if old_data_type != "TEXT":
data_type = self.get_data_type(value) # Get the current value.
value = current_row[check_column]
# Get the data type of the current value.
data_type = self.get_data_type(value)
# If the data type is not null, write the data type in the data type list. Converting REAL to INT is # If the data type is not null, write the data type in the data type list. Converting REAL to
# not allowed. # INT is not allowed.
if data_type != "NULL" and (not (old_data_type == "REAL" and data_type == "INT")): if data_type != "NULL" and (not (old_data_type == "REAL" and data_type == "INT")):
self.data_types[check_column] = data_type self.data_types[check_column] = data_type
# Catch the index error of data type list, caused by a wrong delimiter in the csv file.
except IndexError:
# Log the incident.
logging.critical("The data types can not be assumed based on an index error. This is caused by a "
"wrong delimiter for the csv file.", exc_info=True)
# Raise the exception for further handling, for example in the user interface.
raise
def get_data_type(self, value): def get_data_type(self, value):
""" """
@ -131,18 +143,18 @@ class CSVImporter:
# Return TEXT, if a match could not be made. # Return TEXT, if a match could not be made.
return "TEXT" return "TEXT"
def create_table_for_csv_data(self): def create_table_for_csv_data(self, create_statement=None):
""" """
Create the table to store the csv data in the database. Create the table to store the csv data in the database. Set the default parameter for the create statement to
None, so a statement is created out of the given csv file. If the parameter is used, a new create statement can
be set, for example by a user.
""" """
# Get the create statement of the table. if create_statement is None:
create_statement = self.get_create_statement() # Get the create statement of the table.
create_statement = self.get_create_statement()
# Assign the create statement as query to the table. self.execute_query(create_statement)
self.database_query_executor.database_query = create_statement
# Execute!
self.database_query_executor.submit_and_execute_query()
def get_create_statement(self, check_ddl=True): def get_create_statement(self, check_ddl=True):
""" """
@ -278,16 +290,7 @@ class CSVImporter:
insert_query = "{}{}".format(insert_query, value_query) insert_query = "{}{}".format(insert_query, value_query)
# Execute the insert query. # Execute the insert query.
self.execute_insert_query(insert_query, parameter_list) self.execute_query(insert_query, parameter_list)
def execute_insert_query(self, insert_query, insert_parameters):
"""
Get the query and parameters for an insert and execute it with the database query executor.
"""
self.database_query_executor.database_query = insert_query
self.database_query_executor.database_query_parameter = insert_parameters
self.database_query_executor.submit_and_execute_query()
def create_insert_query_begin(self): def create_insert_query_begin(self):
""" """
@ -318,6 +321,46 @@ class CSVImporter:
# Return the begin of the query. # Return the begin of the query.
return insert_query return insert_query
def drop_table(self, table_name=None):
"""
Drop the given table, defined by the table name. If the table name stays None as default parameter, use the
deduced name.
"""
# Get the statement.
drop_statement = self.get_drop_statement(table_name)
# Execute the query.
self.execute_query(drop_statement)
def get_drop_statement(self, table_name=None):
"""
Create the drop statement for the existing table.
"""
# If the given table name is None, use the attribute of the class, defined by the name of the csv file.
if table_name is None:
self.get_table_name()
table_name = self.table_name
# Create the statement.
drop_statement = "DROP TABLE {};".format(table_name)
# Return the statement.
return drop_statement
def execute_query(self, query, parameters=None):
"""
Execute the given query with the parameters. If the parameters are not given, the default None is used, so only
the query is executed.
"""
# Assign the query as database query for the executor.
self.database_query_executor.database_query = query
# Use the parameters.
self.database_query_executor.database_query_parameter = parameters
# Execute!
self.database_query_executor.submit_and_execute_query()
@staticmethod @staticmethod
def check_ddl_parameter(parameter): def check_ddl_parameter(parameter):
""" """

View File

@ -1,7 +1,8 @@
import sys import sys
import time import time
from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QApplication, QPushButton, QMessageBox, QLineEdit from PyQt5.QtWidgets import QDialog, QGridLayout, QLabel, QApplication, QPushButton, QMessageBox, QLineEdit, \
QInputDialog
from pygadmin.connectionfactory import global_connection_factory from pygadmin.connectionfactory import global_connection_factory
from pygadmin.csv_importer import CSVImporter from pygadmin.csv_importer import CSVImporter
@ -20,29 +21,65 @@ class CSVImportDialog(QDialog):
icon_adder = IconAdder() icon_adder = IconAdder()
icon_adder.add_icon_to_widget(self) icon_adder.add_icon_to_widget(self)
# TODO: Workaround for delimiter # Get the result of an input dialog for getting the delimiter of the csv file.
self.csv_importer = CSVImporter(database_connection, csv_file) delimiter_result = self.init_delimiter_question(csv_file)
# The second variable in the tuple of the input dialog result contains the success. So if the user cancels the
# dialog, the result contains False.
if delimiter_result[1] is False:
# Inform the user about the aborting of the process.
QMessageBox.information(self, "Abort", "The csv import process has been aborted by the user.")
# End the function with a return.
return
# At this point, the input dialog for the delimiter was successful, so the result given by the user can be used
# as delimiter for the csv file.
self.csv_importer = CSVImporter(database_connection, csv_file, delimiter=delimiter_result[0])
# If the csv file exists, initialize the relevant parts of the dialog. # If the csv file exists, initialize the relevant parts of the dialog.
if self.csv_importer.check_existence_csv_file(): if self.csv_importer.check_existence_csv_file():
# Parse the given csv file.
self.csv_importer.parse_csv_file()
# Try to get the data types of the csv file. A fail or an error is possible and caused by a wrong delimiter.
try:
# Get the data types of the given csv file.
self.csv_importer.assume_data_types()
# Catch the error with showing the error to the user and end the initialization of the dialog.
except IndexError:
self.init_error_ui("The given delimiter is wrong, so the csv file {} cannot be processed.".format(
csv_file))
return
# Initialize the user interface.
self.init_ui() self.init_ui()
# Initialize the grid layout.
self.init_grid() self.init_grid()
self.csv_importer.database_query_executor.result_data.connect(self.show_success) # TODO: Better functions for connecting for the signals of the database query executor.
self.csv_importer.database_query_executor.error.connect(self.show_error)
# Show an error for a non existing file. # Show an error for a non existing file.
else: else:
self.init_error_ui(csv_file) self.init_error_ui("The given csv file {} is invalid".format(csv_file))
def init_delimiter_question(self, csv_file):
"""
Create an input dialog for getting the delimiter of the csv file.
"""
# Use an input dialog for getting the csv delimiter with the default of a comma, because it is called comma
# separated values.
delimiter_result = QInputDialog.getText(self, "CSV Delimiter", "What is the delimiter of your csv file "
"{}?".format(csv_file), text=",")
# Return the result of the dialog with the input and the success of the dialog.
return delimiter_result
def init_ui(self): def init_ui(self):
""" """
Initialize the user interface. Initialize the user interface.
""" """
# Parse the given csv file.
self.csv_importer.parse_csv_file()
# Get the data types of the given csv file.
self.csv_importer.assume_data_types()
# Get the create statement of the given csv file based on the column names in the file and the data types. # Get the create statement of the given csv file based on the column names in the file and the data types.
create_statement = self.csv_importer.get_create_statement() create_statement = self.csv_importer.get_create_statement()
@ -58,7 +95,7 @@ class CSVImportDialog(QDialog):
# Delete the first element, because this element does only contain a "CREATE TABLE". # Delete the first element, because this element does only contain a "CREATE TABLE".
del column_definition_list[0] del column_definition_list[0]
# Delete the last element as closing part of the statement. # Delete the last element as closing part of the statement.
del column_definition_list[len(column_definition_list)-1] del column_definition_list[len(column_definition_list) - 1]
# Split the column definition at commas. # Split the column definition at commas.
column_definition_list = [column_definition.replace(",", "") for column_definition in column_definition_list] column_definition_list = [column_definition.replace(",", "") for column_definition in column_definition_list]
@ -71,7 +108,7 @@ class CSVImportDialog(QDialog):
# Split the column at whitespaces, so the # Split the column at whitespaces, so the
identifier_list = column.split(" ") identifier_list = column.split(" ")
# Get the index of the last element based on the length of the identifier list. # Get the index of the last element based on the length of the identifier list.
last_element_index = len(identifier_list)-1 last_element_index = len(identifier_list) - 1
# The last item is the column data type, which is only one word with any whitespaces. # The last item is the column data type, which is only one word with any whitespaces.
column_data_type = identifier_list[last_element_index] column_data_type = identifier_list[last_element_index]
# Delete the last element from the list, because it is not necessary anymore and possibly disturbs the # Delete the last element from the list, because it is not necessary anymore and possibly disturbs the
@ -101,11 +138,23 @@ class CSVImportDialog(QDialog):
# Define a label for closing the statement. # Define a label for closing the statement.
self.close_label = QLabel(");") self.close_label = QLabel(");")
# Use a button for creating the given table.
self.create_button = QPushButton("Create Table")
# Connect the button with the function for creating the table.
self.create_button.clicked.connect(self.create_table)
# Create a drop button for dropping the table.
self.drop_button = QPushButton("Drop Table")
# Connect the button with the signal for actually dropping the table.
self.drop_button.clicked.connect(self.drop_table)
# Connect the signals for the result data and the error with functions for processing.
self.csv_importer.database_query_executor.result_data.connect(self.process_create_drop_success)
self.csv_importer.database_query_executor.error.connect(self.process_create_drop_error)
# Under construction TODO # Under construction TODO
self.insert_button = QPushButton("Insert") self.insert_button = QPushButton("Insert")
self.create_button = QPushButton("Create")
self.insert_button.clicked.connect(self.insert_data) self.insert_button.clicked.connect(self.insert_data)
self.create_button.clicked.connect(self.create_table)
self.show() self.show()
def init_grid(self): def init_grid(self):
@ -134,23 +183,27 @@ class CSVImportDialog(QDialog):
# Increment the count, so the next tuple of line edits is placed under those ones. # Increment the count, so the next tuple of line edits is placed under those ones.
line_edit_count += 1 line_edit_count += 1
# Place the close label at the end of CREATE # Place the close label at the end of CREATE.
grid_layout.addWidget(self.close_label, line_edit_count, 0) grid_layout.addWidget(self.close_label, line_edit_count, 0)
# Set the create button at the end of the statement, created by the line edits.
grid_layout.addWidget(self.create_button, line_edit_count, 1)
# Set the drop button on the right of the widget.
grid_layout.addWidget(self.drop_button, 0, 4)
# Set the spacing of the grid. # Set the spacing of the grid.
grid_layout.setSpacing(10) grid_layout.setSpacing(10)
# Set the layout to the grid layout. # Set the layout to the grid layout.
self.setLayout(grid_layout) self.setLayout(grid_layout)
def init_error_ui(self, csv_file): def init_error_ui(self, message):
""" """
Initialize the user interface for the error case and show the name of the invalid csv file. Initialize the user interface for the error case and use an error message for specifying the error.
""" """
# Get the layout as grid layout. # Get the layout as grid layout.
grid_layout = QGridLayout(self) grid_layout = QGridLayout(self)
# Add a label with an error. # Add a label with an error.
grid_layout.addWidget(QLabel("The given csv file {} is invalid".format(csv_file)), 0, 0) grid_layout.addWidget(QLabel(message), 0, 0)
self.setLayout(grid_layout) self.setLayout(grid_layout)
self.setMaximumSize(10, 100) self.setMaximumSize(10, 100)
self.showMaximized() self.showMaximized()
@ -160,24 +213,82 @@ class CSVImportDialog(QDialog):
def create_table(self): def create_table(self):
""" """
TODO: Build create statement and use the csv importer for execution Build the create statement specified by the user in the line edits and use the csv importer for execution of the
statement.
""" """
self.csv_importer.create_table_for_csv_data() create_statement = self.get_user_create_statement()
self.csv_importer.database_query_executor.result_data.connect(self.process_create_drop_success)
self.csv_importer.database_query_executor.error.connect(self.process_create_drop_error)
self.csv_importer.create_table_for_csv_data(create_statement)
def get_user_create_statement(self):
"""
Build the create statement specified by the user in the line edits.
"""
# Initialize the statement with the begin, the table name and the opening (.
create_statement = "{}{} {}\n".format(self.create_table_start_label.text(), self.table_name_line_edit.text(),
self.open_label.text())
# Iterate over the list of column line edits for getting the user data.
for line_edit_count in range(len(self.column_line_edit_list)):
# Get the line edit tuple.
line_edit = self.column_line_edit_list[line_edit_count]
# The first element contains the line edit for name of the column.
name_line_edit = line_edit[0]
# The second element contains the line edit for the data type of the column.
datatype_line_edit = line_edit[1]
# If the current line edit tuple is not the last one, use a comma as comma value for separating the column
# definitions.
if line_edit_count != len(self.column_line_edit_list) - 1:
comma_value = ","
# If the current line edit tuple is the last one, a comma is not necessary.
else:
comma_value = ""
# Add the column definition to the create statement.
create_statement = "{} {} {}{}\n".format(create_statement, name_line_edit.text(), datatype_line_edit.text(),
comma_value)
# Add the closing text to the create statement.
create_statement = "{}{}".format(create_statement, self.close_label.text())
# Return the result.
return create_statement
def drop_table(self):
"""
Drop the table specified in the line edit with the table name.
"""
table_name = self.table_name_line_edit.text()
self.csv_importer.drop_table(table_name)
def process_create_drop_success(self, result):
"""
Process the result of the table creation or dropping.
"""
# The parameter result contains a list. If the list is not empty, a success can be processed.
if result:
QMessageBox.information(self, "Create Success", "The result is {}".format(result))
def process_create_drop_error(self, result):
"""
Process the error during the create or drop process.
"""
QMessageBox.critical(self, "Create Error", "An error occurred during the create process: {}".format(result))
# under construction: TODO # under construction: TODO
def insert_data(self): def insert_data(self):
begin = time.time() begin = time.time()
self.csv_importer.create_and_execute_insert_queries() self.csv_importer.create_and_execute_insert_queries()
end = time.time() end = time.time()
print("Runtime: {}".format(end-begin)) print("Runtime: {}".format(end - begin))
def show_success(self, result):
QMessageBox.information(self, "Success", "The result is {}".format(result))
def show_error(self, error_message):
QMessageBox.critical(self, "Error", "{}".format(error_message))
print(error_message)
if __name__ == "__main__": if __name__ == "__main__":
@ -185,4 +296,3 @@ if __name__ == "__main__":
csv_import = CSVImportDialog(global_connection_factory.get_database_connection("localhost", "testuser", "testdb"), csv_import = CSVImportDialog(global_connection_factory.get_database_connection("localhost", "testuser", "testdb"),
"/home/sqlea/test.csv") "/home/sqlea/test.csv")
sys.exit(app.exec()) sys.exit(app.exec())