# This example is for a dashboard user to modify data in a PostgreSQL DB table using 
# a modern grid configured with transaction functions. The workflow will rely on this 
# script to make actual modifications in the table. The table named USERS has 4 
# columns USER_ID(Int), USER_LOGIN(Varchar), HIRED_DATE(Date) and EMAIL(VarChar) 
# with USER_ID as the primary. Data changes for HIRED_DATE (Date) and EMAIL(VarChar) 
# are optional.

# In this template we use the library 'psycopg2' (installed in runtime as
# 'psycopg2-binary') to connect with the PostgreSQL database, submit atomic requests 
# and execute operations. For using other types of database, make sure to use
# their methods correctly. 

# Python libraries to handle SQL connections are not installed on the environment by
# default so it is necessary to choose runtime which have such library installed in the
# settings of the script. Additionally it is necessary to specify correct `Network
# Access` to allow script to communicate with some other IP then IP of the environment.
import psycopg2
import datetime


# helper method to handle not adding None values to the query
def append_not_none_value(array, value):
    if value is not None:
        array.append(value)
        return True
    return False

# Helper method to return datetime object in correct format for SQL operations
def parseDateTimeValue(dateValue):
    # somehow it will be converted to this datetime format
    #   "%Y-%m-%d %H:%M:%S.%f" -> 2023-04-01 00:00:00.000, 
    date_formats = [
        "%Y-%m-%d %H:%M:%S.%f",
        "%m/%d/%Y",
        "%m/%d/%Y %H:%M:%S",
        "%Y-%d-%m %H:%M:%S",
        "%m/%d/%Y %H:%M:%S.%f",
        "%m-%d-%Y"
    ]
    parsed_datetime = None
    for date_format in date_formats:
        try:
            parsed_datetime = datetime.datetime.strptime(dateValue, date_format)
            break
        except:
            pass

    if (parsed_datetime == None):
        raise Exception("Wrong Type of the datatime input value convertion!!!!")
    else:
        return parsed_datetime


try:
    # create connection to database. If you want to use other way then providing
    # user and password please check documentation of this method 
    connection = None
    connection = psycopg2.connect(
        host=$host,
        port=$port,
        database=$database,
        user=$user_name,
        password=$user_password
    )

    # Prepare query for database operations. Due to the fact that for some rows
    # we might want to pass `None` value we need some additional logic. This logic
    # require to create query during the time of reading passed properties.

    # Parameterized query for edit the existing row
    # We assume that when updating we must pass only `USER_LOGIN` and the other
    # properties are optional. Update is done based on `USER_ID` and `WHERE` clause
    # is added in the end of preparing query for each row
    tmp_sql_update_query = """
        UPDATE USERS SET USER_LOGIN = %s
    """

    # create cursor to execute operations on database
    cursor = connection.cursor()
    
    # deal with the every row data via prepared statements
    # we assume that the logic of passing data from dashboard works correctly and each
    # transactional column is either the array of length which is equal to number
    # of rows passed from dashboard or `None`.
    for index in range(len($user_login)):
        optional_values_array = []
        optional_values_in_update_query = ''
        # when array with values for each property is not `None` and the value
        # for particular row is not `None` then we add it 
        if $hired_date and $hired_date[index]:
            optional_values_array.append(parseDateTimeValue($hired_date[index]))
            optional_values_in_update_query += f",HIRED_DATE = %s"
        if $email and append_not_none_value(optional_values_array, $email[index]):
            optional_values_in_update_query += f",EMAIL = %s"

        # final UPDATE query is created based on initial UPDATE query and the part
        # created for particular row based on properties passed for this row
        # after adding optional properties we need to add `WHERE` clause
        # which in this case is based on `USER_ID`
        optional_values_array.append(int($user_id[index]))
        optional_values_in_update_query += f" WHERE USER_ID = %s"

        sql_update_tuple = (
            $user_login[index],
        ) + tuple(optional_values_array)
        sql_update_query = tmp_sql_update_query + optional_values_in_update_query
        # execute update query
        cursor.execute(sql_update_query, sql_update_tuple)

    # commit all changes when there weren't any errors
    connection.commit()

# error handling
except psycopg2.Error as e:
    # in case of any error we have to rollback all of the changes which
    # has been made on database
    if connection:
        connection.rollback()
    print("Error:", e)

finally:
    # close the database connection
    if connection:
        connection.close()
        