diff --git a/web/pgadmin/tools/sqleditor/tests/test_download_csv_query_tool.py b/web/pgadmin/tools/sqleditor/tests/test_download_csv_query_tool.py index c60fbe1e5e2..f4157dbdc7f 100644 --- a/web/pgadmin/tools/sqleditor/tests/test_download_csv_query_tool.py +++ b/web/pgadmin/tools/sqleditor/tests/test_download_csv_query_tool.py @@ -29,11 +29,12 @@ class TestDownloadCSV(BaseTestGenerator): ( 'Download csv URL with valid query', dict( - sql='SELECT 1 as "A",2 as "B",3 as "C"', + sql='SELECT 1 as "A",2 as "B",3 as "C",2300::numeric' + ' as "Price"', init_url='/sqleditor/initialize/sqleditor/{0}/{1}/{2}/{3}', donwload_url="/sqleditor/query_tool/download/{0}", - output_columns='"A","B","C"', - output_values='1,2,3', + output_columns='"A","B","C","Price"', + output_values='1,2,3,2300', is_valid_tx=True, is_valid=True, download_as_txt=False, @@ -205,8 +206,8 @@ def runTest(self): # when valid query self.assertEqual(response.status_code, 200) csv_data = response.data.decode() - self.assertTrue(self.output_columns in csv_data) - self.assertTrue(self.output_values in csv_data) + self.assertIn(self.output_columns, csv_data) + self.assertIn(self.output_values, csv_data) self.assertIn('text/csv', headers['Content-Type']) self.assertIn(self.filename, headers['Content-Disposition']) elif not self.is_valid and self.is_valid_tx: @@ -214,8 +215,8 @@ def runTest(self): self.assertEqual(response.status_code, 200) response_data = json.loads(response.data.decode('utf-8')) self.assertFalse(response_data['data']['status']) - self.assertTrue( - 'relation "this_table_does_not_exist" does not exist' in + self.assertIn( + 'relation "this_table_does_not_exist" does not exist', response_data['data']['result'] ) else: diff --git a/web/pgadmin/utils/driver/psycopg3/connection.py b/web/pgadmin/utils/driver/psycopg3/connection.py index 55b77ccaac5..f28357076a7 100644 --- a/web/pgadmin/utils/driver/psycopg3/connection.py +++ b/web/pgadmin/utils/driver/psycopg3/connection.py @@ -34,7 +34,8 @@ from .cursor import DictCursor, AsyncDictCursor, AsyncDictServerCursor from .typecast import register_global_typecasters,\ register_string_typecasters, register_binary_typecasters, \ - register_array_to_string_typecasters, ALL_JSON_TYPES + register_array_to_string_typecasters, ALL_JSON_TYPES, \ + register_numeric_typecasters from .encoding import get_encoding, configure_driver_encodings from pgadmin.utils import csv_lib as csv from pgadmin.utils.master_password import get_crypt_key @@ -903,6 +904,8 @@ def gen(conn_obj, trans_obj, quote='strings', quote_char="'", cur.scroll(0, mode='absolute') except Exception as e: print(str(e)) + # Make sure numeric values will be fetched without quoting + register_numeric_typecasters(cur) results = cur.fetchmany(records) if not results: yield gettext('The query executed did not return any data.') diff --git a/web/pgadmin/utils/driver/psycopg3/typecast.py b/web/pgadmin/utils/driver/psycopg3/typecast.py index 06f41b82617..b906a23f95c 100644 --- a/web/pgadmin/utils/driver/psycopg3/typecast.py +++ b/web/pgadmin/utils/driver/psycopg3/typecast.py @@ -17,6 +17,7 @@ from psycopg._encodings import py_codecs as encodings from .encoding import get_encoding, configure_driver_encodings from psycopg.types.net import InetLoader +from psycopg.types.numeric import NumericLoader, IntLoader, FloatLoader from psycopg.adapt import Loader from ipaddress import ip_address, ip_interface from psycopg._encodings import py_codecs as encodings @@ -168,6 +169,19 @@ def register_string_typecasters(connection): connection.adapters.register_loader(typ, TextLoaderpgAdmin) +def register_numeric_typecasters(_cursor): + # These original loader registration works on cursor level + + _cursor.adapters.register_loader(1700, + NumericLoader) + _cursor.adapters.register_loader(700, + FloatLoader) + _cursor.adapters.register_loader(701, + FloatLoader) + _cursor.adapters.register_loader(20, + IntLoader) + + def register_binary_typecasters(connection): # The new classes can be registered globally, on a connection, on a cursor