</%block>
<%def name="sql_row(tab_index)">
+ <tr>
+ <%self.lib:td_label for_id="search_path_id">
+ search_path
+ </%self.lib:td_label>
+ <%self.lib:td_input tab_index="${tab_index}">
+ <input name="search_path"
+ tabindex="${tab_index.val}"
+ id="search_path_id"
+ type="text"
+ size="30"
+ value="${search_path}"
+ />
+ </%self.lib:td_input>
+ </tr>
<tr>
<%self.lib:td_input tab_index="${tab_index}" colspan="2">
<textarea name="sql"
--- /dev/null
+# Copyright (C) 2024 The Meme Factory, Inc. http://www.karlpinc.com/
+
+# This file is part of PGWUI_SQL.
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public
+# License along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+#
+
+from wtforms.fields import TextAreaField
+import attrs
+import pgwui_core.core
+import pgwui_core.forms
+
+
+@attrs.define(slots=False)
+class SQLInitialPost(pgwui_core.forms.UserInitialPost):
+ sql = attrs.field(default='')
+
+
+class SQLWTForm(pgwui_core.forms.AuthWTForm):
+ '''The wtform used to connect to the db to execute SQL.'''
+ # We don't actually use the labels, wanting the template to
+ # look (and render) like html, but I'll define them anyway
+ # just to keep my hand in.
+ sql = TextAreaField('SQL:', id='sql_id')
+
+
+@attrs.define(slots=False)
+class SQLForm(pgwui_core.forms.UploadFormBaseMixin,
+ pgwui_core.forms.AuthLoadedForm):
+ '''
+ Acts like a dict, but with extra methods.
+
+ Attributes:
+ uh The UploadHandler instance using the form
+ '''
+ def read(self):
+ '''
+ Read form data from the client
+ '''
+
+ # Read parent's data
+ super().read()
+
+ # Read our own data
+ if self._form.sql.data is None:
+ self['sql'] = ''
+ else:
+ self['sql'] = self._form.sql.data
+
+ def write(self, result, errors):
+ '''
+ Produces the dict pyramid will use to render the form.
+ '''
+ response = super().write(result, errors)
+ response['sql'] = self['sql']
+ return response
#
from pyramid.view import view_config
-from wtforms.fields import TextAreaField
import attrs
import logging
import markupsafe
import psycopg.errors
import pgwui_core.core
-import pgwui_core.forms
import pgwui_core.utils
+import pgwui_sql.views.base
from pgwui_common.view import auth_base_view
from pgwui_sql import exceptions as sql_ex
log = logging.getLogger(__name__)
-@attrs.define(slots=False)
-class SQLInitialPost(pgwui_core.forms.UserInitialPost):
- sql = attrs.field(default='')
-
-
-class SQLWTForm(pgwui_core.forms.AuthWTForm):
- '''The wtform used to connect to the db to execute SQL.'''
- # We don't actually use the labels, wanting the template to
- # look (and render) like html, but I'll define them anyway
- # just to keep my hand in.
- sql = TextAreaField('SQL:', id='sql_id')
-
-
-@attrs.define(slots=False)
-class SQLForm(pgwui_core.forms.UploadFormBaseMixin,
- pgwui_core.forms.AuthLoadedForm):
- '''
- Acts like a dict, but with extra methods.
-
- Attributes:
- uh The UploadHandler instance using the form
- '''
- def read(self):
- '''
- Read form data from the client
- '''
-
- # Read parent's data
- super().read()
-
- # Read our own data
- if self._form.sql.data is None:
- self['sql'] = ''
- else:
- self['sql'] = self._form.sql.data
-
- def write(self, result, errors):
- '''
- Produces the dict pyramid will use to render the form.
- '''
- response = super().write(result, errors)
- response['sql'] = self['sql']
- return response
-
-
@attrs.define()
class SQLResult():
rows = attrs.field(factory=list)
@attrs.define(slots=False)
-class SQLHandler(pgwui_core.core.SessionDBHandler):
+class SQLResultsHandler(pgwui_core.core.SessionDBHandler):
'''
Deliver no data to the upload engine, instead do all the SQL
execution here, in the cleanup method.
sql_results = attrs.field(factory=list)
def make_form(self):
- '''
- Make the upload form needed by this handler.
- '''
- return SQLForm().build(self, ip=SQLInitialPost(), fc=SQLWTForm)
-
- def get_data(self):
- '''Return no data. Data is in lines and we have no lines.
- '''
- self.data = tuple()
+ return pgwui_sql.views.base.SQLForm().build(
+ self, ip=pgwui_sql.views.base.SQLInitialPost(),
+ fc=pgwui_sql.views.base.SQLWTForm)
def write(self, result, errors):
'''
and self.uf['action'] != '')
return response
+ def get_data(self):
+ '''Return no data. Data is in lines and we have no lines.
+ '''
+ self.data = tuple()
+
def format_detail(self, err, stmt_text):
detail = []
if err.diag.message_detail is not None:
@auth_base_view
def sql_view(request):
- uh = SQLHandler(request).init()
- response = pgwui_core.core.UploadEngine(uh).run()
-
- settings = request.registry.settings
- response.setdefault('pgwui', dict())
- response['pgwui']['pgwui_sql'] = settings['pgwui']['pgwui_sql']
-
- response['result_rows'] = uh.sql_results
-
- if response['report_success']:
- # if pgwui_core.utils.is_checked(response['csv_checked']):
- # download_fmt = 'CSV'
- # else:
- # download_fmt = 'TAB'
- log.info('Successful sql: DB {db}:'
- # ' File ({filename}):'
- # ' Format {format}:'
- ' By user {user}'
- .format( # filename=response['filename'],
- # format=download_fmt,
- db=response['db'],
- user=response['user']))
- return response
-
-
-@view_config(route_name='pgwui_sql_edit',
- renderer='pgwui_sql:templates/sql_edit.mak')
-@auth_base_view
-def sql_edit_view(request):
- # We don't worry about this contacting the db to execute sql
- # because pgwui_core.core does not try to alter the db until
- # the hidden "action" POST variable is something other than ''.
-
- uh = SQLHandler(request).init()
+ uh = SQLResultsHandler(request).init()
response = pgwui_core.core.UploadEngine(uh).run()
settings = request.registry.settings
--- /dev/null
+# Copyright (C) 2024 The Meme Factory, Inc. http://www.karlpinc.com/
+
+# This file is part of PGWUI_SQL.
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public
+# License along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+#
+
+from pyramid.view import view_config
+import attrs
+import logging
+import pgwui_core.core
+import pgwui_core.forms
+import pgwui_sql.views.base
+import pgwui_sql.exceptions as sql_ex
+
+from pgwui_common.view import auth_base_view
+
+log = logging.getLogger(__name__)
+
+
+@attrs.define(slots=False)
+class SQLEditForm(pgwui_sql.views.base.SQLForm):
+ '''Always set the "action" so that the SQL gets executed
+ '''
+ def read(self):
+ super().read()
+ self['action'] = 'u'
+
+
+# Utility functions
+def _fetcher(cur):
+ '''Get the result of `SHOW search_path;` from a psycopg3 cursor.
+ '''
+ result = cur.fetchone()
+ final = cur.fetchone()
+ if final is not None:
+ raise sql_ex.ExecutionError(
+ 'Problem obtaining the search_path', '',
+ f'Extra result row returned ({final})')
+ return result[0]
+
+
+@attrs.define(slots=False)
+class SQLEditHandler(pgwui_core.core.SessionDBHandler):
+ '''
+ Execute pre-defined SQL statement(s)
+ '''
+ def make_form(self):
+ return SQLEditForm().build(
+ self, ip=pgwui_sql.views.base.SQLInitialPost(),
+ fc=pgwui_sql.views.base.SQLWTForm)
+
+ def get_data(self):
+ '''
+ Build and stash the SQL to be executed.
+
+ Returns:
+ A SQLData instance
+ '''
+ # Get the search_path
+ self.data = pgwui_core.sql.SQLData(
+ [pgwui_core.sql.SQLCommand(
+ 'SHOW search_path;', (), fetcher=_fetcher)])
+
+ def factory(self, ue):
+ '''Make a db loader function from an UploadEngine.
+
+ Input:
+
+ Side Effects:
+ Yes, lots.
+ '''
+ return pgwui_core.sql.ExecuteSQL(ue, self)
+
+ def render(self, errors, result):
+ '''Instead of rendering, just return our results so we can
+ decide what to do next.
+
+ Input:
+ errors List of Error instances
+ result Db connection result dict
+ '''
+ response = super().render(errors, result)
+ return (response, errors)
+
+
+@view_config(route_name='pgwui_sql_edit',
+ renderer='pgwui_sql:templates/sql_edit.mak')
+@auth_base_view
+def sql_edit_view(request):
+ # This can use the UnsafeUploadEngine because it does not alter
+ # server state. We are not getting any form data from the user.
+ # We don't have access to the CSRF token because the page is not
+ # sent a POST request.
+
+ uh = SQLEditHandler(request).init()
+ response, errors = pgwui_core.core.UnsafeUploadEngine(uh).run()
+ response['errors'] = errors
+ if errors:
+ log.warning(f'Failed to get search_path: ({errors})')
+
+ settings = request.registry.settings
+ response.setdefault('pgwui', dict())
+ response['pgwui']['pgwui_sql'] = settings['pgwui']['pgwui_sql']
+
+ response['search_path'] = uh.data.stmts[0].result
+
+ # if pgwui_core.utils.is_checked(response['csv_checked']):
+ # download_fmt = 'CSV'
+ # else:
+ # download_fmt = 'TAB'
+ log.debug('Successful sql editor request: DB {db}:'
+ # ' File ({filename}):'
+ # ' Format {format}:'
+ ' By user {user}:'
+ ' search_path ({search_path})'
+ .format( # filename=response['filename'],
+ # format=download_fmt,
+ db=response['db'],
+ user=response['user'],
+ search_path=response['search_path']))
+ return response
'user': '',
'csrf_token': 'somecsrftoken',
'sql': 'select 1;',
+ 'search_path': '"$user", somedb',
}
# The templates to test