Added SQL Formatter support in Query Tool. Fixes #2042

This commit is contained in:
Dave Page authored and Akshay Joshi committed 2020-08-20 13:05:00 +05:30
1 parent cc5a7ea334
commit 79e6480513
12 files changed
+280 -32

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 KiB

After

Width:  |  Height:  |  Size: 208 KiB

+31 -9
View File
@@ -306,12 +306,6 @@ Use the fields on the *Editor* panel to change settings of the query editor.
changed to text/plain. Keyword highlighting and code folding will be disabled. changed to text/plain. Keyword highlighting and code folding will be disabled.
This will improve editor performance with large files. This will improve editor performance with large files.
* Use the *Tab size* field to specify the number of spaces per tab character in
the editor.
* When the *Use spaces* switch is set to *True*, the editor will insert spaces
(instead of tab characters) when the tab key or auto-indent are used.
.. image:: images/preferences_sql_explain.png .. image:: images/preferences_sql_explain.png
:alt: Preferences dialog sqleditor explain options :alt: Preferences dialog sqleditor explain options
:align: center :align: center
@@ -375,13 +369,41 @@ preferences for copied data.
* Use the *Result copy quoting* drop-down listbox to select which type of fields * Use the *Result copy quoting* drop-down listbox to select which type of fields
require quoting; select *All*, *None*, or *Strings*. require quoting; select *All*, *None*, or *Strings*.
Use the fields on the *Keyboard shortcuts* panel to configure shortcuts for the
Query Tool window navigation:
.. image:: images/preferences_sql_keyboard_shortcuts.png .. image:: images/preferences_sql_keyboard_shortcuts.png
:alt: Preferences dialog sql keyboard shortcuts section :alt: Preferences dialog sql keyboard shortcuts section
:align: center :align: center
Use the fields on the *Keyboard shortcuts* panel to configure shortcuts for the
Query Tool window navigation:
.. image:: images/preferences_sql_formatting.png
:alt: Preferences dialog SQL Formatting section
:align: center
Use the fields on the *SQL formatting* panel to specify your preferences for
reformatting of SQL.
* Use the *Command-first notation* option to specify whether to place commas
before or after column names.
* Use the *Identifier case* option to specify whether to change identifiers
(object names) into upper, lower, or capitalized case.
* Use the *Keyword case* option to specify whether to change keywords into
upper, lower, or capitalized case.
* Use the *Re-indent aligned?* option to specify that indentations of statements
should be changed, aligned by keywords.
* Use the *Re-indent?* option to specify that indentations of statements should
be changed.
* Use the *Spaces around operators?* option to specify whether or not to include
spaces on either side of operators.
* Use the *Strip comments?* option to specify whether or not comments should be
removed.
* Use the *Tab size* option to specify the number of spaces per tab or indent.
* Use the *Use spaces?* option to select whether to use spaces or tabs when
indenting.
* Use the *Wrap after N characters* option to specify the column limit for
wrapping column separated lists (e.g. of column names in a table). If set to
0 (zero), each item will be on it's own line.
The Schema Diff Node The Schema Diff Node
******************** ********************
+2
View File
@@ -95,6 +95,8 @@ Editing Options
| +---------------------------------------------------------------------------------------------------+----------------+ | +---------------------------------------------------------------------------------------------------+----------------+
| | Select *Block Comment* to enclose all lines that contain the selection in C style comment | Shift+Cmd+/ | | | Select *Block Comment* to enclose all lines that contain the selection in C style comment | Shift+Cmd+/ |
| | notation. This option acts as a toggle. | | | | notation. This option acts as a toggle. | |
| +---------------------------------------------------------------------------------------------------+----------------+
| | Select *Format SQL* to format the selected SQL or all the SQL if none is selected | Shift+Cmd+K |
+----------------------+---------------------------------------------------------------------------------------------------+----------------+ +----------------------+---------------------------------------------------------------------------------------------------+----------------+
View/Edit Data Resultset Control View/Edit Data Resultset Control
+1
View File
@@ -11,6 +11,7 @@ notes for it.
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
release_notes_4_26
release_notes_4_25 release_notes_4_25
release_notes_4_24 release_notes_4_24
release_notes_4_23 release_notes_4_23
+20
View File
@@ -0,0 +1,20 @@
************
Version 4.26
************
Release date: 2020-09-17
This release contains a number of bug fixes and new features since the release of pgAdmin4 4.25.
New features
************
| `Issue #2042 <https://redmine.postgresql.org/issues/2042>`_ - Added SQL Formatter support in Query Tool.
Housekeeping
************
Bug fixes
*********
+60 -1
View File
@@ -9,8 +9,12 @@
"""A blueprint module providing utility functions for the application.""" """A blueprint module providing utility functions for the application."""
from flask import url_for import sqlparse
from flask import request, url_for
from flask_security import login_required
from pgadmin.utils import PgAdminModule from pgadmin.utils import PgAdminModule
from pgadmin.utils.ajax import make_json_response
from pgadmin.utils.preferences import Preferences
MODULE_NAME = 'sql' MODULE_NAME = 'sql'
@@ -23,6 +27,61 @@ class SQLModule(PgAdminModule):
'when': None 'when': None
}] }]
def get_exposed_url_endpoints(self):
"""
Returns:
list: URL endpoints
"""
return [
'sql.format', 'sql.format'
]
# Initialise the module # Initialise the module
blueprint = SQLModule(MODULE_NAME, __name__, url_prefix='/misc/sql') blueprint = SQLModule(MODULE_NAME, __name__, url_prefix='/misc/sql')
def sql_format(sql):
"""
This function takes a SQL statement, formats it, and returns it
"""
p = Preferences.module('sqleditor')
output = sqlparse.format(sql,
keyword_case=p.preference(
'keyword_case').get(),
identifier_case=p.preference(
'identifier_case').get(),
strip_comments=p.preference(
'strip_comments').get(),
reindent=p.preference(
'reindent').get(),
reindent_aligned=p.preference(
'reindent_aligned').get(),
use_space_around_operators=p.preference(
'spaces_around_operators').get(),
comma_first=p.preference(
'comma_first').get(),
wrap_after=p.preference(
'wrap_after').get(),
indent_tabs=not p.preference(
'use_spaces').get(),
indent_width=p.preference(
'tab_size').get())
return output
@blueprint.route("/format", methods=['POST'], endpoint="format")
@login_required
def sql_format_wrapper():
"""
This endpoint takes a SQL statement, formats it, and returns it
"""
sql = ''
if request.data:
sql = sql_format(request.get_json()['sql'])
return make_json_response(
data={'sql': sql},
status=200
)
+8 -1
View File
@@ -17,7 +17,8 @@ const PERIOD_KEY = 190,
LEFT_KEY = 37, LEFT_KEY = 37,
UP_KEY = 38, UP_KEY = 38,
RIGHT_KEY = 39, RIGHT_KEY = 39,
DOWN_KEY = 40; DOWN_KEY = 40,
K_KEY = 75;
function isMac() { function isMac() {
return window.navigator.platform.search('Mac') != -1; return window.navigator.platform.search('Mac') != -1;
@@ -247,6 +248,12 @@ function keyboardShortcutsQueryTool(
) && keyCode === PERIOD_KEY) { ) && keyCode === PERIOD_KEY) {
this._stopEventPropagation(event); this._stopEventPropagation(event);
queryToolActions.uncommentLineCode(sqlEditorController); queryToolActions.uncommentLineCode(sqlEditorController);
} else if ((
(this.isMac() && event.metaKey) ||
(!this.isMac() && event.ctrlKey)
) && !event.altKey && event.shiftKey && keyCode === K_KEY) {
this._stopEventPropagation(event);
queryToolActions.formatSql(sqlEditorController);
} else if (keyCode == ESC_KEY) { } else if (keyCode == ESC_KEY) {
queryToolActions.focusOut(sqlEditorController); queryToolActions.focusOut(sqlEditorController);
/*Apply only for sub-dropdown*/ /*Apply only for sub-dropdown*/
@@ -123,6 +123,10 @@ let queryToolActions = {
); );
}, },
formatSql: function (sqlEditorController) {
sqlEditorController.gridView.on_format_sql();
},
focusOut: function () { focusOut: function () {
document.activeElement.blur(); document.activeElement.blur();
pgWindow.document.activeElement.blur(); pgWindow.document.activeElement.blur();
@@ -189,6 +189,15 @@
{{ _(' (Shift+Ctrl+/)') }}{%- endif %}</span> {{ _(' (Shift+Ctrl+/)') }}{%- endif %}</span>
</a> </a>
</li> </li>
<li class="dropdown-divider"></li>
<li>
<a class="dropdown-item" id="btn-format-sql" href="#" tabindex="0">
<span>{{ _('Format SQL') }}{% if client_platform == 'macos' -%}
{{ _(' (Shift+Cmd+K)') }}
{% else %}
{{ _(' (Shift+Ctrl+K)') }}{%- endif %}</span>
</a>
</li>
</ul> </ul>
</div> </div>
<div class="btn-group mr-1" role="group" aria-label=""> <div class="btn-group mr-1" role="group" aria-label="">
@@ -140,6 +140,9 @@ define('tools.querytool', [
// Indentation options // Indentation options
'click #btn-indent-code': 'on_indent_code', 'click #btn-indent-code': 'on_indent_code',
'click #btn-unindent-code': 'on_unindent_code', 'click #btn-unindent-code': 'on_unindent_code',
// Format
'click #btn-format-sql': 'on_format_sql',
// Transaction control
'click #btn-commit': 'on_commit_transaction', 'click #btn-commit': 'on_commit_transaction',
'click #btn-rollback': 'on_rollback_transaction', 'click #btn-rollback': 'on_rollback_transaction',
}, },
@@ -1743,6 +1746,16 @@ define('tools.querytool', [
); );
}, },
on_format_sql: function() {
var self = this;
// Trigger the format signal to the SqlEditorController class
self.handler.trigger(
'pgadmin-sqleditor:format_sql',
self,
self.handler
);
},
// Callback function for the clear button click. // Callback function for the clear button click.
on_clear: function(ev) { on_clear: function(ev) {
var self = this; var self = this;
@@ -2401,6 +2414,8 @@ define('tools.querytool', [
// Indentation related // Indentation related
self.on('pgadmin-sqleditor:indent_selected_code', self._indent_selected_code, self); self.on('pgadmin-sqleditor:indent_selected_code', self._indent_selected_code, self);
self.on('pgadmin-sqleditor:unindent_selected_code', self._unindent_selected_code, self); self.on('pgadmin-sqleditor:unindent_selected_code', self._unindent_selected_code, self);
// Format
self.on('pgadmin-sqleditor:format_sql', self._format_sql, self);
window.parent.$(window.parent.document).on('pgadmin-sqleditor:rows-copied', self._copied_in_other_session); window.parent.$(window.parent.document).on('pgadmin-sqleditor:rows-copied', self._copied_in_other_session);
}, },
@@ -4230,6 +4245,41 @@ define('tools.querytool', [
editor.execCommand('indentLess'); editor.execCommand('indentLess');
}, },
/*
* This function will format the SQL
*/
_format_sql: function() {
var self = this,
editor = self.gridView.query_tool_obj,
selection = true,
sql = '';
sql = editor.getSelection();
if (sql == '') {
sql = editor.getValue();
selection = false;
}
$.ajax({
url: url_for('sql.format'),
data: JSON.stringify({'sql': sql}),
method: 'POST',
contentType: 'application/json',
dataType: 'json',
})
.done(function(res) {
if (selection === true) {
editor.replaceSelection(res.data.sql, 'around');
} else {
editor.setValue(res.data.sql);
}
})
.fail(function() {
/* failure should be ignored */
});
},
isQueryRunning: function() { isQueryRunning: function() {
return is_query_running; return is_query_running;
}, },
@@ -169,27 +169,6 @@ def register_query_tool_preferences(self):
) )
) )
self.tab_size = self.preference.register(
'Editor', 'tab_size',
gettext("Tab size"), 'integer', 4,
min_val=2,
max_val=8,
category_label=gettext('Options'),
help_str=gettext(
'The number of spaces per tab. Minimum 2, maximum 8.'
)
)
self.use_spaces = self.preference.register(
'Editor', 'use_spaces',
gettext("Use spaces?"), 'boolean', False,
category_label=gettext('Options'),
help_str=gettext(
'Specifies whether or not to insert spaces instead of tabs '
'when the tab key or auto-indent are used.'
)
)
self.wrap_code = self.preference.register( self.wrap_code = self.preference.register(
'Editor', 'wrap_code', 'Editor', 'wrap_code',
gettext("Line wrapping?"), 'boolean', False, gettext("Line wrapping?"), 'boolean', False,
@@ -703,3 +682,98 @@ def register_query_tool_preferences(self):
category_label=gettext('Keyboard shortcuts'), category_label=gettext('Keyboard shortcuts'),
fields=shortcut_fields fields=shortcut_fields
) )
# Register options for SQL formatting
self.keyword_case = self.preference.register(
'editor', 'keyword_case',
gettext("Keyword case"), 'radioModern', 'upper',
options=[{'label': 'Upper case', 'value': 'upper'},
{'label': 'Lower case', 'value': 'lower'},
{'label': 'Capitalized', 'value': 'capitalize'}],
category_label=gettext('SQL formatting'),
help_str=gettext(
'Convert keywords to upper, lower, or capitalized casing.'
)
)
self.identifier_case = self.preference.register(
'editor', 'identifier_case',
gettext("Identifier case"), 'radioModern', 'upper',
options=[{'label': 'Upper case', 'value': 'upper'},
{'label': 'Lower case', 'value': 'lower'},
{'label': 'Capitalized', 'value': 'capitalize'}],
category_label=gettext('SQL formatting'),
help_str=gettext(
'Convert identifiers to upper, lower, or capitalized casing.'
)
)
self.strip_comments = self.preference.register(
'editor', 'strip_comments',
gettext("Strip comments?"), 'boolean', False,
category_label=gettext('SQL formatting'),
help_str=gettext('If set to True, comments will be removed.')
)
self.reindent = self.preference.register(
'editor', 'reindent',
gettext("Re-indent?"), 'boolean', True,
category_label=gettext('SQL formatting'),
help_str=gettext('If set to True, the indentations of the '
'statements are changed.')
)
self.reindent_aligned = self.preference.register(
'editor', 'reindent_aligned',
gettext("Re-indent aligned?"), 'boolean', False,
category_label=gettext('SQL formatting'),
help_str=gettext('If set to True, the indentations of the '
'statements are changed, and statements are '
'aligned by keywords.')
)
self.spaces_around_operators = self.preference.register(
'editor', 'spaces_around_operators',
gettext("Spaces around operators?"), 'boolean', True,
category_label=gettext('SQL formatting'),
help_str=gettext('If set to True, spaces are used around all '
'operators.')
)
self.comma_first = self.preference.register(
'editor', 'comma_first',
gettext("Comma-first notation?"), 'boolean', False,
category_label=gettext('SQL formatting'),
help_str=gettext('If set to True, comma-first notation for column '
'names is used.')
)
self.wrap_after = self.preference.register(
'editor', 'wrap_after',
gettext("Wrap after N characters"), 'integer', 4,
category_label=gettext('SQL formatting'),
help_str=gettext("The column limit (in characters) for wrapping "
"comma-separated lists. If zero, it puts "
"every item in the list on its own line.")
)
self.tab_size = self.preference.register(
'editor', 'tab_size',
gettext("Tab size"), 'integer', 4,
min_val=2,
max_val=8,
category_label=gettext('SQL formatting'),
help_str=gettext(
'The number of spaces per tab. Minimum 2, maximum 8.'
)
)
self.use_spaces = self.preference.register(
'editor', 'use_spaces',
gettext("Use spaces?"), 'boolean', False,
category_label=gettext('SQL formatting'),
help_str=gettext(
'Specifies whether or not to insert spaces instead of tabs '
'when the tab key or auto-indent are used.'
)
)