diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/__init__.py
new file mode 100644
index 0000000..0cf291f
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/__init__.py
@@ -0,0 +1,476 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+import json
+from flask import render_template, make_response, request, jsonify
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_json_response, \
+    make_response as ajax_response, internal_server_error
+from pgadmin.browser.utils import NodeView
+from pgadmin.browser.collection import CollectionNodeModule
+import pgadmin.browser.server_groups.servers as servers
+from pgadmin.utils.ajax import precondition_required
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from pgadmin.browser.server_groups.servers.utils import parse_priv_from_db, \
+    parse_priv_to_db
+from functools import wraps
+
+class ForeignDataWrapperModule(CollectionNodeModule):
+    NODE_TYPE = 'foreign_data_wrapper'
+    COLLECTION_LABEL = gettext("Foreign Data Wrappers")
+
+    def __init__(self, *args, **kwargs):
+        self.min_ver = None
+        self.max_ver = None
+
+        super(ForeignDataWrapperModule, self).__init__(*args, **kwargs)
+
+    def get_nodes(self, gid, sid, did):
+        """
+        Generate the collection node
+        """
+        yield self.generate_browser_collection_node(did)
+
+    @property
+    def script_load(self):
+        """
+        Load the module script for foreign data wrapper, when any of the database node is
+        initialized.
+        """
+        return servers.ServerModule.NODE_TYPE
+
+blueprint = ForeignDataWrapperModule(__name__)
+
+class ForeignDataWrapperView(NodeView):
+    node_type = blueprint.node_type
+
+    parent_ids = [
+            {'type': 'int', 'id': 'gid'},
+            {'type': 'int', 'id': 'sid'},
+            {'type': 'int', 'id': 'did'}
+            ]
+    ids = [
+            {'type': 'int', 'id': 'fid'}
+            ]
+
+    operations = dict({
+        'obj': [
+            {'get': 'properties', 'delete': 'delete', 'put': 'update'},
+            {'get': 'list', 'post': 'create'}
+        ],
+        'delete': [{
+           'delete': 'delete'
+        }],
+        'nodes': [{'get': 'node'}, {'get': 'nodes'}],
+        'children': [{'get': 'children'}],
+        'sql': [{'get': 'sql'}],
+        'msql': [{'get': 'msql'}, {'get': 'msql'}],
+        'stats': [{'get': 'statistics'}],
+        'dependency': [{'get': 'dependencies'}],
+        'dependent': [{'get': 'dependents'}],
+        'module.js': [{}, {}, {'get': 'module_js'}],
+        'gethandlers': [{}, {'get': 'gethandlers'}],
+        'getvalidators': [{}, {'get': 'getvalidators'}]
+    })
+
+    def module_js(self):
+        """
+        This property defines (if javascript) exists for this node.
+        Override this property for your own logic.
+        """
+        return make_response(
+                render_template(
+                    "foreign_data_wrappers/js/foreign_data_wrappers.js",
+                    _=gettext
+                    ),
+                200, {'Content-Type': 'application/x-javascript'}
+                )
+
+    def check_precondition(f):
+        """
+        This function will behave as a decorator which will checks
+        database connection before running view, it will also attaches
+        manager,conn & template_path properties to self
+        """
+        @wraps(f)
+        def wrap(*args, **kwargs):
+            # Here args[0] will hold self & kwargs will hold gid,sid,did
+            self = args[0]
+            self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(kwargs['sid'])
+            self.conn = self.manager.connection(did=kwargs['did'])
+
+            # If DB not connected then return error to browser
+            if not self.conn.connected():
+                return precondition_required(
+                    gettext(
+                            "Connection to the server has been lost!"
+                    )
+                )
+
+            self.template_path = 'foreign_data_wrappers/sql'
+
+            return f(*args, **kwargs)
+        return wrap
+
+    @check_precondition
+    def list(self, gid, sid, did):
+        SQL = render_template("/".join([self.template_path, 'properties.sql']),conn=self.conn)
+        status, res = self.conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        for row in res['rows']:
+            if row['fdwoptions'] is not None:
+                row['fdwoptions'] = self.tokenizeOptions(row['fdwoptions'])
+
+        return ajax_response(
+                response=res['rows'],
+                status=200
+                )
+
+    @check_precondition
+    def nodes(self, gid, sid, did):
+        res = []
+        SQL = render_template("/".join([self.template_path, 'properties.sql']),conn=self.conn)
+        status, rset = self.conn.execute_2darray(SQL)
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        for row in rset['rows']:
+            res.append(
+                    self.blueprint.generate_browser_node(
+                        row['fdwoid'],
+                        did,
+                        row['name'],
+                        icon="icon-foreign_data_wrapper"
+                    ))
+
+        return make_json_response(
+                data=res,
+                status=200
+                )
+
+    def tokenizeOptions(self, optionValue):
+        """
+        This function will tokenize the string stored in database
+        e.g. database store the value as below
+        key1=value1, key2=value2, key3=value3, ....
+        This function will extract key and value from above string
+        """
+        if optionValue is not None:
+            OptionStr = optionValue.split(',')
+            fdwoptions = []
+            for fdwoption in OptionStr:
+                k, v = fdwoption.split('=', 1)
+                fdwoptions.append({'fdwoption' : k,'fdwvalue' : v})
+            return fdwoptions
+
+    @check_precondition
+    def properties(self, gid, sid, did, fid):
+        SQL = render_template("/".join([self.template_path, 'properties.sql']), fid=fid,conn=self.conn)
+        status, res = self.conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        if res['rows'][0]['fdwoptions'] is not None:
+            res['rows'][0]['fdwoptions'] = self.tokenizeOptions(res['rows'][0]['fdwoptions'])
+
+        SQL = render_template("/".join([self.template_path, 'acl.sql']), fid=fid)
+        status, fdwaclres = self.conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=fdwaclres)
+
+        for row in fdwaclres['rows']:
+            priv = parse_priv_from_db(row)
+            if row['deftype'] in res['rows'][0]:
+                res['rows'][0][row['deftype']].append(priv)
+            else:
+                res['rows'][0][row['deftype']] = [priv]
+
+        return ajax_response(
+                response=res['rows'][0],
+                status=200
+                )
+
+    @check_precondition
+    def create(self, gid, sid, did):
+        """
+        This function will creates new the foreign data wrapper object
+        """
+
+        required_args = [
+            'name'
+        ]
+
+        data = request.form if request.form else json.loads(request.data.decode())
+        for arg in required_args:
+            if arg not in data:
+                return make_json_response(
+                    status=410,
+                    success=0,
+                    errormsg=gettext(
+                        "Couldn't find the required parameter (%s)." % arg
+                    )
+                )
+
+        try:
+
+            if 'fdwacl' in data:
+                data['fdwacl'] = parse_priv_to_db(data['fdwacl'])
+
+            SQL = render_template("/".join([self.template_path, 'create.sql']), data=data,conn=self.conn)
+            status, res = self.conn.execute_dict(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            SQL = render_template("/".join([self.template_path, 'properties.sql']), fname=data['name'],conn=self.conn)
+
+            status, rset = self.conn.execute_dict(SQL)
+            if not status:
+                return internal_server_error(errormsg=rset)
+
+            for row in rset['rows']:
+                return jsonify(
+                node= self.blueprint.generate_browser_node(
+                    row['fdwoid'],
+                    did,
+                    row['name'],
+                    icon='icon-foreign_data_wrapper'
+                )
+            )
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def update(self, gid, sid, did, fid):
+        """
+        This function will update foreign data wrapper object
+        """
+        data = request.form if request.form else json.loads(request.data.decode())
+        SQL = self.getSQL(gid, sid, data, did, fid)
+        try:
+            if SQL and SQL.strip('\n') and SQL.strip(' '):
+                status, res = self.conn.execute_scalar(SQL)
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+                return make_json_response(
+                    success=1,
+                    info="Foreign Data Wrapper updated",
+                    data={
+                        'id':  fid,
+                        'did': did,
+                        'sid': sid,
+                        'gid': gid
+                    }
+                )
+            else:
+                return make_json_response(
+                    success=1,
+                    info="Nothing to update",
+                    data={
+                        'id':  fid,
+                        'did': did,
+                        'sid': sid,
+                        'gid': gid
+                    }
+                )
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def delete(self, gid, sid, did, fid):
+        """
+        This function will drop the foreign data wrapper object
+        """
+        if self.cmd == 'delete':
+            # This is a cascade operation
+            cascade = True
+        else:
+            cascade = False
+
+        try:
+            # Get name of foreign data wrapper from fid
+            SQL = render_template("/".join([self.template_path, 'delete.sql']), fid=fid, conn=self.conn)
+            status, name = self.conn.execute_scalar(SQL)
+            if not status:
+                return internal_server_error(errormsg=name)
+            # drop foreign data wrapper node
+            SQL = render_template("/".join([self.template_path, 'delete.sql']), name=name, cascade=cascade, conn=self.conn)
+            status, res = self.conn.execute_scalar(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            return make_json_response(
+                success=1,
+                info=gettext("Foreign Data Wrapper dropped"),
+                data={
+                    'id': fid,
+                    'did': did,
+                    'sid': sid,
+                    'gid': gid,
+                }
+            )
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def msql(self, gid, sid, did, fid=None):
+        """
+        This function to return modified SQL
+        """
+        data = {}
+        for k, v in request.args.items():
+            try:
+                data[k] = json.loads(v)
+            except ValueError:
+                data[k] = v
+
+        SQL = self.getSQL(gid, sid, data, did, fid)
+        if SQL and SQL.strip('\n') and SQL.strip(' '):
+            return make_json_response(data=SQL,status=200)
+        else:
+            return make_json_response(data='-- Modified SQL --',status=200)
+
+    def getSQL(self, gid, sid, data, did, fid=None):
+        """
+        This function will generate sql from model data
+        """
+        required_args = [
+            'name'
+        ]
+        try:
+            if fid is not None:
+                SQL = render_template("/".join([self.template_path, 'properties.sql']), fid=fid, conn=self.conn)
+                status, res = self.conn.execute_dict(SQL)
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+                if res['rows'][0]['fdwoptions'] is not None:
+                    res['rows'][0]['fdwoptions'] = self.tokenizeOptions(res['rows'][0]['fdwoptions'])
+
+                for key in ['fdwacl']:
+                    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'])
+                        if 'changed' in data[key]:
+                          data[key]['changed'] = parse_priv_to_db(data[key]['changed'])
+                        if 'deleted' in data[key]:
+                          data[key]['deleted'] = parse_priv_to_db(data[key]['deleted'])
+
+                old_data = res['rows'][0]
+                for arg in required_args:
+                    if arg not in data:
+                        data[arg] = old_data[arg]
+                SQL = render_template("/".join([self.template_path, 'update.sql']), data=data, o_data=old_data,conn=self.conn)
+            else:
+                for key in ['fdwacl']:
+                    if key in data and data[key] is not None:
+                        data[key] = parse_priv_to_db(data[key])
+
+                SQL = render_template("/".join([self.template_path, 'create.sql']), data=data,conn=self.conn)
+                SQL += "\n"
+            return SQL
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def sql(self, gid, sid, did, fid):
+        """
+        This function will generate sql for sql panel
+        """
+        SQL = render_template("/".join([self.template_path, 'properties.sql']), fid=fid, conn=self.conn)
+        status, res = self.conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        if res['rows'][0]['fdwoptions'] is not None:
+            res['rows'][0]['fdwoptions'] = self.tokenizeOptions(res['rows'][0]['fdwoptions'])
+
+        SQL = render_template("/".join([self.template_path, 'acl.sql']), fid=fid)
+        status, fdwaclres = self.conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=fdwaclres)
+
+        for row in fdwaclres['rows']:
+            priv = parse_priv_from_db(row)
+            if row['deftype'] in res['rows'][0]:
+                res['rows'][0][row['deftype']].append(priv)
+            else:
+                res['rows'][0][row['deftype']] = [priv]
+
+        # To format privileges
+        if 'fdwacl' in res['rows'][0]:
+            res['rows'][0]['fdwacl'] = parse_priv_to_db(res['rows'][0]['fdwacl'])
+
+        SQL = ''
+        SQL = render_template("/".join([self.template_path, 'create.sql']), data=res['rows'][0], conn=self.conn)
+        SQL += "\n"
+
+        sql_header = """
+-- Foreign Data Wrapper: {0}
+
+-- DROP Foreign Data Wrapper {0}
+
+""".format(res['rows'][0]['name'])
+
+        SQL = sql_header + SQL
+
+        return ajax_response(response=SQL)
+
+    @check_precondition
+    def getvalidators(self, gid, sid, did):
+       res = [{ 'label': '', 'value': '' }]
+       try:
+           SQL = render_template("/".join([self.template_path, 'validators.sql']),conn=self.conn)
+           status, rset = self.conn.execute_2darray(SQL)
+
+           if not status:
+               return internal_server_error(errormsg=rset)
+
+           for row in rset['rows']:
+               res.append(
+                           {'label': row['fdwvalue'], 'value': row['fdwvalue']}
+                       )
+           return make_json_response(
+                   data=res,
+                   status=200
+                   )
+
+       except Exception as e:
+           return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def gethandlers(self, gid, sid, did):
+       res = [{'label': '', 'value': ''}]
+       try:
+           SQL = render_template("/".join([self.template_path, 'handlers.sql']),conn=self.conn)
+           status, rset = self.conn.execute_2darray(SQL)
+
+           if not status:
+               return internal_server_error(errormsg=rset)
+
+           for row in rset['rows']:
+               res.append(
+                           {'label': row['fdwhan'], 'value': row['fdwhan']}
+                       )
+           return make_json_response(
+                   data=res,
+                   status=200
+                   )
+
+       except Exception as e:
+           return internal_server_error(errormsg=str(e))
+
+ForeignDataWrapperView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/__init__.py
new file mode 100644
index 0000000..769492a
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/__init__.py
@@ -0,0 +1,455 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+import json
+from flask import render_template, make_response, request, jsonify
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_json_response, \
+    make_response as ajax_response, internal_server_error
+from pgadmin.browser.utils import NodeView
+from pgadmin.browser.collection import CollectionNodeModule
+import pgadmin.browser.server_groups.servers as servers
+from pgadmin.utils.ajax import precondition_required
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from pgadmin.browser.server_groups.servers.utils import parse_priv_from_db, \
+    parse_priv_to_db
+from functools import wraps
+
+class ForeignServerModule(CollectionNodeModule):
+    NODE_TYPE = 'foreign_server'
+    COLLECTION_LABEL = gettext("Foreign Servers")
+
+    def __init__(self, *args, **kwargs):
+        self.min_ver = None
+        self.max_ver = None
+
+        super(ForeignServerModule, self).__init__(*args, **kwargs)
+
+    def get_nodes(self, gid, sid, did, fid):
+        """
+        Generate the collection node
+        """
+        yield self.generate_browser_collection_node(fid)
+
+    @property
+    def script_load(self):
+        """
+        Load the module script for foreign server, when any of the foreign data wrapper node is
+        initialized.
+        """
+        return servers.ServerModule.NODE_TYPE
+
+blueprint = ForeignServerModule(__name__)
+
+
+class ForeignServerView(NodeView):
+    node_type = blueprint.node_type
+
+    parent_ids = [
+            {'type': 'int', 'id': 'gid'},
+            {'type': 'int', 'id': 'sid'},
+            {'type': 'int', 'id': 'did'},
+            {'type': 'int', 'id': 'fid'}
+            ]
+    ids = [
+            {'type': 'int', 'id': 'fsid'}
+            ]
+
+    operations = dict({
+        'obj': [
+            {'get': 'properties', 'delete': 'delete', 'put': 'update'},
+            {'get': 'list', 'post': 'create'}
+        ],
+        'delete': [{
+           'delete': 'delete'
+        }],
+        'nodes': [{'get': 'node'}, {'get': 'nodes'}],
+        'children': [{'get': 'children'}],
+        'sql': [{'get': 'sql'}],
+        'msql': [{'get': 'msql'}, {'get': 'msql'}],
+        'stats': [{'get': 'statistics'}],
+        'dependency': [{'get': 'dependencies'}],
+        'dependent': [{'get': 'dependents'}],
+        'module.js': [{}, {}, {'get': 'module_js'}]
+    })
+
+    def module_js(self):
+        """
+        This property defines (if javascript) exists for this node.
+        Override this property for your own logic.
+        """
+        return make_response(
+                render_template(
+                    "foreign_servers/js/foreign_servers.js",
+                    _=gettext
+                    ),
+                200, {'Content-Type': 'application/x-javascript'}
+                )
+
+    def check_precondition(f):
+        """
+        This function will behave as a decorator which will checks
+        database connection before running view, it will also attaches
+        manager,conn & template_path properties to self
+        """
+        @wraps(f)
+        def wrap(*args, **kwargs):
+            # Here args[0] will hold self & kwargs will hold gid,sid,did
+            self = args[0]
+            self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(kwargs['sid'])
+            self.conn = self.manager.connection(did=kwargs['did'])
+
+            # If DB not connected then return error to browser
+            if not self.conn.connected():
+                return precondition_required(
+                    gettext(
+                            "Connection to the server has been lost!"
+                    )
+                )
+
+            self.template_path = 'foreign_servers/sql'
+
+            return f(*args, **kwargs)
+        return wrap
+
+    @check_precondition
+    def list(self, gid, sid, did, fid):
+        SQL = render_template("/".join([self.template_path, 'properties.sql']), fid=fid,conn=self.conn)
+        status, res = self.conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        return ajax_response(
+                response=res['rows'],
+                status=200
+                )
+
+    @check_precondition
+    def nodes(self, gid, sid, did, fid):
+        res = []
+        SQL = render_template("/".join([self.template_path, 'properties.sql']), fid=fid,conn=self.conn)
+        status, rset = self.conn.execute_2darray(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        for row in rset['rows']:
+            res.append(
+                    self.blueprint.generate_browser_node(
+                        row['fsrvid'],
+                        fid,
+                        row['name'],
+                        icon="icon-foreign_server"
+                    ))
+
+        return make_json_response(
+                data=res,
+                status=200
+                )
+
+    def tokenizeOptions(self, optionValue):
+        """
+        This function will tokenize the string stored in database
+        e.g. database store the value as below
+        key1=value1, key2=value2, key3=value3, ....
+        This function will extract key and value from above string
+        """
+        if optionValue is not None:
+            OptionStr = optionValue.split(',')
+            fsrvoptions = []
+            for fsrvoption in OptionStr:
+                k, v = fsrvoption.split('=', 1)
+                fsrvoptions.append({'fsrvoption' : k,'fsrvvalue' : v})
+            return fsrvoptions
+
+    @check_precondition
+    def properties(self, gid, sid, did, fid, fsid):
+        SQL = render_template("/".join([self.template_path, 'properties.sql']), fsid=fsid,conn=self.conn)
+        status, res = self.conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        if res['rows'][0]['fsrvoptions'] is not None:
+            res['rows'][0]['fsrvoptions'] = self.tokenizeOptions(res['rows'][0]['fsrvoptions'])
+
+        SQL = render_template("/".join([self.template_path, 'acl.sql']), fsid=fsid)
+        status, fsrvaclres = self.conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=fsrvaclres)
+
+        for row in fsrvaclres['rows']:
+            priv = parse_priv_from_db(row)
+            if row['deftype'] in res['rows'][0]:
+                res['rows'][0][row['deftype']].append(priv)
+            else:
+                res['rows'][0][row['deftype']] = [priv]
+
+        return ajax_response(
+                response=res['rows'][0],
+                status=200
+                )
+
+    @check_precondition
+    def create(self, gid, sid, did, fid):
+        """
+        This function will creates new foreign server object
+        """
+
+        required_args = [
+            'name'
+        ]
+
+        data = request.form if request.form else json.loads(request.data.decode())
+        for arg in required_args:
+            if arg not in data:
+                return make_json_response(
+                    status=410,
+                    success=0,
+                    errormsg=gettext(
+                        "Couldn't find the required parameter (%s)." % arg
+                    )
+                )
+        try:
+            if 'fsrvacl' in data:
+                data['fsrvacl'] = parse_priv_to_db(data['fsrvacl'])
+
+            SQL = render_template("/".join([self.template_path, 'properties.sql']), fdwid=fid,conn=self.conn)
+            status, res1 = self.conn.execute_dict(SQL)
+            if not status:
+                return internal_server_error(errormsg=res1)
+
+            fdwdata = res1['rows'][0]
+
+            SQL = render_template("/".join([self.template_path, 'create.sql']), data=data, fdwdata=fdwdata,conn=self.conn)
+            status, res = self.conn.execute_scalar(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            SQL = render_template("/".join([self.template_path, 'properties.sql']), data=data, fdwdata=fdwdata,conn=self.conn)
+            status, rset = self.conn.execute_dict(SQL)
+            if not status:
+                return internal_server_error(errormsg=rset)
+
+            return jsonify(
+                node=self.blueprint.generate_browser_node(
+                    rset['rows'][0]['fsrvid'],
+                    fid,
+                    rset['rows'][0]['name'],
+                    icon="icon-foreign_server"
+                )
+            )
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def update(self, gid, sid, did, fid, fsid):
+        """
+        This function will update foreign server object
+        """
+        data = request.form if request.form else json.loads(request.data.decode())
+        SQL = self.getSQL(gid, sid, data, did, fid, fsid)
+        try:
+            if SQL and SQL.strip('\n') and SQL.strip(' '):
+                status, res = self.conn.execute_scalar(SQL)
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+                return make_json_response(
+                    success=1,
+                    info="Foreign server updated",
+                    data={
+                        'id':  fsid,
+                        'fid': fid,
+                        'did': did,
+                        'sid': sid,
+                        'gid': gid
+                    }
+                )
+            else:
+                return make_json_response(
+                    success=1,
+                    info="Nothing to update",
+                    data={
+                        'id':  fsid,
+                        'fid': fid,
+                        'did': did,
+                        'sid': sid,
+                        'gid': gid
+                    }
+                )
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def delete(self, gid, sid, did, fid, fsid):
+        """
+        This function will drop the foreign server object
+        """
+        if self.cmd == 'delete':
+            # This is a cascade operation
+            cascade = True
+        else:
+            cascade = False
+
+        try:
+            # Get name of foreign data wrapper from fid
+            SQL = render_template("/".join([self.template_path, 'delete.sql']), fsid=fsid,conn=self.conn)
+            status, name = self.conn.execute_scalar(SQL)
+            if not status:
+                return internal_server_error(errormsg=name)
+            # drop foreign server
+            SQL = render_template("/".join([self.template_path, 'delete.sql']), name=name, cascade=cascade,conn=self.conn)
+            status, res = self.conn.execute_scalar(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            return make_json_response(
+                success=1,
+                info=gettext("Foreign Server dropped"),
+                data={
+                    'id': fsid,
+                    'fid': fid,
+                    'did': did,
+                    'sid': sid,
+                    'gid': gid,
+                }
+            )
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def msql(self, gid, sid, did, fid, fsid=None):
+        """
+        This function to return modified SQL
+        """
+        data = {}
+        for k, v in request.args.items():
+            try:
+                data[k] = json.loads(v)
+            except ValueError:
+                data[k] = v
+
+        SQL = self.getSQL(gid, sid, data, did, fid, fsid)
+        if SQL and SQL.strip('\n') and SQL.strip(' '):
+            return make_json_response(
+                    data=SQL,
+                    status=200
+                    )
+        else:
+            return make_json_response(
+                    data='-- Modified SQL --',
+                    status=200
+                    )
+
+    def getSQL(self, gid, sid, data, did, fid, fsid=None):
+        """
+        This function will generate sql from model data
+        """
+        required_args = [
+            'name'
+        ]
+        try:
+            if fsid is not None:
+                SQL = render_template("/".join([self.template_path, 'properties.sql']), fsid=fsid,conn=self.conn)
+                status, res = self.conn.execute_dict(SQL)
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+                if res['rows'][0]['fsrvoptions'] is not None:
+                    res['rows'][0]['fsrvoptions'] = self.tokenizeOptions(res['rows'][0]['fsrvoptions'])
+
+                for key in ['fsrvacl']:
+                    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'])
+                        if 'changed' in data[key]:
+                          data[key]['changed'] = parse_priv_to_db(data[key]['changed'])
+                        if 'deleted' in data[key]:
+                          data[key]['deleted'] = parse_priv_to_db(data[key]['deleted'])
+
+                old_data = res['rows'][0]
+                for arg in required_args:
+                    if arg not in data:
+                        data[arg] = old_data[arg]
+                SQL = render_template("/".join([self.template_path, 'update.sql']), data=data, o_data=old_data, conn=self.conn)
+            else:
+                SQL = render_template("/".join([self.template_path, 'properties.sql']), fdwid=fid, conn=self.conn)
+                status, res = self.conn.execute_dict(SQL)
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+                fdwdata = res['rows'][0]
+
+                for key in ['fsrvacl']:
+                    if key in data and data[key] is not None:
+                        data[key] = parse_priv_to_db(data[key])
+
+                SQL = render_template("/".join([self.template_path, 'create.sql']), data=data, fdwdata=fdwdata, conn=self.conn)
+                SQL += "\n"
+            return SQL
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def sql(self, gid, sid, did, fid, fsid):
+        """
+        This function will generate sql for sql panel
+        """
+        SQL = render_template("/".join([self.template_path, 'properties.sql']), fsid=fsid, conn=self.conn)
+        status, res = self.conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        if res['rows'][0]['fsrvoptions'] is not None:
+            res['rows'][0]['fsrvoptions'] = self.tokenizeOptions(res['rows'][0]['fsrvoptions'])
+
+        SQL = render_template("/".join([self.template_path, 'acl.sql']), fsid=fsid)
+        status, fsrvaclres = self.conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=fsrvaclres)
+
+        for row in fsrvaclres['rows']:
+            priv = parse_priv_from_db(row)
+            if row['deftype'] in res['rows'][0]:
+                res['rows'][0][row['deftype']].append(priv)
+            else:
+                res['rows'][0][row['deftype']] = [priv]
+
+        # To format privileges
+        if 'fsrvacl' in res['rows'][0]:
+            res['rows'][0]['fsrvacl'] = parse_priv_to_db(res['rows'][0]['fsrvacl'])
+
+        SQL = render_template("/".join([self.template_path, 'properties.sql']), fdwid=fid, conn=self.conn)
+        status, res1 = self.conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res1)
+
+        fdwdata = res1['rows'][0]
+
+        SQL = ''
+        SQL = render_template("/".join([self.template_path, 'create.sql']), data=res['rows'][0], fdwdata=fdwdata, conn=self.conn)
+        SQL += "\n"
+
+        sql_header = """
+-- Foreign Server: {0}
+
+-- DROP Foreign Server {0}
+
+""".format(res['rows'][0]['name'])
+
+        SQL = sql_header + SQL
+
+        return ajax_response(response=SQL)
+
+ForeignServerView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/static/img/coll-foreign_server.png b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/static/img/coll-foreign_server.png
new file mode 100644
index 0000000000000000000000000000000000000000..c1071768f4d2ed9797585b8725bfce1d3dbfafab
GIT binary patch
literal 476
zcmV<20VDp2P)<h;3K|Lk000e1NJLTq000mG000mO0{{R3C@l|D00001b5ch_0Itp)
z=>Px#u~1A@MSGN_gKT`3fsg9n=i=4g*~!(=z01b2z`v%siE@IOhLgCcv+3XE*vZt-
zy~@9+xSfQS+tJwS-{#`g-Os+tu$QUO#mn;X^7;7r_3`rh`TFtg@8sLx@b2&S_4eS?
z*Tk!}T%@*)tGMs(?)UZf#H+MhptB#^$78X<n0%FIg_}>8t@QEo;ndi}s<T_Aw(sum
z;MCY#qO|q#^ZEJt=I`;(+u-5U*~6=}TB5Y}_4R<H6+8d{00DGTPE!Ct=GbNc0004E
zOGiWihy@);00009a7bBm000XU000XU0RWnu7ytkO2XskIMF-mh9Tf%_o|^Ck0001&
zNkl<Z7}G_L%MyYx3`MD;AmH2D>!aedF2ZD=|Nk^wNyodpXOfdtRf^G6C5vUfS~u;c
z^sXQ3?XKA$ItAu<I&UvcYs-$gI^1x7JpaJI#&>Utf+&cek(eY9^AAkZ!~tFaSanc<
zFu{AMgHmU4B5npOxx+;-vxA42rQ)LK!~moV1V?2hJ2;zWU_<UnqkkAf#F{S@02HNy
S0*^ZY0000<MNUMnLSTZ=!0wp<

literal 0
HcmV?d00001

diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/static/img/foreign_server.png b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/static/img/foreign_server.png
new file mode 100644
index 0000000000000000000000000000000000000000..dda392a2280c994f48d89e3ed67bbfcbe14ba045
GIT binary patch
literal 518
zcmV+h0{Q)kP)<h;3K|Lk000e1NJLTq000mG000mO0{{R3C@l|D00001b5ch_0Itp)
z=>Px#&rnQMMX->YgKT`3fsg9n=i=4g*~!(=z01b2z`v%siE@IOhLgCcv+3XE*vZt-
zy~@9+xSfQS+tJwS-{#`g-Os+tu$QUO#mn;X^7;7r_3`rh`TFtg@8sLx@b2&S_4eS?
z*Tk!}w3VU6t+wy(?)UZf#H+Njke<V>wf6P(;ndi~uDI{+@58FIvXGs`t+&U<$Kcf1
zqKuTamZPDLmd(t}k#&HymZ8I~wydJ3@b2)su(W%Wq)V8njH|fe)Y+kolv<*+AKJ%K
zq_by(l3S&=Pb(w<00001bW%=J06^y0W&i*H0b)x>L;#2d9Y_EG010qNS#tmY3ljhU
z3ljkVnw%H_000McNliru+XEdH1{&ZijJW^+0HaAnK~xyiMUK4=!Y~Yk8z{7t76^Ya
z5NM?^ATeagkRWBT{Ql2Gs<?35eLkOE6iFhh_-QsT7t5+%O}<&TWw)ueyM}>~`$KoE
z>eI9&&I&GYz3D%=Kl<V0iK1j+o;@MiNQj(xVT7Ooz-REL2o4cK08S8CM5mlo8K6yb
z3CaY`pf#z~X=O6-*a9G?Vih?d$IjF$`K*s}^l14HA&8Lm1+R%2nMf@_3jhEB07*qo
IM6N<$g0IE{NB{r;

literal 0
HcmV?d00001

diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/templates/foreign_servers/js/foreign_servers.js b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/templates/foreign_servers/js/foreign_servers.js
new file mode 100644
index 0000000..448b784
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/templates/foreign_servers/js/foreign_servers.js
@@ -0,0 +1,136 @@
+define(
+        ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', 'alertify', 'pgadmin.browser.collection',
+        'pgadmin.browser.server.privilege'],
+function($, _, S, pgAdmin, pgBrowser, alertify) {
+
+    var OptionsModel = pgAdmin.Browser.Node.Model.extend({
+        defaults: {
+          fsrvoption: undefined,
+          fsrvvalue: undefined
+        },
+        schema: [
+          {id: 'fsrvoption', label:'Options', type:'text', group: null, editable: true},
+          {id: 'fsrvvalue', label:'Value', type: 'text', group:null, extraHeaderClasses: 'cellwidth-40', editable: true},
+        ],
+        validate: function() {
+          if (_.isUndefined(this.get('fsrvvalue')) ||
+            _.isNull(this.get('fsrvvalue')) ||
+            String(this.get('fsrvvalue')).replace(/^\s+|\s+$/g, '') == '') {
+            var msg = 'Please enter some value!';
+            this.errorModel.set('fsrvvalue', msg);
+            return msg;
+          } else {
+            this.errorModel.unset('fsrvvalue');
+          }
+          return null;
+        }
+    });
+
+  if (!pgBrowser.Nodes['coll-foreign_server']) {
+    var foreign_data_wrappers = pgAdmin.Browser.Nodes['coll-foreign_server'] =
+      pgAdmin.Browser.Collection.extend({
+        node: 'foreign_server',
+        label: '{{ _('Foreign Servers') }}',
+        type: 'coll-foreign_server',
+        columns: ['name','fsrvoid','fsrvowner','description']
+      });
+  };
+
+  if (!pgBrowser.Nodes['foreign_server']) {
+    pgAdmin.Browser.Nodes['foreign_server'] = pgAdmin.Browser.Node.extend({
+      parent_type: 'foreign_data_wrapper',
+      type: 'foreign_server',
+      label: '{{ _('Foreign Server') }}',
+      hasSQL:  true,
+      canDrop: true,
+      canDropCascade: true,
+      Init: function() {
+        /* Avoid mulitple registration of menus */
+        if (this.initialized)
+            return;
+
+        this.initialized = true;
+
+        pgBrowser.add_menus([{
+          name: 'create_foreign_server_on_coll', node: 'coll-foreign_server', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: '{{ _('Foreign Server...') }}',
+          icon: 'wcTabIcon pg-icon-foreign_server', data: {action: 'create'}
+        },{
+          name: 'create_foreign_server', node: 'foreign_server', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: '{{ _('Foreign Server...') }}',
+          icon: 'wcTabIcon pg-icon-foreign_server', data: {action: 'create'}
+        }
+        ]);
+      },
+      model: pgAdmin.Browser.Node.Model.extend({
+        defaults: {
+          name: undefined,
+          fsrvtype: undefined,
+          fsrvversion: undefined,
+          fsrvvalue: undefined,
+          fsrvoptions: [],
+          fsrvowner: undefined,
+          description: undefined,
+          fsrvacl: []
+        },
+        schema: [{
+          id: 'name', label: '{{ _('Name') }}', cell: 'string',
+          type: 'text', disabled: function(m) {
+            if (this.mode == 'edit' && this.node_info.server.version < 90200) {
+              return true;
+            }
+            else
+              return false;
+          }
+        },{
+          id: 'fsrvid', label:'{{ _('Oid') }}', cell: 'string',
+          type: 'text', disabled: true
+        },{
+          id: 'fsrvowner', label:'{{ _('Owner') }}', type: 'text',
+          control: Backform.NodeListByNameControl, node: 'role',
+          mode: ['edit', 'create', 'properties']
+        },{
+          id: 'fsrvtype', label:'{{ _('Type') }}', cell: 'string',
+          group: 'Definition', type: 'text', mode: ['edit','create','properties'], disabled: function(m) {
+            return !m.isNew();
+          }
+        },{
+          id: 'fsrvversion', label:'{{ _('Version') }}', cell: 'string',
+          group: 'Definition', type: 'text'
+        },{
+          id: 'description', label:'{{ _('Comment') }}', cell: 'string',
+          type: 'multiline'
+        },{
+          id: 'fsrvoptions', label: 'Options', type: 'collection', group: "Options",
+          model: OptionsModel, control: 'unique-col-collection', mode: ['edit', 'create', 'properties'],
+          canAdd: true, canDelete: true, uniqueCol : ['fsrvoption'],
+          columns: ['fsrvoption','fsrvvalue']
+         },{
+            id: 'fsrvacl', label: 'Privileges', type: 'collection', group: "Privileges",
+            model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend({privileges: ['U']}), control: 'unique-col-collection',
+            mode: ['properties', 'edit', 'create'], canAdd: true, canDelete: true, uniqueCol : ['grantee'],
+            columns: ['grantee', 'grantor', 'privileges']
+         }
+        ],
+        validate: function() {
+          var name = this.get('name');
+
+          if (_.isUndefined(name) || _.isNull(name) ||
+            String(name).replace(/^\s+|\s+$/g, '') == '') {
+            var msg = '{{ _('Name can not be empty!') }}';
+            this.errorModel.set('name', msg);
+            return msg;
+          } else {
+            this.errorModel.unset('name');
+          }
+          return null;
+        }
+      })
+  });
+
+  }
+
+  return pgBrowser.Nodes['coll-foreign_server'];
+});
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/templates/foreign_servers/sql/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/templates/foreign_servers/sql/acl.sql
new file mode 100644
index 0000000..84c6dc5
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/templates/foreign_servers/sql/acl.sql
@@ -0,0 +1,21 @@
+SELECT 'fsrvacl' as deftype, COALESCE(gt.rolname, 'public') grantee, g.rolname grantor, array_agg(privilege_type) as privileges, array_agg(is_grantable) as grantable
+FROM
+	(SELECT
+		d.grantee, d.grantor, d.is_grantable,
+		CASE d.privilege_type
+		WHEN 'USAGE' THEN 'U'
+		ELSE 'UNKNOWN'
+		END AS privilege_type
+	FROM
+		(SELECT srvacl FROM pg_foreign_server db
+		    LEFT OUTER JOIN pg_shdescription descr ON (
+			db.oid=descr.objoid AND descr.classoid='pg_foreign_server'::regclass)
+{% if fsid %}
+  WHERE db.oid = {{ fsid|qtLiteral }}::OID
+{% endif %}
+		) acl,
+		aclexplode(srvacl) d
+		) d
+	LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid)
+	LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid)
+GROUP BY g.rolname, gt.rolname
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/templates/foreign_servers/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/templates/foreign_servers/sql/create.sql
new file mode 100644
index 0000000..cffa47b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/templates/foreign_servers/sql/create.sql
@@ -0,0 +1,42 @@
+{# ============= Create foreign server ============= #}
+{% import 'macros/privilege.macros' as PRIVILEGE %}
+{% if data %}
+CREATE SERVER {{ conn|qtIdent(data.name) }}{% if data.fsrvtype %}
+
+    TYPE {{ data.fsrvtype|qtLiteral }}{% endif %}{% if data.fsrvversion %}
+
+    VERSION {{ data.fsrvversion|qtLiteral }}{%-endif %}{% if fdwdata %}
+
+    FOREIGN DATA WRAPPER {{ conn|qtIdent(fdwdata.name) }}{% endif %}{% if data.fsrvoptions %}
+
+{% set addAlter = "False" %}
+{% for variable in data.fsrvoptions %}
+{% if variable.fsrvoption and variable.fsrvoption != '' %}
+{% if addAlter == "False" %}
+    OPTIONS ({% set addAlter = "True" %}{% endif %}
+{{ conn|qtIdent(variable.fsrvoption) }} {{variable.fsrvvalue|qtLiteral}}{% if not loop.last %},{% else %}){% endif %}
+{% endif %}
+{% endfor %}
+{% endif %}{% if data %};{% endif %}
+
+
+{# ============= Set the owner for foreign server ============= #}
+{% if data.fsrvowner %}
+ALTER SERVER {{ conn|qtIdent(data.name) }}
+    OWNER TO {{ conn|qtIdent(data.fsrvowner) }};
+{% endif %}
+
+{# ============= Set the comment for foreign server ============= #}
+{% if data.description %}
+COMMENT ON SERVER {{ conn|qtIdent(data.name) }}
+    IS {{ data.description|qtLiteral }};
+{% endif %}
+
+{# ============= Set the ACL for foreign server ============= #}
+{% if data.fsrvacl %}
+{% for priv in data.fsrvacl %}
+{{ PRIVILEGE.APPLY(conn, 'FOREIGN SERVER', priv.grantee, data.name, priv.without_grant, priv.with_grant) }}
+{% endfor %}
+{% endif %}
+
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/templates/foreign_servers/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/templates/foreign_servers/sql/delete.sql
new file mode 100644
index 0000000..ce89201
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/templates/foreign_servers/sql/delete.sql
@@ -0,0 +1,9 @@
+{# ============= Give foreign server name from foreign server id ============= #}
+{% if fsid %}
+SELECT srvname as name FROM pg_foreign_server srv LEFT OUTER JOIN pg_foreign_data_wrapper fdw on fdw.oid=srvfdw
+WHERE srv.oid={{fsid}}::int;
+{% endif %}
+{# ============= Delete/Drop cascade foreign server ============= #}
+{% if name %}
+DROP SERVER {{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %};
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/templates/foreign_servers/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/templates/foreign_servers/sql/properties.sql
new file mode 100644
index 0000000..2033ef2
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/templates/foreign_servers/sql/properties.sql
@@ -0,0 +1,26 @@
+{# ============= Give all the properties of foreign server ============= #}
+{% if fdwid %}
+SELECT fdw.oid as fdwoid, fdwname as name
+FROM pg_foreign_data_wrapper fdw
+LEFT OUTER JOIN pg_description des ON (des.objoid=fdw.oid AND des.objsubid=0 AND des.classoid='pg_foreign_data_wrapper'::regclass)
+WHERE fdw.oid={{fdwid}}::int
+{% else %}
+SELECT srv.oid as fsrvid, srvname as name, srvtype as fsrvtype, srvversion as fsrvversion, fdw.fdwname as fdwname, description,
+array_to_string(srvoptions, ',') AS fsrvoptions,
+pg_get_userbyid(srvowner) as fsrvowner
+FROM pg_foreign_server srv
+LEFT OUTER JOIN pg_foreign_data_wrapper fdw on fdw.oid=srvfdw
+LEFT OUTER JOIN pg_description des ON (des.objoid=srv.oid AND des.objsubid=0 AND des.classoid='pg_foreign_server'::regclass)
+{% if data and fdwdata %}
+WHERE fdw.fdwname = {{ fdwdata.name|qtLiteral }}::text and srvname = {{ data.name|qtLiteral }}::text
+{% elif fdwdata %}
+WHERE fdw.fdwname = {{fdwdata.name|qtLiteral}}::text
+{% endif %}
+{% if fid %}
+WHERE srvfdw={{fid}}::int
+{% endif %}
+{% if fsid %}
+WHERE srv.oid={{fsid}}::int
+{% endif %}
+ORDER BY srvname;
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/templates/foreign_servers/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/templates/foreign_servers/sql/update.sql
new file mode 100644
index 0000000..319b0a4
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/templates/foreign_servers/sql/update.sql
@@ -0,0 +1,82 @@
+{% import 'macros/privilege.macros' as PRIVILEGE %}
+{% if data %}
+{# ============= Update foreign server name ============= #}
+{% if data.name != o_data.name %}
+ALTER SERVER {{ conn|qtIdent(o_data.name) }}
+    RENAME TO {{ conn|qtIdent(data.name) }};
+
+{% endif %}
+{# ============= Update foreign server owner ============= #}
+{% if data.fsrvowner and data.fsrvowner != o_data.fsrvowner %}
+ALTER SERVER {{ conn|qtIdent(data.name) }}
+    OWNER TO {{ conn|qtIdent(data.fsrvowner) }};
+
+{% endif %}
+{# ============= Update foreign server version ============= #}
+{% if data.fsrvversion and data.fsrvversion != o_data.fsrvversion %}
+ALTER SERVER {{ conn|qtIdent(data.name) }}
+    VERSION {{ data.fsrvversion|qtLiteral }};
+
+{% endif %}
+{# ============= Update foreign server comments ============= #}
+{% if data.description and data.description != o_data.description %}
+COMMENT ON SERVER {{ conn|qtIdent(data.name) }}
+    IS {{ data.description|qtLiteral }};
+
+{% endif %}
+{# ============= Update foreign server options and values ============= #}
+{% if data.fsrvoptions and data.fsrvoptions.added %}
+{% set addAlter = "False" %}
+{% for variable in data.fsrvoptions.added %}
+{% if variable.fsrvoption and variable.fsrvoption != '' %}
+{% if addAlter == "False" %}
+ALTER SERVER {{ conn|qtIdent(data.name) }}
+    OPTIONS ({% set addAlter = "True" %}{%endif%}
+ADD {{ conn|qtIdent(variable.fsrvoption) }} {{variable.fsrvvalue|qtLiteral}}{% if not loop.last %},{% else %});{% endif %}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% if data.fsrvoptions and data.fsrvoptions.changed %}
+{% set addAlter = "False" %}
+{% for variable in data.fsrvoptions.changed %}
+{% if variable.fsrvoption and variable.fsrvoption != '' %}
+{% if addAlter == "False" %}
+ALTER SERVER {{ conn|qtIdent(data.name) }}
+    OPTIONS ({% set addAlter = "True" %}{%endif%}
+ADD {{conn|qtIdent(variable.fsrvoption)}} {{variable.fsrvvalue|qtLiteral}}{% if not loop.last %},{% else %});{% endif %}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% if data.fsrvoptions and data.fsrvoptions.deleted %}
+{% set addAlter = "False" %}
+{% for variable in data.fsrvoptions.deleted %}
+{% if variable.fsrvoption and variable.fsrvoption != '' %}
+{% if addAlter == "False" %}
+ALTER SERVER {{ conn|qtIdent(data.name) }}
+    OPTIONS ({% set addAlter = "True" %}{%endif%}
+DROP {{conn|qtIdent(variable.fsrvoption)}}{% if not loop.last %},{% else %});{% endif %}
+{% endif %}
+{% endfor %}
+{% endif %}
+
+{# Change the privileges #}
+{% if data.fsrvacl %}
+{% if 'deleted' in data.fsrvacl %}
+{% for priv in data.fsrvacl.deleted %}
+{{ PRIVILEGE.RESETALL(conn, 'FOREIGN SERVER', priv.grantee, data.name) }}
+{% endfor %}
+{% endif %}
+{% if 'changed' in data.fsrvacl %}
+{% for priv in data.fsrvacl.changed %}
+{{ PRIVILEGE.RESETALL(conn, 'FOREIGN SERVER', priv.grantee, data.name) }}
+{{ PRIVILEGE.APPLY(conn, 'FOREIGN SERVER', priv.grantee, data.name, priv.without_grant, priv.with_grant) }}
+{% endfor %}
+{% endif %}
+{% if 'added' in data.fsrvacl %}
+{% for priv in data.fsrvacl.added %}
+{{ PRIVILEGE.APPLY(conn, 'FOREIGN SERVER', priv.grantee, data.name, priv.without_grant, priv.with_grant) }}
+{% endfor %}
+{% endif %}
+{% endif %}
+
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/__init__.py
new file mode 100644
index 0000000..73ba720
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/__init__.py
@@ -0,0 +1,466 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2016, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+import json
+from flask import render_template, make_response, request, jsonify
+from flask.ext.babel import gettext
+from pgadmin.utils.ajax import make_json_response, \
+    make_response as ajax_response, internal_server_error
+from pgadmin.browser.utils import NodeView
+from pgadmin.browser.collection import CollectionNodeModule
+import pgadmin.browser.server_groups.servers as servers
+from pgadmin.utils.ajax import precondition_required
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from functools import wraps
+
+class UserMappingModule(CollectionNodeModule):
+    NODE_TYPE = 'user_mapping'
+    COLLECTION_LABEL = gettext("User Mappings")
+
+    def __init__(self, *args, **kwargs):
+        self.min_ver = None
+        self.max_ver = None
+
+        super(UserMappingModule, self).__init__(*args, **kwargs)
+
+    def get_nodes(self, gid, sid, did, fid, fsid):
+        """
+        Generate the collection node
+        """
+        yield self.generate_browser_collection_node(fsid)
+
+    @property
+    def node_inode(self):
+        """
+        node_inode
+
+        Override this property to make the node as leaf node.
+        """
+        return False
+
+    @property
+    def script_load(self):
+        """
+        Load the module script for user-mapping, when any of the foreign server node is
+        initialized.
+        """
+        return servers.ServerModule.NODE_TYPE
+
+blueprint = UserMappingModule(__name__)
+
+class UserMappingView(NodeView):
+    node_type = blueprint.node_type
+
+    parent_ids = [
+            {'type': 'int', 'id': 'gid'},
+            {'type': 'int', 'id': 'sid'},
+            {'type': 'int', 'id': 'did'},
+            {'type': 'int', 'id': 'fid'},
+            {'type': 'int', 'id': 'fsid'}
+            ]
+    ids = [
+            {'type': 'int', 'id': 'umid'}
+            ]
+
+    operations = dict({
+        'obj': [
+            {'get': 'properties', 'delete': 'delete', 'put': 'update'},
+            {'get': 'list', 'post': 'create'}
+        ],
+        'delete': [{
+           'delete': 'delete'
+        }],
+        'nodes': [{'get': 'node'}, {'get': 'nodes'}],
+        'children': [{'get': 'children'}],
+        'sql': [{'get': 'sql'}],
+        'msql': [{'get': 'msql'}, {'get': 'msql'}],
+        'stats': [{'get': 'statistics'}],
+        'dependency': [{'get': 'dependencies'}],
+        'dependent': [{'get': 'dependents'}],
+        'module.js': [{}, {}, {'get': 'module_js'}],
+        'getroles': [{}, {'get': 'getroles'}]
+    })
+
+    def module_js(self):
+        """
+        This property defines (if javascript) exists for this node.
+        Override this property for your own logic.
+        """
+        return make_response(
+                render_template(
+                    "user_mappings/js/user_mappings.js",
+                    _=gettext
+                    ),
+                200, {'Content-Type': 'application/x-javascript'}
+                )
+
+    def check_precondition(f):
+        """
+        This function will behave as a decorator which will checks
+        database connection before running view, it will also attaches
+        manager,conn & template_path properties to self
+        """
+        @wraps(f)
+        def wrap(*args, **kwargs):
+            # Here args[0] will hold self & kwargs will hold gid,sid,did
+            self = args[0]
+            self.manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(kwargs['sid'])
+            self.conn = self.manager.connection(did=kwargs['did'])
+
+            # If DB not connected then return error to browser
+            if not self.conn.connected():
+                return precondition_required(
+                    gettext(
+                            "Connection to the server has been lost!"
+                    )
+                )
+
+            self.template_path = 'user_mappings/sql'
+
+            return f(*args, **kwargs)
+        return wrap
+
+    @check_precondition
+    def list(self, gid, sid, did, fid, fsid):
+        SQL = render_template("/".join([self.template_path, 'properties.sql']), fsid=fsid, conn=self.conn)
+        status, res = self.conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        return ajax_response(
+                response=res['rows'],
+                status=200
+                )
+
+    @check_precondition
+    def nodes(self, gid, sid, did, fid, fsid):
+        res = []
+        SQL = render_template("/".join([self.template_path, 'properties.sql']), fsid=fsid, conn=self.conn)
+        status, rset = self.conn.execute_2darray(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=rset)
+
+        for row in rset['rows']:
+            res.append(
+                    self.blueprint.generate_browser_node(
+                        row['um_oid'],
+                        fsid,
+                        row['name'],
+                        icon="icon-user_mapping"
+                    ))
+
+        return make_json_response(
+                data=res,
+                status=200
+                )
+
+    def tokenizeOptions(self, optionValue):
+        """
+        This function will tokenize the string stored in database
+        e.g. database store the value as below
+        key1=value1, key2=value2, key3=value3, ....
+        This function will extract key and value from above string
+        """
+        if optionValue is not None:
+            OptionStr = optionValue.split(',')
+            umoptions = []
+            for umoption in OptionStr:
+                k, v = umoption.split('=', 1)
+                umoptions.append({'umoption' : k,'umvalue' : v})
+            return umoptions
+
+    @check_precondition
+    def properties(self, gid, sid, did, fid, fsid, umid):
+        SQL = render_template("/".join([self.template_path, 'properties.sql']), umid=umid, conn=self.conn)
+        status, res = self.conn.execute_dict(SQL)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        if res['rows'][0]['name'] == 'public':
+            res['rows'][0]['name'] = 'PUBLIC'
+
+        if res['rows'][0]['umoptions'] is not None:
+            res['rows'][0]['umoptions'] = self.tokenizeOptions(res['rows'][0]['umoptions'])
+
+        return ajax_response(
+                response=res['rows'][0],
+                status=200
+                )
+
+    @check_precondition
+    def create(self, gid, sid, did, fid, fsid):
+        """
+        This function will creates new user mapping object
+        """
+
+        required_args = [
+            'name'
+        ]
+
+        data = request.form if request.form else json.loads(request.data.decode())
+        for arg in required_args:
+            if arg not in data:
+                return make_json_response(
+                    status=410,
+                    success=0,
+                    errormsg=gettext(
+                        "Couldn't find the required parameter (%s)." % arg
+                    )
+                )
+
+        try:
+            SQL = render_template("/".join([self.template_path, 'properties.sql']), fserid=fsid, conn=self.conn)
+            status, res1 = self.conn.execute_dict(SQL)
+
+            if not status:
+                return internal_server_error(errormsg=res1)
+
+            fdwdata = res1['rows'][0]
+
+            SQL = render_template("/".join([self.template_path, 'create.sql']), data=data, fdwdata=fdwdata, conn=self.conn)
+            status, res = self.conn.execute_scalar(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            SQL = render_template("/".join([self.template_path, 'properties.sql']), fsid=fsid, data=data, conn=self.conn)
+            status, rset = self.conn.execute_dict(SQL)
+            if not status:
+                return internal_server_error(errormsg=rset)
+
+            for row in rset['rows']:
+                return jsonify(
+                node= self.blueprint.generate_browser_node(
+                    row['um_oid'],
+                    fsid,
+                    row['name'],
+                    icon='icon-user_mapping'
+                )
+            )
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def update(self, gid, sid, did, fid, fsid, umid):
+        """
+        This function will update user mapping object
+        """
+        data = request.form if request.form else json.loads(request.data.decode())
+        SQL = self.getSQL(gid, sid, data, did, fid, fsid, umid)
+        try:
+            if SQL and SQL.strip('\n') and SQL.strip(' '):
+                status, res = self.conn.execute_scalar(SQL)
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+                return make_json_response(
+                    success=1,
+                    info="User Mapping updated",
+                    data={
+                        'id':  umid,
+                        'fsid': fsid,
+                        'fid': fid,
+                        'did': did,
+                        'sid': sid,
+                        'gid': gid
+                    }
+                )
+            else:
+                return make_json_response(
+                    success=1,
+                    info="Nothing to update",
+                    data={
+                        'id':  umid,
+                        'fsid': fsid,
+                        'fid': fid,
+                        'did': did,
+                        'sid': sid,
+                        'gid': gid
+                    }
+                )
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def delete(self, gid, sid, did, fid, fsid, umid):
+        """
+        This function will drop the user mapping object
+        """
+        if self.cmd == 'delete':
+            # This is a cascade operation
+            cascade = True
+        else:
+            cascade = False
+
+        try:
+            # Get name of foreign server from fsid
+            SQL = render_template("/".join([self.template_path, 'delete.sql']), fsid=fsid, conn=self.conn)
+            status, name = self.conn.execute_scalar(SQL)
+            if not status:
+                return internal_server_error(errormsg=name)
+
+            SQL = render_template("/".join([self.template_path, 'properties.sql']), umid=umid, conn=self.conn)
+            status, res = self.conn.execute_dict(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            data = res['rows'][0]
+
+            # drop user mapping
+            SQL = render_template("/".join([self.template_path, 'delete.sql']), data=data, name=name, cascade=cascade, conn=self.conn)
+            status, res = self.conn.execute_scalar(SQL)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            return make_json_response(
+                success=1,
+                info=gettext("User Mapping dropped"),
+                data={
+                    'id': umid,
+                    'fsid': fsid,
+                    'fid': fid,
+                    'did': did,
+                    'sid': sid,
+                    'gid': gid,
+                }
+            )
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def msql(self, gid, sid, did, fid, fsid, umid=None):
+        """
+        This function to return modified SQL
+        """
+        data = {}
+        for k, v in request.args.items():
+            try:
+                data[k] = json.loads(v)
+            except ValueError:
+                data[k] = v
+
+        SQL = self.getSQL(gid, sid, data, did, fid, fsid, umid)
+        if SQL and SQL.strip('\n') and SQL.strip(' '):
+            return make_json_response(
+                    data=SQL,
+                    status=200
+                    )
+        else:
+            return make_json_response(
+                    data='-- Modified SQL --',
+                    status=200
+                    )
+
+    def getSQL(self, gid, sid, data, did, fid, fsid, umid=None):
+        """
+        This function will generate sql from model data
+        """
+        required_args = [
+            'name'
+        ]
+        try:
+            if umid is not None:
+                SQL = render_template("/".join([self.template_path, 'properties.sql']), umid=umid, conn=self.conn)
+                status, res = self.conn.execute_dict(SQL)
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+                if res['rows'][0]['umoptions'] is not None:
+                    res['rows'][0]['umoptions'] = self.tokenizeOptions(res['rows'][0]['umoptions'])
+
+                old_data = res['rows'][0]
+
+            SQL = render_template("/".join([self.template_path, 'properties.sql']), fserid=fsid, conn=self.conn)
+            status, res1 = self.conn.execute_dict(SQL)
+            if not status:
+                return internal_server_error(errormsg=res1)
+
+            fdwdata = res1['rows'][0]
+
+            for arg in required_args:
+                if arg not in data:
+                    data[arg] = old_data[arg]
+                    SQL = render_template("/".join([self.template_path, 'update.sql']), data=data, o_data=old_data, fdwdata=fdwdata, conn=self.conn)
+                else:
+                    SQL = render_template("/".join([self.template_path, 'properties.sql']), fserid=fsid, conn=self.conn)
+                    status, res = self.conn.execute_dict(SQL)
+                    if not status:
+                        return internal_server_error(errormsg=res)
+                    fdwdata = res['rows'][0]
+                    SQL = render_template("/".join([self.template_path, 'create.sql']), data=data, fdwdata=fdwdata, conn=self.conn)
+                    SQL += "\n"
+                return SQL
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def sql(self, gid, sid, did, fid, fsid, umid):
+        """
+        This function will generate sql for sql panel
+        """
+        SQL = render_template("/".join([self.template_path, 'properties.sql']), umid=umid, conn=self.conn)
+        status, res = self.conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        if res['rows'][0]['umoptions'] is not None:
+            res['rows'][0]['umoptions'] = self.tokenizeOptions(res['rows'][0]['umoptions'])
+
+        SQL = render_template("/".join([self.template_path, 'properties.sql']), fserid=fsid, conn=self.conn)
+        status, res1 = self.conn.execute_dict(SQL)
+        if not status:
+            return internal_server_error(errormsg=res1)
+
+        fdwdata = res1['rows'][0]
+
+        SQL = ''
+        SQL = render_template("/".join([self.template_path, 'create.sql']), data=res['rows'][0], fdwdata=fdwdata, conn=self.conn)
+        SQL += "\n"
+
+        sql_header = """
+-- User Mapping : {0}
+
+-- DROP User Mapping {0}
+
+""".format(res['rows'][0]['name'])
+
+        SQL = sql_header + SQL
+
+        return ajax_response(response=SQL)
+
+    @check_precondition
+    def getroles(self, gid, sid, did,fid, fsid):
+       res = [{ 'label': '', 'value': '' }]
+       res.append({ 'label': 'CURRENT_USER', 'value': 'CURRENT_USER' })
+       res.append({ 'label': 'PUBLIC', 'value': 'PUBLIC' })
+
+       try:
+           SQL = render_template("/".join([self.template_path, 'role.sql']), conn=self.conn)
+           status, rset = self.conn.execute_2darray(SQL)
+           if not status:
+               return internal_server_error(errormsg=rset)
+           for row in rset['rows']:
+               res.append(
+                           { 'label': row['rolname'], 'value': row['rolname'] }
+                       )
+           return make_json_response(
+                   data=res,
+                   status=200
+                   )
+
+       except Exception as e:
+           return internal_server_error(errormsg=str(e))
+
+
+UserMappingView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/static/img/coll-user_mapping.png b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/static/img/coll-user_mapping.png
new file mode 100644
index 0000000000000000000000000000000000000000..0e68c2b12866ad4b480a41a7d1032a1df34a1f45
GIT binary patch
literal 557
zcmV+|0@D47P)<h;3K|Lk000e1NJLTq000mG000mO0{{R3C@l|D00001b5ch_0Itp)
z=>Px#<WNjhMbnU#goK1#TwK4uzo4L?hJt>}!MO6|+snbbl8uXccXagT+W6|=`0C>K
z>g4n0;*yPwlarIZw5<2)=J)F8%E7^KaB$w!%J=H(@#N;Vwziv<l9-Z`RaI4+o10`~
zV$;dS|NsB|{{Hy<{`30%?fCrX_4?uS`P%UKR#sMIV`H0@mD`h+$N&J-?e<(<UYez-
z$K2tYsjbcH^_QZh)9Uch=<v+u@zh>m&s<-|==9#+-fnVp$mQ|WnV-Pr^Qfq(mZGJ^
z<MAF)t-9m!a&mIJ&(^@<@wDOaxVX5l-|ws4@1b~96aWAK0d!JMQvg8b*k%9#00Cl4
zM??UK1szBL000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipV@5d|U~`>c=v006Z~
zL_t&-(?yQg4#F@D08{qfqYSg$l)WM-NCgS7nEC(z!9fM??M}86Aq-Z?3X4CHXe^#c
zM*bm{&SZ1>!q1B(_Hw0aeM7Bo+-`)vq1non+Yrz(rQ54Oe}Im3ha)gyJeh*=*<5MO
zzFe&ppfjIov_0go*?K#0<X)pDLg63Sk04!TTuX5}vtPg^{UUmx6xSR39fsuFilZl<
v*U$SS@(~s91fPQikMj*uLFm9qKgjR~4gnsVZ8Ue000000NkvXXu0mjf48<Lc

literal 0
HcmV?d00001

diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/static/img/user_mapping.png b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/static/img/user_mapping.png
new file mode 100644
index 0000000000000000000000000000000000000000..e768c9bfc0b3e55f32f12bd037b7ecd52a9e430f
GIT binary patch
literal 518
zcmV+h0{Q)kP)<h;3K|Lk000e1NJLTq000mG000mO0{{R3C@l|D00001b5ch_0Itp)
z=>Px#%uq~JMYgW7nVFetYHGc`y?%Okx2~t;*v{nG(6+9setLP=%)|KV-}vg`_v+&J
z>g3nV$$olz_v+^N>gcwvuJ`Kd<Jj8w>g?m#+xP13*38g-dVE`4Tv}XQYHe+%w70ay
z$%l}WWM^n&XJ~_tlc>MSnXR{FYi(LuTBESD)avlj>G00y@XO}$$K>(E<MF}a@x9>j
zlBl#;T3dsPjJn?Px83r0g^ZMxl%=z`wA}KLs<>`$ZokvnvfJ{b#@kp}Sj^?_vD)&m
z*zi?VRXL11s{jB10d!JMQvg8b*k%9#00Cl4M??UK1szBL000SaNLh0L01FcU01FcV
z0GgZ_00007bV*G`2ipV@5d#koeph1v005>*L_t&-(?!kO4#FS|fMH-~Pn%P*iBT^d
z+q^Q1rW!`z{ol#5$n4$!g%-x9;X-ispD0e!EH9!N;>B{c-jr2np7`F@yRaqFsrLsF
zAe&kq1Mu88qSg45qO4H^7yxJ{I91dZkpO(3FIV(!!44j7jY3O307x(d57nZQh}s&E
z1kYF7p#nhkhc#pGPuJ6P+M%lheLR}}-sr(Mk}~E-=N!)S1$}xN(oSpy>i_@%07*qo
IM6N<$g7m}xI{*Lx

literal 0
HcmV?d00001

diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/templates/user_mappings/js/user_mappings.js b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/templates/user_mappings/js/user_mappings.js
new file mode 100644
index 0000000..bd6ef9e
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/templates/user_mappings/js/user_mappings.js
@@ -0,0 +1,103 @@
+define(
+        ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser', 'alertify', 'pgadmin.browser.collection'],
+function($, _, S, pgAdmin, pgBrowser, alertify) {
+
+    var OptionsModel = pgAdmin.Browser.Node.Model.extend({
+        defaults: {
+          umoption: undefined,
+          umvalue: undefined
+        },
+        schema: [
+          {id: 'umoption', label:'Options', type:'text', group: null, editable: true},
+          {id: 'umvalue', label:'Value', type: 'text', group:null, extraHeaderClasses: 'cellwidth-40', editable: true},
+        ],
+        validate: function() {
+          if (_.isUndefined(this.get('umvalue')) ||
+            _.isNull(this.get('umvalue')) ||
+            String(this.get('umvalue')).replace(/^\s+|\s+$/g, '') == '') {
+            var msg = 'Please enter some value!';
+            this.errorModel.set('umvalue', msg);
+            return msg;
+          } else {
+            this.errorModel.unset('umvalue');
+          }
+          return null;
+        }
+    });
+
+  if (!pgBrowser.Nodes['coll-user_mapping']) {
+    var foreign_data_wrappers = pgAdmin.Browser.Nodes['coll-user_mapping'] =
+      pgAdmin.Browser.Collection.extend({
+        node: 'user_mapping',
+        label: '{{ _('User Mappings') }}',
+        type: 'coll-user_mapping',
+        columns: ['name','um_oid']
+      });
+  };
+
+  if (!pgBrowser.Nodes['user_mapping']) {
+    pgAdmin.Browser.Nodes['user_mapping'] = pgAdmin.Browser.Node.extend({
+      parent_type: 'foreign_server',
+      type: 'user_mapping',
+      label: '{{ _('User Mapping') }}',
+      hasSQL:  true,
+      canDrop: true,
+      canDropCascade: true,
+      Init: function() {
+        /* Avoid mulitple registration of menus */
+        if (this.initialized)
+            return;
+
+        this.initialized = true;
+
+        pgBrowser.add_menus([{
+          name: 'create_user_mapping_on_coll', node: 'coll-user_mapping', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: '{{ _('User Mapping...') }}',
+          icon: 'wcTabIcon pg-icon-user_mapping', data: {action: 'create'}
+        },{
+          name: 'create_user_mapping', node: 'user_mapping', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: '{{ _('User Mapping...') }}',
+          icon: 'wcTabIcon pg-icon-user_mapping', data: {action: 'create'}
+        }
+        ]);
+      },
+      model: pgAdmin.Browser.Node.Model.extend({
+        defaults: {
+          name: undefined,
+          um_options: []
+        },
+        schema: [{
+          id: 'name', label:'{{ _('User') }}', type: 'text', control: 'node-ajax-options',
+          mode: ['edit', 'create', 'properties'], url:'getroles',
+          disabled: function(m) { return !m.isNew(); }
+        },{
+          id: 'um_oid', label:'{{ _('Oid') }}', cell: 'string',
+          type: 'text', disabled: true
+        },{
+          id: 'umoptions', label: 'Options', type: 'collection', group: "Options",
+          model: OptionsModel, control: 'unique-col-collection', mode: ['create', 'edit', 'properties'],
+          canAdd: true, canDelete: true, uniqueCol : ['umoption']
+        }
+        ],
+        validate: function() {
+          var name = this.get('name');
+
+          if (_.isUndefined(name) || _.isNull(name) ||
+            String(name).replace(/^\s+|\s+$/g, '') == '') {
+            var msg = '{{ _('Name can not be empty!') }}';
+            this.errorModel.set('name', msg);
+            return msg;
+          } else {
+            this.errorModel.unset('name');
+          }
+          return null;
+        }
+      })
+  });
+
+  }
+
+  return pgBrowser.Nodes['coll-user_mapping'];
+});
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/templates/user_mappings/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/templates/user_mappings/sql/create.sql
new file mode 100644
index 0000000..f467ae4
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/templates/user_mappings/sql/create.sql
@@ -0,0 +1,13 @@
+{# ============= Create of user mapping for server ============= #}
+{% if data and fdwdata %}
+CREATE USER MAPPING FOR {% if data.name == "CURRENT_USER" or data.name == "PUBLIC" %}{{ data.name }}{% else %}{{ conn|qtIdent(data.name) }}{% endif %} SERVER {{ conn|qtIdent(fdwdata.name) }}{%endif%}{% if data.umoptions %}
+
+{% set addAlter = "False" %}
+{% for variable in data.umoptions %}
+{% if variable.umoption and variable.umoption != '' %}
+{% if addAlter == "False" %}
+    OPTIONS ({% set addAlter = "True" %}{% endif %}
+{{ conn|qtIdent(variable.umoption) }} {{variable.umvalue|qtLiteral}}{% if not loop.last %},{%else%}){% endif %}
+{% endif %}
+{% endfor %}
+{% endif %}{% if data %};{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/templates/user_mappings/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/templates/user_mappings/sql/delete.sql
new file mode 100644
index 0000000..099d265
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/templates/user_mappings/sql/delete.sql
@@ -0,0 +1,9 @@
+{# ============= Get the foreing server name from id ============= #}
+{% if fsid %}
+SELECT srvname as name FROM pg_foreign_server srv LEFT OUTER JOIN pg_foreign_data_wrapper fdw on fdw.oid=srvfdw
+WHERE srv.oid={{fsid}}::int;
+{% endif %}
+{# ============= Drop/Delete cascade user mapping ============= #}
+{% if name and data %}
+DROP USER MAPPING FOR  {{ conn|qtIdent(data.name) }} SERVER {{ conn|qtIdent(name) }}
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/templates/user_mappings/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/templates/user_mappings/sql/properties.sql
new file mode 100644
index 0000000..59f5b41
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/templates/user_mappings/sql/properties.sql
@@ -0,0 +1,23 @@
+{# ============= Get the properties of user mapping ============= #}
+{% if fserid %}
+SELECT srv.oid as fsrvid, srvname as name
+FROM pg_foreign_server srv
+LEFT OUTER JOIN pg_description des ON (des.objoid=srv.oid AND des.objsubid=0 AND des.classoid='pg_foreign_server'::regclass)
+WHERE srv.oid = {{fserid}}::int
+{% endif %}
+{% if fsid or umid or fdwdata or data %}
+WITH umapData AS
+(
+SELECT u.oid AS um_oid, CASE WHEN u.umuser = 0::oid THEN 'public'::name ELSE a.rolname END AS name,
+array_to_string(u.umoptions, ',') AS umoptions FROM pg_user_mapping u
+LEFT JOIN pg_authid a ON a.oid = u.umuser {% if fsid %} WHERE u.umserver = {{fsid}}::int {% endif %} {% if umid %} WHERE u.oid= {{umid}}::int {% endif %}
+)
+select * from umapData
+{% if data %}
+WHERE {% if data.name == "CURRENT_USER" %} name = {{data.name}} {% elif data.name == "PUBLIC" %} name = {{data.name.lower()|qtLiteral}} {% else %} name = {{data.name|qtLiteral}} {% endif %}
+{% endif %}
+{% if fdwdata %}
+WHERE fdw.fdwname = {{fdwdata.name|qtLiteral}}::text
+{% endif %}
+ORDER BY 2;
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/templates/user_mappings/sql/role.sql b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/templates/user_mappings/sql/role.sql
new file mode 100644
index 0000000..12be482
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/templates/user_mappings/sql/role.sql
@@ -0,0 +1,2 @@
+{# ============= Get list of roles from database server ============= #}
+SELECT rolname FROM pg_roles ORDER BY rolname;
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/templates/user_mappings/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/templates/user_mappings/sql/update.sql
new file mode 100644
index 0000000..b255f5e
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/foreign_servers/user_mapping/templates/user_mappings/sql/update.sql
@@ -0,0 +1,34 @@
+{# ============= Update user mapping options and values ============= #}
+{% if o_data.name and data.umoptions and data.umoptions.added and fdwdata %}
+{% set addAlter = "False" %}
+{% for variable in data.umoptions.added %}
+{% if variable.umoption and variable.umoption != '' %}
+{% if addAlter == "False" %}
+ALTER USER MAPPING FOR {{ conn|qtIdent(o_data.name) }} SERVER {{ conn|qtIdent(fdwdata.name) }}
+    OPTIONS ({% set addAlter = "True" %}{% endif %}
+ADD {{ conn|qtIdent(variable.umoption) }} {{variable.umvalue|qtLiteral}}{% if not loop.last %},{% else %});{% endif %}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% if data.umoptions and data.umoptions.changed and fdwdata %}
+{% set addAlter = "False" %}
+{% for variable in data.umoptions.changed %}
+{% if variable.umoption and variable.umoption != '' %}
+{% if addAlter == "False" %}
+ALTER USER MAPPING FOR {{ conn|qtIdent(o_data.name) }} SERVER {{ conn|qtIdent(fdwdata.name) }}
+    OPTIONS ({% set addAlter = "True" %}{%endif%}
+ADD {{conn|qtIdent(variable.umoption)}} {{variable.umvalue|qtLiteral}}{% if not loop.last %},{% else %});{% endif %}
+{%endif%}
+{% endfor %}
+{% endif %}
+{% if data.umoptions and data.umoptions.deleted and fdwdata %}
+{% set addAlter = "False" %}
+{% for variable in data.umoptions.deleted %}
+{% if variable.umoption and variable.umoption != '' %}
+{% if addAlter == "False" %}
+ALTER USER MAPPING FOR {{ conn|qtIdent(o_data.name) }} SERVER {{ conn|qtIdent(fdwdata.name) }}
+    OPTIONS ({% set addAlter = "True" %}{%endif%}
+DROP {{conn|qtIdent(variable.umoption)}}{% if not loop.last %},{% else %});{% endif %}
+{% endif %}
+{% endfor %}
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/static/img/coll-foreign_data_wrapper.png b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/static/img/coll-foreign_data_wrapper.png
new file mode 100644
index 0000000000000000000000000000000000000000..6b1c8543c69448b928acd21032fe50b21f032dd1
GIT binary patch
literal 444
zcmV;t0Ym<YP)<h;3K|Lk000e1NJLTq000mG000mO0{{R3C@l|D00001b5ch_0Itp)
z=>Px#u24)=MV5h&gKT{2-{<1h-r333(7nsXvB1BkxruUunTC_NsI%$c<=Dy8&%MgO
zsJNYkmfO+T>fh$#)!om&%CMKI(8bI0@$&ij`StPg`uY0t?eFB<-|+75_VxDQ)7QkS
zwOpjOjH|fs?(X;X_Qb2STcEQa+Q(zD!I*rNXN8+jm#y^i^5N9j!>Y4erMB<x@8Hzf
zTcWh}@$>ol`R4EO&)eYP)Y-$Uv|6IH_Vx9cr0AId0004WQchC<K<3zH00001VoOIv
z0Eh)0NB{r;32;bRa{vGf6951U69E94oEQKA00(qQO+^RW1059+BqYfdga7~ldPzh<
zR2b7;j<E^^ArM2gy=}!H*l075KL7t~H#)nEcWES#m`Tcq(vrVz>j1x*-2O5foEZm^
z`4qFE7i6Pp7#W~t4s-!1H?RTG&^sRBo~lya!!orz>g<ZcyyhxZor<lz0KZ_U7V|ys
mf4G_gl(D?mFhELXy!8cdl@mA=ku9_U0000<MNUMnLSTY>Cg2eO

literal 0
HcmV?d00001

diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/static/img/foreign_data_wrapper.png b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/static/img/foreign_data_wrapper.png
new file mode 100644
index 0000000000000000000000000000000000000000..0cb967671834e280190bbd5777c3ddc0ab07901d
GIT binary patch
literal 411
zcmV;M0c8G(P)<h;3K|Lk000e1NJLTq000mG000mO0{{R3C@l|D00001b5ch_0Itp)
z=>Px#%uq~JMV5h&gKT{2-{<1h-r333(7nsXvB1BkxruUunTC_NsI%$c<=Dy8&%MgO
zsJNYkmfO+T>fh$#)!om&%CMKI(8bI0@$&ij`StPg`uY0t?eFB<-|+75_VxDQ)7QkS
zwX~I?#I3gP?(X;X_Qb2SvXGv`t+n>`_2Jam#ICsS?(f5@v$Bw##I3i-#>e2)*rJS-
zwU(oyj+V{L%#n3~w3ea6t+uS9r||CZy0El+l%z|TsEn();ndlojFeiUv>)2XQlztI
zgOXdNwqq#b)Bpeg0d!JMQvg8b*k%9#00Cl4M??UK1szBL000SaNLh0L01FcU01FcV
z0GgZ_00007bV*G`2ipT36%jB#6T$fa0028lL_t&-)4k8p4FEs{1JD|y`!6c4MJ#YW
zi1$$e#3`VFu7Q~S0WCEI5;Xx8$OU%L03E#0hyTHSKLJ-#4RL;k+`<3=002ovPDHLk
FV1jzK&rSdU

literal 0
HcmV?d00001

diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/js/foreign_data_wrappers.js b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/js/foreign_data_wrappers.js
new file mode 100644
index 0000000..a1983a8
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/js/foreign_data_wrappers.js
@@ -0,0 +1,134 @@
+define(
+        ['jquery', 'underscore', 'underscore.string', 'pgadmin', 'pgadmin.browser',
+        'alertify', 'pgadmin.browser.collection',
+        'pgadmin.browser.server.privilege'],
+function($, _, S, pgAdmin, pgBrowser, alertify) {
+
+    var OptionsModel = pgAdmin.Browser.Node.Model.extend({
+        defaults: {
+          fdwoption: undefined,
+          fdwvalue: undefined
+        },
+        schema: [
+          {id: 'fdwoption', label:'Options', type:'text', group: null, editable: true},
+          {id: 'fdwvalue', label:'Value', type: 'text', group:null, extraHeaderClasses: 'cellwidth-40', editable: true},
+        ],
+        validate: function() {
+          if (_.isUndefined(this.get('fdwvalue')) ||
+            _.isNull(this.get('fdwvalue')) ||
+            String(this.get('fdwvalue')).replace(/^\s+|\s+$/g, '') == '') {
+            var msg = 'Please enter some value!';
+            this.errorModel.set('fdwvalue', msg);
+            return msg;
+          } else {
+            this.errorModel.unset('fdwvalue');
+          }
+          return null;
+        }
+    });
+
+  if (!pgBrowser.Nodes['coll-foreign_data_wrapper']) {
+    var foreign_data_wrappers = pgAdmin.Browser.Nodes['coll-foreign_data_wrapper'] =
+      pgAdmin.Browser.Collection.extend({
+        node: 'foreign_data_wrapper',
+        label: '{{ _('Foreign Data Wrappers') }}',
+        type: 'coll-foreign_data_wrapper',
+        columns: ['name','fdwoid','fdwowner','description']
+      });
+  };
+
+  if (!pgBrowser.Nodes['foreign_data_wrapper']) {
+    pgAdmin.Browser.Nodes['foreign_data_wrapper'] = pgAdmin.Browser.Node.extend({
+      parent_type: 'database',
+      type: 'foreign_data_wrapper',
+      label: '{{ _('Foreign Data Wrapper') }}',
+      hasSQL:  true,
+      canDrop: true,
+      canDropCascade: true,
+      Init: function() {
+        /* Avoid mulitple registration of menus */
+        if (this.initialized)
+            return;
+
+        this.initialized = true;
+
+        pgBrowser.add_menus([{
+          name: 'create_foreign_data_wrapper_on_coll', node: 'coll-foreign_data_wrapper', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: '{{ _('Foreign Data Wrapper...') }}',
+          icon: 'wcTabIcon pg-icon-foreign_data_wrapper', data: {action: 'create'}
+        },{
+          name: 'create_foreign_data_wrapper', node: 'foreign_data_wrapper', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: '{{ _('Foreign Data Wrapper...') }}',
+          icon: 'wcTabIcon pg-icon-foreign_data_wrapper', data: {action: 'create'}
+        }
+        ]);
+      },
+      model: pgAdmin.Browser.Node.Model.extend({
+        defaults: {
+          name: undefined,
+          fdwowner: undefined,
+          comment: undefined,
+          fdwvalue: undefined,
+          fdwhan: undefined,
+          fdwoption: undefined,
+          fdwacl: []
+        },
+        schema: [{
+          id: 'name', label: '{{ _('Name') }}', cell: 'string',
+          type: 'text', disabled: function(m) {
+            if (this.mode == 'edit' && this.node_info.server.version < 90200) {
+              return true;
+            }
+            else
+              return false;
+          }
+        },{
+          id: 'fdwoid', label:'{{ _('Oid') }}', cell: 'string',
+          type: 'text', disabled: true
+        },{
+          id: 'fdwowner', label:'{{ _('Owner') }}', type: 'text',
+          control: Backform.NodeListByNameControl, node: 'role',
+          mode: ['edit', 'create', 'properties']
+        },{
+          id: 'fdwhan', label:'{{ _('Handler') }}', type: 'text', control: 'node-ajax-options',
+          group: 'Definition', mode: ['edit', 'create', 'properties'], url:'gethandlers'
+        },{
+          id: 'description', label:'{{ _('Comment') }}', cell: 'string',
+          type: 'multiline'
+        },{
+          id: 'fdwoptions', label: 'Options', type: 'collection', group: "Options",
+          model: OptionsModel, control: 'unique-col-collection', mode: ['edit', 'create', 'properties'],
+          canAdd: true, canDelete: true, uniqueCol : ['fdwoption'],
+          columns: ['fdwoption','fdwvalue']
+        },{
+          id: 'fdwvalue', label:'{{ _('Validator') }}', type: 'text', control: 'node-ajax-options',
+          group: 'Definition', mode: ['edit', 'create', 'properties'], url:'getvalidators'
+        },{
+            id: 'fdwacl', label: 'Privileges', type: 'collection', group: "Privileges",
+            model: pgAdmin.Browser.Node.PrivilegeRoleModel.extend({privileges: ['U']}), control: 'unique-col-collection',
+            mode: ['properties', 'edit', 'create'], canAdd: true, canDelete: true, uniqueCol : ['grantee'],
+            columns: ['grantee', 'grantor', 'privileges']
+         }
+        ],
+        validate: function() {
+          var name = this.get('name');
+
+          if (_.isUndefined(name) || _.isNull(name) ||
+            String(name).replace(/^\s+|\s+$/g, '') == '') {
+            var msg = '{{ _('Name can not be empty!') }}';
+            this.errorModel.set('name', msg);
+            return msg;
+          } else {
+            this.errorModel.unset('name');
+          }
+          return null;
+        }
+      })
+  });
+
+  }
+
+  return pgBrowser.Nodes['coll-foreign_data_wrapper'];
+});
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/acl.sql b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/acl.sql
new file mode 100644
index 0000000..def271f
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/acl.sql
@@ -0,0 +1,21 @@
+SELECT 'fdwacl' as deftype, COALESCE(gt.rolname, 'public') grantee, g.rolname grantor, array_agg(privilege_type) as privileges, array_agg(is_grantable) as grantable
+FROM
+	(SELECT
+		d.grantee, d.grantor, d.is_grantable,
+		CASE d.privilege_type
+		WHEN 'USAGE' THEN 'U'
+		ELSE 'UNKNOWN'
+		END AS privilege_type
+	FROM
+		(SELECT fdwacl FROM pg_foreign_data_wrapper db
+		    LEFT OUTER JOIN pg_shdescription descr ON (
+			db.oid=descr.objoid AND descr.classoid='pg_foreign_data_wrapper'::regclass)
+{% if fid %}
+  WHERE db.oid = {{ fid|qtLiteral }}::OID
+{% endif %}
+		) acl,
+		aclexplode(fdwacl) d
+		) d
+	LEFT JOIN pg_catalog.pg_roles g ON (d.grantor = g.oid)
+	LEFT JOIN pg_catalog.pg_roles gt ON (d.grantee = gt.oid)
+GROUP BY g.rolname, gt.rolname
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/create.sql b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/create.sql
new file mode 100644
index 0000000..231f451
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/create.sql
@@ -0,0 +1,39 @@
+{# ============= Create foreign data wrapper ============= #}
+{% import 'macros/privilege.macros' as PRIVILEGE %}
+{% if data.name %}
+CREATE FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}{% if data.fdwvalue %}
+
+    VALIDATOR {{ conn|qtIdent(data.fdwvalue) }}{%endif%}{% if data.fdwhan %}
+
+    HANDLER {{ conn|qtIdent(data.fdwhan) }}{% endif %}{% if data.fdwoptions %}
+
+{% set addAlter = "False" %}
+{% for variable in data.fdwoptions %}
+{% if variable.fdwoption and variable.fdwoption != '' %}
+{% if addAlter == "False" %}
+    OPTIONS ({% set addAlter = "True" %}{% endif %}
+{{ conn|qtIdent(variable.fdwoption) }} {{variable.fdwvalue|qtLiteral}}{% if not loop.last %},{% else %}){% endif %}
+{% endif %}
+{% endfor %}{% endif %}{%if data %};{%endif%}
+
+
+{# ============= Set the owner for foreign data wrapper ============= #}
+{% if data.fdwowner %}
+ALTER FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}
+    OWNER TO {{ conn|qtIdent(data.fdwowner) }};
+{% endif %}
+
+{# ============= Comment on of foreign data wrapper object ============= #}
+{% if data.description %}
+COMMENT ON FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}
+    IS {{ data.description|qtLiteral }};
+{% endif %}
+
+{# ============= Create ACL for foreign data wrapper ============= #}
+{% if data.fdwacl %}
+{% for priv in data.fdwacl %}
+{{ PRIVILEGE.APPLY(conn, 'FOREIGN DATA WRAPPER', priv.grantee, data.name, priv.without_grant, priv.with_grant) }}
+{% endfor %}
+{% endif %}
+
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/delete.sql
new file mode 100644
index 0000000..5677c99
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/delete.sql
@@ -0,0 +1,8 @@
+{# ============= Get foreign data wrapper from fid ============= #}
+{% if fid %}
+SELECT fdwname as name from pg_foreign_data_wrapper WHERE oid={{fid}}::int;
+{% endif %}
+{# ============= Delete/Drop cascade foreign data wrapper ============= #}
+{% if name %}
+DROP FOREIGN DATA WRAPPER {{ conn|qtIdent(name) }} {% if cascade %} CASCADE {% endif %};
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/handlers.sql b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/handlers.sql
new file mode 100644
index 0000000..50d27c6
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/handlers.sql
@@ -0,0 +1,2 @@
+{# ============= Get the handlers of foreign data wrapper ============= #}
+SELECT proname as fdwhan FROM pg_proc p JOIN pg_namespace nsp ON nsp.oid=pronamespace WHERE pronargs=0 AND prorettype=3115;
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/properties.sql
new file mode 100644
index 0000000..3024a76
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/properties.sql
@@ -0,0 +1,14 @@
+{# ============= Get all the properties of foreign data wrapper ============= #}
+SELECT fdw.oid as fdwoid, fdwname as name, fdwhandler, fdwvalidator, vh.proname as fdwhan, vp.proname as fdwvalue, description,
+           array_to_string(fdwoptions, ',') AS fdwoptions, pg_get_userbyid(fdwowner) as fdwowner
+FROM pg_foreign_data_wrapper fdw
+LEFT OUTER JOIN pg_proc vh on vh.oid=fdwhandler
+LEFT OUTER JOIN pg_proc vp on vp.oid=fdwvalidator
+LEFT OUTER JOIN pg_description des ON (des.objoid=fdw.oid AND des.objsubid=0 AND des.classoid='pg_foreign_data_wrapper'::regclass)
+{% if fid %}
+WHERE fdw.oid={{fid}}::int
+{% endif %}
+{% if fname %}
+WHERE fdw.fdwname={{ fname|qtLiteral }}::text
+{% endif %}
+ORDER BY fdwname
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/update.sql b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/update.sql
new file mode 100644
index 0000000..fc76aaa
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/update.sql
@@ -0,0 +1,98 @@
+{% import 'macros/privilege.macros' as PRIVILEGE %}
+{% if data %}
+{# ============= Update foreign data wrapper name ============= #}
+{% if data.name != o_data.name %}
+ALTER FOREIGN DATA WRAPPER {{ conn|qtIdent(o_data.name) }}
+    RENAME TO {{ conn|qtIdent(data.name) }};
+
+{% endif %}
+{# ============= Update foreign data wrapper owner ============= #}
+{% if data.fdwowner and data.fdwowner != o_data.fdwowner %}
+ALTER FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}
+    OWNER TO {{ conn|qtIdent(data.fdwowner) }};
+
+{% endif %}
+{# ============= Update foreign data wrapper validator ============= #}
+{% if data.fdwvalue and data.fdwvalue != o_data.fdwvalue %}
+ALTER FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}
+    VALIDATOR {{ conn|qtIdent(data.fdwvalue) }};
+
+{% endif %}
+{% if data.fdwvalue == '' and data.fdwvalue != o_data.fdwvalue %}
+ALTER FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}
+    NO VALIDATOR;
+
+{% endif %}
+{# ============= Update foreign data wrapper handler ============= #}
+{% if data.fdwhan and data.fdwhan != o_data.fdwhan %}
+ALTER FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}
+    HANDLER {{ conn|qtIdent(data.fdwhan) }};
+
+{% endif %}
+{% if data.fdwhan == '' and data.fdwhan != o_data.fdwhan %}
+ALTER FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}
+    NO HANDLER;
+
+{% endif %}
+{# ============= Update foreign data wrapper comments ============= #}
+{% if data.description and data.description != o_data.description %}
+COMMENT ON FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}
+    IS {{ data.description|qtLiteral }};
+
+{% endif %}
+{# ============= Update foreign data wrapper options and values ============= #}
+{% if data.fdwoptions and data.fdwoptions.added %}
+{% set addAlter = "False" %}
+{% for variable in data.fdwoptions.added %}
+{% if variable.fdwoption and variable.fdwoption != '' %}
+{% if addAlter == "False" %}
+ALTER FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}
+    OPTIONS ({% set addAlter = "True" %}{% endif %}
+ADD {{ conn|qtIdent(variable.fdwoption) }} {{variable.fdwvalue|qtLiteral}}{% if not loop.last %},{% else %});{% endif %}
+{% endif %}
+{%endfor%}
+{% endif %}
+{% if data.fdwoptions and data.fdwoptions.changed %}
+{% set addAlter = "False" %}
+{% for variable in data.fdwoptions.changed %}
+{% if variable.fdwoption and variable.fdwoption != '' %}
+{% if addAlter == "False" %}
+ALTER FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}
+    OPTIONS ({% set addAlter = "True" %}{% endif %}
+ADD {{ conn|qtIdent(variable.fdwoption) }} {{variable.fdwvalue|qtLiteral}}{% if not loop.last %},{% else %});{% endif %}
+{% endif %}
+{%endfor%}
+{% endif %}
+{% if data.fdwoptions and data.fdwoptions.deleted %}
+{% set addAlter = "False" %}
+{% for variable in data.fdwoptions.deleted %}
+{% if variable.fdwoption and variable.fdwoption != '' %}
+{% if addAlter == "False" %}
+ALTER FOREIGN DATA WRAPPER {{ conn|qtIdent(data.name) }}
+    OPTIONS ({% set addAlter = "True" %}{%endif%}
+DROP {{ conn|qtIdent(variable.fdwoption) }}{% if not loop.last %},{% else %});{% endif %}
+{% endif %}
+{% endfor %}
+{% endif %}
+
+{# Change the privileges #}
+{% if data.fdwacl %}
+{% if 'deleted' in data.fdwacl %}
+{% for priv in data.fdwacl.deleted %}
+{{ PRIVILEGE.RESETALL(conn, 'FOREIGN DATA WRAPPER', priv.grantee, data.name) }}
+{% endfor %}
+{% endif %}
+{% if 'changed' in data.fdwacl %}
+{% for priv in data.fdwacl.changed %}
+{{ PRIVILEGE.RESETALL(conn, 'FOREIGN DATA WRAPPER', priv.grantee, data.name) }}
+{{ PRIVILEGE.APPLY(conn, 'FOREIGN DATA WRAPPER', priv.grantee, data.name, priv.without_grant, priv.with_grant) }}
+{% endfor %}
+{% endif %}
+{% if 'added' in data.fdwacl %}
+{% for priv in data.fdwacl.added %}
+{{ PRIVILEGE.APPLY(conn, 'FOREIGN DATA WRAPPER', priv.grantee, data.name, priv.without_grant, priv.with_grant) }}
+{% endfor %}
+{% endif %}
+{% endif %}
+
+{% endif %}
\ No newline at end of file
diff --git a/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/validators.sql b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/validators.sql
new file mode 100644
index 0000000..6f3d019
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/foreign_data_wrappers/templates/foreign_data_wrappers/sql/validators.sql
@@ -0,0 +1,2 @@
+{# ============= Get the validators of foreign data wrapper ============= #}
+SELECT proname as fdwvalue FROM pg_proc p JOIN pg_namespace nsp ON nsp.oid=pronamespace WHERE proargtypes[0]=1009 AND proargtypes[1]=26;
