diff --git a/docs/en_US/query_tool_toolbar.rst b/docs/en_US/query_tool_toolbar.rst
index 2168c3930..a6c2c6ac5 100644
--- a/docs/en_US/query_tool_toolbar.rst
+++ b/docs/en_US/query_tool_toolbar.rst
@@ -179,10 +179,10 @@ Query Execution
| | | |
| | * Select *Clear History* to erase the content of the *History* tab. | |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
- | *Download as CSV/TXT*| Click the *Download as CSV/TXT* icon to download the result set of the current query as a *.csv* | F8 |
- | | or as a *.txt* file. if *CSV field seperator* set to comma(,) else as a *.txt* file. | |
- | | You can specify the CSV/TXT settings through *Preferences -> SQL Editor -> CSV/TXT output* | |
- | | dialogue. | |
+ | *Save results to* | Click the *Save results to file as CSV/TXT* icon to download the result set of the current query | F8 |
+ | *file as CSV/TXT* | as a *.csv* or as a *.txt* file. if *CSV field seperator* set to comma(,) else as a *.txt* file. | |
+ | | Button will only be enabled when there are results to save. You can specify the CSV/TXT settings | |
+ | | through *Preferences -> SQL Editor -> CSV/TXT output* dialogue. | |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+
| *Macros* | Click the *Macros* icon to manage the macros. You can create, edit or clear the macros through | |
| | the *Manage Macros* option. | |
diff --git a/web/pgadmin/misc/file_manager/static/scss/_file_manager.scss b/web/pgadmin/misc/file_manager/static/scss/_file_manager.scss
index d2682f686..4d15c348c 100644
--- a/web/pgadmin/misc/file_manager/static/scss/_file_manager.scss
+++ b/web/pgadmin/misc/file_manager/static/scss/_file_manager.scss
@@ -333,7 +333,7 @@
top: -8px;
left: -6px;
font-size: 8px;
- margin-right: -8px;
+ margin-right: -7px;
}
table.tablesorter {
diff --git a/web/pgadmin/static/js/keyboard_shortcuts.js b/web/pgadmin/static/js/keyboard_shortcuts.js
index abda8d8f2..059df0628 100644
--- a/web/pgadmin/static/js/keyboard_shortcuts.js
+++ b/web/pgadmin/static/js/keyboard_shortcuts.js
@@ -211,8 +211,10 @@ function keyboardShortcutsQueryTool(
this._stopEventPropagation(event);
queryToolActions.explainAnalyze(sqlEditorController);
} else if (this.validateShortcutKeys(downloadCsvKeys, event)) {
- this._stopEventPropagation(event);
- queryToolActions.download(sqlEditorController);
+ if(!sqlEditorController.is_save_results_to_file_disabled) {
+ this._stopEventPropagation(event);
+ queryToolActions.download(sqlEditorController);
+ }
} else if (this.validateShortcutKeys(toggleCaseKeys, event)) {
this._stopEventPropagation(event);
queryToolActions.toggleCaseOfSelectedText(sqlEditorController);
diff --git a/web/pgadmin/static/js/sqleditor/call_render_after_poll.js b/web/pgadmin/static/js/sqleditor/call_render_after_poll.js
index d8d364722..2c52baf10 100644
--- a/web/pgadmin/static/js/sqleditor/call_render_after_poll.js
+++ b/web/pgadmin/static/js/sqleditor/call_render_after_poll.js
@@ -43,6 +43,7 @@ export function callRenderAfterPoll(sqlEditor, alertify, res) {
if (isNotificationEnabled(sqlEditor)) {
alertify.success(msg, sqlEditor.info_notifier_timeout);
}
+ sqlEditor.enable_disable_download_btn(true);
}
if (isQueryTool(sqlEditor)) {
diff --git a/web/pgadmin/static/js/sqleditor/execute_query.js b/web/pgadmin/static/js/sqleditor/execute_query.js
index 6c4069d24..389655fa9 100644
--- a/web/pgadmin/static/js/sqleditor/execute_query.js
+++ b/web/pgadmin/static/js/sqleditor/execute_query.js
@@ -83,6 +83,7 @@ class ExecuteQuery {
} else {
self.loadingScreen.hide();
self.enableSQLEditorButtons();
+ self.disableDownloadButton();
// Enable/Disable commit and rollback button.
if (result.data.data.transaction_status == queryTxnStatus.TRANSACTION_STATUS_INTRANS
|| result.data.data.transaction_status == queryTxnStatus.TRANSACTION_STATUS_INERROR) {
@@ -201,7 +202,7 @@ class ExecuteQuery {
this.loadingScreen.show(gettext('Running query...'));
$('#btn-flash').prop('disabled', true);
- $('#btn-download').prop('disabled', true);
+ this.disableDownloadButton();
this.sqlServerObject.query_start_time = new Date();
if (typeof sqlStatement === 'object') {
@@ -281,6 +282,10 @@ class ExecuteQuery {
}
}
+ disableDownloadButton() {
+ this.sqlServerObject.enable_disable_download_btn(true)
+ }
+
enableSQLEditorButtons() {
this.sqlServerObject.disable_tool_buttons(false);
}
diff --git a/web/pgadmin/static/js/sqleditor/query_tool_actions.js b/web/pgadmin/static/js/sqleditor/query_tool_actions.js
index 81937058c..a3e16bdff 100644
--- a/web/pgadmin/static/js/sqleditor/query_tool_actions.js
+++ b/web/pgadmin/static/js/sqleditor/query_tool_actions.js
@@ -81,13 +81,7 @@ let queryToolActions = {
},
download: function (sqlEditorController) {
- let sqlQuery = sqlEditorController.gridView.query_tool_obj.getSelection();
- if (!sqlQuery) {
- sqlQuery = sqlEditorController.gridView.query_tool_obj.getValue();
- }
-
- if (!sqlQuery) return;
let extension = sqlEditorController.preferences.csv_field_separator === ',' ? '.csv': '.txt';
let filename = 'data-' + new Date().getTime() + extension;
@@ -95,7 +89,7 @@ let queryToolActions = {
filename = sqlEditorController.table_name + extension;
}
- sqlEditorController.trigger_csv_download(sqlQuery, filename);
+ sqlEditorController.trigger_csv_download(filename);
},
commentBlockCode: function (sqlEditorController) {
diff --git a/web/pgadmin/static/js/sqleditor/query_tool_preferences.js b/web/pgadmin/static/js/sqleditor/query_tool_preferences.js
index ba4e7c882..e2d4a65ca 100644
--- a/web/pgadmin/static/js/sqleditor/query_tool_preferences.js
+++ b/web/pgadmin/static/js/sqleditor/query_tool_preferences.js
@@ -112,11 +112,11 @@ function updateUIPreferences(sqlEditor) {
.attr('aria-label',
shortcut_title(gettext('Explain Analyze'),preferences.explain_analyze_query));
- $el.find('#btn-download')
+ $el.find('#btn-save-results-to-file')
.attr('title',
- shortcut_title(gettext('Download as CSV/TXT'),preferences.download_csv))
+ shortcut_title(gettext('Save results to file as CSV/TXT'),preferences.download_csv))
.attr('aria-label',
- shortcut_title(gettext('Download as CSV/TXT'),preferences.download_csv));
+ shortcut_title(gettext('Save results to file as CSV/TXT'),preferences.download_csv));
$el.find('#btn-save-data')
.attr('title',
diff --git a/web/pgadmin/tools/datagrid/templates/datagrid/index.html b/web/pgadmin/tools/datagrid/templates/datagrid/index.html
index cbee98222..d9d0d2254 100644
--- a/web/pgadmin/tools/datagrid/templates/datagrid/index.html
+++ b/web/pgadmin/tools/datagrid/templates/datagrid/index.html
@@ -374,9 +374,9 @@
-
diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py
index 0784c5468..a9f5627c6 100644
--- a/web/pgadmin/tools/sqleditor/__init__.py
+++ b/web/pgadmin/tools/sqleditor/__init__.py
@@ -1336,7 +1336,7 @@ def start_query_download_tool(trans_id):
)
data = request.values if request.values else None
- if data is None or (data and 'query' not in data):
+ if data is None:
return make_json_response(
status=410,
success=0,
@@ -1346,12 +1346,9 @@ def start_query_download_tool(trans_id):
)
try:
- sql = data['query']
# This returns generator of records.
- status, gen = sync_conn.execute_on_server_as_csv(
- sql, records=2000
- )
+ status, gen = sync_conn.execute_on_server_as_csv(records=2000)
if not status:
return make_json_response(
@@ -1362,6 +1359,7 @@ def start_query_download_tool(trans_id):
r = Response(
gen(
+ trans_obj,
quote=blueprint.csv_quoting.get(),
quote_char=blueprint.csv_quote_char.get(),
field_separator=blueprint.csv_field_separator.get(),
diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
index 00791fb2c..2f0b2c992 100644
--- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
@@ -136,7 +136,7 @@ define('tools.querytool', [
'click #btn-flash': 'on_flash',
'click #btn-flash-menu': 'on_flash',
'click #btn-cancel-query': 'on_cancel_query',
- 'click #btn-download': 'on_download',
+ 'click #btn-save-results-to-file': 'on_download',
'click #btn-clear': 'on_clear',
'click #btn-auto-commit': 'on_auto_commit',
'click #btn-auto-rollback': 'on_auto_rollback',
@@ -1358,7 +1358,7 @@ define('tools.querytool', [
self.handler.fetching_rows = true;
$('#btn-flash').prop('disabled', true);
- $('#btn-download').prop('disabled', true);
+ self.enable_disable_download_btn(true);
if (fetch_all) {
self.handler.trigger(
@@ -1382,7 +1382,7 @@ define('tools.querytool', [
.done(function(res) {
self.handler.has_more_rows = res.data.has_more_rows;
$('#btn-flash').prop('disabled', false);
- $('#btn-download').prop('disabled', false);
+ self.enable_disable_download_btn(false);
self.handler.trigger('pgadmin-sqleditor:loading-icon:hide');
self.update_grid_data(res.data.result);
self.handler.fetching_rows = false;
@@ -1392,7 +1392,7 @@ define('tools.querytool', [
})
.fail(function(e) {
$('#btn-flash').prop('disabled', false);
- $('#btn-download').prop('disabled', false);
+ self.enable_disable_download_btn(false);
self.handler.trigger('pgadmin-sqleditor:loading-icon:hide');
self.handler.has_more_rows = false;
self.handler.fetching_rows = false;
@@ -2534,6 +2534,7 @@ define('tools.querytool', [
self.server_type = url_params.server_type;
self.url_params = url_params;
self.is_transaction_buttons_disabled = true;
+ self.is_save_results_to_file_disabled = true;
// We do not allow to call the start multiple times.
if (self.gridView)
@@ -2783,7 +2784,7 @@ define('tools.querytool', [
);
$('#btn-flash').prop('disabled', true);
- $('#btn-download').prop('disabled', true);
+ self.enable_disable_download_btn(true);
self.trigger(
'pgadmin-sqleditor:loading-icon:message',
@@ -3058,7 +3059,12 @@ define('tools.querytool', [
// Hide the loading icon
self_col.trigger('pgadmin-sqleditor:loading-icon:hide');
$('#btn-flash').prop('disabled', false);
- $('#btn-download').prop('disabled', false);
+ if (!_.isUndefined(data) && Array.isArray(data.result) && data.result.length > 0) {
+ self.enable_disable_download_btn(false);
+ }
+ else {
+ self.enable_disable_download_btn(true);
+ }
}.bind(self)
);
},
@@ -3239,7 +3245,6 @@ define('tools.querytool', [
if (status != 'Busy') {
$('#btn-flash').prop('disabled', false);
- $('#btn-download').prop('disabled', false);
self.trigger('pgadmin-sqleditor:loading-icon:hide');
if(!self.total_time) {
@@ -3301,6 +3306,12 @@ define('tools.querytool', [
return (self.get('can_edit'));
},
+ enable_disable_download_btn: function(is_save_results_to_file_disabled) {
+ var self = this;
+ self.is_save_results_to_file_disabled = is_save_results_to_file_disabled;
+ $('#btn-save-results-to-file').prop('disabled', is_save_results_to_file_disabled);
+ },
+
rows_to_delete: function(data) {
let self = this;
let tmp_keys = self.primary_keys;
@@ -4371,7 +4382,6 @@ define('tools.querytool', [
if(!_.isUndefined(self.download_csv_obj)) {
self.download_csv_obj.abort();
$('#btn-flash').prop('disabled', false);
- $('#btn-download').prop('disabled', false);
self.trigger(
'pgadmin-sqleditor:loading-icon:hide');
}
@@ -4389,16 +4399,16 @@ define('tools.querytool', [
},
// Trigger query result download to csv.
- trigger_csv_download: function(query, filename) {
+ trigger_csv_download: function(filename) {
var self = this,
url = url_for('sqleditor.query_tool_download', {
'trans_id': self.transId,
}),
- data = { query: query, filename: filename };
+ data = { filename: filename };
// Disable the Execute button
$('#btn-flash').prop('disabled', true);
- $('#btn-download').prop('disabled', true);
+ self.enable_disable_download_btn(true);
self.disable_tool_buttons(true);
self.set_sql_message('');
self.trigger(
@@ -4443,14 +4453,14 @@ define('tools.querytool', [
// Enable the execute button
$('#btn-flash').prop('disabled', false);
- $('#btn-download').prop('disabled', false);
+ self.enable_disable_download_btn(false);
self.disable_tool_buttons(false);
self.trigger('pgadmin-sqleditor:loading-icon:hide');
}).fail(function(err) {
let msg = '';
// Enable the execute button
$('#btn-flash').prop('disabled', false);
- $('#btn-download').prop('disabled', false);
+ self.enable_disable_download_btn(false);
self.disable_tool_buttons(false);
self.trigger('pgadmin-sqleditor:loading-icon:hide');
diff --git a/web/pgadmin/utils/driver/psycopg2/connection.py b/web/pgadmin/utils/driver/psycopg2/connection.py
index 4f40e652b..f7eb957e4 100644
--- a/web/pgadmin/utils/driver/psycopg2/connection.py
+++ b/web/pgadmin/utils/driver/psycopg2/connection.py
@@ -728,10 +728,8 @@ WHERE db.datname = current_database()""")
if self.async_ == 1:
self._wait(cur.connection)
- def execute_on_server_as_csv(self,
- query, params=None,
- formatted_exception_msg=False,
- records=2000):
+ def execute_on_server_as_csv(self, params=None,
+ formatted_exception_msg=False, records=2000):
"""
To fetch query result and generate CSV output
@@ -743,39 +741,36 @@ WHERE db.datname = current_database()""")
Returns:
Generator response
"""
- status, cur = self.__cursor()
- self.row_count = 0
+ cur = self.__async_cursor
+ if not cur:
+ return False, self.CURSOR_NOT_FOUND
- if not status:
- return False, str(cur)
- query_id = random.randint(1, 9999999)
+ if self.conn.isexecuting():
+ return False, gettext(
+ "Asynchronous query execution/operation underway."
+ )
current_app.logger.log(
25,
"Execute (with server cursor) for server #{server_id} - "
- "{conn_id} (Query-id: {query_id}):\n{query}".format(
- server_id=self.manager.sid,
- conn_id=self.conn_id,
- query=query,
- query_id=query_id
- )
+ "{conn_id}".format(server_id=self.manager.sid,
+ conn_id=self.conn_id)
)
try:
# Unregistering type casting for large size data types.
unregister_numeric_typecasters(self.conn)
- self.__internal_blocking_execute(cur, query, params)
+ if self.async_ == 1:
+ self._wait(cur.connection)
except psycopg2.Error as pe:
cur.close()
errmsg = self._formatted_exception_msg(pe, formatted_exception_msg)
current_app.logger.error(
"failed to execute query ((with server cursor) "
"for the server #{server_id} - {conn_id} "
- "(query-id: {query_id}):\n"
- "error message:{errmsg}".format(
+ "\nerror message:{errmsg}".format(
server_id=self.manager.sid,
conn_id=self.conn_id,
errmsg=errmsg,
- query_id=query_id
)
)
return False, errmsg
@@ -809,13 +804,12 @@ WHERE db.datname = current_database()""")
return results
- def gen(quote='strings', quote_char="'", field_separator=',',
- replace_nulls_with=None):
+ def gen(trans_obj, quote='strings', quote_char="'",
+ field_separator=',', replace_nulls_with=None):
+ cur.scroll(0, mode='absolute')
results = cur.fetchmany(records)
if not results:
- if not cur.closed:
- cur.close()
yield gettext('The query executed did not return any data.')
return
@@ -857,8 +851,6 @@ WHERE db.datname = current_database()""")
results = cur.fetchmany(records)
if not results:
- if not cur.closed:
- cur.close()
break
res_io = StringIO()
@@ -874,9 +866,21 @@ WHERE db.datname = current_database()""")
results = handle_null_values(results, replace_nulls_with)
csv_writer.writerows(results)
yield res_io.getvalue()
+
+ try:
+ # try to reset the cursor scroll back to where it was,
+ # bypass error, if cannot scroll back
+ rows_fetched_from = trans_obj.get_fetched_row_cnt()
+ cur.scroll(rows_fetched_from, mode='absolute')
+ except psycopg2.Error:
+ # bypassing the error as cursor tried to scroll on the specified
+ # position, but end of records found
+ pass
+
# Registering back type caster for large size data types to string
# which was unregistered at starting
register_string_typecasters(self.conn)
+ # self.conn.autocommit = False
return True, gen
def execute_scalar(self, query, params=None,
@@ -1224,6 +1228,7 @@ WHERE db.datname = current_database()""")
Args:
records: no of records to fetch. use -1 to fetchall.
formatted_exception_msg:
+ for_download: if True, will fetch all records and reset the cursor
Returns:
diff --git a/web/pgadmin/utils/driver/psycopg2/cursor.py b/web/pgadmin/utils/driver/psycopg2/cursor.py
index 82ec3d592..b740750b6 100644
--- a/web/pgadmin/utils/driver/psycopg2/cursor.py
+++ b/web/pgadmin/utils/driver/psycopg2/cursor.py
@@ -228,6 +228,13 @@ class DictCursor(_cursor):
if tuples is not None:
return [self._dict_tuple(t) for t in tuples]
+ def rollback(self):
+ """
+
+ :return:
+ """
+ _cursor.rollback()
+
def __iter__(self):
it = _cursor.__iter__(self)
try: