Trap and do basic reporting on encoding exceptions, both client and server-side
authorKarl O. Pinc <kop@karlpinc.com>
Tue, 5 Mar 2024 21:02:16 +0000 (15:02 -0600)
committerKarl O. Pinc <kop@karlpinc.com>
Tue, 5 Mar 2024 21:02:16 +0000 (15:02 -0600)
src/pgwui_upload_core/views/upload.py

index a676f0386651681659dc452528d530a3f181f93d..acb735b75059b859302a571eb85c2237d3eea977 100644 (file)
@@ -42,6 +42,7 @@ from pgwui_core.constants import (
     TAB,
 )
 
+from pgwui_core import exceptions as core_ex
 from pgwui_upload_core import exceptions as upload_ex
 
 
@@ -145,12 +146,32 @@ class BaseTableUploadHandler(TabularFileUploadHandler):
         self.write_double_key(response)
         return response
 
+    def _execute(self, stmt, tupl):
+        '''Execute a statement and express encoding errors
+        '''
+        try:
+            self.cur.execute(stmt, tupl)
+        except UnicodeEncodeError as err:
+            raise core_ex.SQLEncodingError(
+                err,
+                ("Data cannot be represented in the database"
+                 " connection's client-side character encoding"),
+                (f'The SQL statement is ({stmt}) and the data supplied'
+                 f' is ({tupl})'))
+        except psycopg.errors.UntranslatableCharacter as err:
+            raise core_ex.SQLEncodingError(
+                err,
+                ("Data cannot be represented in the"
+                 " character encoding of the database"),
+                (f'The SQL statement is ({stmt}) and the data supplied'
+                 f' is ({tupl})'))
+
     def resolve_normalized_table(self, qualified_table):
         '''Return (schema, table) tuple of table name, or raise exception
         if not resolvable.
         '''
         try:
-            self.cur.execute(
+            self._execute(
                 ('SELECT nspname, relname'
                  '  FROM pg_class'
                  '       JOIN pg_namespace'
@@ -213,7 +234,7 @@ class BaseTableUploadHandler(TabularFileUploadHandler):
                # tables.is_insertable_into does not reflect whether
                # there's an insert trigger on the table.
                "             OR tables.table_type = 'VIEW')")
-        self.cur.execute(sql, (table, schema))
+        self._execute(sql, (table, schema))
         return self.cur.fetchone() is not None
 
     def quote_columns(self, settings):
@@ -298,7 +319,7 @@ class BaseTableUploadHandler(TabularFileUploadHandler):
         bad_cols = []
         for col_name in data.headers.tuples:
             # Check that colum name exists
-            self.cur.execute(column_sql, (table, schema, col_name))
+            self._execute(column_sql, (table, schema, col_name))
             if self.cur.fetchone() is None:
                 bad_cols.append(col_name)
             else: