On Sat, Dec 2, 2017 at 10:42 AM, Dave Page <[email protected]> wrote:
> Hi
>
> On Thursday, November 30, 2017, Khushboo Vashi <
> [email protected]> wrote:
>
>> Hi,
>>
>> Please find the attached updated patch.
>>
>> On Mon, Nov 27, 2017 at 5:15 PM, Dave Page <[email protected]> wrote:
>>
>>> Hi
>>>
>>> On Thu, Nov 23, 2017 at 1:28 PM, Khushboo Vashi <
>>> [email protected]> wrote:
>>>
>>>> Hi,
>>>>
>>>> Please find the attached patch for RM #2849: Allow editing of data on
>>>> tables with OIDs but no primary key.
>>>>
>>>
>>> I like that if I add a new row or rows and hit Save, the OIDs are
>>> fetched immediately. However;
>>>
>>> - It marks the row as read-only. We do that currently because we don't
>>> return the key info on Save, and thus require a Refresh before any further
>>> editing. However, if we have the OID, we can edit again immediately.
>>>
>>> Fixed
>>
>>> - If we can return the new OIDs on Save, can't we do the same for
>>> primary key values? That would be consistent with OIDs, and would remove
>>> the need to disable rows, leading to a much nicer use experience I think.
>>>
>>> Implemented
>>
>
> This looks great, however there is one small issue I spotted; it looks
> like we're inserting rows in a random order. For example, in the screenshot
> attached, the last 5 rows were added in the order seen in the key1 column,
> and then I hit Save and got the id values returned. Is that caused by
> something we're doing, or the database server?
>
> The root cause of the issue is, Python dictionary does not preserve the
order. To fix this issue I have manually sorted the added rows index and
stored it into OrderedDict.
Please find the attached updated patch.
> Thanks!
>
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>
>
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/nodes.sql
index f3353d6..2c3e573 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/nodes.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/9.2_plus/nodes.sql
@@ -15,9 +15,13 @@ WHERE
{% if clid %}
AND att.attnum = {{ clid|qtLiteral }}
{% endif %}
- {### To show system objects ###}
- {% if not show_sys_objects %}
+{### To show system objects ###}
+{% if not show_sys_objects and not has_oids %}
AND att.attnum > 0
- {% endif %}
+{% endif %}
+{### To show oids in view data ###}
+{% if has_oids %}
+ AND (att.attnum > 0 OR (att.attname = 'oid' AND att.attnum < 0))
+{% endif %}
AND att.attisdropped IS FALSE
ORDER BY att.attnum
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/default/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/default/nodes.sql
index 4f1de2a..584f7b1 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/default/nodes.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/column/sql/default/nodes.sql
@@ -16,8 +16,12 @@ WHERE
AND att.attnum = {{ clid|qtLiteral }}
{% endif %}
{### To show system objects ###}
-{% if not show_sys_objects %}
+{% if not show_sys_objects and not has_oids %}
AND att.attnum > 0
{% endif %}
+{### To show oids in view data ###}
+{% if has_oids %}
+ AND (att.attnum > 0 OR (att.attname = 'oid' AND att.attnum < 0))
+{% endif %}
AND att.attisdropped IS FALSE
ORDER BY att.attnum
diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py
index 8dcb444..363d6f8 100644
--- a/web/pgadmin/tools/sqleditor/__init__.py
+++ b/web/pgadmin/tools/sqleditor/__init__.py
@@ -433,6 +433,9 @@ def start_view_data(trans_id):
sql = trans_obj.get_sql()
pk_names, primary_keys = trans_obj.get_primary_keys(default_conn)
+ # Fetch OIDs status
+ has_oids = trans_obj.has_oids(default_conn)
+
# Fetch the applied filter.
filter_applied = trans_obj.is_filter_applied()
@@ -444,6 +447,10 @@ def start_view_data(trans_id):
# Store the primary keys to the session object
session_obj['primary_keys'] = primary_keys
+
+ # Store the OIDs status into session object
+ session_obj['has_oids'] = has_oids
+
update_session_grid_transaction(trans_id, session_obj)
# Execute sql asynchronously
@@ -655,6 +662,8 @@ def poll(trans_id):
types = {}
client_primary_key = None
rset = None
+ has_oids = False
+ oids = None
# Check the transaction and connection status
status, error_msg, conn, trans_obj, session_obj = check_transaction_status(trans_id)
@@ -680,6 +689,11 @@ def poll(trans_id):
if 'primary_keys' in session_obj:
primary_keys = session_obj['primary_keys']
+ if 'has_oids' in session_obj:
+ has_oids = session_obj['has_oids']
+ if has_oids:
+ oids = {'oid': 'oid'}
+
# Fetch column information
columns_info = conn.get_column_info()
client_primary_key = generate_client_primary_key_name(
@@ -698,7 +712,8 @@ def poll(trans_id):
SQL = render_template("/".join([template_path,
'nodes.sql']),
- tid=command_obj.obj_id)
+ tid=command_obj.obj_id,
+ has_oids=True)
# rows with attribute not_null
colst, rset = conn.execute_2darray(SQL)
if not colst:
@@ -811,7 +826,9 @@ def poll(trans_id):
'colinfo': columns_info,
'primary_keys': primary_keys,
'types': types,
- 'client_primary_key': client_primary_key
+ 'client_primary_key': client_primary_key,
+ 'has_oids': has_oids,
+ 'oids': oids
}
)
@@ -945,7 +962,7 @@ def save(trans_id):
and trans_obj is not None and session_obj is not None:
# If there is no primary key found then return from the function.
- if len(session_obj['primary_keys']) <= 0 or len(changed_data) <= 0:
+ if (len(session_obj['primary_keys']) <= 0 or len(changed_data) <= 0) and 'has_oids' not in session_obj:
return make_json_response(
data={
'status': False,
diff --git a/web/pgadmin/tools/sqleditor/command.py b/web/pgadmin/tools/sqleditor/command.py
index d09bf2b..3fe4cea 100644
--- a/web/pgadmin/tools/sqleditor/command.py
+++ b/web/pgadmin/tools/sqleditor/command.py
@@ -364,16 +364,20 @@ class TableCommand(GridCommand):
# Fetch the primary keys for the table
pk_names, primary_keys = self.get_primary_keys(default_conn)
+ # Fetch OIDs status
+ has_oids = self.has_oids(default_conn)
+
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,
- limit=self.limit, primary_keys=primary_keys)
+ limit=self.limit, primary_keys=primary_keys, has_oids=has_oids)
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,
- sql_filter=sql_filter, limit=self.limit, primary_keys=primary_keys)
+ sql_filter=sql_filter, limit=self.limit, primary_keys=primary_keys,
+ has_oids=has_oids)
return sql
@@ -418,6 +422,31 @@ class TableCommand(GridCommand):
def can_filter(self):
return True
+ 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
+
def save(self,
changed_data,
columns_info,
@@ -464,6 +493,7 @@ class TableCommand(GridCommand):
if len(changed_data[of_type]) < 1:
continue
+
column_type = {}
column_data = {}
for each_col in columns_info:
@@ -481,6 +511,12 @@ class TableCommand(GridCommand):
# For newly added rows
if of_type == 'added':
+ # 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])))
list_of_sql[of_type] = []
# When new rows are added, only changed columns data is
@@ -489,9 +525,12 @@ class TableCommand(GridCommand):
# of not null which is set by default.
column_data = {}
pk_names, primary_keys = self.get_primary_keys()
+ has_oids = 'oid' in column_type
- for each_row in changed_data[of_type]:
- data = changed_data[of_type][each_row]['data']
+ 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']
# Remove our unique tracking key
data.pop(client_primary_key, None)
data.pop('is_row_copied', None)
@@ -507,8 +546,11 @@ class TableCommand(GridCommand):
object_name=self.object_name,
nsp_name=self.nsp_name,
data_type=column_type,
- pk_names=pk_names)
- list_of_sql[of_type].append({'sql': sql, 'data': data})
+ pk_names=pk_names,
+ has_oids=has_oids)
+ list_of_sql[of_type].append({'sql': sql, 'data': data,
+ 'has_oids': has_oids, 'client_row': tmp_row_index,
+ 'ret_ids': True})
# Reset column data
column_data = {}
@@ -568,13 +610,21 @@ class TableCommand(GridCommand):
for opr, sqls in list_of_sql.items():
for item in sqls:
if item['sql']:
- status, res = conn.execute_void(
- item['sql'], item['data'])
- rows_affected = conn.rows_affected()
-
- # store the result of each query in dictionary
- query_res[count] = {'status': status, 'result': res,
- 'sql': sql, 'rows_affected': rows_affected}
+ oids = None
+ pk_values = None
+ # Send oids/primary keys
+ if ('has_oids' in item and item['has_oids']) or\
+ ('ret_ids' in item and item['ret_ids']):
+ status, res = conn.execute_dict(
+ item['sql'], item['data'])
+
+ if 'has_oids' in item and item['has_oids']: # Get OIDs
+ oids = {item['client_row']: res['rows'][0]}
+ elif res and 'rows' in res and len(res['rows']) > 0: # Get Primary Keys
+ pk_values = {item['client_row']: res['rows'][0]}
+ else:
+ status, res = conn.execute_void(
+ item['sql'], item['data'])
if not status:
conn.execute_void('ROLLBACK;')
@@ -591,6 +641,14 @@ class TableCommand(GridCommand):
_rowid = 0
return status, res, query_res, _rowid
+
+ rows_affected = conn.rows_affected()
+
+ # store the result of each query in dictionary
+ query_res[count] = {'status': status, 'result': None if oids or pk_values else res,
+ 'sql': sql, 'rows_affected': rows_affected,
+ 'oids': oids, 'pk_values': pk_values}
+
count += 1
# Commit the transaction if there is no error found
diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
index 8c1a82c..ca4d043 100644
--- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
@@ -599,7 +599,10 @@ define('tools.querytool', [
options['width'] = column_size[table_name][c.name];
}
// If grid is editable then add editor else make it readonly
- if (c.cell == 'Json') {
+ if (c.cell == 'oid') {
+ options['editor'] = null;
+ }
+ else if (c.cell == 'Json') {
options['editor'] = is_editable ? Slick.Editors.JsonText
: Slick.Editors.ReadOnlyJsonText;
options['formatter'] = c.is_array ? Slick.Formatters.JsonStringArray : Slick.Formatters.JsonString;
@@ -688,13 +691,14 @@ define('tools.querytool', [
grid.registerPlugin(gridSelector);
var editor_data = {
- keys: self.handler.primary_keys,
+ keys: (_.isEmpty(self.handler.primary_keys) && self.handler.has_oids) ? self.handler.oids : self.handler.primary_keys,
vals: collection,
columns: columns,
grid: grid,
selection: grid.getSelectionModel(),
editor: self,
- client_primary_key: self.client_primary_key
+ client_primary_key: self.client_primary_key,
+ has_oids: self.handler.has_oids
};
self.handler.slickgrid = grid;
@@ -820,7 +824,8 @@ define('tools.querytool', [
// self.handler.data_store.updated will holds all the updated data
var changed_column = args.grid.getColumns()[args.cell].field,
updated_data = args.item[changed_column], // New value for current field
- _pk = args.item[self.client_primary_key] || null, // Unique key to identify row
+ _pk = args.item[self.client_primary_key] || null, // Unique key to identify row
+ has_oids = self.handler.has_oids || null, // Unique key to identify row
column_data = {},
_type;
@@ -852,6 +857,7 @@ define('tools.querytool', [
column_data[changed_column] = updated_data;
+
if (_pk) {
// Check if it is in newly added row by user?
if (_pk in self.handler.data_store.added) {
@@ -859,7 +865,7 @@ define('tools.querytool', [
self.handler.data_store.added[_pk]['data'],
column_data);
//Find type for current column
- self.handler.data_store.added[_pk]['err'] = false
+ self.handler.data_store.added[_pk]['err'] = false;
// Check if it is updated data from existing rows?
} else if (_pk in self.handler.data_store.updated) {
_.extend(
@@ -1900,18 +1906,20 @@ define('tools.querytool', [
_render: function (data) {
var self = this;
self.colinfo = data.col_info;
- self.primary_keys = data.primary_keys;
+ self.primary_keys = (_.isEmpty(data.primary_keys) && data.has_oids)? data.oids : data.primary_keys;
self.client_primary_key = data.client_primary_key;
self.cell_selected = false;
self.selected_model = null;
self.changedModels = [];
+ self.has_oids = data.has_oids;
+ self.oids = data.oids;
$('.sql-editor-explain').empty();
/* If object don't have primary keys then set the
* can_edit flag to false.
*/
- if (self.primary_keys === null || self.primary_keys === undefined
- || _.size(self.primary_keys) === 0)
+ if ((self.primary_keys === null || self.primary_keys === undefined
+ || _.size(self.primary_keys) === 0) && self.has_oids == false)
self.can_edit = false;
else
self.can_edit = true;
@@ -2072,6 +2080,9 @@ define('tools.querytool', [
}
// Identify cell type of column.
switch (type) {
+ case "oid":
+ col_cell = 'oid';
+ break;
case "json":
case "json[]":
case "jsonb":
@@ -2128,7 +2139,7 @@ define('tools.querytool', [
'pos': c.pos,
'label': column_label,
'cell': col_cell,
- 'can_edit': self.can_edit,
+ 'can_edit': (c.name == 'oid') ? false : self.can_edit,
'type': type,
'not_null': c.not_null,
'has_default_val': c.has_default_val,
@@ -2380,6 +2391,40 @@ define('tools.querytool', [
data_length = dataView.getLength(),
data = [];
if (res.data.status) {
+ if(self.has_oids && is_added) {
+ // Update the oids in a grid after addition
+ dataView.beginUpdate();
+ _.each(res.data.query_result, function (r) {
+ if(!_.isNull(r.oids)) {
+ var row_id = Object.keys(r.oids)[0];
+ var item = grid.getDataItem(row_id);
+ item['oid'] = r.oids[row_id];
+ }
+ });
+ dataView.endUpdate();
+ }
+
+ if(is_added) {
+ // Update the primary keys in a grid after addition
+ dataView.beginUpdate();
+ _.each(res.data.query_result, function (r) {
+ if(!_.isNull(r.pk_values)) {
+ // Fetch temp_id returned by server after addition
+ var row_id = Object.keys(r.pk_values)[0];
+ _.each(req_data.added_index, function(v, k) {
+ if (v == row_id) {
+ // Fetch item data through row index
+ var item = grid.getDataItem(k);
+ _.each(r.pk_values[row_id], function (p, k) {
+ // Update the primary key values if different
+ if(item[k] != p) item[k] = p;
+ })
+ }
+ })
+ }
+ });
+ dataView.endUpdate();
+ }
// Remove flag is_row_copied from copied rows
_.each(data, function (row, idx) {
if (row.is_row_copied) {
@@ -2412,15 +2457,6 @@ define('tools.querytool', [
grid.setSelectedRows([]);
}
- // whether a cell is editable or not is decided in
- // grid.onBeforeEditCell function (on cell click)
- // but this function should do its job after save
- // operation. So assign list of added rows to original
- // rows_to_disable array.
- if (is_added) {
- self.rows_to_disable = _.clone(self.temp_new_rows);
- }
-
grid.setSelectedRows([]);
// Reset data store
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/has_oids.sql b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/has_oids.sql
new file mode 100644
index 0000000..edeeb83
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/has_oids.sql
@@ -0,0 +1,6 @@
+{# ============= Check object has OIDs or not ============= #}
+{% if obj_id %}
+SELECT rel.relhasoids AS has_oids
+FROM pg_class rel
+WHERE rel.oid = {{ obj_id }}::oid
+{% endif %}
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/insert.sql b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/insert.sql
index 23ffcb4..a4ed872 100644
--- a/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/insert.sql
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/insert.sql
@@ -5,4 +5,6 @@ INSERT INTO {{ conn|qtIdent(nsp_name, object_name) }} (
) VALUES (
{% for col in data_to_be_saved %}
{% if not loop.first %}, {% endif %}%({{ col }})s::{{ data_type[col] }}{% endfor %}
-);
+)
+{% if pk_names %} returning {{pk_names}}{% endif %}
+{% if has_oids %} returning oid{% endif %};
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/objectquery.sql b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/objectquery.sql
index 2c6ba58..1cb60d9 100644
--- a/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/objectquery.sql
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/objectquery.sql
@@ -1,5 +1,5 @@
{# SQL query for objects #}
-SELECT * FROM {{ conn|qtIdent(nsp_name, object_name) }}
+SELECT {% if has_oids %}oid, {% endif %}* FROM {{ conn|qtIdent(nsp_name, object_name) }}
{% if sql_filter %}
WHERE {{ sql_filter }}
{% endif %}