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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 182 additions and 144 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 190 KiB

View File

@ -486,26 +486,27 @@ Query Tool window navigation:
Use the fields on the *SQL formatting* panel to specify your preferences for
reformatting of SQL.
* Use the *Comma-first notation* option to specify whether to place commas
before or after column names.
* Use the *Data type case* option to specify whether to change data types
into upper, lower, or preserve case.
* Use the *Expression width* option to specify maximum number of characters
in parenthesized expressions to be kept on single line.
* Use the *Function case* option to specify whether to change function
names into upper, lower, or preserve case.
* 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.
upper, lower, or preserve case.
* Use *Lines between queries* to specify how many empty lines to leave
between SQL statements. If set to zero it puts no new line.
* Use *Logical operator new line* to specify newline placement before or
after logical operators (AND, OR, XOR).
* Use *New line before semicolon?* to specify whether to place query
separator (;) on a separate line.
* 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
********************

View File

@ -152,6 +152,7 @@
"snapsvg-cjs": "^0.0.6",
"socket.io-client": "^4.5.0",
"split.js": "^1.5.10",
"sql-formatter": "^15.1.2",
"styled-components": "^5.2.1",
"uplot": "^1.6.24",
"uplot-react": "^1.1.4",

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',

View File

@ -6237,7 +6237,7 @@ __metadata:
languageName: node
linkType: hard
"commander@npm:^2.20.0, commander@npm:^2.8.1":
"commander@npm:^2.19.0, commander@npm:^2.20.0, commander@npm:^2.8.1":
version: 2.20.3
resolution: "commander@npm:2.20.3"
checksum: ab8c07884e42c3a8dbc5dd9592c606176c7eb5c1ca5ff274bcf907039b2c41de3626f684ea75ccf4d361ba004bbaff1f577d5384c155f3871e456bdf27becf9e
@ -7260,6 +7260,13 @@ __metadata:
languageName: node
linkType: hard
"discontinuous-range@npm:1.0.0":
version: 1.0.0
resolution: "discontinuous-range@npm:1.0.0"
checksum: 8ee88d7082445b6eadc7c03bebe6dc978f96760c45e9f65d16ca66174d9e086a9e3855ee16acf65625e1a07a846a17de674f02a5964a6aebe5963662baf8b5c8
languageName: node
linkType: hard
"dnd-core@npm:14.0.1":
version: 14.0.1
resolution: "dnd-core@npm:14.0.1"
@ -8714,6 +8721,13 @@ __metadata:
languageName: node
linkType: hard
"get-stdin@npm:=8.0.0":
version: 8.0.0
resolution: "get-stdin@npm:8.0.0"
checksum: 40128b6cd25781ddbd233344f1a1e4006d4284906191ed0a7d55ec2c1a3e44d650f280b2c9eeab79c03ac3037da80257476c0e4e5af38ddfb902d6ff06282d77
languageName: node
linkType: hard
"get-stream@npm:3.0.0, get-stream@npm:^3.0.0":
version: 3.0.0
resolution: "get-stream@npm:3.0.0"
@ -11877,6 +11891,13 @@ __metadata:
languageName: node
linkType: hard
"moo@npm:^0.5.0":
version: 0.5.2
resolution: "moo@npm:0.5.2"
checksum: 5a41ddf1059fd0feb674d917c4774e41c877f1ca980253be4d3aae1a37f4bc513f88815041243f36f5cf67a62fb39324f3f997cf7fb17b6cb00767c165e7c499
languageName: node
linkType: hard
"mousetrap@npm:^1.6.3":
version: 1.6.5
resolution: "mousetrap@npm:1.6.5"
@ -11956,6 +11977,23 @@ __metadata:
languageName: node
linkType: hard
"nearley@npm:^2.20.1":
version: 2.20.1
resolution: "nearley@npm:2.20.1"
dependencies:
commander: ^2.19.0
moo: ^0.5.0
railroad-diagrams: ^1.0.0
randexp: 0.4.6
bin:
nearley-railroad: bin/nearley-railroad.js
nearley-test: bin/nearley-test.js
nearley-unparse: bin/nearley-unparse.js
nearleyc: bin/nearleyc.js
checksum: 42c2c330c13c7991b48221c5df00f4352c2f8851636ae4d1f8ca3c8e193fc1b7668c78011d1cad88cca4c1c4dc087425420629c19cc286d7598ec15533aaef26
languageName: node
linkType: hard
"neatequal@npm:^1.0.0":
version: 1.0.0
resolution: "neatequal@npm:1.0.0"
@ -13709,6 +13747,23 @@ __metadata:
languageName: node
linkType: hard
"railroad-diagrams@npm:^1.0.0":
version: 1.0.0
resolution: "railroad-diagrams@npm:1.0.0"
checksum: 9e312af352b5ed89c2118edc0c06cef2cc039681817f65266719606e4e91ff6ae5374c707cc9033fe29a82c2703edf3c63471664f97f0167c85daf6f93496319
languageName: node
linkType: hard
"randexp@npm:0.4.6":
version: 0.4.6
resolution: "randexp@npm:0.4.6"
dependencies:
discontinuous-range: 1.0.0
ret: ~0.1.10
checksum: 3c0d440a3f89d6d36844aa4dd57b5cdb0cab938a41956a16da743d3a3578ab32538fc41c16cc0984b6938f2ae4cbc0216967e9829e52191f70e32690d8e3445d
languageName: node
linkType: hard
"randombytes@npm:^2.0.0, randombytes@npm:^2.0.1, randombytes@npm:^2.0.5, randombytes@npm:^2.1.0":
version: 2.1.0
resolution: "randombytes@npm:2.1.0"
@ -14676,6 +14731,13 @@ __metadata:
languageName: node
linkType: hard
"ret@npm:~0.1.10":
version: 0.1.15
resolution: "ret@npm:0.1.15"
checksum: d76a9159eb8c946586567bd934358dfc08a36367b3257f7a3d7255fdd7b56597235af23c6afa0d7f0254159e8051f93c918809962ebd6df24ca2a83dbe4d4151
languageName: node
linkType: hard
"retry@npm:^0.12.0":
version: 0.12.0
resolution: "retry@npm:0.12.0"
@ -14868,6 +14930,7 @@ __metadata:
snapsvg-cjs: ^0.0.6
socket.io-client: ^4.5.0
split.js: ^1.5.10
sql-formatter: ^15.1.2
style-loader: ^3.3.2
styled-components: ^5.2.1
stylis: ^4.0.7
@ -15508,6 +15571,19 @@ __metadata:
languageName: node
linkType: hard
"sql-formatter@npm:^15.1.2":
version: 15.1.2
resolution: "sql-formatter@npm:15.1.2"
dependencies:
argparse: ^2.0.1
get-stdin: =8.0.0
nearley: ^2.20.1
bin:
sql-formatter: bin/sql-formatter-cli.cjs
checksum: 77379dd209bd65bac89f53d28e8e409c32878f008ec37572dcee59231db306c690771cf3a318e02a84a920d8059f22bb1e30d197ea29c1abecbfcd96c3e672ff
languageName: node
linkType: hard
"ssri@npm:^10.0.0":
version: 10.0.4
resolution: "ssri@npm:10.0.4"