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 f3353d642..2c3e5731f 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 4f1de2ade..584f7b1b4 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 8dcb44483..363d6f8a1 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 d09bf2b6a..70db0c931 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,16 @@ 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) + 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}) # Reset column data column_data = {} @@ -568,13 +615,15 @@ 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() + row_added = None - # store the result of each query in dictionary - query_res[count] = {'status': status, 'result': res, - 'sql': sql, 'rows_affected': rows_affected} + # 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']) if not status: conn.execute_void('ROLLBACK;') @@ -591,6 +640,38 @@ class TableCommand(GridCommand): _rowid = 0 return status, res, query_res, _rowid + + # 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} + 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 8c1a82c20..e0d7a67d4 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, @@ -2379,7 +2390,26 @@ define('tools.querytool', [ dataView = grid.getData(), data_length = dataView.getLength(), data = []; + if (res.data.status) { + if(is_added) { + // Update the rows in a grid after addition + dataView.beginUpdate(); + _.each(res.data.query_result, function (r) { + if(!_.isNull(r.row_added)) { + // Fetch temp_id returned by server after addition + var row_id = Object.keys(r.row_added)[0]; + _.each(req_data.added_index, function(v, k) { + if (v == row_id) { + // Fetch item data through row index + var item = grid.getDataItem(k); + _.extend(item, r.row_added[row_id]); + } + }) + } + }); + dataView.endUpdate(); + } // Remove flag is_row_copied from copied rows _.each(data, function (row, idx) { if (row.is_row_copied) { @@ -2412,15 +2442,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 000000000..edeeb839f --- /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 23ffcb4f0..a4ed8729c 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 2c6ba580d..1cb60d913 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 %} diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/select.sql b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/select.sql new file mode 100644 index 000000000..45f364aa2 --- /dev/null +++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/sql/default/select.sql @@ -0,0 +1,9 @@ +{# Select table rows #} +SELECT {% if has_oids %}oid, {% endif %}* FROM {{ conn|qtIdent(nsp_name, object_name) }} +WHERE +{% if has_oids %} + oid = %(oid)s +{% elif pk_names %} + {% for pk in pk_names %} + {% if not loop.first %} AND {% endif %}{{ conn|qtIdent(pk) }} = %({{ pk }})s{% endfor %} +{% endif %};