diff --git a/web/pgadmin/feature_tests/query_tool_journey_test.py b/web/pgadmin/feature_tests/query_tool_journey_test.py index 0047ed1f7..36521a086 100644 --- a/web/pgadmin/feature_tests/query_tool_journey_test.py +++ b/web/pgadmin/feature_tests/query_tool_journey_test.py @@ -9,6 +9,7 @@ import sys import random +import traceback from selenium.webdriver import ActionChains from selenium.webdriver.common.keys import Keys @@ -65,36 +66,42 @@ class QueryToolJourneyTest(BaseFeatureTest): self.wait = WebDriverWait(self.page.driver, 10) def runTest(self): - self._navigate_to_query_tool() - self.page.execute_query( - "SELECT * FROM %s ORDER BY value " % self.test_table_name) + try: + self._navigate_to_query_tool() + self.page.execute_query( + "SELECT * FROM %s ORDER BY value " % self.test_table_name) - print("Copy rows...", file=sys.stderr, end="") - self._test_copies_rows() - print(" OK.", file=sys.stderr) + print("Copy rows...", file=sys.stderr, end="") + self._test_copies_rows() + print(" OK.", file=sys.stderr) - print("Copy columns...", file=sys.stderr, end="") - self._test_copies_columns() - print(" OK.", file=sys.stderr) + print("Copy columns...", file=sys.stderr, end="") + self._test_copies_columns() + print(" OK.", file=sys.stderr) - print("History tab...", file=sys.stderr, end="") - self._test_history_tab() - print(" OK.", file=sys.stderr) + print("History tab...", file=sys.stderr, end="") + self._test_history_tab() + print(" OK.", file=sys.stderr) - self._insert_data_into_test_editable_table() + self._insert_data_into_test_editable_table() - print("History query source icons and generated queries toggle...", - file=sys.stderr, end="") - self._test_query_sources_and_generated_queries() - print(" OK.", file=sys.stderr) + print("History query source icons and generated queries toggle...", + file=sys.stderr, end="") + self._test_query_sources_and_generated_queries() + print(" OK.", file=sys.stderr) - print("Updatable result sets...", file=sys.stderr, end="") - self._test_updatable_resultset() - print(" OK.", file=sys.stderr) + print("Updatable result sets...", file=sys.stderr, end="") + self._test_updatable_resultset() + print(" OK.", file=sys.stderr) - print("Is editable column header icons...", file=sys.stderr, end="") - self._test_is_editable_columns_icons() - print(" OK.", file=sys.stderr) + print("Is editable column header icons...", + file=sys.stderr, end="") + self._test_is_editable_columns_icons() + print(" OK.", file=sys.stderr) + except Exception as e: + traceback.print_exc() + self.assertTrue(False, 'Exception occurred in run test Tests the ' + 'path through the query tool data' + str(e)) def _test_copies_rows(self): self.page.driver.switch_to.default_content() @@ -346,27 +353,6 @@ class QueryToolJourneyTest(BaseFeatureTest): self._commit_transaction() self.page.wait_for_spinner_to_disappear() - - # Turn on autocommit - # self.page.check_execute_option("auto_commit") - # query_options = self.page.find_by_css_selector( - # QueryToolLocators.btn_query_dropdown) - # query_options.click() - # retry = 3 - # while retry > 0: - # query_options = self.page.find_by_css_selector( - # QueryToolLocators.btn_query_dropdown) - # query_options.click() - # expanded = query_options.get_attribute("aria-expanded") - # if expanded == "false": - # print("query option not yet expanded clicking commit again", - # file=sys.stderr) - # self._commit_transaction() - # time.sleep(0.5) - # query_options.click() - # break - # else: - # retry -= 1 self.page.check_execute_option("auto_commit") def _check_history_queries_and_icons(self, history_queries, history_icons): @@ -396,16 +382,24 @@ class QueryToolJourneyTest(BaseFeatureTest): """ Updates a numeric cell in the first row of the resultset """ - cell_el = self.page.find_by_css_selector( - QueryToolLocators.output_row_col.format(2, 3)) - ActionChains(self.driver).double_click(cell_el).perform() - ActionChains(self.driver).send_keys(value). \ - send_keys(Keys.ENTER).perform() - - save_btn = WebDriverWait(self.driver, 5).until( - EC.element_to_be_clickable( - (By.CSS_SELECTOR, QueryToolLocators.btn_save_data))) - save_btn.click() + retry = 3 + while retry > 0: + cell_el = self.page.find_by_css_selector( + QueryToolLocators.output_row_col.format(2, 3)) + cell_el.click() + time.sleep(0.2) + ActionChains(self.driver).double_click(cell_el).perform() + ActionChains(self.driver).send_keys(value). \ + send_keys(Keys.ENTER).perform() + try: + save_btn = WebDriverWait(self.driver, 2).until( + EC.element_to_be_clickable( + (By.CSS_SELECTOR, QueryToolLocators.btn_save_data))) + save_btn.click() + break + except Exception: + print('Exception occurred') + retry -= 1 def _insert_data_into_test_editable_table(self): self.page.click_tab(self.query_editor_tab_id, rc_dock=True) @@ -473,6 +467,7 @@ class QueryToolJourneyTest(BaseFeatureTest): cell_value = int(cell_el.text) new_value = cell_value + 1 # Try to update value + cell_el.click() ActionChains(self.driver).double_click(cell_el).perform() ActionChains(self.driver).send_keys(new_value). \ send_keys(Keys.ENTER).perform() diff --git a/web/pgadmin/feature_tests/query_tool_tests.py b/web/pgadmin/feature_tests/query_tool_tests.py index c8f9a3c0a..d3102a1d0 100644 --- a/web/pgadmin/feature_tests/query_tool_tests.py +++ b/web/pgadmin/feature_tests/query_tool_tests.py @@ -118,9 +118,8 @@ class QueryToolFeatureTest(BaseFeatureTest): # this will set focus to correct iframe. self.page.fill_codemirror_area_with('') - self.page.retry_click( - (By.CSS_SELECTOR, QueryToolLocators.btn_explain_options_dropdown), - (By.CSS_SELECTOR, QueryToolLocators.btn_explain_verbose)) + self.assertTrue(self.page.open_explain_options(), + 'Unable to open Explain Options dropdown') # disable Explain options and auto rollback only if they are enabled. for op in (QueryToolLocators.btn_explain_verbose, @@ -261,10 +260,8 @@ SELECT generate_series(1, 1000) as id order by id desc""" self.page.fill_codemirror_area_with(query) time.sleep(0.5) - explain_op_btn_click = self.page.retry_click( - (By.CSS_SELECTOR, QueryToolLocators.btn_explain_options_dropdown), - (By.CSS_SELECTOR, QueryToolLocators.btn_explain_verbose)) - self.assertTrue(explain_op_btn_click, 'Explain Op button click failed') + self.assertTrue(self.page.open_explain_options(), + 'Unable to open Explain Options dropdown') # disable Explain options and auto rollback only if they are enabled. for op in (QueryToolLocators.btn_explain_verbose, @@ -298,10 +295,8 @@ SELECT generate_series(1, 1000) as id order by id desc""" self.page.fill_codemirror_area_with(query) - explain_op_btn_click = self.page.retry_click( - (By.CSS_SELECTOR, QueryToolLocators.btn_explain_options_dropdown), - (By.CSS_SELECTOR, QueryToolLocators.btn_explain_verbose)) - self.assertTrue(explain_op_btn_click, 'Explain Op button click failed') + self.assertTrue(self.page.open_explain_options(), + 'Unable to open Explain Options') # disable Explain options and auto rollback only if they are enabled. for op in (QueryToolLocators.btn_explain_buffers, @@ -684,9 +679,8 @@ SELECT 1, pg_sleep(300)""" self.page.fill_codemirror_area_with( "SELECT count(*) FROM pg_catalog.pg_class;") - self.page.retry_click( - (By.CSS_SELECTOR, QueryToolLocators.btn_explain_options_dropdown), - (By.CSS_SELECTOR, QueryToolLocators.btn_explain_verbose)) + self.assertTrue(self.page.open_explain_options(), + 'Unable to open Explain Options') # disable Explain options and only enable COST option for op in (QueryToolLocators.btn_explain_verbose, diff --git a/web/pgadmin/feature_tests/view_data_dml_queries.py b/web/pgadmin/feature_tests/view_data_dml_queries.py index 7c9b54f76..1c288664d 100644 --- a/web/pgadmin/feature_tests/view_data_dml_queries.py +++ b/web/pgadmin/feature_tests/view_data_dml_queries.py @@ -10,6 +10,7 @@ import json import os import time +import traceback from selenium.webdriver import ActionChains from regression.python_test_utils import test_utils @@ -119,22 +120,29 @@ CREATE TABLE public.nonintpkey connection.close() def runTest(self): - self.page.wait_for_spinner_to_disappear() - self.page.add_server(self.server) - self.page.expand_tables_node("Server", self.server['name'], - self.server['db_password'], self.test_db, - 'public') + try: + self.page.wait_for_spinner_to_disappear() + self.page.add_server(self.server) + self.page.expand_tables_node("Server", self.server['name'], + self.server['db_password'], + self.test_db, + 'public') - self._load_config_data('table_insert_update_cases') - data_local = config_data - # iterate on both tables - for cnt in (1, 2): - self._perform_test_for_table('defaults_{0}'.format(str(cnt)), - data_local) - # test nonint pkey table - self._load_config_data('table_insert_update_nonint') - data_local = config_data - self._perform_test_for_table('nonintpkey', data_local) + self._load_config_data('table_insert_update_cases') + data_local = config_data + # iterate on both tables + for cnt in (1, 2): + self._perform_test_for_table('defaults_{0}'.format(str(cnt)), + data_local) + # test nonint pkey table + self._load_config_data('table_insert_update_nonint') + data_local = config_data + self._perform_test_for_table('nonintpkey', data_local) + except Exception: + traceback.print_exc() + self.assertTrue(False, 'Exception occurred in run test ' + 'Validate Insert, Update operations in ' + 'View/Edit data with given test data') def after(self): self.page.remove_server(self.server) @@ -218,6 +226,7 @@ CREATE TABLE public.nonintpkey cell_el = self.page.find_by_xpath(xpath) self.page.driver.execute_script("arguments[0].scrollIntoView(false)", cell_el) + cell_el.click() ActionChains(self.driver).move_to_element(cell_el).double_click( cell_el ).perform() @@ -231,10 +240,20 @@ CREATE TABLE public.nonintpkey ActionChains(self.driver).send_keys(value). \ send_keys(Keys.ENTER).perform() elif cell_type in ['text', 'text[]', 'boolean[]']: - text_area_ele = WebDriverWait(self.driver, 5).until( - EC.visibility_of_element_located( - (By.CSS_SELECTOR, - QueryToolLocators.row_editor_text_area_css))) + retry = 2 + text_area_ele = None + while retry > 0: + try: + text_area_ele = WebDriverWait(self.driver, 2).until( + EC.visibility_of_element_located( + (By.CSS_SELECTOR, + QueryToolLocators.row_editor_text_area_css))) + retry = 0 + except Exception: + ActionChains(self.driver).move_to_element(cell_el).\ + double_click(cell_el).perform() + retry -= 1 + self.assertIsNotNone(text_area_ele, 'Text editor did not open.') text_area_ele.clear() text_area_ele.click() text_area_ele.send_keys(value) @@ -242,8 +261,6 @@ CREATE TABLE public.nonintpkey self.page.find_by_css_selector( QueryToolLocators.text_editor_ok_btn_css).click() elif cell_type in ['json', 'jsonb']: - jsoneditor_area_ele = self.page.find_by_css_selector( - QueryToolLocators.json_editor_text_area_css) platform = 'mac' if "platform" in self.driver.capabilities: platform = (self.driver.capabilities["platform"]).lower() @@ -359,7 +376,6 @@ CREATE TABLE public.nonintpkey "document.querySelector('.rdg').scrollLeft=0" ) - xpath = QueryToolLocators.output_row_xpath.format(2) scroll_on_arg_for_js = "arguments[0].scrollIntoView(false)" self.page.wait_for_query_tool_loading_indicator_to_disappear() diff --git a/web/regression/feature_utils/pgadmin_page.py b/web/regression/feature_utils/pgadmin_page.py index 1b0a0873f..8a0e15955 100644 --- a/web/regression/feature_utils/pgadmin_page.py +++ b/web/regression/feature_utils/pgadmin_page.py @@ -243,71 +243,82 @@ class PgadminPage: def check_execute_option(self, option): """"This function will check auto commit or auto roll back based on user input. If button is already checked, no action will be taken""" - menu_btn = self.driver.find_element( - By.CSS_SELECTOR, QueryToolLocators.btn_query_dropdown) - if menu_btn.get_attribute('data-state') == "closed": - menu_btn.click() - - def update_execute_option_setting(css_selector_of_option): - retry = 3 - menu_option = self.driver.find_element(By.CSS_SELECTOR, - css_selector_of_option) - if menu_option.get_attribute('data-checked') == 'false': - while retry > 0: - try: - menu_option.click() - time.sleep(0.2) - if menu_option.get_attribute('data-checked') == 'true': - break - else: - retry -= 1 - except Exception: - retry -= 1 - if option == 'auto_commit': - update_execute_option_setting( - QueryToolLocators.btn_auto_commit) + self._update_execute_option_setting( + QueryToolLocators.btn_auto_commit, True) if option == 'auto_rollback': - update_execute_option_setting( - QueryToolLocators.btn_auto_rollback) + self._update_execute_option_setting( + QueryToolLocators.btn_auto_rollback, True) - if menu_btn.get_attribute('data-state') == "open": - menu_btn.click() + def _update_drop_down_options(self, css_selector_of_option, is_selected): + is_selected = 'true' if is_selected else 'false' + retry = 3 + option_set_as_required = False + menu_option = self.driver.find_element(By.CSS_SELECTOR, + css_selector_of_option) + while retry > 0: + if menu_option.get_attribute('data-checked') == is_selected: + # Nothing to do + option_set_as_required = True + break + else: + menu_option.click() + time.sleep(0.2) + if menu_option.get_attribute('data-checked') == is_selected: + option_set_as_required = True + break + else: + retry -= 1 + return option_set_as_required + + def _open_query_tool_bar_drop_down(self, css_selector_of_dd): + menu_drop_down_opened = False + retry = 5 + while retry > 0: + dd_btn = self.driver.find_element( + By.CSS_SELECTOR, css_selector_of_dd) + if dd_btn.get_attribute('data-state') == "closed": + dd_btn.click() + time.sleep(0.2) + if dd_btn.get_attribute('data-state') == "open": + menu_drop_down_opened = True + retry = 0 + else: + retry -= 1 + self.driver.find_element( + By.CSS_SELECTOR, "button[data-label='Macros']").click() + dd_btn = self.driver.find_element( + By.CSS_SELECTOR, css_selector_of_dd) + action = ActionChains(self.driver) + action.move_to_element(dd_btn).perform() + action.click(dd_btn).perform() + else: + menu_drop_down_opened = True + retry = 0 + return menu_drop_down_opened + + def _update_execute_option_setting(self, + css_selector_of_option, is_selected): + if self._open_query_tool_bar_drop_down( + QueryToolLocators.btn_query_dropdown): + return self._update_drop_down_options( + css_selector_of_option, is_selected) + else: + return False def uncheck_execute_option(self, option): """"This function will uncheck auto commit or auto roll back based on user input. If button is already unchecked, no action will be taken""" - menu = self.driver.find_element( - By.CSS_SELECTOR, - QueryToolLocators.query_tool_menu.format('Execute Options Menu')) - - if menu.get_attribute('data-state') == "closed": - self.driver.find_element( - By.CSS_SELECTOR, QueryToolLocators.btn_query_dropdown).click() - - def update_execute_option_setting(css_selector_of_option): - retry = 3 - menu_option = self.driver.find_element( - By.CSS_SELECTOR, css_selector_of_option) - if menu_option.get_attribute('data-checked') == 'true': - while retry > 0: - menu_option.click() - time.sleep(0.2) - if menu_option.get_attribute('data-checked') == 'false': - break - else: - retry -= 1 - if option == 'auto_commit': - update_execute_option_setting( - QueryToolLocators.btn_auto_commit) + return self._update_execute_option_setting( + QueryToolLocators.btn_auto_commit, False) if option == 'auto_rollback': - update_execute_option_setting( - QueryToolLocators.btn_auto_rollback) + return self._update_execute_option_setting( + QueryToolLocators.btn_auto_rollback, False) - if menu.get_attribute('data-state') == "open": - self.driver.find_element( - By.CSS_SELECTOR, QueryToolLocators.btn_query_dropdown).click() + def open_explain_options(self): + return self._open_query_tool_bar_drop_down( + QueryToolLocators.btn_explain_options_dropdown) def close_data_grid(self): self.driver.switch_to.default_content() @@ -333,54 +344,6 @@ class PgadminPage: print("%s Server is not removed", server_config['name'], file=sys.stderr) - # TODO - Not used to be deleted - def select_tree_item(self, tree_item_text): - item = self.find_by_xpath( - "//*[@id='tree']//*[contains(text(), '" + tree_item_text + - "')]/parent::span[@class='aciTreeItem']") - self.driver.execute_script(self.js_executor_scrollintoview_arg, item) - # unexpected exception like element overlapping, click attempts more - # than one time - attempts = 3 - while attempts > 0: - try: - item.click() - break - except Exception as e: - attempts -= 1 - time.sleep(.4) - if attempts == 0: - raise e - - # TODO - Not used to be deleted - def click_a_tree_node(self, element_name, list_of_element): - """It will click a tree node eg. server, schema, table name etc - will take server name and list of element where this node lies""" - operation_status = False - elements = list_of_element = self.find_by_xpath_list( - list_of_element) - if len(elements) > 0: - index_of_element = self.get_index_of_element( - elements, element_name) - if index_of_element >= 0: - self.driver.execute_script( - self.js_executor_scrollintoview_arg, - list_of_element[index_of_element]) - self.wait_for_elements_to_appear( - self.driver, list_of_element[index_of_element]) - time.sleep(1) - list_of_element[index_of_element].click() - operation_status = True - else: - print("{ERROR} - The required element with name: " + str( - element_name) + - " is not found in function click_a_tree_node, " - "so click operation is not performed") - else: - print("{ERROR} - The element list passed to function " - "click_a_tree_node seems empty") - return operation_status - def click_to_expand_tree_node(self, tree_node_web_element, tree_node_exp_check_xpath): """ @@ -1197,20 +1160,6 @@ class PgadminPage: return self.wait_for_elements( lambda driver: driver.find_elements(By.XPATH, xpath)) - # TODO Not used any where to be removed - def get_index_of_element(self, element_list, target_string): - """it will return index of an element from provided element list""" - index_of_required_server = -1 - if len(element_list) > 0: - for index, element in enumerate(element_list): - if element.text.startswith(target_string) and ( - target_string in element.text): - index_of_required_server = index - break - else: - print("There seems no record in the provided element list") - return index_of_required_server - def set_switch_box_status(self, switch_box, required_status): """it will change switch box status to required one. Two elements of the switch boxes are to be provided i) button which is needed to @@ -1271,7 +1220,7 @@ class PgadminPage: WebDriverWait(self.driver, 10).until( EC.visibility_of_element_located(verify_locator)) click_status = True - except Exception as e: + except Exception: attempt += 1 return click_status diff --git a/web/regression/feature_utils/tree_area_locators.py b/web/regression/feature_utils/tree_area_locators.py index 0d9e7bb61..7b139bcf4 100644 --- a/web/regression/feature_utils/tree_area_locators.py +++ b/web/regression/feature_utils/tree_area_locators.py @@ -7,7 +7,7 @@ ########################################################################## -class TreeAreaLocators(): +class TreeAreaLocators: """This will contains element locators for tree area, will also contain parametrized xpath where applicable""" @@ -98,46 +98,42 @@ class TreeAreaLocators(): % schema_name # Schema child + child_node_exp_status = \ + "//div[div[span[span[starts-with(text(),'%s')]]]]/" \ + "following-sibling::div//span[span[text()='%s']]/" \ + "preceding-sibling::i[@class='directory-toggle open']" + + child_node = "//div[div[span[span[starts-with(text(),'%s')]]]]/" \ + "following-sibling::div//span[text()='%s']" + @staticmethod def schema_child_node_exp_status(schema_name, child_node_name): - return "//div[div[span[span[starts-with(text(),'%s')]]]]/" \ - "following-sibling::div//span[span[text()='%s']]/" \ - "preceding-sibling::i[@class='directory-toggle open']" \ - % (schema_name, child_node_name) + return TreeAreaLocators.child_node_exp_status \ + % (schema_name, child_node_name) @staticmethod def schema_child_node(schema_name, child_node_name): - return "//div[div[span[span[starts-with(text(),'%s')]]]]/" \ - "following-sibling::div//span[text()='%s']" \ - % (schema_name, child_node_name) + return TreeAreaLocators.child_node % (schema_name, child_node_name) # Database child @staticmethod def database_child_node_exp_status(database_name, child_node_name): - return "//div[div[span[span[starts-with(text(),'%s')]]]]/" \ - "following-sibling::div//span[span[text()='%s']]/" \ - "preceding-sibling::i[@class='directory-toggle open']"\ - % (database_name, child_node_name) + return TreeAreaLocators.child_node_exp_status \ + % (database_name, child_node_name) @staticmethod def database_child_node(database_name, child_node_name): - return "//div[div[span[span[starts-with(text(),'%s')]]]]/" \ - "following-sibling::div//span[text()='%s']" \ - % (database_name, child_node_name) + return TreeAreaLocators.child_node % (database_name, child_node_name) # Server child @staticmethod def server_child_node_exp_status(server_name, child_node_name): - return "//div[div[span[span[starts-with(text(),'%s')]]]]/" \ - "following-sibling::div//span[span[text()='%s']]/" \ - "preceding-sibling::i[@class='directory-toggle open']"\ - % (server_name, child_node_name) + return TreeAreaLocators.child_node_exp_status \ + % (server_name, child_node_name) @staticmethod def server_child_node(server_name, child_node_name): - return "//div[div[span[span[starts-with(text(),'%s')]]]]/" \ - "following-sibling::div//span[text()='%s']" \ - % (server_name, child_node_name) + return TreeAreaLocators.child_node % (server_name, child_node_name) # Table Node @staticmethod diff --git a/web/regression/python_test_utils/test_gui_helper.py b/web/regression/python_test_utils/test_gui_helper.py index 3d72fb54d..95766b85b 100644 --- a/web/regression/python_test_utils/test_gui_helper.py +++ b/web/regression/python_test_utils/test_gui_helper.py @@ -7,6 +7,7 @@ # ########################################################################## from regression.feature_utils.locators import NavMenuLocators +from selenium.webdriver.common.by import By def close_bgprocess_popup(tester): @@ -16,31 +17,36 @@ def close_bgprocess_popup(tester): # In cases where backup div is not closed (sometime due to some error) try: tester.page.wait_for_element_to_disappear( - lambda x: tester.driver.find_element_by_xpath( - ".ajs-message.ajs-bg-bgprocess.ajs-visible")) + lambda x: tester.driver.find_element( + By.XPATH, ".ajs-message.ajs-bg-bgprocess.ajs-visible")) except Exception: - tester.driver.find_element_by_css_selector( + tester.driver.find_element( + By.CSS_SELECTOR, NavMenuLocators.process_watcher_error_close_xpath).click() # In cases where restore div is not closed (sometime due to some error) try: tester.page.wait_for_element_to_disappear( - lambda x: tester.driver.find_element_by_xpath( + lambda x: tester.driver.find_element( + By.XPATH, "//div[@class='card-header bg-primary d-flex']/div" "[contains(text(), 'Restoring backup')]")) except Exception: - tester.driver.find_element_by_css_selector( + tester.driver.find_element( + By.CSS_SELECTOR, NavMenuLocators.process_watcher_error_close_xpath).click() # In cases where maintenance window is not closed (sometime due to some # error) try: tester.page.wait_for_element_to_disappear( - lambda x: tester.driver.find_element_by_xpath( + lambda x: tester.driver.find_element( + By.XPATH, "//div[@class='card-header bg-primary d-flex']/div" "[contains(text(), 'Maintenance')]")) except Exception: - tester.driver.find_element_by_css_selector( + tester.driver.find_element( + By.CSS_SELECTOR, NavMenuLocators.process_watcher_error_close_xpath).click() diff --git a/web/regression/python_test_utils/test_utils.py b/web/regression/python_test_utils/test_utils.py index 5cbdd3685..5a5a9379a 100644 --- a/web/regression/python_test_utils/test_utils.py +++ b/web/regression/python_test_utils/test_utils.py @@ -359,7 +359,7 @@ def create_debug_function(server, db_name, function_name="test_func"): pg_cursor = connection.cursor() try: pg_cursor.execute('''CREATE EXTENSION pldbgapi;''') - except Exception as e: + except Exception: pass pg_cursor.execute(''' CREATE OR REPLACE FUNCTION public."%s"()