Query tool efficiency.Convert the data as a 2D array instead of dict. Fixes #2036

Incremental back off when polling. Fixes #2038

Initial Patch By: Dave Page
This commit is contained in:
Murtuza Zabuawala
2017-03-01 16:58:51 +05:30
committed by Akshay Joshi
parent 6d438fec5e
commit 480e00fddf
6 changed files with 331 additions and 133 deletions

View File

@@ -110,11 +110,11 @@
// When text editor opens
this.loadValue = function (item) {
if (item[args.column.field] === "") {
if (item[args.column.pos] === "") {
$input.val("''");
}
else {
$input.val(defaultValue = item[args.column.field]);
$input.val(defaultValue = item[args.column.pos]);
$input.select();
}
};
@@ -141,7 +141,7 @@
};
this.applyValue = function (item, state) {
item[args.column.field] = state;
item[args.column.pos] = state;
};
this.isValueChanged = function () {
@@ -252,7 +252,7 @@
};
this.loadValue = function (item) {
var data = defaultValue = item[args.column.field];
var data = defaultValue = item[args.column.pos];
if (typeof data === "object" && !Array.isArray(data)) {
data = JSON.stringify(data);
} else if (Array.isArray(data)) {
@@ -278,7 +278,7 @@
};
this.applyValue = function (item, state) {
item[args.column.field] = state;
item[args.column.pos] = state;
};
this.isValueChanged = function () {
@@ -385,7 +385,7 @@
};
this.loadValue = function (item) {
$input.val(defaultValue = item[args.column.field]);
$input.val(defaultValue = item[args.column.pos]);
$input.select();
};
@@ -394,7 +394,7 @@
};
this.applyValue = function (item, state) {
item[args.column.field] = state;
item[args.column.pos] = state;
};
this.isValueChanged = function () {
@@ -468,12 +468,12 @@
};
this.loadValue = function (item) {
defaultValue = item[args.column.field];
defaultValue = item[args.column.pos];
if (_.isNull(defaultValue)) {
$select.prop('indeterminate', true);
}
else {
defaultValue = !!item[args.column.field];
defaultValue = !!item[args.column.pos];
if (defaultValue) {
$select.prop('checked', true);
} else {
@@ -490,7 +490,7 @@
};
this.applyValue = function (item, state) {
item[args.column.field] = state;
item[args.column.pos] = state;
};
this.isValueChanged = function () {
@@ -590,7 +590,7 @@
};
this.loadValue = function (item) {
var data = defaultValue = item[args.column.field];
var data = defaultValue = item[args.column.pos];
if (typeof data === "object" && !Array.isArray(data)) {
data = JSON.stringify(data);
} else if (Array.isArray(data)) {
@@ -613,7 +613,7 @@
};
this.applyValue = function (item, state) {
item[args.column.field] = state;
item[args.column.pos] = state;
};
this.isValueChanged = function () {
@@ -667,7 +667,7 @@
};
this.loadValue = function (item) {
var value = item[args.column.field];
var value = item[args.column.pos];
// Check if value is null or undefined
if (value === undefined && typeof value === "undefined") {
@@ -819,7 +819,7 @@
};
this.loadValue = function (item) {
defaultValue = item[args.column.field];
defaultValue = item[args.column.pos];
$input.val(defaultValue);
$input[0].defaultValue = defaultValue;
$input.select();
@@ -833,7 +833,7 @@
};
this.applyValue = function (item, state) {
item[args.column.field] = state;
item[args.column.pos] = state;
};
this.isValueChanged = function () {

View File

@@ -881,7 +881,7 @@ def messages(trans_id):
port_number = ''
if conn.connected():
status, result, my_result = conn.poll()
status, result = conn.poll()
notify = conn.messages()
if notify:
# In notice message we need to find "PLDBGBREAK" string to find the port number to attach.
@@ -1363,7 +1363,7 @@ def poll_end_execution_result(trans_id):
statusmsg = conn.status_message()
if statusmsg and statusmsg == 'SELECT 1':
statusmsg = ''
status, result, col_info = conn.poll()
status, result = conn.poll()
if status == ASYNC_OK and \
not session['functionData'][str(trans_id)]['is_func'] and \
session['functionData'][str(trans_id)]['language'] == 'edbspl':
@@ -1396,6 +1396,7 @@ def poll_end_execution_result(trans_id):
statusmsg = additional_msgs
columns = []
col_info = conn.get_column_info()
# Check column info is available or not
if col_info is not None and len(col_info) > 0:
for col in col_info:
@@ -1405,6 +1406,21 @@ def poll_end_execution_result(trans_id):
column['type_code'] = items[1][1]
columns.append(column)
# We need to convert result from 2D array to dict for BackGrid
# BackGrid do not support for 2D array result as it it Backbone Model based grid
# This Conversion is not an overhead as most of the time
# result will be smaller
_tmp_result = []
for row in result:
temp = dict()
count = 0
for item in row:
temp[columns[count]['name']] = item
count += 1
_tmp_result.append(temp)
# Replace 2d array with dict result
result = _tmp_result
return make_json_response(success=1, info=gettext("Execution Completed."),
data={'status': status, 'result': result,
'col_info': columns, 'status_message': statusmsg})
@@ -1453,7 +1469,7 @@ def poll_result(trans_id):
conn = manager.connection(did=obj['database_id'], conn_id=obj['exe_conn_id'])
if conn.connected():
status, result, my_result = conn.poll()
status, result = conn.poll()
if status == ASYNC_OK and result is not None:
status = 'Success'
else:

View File

@@ -420,6 +420,43 @@ def preferences(trans_id):
return success_return()
@blueprint.route('/columns/<int:trans_id>', methods=["GET"])
@login_required
def get_columns(trans_id):
"""
This method will returns list of columns of last async query.
Args:
trans_id: unique transaction id
"""
columns = dict()
columns_info = None
primary_keys = None
status, error_msg, conn, trans_obj, session_obj = check_transaction_status(trans_id)
if status and conn is not None and session_obj is not None:
# Check PK column info is available or not
if 'primary_keys' in session_obj:
primary_keys = session_obj['primary_keys']
# Fetch column information
columns_info = conn.get_column_info()
if columns_info is not None:
for col in columns_info:
col_type = dict()
col_type['type_code'] = col['type_code']
col_type['type_name'] = None
columns[col['name']] = col_type
# As we changed the transaction object we need to
# restore it and update the session variable.
session_obj['columns_info'] = columns
update_session_grid_transaction(trans_id, session_obj)
return make_json_response(data={'status': True,
'columns': columns_info,
'primary_keys': primary_keys})
@blueprint.route('/poll/<int:trans_id>', methods=["GET"])
@login_required
def poll(trans_id):
@@ -429,20 +466,19 @@ def poll(trans_id):
Args:
trans_id: unique transaction id
"""
col_info = None
result = None
primary_keys = None
rows_affected = 0
additional_result = []
# Check the transaction and connection status
status, error_msg, conn, trans_obj, session_obj = check_transaction_status(trans_id)
if status and conn is not None and session_obj is not None:
status, result, col_info = conn.poll()
status, result = conn.poll()
if status == ASYNC_OK:
status = 'Success'
if 'primary_keys' in session_obj:
primary_keys = session_obj['primary_keys']
rows_affected = conn.rows_affected()
# if transaction object is instance of QueryToolCommand
# and transaction aborted for some reason then issue a
@@ -464,22 +500,6 @@ def poll(trans_id):
status = 'NotConnected'
result = error_msg
# Check column info is available or not
if col_info is not None and len(col_info) > 0:
columns = dict()
rows_affected = conn.rows_affected()
for col in col_info:
col_type = dict()
col_type['type_code'] = col['type_code']
col_type['type_name'] = None
columns[col['name']] = col_type
# As we changed the transaction object we need to
# restore it and update the session variable.
session_obj['columns_info'] = columns
update_session_grid_transaction(trans_id, session_obj)
"""
Procedure/Function output may comes in the form of Notices from the
database server, so we need to append those outputs with the
@@ -497,8 +517,6 @@ def poll(trans_id):
else:
result = additional_result
rows_affected = conn.rows_affected()
# There may be additional messages even if result is present
# eg: Function can provide result as well as RAISE messages
additional_messages = None
@@ -510,7 +528,6 @@ def poll(trans_id):
return make_json_response(
data={
'status': status, 'result': result,
'colinfo': col_info, 'primary_keys': primary_keys,
'rows_affected': rows_affected,
'additional_messages': additional_messages
}

View File

@@ -418,6 +418,14 @@ class TableCommand(GridCommand):
list_of_sql = []
_rowid = None
# Replace column positions with names
def set_column_names(data):
new_data = {}
for key in data:
new_data[changed_data['columns'][int(key)]['name']] = data[key]
return new_data
if conn.connected():
# Start the transaction
@@ -425,8 +433,10 @@ class TableCommand(GridCommand):
# Iterate total number of records to be updated/inserted
for of_type in changed_data:
# if no data to be saved then continue
# No need to go further if its not add/update/delete operation
if of_type not in ('added', 'updated', 'deleted'):
continue
# if no data to be save then continue
if len(changed_data[of_type]) < 1:
continue
@@ -434,10 +444,12 @@ class TableCommand(GridCommand):
if of_type == 'added':
for each_row in changed_data[of_type]:
data = changed_data[of_type][each_row]['data']
data_type = changed_data[of_type][each_row]['data_type']
list_of_rowid.append(data.get('__temp_PK'))
# Remove our unique tracking key
data.pop('__temp_PK', None)
data = set_column_names(data)
data_type = set_column_names(changed_data[of_type][each_row]['data_type'])
list_of_rowid.append(data.get('__temp_PK'))
sql = render_template("/".join([self.sql_path, 'insert.sql']),
data_to_be_saved=data,
primary_keys=None,
@@ -449,9 +461,9 @@ class TableCommand(GridCommand):
# For updated rows
elif of_type == 'updated':
for each_row in changed_data[of_type]:
data = changed_data[of_type][each_row]['data']
pk = changed_data[of_type][each_row]['primary_keys']
data_type = changed_data[of_type][each_row]['data_type']
data = set_column_names(changed_data[of_type][each_row]['data'])
pk = set_column_names(changed_data[of_type][each_row]['primary_keys'])
data_type = set_column_names(changed_data[of_type][each_row]['data_type'])
sql = render_template("/".join([self.sql_path, 'update.sql']),
data_to_be_saved=data,
primary_keys=pk,
@@ -472,10 +484,22 @@ class TableCommand(GridCommand):
# Fetch the keys for SQL generation
if is_first:
# We need to covert dict_keys to normal list in Python3
# In Python2, it's already a list
keys = list(changed_data[of_type][each_row].keys())
# In Python2, it's already a list & We will also fetch column names using index
keys = [
changed_data['columns'][int(k)]['name']
for k in list(changed_data[of_type][each_row].keys())
]
no_of_keys = len(keys)
is_first = False
# Map index with column name for each row
for row in rows_to_delete:
for k, v in row.items():
# Set primary key with label & delete index based mapped key
try:
row[keys[int(k)]] = v
except ValueError:
continue
del row[k]
sql = render_template("/".join([self.sql_path, 'delete.sql']),
data=rows_to_delete,

View File

@@ -577,6 +577,15 @@ define(
- We are using this event for Copy operation on grid
*/
// Get the item column value using a custom 'fieldIdx' column param
get_item_column_value: function (item, column) {
if (column.pos !== undefined) {
return item[column.pos];
} else {
return null;
}
},
// This function is responsible to create and render the SlickGrid.
render_grid: function(collection, columns, is_editable) {
var self = this;
@@ -617,6 +626,7 @@ define(
_.each(columns, function(c) {
var options = {
id: c.name,
pos: c.pos,
field: c.name,
name: c.label
};
@@ -652,7 +662,8 @@ define(
enableCellNavigation: true,
enableColumnReorder: false,
asyncEditorLoading: false,
autoEdit: false
autoEdit: false,
dataItemColumnValueExtractor: this.get_item_column_value
};
var $data_grid = self.$el.find('#datagrid');
@@ -705,11 +716,26 @@ define(
if (editor_data.selection) {
editor_data.selection.onSelectedRangesChanged.subscribe(function(e, args) {
var collection = this.grid.getData(),
_pk = _.keys(this.keys),
primary_key_list = _.keys(this.keys),
_tmp_keys = [],
_columns = this.columns,
rows_for_stage = {}, selected_rows_list = [];
// Only if entire row(s) are selected via check box
if(_.has(this.selection, 'getSelectedRows')) {
selected_rows_list = this.selection.getSelectedRows();
// We will map selected row primary key name with position
// For each Primary key
_.each(primary_key_list, function(p) {
// For each columns search primary key position
_.each(_columns, function(c) {
if(c.name == p) {
_tmp_keys.push(c.pos);
}
});
});
// Now assign mapped temp PK to PK
primary_key_list = _tmp_keys;
}
// If any row(s) selected ?
@@ -717,12 +743,14 @@ define(
if(this.editor.handler.can_edit)
// Enable delete rows button
$("#btn-delete-row").prop('disabled', false);
// Enable copy rows button
$("#btn-copy-row").prop('disabled', false);
// Collect primary key data from collection as needed for stage row
_.each(selected_rows_list, function(row_index) {
var row_data = collection[row_index], _selected = {};
rows_for_stage[row_data.__temp_PK] = _.pick(row_data, _pk);
var row_data = collection[row_index];
// Store Primary key data for selected rows
rows_for_stage[row_data.__temp_PK] = _.pick(row_data, primary_key_list);
});
} else {
// Clear the object as no rows to delete
@@ -733,7 +761,7 @@ define(
}
// Update main data store
this.editor.handler.data_store.staged_rows = rows_for_stage;
this.editor.handler.data_store.staged_rows = rows_for_stage;
}.bind(editor_data));
}
@@ -742,7 +770,7 @@ define(
// This will be used to collect primary key for that row
grid.onBeforeEditCell.subscribe(function (e, args) {
var before_data = args.item;
if(before_data && '__temp_PK' in before_data) {
if(self.handler.can_edit && before_data && '__temp_PK' in before_data) {
var _pk = before_data.__temp_PK,
_keys = self.handler.primary_keys,
current_pk = {}, each_pk_key = {};
@@ -753,8 +781,10 @@ define(
}
// Fetch primary keys for the row before they gets modified
var _columns = self.handler.columns;
_.each(_keys, function(value, key) {
current_pk[key] = before_data[key];
pos = _.where(_columns, {name: key})[0]['pos']
current_pk[pos] = before_data[pos];
});
// Place it in main variable for later use
self.handler.primary_keys_data[_pk] = current_pk
@@ -787,8 +817,8 @@ define(
// Fetch current row data from grid
column_values = grid.getDataItem(row, cell)
// Get the value from cell
value = column_values[column_info.field] || '';
//Copy this value to Clipborad
value = column_values[column_info.pos] || '';
// Copy this value to Clipboard
if(value)
this.editor.handler.copyTextToClipboard(value);
// Go to cell again
@@ -801,7 +831,7 @@ define(
// Listener function which will be called when user updates existing rows
grid.onCellChange.subscribe(function (e, args) {
// self.handler.data_store.updated will holds all the updated data
var changed_column = args.grid.getColumns()[args.cell].field, // Current filed name
var changed_column = args.grid.getColumns()[args.cell].pos, // Current field pos
updated_data = args.item[changed_column], // New value for current field
_pk = args.item.__temp_PK || null, // Unique key to identify row
column_data = {},
@@ -817,7 +847,7 @@ define(
column_data);
//Find type for current column
self.handler.data_store.added[_pk]['err'] = false
self.handler.data_store.added[_pk]['data_type'][changed_column] = _.where(this.columns, {name: changed_column})[0]['type'];
self.handler.data_store.added[_pk]['data_type'][changed_column] = _.where(this.columns, {pos: changed_column})[0]['type'];
// Check if it is updated data from existing rows?
} else if(_pk in self.handler.data_store.updated) {
_.extend(
@@ -827,7 +857,7 @@ define(
self.handler.data_store.updated[_pk]['err'] = false
//Find type for current column
self.handler.data_store.updated[_pk]['data_type'][changed_column] = _.where(this.columns, {name: changed_column})[0]['type'];
self.handler.data_store.updated[_pk]['data_type'][changed_column] = _.where(this.columns, {pos: changed_column})[0]['type'];
} else {
// First updated data for this primary key
self.handler.data_store.updated[_pk] = {
@@ -837,7 +867,7 @@ define(
self.handler.data_store.updated_index[args.row] = _pk;
// Find & add column data type for current changed column
var temp = {};
temp[changed_column] = _.where(this.columns, {name: changed_column})[0]['type'];
temp[changed_column] = _.where(this.columns, {pos: changed_column})[0]['type'];
self.handler.data_store.updated[_pk]['data_type'] = temp;
}
}
@@ -860,7 +890,7 @@ define(
self.handler.data_store.added_index[data_length] = _key;
// Fetch data type & add it for the column
var temp = {};
temp[column.field] = _.where(this.columns, {name: column.field})[0]['type'];
temp[column.pos] = _.where(this.columns, {pos: column.pos})[0]['type'];
self.handler.data_store.added[_key]['data_type'] = temp;
grid.invalidateRows([collection.length - 1]);
grid.updateRowCount();
@@ -1666,6 +1696,7 @@ define(
var self = this;
self.query_start_time = new Date();
self.rows_affected = 0;
self._init_polling_flags();
self.trigger(
'pgadmin-sqleditor:loading-icon:show',
@@ -1744,6 +1775,74 @@ define(
});
},
// This function makes the ajax call to fetch columns for last async query,
get_columns: function(poll_result) {
var self = this;
// Check the flag and decide if we need to fetch columns from server
// or use the columns data stored locally from previous call?
if (self.FETCH_COLUMNS_FROM_SERVER) {
$.ajax({
url: "{{ url_for('sqleditor.index') }}" + "columns/" + self.transId,
method: 'GET',
success: function(res) {
poll_result.colinfo = res.data.columns;
poll_result.primary_keys = res.data.primary_keys;
self.call_render_after_poll(poll_result);
// Set a flag to get columns to false & set the value for future use
self.FETCH_COLUMNS_FROM_SERVER = false;
self.COLUMNS_DATA = res;
},
error: function(e) {
var msg = e.responseText;
if (e.responseJSON != undefined && e.responseJSON.errormsg != undefined)
msg = e.responseJSON.errormsg;
alertify.error(msg, 5);
}
});
} else {
// Use the previously saved columns data
poll_result.colinfo = self.COLUMNS_DATA.data.columns;
poll_result.primary_keys = self.COLUMNS_DATA.data.primary_keys;
self.call_render_after_poll(poll_result);
}
},
// This is a wrapper to call _render function
// We need this because we have separated columns route & result route
// We need to combine both result here in wrapper before rendering grid
call_render_after_poll: function(res) {
var self = this;
self.query_end_time = new Date();
self.rows_affected = res.rows_affected;
/* If no column information is available it means query
runs successfully with no result to display. In this
case no need to call render function.
*/
if (res.colinfo != null)
self._render(res);
else {
// Show message in message and history tab in case of query tool
self.total_time = self.get_query_run_time(self.query_start_time, self.query_end_time);
var msg = S('{{ _('Query returned successfully in %s.') }}').sprintf(self.total_time).value();
res.result += "\n\n" + msg;
self.update_msg_history(true, res.result, false);
// Display the notifier if the timeout is set to >= 0
if (self.info_notifier_timeout >= 0) {
alertify.success(msg, self.info_notifier_timeout);
}
}
// Enable/Disable query tool button only if is_query_tool is true.
if (self.is_query_tool) {
self.disable_tool_buttons(false);
$("#btn-cancel-query").prop('disabled', true);
}
is_query_running = false;
self.trigger('pgadmin-sqleditor:loading-icon:hide');
},
/* This function makes the ajax call to poll the result,
* if status is Busy then recursively call the poll function
* till the status is 'Success' or 'NotConnected'. If status is
@@ -1751,6 +1850,7 @@ define(
*/
_poll: function() {
var self = this;
setTimeout(
function() {
$.ajax({
@@ -1762,35 +1862,7 @@ define(
'pgadmin-sqleditor:loading-icon:message',
'{{ _('Loading data from the database server and rendering...') }}'
);
self.query_end_time = new Date();
self.rows_affected = res.data.rows_affected;
/* If no column information is available it means query
runs successfully with no result to display. In this
case no need to call render function.
*/
if (res.data.colinfo != null)
self._render(res.data);
else {
// Show message in message and history tab in case of query tool
self.total_time = self.get_query_run_time(self.query_start_time, self.query_end_time);
var msg = S('{{ _('Query returned successfully in %s.') }}').sprintf(self.total_time).value();
res.data.result += "\n\n" + msg;
self.update_msg_history(true, res.data.result, false);
// Display the notifier if the timeout is set to >= 0
if (self.info_notifier_timeout >= 0) {
alertify.success(msg, self.info_notifier_timeout);
}
}
// Enable/Disable query tool button only if is_query_tool is true.
if (self.is_query_tool) {
self.disable_tool_buttons(false);
$("#btn-cancel-query").prop('disabled', true);
}
is_query_running = false;
self.trigger('pgadmin-sqleditor:loading-icon:hide');
self.get_columns(res.data);
}
else if (res.data.status === 'Busy') {
// If status is Busy then poll the result by recursive call to the poll function
@@ -1837,7 +1909,7 @@ define(
self.update_msg_history(false, msg);
}
});
}, 1);
}, self.POLL_FALLBACK_TIME());
},
/* This function is used to create the backgrid columns,
@@ -1922,13 +1994,13 @@ define(
* and add json formatted data to collection and render.
*/
var explain_data_array = [];
if(
if (
data.result && data.result.length >= 1 &&
data.result[0] && data.result[0].hasOwnProperty(
'QUERY PLAN'
) && _.isObject(data.result[0]['QUERY PLAN'])
data.result[0] && data.result[0][0] && data.result[0][0][0] &&
data.result[0][0][0].hasOwnProperty('Plan') &&
_.isObject(data.result[0][0][0]['Plan'])
) {
var explain_data = {'QUERY PLAN' : JSON.stringify(data.result[0]['QUERY PLAN'], null, 2)};
var explain_data = [JSON.stringify(data.result[0][0], null, 2)];
explain_data_array.push(explain_data);
// Make sure - the 'Data Output' panel is visible, before - we
// start rendering the grid.
@@ -1942,7 +2014,7 @@ define(
// start rendering the grid.
self.gridView.explain_panel.focus();
pgExplain.DrawJSONPlan(
$('.sql-editor-explain'), data.result[0]['QUERY PLAN']
$('.sql-editor-explain'), data.result[0][0]
);
}, 10
);
@@ -2067,6 +2139,7 @@ define(
var col = {
'name': c.name,
'pos': c.pos,
'label': column_label,
'cell': col_cell,
'can_edit': self.can_edit,
@@ -2278,12 +2351,16 @@ define(
'{{ _('Saving the updated data...') }}'
);
// Add the columns to the data so the server can remap the data
req_data = self.data_store
req_data.columns = view ? view.handler.columns : self.columns;
// Make ajax call to save the data
$.ajax({
url: "{{ url_for('sqleditor.index') }}" + "save/" + self.transId,
method: 'POST',
contentType: "application/json",
data: JSON.stringify(self.data_store),
data: JSON.stringify(req_data),
success: function(res) {
var grid = self.slickgrid,
data = grid.getData();
@@ -2592,6 +2669,37 @@ define(
}
},
// This function will set the required flag for polling response data
_init_polling_flags: function() {
var self = this;
// Set a flag to get columns
self.FETCH_COLUMNS_FROM_SERVER = true;
// We will set columns data in this variable for future use once we fetch it
// from server
self.COLUMNS_DATA = {};
// To get a timeout for polling fallback timer in seconds in
// regards to elapsed time
self.POLL_FALLBACK_TIME = function() {
var seconds = parseInt((Date.now() - self.query_start_time.getTime())/1000);
// calculate & return fall back polling timeout
if(seconds >= 10 && seconds < 30) {
return 500;
}
else if(seconds >= 30 && seconds < 60) {
return 1000;
}
else if(seconds >= 60 && seconds < 90) {
return 2000;
}
else if(seconds >= 90) {
return 5000;
}
else
return 1;
}
},
// This function will show the filter in the text area.
_show_filter: function() {
var self = this;
@@ -2663,8 +2771,8 @@ define(
if (_.isNull(_values) || _.isUndefined(_values))
return;
// Add column name and it's' value to data
data[column_info.field] = _values[column_info.field] || '';
// Add column position and it's value to data
data[column_info.pos] = _values[column_info.pos] || '';
self.trigger(
'pgadmin-sqleditor:loading-icon:show',
@@ -2733,8 +2841,8 @@ define(
if (_.isNull(_values) || _.isUndefined(_values))
return;
// Add column name and it's' value to data
data[column_info.field] = _values[column_info.field] || '';
// Add column position and it's value to data
data[column_info.pos] = _values[column_info.pos] || '';
self.trigger(
'pgadmin-sqleditor:loading-icon:show',
@@ -2971,10 +3079,10 @@ define(
self.copied_rows.push(_rowData);
// Convert it as CSV for clipboard
for (var j = 0; j < self.columns.length; j += 1) {
var val = _rowData[self.columns[j].name];
var val = _rowData[self.columns[j].pos];
if(val && _.isObject(val))
val = "'" + JSON.stringify(val) + "'";
else if(val && typeof val != "number")
else if(val && typeof val != "number" && typeof true != "boolean")
val = "'" + val.toString() + "'";
else if (_.isNull(val) || _.isUndefined(val))
val = '';
@@ -2995,7 +3103,8 @@ define(
grid = self.slickgrid,
data = grid.getData();
// Deep copy
var copied_rows = $.extend(true, [], self.copied_rows);
var copied_rows = $.extend(true, [], self.copied_rows),
_tmp_copied_row = {};
// If there are rows to paste?
if(copied_rows.length > 0) {
@@ -3016,7 +3125,7 @@ define(
// Fetch column name & its data type
_.each(self.columns, function(c) {
col_info[c.name] = c.type;
col_info[String(c.pos)] = c.type;
});
// insert these data in data_store as well to save them on server
@@ -3026,16 +3135,22 @@ define(
'data': {}
};
self.data_store.added[copied_rows[j].__temp_PK]['data_type'] = col_info;
// We need to convert it from array to dict so that server can
// understand the data properly
_.each(copied_rows[j], function(val, key) {
// If value is array then convert it to string
if(_.isArray(val)) {
copied_rows[j][key] = val.toString();
_tmp_copied_row[String(key)] = val.toString();
// If value is object then stringify it
} else if(_.isObject(val)) {
copied_rows[j][key] = JSON.stringify(val);
_tmp_copied_row[j][String(key)] = JSON.stringify(val);
} else {
_tmp_copied_row[String(key)] = val;
}
});
self.data_store.added[copied_rows[j].__temp_PK]['data'] = copied_rows[j];
self.data_store.added[copied_rows[j].__temp_PK]['data'] = _tmp_copied_row;
// reset the variable
_tmp_copied_row = {};
}
}
},
@@ -3132,7 +3247,7 @@ define(
self.query_start_time = new Date();
self.query = sql;
self.rows_affected = 0;
self._init_polling_flags();
self.disable_tool_buttons(true);
$("#btn-cancel-query").prop('disabled', false);

View File

@@ -42,8 +42,6 @@ else:
_ = gettext
ASYNC_WAIT_TIMEOUT = 0.01 # in seconds or 10 milliseconds
# This registers a unicode type caster for datatype 'RECORD'.
psycopg2.extensions.register_type(
psycopg2.extensions.new_type((2249,), "RECORD",
@@ -696,7 +694,7 @@ WHERE
self.__notices = []
self.execution_aborted = False
cur.execute(query, params)
res = self._wait_timeout(cur.connection, ASYNC_WAIT_TIMEOUT)
res = self._wait_timeout(cur.connection)
except psycopg2.Error as pe:
errmsg = self._formatted_exception_msg(pe, formatted_exception_msg)
current_app.logger.error(u"""
@@ -1003,7 +1001,7 @@ Failed to reset the connection to the server due to following error:
else:
raise psycopg2.OperationalError("poll() returned %s from _wait function" % state)
def _wait_timeout(self, conn, time):
def _wait_timeout(self, conn):
"""
This function is used for the asynchronous connection,
it will call poll method and return the status. If state is
@@ -1021,7 +1019,7 @@ Failed to reset the connection to the server due to following error:
elif state == psycopg2.extensions.POLL_WRITE:
# Wait for the given time and then check the return status
# If three empty lists are returned then the time-out is reached.
timeout_status = select.select([], [conn.fileno()], [], time)
timeout_status = select.select([], [conn.fileno()], [])
if timeout_status == ([], [], []):
return self.ASYNC_WRITE_TIMEOUT
@@ -1034,16 +1032,25 @@ Failed to reset the connection to the server due to following error:
elif state == psycopg2.extensions.POLL_READ:
# Wait for the given time and then check the return status
# If three empty lists are returned then the time-out is reached.
timeout_status = select.select([conn.fileno()], [], [], time)
timeout_status = select.select([conn.fileno()], [], [])
if timeout_status == ([], [], []):
return self.ASYNC_READ_TIMEOUT
# poll again to check the state if it is still POLL_READ
# then return ASYNC_READ_TIMEOUT else return ASYNC_OK.
state = conn.poll()
if state == psycopg2.extensions.POLL_READ:
return self.ASYNC_READ_TIMEOUT
return self.ASYNC_OK
# select.select timeout option works only if we provide
# empty [] [] [] file descriptor in select.select() function
# and that also works only on UNIX based system, it do not support Windows
# Hence we have wrote our own pooling mechanism to read data fast
# each call conn.poll() reads chunks of data from connection object
# more we poll more we read data from connection
cnt = 0
while cnt < 1000:
# poll again to check the state if it is still POLL_READ
# then return ASYNC_READ_TIMEOUT else return ASYNC_OK.
state = conn.poll()
if state == psycopg2.extensions.POLL_OK:
return self.ASYNC_OK
cnt += 1
return self.ASYNC_READ_TIMEOUT
else:
raise psycopg2.OperationalError(
"poll() returned %s from _wait_timeout function" % state
@@ -1075,42 +1082,48 @@ Failed to reset the connection to the server due to following error:
)
try:
status = self._wait_timeout(self.conn, ASYNC_WAIT_TIMEOUT)
status = self._wait_timeout(self.conn)
except psycopg2.Error as pe:
if cur.closed:
if self.conn.closed:
raise ConnectionLost(
self.manager.sid,
self.db,
self.conn_id[5:]
)
errmsg = self._formatted_exception_msg(pe, formatted_exception_msg)
return False, errmsg, None
return False, errmsg
if self.conn.notices and self.__notices is not None:
while self.conn.notices:
self.__notices.append(self.conn.notices.pop(0)[:])
colinfo = None
result = None
self.row_count = 0
self.column_info = None
if status == self.ASYNC_OK:
# if user has cancelled the transaction then changed the status
if self.execution_aborted:
status = self.ASYNC_EXECUTION_ABORTED
self.execution_aborted = False
return status, result, colinfo
return status, result
# Fetch the column information
if cur.description is not None:
colinfo = [
self.column_info = [
desc.to_dict() for desc in cur.ordered_description()
]
pos = 0
for col in self.column_info:
col['pos'] = pos
pos = pos + 1
self.row_count = cur.rowcount
if cur.rowcount > 0:
result = []
# For DDL operation, we may not have result.
#
# Because - there is not direct way to differentiate DML and
@@ -1118,10 +1131,15 @@ Failed to reset the connection to the server due to following error:
# out at the moment.
try:
for row in cur:
result.append(dict(row))
new_row = []
for col in self.column_info:
new_row.append(row[col['name']])
result.append(new_row)
except psycopg2.ProgrammingError:
result = None
return status, result, colinfo
return status, result
def status_message(self):
"""
@@ -1148,6 +1166,14 @@ Failed to reset the connection to the server due to following error:
return self.row_count
def get_column_info(self):
"""
This function will returns list of columns for last async sql command
executed on the server.
"""
return self.column_info
def cancel_transaction(self, conn_id, did=None):
"""
This function is used to cancel the running transaction