We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Datastore plugin fails to startup sometimes
We are running ckan using gunicorn.
Gunicorn run multiple threads.

At start time, datastore plugin checks the existence of db write permission by creating a table named _foo and removes it immediately afterwards. 
However, since we are starting multiple threads, it just happens that a second thread will try to create the table while it has not been removed yet (race condition? :) ).
A solution would be to change the table name with something random / depending on the time.

Please find solutions, as this is another "untouchable" piece from ckan.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ckanext/datastore/plugin.py`
Content:
```
1 import sys
2 import logging
3 
4 import ckan.plugins as p
5 import ckanext.datastore.logic.action as action
6 import ckanext.datastore.logic.auth as auth
7 import ckanext.datastore.db as db
8 import ckan.logic as logic
9 import ckan.model as model
10 
11 log = logging.getLogger(__name__)
12 _get_or_bust = logic.get_or_bust
13 
14 DEFAULT_FORMATS = []
15 
16 
17 class DatastoreException(Exception):
18     pass
19 
20 
21 class DatastorePlugin(p.SingletonPlugin):
22     p.implements(p.IConfigurable, inherit=True)
23     p.implements(p.IActions)
24     p.implements(p.IAuthFunctions)
25     p.implements(p.IResourceUrlChange)
26     p.implements(p.IDomainObjectModification, inherit=True)
27     p.implements(p.IRoutes, inherit=True)
28     p.implements(p.IResourceController, inherit=True)
29 
30     legacy_mode = False
31     resource_show_action = None
32 
33     def configure(self, config):
34         self.config = config
35         # check for ckan.datastore.write_url and ckan.datastore.read_url
36         if (not 'ckan.datastore.write_url' in config):
37             error_msg = 'ckan.datastore.write_url not found in config'
38             raise DatastoreException(error_msg)
39 
40         # Legacy mode means that we have no read url. Consequently sql search is not
41         # available and permissions do not have to be changed. In legacy mode, the
42         # datastore runs on PG prior to 9.0 (for example 8.4).
43         self.legacy_mode = 'ckan.datastore.read_url' not in self.config
44 
45         datapusher_formats = config.get('datapusher.formats', '').split()
46         self.datapusher_formats = datapusher_formats or DEFAULT_FORMATS
47 
48         # Check whether we are running one of the paster commands which means
49         # that we should ignore the following tests.
50         if sys.argv[0].split('/')[-1] == 'paster' and 'datastore' in sys.argv[1:]:
51             log.warn('Omitting permission checks because you are '
52                      'running paster commands.')
53             return
54 
55         self.ckan_url = self.config['sqlalchemy.url']
56         self.write_url = self.config['ckan.datastore.write_url']
57         if self.legacy_mode:
58             self.read_url = self.write_url
59             log.warn('Legacy mode active. '
60                      'The sql search will not be available.')
61         else:
62             self.read_url = self.config['ckan.datastore.read_url']
63 
64         self.read_engine = db._get_engine(
65             {'connection_url': self.read_url})
66         if not model.engine_is_pg(self.read_engine):
67             log.warn('We detected that you do not use a PostgreSQL '
68                      'database. The DataStore will NOT work and DataStore '
69                      'tests will be skipped.')
70             return
71 
72         if self._is_read_only_database():
73             log.warn('We detected that CKAN is running on a read '
74                      'only database. Permission checks and the creation '
75                      'of _table_metadata are skipped.')
76         else:
77             self._check_urls_and_permissions()
78             self._create_alias_table()
79 
80 
81     def notify(self, entity, operation=None):
82         if not isinstance(entity, model.Package) or self.legacy_mode:
83             return
84         # if a resource is new, it cannot have a datastore resource, yet
85         if operation == model.domain_object.DomainObjectOperation.changed:
86             context = {'model': model, 'ignore_auth': True}
87             if entity.private:
88                 func = p.toolkit.get_action('datastore_make_private')
89             else:
90                 func = p.toolkit.get_action('datastore_make_public')
91             for resource in entity.resources:
92                 try:
93                     func(context, {
94                         'connection_url': self.write_url,
95                         'resource_id': resource.id})
96                 except p.toolkit.ObjectNotFound:
97                     pass
98 
99     def _log_or_raise(self, message):
100         if self.config.get('debug'):
101             log.critical(message)
102         else:
103             raise DatastoreException(message)
104 
105     def _check_urls_and_permissions(self):
106         # Make sure that the right permissions are set
107         # so that no harmful queries can be made
108 
109         if self._same_ckan_and_datastore_db():
110             self._log_or_raise('CKAN and DataStore database '
111                                'cannot be the same.')
112 
113         # in legacy mode, the read and write url are the same (both write url)
114         # consequently the same url check and and write privilege check
115         # don't make sense
116         if not self.legacy_mode:
117             if self._same_read_and_write_url():
118                 self._log_or_raise('The write and read-only database '
119                                    'connection urls are the same.')
120 
121             if not self._read_connection_has_correct_privileges():
122                 self._log_or_raise('The read-only user has write privileges.')
123 
124     def _is_read_only_database(self):
125         ''' Returns True if no connection has CREATE privileges on the public
126         schema. This is the case if replication is enabled.'''
127         for url in [self.ckan_url, self.write_url, self.read_url]:
128             connection = db._get_engine({'connection_url': url}).connect()
129             try:
130                 sql = u"SELECT has_schema_privilege('public', 'CREATE')"
131                 is_writable = connection.execute(sql).first()[0]
132             finally:
133                 connection.close()
134             if is_writable:
135                 return False
136         return True
137 
138     def _same_ckan_and_datastore_db(self):
139         '''Returns True if the CKAN and DataStore db are the same'''
140         return self._get_db_from_url(self.ckan_url) == self._get_db_from_url(self.read_url)
141 
142     def _get_db_from_url(self, url):
143         return url[url.rindex("@"):]
144 
145     def _same_read_and_write_url(self):
146         return self.write_url == self.read_url
147 
148     def _read_connection_has_correct_privileges(self):
149         ''' Returns True if the right permissions are set for the read
150         only user. A table is created by the write user to test the
151         read only user.
152         '''
153         write_connection = db._get_engine(
154             {'connection_url': self.write_url}).connect()
155         read_connection = db._get_engine(
156             {'connection_url': self.read_url}).connect()
157 
158         drop_foo_sql = u'DROP TABLE IF EXISTS _foo'
159 
160         write_connection.execute(drop_foo_sql)
161 
162         try:
163             try:
164                 write_connection.execute(u'CREATE TABLE _foo ()')
165                 for privilege in ['INSERT', 'UPDATE', 'DELETE']:
166                     test_privilege_sql = u"SELECT has_table_privilege('_foo', '{privilege}')"
167                     sql = test_privilege_sql.format(privilege=privilege)
168                     have_privilege = read_connection.execute(sql).first()[0]
169                     if have_privilege:
170                         return False
171             finally:
172                 write_connection.execute(drop_foo_sql)
173         finally:
174             write_connection.close()
175             read_connection.close()
176         return True
177 
178     def _create_alias_table(self):
179         mapping_sql = '''
180             SELECT DISTINCT
181                 substr(md5(dependee.relname || COALESCE(dependent.relname, '')), 0, 17) AS "_id",
182                 dependee.relname AS name,
183                 dependee.oid AS oid,
184                 dependent.relname AS alias_of
185                 -- dependent.oid AS oid
186             FROM
187                 pg_class AS dependee
188                 LEFT OUTER JOIN pg_rewrite AS r ON r.ev_class = dependee.oid
189                 LEFT OUTER JOIN pg_depend AS d ON d.objid = r.oid
190                 LEFT OUTER JOIN pg_class AS dependent ON d.refobjid = dependent.oid
191             WHERE
192                 (dependee.oid != dependent.oid OR dependent.oid IS NULL) AND
193                 (dependee.relname IN (SELECT tablename FROM pg_catalog.pg_tables)
194                     OR dependee.relname IN (SELECT viewname FROM pg_catalog.pg_views)) AND
195                 dependee.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname='public')
196             ORDER BY dependee.oid DESC;
197         '''
198         create_alias_table_sql = u'CREATE OR REPLACE VIEW "_table_metadata" AS {0}'.format(mapping_sql)
199         try:
200             connection = db._get_engine(
201                 {'connection_url': self.write_url}).connect()
202             connection.execute(create_alias_table_sql)
203         finally:
204             connection.close()
205 
206     def get_actions(self):
207         actions = {'datastore_create': action.datastore_create,
208                    'datastore_upsert': action.datastore_upsert,
209                    'datastore_delete': action.datastore_delete,
210                    'datastore_search': action.datastore_search,
211                   }
212         if not self.legacy_mode:
213             actions.update({
214                 'datastore_search_sql': action.datastore_search_sql,
215                 'datastore_make_private': action.datastore_make_private,
216                 'datastore_make_public': action.datastore_make_public})
217         return actions
218 
219     def get_auth_functions(self):
220         return {'datastore_create': auth.datastore_create,
221                 'datastore_upsert': auth.datastore_upsert,
222                 'datastore_delete': auth.datastore_delete,
223                 'datastore_search': auth.datastore_search,
224                 'datastore_search_sql': auth.datastore_search_sql,
225                 'datastore_change_permissions': auth.datastore_change_permissions}
226 
227     def before_map(self, m):
228         m.connect('/datastore/dump/{resource_id}',
229                   controller='ckanext.datastore.controller:DatastoreController',
230                   action='dump')
231         return m
232 
233     def before_show(self, resource_dict):
234         # Modify the resource url of datastore resources so that
235         # they link to the datastore dumps.
236         if resource_dict.get('url_type') == 'datastore':
237             resource_dict['url'] = p.toolkit.url_for(
238                 controller='ckanext.datastore.controller:DatastoreController',
239                 action='dump', resource_id=resource_dict['id'])
240 
241         try:
242             connection = self.read_engine.connect()
243             result = connection.execute(
244                 'SELECT 1 FROM "_table_metadata" WHERE name = %s AND alias_of IS NULL',
245                 resource_dict['id']
246             ).fetchone()
247             if result:
248                 resource_dict['datastore_active'] = True
249             else:
250                 resource_dict['datastore_active'] = False
251         finally:
252             connection.close()
253         return resource_dict
254
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/ckanext/datastore/plugin.py b/ckanext/datastore/plugin.py
--- a/ckanext/datastore/plugin.py
+++ b/ckanext/datastore/plugin.py
@@ -1,6 +1,9 @@
 import sys
 import logging
 
+import string
+import random
+
 import ckan.plugins as p
 import ckanext.datastore.logic.action as action
 import ckanext.datastore.logic.auth as auth
@@ -155,15 +158,19 @@
         read_connection = db._get_engine(
             {'connection_url': self.read_url}).connect()
 
-        drop_foo_sql = u'DROP TABLE IF EXISTS _foo'
+        # create a nice random table name
+        chars = string.uppercase + string.lowercase
+        table_name = '_'.join(random.choice(chars) for _ in range(9))
+        
+        drop_foo_sql = u'DROP TABLE IF EXISTS ' + table_name
 
         write_connection.execute(drop_foo_sql)
 
         try:
             try:
-                write_connection.execute(u'CREATE TABLE _foo ()')
+                write_connection.execute(u'CREATE TEMP TABLE ' + table_name + ' ()')
                 for privilege in ['INSERT', 'UPDATE', 'DELETE']:
-                    test_privilege_sql = u"SELECT has_table_privilege('_foo', '{privilege}')"
+                    test_privilege_sql = u"SELECT has_table_privilege('" + table_name + "', '{privilege}')"
                     sql = test_privilege_sql.format(privilege=privilege)
                     have_privilege = read_connection.execute(sql).first()[0]
                     if have_privilege:

{"golden_diff": "diff --git a/ckanext/datastore/plugin.py b/ckanext/datastore/plugin.py\n--- a/ckanext/datastore/plugin.py\n+++ b/ckanext/datastore/plugin.py\n@@ -1,6 +1,9 @@\n import sys\n import logging\n \n+import string\n+import random\n+\n import ckan.plugins as p\n import ckanext.datastore.logic.action as action\n import ckanext.datastore.logic.auth as auth\n@@ -155,15 +158,19 @@\n         read_connection = db._get_engine(\n             {'connection_url': self.read_url}).connect()\n \n-        drop_foo_sql = u'DROP TABLE IF EXISTS _foo'\n+        # create a nice random table name\n+        chars = string.uppercase + string.lowercase\n+        table_name = '_'.join(random.choice(chars) for _ in range(9))\n+        \n+        drop_foo_sql = u'DROP TABLE IF EXISTS ' + table_name\n \n         write_connection.execute(drop_foo_sql)\n \n         try:\n             try:\n-                write_connection.execute(u'CREATE TABLE _foo ()')\n+                write_connection.execute(u'CREATE TEMP TABLE ' + table_name + ' ()')\n                 for privilege in ['INSERT', 'UPDATE', 'DELETE']:\n-                    test_privilege_sql = u\"SELECT has_table_privilege('_foo', '{privilege}')\"\n+                    test_privilege_sql = u\"SELECT has_table_privilege('\" + table_name + \"', '{privilege}')\"\n                     sql = test_privilege_sql.format(privilege=privilege)\n                     have_privilege = read_connection.execute(sql).first()[0]\n                     if have_privilege:\n", "issue": "Datastore plugin fails to startup sometimes\nWe are running ckan using gunicorn.\nGunicorn run multiple threads.\n\nAt start time, datastore plugin checks the existence of db write permission by creating a table named _foo and removes it immediately afterwards. \nHowever, since we are starting multiple threads, it just happens that a second thread will try to create the table while it has not been removed yet (race condition? :) ).\nA solution would be to change the table name with something random / depending on the time.\n\nPlease find solutions, as this is another \"untouchable\" piece from ckan.\n\n", "before_files": [{"content": "import sys\nimport logging\n\nimport ckan.plugins as p\nimport ckanext.datastore.logic.action as action\nimport ckanext.datastore.logic.auth as auth\nimport ckanext.datastore.db as db\nimport ckan.logic as logic\nimport ckan.model as model\n\nlog = logging.getLogger(__name__)\n_get_or_bust = logic.get_or_bust\n\nDEFAULT_FORMATS = []\n\n\nclass DatastoreException(Exception):\n    pass\n\n\nclass DatastorePlugin(p.SingletonPlugin):\n    p.implements(p.IConfigurable, inherit=True)\n    p.implements(p.IActions)\n    p.implements(p.IAuthFunctions)\n    p.implements(p.IResourceUrlChange)\n    p.implements(p.IDomainObjectModification, inherit=True)\n    p.implements(p.IRoutes, inherit=True)\n    p.implements(p.IResourceController, inherit=True)\n\n    legacy_mode = False\n    resource_show_action = None\n\n    def configure(self, config):\n        self.config = config\n        # check for ckan.datastore.write_url and ckan.datastore.read_url\n        if (not 'ckan.datastore.write_url' in config):\n            error_msg = 'ckan.datastore.write_url not found in config'\n            raise DatastoreException(error_msg)\n\n        # Legacy mode means that we have no read url. Consequently sql search is not\n        # available and permissions do not have to be changed. In legacy mode, the\n        # datastore runs on PG prior to 9.0 (for example 8.4).\n        self.legacy_mode = 'ckan.datastore.read_url' not in self.config\n\n        datapusher_formats = config.get('datapusher.formats', '').split()\n        self.datapusher_formats = datapusher_formats or DEFAULT_FORMATS\n\n        # Check whether we are running one of the paster commands which means\n        # that we should ignore the following tests.\n        if sys.argv[0].split('/')[-1] == 'paster' and 'datastore' in sys.argv[1:]:\n            log.warn('Omitting permission checks because you are '\n                     'running paster commands.')\n            return\n\n        self.ckan_url = self.config['sqlalchemy.url']\n        self.write_url = self.config['ckan.datastore.write_url']\n        if self.legacy_mode:\n            self.read_url = self.write_url\n            log.warn('Legacy mode active. '\n                     'The sql search will not be available.')\n        else:\n            self.read_url = self.config['ckan.datastore.read_url']\n\n        self.read_engine = db._get_engine(\n            {'connection_url': self.read_url})\n        if not model.engine_is_pg(self.read_engine):\n            log.warn('We detected that you do not use a PostgreSQL '\n                     'database. The DataStore will NOT work and DataStore '\n                     'tests will be skipped.')\n            return\n\n        if self._is_read_only_database():\n            log.warn('We detected that CKAN is running on a read '\n                     'only database. Permission checks and the creation '\n                     'of _table_metadata are skipped.')\n        else:\n            self._check_urls_and_permissions()\n            self._create_alias_table()\n\n\n    def notify(self, entity, operation=None):\n        if not isinstance(entity, model.Package) or self.legacy_mode:\n            return\n        # if a resource is new, it cannot have a datastore resource, yet\n        if operation == model.domain_object.DomainObjectOperation.changed:\n            context = {'model': model, 'ignore_auth': True}\n            if entity.private:\n                func = p.toolkit.get_action('datastore_make_private')\n            else:\n                func = p.toolkit.get_action('datastore_make_public')\n            for resource in entity.resources:\n                try:\n                    func(context, {\n                        'connection_url': self.write_url,\n                        'resource_id': resource.id})\n                except p.toolkit.ObjectNotFound:\n                    pass\n\n    def _log_or_raise(self, message):\n        if self.config.get('debug'):\n            log.critical(message)\n        else:\n            raise DatastoreException(message)\n\n    def _check_urls_and_permissions(self):\n        # Make sure that the right permissions are set\n        # so that no harmful queries can be made\n\n        if self._same_ckan_and_datastore_db():\n            self._log_or_raise('CKAN and DataStore database '\n                               'cannot be the same.')\n\n        # in legacy mode, the read and write url are the same (both write url)\n        # consequently the same url check and and write privilege check\n        # don't make sense\n        if not self.legacy_mode:\n            if self._same_read_and_write_url():\n                self._log_or_raise('The write and read-only database '\n                                   'connection urls are the same.')\n\n            if not self._read_connection_has_correct_privileges():\n                self._log_or_raise('The read-only user has write privileges.')\n\n    def _is_read_only_database(self):\n        ''' Returns True if no connection has CREATE privileges on the public\n        schema. This is the case if replication is enabled.'''\n        for url in [self.ckan_url, self.write_url, self.read_url]:\n            connection = db._get_engine({'connection_url': url}).connect()\n            try:\n                sql = u\"SELECT has_schema_privilege('public', 'CREATE')\"\n                is_writable = connection.execute(sql).first()[0]\n            finally:\n                connection.close()\n            if is_writable:\n                return False\n        return True\n\n    def _same_ckan_and_datastore_db(self):\n        '''Returns True if the CKAN and DataStore db are the same'''\n        return self._get_db_from_url(self.ckan_url) == self._get_db_from_url(self.read_url)\n\n    def _get_db_from_url(self, url):\n        return url[url.rindex(\"@\"):]\n\n    def _same_read_and_write_url(self):\n        return self.write_url == self.read_url\n\n    def _read_connection_has_correct_privileges(self):\n        ''' Returns True if the right permissions are set for the read\n        only user. A table is created by the write user to test the\n        read only user.\n        '''\n        write_connection = db._get_engine(\n            {'connection_url': self.write_url}).connect()\n        read_connection = db._get_engine(\n            {'connection_url': self.read_url}).connect()\n\n        drop_foo_sql = u'DROP TABLE IF EXISTS _foo'\n\n        write_connection.execute(drop_foo_sql)\n\n        try:\n            try:\n                write_connection.execute(u'CREATE TABLE _foo ()')\n                for privilege in ['INSERT', 'UPDATE', 'DELETE']:\n                    test_privilege_sql = u\"SELECT has_table_privilege('_foo', '{privilege}')\"\n                    sql = test_privilege_sql.format(privilege=privilege)\n                    have_privilege = read_connection.execute(sql).first()[0]\n                    if have_privilege:\n                        return False\n            finally:\n                write_connection.execute(drop_foo_sql)\n        finally:\n            write_connection.close()\n            read_connection.close()\n        return True\n\n    def _create_alias_table(self):\n        mapping_sql = '''\n            SELECT DISTINCT\n                substr(md5(dependee.relname || COALESCE(dependent.relname, '')), 0, 17) AS \"_id\",\n                dependee.relname AS name,\n                dependee.oid AS oid,\n                dependent.relname AS alias_of\n                -- dependent.oid AS oid\n            FROM\n                pg_class AS dependee\n                LEFT OUTER JOIN pg_rewrite AS r ON r.ev_class = dependee.oid\n                LEFT OUTER JOIN pg_depend AS d ON d.objid = r.oid\n                LEFT OUTER JOIN pg_class AS dependent ON d.refobjid = dependent.oid\n            WHERE\n                (dependee.oid != dependent.oid OR dependent.oid IS NULL) AND\n                (dependee.relname IN (SELECT tablename FROM pg_catalog.pg_tables)\n                    OR dependee.relname IN (SELECT viewname FROM pg_catalog.pg_views)) AND\n                dependee.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname='public')\n            ORDER BY dependee.oid DESC;\n        '''\n        create_alias_table_sql = u'CREATE OR REPLACE VIEW \"_table_metadata\" AS {0}'.format(mapping_sql)\n        try:\n            connection = db._get_engine(\n                {'connection_url': self.write_url}).connect()\n            connection.execute(create_alias_table_sql)\n        finally:\n            connection.close()\n\n    def get_actions(self):\n        actions = {'datastore_create': action.datastore_create,\n                   'datastore_upsert': action.datastore_upsert,\n                   'datastore_delete': action.datastore_delete,\n                   'datastore_search': action.datastore_search,\n                  }\n        if not self.legacy_mode:\n            actions.update({\n                'datastore_search_sql': action.datastore_search_sql,\n                'datastore_make_private': action.datastore_make_private,\n                'datastore_make_public': action.datastore_make_public})\n        return actions\n\n    def get_auth_functions(self):\n        return {'datastore_create': auth.datastore_create,\n                'datastore_upsert': auth.datastore_upsert,\n                'datastore_delete': auth.datastore_delete,\n                'datastore_search': auth.datastore_search,\n                'datastore_search_sql': auth.datastore_search_sql,\n                'datastore_change_permissions': auth.datastore_change_permissions}\n\n    def before_map(self, m):\n        m.connect('/datastore/dump/{resource_id}',\n                  controller='ckanext.datastore.controller:DatastoreController',\n                  action='dump')\n        return m\n\n    def before_show(self, resource_dict):\n        # Modify the resource url of datastore resources so that\n        # they link to the datastore dumps.\n        if resource_dict.get('url_type') == 'datastore':\n            resource_dict['url'] = p.toolkit.url_for(\n                controller='ckanext.datastore.controller:DatastoreController',\n                action='dump', resource_id=resource_dict['id'])\n\n        try:\n            connection = self.read_engine.connect()\n            result = connection.execute(\n                'SELECT 1 FROM \"_table_metadata\" WHERE name = %s AND alias_of IS NULL',\n                resource_dict['id']\n            ).fetchone()\n            if result:\n                resource_dict['datastore_active'] = True\n            else:\n                resource_dict['datastore_active'] = False\n        finally:\n            connection.close()\n        return resource_dict\n", "path": "ckanext/datastore/plugin.py"}], "after_files": [{"content": "import sys\nimport logging\n\nimport string\nimport random\n\nimport ckan.plugins as p\nimport ckanext.datastore.logic.action as action\nimport ckanext.datastore.logic.auth as auth\nimport ckanext.datastore.db as db\nimport ckan.logic as logic\nimport ckan.model as model\n\nlog = logging.getLogger(__name__)\n_get_or_bust = logic.get_or_bust\n\nDEFAULT_FORMATS = []\n\n\nclass DatastoreException(Exception):\n    pass\n\n\nclass DatastorePlugin(p.SingletonPlugin):\n    p.implements(p.IConfigurable, inherit=True)\n    p.implements(p.IActions)\n    p.implements(p.IAuthFunctions)\n    p.implements(p.IResourceUrlChange)\n    p.implements(p.IDomainObjectModification, inherit=True)\n    p.implements(p.IRoutes, inherit=True)\n    p.implements(p.IResourceController, inherit=True)\n\n    legacy_mode = False\n    resource_show_action = None\n\n    def configure(self, config):\n        self.config = config\n        # check for ckan.datastore.write_url and ckan.datastore.read_url\n        if (not 'ckan.datastore.write_url' in config):\n            error_msg = 'ckan.datastore.write_url not found in config'\n            raise DatastoreException(error_msg)\n\n        # Legacy mode means that we have no read url. Consequently sql search is not\n        # available and permissions do not have to be changed. In legacy mode, the\n        # datastore runs on PG prior to 9.0 (for example 8.4).\n        self.legacy_mode = 'ckan.datastore.read_url' not in self.config\n\n        datapusher_formats = config.get('datapusher.formats', '').split()\n        self.datapusher_formats = datapusher_formats or DEFAULT_FORMATS\n\n        # Check whether we are running one of the paster commands which means\n        # that we should ignore the following tests.\n        if sys.argv[0].split('/')[-1] == 'paster' and 'datastore' in sys.argv[1:]:\n            log.warn('Omitting permission checks because you are '\n                     'running paster commands.')\n            return\n\n        self.ckan_url = self.config['sqlalchemy.url']\n        self.write_url = self.config['ckan.datastore.write_url']\n        if self.legacy_mode:\n            self.read_url = self.write_url\n            log.warn('Legacy mode active. '\n                     'The sql search will not be available.')\n        else:\n            self.read_url = self.config['ckan.datastore.read_url']\n\n        self.read_engine = db._get_engine(\n            {'connection_url': self.read_url})\n        if not model.engine_is_pg(self.read_engine):\n            log.warn('We detected that you do not use a PostgreSQL '\n                     'database. The DataStore will NOT work and DataStore '\n                     'tests will be skipped.')\n            return\n\n        if self._is_read_only_database():\n            log.warn('We detected that CKAN is running on a read '\n                     'only database. Permission checks and the creation '\n                     'of _table_metadata are skipped.')\n        else:\n            self._check_urls_and_permissions()\n            self._create_alias_table()\n\n\n    def notify(self, entity, operation=None):\n        if not isinstance(entity, model.Package) or self.legacy_mode:\n            return\n        # if a resource is new, it cannot have a datastore resource, yet\n        if operation == model.domain_object.DomainObjectOperation.changed:\n            context = {'model': model, 'ignore_auth': True}\n            if entity.private:\n                func = p.toolkit.get_action('datastore_make_private')\n            else:\n                func = p.toolkit.get_action('datastore_make_public')\n            for resource in entity.resources:\n                try:\n                    func(context, {\n                        'connection_url': self.write_url,\n                        'resource_id': resource.id})\n                except p.toolkit.ObjectNotFound:\n                    pass\n\n    def _log_or_raise(self, message):\n        if self.config.get('debug'):\n            log.critical(message)\n        else:\n            raise DatastoreException(message)\n\n    def _check_urls_and_permissions(self):\n        # Make sure that the right permissions are set\n        # so that no harmful queries can be made\n\n        if self._same_ckan_and_datastore_db():\n            self._log_or_raise('CKAN and DataStore database '\n                               'cannot be the same.')\n\n        # in legacy mode, the read and write url are the same (both write url)\n        # consequently the same url check and and write privilege check\n        # don't make sense\n        if not self.legacy_mode:\n            if self._same_read_and_write_url():\n                self._log_or_raise('The write and read-only database '\n                                   'connection urls are the same.')\n\n            if not self._read_connection_has_correct_privileges():\n                self._log_or_raise('The read-only user has write privileges.')\n\n    def _is_read_only_database(self):\n        ''' Returns True if no connection has CREATE privileges on the public\n        schema. This is the case if replication is enabled.'''\n        for url in [self.ckan_url, self.write_url, self.read_url]:\n            connection = db._get_engine({'connection_url': url}).connect()\n            try:\n                sql = u\"SELECT has_schema_privilege('public', 'CREATE')\"\n                is_writable = connection.execute(sql).first()[0]\n            finally:\n                connection.close()\n            if is_writable:\n                return False\n        return True\n\n    def _same_ckan_and_datastore_db(self):\n        '''Returns True if the CKAN and DataStore db are the same'''\n        return self._get_db_from_url(self.ckan_url) == self._get_db_from_url(self.read_url)\n\n    def _get_db_from_url(self, url):\n        return url[url.rindex(\"@\"):]\n\n    def _same_read_and_write_url(self):\n        return self.write_url == self.read_url\n\n    def _read_connection_has_correct_privileges(self):\n        ''' Returns True if the right permissions are set for the read\n        only user. A table is created by the write user to test the\n        read only user.\n        '''\n        write_connection = db._get_engine(\n            {'connection_url': self.write_url}).connect()\n        read_connection = db._get_engine(\n            {'connection_url': self.read_url}).connect()\n\n        # create a nice random table name\n        chars = string.uppercase + string.lowercase\n        table_name = '_'.join(random.choice(chars) for _ in range(9))\n        \n        drop_foo_sql = u'DROP TABLE IF EXISTS ' + table_name\n\n        write_connection.execute(drop_foo_sql)\n\n        try:\n            try:\n                write_connection.execute(u'CREATE TEMP TABLE ' + table_name + ' ()')\n                for privilege in ['INSERT', 'UPDATE', 'DELETE']:\n                    test_privilege_sql = u\"SELECT has_table_privilege('\" + table_name + \"', '{privilege}')\"\n                    sql = test_privilege_sql.format(privilege=privilege)\n                    have_privilege = read_connection.execute(sql).first()[0]\n                    if have_privilege:\n                        return False\n            finally:\n                write_connection.execute(drop_foo_sql)\n        finally:\n            write_connection.close()\n            read_connection.close()\n        return True\n\n    def _create_alias_table(self):\n        mapping_sql = '''\n            SELECT DISTINCT\n                substr(md5(dependee.relname || COALESCE(dependent.relname, '')), 0, 17) AS \"_id\",\n                dependee.relname AS name,\n                dependee.oid AS oid,\n                dependent.relname AS alias_of\n                -- dependent.oid AS oid\n            FROM\n                pg_class AS dependee\n                LEFT OUTER JOIN pg_rewrite AS r ON r.ev_class = dependee.oid\n                LEFT OUTER JOIN pg_depend AS d ON d.objid = r.oid\n                LEFT OUTER JOIN pg_class AS dependent ON d.refobjid = dependent.oid\n            WHERE\n                (dependee.oid != dependent.oid OR dependent.oid IS NULL) AND\n                (dependee.relname IN (SELECT tablename FROM pg_catalog.pg_tables)\n                    OR dependee.relname IN (SELECT viewname FROM pg_catalog.pg_views)) AND\n                dependee.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname='public')\n            ORDER BY dependee.oid DESC;\n        '''\n        create_alias_table_sql = u'CREATE OR REPLACE VIEW \"_table_metadata\" AS {0}'.format(mapping_sql)\n        try:\n            connection = db._get_engine(\n                {'connection_url': self.write_url}).connect()\n            connection.execute(create_alias_table_sql)\n        finally:\n            connection.close()\n\n    def get_actions(self):\n        actions = {'datastore_create': action.datastore_create,\n                   'datastore_upsert': action.datastore_upsert,\n                   'datastore_delete': action.datastore_delete,\n                   'datastore_search': action.datastore_search,\n                  }\n        if not self.legacy_mode:\n            actions.update({\n                'datastore_search_sql': action.datastore_search_sql,\n                'datastore_make_private': action.datastore_make_private,\n                'datastore_make_public': action.datastore_make_public})\n        return actions\n\n    def get_auth_functions(self):\n        return {'datastore_create': auth.datastore_create,\n                'datastore_upsert': auth.datastore_upsert,\n                'datastore_delete': auth.datastore_delete,\n                'datastore_search': auth.datastore_search,\n                'datastore_search_sql': auth.datastore_search_sql,\n                'datastore_change_permissions': auth.datastore_change_permissions}\n\n    def before_map(self, m):\n        m.connect('/datastore/dump/{resource_id}',\n                  controller='ckanext.datastore.controller:DatastoreController',\n                  action='dump')\n        return m\n\n    def before_show(self, resource_dict):\n        # Modify the resource url of datastore resources so that\n        # they link to the datastore dumps.\n        if resource_dict.get('url_type') == 'datastore':\n            resource_dict['url'] = p.toolkit.url_for(\n                controller='ckanext.datastore.controller:DatastoreController',\n                action='dump', resource_id=resource_dict['id'])\n\n        try:\n            connection = self.read_engine.connect()\n            result = connection.execute(\n                'SELECT 1 FROM \"_table_metadata\" WHERE name = %s AND alias_of IS NULL',\n                resource_dict['id']\n            ).fetchone()\n            if result:\n                resource_dict['datastore_active'] = True\n            else:\n                resource_dict['datastore_active'] = False\n        finally:\n            connection.close()\n        return resource_dict\n", "path": "ckanext/datastore/plugin.py"}]}