Fixed an issue where format sql was messing up operator. #5083

- The formatting is done on client side now using sql-formattor JS lib. Backend API based formatting is removed.
- Added new options data type case, expression width, function case, lines between queries, logical operator new line and new line before semicolon available in the new lib.
- Removed old options comma-first notations, re-indent aligned, re-indent, strip comments and wrap after N characters as not available in the new library.
- Capitalise casing is replaced with Preserve casing as it is not supported by the new library.
- Also fixes #6785, #6990, #7115.
This commit is contained in:
Pravesh Sharma
2024-02-02 12:28:25 +05:30
committed by GitHub
parent 459121566c
commit f7045b58d4
8 changed files with 182 additions and 144 deletions

View File

@@ -120,9 +120,6 @@ class MiscModule(PgAdminModule):
from .file_manager import blueprint as module
self.submodules.append(module)
from .sql import blueprint as module
self.submodules.append(module)
from .statistics import blueprint as module
self.submodules.append(module)

View File

@@ -1,80 +0,0 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2024, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
"""A blueprint module providing utility functions for the application."""
import sqlparse
from flask import request, url_for
from flask_security import login_required
from pgadmin.utils import PgAdminModule
from pgadmin.utils.ajax import make_json_response
from pgadmin.utils.preferences import Preferences
MODULE_NAME = 'sql'
class SQLModule(PgAdminModule):
def get_exposed_url_endpoints(self):
"""
Returns:
list: URL endpoints
"""
return [
'sql.format', 'sql.format'
]
# Initialise the module
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')
use_spaces = p.preference('use_spaces').get()
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 use_spaces,
indent_width=p.preference(
'tab_size').get() if use_spaces else 1)
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
)

View File

@@ -8,6 +8,7 @@
//////////////////////////////////////////////////////////////
import { makeStyles } from '@material-ui/styles';
import React, {useContext, useCallback, useEffect } from 'react';
import { format } from 'sql-formatter';
import { QueryToolContext, QueryToolEventsContext } from '../QueryToolComponent';
import CodeMirror from '../../../../../../static/js/components/CodeMirror';
import {PANELS, QUERY_TOOL_EVENTS} from '../QueryToolConstants';
@@ -444,19 +445,35 @@ export default function Query() {
});
eventBus.registerListener(QUERY_TOOL_EVENTS.TRIGGER_FORMAT_SQL, ()=>{
let selection = true, sql = editor.current?.getSelection();
let sqlEditorPref = preferencesStore.getPreferencesForModule('sqleditor');
/* New library does not support capitalize casing
so if a user has set capitalize casing we will
use preserve casing which is default for the library.
*/
let formatPrefs = {
language: 'postgresql',
keywordCase: sqlEditorPref.keyword_case === 'capitalize' ? 'preserve' : sqlEditorPref.keyword_case,
identifierCase: sqlEditorPref.identifier_case === 'capitalize' ? 'preserve' : sqlEditorPref.identifier_case,
dataTypeCase: sqlEditorPref.data_type_case,
functionCase: sqlEditorPref.function_case,
logicalOperatorNewline: sqlEditorPref.logical_operator_new_line,
expressionWidth: sqlEditorPref.expression_width,
linesBetweenQueries: sqlEditorPref.lines_between_queries,
tabWidth: sqlEditorPref.tab_size,
useTabs: !sqlEditorPref.use_spaces,
denseOperators: !sqlEditorPref.spaces_around_operators,
newlineBeforeSemicolon: sqlEditorPref.new_line_before_semicolon
};
if(sql == '') {
sql = editor.current.getValue();
selection = false;
}
queryToolCtx.api.post(url_for('sql.format'), {
'sql': sql,
}).then((res)=>{
if(selection) {
editor.current.replaceSelection(res.data.data.sql, 'around');
} else {
editor.current.setValue(res.data.data.sql);
}
}).catch(()=>{/* failure should be ignored */});
let formattedSql = format(sql,formatPrefs);
if(selection) {
editor.current.replaceSelection(formattedSql, 'around');
} else {
editor.current.setValue(formattedSql);
}
});
eventBus.registerListener(QUERY_TOOL_EVENTS.EDITOR_TOGGLE_CASE, ()=>{
let selectedText = editor.current?.getSelection();

View File

@@ -735,10 +735,10 @@ def register_query_tool_preferences(self):
gettext("Keyword case"), 'radioModern', 'upper',
options=[{'label': gettext('Upper case'), 'value': 'upper'},
{'label': gettext('Lower case'), 'value': 'lower'},
{'label': gettext('Capitalized'), 'value': 'capitalize'}],
{'label': gettext('Preserve'), 'value': 'preserve'}],
category_label=PREF_LABEL_SQL_FORMATTING,
help_str=gettext(
'Convert keywords to upper, lower, or capitalized casing.'
'Convert keywords to upper, lower, or preserve casing.'
)
)
@@ -747,35 +747,35 @@ def register_query_tool_preferences(self):
gettext("Identifier case"), 'radioModern', 'upper',
options=[{'label': gettext('Upper case'), 'value': 'upper'},
{'label': gettext('Lower case'), 'value': 'lower'},
{'label': gettext('Capitalized'), 'value': 'capitalize'}],
{'label': gettext('Preserve'), 'value': 'preserve'}],
category_label=PREF_LABEL_SQL_FORMATTING,
help_str=gettext(
'Convert identifiers to upper, lower, or capitalized casing.'
'Convert identifiers to upper, lower, or preserve casing.'
)
)
self.strip_comments = self.preference.register(
'editor', 'strip_comments',
gettext("Strip comments?"), 'boolean', False,
self.function_case = self.preference.register(
'editor', 'function_case',
gettext("Function case"), 'radioModern', 'upper',
options=[{'label': gettext('Upper case'), 'value': 'upper'},
{'label': gettext('Lower case'), 'value': 'lower'},
{'label': gettext('Preserve'), 'value': 'preserve'}],
category_label=PREF_LABEL_SQL_FORMATTING,
help_str=gettext('If set to True, comments will be removed.')
help_str=gettext(
'Convert function names to upper, lower, or preserve casing.'
)
)
self.reindent = self.preference.register(
'editor', 'reindent',
gettext("Re-indent?"), 'boolean', True,
self.data_type_case = self.preference.register(
'editor', 'data_type_case',
gettext("Data type case"), 'radioModern', 'upper',
options=[{'label': gettext('Upper case'), 'value': 'upper'},
{'label': gettext('Lower case'), 'value': 'lower'},
{'label': gettext('Preserve'), 'value': 'preserve'}],
category_label=PREF_LABEL_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=PREF_LABEL_SQL_FORMATTING,
help_str=gettext('If set to True, the indentations of the '
'statements are changed, and statements are '
'aligned by keywords.')
help_str=gettext(
'Convert data types to upper, lower, or preserve casing.'
)
)
self.spaces_around_operators = self.preference.register(
@@ -786,23 +786,6 @@ def register_query_tool_preferences(self):
'operators.')
)
self.comma_first = self.preference.register(
'editor', 'comma_first',
gettext("Comma-first notation?"), 'boolean', False,
category_label=PREF_LABEL_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=PREF_LABEL_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,
@@ -824,6 +807,49 @@ def register_query_tool_preferences(self):
)
)
self.expression_width = self.preference.register(
'editor', 'expression_width',
gettext("Expression Width"), 'integer', 50,
category_label=PREF_LABEL_SQL_FORMATTING,
help_str=gettext(
'maximum number of characters in parenthesized expressions to be '
'kept on single line.'
)
)
self.logical_operator_new_line = self.preference.register(
'editor', 'logical_operator_new_line',
gettext("Logical operator new line"), 'radioModern', 'before',
options=[{'label': gettext('Before'), 'value': 'before'},
{'label': gettext('After'), 'value': 'after'}],
category_label=PREF_LABEL_SQL_FORMATTING,
help_str=gettext(
'Decides newline placement before or after logical operators '
'(AND, OR, XOR).'
)
)
self.lines_between_queries = self.preference.register(
'editor', 'lines_between_queries',
gettext("Lines between queries"), 'integer', 1,
min_val=0,
max_val=5,
category_label=PREF_LABEL_SQL_FORMATTING,
help_str=gettext(
'Decides how many empty lines to leave between SQL statements. '
'If zero it puts no new line.'
)
)
self.new_line_before_semicolon = self.preference.register(
'editor', 'new_line_before_semicolon',
gettext("New line before semicolon?"), 'boolean', False,
category_label=PREF_LABEL_SQL_FORMATTING,
help_str=gettext(
'Whether to place query separator (;) on a separate line.'
)
)
self.row_limit = self.preference.register(
'graph_visualiser', 'row_limit',
gettext("Row Limit"), 'integer',