mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-20 11:48:31 -06:00
Using own version of cursor class to allow us to take care of the
duplicate name in column description.
This commit is contained in:
parent
d15dfac60f
commit
9ba6bafb2b
@ -410,7 +410,8 @@ def poll(trans_id):
|
||||
# rollback to cleanup
|
||||
if isinstance(trans_obj, QueryToolCommand):
|
||||
trans_status = conn.transaction_status()
|
||||
if trans_status == TX_STATUS_INERROR and trans_obj.auto_rollback:
|
||||
if (trans_status == TX_STATUS_INERROR and
|
||||
trans_obj.auto_rollback):
|
||||
conn.execute_void("ROLLBACK;")
|
||||
elif status == ASYNC_EXECUTION_ABORTED:
|
||||
status = 'Cancel'
|
||||
@ -426,10 +427,11 @@ def poll(trans_id):
|
||||
rows_affected = conn.rows_affected()
|
||||
|
||||
for col in col_info:
|
||||
items = list(col.items())
|
||||
col_type = dict()
|
||||
col_type['type_code'] = col[1]
|
||||
col_type['type_code'] = items[1][1]
|
||||
col_type['type_name'] = None
|
||||
columns[col[0]] = col_type
|
||||
columns[items[0][1]] = col_type
|
||||
|
||||
# As we changed the transaction object we need to
|
||||
# restore it and update the session variable.
|
||||
@ -440,9 +442,9 @@ def poll(trans_id):
|
||||
result = conn.status_message()
|
||||
additional_result = conn.messages()
|
||||
"""
|
||||
'Procedure/Function output may comes in the form of Notices from the
|
||||
database server, so we need to append those outputs with the original
|
||||
result.
|
||||
Procedure/Function output may comes in the form of Notices from the
|
||||
database server, so we need to append those outputs with the
|
||||
original result.
|
||||
"""
|
||||
if isinstance(additional_result, list) \
|
||||
and len(additional_result) > 0:
|
||||
@ -450,9 +452,13 @@ def poll(trans_id):
|
||||
|
||||
rows_affected = conn.rows_affected()
|
||||
|
||||
return make_json_response(data={'status': status, 'result': result,
|
||||
'colinfo': col_info, 'primary_keys': primary_keys,
|
||||
'rows_affected': rows_affected})
|
||||
return make_json_response(
|
||||
data={
|
||||
'status': status, 'result': result,
|
||||
'colinfo': col_info, 'primary_keys': primary_keys,
|
||||
'rows_affected': rows_affected
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route('/fetch/types/<int:trans_id>', methods=["GET"])
|
||||
|
@ -1523,7 +1523,7 @@ define(
|
||||
column_type = document.createElement('span'),
|
||||
col_label = '',
|
||||
col_type = '';
|
||||
label_text.innerText = c.name;
|
||||
label_text.innerText = c.display_name;
|
||||
|
||||
var type = pg_types[c.type_code] ?
|
||||
pg_types[c.type_code][0] :
|
||||
|
@ -30,6 +30,7 @@ import config
|
||||
from pgadmin.model import Server, User
|
||||
from .keywords import ScanKeyword
|
||||
from ..abstract import BaseDriver, BaseConnection
|
||||
from .cursor import DictCursor
|
||||
|
||||
_ = gettext
|
||||
|
||||
@ -63,12 +64,10 @@ def register_date_typecasters(connection):
|
||||
return value
|
||||
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('SELECT NULL::date')
|
||||
cursor.execute('SELECT NULL::date, NULL::timestamp, NULL::timestamptz')
|
||||
date_oid = cursor.description[0][1]
|
||||
cursor.execute('SELECT NULL::timestamp')
|
||||
timestamp_oid = cursor.description[0][1]
|
||||
cursor.execute('SELECT NULL::timestamptz')
|
||||
timestamptz_oid = cursor.description[0][1]
|
||||
timestamp_oid = cursor.description[1][1]
|
||||
timestamptz_oid = cursor.description[2][1]
|
||||
oids = (date_oid, timestamp_oid, timestamptz_oid)
|
||||
new_type = psycopg2.extensions.new_type(oids, 'DATE', cast_date)
|
||||
psycopg2.extensions.register_type(new_type)
|
||||
@ -415,7 +414,7 @@ Attempt to reconnect failed with the error:
|
||||
return False, msg
|
||||
|
||||
try:
|
||||
cur = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
|
||||
cur = self.conn.cursor(cursor_factory=DictCursor)
|
||||
except psycopg2.Error as pe:
|
||||
errmsg = gettext("""
|
||||
Failed to create cursor for psycopg2 connection with error message for the \
|
||||
@ -475,17 +474,19 @@ Attempt to reconnect it failed with the error:
|
||||
return False, str(cur)
|
||||
query_id = random.randint(1, 9999999)
|
||||
|
||||
current_app.logger.log(25,
|
||||
"Execute (scalar) for server #{server_id} - {conn_id} (Query-id: {query_id}):\n{query}".format(
|
||||
server_id=self.manager.sid,
|
||||
conn_id=self.conn_id,
|
||||
query=query,
|
||||
query_id=query_id
|
||||
)
|
||||
)
|
||||
current_app.logger.log(
|
||||
25,
|
||||
"Execute (scalar) for server #{server_id} - {conn_id} (Query-id: {query_id}):\n{query}".format(
|
||||
server_id=self.manager.sid,
|
||||
conn_id=self.conn_id,
|
||||
query=query,
|
||||
query_id=query_id
|
||||
)
|
||||
)
|
||||
try:
|
||||
self.__internal_blocking_execute(cur, query, params)
|
||||
except psycopg2.Error as pe:
|
||||
current_app.logger.exception(pe)
|
||||
cur.close()
|
||||
errmsg = self._formatted_exception_msg(pe, formatted_exception_msg)
|
||||
current_app.logger.error(
|
||||
@ -523,15 +524,15 @@ Attempt to reconnect it failed with the error:
|
||||
return False, str(cur)
|
||||
query_id = random.randint(1, 9999999)
|
||||
|
||||
current_app.logger.log(25, """
|
||||
Execute (async) for server #{server_id} - {conn_id} (Query-id: {query_id}):\n{query}
|
||||
""".format(
|
||||
server_id=self.manager.sid,
|
||||
conn_id=self.conn_id,
|
||||
query=query,
|
||||
query_id=query_id
|
||||
current_app.logger.log(
|
||||
25,
|
||||
"Execute (async) for server #{server_id} - {conn_id} (Query-id: {query_id}):\n{query}".format(
|
||||
server_id=self.manager.sid,
|
||||
conn_id=self.conn_id,
|
||||
query=query,
|
||||
query_id=query_id
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
self.execution_aborted = False
|
||||
@ -573,15 +574,15 @@ Failed to execute query (execute_async) for the server #{server_id} - {conn_id}
|
||||
return False, str(cur)
|
||||
query_id = random.randint(1, 9999999)
|
||||
|
||||
current_app.logger.log(25, """
|
||||
Execute (void) for server #{server_id} - {conn_id} (Query-id: {query_id}):\n{query}
|
||||
""".format(
|
||||
server_id=self.manager.sid,
|
||||
conn_id=self.conn_id,
|
||||
query=query,
|
||||
query_id=query_id
|
||||
current_app.logger.log(
|
||||
25,
|
||||
"Execute (void) for server #{server_id} - {conn_id} (Query-id: {query_id}):\n{query}".format(
|
||||
server_id=self.manager.sid,
|
||||
conn_id=self.conn_id,
|
||||
query=query,
|
||||
query_id=query_id
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
self.__internal_blocking_execute(cur, query, params)
|
||||
@ -613,14 +614,15 @@ Failed to execute query (execute_void) for the server #{server_id} - {conn_id}
|
||||
return False, str(cur)
|
||||
|
||||
query_id = random.randint(1, 9999999)
|
||||
current_app.logger.log(25,
|
||||
"Execute (2darray) for server #{server_id} - {conn_id} (Query-id: {query_id}):\n{query}".format(
|
||||
server_id=self.manager.sid,
|
||||
conn_id=self.conn_id,
|
||||
query=query,
|
||||
query_id=query_id
|
||||
)
|
||||
)
|
||||
current_app.logger.log(
|
||||
25,
|
||||
"Execute (2darray) for server #{server_id} - {conn_id} (Query-id: {query_id}):\n{query}".format(
|
||||
server_id=self.manager.sid,
|
||||
conn_id=self.conn_id,
|
||||
query=query,
|
||||
query_id=query_id
|
||||
)
|
||||
)
|
||||
try:
|
||||
self.__internal_blocking_execute(cur, query, params)
|
||||
except psycopg2.Error as pe:
|
||||
@ -637,10 +639,9 @@ Failed to execute query (execute_void) for the server #{server_id} - {conn_id}
|
||||
)
|
||||
return False, errmsg
|
||||
|
||||
import copy
|
||||
# Get Resultset Column Name, Type and size
|
||||
columns = cur.description and [
|
||||
copy.deepcopy(desc._asdict()) for desc in cur.description
|
||||
desc.to_dict() for desc in cur.ordered_description()
|
||||
] or []
|
||||
|
||||
rows = []
|
||||
@ -658,14 +659,15 @@ Failed to execute query (execute_void) for the server #{server_id} - {conn_id}
|
||||
if not status:
|
||||
return False, str(cur)
|
||||
query_id = random.randint(1, 9999999)
|
||||
current_app.logger.log(25,
|
||||
"Execute (dict) for server #{server_id} - {conn_id} (Query-id: {query_id}):\n{query}".format(
|
||||
server_id=self.manager.sid,
|
||||
conn_id=self.conn_id,
|
||||
query=query,
|
||||
query_id=query_id
|
||||
)
|
||||
)
|
||||
current_app.logger.log(
|
||||
25,
|
||||
"Execute (dict) for server #{server_id} - {conn_id} (Query-id: {query_id}):\n{query}".format(
|
||||
server_id=self.manager.sid,
|
||||
conn_id=self.conn_id,
|
||||
query=query,
|
||||
query_id=query_id
|
||||
)
|
||||
)
|
||||
try:
|
||||
self.__internal_blocking_execute(cur, query, params)
|
||||
except psycopg2.Error as pe:
|
||||
@ -681,10 +683,9 @@ Failed to execute query (execute_void) for the server #{server_id} - {conn_id}
|
||||
)
|
||||
return False, errmsg
|
||||
|
||||
import copy
|
||||
# Get Resultset Column Name, Type and size
|
||||
columns = cur.description and [
|
||||
copy.deepcopy(desc._asdict()) for desc in cur.description
|
||||
desc.to_dict() for desc in cur.ordered_description()
|
||||
] or []
|
||||
|
||||
rows = []
|
||||
@ -873,7 +874,9 @@ Failed to reset the connection to the server due to following error:
|
||||
|
||||
# Fetch the column information
|
||||
if cur.description is not None:
|
||||
colinfo = [desc for desc in cur.description]
|
||||
colinfo = [
|
||||
desc.to_dict() for desc in cur.ordered_description()
|
||||
]
|
||||
|
||||
self.row_count = cur.rowcount
|
||||
if cur.rowcount > 0:
|
||||
|
213
web/pgadmin/utils/driver/psycopg2/cursor.py
Normal file
213
web/pgadmin/utils/driver/psycopg2/cursor.py
Normal file
@ -0,0 +1,213 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
"""
|
||||
Implementation of an extended cursor, which returns ordered dictionary when
|
||||
fetching results from it, and also takes care of the duplicate column name in
|
||||
result.
|
||||
"""
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from ordereddict import OrderedDict
|
||||
|
||||
from psycopg2.extensions import cursor as _cursor
|
||||
|
||||
|
||||
class _WrapperColumn(object):
|
||||
"""
|
||||
class _WrapperColumn(object)
|
||||
|
||||
A wrapper class, which wraps the individual description column object,
|
||||
to allow identify the duplicate column name, created by PostgreSQL database
|
||||
server implicitly during query execution.
|
||||
|
||||
Methods:
|
||||
-------
|
||||
* __init__(_col, _name)
|
||||
- Initialize the wrapper around the description column object, which will
|
||||
present the dummy name when available instead of the duplicate name.
|
||||
|
||||
* __getattribute__(name)
|
||||
- Get attributes from the original column description (which is a named
|
||||
tuple) except for few of the attributes of this object (i.e. orig_col,
|
||||
dummy_name, __class__, to_dict) are part of this object.
|
||||
|
||||
* __getitem__(idx)
|
||||
- Get the item from the original object except for the 0th index item,
|
||||
which is for 'name'.
|
||||
|
||||
* __setitem__(idx, value)
|
||||
* __delitem__(idx)
|
||||
- Override them to make the operations on original object.
|
||||
|
||||
* to_dict()
|
||||
- Converts original objects data as OrderedDict (except the name will same
|
||||
as dummy name (if available), and one more parameter as 'display_name'.
|
||||
"""
|
||||
|
||||
def __init__(self, _col, _name):
|
||||
"""Initializer for _WrapperColumn"""
|
||||
self.orig_col = _col
|
||||
self.dummy_name = _name
|
||||
|
||||
def __getattribute__(self, name):
|
||||
"""Getting the attributes from the original object. (except few)"""
|
||||
if (name == 'orig_col' or name == 'dummy_name' or
|
||||
name == '__class__' or name == 'to_dict'):
|
||||
return object.__getattribute__(self, name)
|
||||
elif name == 'name':
|
||||
res = object.__getattribute__(self, 'dummy_name')
|
||||
if res is not None:
|
||||
return res
|
||||
return self.orig_col.__getattribute__(name)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
"""Overrides __getitem__ to fetch item from original object"""
|
||||
if idx == 0 and self.dummy_name is not None:
|
||||
return self.name
|
||||
return self.orig_col.__getitem__(idx)
|
||||
|
||||
def __setitem__(self, *args, **kwargs):
|
||||
"""Orverrides __setitem__ to do the operations on original object."""
|
||||
return self.orig_col.__setitem__(*args, **kwargs)
|
||||
|
||||
def __delitem__(self, *args, **kwargs):
|
||||
"""Orverrides __delitem__ to do the operations on original object."""
|
||||
return self.orig_col.__delitem__(*args, **kwargs)
|
||||
|
||||
def to_dict(self):
|
||||
"""
|
||||
Generates an OrderedDict from the fields of the original objects
|
||||
with avoiding the duplicate name.
|
||||
"""
|
||||
ores = OrderedDict(self.orig_col._asdict())
|
||||
name = ores['name']
|
||||
if self.dummy_name:
|
||||
ores['name'] = self.dummy_name
|
||||
ores['display_name'] = name
|
||||
return ores
|
||||
|
||||
|
||||
class DictCursor(_cursor):
|
||||
"""
|
||||
DictCursor
|
||||
|
||||
A class to generate the dictionary from the tuple, and also takes care of
|
||||
the duplicate column name in result description.
|
||||
|
||||
Methods:
|
||||
-------
|
||||
* __init__()
|
||||
- Initialize the cursor object
|
||||
|
||||
* _dict_tuple(tuple)
|
||||
- Generate a dictionary object from a tuple, based on the column
|
||||
description.
|
||||
|
||||
* _ordered_description()
|
||||
- Generates the _WrapperColumn object from the description column, and
|
||||
identifies duplicate column name
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Initialize the cursor object.
|
||||
"""
|
||||
self._odt_desc = None
|
||||
_cursor.__init__(self, *args, **kwargs)
|
||||
|
||||
def _dict_tuple(self, tup):
|
||||
"""
|
||||
Transform the tuple into a dictionary object.
|
||||
"""
|
||||
if self._odt_desc is None:
|
||||
self._ordered_description()
|
||||
return {k[0]: v for k, v in zip(self._odt_desc, tup)}
|
||||
|
||||
def _ordered_description(self):
|
||||
"""
|
||||
Transform the regular description to wrapper object, which handles
|
||||
duplicate column name.
|
||||
"""
|
||||
self._odt_desc = _cursor.__getattribute__(self, 'description')
|
||||
desc = self._odt_desc
|
||||
|
||||
if desc is None or len(desc) == 0:
|
||||
return
|
||||
|
||||
res = list()
|
||||
od = {d[0]: 0 for d in desc}
|
||||
for d in desc:
|
||||
dummy = None
|
||||
idx = od[d.name]
|
||||
if idx == 0:
|
||||
od[d.name] = 1
|
||||
else:
|
||||
name = ("%s-%s" % (d.name, idx))
|
||||
while name in od:
|
||||
idx += 1
|
||||
name = ("%s-%s" % (d.name, idx))
|
||||
od[d.name] = idx
|
||||
dummy = name
|
||||
res.append(_WrapperColumn(d, dummy))
|
||||
self._odt_desc = tuple(res)
|
||||
|
||||
def ordered_description(self):
|
||||
"""
|
||||
Use this to fetch the description
|
||||
"""
|
||||
if self._odt_desc is None:
|
||||
self._ordered_description()
|
||||
return self._odt_desc
|
||||
|
||||
def execute(self, query, params=None):
|
||||
"""
|
||||
Execute function
|
||||
"""
|
||||
self._odt_desc = None
|
||||
return _cursor.execute(self, query, params)
|
||||
|
||||
def executemany(self, query, params=None):
|
||||
"""
|
||||
Execute many function of regular cursor.
|
||||
"""
|
||||
self._odt_desc = None
|
||||
return _cursor.executemany(self, query, params)
|
||||
|
||||
def callproc(self, proname, params=None):
|
||||
"""
|
||||
Call a procedure by a name.
|
||||
"""
|
||||
self._odt_desc = None
|
||||
return _cursor.callproc(self, proname, params)
|
||||
|
||||
def fetchmany(self, size=None):
|
||||
"""
|
||||
Fetch many tuples as ordered dictionary list.
|
||||
"""
|
||||
tuples = _cursor.fetchmany(self, size)
|
||||
if tuples is not None:
|
||||
return [self._dict_tuple(t) for t in tuples]
|
||||
return None
|
||||
|
||||
def fetchall(self):
|
||||
"""
|
||||
Fetch all tuples as orderd dictionary list.
|
||||
"""
|
||||
tuples = _cursor.fetchall(self)
|
||||
if tuples is not None:
|
||||
return [self._dict_tuple(t) for t in tuples]
|
||||
|
||||
def __iter__(self):
|
||||
it = _cursor.__iter__(self)
|
||||
yield self._dict_tuple(next(it))
|
||||
while 1:
|
||||
yield self._dict_tuple(next(it))
|
Loading…
Reference in New Issue
Block a user