2016-04-14 09:04:03 -05:00
|
|
|
##########################################################################
|
|
|
|
#
|
|
|
|
# pgAdmin 4 - PostgreSQL Tools
|
|
|
|
#
|
2018-01-05 04:42:49 -06:00
|
|
|
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
|
2016-04-14 09:04:03 -05:00
|
|
|
# This software is released under the PostgreSQL Licence
|
|
|
|
#
|
|
|
|
##########################################################################
|
|
|
|
|
|
|
|
""" Implemented classes for the different object type used by data grid """
|
|
|
|
|
|
|
|
from abc import ABCMeta, abstractmethod
|
2017-05-26 11:34:05 -05:00
|
|
|
try:
|
|
|
|
from collections import OrderedDict
|
|
|
|
except ImportError:
|
|
|
|
from ordereddict import OrderedDict
|
2016-06-21 08:12:14 -05:00
|
|
|
import six
|
2016-04-14 09:04:03 -05:00
|
|
|
from flask import render_template
|
2016-07-22 10:25:23 -05:00
|
|
|
from flask_babel import gettext
|
2016-04-14 09:04:03 -05:00
|
|
|
from pgadmin.utils.ajax import forbidden
|
2016-06-21 08:12:14 -05:00
|
|
|
from pgadmin.utils.driver import get_driver
|
|
|
|
|
2016-04-14 09:04:03 -05:00
|
|
|
from config import PG_DEFAULT_DRIVER
|
|
|
|
|
|
|
|
VIEW_FIRST_100_ROWS = 1
|
|
|
|
VIEW_LAST_100_ROWS = 2
|
|
|
|
VIEW_ALL_ROWS = 3
|
|
|
|
VIEW_FILTERED_ROWS = 4
|
|
|
|
|
|
|
|
|
|
|
|
class ObjectRegistry(ABCMeta):
|
|
|
|
"""
|
|
|
|
class ObjectRegistry(ABCMeta)
|
|
|
|
Every object will be registered automatically by its object type.
|
|
|
|
|
|
|
|
Class-level Methods:
|
|
|
|
----------- -------
|
|
|
|
* get_object(cls, name, **kwargs)
|
|
|
|
- This method returns the object based on register object type
|
|
|
|
else return not implemented error.
|
|
|
|
"""
|
|
|
|
|
|
|
|
registry = dict()
|
|
|
|
|
|
|
|
def __init__(cls, name, bases, d):
|
|
|
|
"""
|
|
|
|
This method is used to register the objects based on object type.
|
|
|
|
"""
|
|
|
|
|
|
|
|
if d and 'object_type' in d:
|
|
|
|
ObjectRegistry.registry[d['object_type']] = cls
|
|
|
|
|
|
|
|
ABCMeta.__init__(cls, name, bases, d)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_object(cls, name, **kwargs):
|
|
|
|
"""
|
|
|
|
This method returns the object based on register object type
|
|
|
|
else return not implemented error
|
|
|
|
|
|
|
|
Args:
|
|
|
|
name: object type for which object to be returned.
|
|
|
|
**kwargs: N number of parameters
|
|
|
|
"""
|
|
|
|
|
|
|
|
if name in ObjectRegistry.registry:
|
|
|
|
return (ObjectRegistry.registry[name])(**kwargs)
|
|
|
|
|
|
|
|
raise NotImplementedError(
|
2017-11-01 10:18:07 -05:00
|
|
|
gettext("This feature has not been implemented for object type '{0}'.").format(name)
|
2016-06-21 08:21:06 -05:00
|
|
|
)
|
2016-04-14 09:04:03 -05:00
|
|
|
|
|
|
|
|
|
|
|
@six.add_metaclass(ObjectRegistry)
|
|
|
|
class BaseCommand(object):
|
|
|
|
"""
|
|
|
|
class BaseCommand
|
|
|
|
|
|
|
|
It is a base class for SQL Tools like data grid and query tool.
|
|
|
|
A different sql tools must implement this to expose abstract methods.
|
|
|
|
|
|
|
|
Abstract Methods:
|
|
|
|
-------- -------
|
|
|
|
* get_sql()
|
|
|
|
- This method returns the proper SQL query for the object type.
|
|
|
|
|
|
|
|
* can_edit()
|
|
|
|
- This method returns True/False, specifying whether data is
|
|
|
|
editable or not.
|
|
|
|
|
|
|
|
* can_filter()
|
|
|
|
- This method returns True/False, specifying whether filter
|
|
|
|
will be applied on data or not.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
"""
|
|
|
|
This method is used to initialize the class and
|
|
|
|
create a proper object name which will be used
|
|
|
|
to fetch the data using namespace name and object name.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
**kwargs : N number of parameters
|
|
|
|
"""
|
|
|
|
|
|
|
|
# Save the server id and database id, namespace id, object id
|
|
|
|
self.sid = kwargs['sid'] if 'sid' in kwargs else None
|
|
|
|
self.did = kwargs['did'] if 'did' in kwargs else None
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
def get_sql(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
def can_edit(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
def can_filter(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class SQLFilter(object):
|
|
|
|
"""
|
|
|
|
class SQLFilter
|
|
|
|
|
|
|
|
Implementation of filter class for sql grid.
|
|
|
|
|
|
|
|
Class-level Methods:
|
|
|
|
----------- -------
|
|
|
|
* get_filter()
|
|
|
|
- This method returns the filter applied.
|
|
|
|
* set_filter(row_filter)
|
|
|
|
- This method sets the filter to be applied.
|
|
|
|
* append_filter(row_filter)
|
|
|
|
- This method is used to append the filter within existing filter
|
|
|
|
* remove_filter()
|
|
|
|
- This method removes the filter applied.
|
|
|
|
* validate_filter(row_filter)
|
|
|
|
- This method validates the given filter.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
"""
|
|
|
|
This method is used to initialize the class and
|
|
|
|
create a proper object name which will be used
|
|
|
|
to fetch the data using namespace name and object name.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
**kwargs : N number of parameters
|
|
|
|
"""
|
|
|
|
# Save the server id and database id, namespace id, object id
|
|
|
|
assert ('sid' in kwargs)
|
|
|
|
assert ('did' in kwargs)
|
|
|
|
assert ('obj_id' in kwargs)
|
|
|
|
|
|
|
|
self.sid = kwargs['sid']
|
|
|
|
self.did = kwargs['did']
|
|
|
|
self.obj_id = kwargs['obj_id']
|
|
|
|
self.__row_filter = kwargs['sql_filter'] if 'sql_filter' in kwargs else None
|
|
|
|
|
|
|
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(self.sid)
|
|
|
|
conn = manager.connection(did=self.did)
|
|
|
|
|
|
|
|
# we will set template path for sql scripts
|
2017-01-30 05:25:02 -06:00
|
|
|
self.sql_path = 'sqleditor/sql/#{0}#'.format(manager.version)
|
2016-04-14 09:04:03 -05:00
|
|
|
|
|
|
|
if conn.connected():
|
|
|
|
# Fetch the Namespace Name and object Name
|
|
|
|
query = render_template("/".join([self.sql_path, 'objectname.sql']), obj_id=self.obj_id)
|
|
|
|
|
|
|
|
status, result = conn.execute_dict(query)
|
|
|
|
if not status:
|
|
|
|
raise Exception(result)
|
|
|
|
|
|
|
|
self.nsp_name = result['rows'][0]['nspname']
|
|
|
|
self.object_name = result['rows'][0]['relname']
|
|
|
|
else:
|
|
|
|
raise Exception(gettext('Not connected to server or connection with the server has been closed.'))
|
|
|
|
|
|
|
|
def get_filter(self):
|
|
|
|
"""
|
|
|
|
This function returns the filter.
|
|
|
|
"""
|
|
|
|
return self.__row_filter
|
|
|
|
|
|
|
|
def set_filter(self, row_filter):
|
|
|
|
"""
|
|
|
|
This function validates the filter and set the
|
|
|
|
given filter to member variable.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
row_filter: sql query
|
|
|
|
"""
|
|
|
|
|
|
|
|
status, msg = self.validate_filter(row_filter)
|
|
|
|
|
|
|
|
if status:
|
|
|
|
self.__row_filter = row_filter
|
|
|
|
|
|
|
|
return status, msg
|
|
|
|
|
|
|
|
def is_filter_applied(self):
|
|
|
|
"""
|
|
|
|
This function returns True if filter is applied else False.
|
|
|
|
"""
|
|
|
|
if self.__row_filter is None or self.__row_filter == '':
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def remove_filter(self):
|
|
|
|
"""
|
|
|
|
This function remove the filter by setting value to None.
|
|
|
|
"""
|
|
|
|
self.__row_filter = None
|
|
|
|
|
|
|
|
def append_filter(self, row_filter):
|
|
|
|
"""
|
|
|
|
This function will used to get the existing filter and append
|
|
|
|
the given filter.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
row_filter: sql query to append
|
|
|
|
"""
|
|
|
|
|
|
|
|
existing_filter = self.get_filter()
|
|
|
|
|
|
|
|
if existing_filter is None or existing_filter == '':
|
|
|
|
self.__row_filter = row_filter
|
|
|
|
else:
|
|
|
|
self.__row_filter = existing_filter + ' \n AND ' + row_filter
|
|
|
|
|
|
|
|
def validate_filter(self, row_filter):
|
|
|
|
"""
|
|
|
|
This function validates the given filter.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
row_filter: sql syntax to validate
|
|
|
|
"""
|
|
|
|
status = True
|
|
|
|
result = None
|
|
|
|
|
|
|
|
if row_filter is None or row_filter == '':
|
2016-05-06 07:53:48 -05:00
|
|
|
return False, gettext('Filter string is empty.')
|
2016-04-14 09:04:03 -05:00
|
|
|
|
|
|
|
manager = get_driver(PG_DEFAULT_DRIVER).connection_manager(self.sid)
|
|
|
|
conn = manager.connection(did=self.did)
|
|
|
|
|
|
|
|
if conn.connected():
|
|
|
|
sql = render_template("/".join([self.sql_path, 'validate.sql']),
|
|
|
|
nsp_name=self.nsp_name, object_name=self.object_name, row_filter=row_filter)
|
|
|
|
|
|
|
|
status, result = conn.execute_scalar(sql)
|
|
|
|
if not status:
|
|
|
|
result = result.partition("\n")[0]
|
|
|
|
|
|
|
|
return status, result
|
|
|
|
|
|
|
|
|
2017-06-27 08:03:04 -05:00
|
|
|
class FetchedRowTracker(object):
|
|
|
|
"""
|
|
|
|
Keeps track of fetched row count.
|
|
|
|
"""
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
self.fetched_rows = 0
|
|
|
|
|
|
|
|
def get_fetched_row_cnt(self):
|
|
|
|
return self.fetched_rows
|
|
|
|
|
|
|
|
def update_fetched_row_cnt(self, rows_cnt):
|
|
|
|
self.fetched_rows = rows_cnt
|
|
|
|
|
|
|
|
|
|
|
|
class GridCommand(BaseCommand, SQLFilter, FetchedRowTracker):
|
2016-04-14 09:04:03 -05:00
|
|
|
"""
|
|
|
|
class GridCommand(object)
|
|
|
|
|
|
|
|
It is a base class for different object type used by data grid.
|
|
|
|
A different object type must implement this to expose abstract methods.
|
|
|
|
|
|
|
|
Class-level Methods:
|
|
|
|
----------- -------
|
|
|
|
* get_primary_keys()
|
|
|
|
- Derived class can implement there own logic to get the primary keys.
|
|
|
|
|
|
|
|
* save()
|
|
|
|
- Derived class can implement there own logic to save the data into the database.
|
|
|
|
|
|
|
|
* set_limit(limit)
|
|
|
|
- This method sets the limit for SQL query
|
|
|
|
|
|
|
|
* get_limit()
|
|
|
|
- This method returns the limit.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
"""
|
|
|
|
This method is used to call base class init to initialize
|
|
|
|
the data.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
**kwargs : N number of parameters
|
|
|
|
"""
|
|
|
|
BaseCommand.__init__(self, **kwargs)
|
|
|
|
SQLFilter.__init__(self, **kwargs)
|
2017-06-27 08:03:04 -05:00
|
|
|
FetchedRowTracker.__init__(self, **kwargs)
|
2016-04-14 09:04:03 -05:00
|
|
|
|
|
|
|
# Save the connection id, command type
|
|
|
|
self.conn_id = kwargs['conn_id'] if 'conn_id' in kwargs else None
|
|
|
|
self.cmd_type = kwargs['cmd_type'] if 'cmd_type' in kwargs else None
|
|
|
|
self.limit = -1
|
|
|
|
|
|
|
|
if self.cmd_type == VIEW_FIRST_100_ROWS or self.cmd_type == VIEW_LAST_100_ROWS:
|
|
|
|
self.limit = 100
|
|
|
|
|
2017-06-27 08:03:04 -05:00
|
|
|
def get_primary_keys(self, *args, **kwargs):
|
2016-04-14 09:04:03 -05:00
|
|
|
return None, None
|
|
|
|
|
2017-06-27 08:03:04 -05:00
|
|
|
def save(self, changed_data, default_conn=None):
|
2016-05-06 07:53:48 -05:00
|
|
|
return forbidden(errmsg=gettext("Data cannot be saved for the current object."))
|
2016-04-14 09:04:03 -05:00
|
|
|
|
|
|
|
def get_limit(self):
|
|
|
|
"""
|
|
|
|
This function returns the limit for the SQL query.
|
|
|
|
"""
|
|
|
|
return self.limit
|
|
|
|
|
|
|
|
def set_limit(self, limit):
|
|
|
|
"""
|
|
|
|
This function sets the limit for the SQL query
|
|
|
|
Args:
|
|
|
|
limit: limit to be set for SQL.
|
|
|
|
"""
|
|
|
|
self.limit = limit
|
|
|
|
|
|
|
|
|
|
|
|
class TableCommand(GridCommand):
|
|
|
|
"""
|
|
|
|
class TableCommand(GridCommand)
|
|
|
|
|
|
|
|
It is a derived class for Table type.
|
|
|
|
"""
|
|
|
|
object_type = 'table'
|
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
"""
|
|
|
|
This method calls the __init__ method of the base class
|
|
|
|
to get the proper object name.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
**kwargs : N number of parameters
|
|
|
|
"""
|
|
|
|
|
|
|
|
# call base class init to fetch the table name
|
|
|
|
super(TableCommand, self).__init__(**kwargs)
|
|
|
|
|
2017-06-27 08:03:04 -05:00
|
|
|
def get_sql(self, default_conn=None):
|
2016-04-14 09:04:03 -05:00
|
|
|
"""
|
|
|
|
This method is used to create a proper SQL query
|
|
|
|
to fetch the data for the specified table
|
|
|
|
"""
|
|
|
|
|
|
|
|
# Fetch the primary keys for the table
|
2017-06-27 08:03:04 -05:00
|
|
|
pk_names, primary_keys = self.get_primary_keys(default_conn)
|
2016-04-14 09:04:03 -05:00
|
|
|
|
2017-12-13 04:28:31 -06:00
|
|
|
# Fetch OIDs status
|
|
|
|
has_oids = self.has_oids(default_conn)
|
|
|
|
|
2016-04-14 09:04:03 -05:00
|
|
|
sql_filter = self.get_filter()
|
|
|
|
|
|
|
|
if sql_filter is None:
|
|
|
|
sql = render_template("/".join([self.sql_path, 'objectquery.sql']), object_name=self.object_name,
|
|
|
|
nsp_name=self.nsp_name, pk_names=pk_names, cmd_type=self.cmd_type,
|
2017-12-13 04:28:31 -06:00
|
|
|
limit=self.limit, primary_keys=primary_keys, has_oids=has_oids)
|
2016-04-14 09:04:03 -05:00
|
|
|
else:
|
|
|
|
sql = render_template("/".join([self.sql_path, 'objectquery.sql']), object_name=self.object_name,
|
|
|
|
nsp_name=self.nsp_name, pk_names=pk_names, cmd_type=self.cmd_type,
|
2017-12-13 04:28:31 -06:00
|
|
|
sql_filter=sql_filter, limit=self.limit, primary_keys=primary_keys,
|
|
|
|
has_oids=has_oids)
|
2016-04-14 09:04:03 -05:00
|
|
|
|
|
|
|
return sql
|
|
|
|
|
2017-06-27 08:03:04 -05:00
|
|
|
def get_primary_keys(self, default_conn=None):
|
2016-04-14 09:04:03 -05:00
|
|
|
"""
|
|
|
|
This function is used to fetch the primary key columns.
|
|
|
|
"""
|
2016-08-04 05:54:36 -05:00
|
|
|
driver = get_driver(PG_DEFAULT_DRIVER)
|
2017-06-27 08:03:04 -05:00
|
|
|
if default_conn is None:
|
|
|
|
manager = driver.connection_manager(self.sid)
|
|
|
|
conn = manager.connection(did=self.did, conn_id=self.conn_id)
|
|
|
|
else:
|
|
|
|
conn = default_conn
|
2016-04-14 09:04:03 -05:00
|
|
|
|
|
|
|
pk_names = ''
|
2017-05-26 10:04:32 -05:00
|
|
|
primary_keys = OrderedDict()
|
2016-04-14 09:04:03 -05:00
|
|
|
|
|
|
|
if conn.connected():
|
|
|
|
|
|
|
|
# Fetch the primary key column names
|
|
|
|
query = render_template("/".join([self.sql_path, 'primary_keys.sql']), obj_id=self.obj_id)
|
|
|
|
|
|
|
|
status, result = conn.execute_dict(query)
|
|
|
|
if not status:
|
|
|
|
raise Exception(result)
|
|
|
|
|
|
|
|
for row in result['rows']:
|
2016-08-04 05:54:36 -05:00
|
|
|
pk_names += driver.qtIdent(conn, row['attname']) + ','
|
2016-04-14 09:04:03 -05:00
|
|
|
primary_keys[row['attname']] = row['typname']
|
|
|
|
|
|
|
|
if pk_names != '':
|
|
|
|
# Remove last character from the string
|
|
|
|
pk_names = pk_names[:-1]
|
|
|
|
else:
|
|
|
|
raise Exception(gettext('Not connected to server or connection with the server has been closed.'))
|
|
|
|
|
|
|
|
return pk_names, primary_keys
|
|
|
|
|
|
|
|
def can_edit(self):
|
|
|
|
return True
|
|
|
|
|
|
|
|
def can_filter(self):
|
|
|
|
return True
|
|
|
|
|
2017-12-13 04:28:31 -06:00
|
|
|
def has_oids(self, default_conn=None):
|
|
|
|
"""
|
|
|
|
This function checks whether the table has oids or not.
|
|
|
|
"""
|
|
|
|
driver = get_driver(PG_DEFAULT_DRIVER)
|
|
|
|
if default_conn is None:
|
|
|
|
manager = driver.connection_manager(self.sid)
|
|
|
|
conn = manager.connection(did=self.did, conn_id=self.conn_id)
|
|
|
|
else:
|
|
|
|
conn = default_conn
|
|
|
|
|
|
|
|
if conn.connected():
|
|
|
|
|
|
|
|
# Fetch the table oids status
|
|
|
|
query = render_template("/".join([self.sql_path, 'has_oids.sql']), obj_id=self.obj_id)
|
|
|
|
|
|
|
|
status, has_oids = conn.execute_scalar(query)
|
|
|
|
if not status:
|
|
|
|
raise Exception(has_oids)
|
|
|
|
|
|
|
|
else:
|
|
|
|
raise Exception(gettext('Not connected to server or connection with the server has been closed.'))
|
|
|
|
|
|
|
|
return has_oids
|
|
|
|
|
2017-06-27 08:03:04 -05:00
|
|
|
def save(self,
|
|
|
|
changed_data,
|
|
|
|
columns_info,
|
|
|
|
client_primary_key='__temp_PK',
|
|
|
|
default_conn=None):
|
2016-04-14 09:04:03 -05:00
|
|
|
"""
|
|
|
|
This function is used to save the data into the database.
|
|
|
|
Depending on condition it will either update or insert the
|
|
|
|
new row into the database.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
changed_data: Contains data to be saved
|
2017-06-27 08:03:04 -05:00
|
|
|
columns_info:
|
|
|
|
default_conn:
|
|
|
|
client_primary_key:
|
2016-04-14 09:04:03 -05:00
|
|
|
"""
|
2017-06-27 08:03:04 -05:00
|
|
|
driver = get_driver(PG_DEFAULT_DRIVER)
|
|
|
|
if default_conn is None:
|
|
|
|
manager = driver.connection_manager(self.sid)
|
|
|
|
conn = manager.connection(did=self.did, conn_id=self.conn_id)
|
|
|
|
else:
|
|
|
|
conn = default_conn
|
2016-04-14 09:04:03 -05:00
|
|
|
|
|
|
|
status = False
|
|
|
|
res = None
|
|
|
|
query_res = dict()
|
|
|
|
count = 0
|
2016-09-02 10:05:00 -05:00
|
|
|
list_of_rowid = []
|
2017-09-18 01:37:15 -05:00
|
|
|
operations = ('added', 'updated', 'deleted')
|
|
|
|
list_of_sql = {}
|
2016-09-02 10:05:00 -05:00
|
|
|
_rowid = None
|
2016-04-14 09:04:03 -05:00
|
|
|
|
|
|
|
if conn.connected():
|
|
|
|
|
|
|
|
# Start the transaction
|
|
|
|
conn.execute_void('BEGIN;')
|
|
|
|
|
|
|
|
# Iterate total number of records to be updated/inserted
|
2016-08-29 09:47:01 -05:00
|
|
|
for of_type in changed_data:
|
2017-03-01 05:28:51 -06:00
|
|
|
# No need to go further if its not add/update/delete operation
|
2017-09-18 01:37:15 -05:00
|
|
|
if of_type not in operations:
|
2017-03-01 05:28:51 -06:00
|
|
|
continue
|
|
|
|
# if no data to be save then continue
|
2016-08-29 09:47:01 -05:00
|
|
|
if len(changed_data[of_type]) < 1:
|
2016-04-14 09:04:03 -05:00
|
|
|
continue
|
|
|
|
|
2017-12-13 04:28:31 -06:00
|
|
|
|
2017-06-27 08:03:04 -05:00
|
|
|
column_type = {}
|
2017-07-03 09:49:59 -05:00
|
|
|
column_data = {}
|
2017-06-27 08:03:04 -05:00
|
|
|
for each_col in columns_info:
|
|
|
|
if (
|
|
|
|
columns_info[each_col]['not_null'] and
|
|
|
|
not columns_info[each_col][
|
|
|
|
'has_default_val']
|
|
|
|
):
|
|
|
|
column_data[each_col] = None
|
|
|
|
column_type[each_col] =\
|
|
|
|
columns_info[each_col]['type_name']
|
|
|
|
else:
|
|
|
|
column_type[each_col] = \
|
|
|
|
columns_info[each_col]['type_name']
|
|
|
|
|
2016-08-29 09:47:01 -05:00
|
|
|
# For newly added rows
|
|
|
|
if of_type == 'added':
|
2017-12-13 04:28:31 -06:00
|
|
|
# Python dict does not honour the inserted item order
|
|
|
|
# So to insert data in the order, we need to make ordered list of added index
|
|
|
|
# We don't need this mechanism in updated/deleted rows as
|
|
|
|
# it does not matter in those operations
|
|
|
|
added_index = OrderedDict(sorted(changed_data['added_index'].items(),
|
|
|
|
key=lambda x: int(x[0])))
|
2017-09-18 01:37:15 -05:00
|
|
|
list_of_sql[of_type] = []
|
2017-05-12 04:53:57 -05:00
|
|
|
|
|
|
|
# When new rows are added, only changed columns data is
|
|
|
|
# sent from client side. But if column is not_null and has
|
|
|
|
# no_default_value, set column to blank, instead
|
|
|
|
# of not null which is set by default.
|
|
|
|
column_data = {}
|
|
|
|
pk_names, primary_keys = self.get_primary_keys()
|
2017-12-13 04:28:31 -06:00
|
|
|
has_oids = 'oid' in column_type
|
2017-05-12 04:53:57 -05:00
|
|
|
|
2017-12-13 04:28:31 -06:00
|
|
|
for each_row in added_index:
|
|
|
|
# Get the row index to match with the added rows dict key
|
|
|
|
tmp_row_index = added_index[each_row]
|
|
|
|
data = changed_data[of_type][tmp_row_index]['data']
|
2016-08-29 09:47:01 -05:00
|
|
|
# Remove our unique tracking key
|
2017-06-27 08:03:04 -05:00
|
|
|
data.pop(client_primary_key, None)
|
2017-05-27 13:51:02 -05:00
|
|
|
data.pop('is_row_copied', None)
|
2017-06-27 08:03:04 -05:00
|
|
|
list_of_rowid.append(data.get(client_primary_key))
|
2017-03-01 05:28:51 -06:00
|
|
|
|
2017-06-27 08:03:04 -05:00
|
|
|
# Update columns value with columns having
|
|
|
|
# not_null=False and has no default value
|
2017-05-12 04:53:57 -05:00
|
|
|
column_data.update(data)
|
|
|
|
|
2016-08-29 09:47:01 -05:00
|
|
|
sql = render_template("/".join([self.sql_path, 'insert.sql']),
|
2017-05-12 04:53:57 -05:00
|
|
|
data_to_be_saved=column_data,
|
2016-08-29 09:47:01 -05:00
|
|
|
primary_keys=None,
|
|
|
|
object_name=self.object_name,
|
|
|
|
nsp_name=self.nsp_name,
|
2017-05-12 04:53:57 -05:00
|
|
|
data_type=column_type,
|
2017-12-13 04:28:31 -06:00
|
|
|
pk_names=pk_names,
|
|
|
|
has_oids=has_oids)
|
|
|
|
select_sql = render_template("/".join([self.sql_path, 'select.sql']),
|
|
|
|
object_name=self.object_name,
|
|
|
|
nsp_name=self.nsp_name,
|
|
|
|
pk_names=pk_names.split(",") if pk_names else None,
|
|
|
|
has_oids=has_oids)
|
|
|
|
list_of_sql[of_type].append({'sql': sql, 'data': data,
|
|
|
|
'client_row': tmp_row_index,
|
|
|
|
'select_sql': select_sql})
|
2017-05-15 09:04:16 -05:00
|
|
|
# Reset column data
|
|
|
|
column_data = {}
|
2016-08-29 09:47:01 -05:00
|
|
|
|
|
|
|
# For updated rows
|
|
|
|
elif of_type == 'updated':
|
2017-09-18 01:37:15 -05:00
|
|
|
list_of_sql[of_type] = []
|
2016-08-29 09:47:01 -05:00
|
|
|
for each_row in changed_data[of_type]:
|
2017-06-27 08:03:04 -05:00
|
|
|
data = changed_data[of_type][each_row]['data']
|
|
|
|
pk = changed_data[of_type][each_row]['primary_keys']
|
2016-08-29 09:47:01 -05:00
|
|
|
sql = render_template("/".join([self.sql_path, 'update.sql']),
|
|
|
|
data_to_be_saved=data,
|
|
|
|
primary_keys=pk,
|
|
|
|
object_name=self.object_name,
|
|
|
|
nsp_name=self.nsp_name,
|
2017-06-27 08:03:04 -05:00
|
|
|
data_type=column_type)
|
2017-09-18 01:37:15 -05:00
|
|
|
list_of_sql[of_type].append({'sql': sql, 'data': data})
|
|
|
|
list_of_rowid.append(data.get(client_primary_key))
|
2016-08-29 09:47:01 -05:00
|
|
|
|
|
|
|
# For deleted rows
|
|
|
|
elif of_type == 'deleted':
|
2017-09-18 01:37:15 -05:00
|
|
|
list_of_sql[of_type] = []
|
2016-09-16 10:46:58 -05:00
|
|
|
is_first = True
|
|
|
|
rows_to_delete = []
|
|
|
|
keys = None
|
|
|
|
no_of_keys = None
|
2016-08-29 09:47:01 -05:00
|
|
|
for each_row in changed_data[of_type]:
|
2016-09-16 10:46:58 -05:00
|
|
|
rows_to_delete.append(changed_data[of_type][each_row])
|
|
|
|
# Fetch the keys for SQL generation
|
|
|
|
if is_first:
|
2017-06-27 08:03:04 -05:00
|
|
|
# We need to covert dict_keys to normal list in
|
|
|
|
# Python3
|
|
|
|
# In Python2, it's already a list & We will also
|
|
|
|
# fetch column names using index
|
|
|
|
keys = list(changed_data[of_type][each_row].keys())
|
|
|
|
|
2016-09-16 10:46:58 -05:00
|
|
|
no_of_keys = len(keys)
|
|
|
|
is_first = False
|
2017-03-01 05:28:51 -06:00
|
|
|
# Map index with column name for each row
|
|
|
|
for row in rows_to_delete:
|
|
|
|
for k, v in row.items():
|
2017-06-27 08:03:04 -05:00
|
|
|
# Set primary key with label & delete index based
|
|
|
|
# mapped key
|
2017-03-01 05:28:51 -06:00
|
|
|
try:
|
2017-03-24 08:32:56 -05:00
|
|
|
row[changed_data['columns'][int(k)]['name']] = v
|
2017-03-01 05:28:51 -06:00
|
|
|
except ValueError:
|
|
|
|
continue
|
|
|
|
del row[k]
|
2016-09-16 10:46:58 -05:00
|
|
|
|
|
|
|
sql = render_template("/".join([self.sql_path, 'delete.sql']),
|
|
|
|
data=rows_to_delete,
|
|
|
|
primary_key_labels=keys,
|
|
|
|
no_of_keys=no_of_keys,
|
|
|
|
object_name=self.object_name,
|
|
|
|
nsp_name=self.nsp_name)
|
2017-09-18 01:37:15 -05:00
|
|
|
list_of_sql[of_type].append({'sql': sql, 'data': {}})
|
|
|
|
|
|
|
|
for opr, sqls in list_of_sql.items():
|
|
|
|
for item in sqls:
|
|
|
|
if item['sql']:
|
2017-12-13 04:28:31 -06:00
|
|
|
row_added = None
|
2017-09-18 01:37:15 -05:00
|
|
|
|
2017-12-13 04:28:31 -06:00
|
|
|
# Fetch oids/primary keys
|
|
|
|
if 'select_sql' in item and item['select_sql']:
|
|
|
|
status, res = conn.execute_dict(
|
|
|
|
item['sql'], item['data'])
|
|
|
|
else:
|
|
|
|
status, res = conn.execute_void(
|
|
|
|
item['sql'], item['data'])
|
2017-09-18 01:37:15 -05:00
|
|
|
|
|
|
|
if not status:
|
|
|
|
conn.execute_void('ROLLBACK;')
|
|
|
|
# If we roll backed every thing then update the message for
|
|
|
|
# each sql query.
|
|
|
|
for val in query_res:
|
|
|
|
if query_res[val]['status']:
|
|
|
|
query_res[val]['result'] = 'Transaction ROLLBACK'
|
|
|
|
|
|
|
|
# If list is empty set rowid to 1
|
|
|
|
try:
|
|
|
|
_rowid = list_of_rowid[count] if list_of_rowid else 1
|
|
|
|
except Exception:
|
|
|
|
_rowid = 0
|
|
|
|
|
|
|
|
return status, res, query_res, _rowid
|
2017-12-13 04:28:31 -06:00
|
|
|
|
|
|
|
# Select added row from the table
|
|
|
|
if 'select_sql' in item:
|
|
|
|
status, sel_res = conn.execute_dict(
|
|
|
|
item['select_sql'], res['rows'][0])
|
|
|
|
|
|
|
|
if not status:
|
|
|
|
conn.execute_void('ROLLBACK;')
|
|
|
|
# If we roll backed every thing then update the message for
|
|
|
|
# each sql query.
|
|
|
|
for val in query_res:
|
|
|
|
if query_res[val]['status']:
|
|
|
|
query_res[val]['result'] = 'Transaction ROLLBACK'
|
|
|
|
|
|
|
|
# If list is empty set rowid to 1
|
|
|
|
try:
|
|
|
|
_rowid = list_of_rowid[count] if list_of_rowid else 1
|
|
|
|
except Exception:
|
|
|
|
_rowid = 0
|
|
|
|
|
|
|
|
return status, sel_res, query_res, _rowid
|
|
|
|
|
|
|
|
if 'rows' in sel_res and len(sel_res['rows']) > 0:
|
|
|
|
row_added = {item['client_row']: sel_res['rows'][0]}
|
|
|
|
|
|
|
|
rows_affected = conn.rows_affected()
|
|
|
|
|
|
|
|
# store the result of each query in dictionary
|
|
|
|
query_res[count] = {'status': status, 'result': None if row_added else res,
|
|
|
|
'sql': sql, 'rows_affected': rows_affected,
|
|
|
|
'row_added': row_added}
|
|
|
|
|
2017-09-18 01:37:15 -05:00
|
|
|
count += 1
|
2016-04-14 09:04:03 -05:00
|
|
|
|
|
|
|
# Commit the transaction if there is no error found
|
|
|
|
conn.execute_void('COMMIT;')
|
|
|
|
|
2016-09-02 10:05:00 -05:00
|
|
|
return status, res, query_res, _rowid
|
2016-04-14 09:04:03 -05:00
|
|
|
|
|
|
|
|
|
|
|
class ViewCommand(GridCommand):
|
|
|
|
"""
|
|
|
|
class ViewCommand(GridCommand)
|
|
|
|
|
|
|
|
It is a derived class for View type.
|
|
|
|
"""
|
|
|
|
object_type = 'view'
|
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
"""
|
|
|
|
This method calls the __init__ method of the base class
|
|
|
|
to get the proper object name.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
**kwargs : N number of parameters
|
|
|
|
"""
|
|
|
|
|
|
|
|
# call base class init to fetch the table name
|
|
|
|
super(ViewCommand, self).__init__(**kwargs)
|
|
|
|
|
2017-06-27 08:03:04 -05:00
|
|
|
def get_sql(self, default_conn=None):
|
2016-04-14 09:04:03 -05:00
|
|
|
"""
|
|
|
|
This method is used to create a proper SQL query
|
|
|
|
to fetch the data for the specified view
|
|
|
|
"""
|
|
|
|
sql_filter = self.get_filter()
|
|
|
|
|
|
|
|
if sql_filter is None:
|
|
|
|
sql = render_template("/".join([self.sql_path, 'objectquery.sql']), object_name=self.object_name,
|
|
|
|
nsp_name=self.nsp_name, cmd_type=self.cmd_type,
|
|
|
|
limit=self.limit)
|
|
|
|
else:
|
|
|
|
sql = render_template("/".join([self.sql_path, 'objectquery.sql']), object_name=self.object_name,
|
|
|
|
nsp_name=self.nsp_name, cmd_type=self.cmd_type,
|
|
|
|
sql_filter=sql_filter, limit=self.limit)
|
|
|
|
|
|
|
|
return sql
|
|
|
|
|
|
|
|
def can_edit(self):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def can_filter(self):
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2016-05-24 13:32:32 -05:00
|
|
|
class MViewCommand(ViewCommand):
|
|
|
|
"""
|
|
|
|
class MViewCommand(ViewCommand)
|
|
|
|
|
|
|
|
It is a derived class for View type has
|
|
|
|
same functionality of View
|
|
|
|
"""
|
|
|
|
object_type = 'mview'
|
|
|
|
|
|
|
|
|
2016-04-14 09:04:03 -05:00
|
|
|
class ForeignTableCommand(GridCommand):
|
|
|
|
"""
|
|
|
|
class ForeignTableCommand(GridCommand)
|
|
|
|
|
|
|
|
It is a derived class for ForeignTable type.
|
|
|
|
"""
|
2017-09-18 08:32:17 -05:00
|
|
|
object_type = 'foreign_table'
|
2016-04-14 09:04:03 -05:00
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
"""
|
|
|
|
This method calls the __init__ method of the base class
|
|
|
|
to get the proper object name.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
**kwargs : N number of parameters
|
|
|
|
"""
|
|
|
|
|
|
|
|
# call base class init to fetch the table name
|
|
|
|
super(ForeignTableCommand, self).__init__(**kwargs)
|
|
|
|
|
2017-06-27 08:03:04 -05:00
|
|
|
def get_sql(self, default_conn=None):
|
2016-04-14 09:04:03 -05:00
|
|
|
"""
|
|
|
|
This method is used to create a proper SQL query
|
|
|
|
to fetch the data for the specified foreign table
|
|
|
|
"""
|
|
|
|
sql_filter = self.get_filter()
|
|
|
|
|
|
|
|
if sql_filter is None:
|
|
|
|
sql = render_template("/".join([self.sql_path, 'objectquery.sql']), object_name=self.object_name,
|
|
|
|
nsp_name=self.nsp_name, cmd_type=self.cmd_type,
|
|
|
|
limit=self.limit)
|
|
|
|
else:
|
|
|
|
sql = render_template("/".join([self.sql_path, 'objectquery.sql']), object_name=self.object_name,
|
|
|
|
nsp_name=self.nsp_name, cmd_type=self.cmd_type,
|
|
|
|
sql_filter=sql_filter, limit=self.limit)
|
|
|
|
|
|
|
|
return sql
|
|
|
|
|
|
|
|
def can_edit(self):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def can_filter(self):
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
class CatalogCommand(GridCommand):
|
|
|
|
"""
|
|
|
|
class CatalogCommand(GridCommand)
|
|
|
|
|
|
|
|
It is a derived class for CatalogObject type.
|
|
|
|
"""
|
|
|
|
object_type = 'catalog_object'
|
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
"""
|
|
|
|
This method calls the __init__ method of the base class
|
|
|
|
to get the proper object name.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
**kwargs : N number of parameters
|
|
|
|
"""
|
|
|
|
|
|
|
|
# call base class init to fetch the table name
|
|
|
|
super(CatalogCommand, self).__init__(**kwargs)
|
|
|
|
|
2017-06-27 08:03:04 -05:00
|
|
|
def get_sql(self, default_conn=None):
|
2016-04-14 09:04:03 -05:00
|
|
|
"""
|
|
|
|
This method is used to create a proper SQL query
|
|
|
|
to fetch the data for the specified catalog object
|
|
|
|
"""
|
|
|
|
sql_filter = self.get_filter()
|
|
|
|
|
|
|
|
if sql_filter is None:
|
|
|
|
sql = render_template("/".join([self.sql_path, 'objectquery.sql']), object_name=self.object_name,
|
|
|
|
nsp_name=self.nsp_name, cmd_type=self.cmd_type,
|
|
|
|
limit=self.limit)
|
|
|
|
else:
|
|
|
|
sql = render_template("/".join([self.sql_path, 'objectquery.sql']), object_name=self.object_name,
|
|
|
|
nsp_name=self.nsp_name, cmd_type=self.cmd_type,
|
|
|
|
sql_filter=sql_filter, limit=self.limit)
|
|
|
|
|
|
|
|
return sql
|
|
|
|
|
|
|
|
def can_edit(self):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def can_filter(self):
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2017-06-27 08:03:04 -05:00
|
|
|
class QueryToolCommand(BaseCommand, FetchedRowTracker):
|
2016-04-14 09:04:03 -05:00
|
|
|
"""
|
|
|
|
class QueryToolCommand(BaseCommand)
|
|
|
|
|
|
|
|
It is a derived class for Query Tool.
|
|
|
|
"""
|
|
|
|
object_type = 'query_tool'
|
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
# call base class init to fetch the table name
|
2017-06-27 08:03:04 -05:00
|
|
|
|
|
|
|
BaseCommand.__init__(self, **kwargs)
|
|
|
|
FetchedRowTracker.__init__(self, **kwargs)
|
2016-04-14 09:04:03 -05:00
|
|
|
|
|
|
|
self.conn_id = None
|
|
|
|
self.auto_rollback = False
|
|
|
|
self.auto_commit = True
|
|
|
|
|
2017-06-27 08:03:04 -05:00
|
|
|
def get_sql(self, default_conn=None):
|
2016-04-14 09:04:03 -05:00
|
|
|
return None
|
|
|
|
|
|
|
|
def can_edit(self):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def can_filter(self):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def set_connection_id(self, conn_id):
|
|
|
|
self.conn_id = conn_id
|
|
|
|
|
|
|
|
def set_auto_rollback(self, auto_rollback):
|
|
|
|
self.auto_rollback = auto_rollback
|
|
|
|
|
|
|
|
def set_auto_commit(self, auto_commit):
|
|
|
|
self.auto_commit = auto_commit
|