diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/edbfuncs/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/edbfuncs/__init__.py index 742637636..1ec612d1f 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/edbfuncs/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/packages/edbfuncs/__init__.py @@ -408,19 +408,21 @@ class EdbFuncView(PGChildNodeView, DataTypeReader): # Insert null value against the parameters which do not have # default values. - if len(proargmodes_fltrd) > len(proargdefaultvals): - dif = len(proargmodes_fltrd) - len(proargdefaultvals) - while (dif > 0): - proargdefaultvals.insert(0, '') - dif -= 1 + dif = len(proargmodes_fltrd) - len(proargdefaultvals) + while dif > 0: + proargdefaultvals.insert(0, '') + dif -= 1 + + def list_get(arr, index, default=''): + return arr[index] if len(arr) > index else default # Prepare list of Argument list dict to be displayed in the Data Grid. params = {"arguments": [ self._map_arguments_dict( - i, proargmodes_fltrd[i] if len(proargmodes_fltrd) > i else '', - proargtypes[i] if len(proargtypes) > i else '', - proargnames[i] if len(proargnames) > i else '', - proargdefaultvals[i] if len(proargdefaultvals) > i else '' + i, list_get(proargmodes_fltrd, i), + list_get(proargtypes, i), + list_get(proargnames, i), + list_get(proargdefaultvals, i) ) for i in range(len(proargtypes))]} @@ -428,10 +430,10 @@ class EdbFuncView(PGChildNodeView, DataTypeReader): # panel. proargs = [self._map_arguments_list( - proargmodes_fltrd[i] if len(proargmodes_fltrd) > i else '', - proargtypes[i] if len(proargtypes) > i else '', - proargnames[i] if len(proargnames) > i else '', - proargdefaultvals[i] if len(proargdefaultvals) > i else '' + list_get(proargmodes_fltrd, i), + list_get(proargtypes, i), + list_get(proargnames, i), + list_get(proargdefaultvals, i) ) for i in range(len(proargtypes))] diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/__init__.py index 407f0f75b..f95355d39 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/types/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/types/__init__.py @@ -14,6 +14,7 @@ from functools import wraps import simplejson as json from flask import render_template, request, jsonify from flask_babelex import gettext +import re import pgadmin.browser.server_groups.servers.databases as database from config import PG_DEFAULT_DRIVER @@ -411,6 +412,65 @@ class TypeView(PGChildNodeView, DataTypeReader, SchemaDiffObjectCompare): data['precision'] = str(data['precision']) return data + def _additional_properties_composite(self, rows): + """ + Used by additional_properties internally for composite type. + :param rows: list of data + :return: formatted response + """ + res = dict() + properties_list = [] + # To display in composite collection grid + composite_lst = [] + + for row in rows: + # We will fetch Full type name + + typelist = ' '.join([row['attname'], row['fulltype']]) + if ( + not row['collname'] or + ( + row['collname'] == 'default' and + row['collnspname'] == 'pg_catalog' + ) + ): + full_collate = '' + collate = '' + else: + full_collate = get_driver(PG_DEFAULT_DRIVER).qtIdent( + self.conn, row['collnspname'], row['collname']) + collate = ' COLLATE ' + full_collate + + typelist += collate + properties_list.append(typelist) + + is_tlength, is_precision, typeval = \ + self.get_length_precision(row.get('elemoid', None)) + + # Split length, precision from type name for grid + t_len, t_prec = DataTypeReader.parse_length_precision( + row['fulltype'], is_tlength, is_precision) + + type_name = DataTypeReader.parse_type_name(row['typname']) + + row['type'] = self._cltype_formatter(type_name) + row['hasSqrBracket'] = self.hasSqrBracket + row = self.convert_length_precision_to_string(row) + composite_lst.append({ + 'attnum': row['attnum'], 'member_name': row['attname'], + 'type': type_name, + 'collation': full_collate, 'cltype': row['type'], + 'tlength': t_len, 'precision': t_prec, + 'is_tlength': is_tlength, 'is_precision': is_precision, + 'hasSqrBracket': row['hasSqrBracket'], + 'fulltype': row['fulltype']}) + + # Adding both results + res['member_list'] = ', '.join(properties_list) + res['composite'] = composite_lst + + return res + def additional_properties(self, copy_dict, tid): """ We will use this function to add additional properties according to @@ -423,93 +483,29 @@ class TypeView(PGChildNodeView, DataTypeReader, SchemaDiffObjectCompare): # Fetching type of type of_type = copy_dict['typtype'] res = dict() - # If type is of Composite then we need to add members list in our - # output + + render_args = {'type': of_type} if of_type == 'c': + render_args['typrelid'] = copy_dict['typrelid'] + else: + render_args['tid'] = tid + + if of_type in ('c', 'e', 'r'): SQL = render_template("/".join([self.template_path, 'additional_properties.sql']), - type='c', - typrelid=copy_dict['typrelid']) + **render_args) status, rset = self.conn.execute_dict(SQL) if not status: return internal_server_error(errormsg=res) + # If type is of Composite then we need to add members list in our + # output + if of_type == 'c': # To display in properties - properties_list = [] - # To display in composite collection grid - composite_lst = [] - - for row in rset['rows']: - # We will fetch Full type name - - typelist = ' '.join([row['attname'], row['fulltype']]) - if ( - not row['collname'] or - ( - row['collname'] == 'default' and - row['collnspname'] == 'pg_catalog' - ) - ): - full_collate = '' - collate = '' - else: - full_collate = get_driver(PG_DEFAULT_DRIVER).qtIdent( - self.conn, row['collnspname'], row['collname']) - collate = ' COLLATE ' + full_collate - typelist += collate - properties_list.append(typelist) - - is_tlength = False - is_precision = False - if 'elemoid' in row: - is_tlength, is_precision, typeval = \ - self.get_length_precision(row['elemoid']) - - # Below logic will allow us to split length, precision from - # type name for grid - import re - t_len = None - t_prec = None - - # If we have length & precision both - if is_tlength and is_precision: - match_obj = re.search(r'(\d+),(\d+)', row['fulltype']) - if match_obj: - t_len = match_obj.group(1) - t_prec = match_obj.group(2) - elif is_tlength: - # If we have length only - match_obj = re.search(r'(\d+)', row['fulltype']) - if match_obj: - t_len = match_obj.group(1) - t_prec = None - - type_name = DataTypeReader.parse_type_name(row['typname']) - - row['type'] = self._cltype_formatter(type_name) - row['hasSqrBracket'] = self.hasSqrBracket - row = self.convert_length_precision_to_string(row) - composite_lst.append({ - 'attnum': row['attnum'], 'member_name': row['attname'], - 'type': type_name, - 'collation': full_collate, 'cltype': row['type'], - 'tlength': t_len, 'precision': t_prec, - 'is_tlength': is_tlength, 'is_precision': is_precision, - 'hasSqrBracket': row['hasSqrBracket'], - 'fulltype': row['fulltype']}) - - # Adding both results - res['member_list'] = ', '.join(properties_list) - res['composite'] = composite_lst + res = self._additional_properties_composite(rset['rows']) # If type is of ENUM then we need to add labels in our output if of_type == 'e': - SQL = render_template("/".join([self.template_path, - 'additional_properties.sql']), - type='e', tid=tid) - status, rset = self.conn.execute_2darray(SQL) - if not status: - return internal_server_error(errormsg=res) # To display in properties properties_list = [] # To display in enum grid @@ -525,13 +521,7 @@ class TypeView(PGChildNodeView, DataTypeReader, SchemaDiffObjectCompare): # If type is of Range then we need to add collation,subtype etc in our # output if of_type == 'r': - SQL = render_template("/".join([self.template_path, - 'additional_properties.sql']), - type='r', tid=tid) - status, res = self.conn.execute_dict(SQL) - if not status: - return internal_server_error(errormsg=res) - range_dict = dict(res['rows'][0]) + range_dict = dict(rset['rows'][0]) res.update(range_dict) if 'seclabels' in copy_dict and copy_dict['seclabels'] is not None: @@ -1246,132 +1236,126 @@ class TypeView(PGChildNodeView, DataTypeReader, SchemaDiffObjectCompare): return data - def get_sql(self, gid, sid, data, scid, tid=None, is_sql=False): + def _get_new_sql(self, data, is_sql): """ - This function will genrate sql from model data + Used by get_sql internally for new type SQL + :param data: input data + :param is_sql: is sql + :return: generated SQL """ - if tid is not None: + required_args = [ + 'name', + 'typtype' + ] - for key in ['typacl']: - if key in data and data[key] is not None: - if 'added' in data[key]: - data[key]['added'] = parse_priv_to_db( - data[key]['added'], self.acl) - if 'changed' in data[key]: - data[key]['changed'] = parse_priv_to_db( - data[key]['changed'], self.acl) - if 'deleted' in data[key]: - data[key]['deleted'] = parse_priv_to_db( - data[key]['deleted'], self.acl) - - if 'composite' in data and len(data['composite']) > 0: - for key in ['added', 'changed', 'deleted']: - if key in data['composite']: - for each_type in data['composite'][key]: - each_type = self. \ - convert_length_precision_to_string(each_type) - if 'type' in each_type: - each_type['cltype'] = self._cltype_formatter( - each_type['type']) - each_type['hasSqrBracket'] = self.hasSqrBracket - - SQL = render_template( - "/".join([self.template_path, - self._PROPERTIES_SQL]), - scid=scid, tid=tid, - datlastsysoid=self.datlastsysoid, - show_system_objects=self.blueprint.show_system_objects - ) - status, res = self.conn.execute_dict(SQL) - if not status: - return internal_server_error(errormsg=res) - if len(res['rows']) == 0: - return gone( - gettext("Could not find the type in the database.") - ) - - # Making copy of output for future use - old_data = dict(res['rows'][0]) - - SQL = render_template("/".join([self.template_path, - self._ACL_SQL]), - scid=scid, tid=tid) - status, acl = self.conn.execute_dict(SQL) - if not status: - return internal_server_error(errormsg=acl) - - # We will set get privileges from acl sql so we don't need - # it from properties sql - old_data['typacl'] = [] - - for row in acl['rows']: - priv = parse_priv_from_db(row) - if row['deftype'] in old_data: - old_data[row['deftype']].append(priv) - else: - old_data[row['deftype']] = [priv] - - # Calling function to check and additional properties if available - old_data.update(self.additional_properties(old_data, tid)) - old_data = self._convert_for_sql(old_data) - - # If typname or collname is changed while comparing - # two schemas then we need to drop type and recreate it - if 'typtype' in data or 'typname' in data or 'collname' in data\ - or 'typinput' in data or 'typoutput' in data: - SQL = render_template( - "/".join([self.template_path, 'type_schema_diff.sql']), - data=data, o_data=old_data, conn=self.conn - ) - else: - SQL = render_template( - "/".join([self.template_path, self._UPDATE_SQL]), - data=data, o_data=old_data, conn=self.conn - ) - else: - required_args = [ - 'name', - 'typtype' - ] - - for arg in required_args: - if arg not in data: - return "-- definition incomplete" + for arg in required_args: + if arg not in data: + return "-- definition incomplete" # Additional checks go here # If type is range then check if subtype is defined or not - if data and data[arg] == 'r' and \ - ('typname' not in data or data['typname'] is None): + if data.get(arg, None) == 'r' and \ + data.get('typname', None) is None: return "-- definition incomplete" # If type is external then check if input/output # conversion function is defined - if data and data[arg] == 'b' and ( - 'typinput' not in data or - 'typoutput' not in data or - data['typinput'] is None or - data['typoutput'] is None): + if data.get(arg, None) == 'b' and ( + data.get('typinput', None) is None or + data.get('typoutput', None) is None): return "-- definition incomplete" - # Privileges - if 'typacl' in data and data['typacl'] is not None: - data['typacl'] = parse_priv_to_db(data['typacl'], self.acl) + # Privileges + if data.get('typacl', None): + data['typacl'] = parse_priv_to_db(data['typacl'], self.acl) - data = self._convert_for_sql(data) + data = self._convert_for_sql(data) - if 'composite' in data and len(data['composite']) > 0: - for each_type in data['composite']: - each_type = self.convert_length_precision_to_string( - each_type) + if len(data.get('composite', [])) > 0: + for each_type in data['composite']: + each_type = self.convert_length_precision_to_string( + each_type) + each_type['cltype'] = self._cltype_formatter( + each_type['type']) + each_type['hasSqrBracket'] = self.hasSqrBracket + + SQL = render_template("/".join([self.template_path, + self._CREATE_SQL]), + data=data, conn=self.conn, is_sql=is_sql) + + return SQL, data['name'] + + def get_sql(self, gid, sid, data, scid, tid=None, is_sql=False): + """ + This function will generate sql from model data + """ + if tid is None: + return self._get_new_sql(data, is_sql) + + for key in ['added', 'changed', 'deleted']: + if key in data.get('typacl', []): + data['typacl'][key] = parse_priv_to_db( + data['typacl'][key], self.acl) + + for each_type in data.get('composite', {}).get(key, []): + each_type = self. \ + convert_length_precision_to_string(each_type) + if 'type' in each_type: each_type['cltype'] = self._cltype_formatter( each_type['type']) each_type['hasSqrBracket'] = self.hasSqrBracket - SQL = render_template("/".join([self.template_path, - self._CREATE_SQL]), - data=data, conn=self.conn, is_sql=is_sql) + SQL = render_template( + "/".join([self.template_path, + self._PROPERTIES_SQL]), + scid=scid, tid=tid, + datlastsysoid=self.datlastsysoid, + show_system_objects=self.blueprint.show_system_objects + ) + status, res = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=res) + if len(res['rows']) == 0: + return gone( + gettext("Could not find the type in the database.") + ) - return SQL, data['name'] if 'name' in data else old_data['name'] + # Making copy of output for future use + old_data = dict(res['rows'][0]) + + SQL = render_template("/".join([self.template_path, + self._ACL_SQL]), + scid=scid, tid=tid) + status, acl = self.conn.execute_dict(SQL) + if not status: + return internal_server_error(errormsg=acl) + + # We will set get privileges from acl sql so we don't need + # it from properties sql + old_data['typacl'] = [] + + for row in acl['rows']: + priv = parse_priv_from_db(row) + old_data[row['deftype']] = \ + old_data.get(row['deftype'], []).append(priv) + + # Calling function to check and additional properties if available + old_data.update(self.additional_properties(old_data, tid)) + old_data = self._convert_for_sql(old_data) + + # If typname or collname is changed while comparing + # two schemas then we need to drop type and recreate it + render_sql = self._UPDATE_SQL + if any([key in data for key in + ['typtype', 'typname', 'collname', 'typinput', 'typoutput']]): + render_sql = 'type_schema_diff.sql' + + SQL = render_template( + "/".join([self.template_path, render_sql]), + data=data, o_data=old_data, conn=self.conn + ) + + return SQL, old_data['name'] @check_precondition def sql(self, gid, sid, did, scid, tid, **kwargs): diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/utils.py index 1eb26cb9e..362209a11 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/utils.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/utils.py @@ -11,6 +11,7 @@ import json import copy +import re from flask import render_template @@ -411,6 +412,30 @@ class DataTypeReader: return type_name + @classmethod + def parse_length_precision(cls, fulltype, is_tlength, is_precision): + """ + Parse the type string and split length, precision. + :param fulltype: type string + :param is_tlength: is length type + :param is_precision: is precision type + :return: length, precision + """ + t_len, t_prec = None, None + if is_tlength and is_precision: + match_obj = re.search(r'(\d+),(\d+)', fulltype) + if match_obj: + t_len = match_obj.group(1) + t_prec = match_obj.group(2) + elif is_tlength: + # If we have length only + match_obj = re.search(r'(\d+)', fulltype) + if match_obj: + t_len = match_obj.group(1) + t_prec = None + + return t_len, t_prec + def trigger_definition(data): """ diff --git a/web/pgadmin/browser/server_groups/servers/pgagent/__init__.py b/web/pgadmin/browser/server_groups/servers/pgagent/__init__.py index d47a100a6..4c9550332 100644 --- a/web/pgadmin/browser/server_groups/servers/pgagent/__init__.py +++ b/web/pgadmin/browser/server_groups/servers/pgagent/__init__.py @@ -566,21 +566,19 @@ SELECT EXISTS( :return: """ # Format the schedule data. Convert the boolean array - if 'jschedules' in data: - if 'added' in data['jschedules']: - for added_schedule in data['jschedules']['added']: - format_schedule_data(added_schedule) - if 'changed' in data['jschedules']: - for changed_schedule in data['jschedules']['changed']: - format_schedule_data(changed_schedule) + for key in ['added', 'changed']: + jschedules = data.get('jschedules', {}) + if key in jschedules: + for schedule in jschedules.get(key, []): + format_schedule_data(schedule) has_connection_str = self.manager.db_info['pgAgent']['has_connstr'] - if 'jsteps' in data and has_connection_str and \ - 'changed' in data['jsteps']: - for changed_step in data['jsteps']['changed']: - if 'jstconntype' not in changed_step and ( - 'jstdbname' in changed_step or - 'jstconnstr' in changed_step): + jssteps = data.get('jsteps', {}) + if 'changed' in jschedules: + for changed_step in jssteps.get('changed', []): + if 'jstconntype' not in changed_step and \ + ('jstdbname' in changed_step or + 'jstconnstr' in changed_step) and has_connection_str: status, rset = self.conn.execute_dict( render_template( "/".join([self.template_path, 'steps.sql']), @@ -596,11 +594,11 @@ SELECT EXISTS( row = rset['rows'][0] changed_step['jstconntype'] = row['jstconntype'] if row['jstconntype']: - if not ('jstdbname' in changed_step): - changed_step['jstdbname'] = row['jstdbname'] + changed_step['jstdbname'] = changed_step.get( + 'jstdbname', row['jstdbname']) else: - if not ('jstconnstr' in changed_step): - changed_step['jstconnstr'] = row['jstconnstr'] + changed_step['jstconnstr'] = changed_step.get( + 'jstconnstr', row['jstconnstr']) JobView.register_node_view(blueprint) diff --git a/web/pgadmin/misc/bgprocess/process_executor.py b/web/pgadmin/misc/bgprocess/process_executor.py index 13e48efdc..1ff667156 100755 --- a/web/pgadmin/misc/bgprocess/process_executor.py +++ b/web/pgadmin/misc/bgprocess/process_executor.py @@ -254,6 +254,39 @@ def update_status(**kw): raise ValueError("Please verify pid and db_file arguments.") +def _handle_execute_exception(ex, args, _stderr, exit_code=None): + """ + Used internally by execute to handle exception + :param ex: exception object + :param args: execute args dict + :param _stderr: stderr + :param exit_code: exit code override + """ + info = _log_exception() + if _stderr: + _stderr.log(info) + else: + print("WARNING: ", ex.strerror, file=sys.stderr) + args.update({'end_time': get_current_time()}) + args.update({ + 'exit_code': ex.errno if exit_code is None else exit_code}) + + +def _fetch_execute_output(process, _stdout, _stderr): + """ + Used internally by execute to fetch execute output and log it. + :param process: process obj + :param _stdout: stdout + :param _stderr: stderr + """ + data = process.communicate() + if data: + if data[0]: + _stdout.log(data[0]) + if data[1]: + _stderr.log(data[1]) + + def execute(argv): """ This function will execute the background process @@ -268,7 +301,6 @@ def execute(argv): # Create seprate thread for stdout and stderr process_stdout = ProcessLogger('out') process_stderr = ProcessLogger('err') - process = None try: # update start_time @@ -283,7 +315,7 @@ def execute(argv): update_status(**args) _log('Status updated...') - if 'PROCID' in os.environ and os.environ['PROCID'] in os.environ: + if os.environ.get(os.environ.get('PROCID', None), None): os.environ['PGPASSWORD'] = os.environ[os.environ['PROCID']] kwargs = dict() @@ -331,34 +363,14 @@ def execute(argv): args.update({'end_time': get_current_time()}) # Fetch last output, and error from process if it has missed. - data = process.communicate() - if data: - if data[0]: - process_stdout.log(data[0]) - if data[1]: - process_stderr.log(data[1]) + _fetch_execute_output(process, process_stdout, process_stderr) # If executable not found or invalid arguments passed except OSError as e: - info = _log_exception() - args.update({'exit_code': 500}) - if process_stderr: - process_stderr.log(info) - else: - print("WARNING: ", e.strerror, file=sys.stderr) - args.update({'end_time': get_current_time()}) - args.update({'exit_code': e.errno}) - + _handle_execute_exception(e, args, process_stderr, exit_code=None) # Unknown errors except Exception as e: - info = _log_exception() - args.update({'exit_code': 501}) - if process_stderr: - process_stderr.log(info) - else: - print("WARNING: ", str(e), file=sys.stderr) - args.update({'end_time': get_current_time()}) - args.update({'exit_code': -1}) + _handle_execute_exception(e, args, process_stderr, exit_code=-1) finally: # Update the execution end_time, and exit-code. update_status(**args) diff --git a/web/pgadmin/tools/backup/__init__.py b/web/pgadmin/tools/backup/__init__.py index 994b28224..ef5c34fc4 100644 --- a/web/pgadmin/tools/backup/__init__.py +++ b/web/pgadmin/tools/backup/__init__.py @@ -11,6 +11,8 @@ import simplejson as json import os +import functools +import operator from flask import render_template, request, current_app, \ url_for, Response @@ -269,6 +271,125 @@ def filename_with_file_manager_path(_file, create_file=True): return short_filepath() +def _get_args_params_values(data, conn, backup_obj_type, backup_file, server, + manager): + """ + Used internally by create_backup_objects_job. This function will create + the required args and params for the job. + :param data: input data + :param conn: connection obj + :param backup_obj_type: object type + :param backup_file: file name + :param server: server obj + :param manager: connection manager + :return: args array + """ + from pgadmin.utils.driver import get_driver + driver = get_driver(PG_DEFAULT_DRIVER) + + host, port = (manager.local_bind_host, str(manager.local_bind_port)) \ + if manager.use_ssh_tunnel else (server.host, str(server.port)) + args = [ + '--file', + backup_file, + '--host', + host, + '--port', + port, + '--username', + server.username, + '--no-password' + ] + + def set_param(key, param, assertion=True): + if not assertion: + return + if data.get(key, None): + args.append(param) + + def set_value(key, param, default_value=None, assertion=True): + if not assertion: + return + val = data.get(key, default_value) + if val: + args.append(param) + args.append(val) + + if backup_obj_type != 'objects': + args.append('--database') + args.append(server.maintenance_db) + + if backup_obj_type == 'globals': + args.append('--globals-only') + + set_param('verbose', '--verbose') + set_param('dqoute', '--quote-all-identifiers') + set_value('role', '--role') + + if backup_obj_type == 'objects' and data.get('format', None): + args.extend(['--format={0}'.format({ + 'custom': 'c', + 'tar': 't', + 'plain': 'p', + 'directory': 'd' + }[data['format']])]) + + set_param('blobs', '--blobs', data['format'] in ['custom', 'tar']) + set_value('ratio', '--compress', None, + ['custom', 'plain', 'directory']) + + set_param('only_data', '--data-only', + data.get('only_data', None)) + set_param('disable_trigger', '--disable-triggers', + data.get('only_data', None) and + data.get('format', '') == 'plain') + + set_param('only_schema', '--schema-only', + data.get('only_schema', None) and + not data.get('only_data', None)) + + set_param('dns_owner', '--no-owner') + set_param('include_create_database', '--create') + set_param('include_drop_database', '--clean') + set_param('pre_data', '--section=pre-data') + set_param('data', '--section=data') + set_param('post_data', '--section=post-data') + set_param('dns_privilege', '--no-privileges') + set_param('dns_tablespace', '--no-tablespaces') + set_param('dns_unlogged_tbl_data', '--no-unlogged-table-data') + set_param('use_insert_commands', '--inserts') + set_param('use_column_inserts', '--column-inserts') + set_param('disable_quoting', '--disable-dollar-quoting') + set_param('with_oids', '--oids') + set_param('use_set_session_auth', '--use-set-session-authorization') + + set_param('no_comments', '--no-comments', manager.version >= 110000) + set_param('load_via_partition_root', '--load-via-partition-root', + manager.version >= 110000) + + set_value('encoding', '--encoding') + set_value('no_of_jobs', '--jobs') + + args.extend( + functools.reduce(operator.iconcat, map( + lambda s: ['--schema', r'{0}'.format(driver.qtIdent(conn, s). + replace('"', '\"'))], + data.get('schemas', [])), [] + ) + ) + + args.extend( + functools.reduce(operator.iconcat, map( + lambda s, t: ['--table', + r'{0}'.format(driver.qtIdent(conn, s, t) + .replace('"', '\"'))], + data.get('tables', [])), [] + ) + ) + + return args + + @blueprint.route( '/job/', methods=['POST'], endpoint='create_server_job' ) @@ -287,20 +408,13 @@ def create_backup_objects_job(sid): Returns: None """ - if request.form: - data = json.loads(request.form['data'], encoding='utf-8') - else: - data = json.loads(request.data, encoding='utf-8') - backup_obj_type = 'objects' - if 'type' in data: - backup_obj_type = data['type'] + data = json.loads(request.data, encoding='utf-8') + backup_obj_type = data.get('type', 'objects') try: - if 'format' in data and data['format'] == 'directory': - backup_file = filename_with_file_manager_path(data['file'], False) - else: - backup_file = filename_with_file_manager_path(data['file']) + backup_file = filename_with_file_manager_path( + data['file'], (data.get('format', '') != 'directory')) except Exception as e: return bad_request(errormsg=str(e)) @@ -338,112 +452,21 @@ def create_backup_objects_job(sid): errormsg=ret_val ) - args = [ - '--file', - backup_file, - '--host', - manager.local_bind_host if manager.use_ssh_tunnel else server.host, - '--port', - str(manager.local_bind_port) if manager.use_ssh_tunnel - else str(server.port), - '--username', - server.username, - '--no-password' - ] - - if backup_obj_type != 'objects': - args.append('--database') - args.append(server.maintenance_db) - - if backup_obj_type == 'globals': - args.append('--globals-only') - - def set_param(key, param): - if key in data and data[key]: - args.append(param) - - def set_value(key, param, default_value=None): - if key in data and data[key] is not None and data[key] != '': - args.append(param) - args.append(data[key]) - elif default_value is not None: - args.append(param) - args.append(default_value) - - set_param('verbose', '--verbose') - set_param('dqoute', '--quote-all-identifiers') - set_value('role', '--role') - - if backup_obj_type == 'objects' and \ - 'format' in data and data['format'] is not None: - if data['format'] == 'custom': - args.extend(['--format=c']) - set_param('blobs', '--blobs') - set_value('ratio', '--compress') - elif data['format'] == 'tar': - args.extend(['--format=t']) - set_param('blobs', '--blobs') - elif data['format'] == 'plain': - args.extend(['--format=p']) - set_value('ratio', '--compress') - elif data['format'] == 'directory': - args.extend(['--format=d']) - set_value('ratio', '--compress') - - if 'only_data' in data and data['only_data']: - set_param('only_data', '--data-only') - if 'format' in data and data['format'] == 'plain': - set_param('disable_trigger', '--disable-triggers') - elif 'only_schema' in data and data['only_schema']: - set_param('only_schema', '--schema-only') - - set_param('dns_owner', '--no-owner') - set_param('include_create_database', '--create') - set_param('include_drop_database', '--clean') - set_param('pre_data', '--section=pre-data') - set_param('data', '--section=data') - set_param('post_data', '--section=post-data') - set_param('dns_privilege', '--no-privileges') - set_param('dns_tablespace', '--no-tablespaces') - set_param('dns_unlogged_tbl_data', '--no-unlogged-table-data') - set_param('use_insert_commands', '--inserts') - set_param('use_column_inserts', '--column-inserts') - set_param('disable_quoting', '--disable-dollar-quoting') - set_param('with_oids', '--oids') - set_param('use_set_session_auth', '--use-set-session-authorization') - - if manager.version >= 110000: - set_param('no_comments', '--no-comments') - set_param('load_via_partition_root', '--load-via-partition-root') - - set_value('encoding', '--encoding') - set_value('no_of_jobs', '--jobs') - - if 'schemas' in data: - for s in data['schemas']: - args.extend(['--schema', r'{0}'.format( - driver.qtIdent(conn, s).replace('"', '\"'))]) - - if 'tables' in data: - for s, t in data['tables']: - args.extend([ - '--table', r'{0}'.format( - driver.qtIdent(conn, s, t).replace('"', '\"')) - ]) + args = _get_args_params_values( + data, conn, backup_obj_type, backup_file, server, manager) escaped_args = [ escape_dquotes_process_arg(arg) for arg in args ] try: + bfile = data['file'].encode('utf-8') \ + if hasattr(data['file'], 'encode') else data['file'] if backup_obj_type == 'objects': args.append(data['database']) escaped_args.append(data['database']) p = BatchProcess( desc=BackupMessage( - BACKUP.OBJECT, sid, - data['file'].encode('utf-8') if hasattr( - data['file'], 'encode' - ) else data['file'], + BACKUP.OBJECT, sid, bfile, *args, database=data['database'] ), @@ -454,10 +477,7 @@ def create_backup_objects_job(sid): desc=BackupMessage( BACKUP.SERVER if backup_obj_type != 'globals' else BACKUP.GLOBALS, - sid, - data['file'].encode('utf-8') if hasattr( - data['file'], 'encode' - ) else data['file'], + sid, bfile, *args ), cmd=utility, args=escaped_args