diff --git a/web/pgadmin/tools/sqleditor/utils/save_changed_data.py b/web/pgadmin/tools/sqleditor/utils/save_changed_data.py index 9bb68a398..769a2ba06 100644 --- a/web/pgadmin/tools/sqleditor/utils/save_changed_data.py +++ b/web/pgadmin/tools/sqleditor/utils/save_changed_data.py @@ -54,6 +54,66 @@ def save_changed_data(changed_data, columns_info, conn, command_obj, if not status: return status, res, query_results, None + def verify_column_and_data_size(row_data): + """ + This function verifies the length of data type and + actual value of that row + :return: None + """ + for val in row_data: + if val in columns_info: + column_size = columns_info[val]['internal_size'] + value_size = len(row_data[val]) + if value_size > column_size: + validate_rows.append(val) + + def query_rollback(): + """ + This function rollback the transaction if any and update the message + for SQL query + :return: None + """ + if is_savepoint: + sql = 'ROLLBACK TO SAVEPOINT save_data;' + msg = 'A ROLLBACK was done for the save operation only. ' \ + 'The active transaction is not affected.' + else: + sql = 'ROLLBACK;' + msg = 'A ROLLBACK was done for the save transaction.' + + rollback_status, rollback_result = \ + execute_void_wrapper(conn, sql, query_results) + if not rollback_status: + return rollback_status, rollback_result, query_results, None + + # If we roll backed every thing then update the + # message for each sql query. + for query in query_results: + if query['status']: + query['result'] = msg + + def failure_handle(res, row_id): + """ + This function handles the query failure also rollback the transaction + and update the message for each SQL query + :param res: query response + :param row_id: row id + :return: res, query_results, row_id + """ + query_rollback() + mogrified_sql = conn.mogrify(item['sql'], item['data']) + mogrified_sql = mogrified_sql if mogrified_sql is not None \ + else item['sql'] + query_results.append({ + 'status': False, + 'result': res, + 'sql': mogrified_sql, + 'rows_affected': 0, + 'row_added': None + }) + return False, res, query_results, row_id + + validate_rows = [] # Iterate total number of records to be updated/inserted for of_type in changed_data: # No need to go further if its not add/update/delete operation @@ -104,6 +164,10 @@ def save_changed_data(changed_data, columns_info, conn, command_obj, # dict key tmp_row_index = added_index[each_row] data = changed_data[of_type][tmp_row_index]['data'] + + # Verify length of row data and length of data type + verify_column_and_data_size(data) + # Remove our unique tracking key data.pop(client_primary_key, None) data.pop('is_row_copied', None) @@ -156,6 +220,10 @@ def save_changed_data(changed_data, columns_info, conn, command_obj, for pk, pk_val in changed_data[of_type][each_row]['primary_keys'].items() } + + # Verify length of row data and length of data type + verify_column_and_data_size(data) + sql = render_template( "/".join([command_obj.sql_path, 'update.sql']), data_to_be_saved=data, @@ -212,38 +280,10 @@ def save_changed_data(changed_data, columns_info, conn, command_obj, ) list_of_sql[of_type].append({'sql': sql, 'data': {}}) - def failure_handle(res, row_id): - mogrified_sql = conn.mogrify(item['sql'], item['data']) - mogrified_sql = mogrified_sql if mogrified_sql is not None \ - else item['sql'] - query_results.append({ - 'status': False, - 'result': res, - 'sql': mogrified_sql, - 'rows_affected': 0, - 'row_added': None - }) - - if is_savepoint: - sql = 'ROLLBACK TO SAVEPOINT save_data;' - msg = 'A ROLLBACK was done for the save operation only. ' \ - 'The active transaction is not affected.' - else: - sql = 'ROLLBACK;' - msg = 'A ROLLBACK was done for the save transaction.' - - rollback_status, rollback_result = \ - execute_void_wrapper(conn, sql, query_results) - if not rollback_status: - return rollback_status, rollback_result, query_results, None - - # If we roll backed every thing then update the - # message for each sql query. - for query in query_results: - if query['status']: - query['result'] = msg - - return False, res, query_results, row_id + if validate_rows: + query_rollback() + return False, "Values are larger than underlying field size",\ + query_results, None for opr, sqls in list_of_sql.items(): for item in sqls: diff --git a/web/pgadmin/tools/sqleditor/utils/tests/test_save_changed_data.py b/web/pgadmin/tools/sqleditor/utils/tests/test_save_changed_data.py index f959b2b4b..1e5c5a7a0 100644 --- a/web/pgadmin/tools/sqleditor/utils/tests/test_save_changed_data.py +++ b/web/pgadmin/tools/sqleditor/utils/tests/test_save_changed_data.py @@ -71,6 +71,55 @@ class TestSaveChangedData(BaseTestGenerator): check_sql='SELECT * FROM %s WHERE pk_col = 3', check_result=[[3, "three"]] )), + ('When inserting row with long data', dict( + save_payload={ + "updated": {}, + "added": { + "2": { + "err": False, + "data": { + "pk_col": "3", + "__temp_PK": "2", + "normal_col": "invalid-log-string" + } + } + }, + "staged_rows": {}, + "deleted": {}, + "updated_index": {}, + "added_index": {"2": "2"}, + "columns": [ + { + "name": "pk_col", + "display_name": "pk_col", + "column_type": "[PK] integer", + "column_type_internal": "integer", + "pos": 0, + "label": "pk_col
[PK] integer", + "cell": "number", + "can_edit": True, + "type": "integer", + "not_null": True, + "has_default_val": False, + "is_array": False}, + {"name": "normal_col", + "display_name": "normal_col", + "column_type": "character varying", + "column_type_internal": "character varying", + "pos": 1, + "label": "normal_col
character varying", + "cell": "string", + "can_edit": True, + "type": "character varying", + "not_null": False, + "has_default_val": False, + "is_array": False} + ] + }, + save_status=False, + check_sql='SELECT * FROM %s WHERE pk_col = 3', + check_result='SELECT 0' + )), ('When inserting new invalid row', dict( save_payload={ "updated": {}, @@ -168,6 +217,53 @@ class TestSaveChangedData(BaseTestGenerator): check_sql='SELECT * FROM %s WHERE pk_col = 1', check_result=[[1, "ONE"]] )), + ('When updating a row in a invalid way', dict( + save_payload={ + "updated": { + "1": + {"err": False, + "data": {"normal_col": "INVALID-COL-LENGTH"}, + "primary_keys": + {"pk_col": 1} + } + }, + "added": {}, + "staged_rows": {}, + "deleted": {}, + "updated_index": {"1": "1"}, + "added_index": {}, + "columns": [ + { + "name": "pk_col", + "display_name": "pk_col", + "column_type": "[PK] integer", + "column_type_internal": "integer", + "pos": 0, + "label": "pk_col
[PK] integer", + "cell": "number", + "can_edit": True, + "type": "integer", + "not_null": True, + "has_default_val": False, + "is_array": False}, + {"name": "normal_col", + "display_name": "normal_col", + "column_type": "character varying", + "column_type_internal": "character varying", + "pos": 1, + "label": "normal_col
character varying", + "cell": "string", + "can_edit": True, + "type": "character varying", + "not_null": False, + "has_default_val": False, + "is_array": False} + ] + }, + save_status=False, + check_sql='SELECT * FROM %s WHERE pk_col = 1', + check_result=[[1, "one"]] + )), ('When updating a row in an invalid way', dict( save_payload={ "updated": { @@ -343,7 +439,7 @@ class TestSaveChangedData(BaseTestGenerator): CREATE TABLE "%s"( pk_col INT PRIMARY KEY, - normal_col VARCHAR); + normal_col VARCHAR(5)); INSERT INTO "%s" VALUES (1, 'one'),