-
Notifications
You must be signed in to change notification settings - Fork 14.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(sqllab): Format sql #25344
feat(sqllab): Format sql #25344
Changes from 5 commits
83cea9a
b0b6bca
53ef737
033e41f
bf9ad0e
f18f7ae
49a9da5
d341c71
7ef8a51
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -146,8 +146,10 @@ const AceEditorWrapper = ({ | |
}; | ||
|
||
const onChangeText = (text: string) => { | ||
setSql(text); | ||
onChange(text); | ||
if (text !== sql) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change prevents the overwrite the sql by |
||
setSql(text); | ||
onChange(text); | ||
} | ||
}; | ||
|
||
const { data: annotations } = useAnnotations({ | ||
|
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -72,6 +72,7 @@ import { | |||||||||
scheduleQuery, | ||||||||||
setActiveSouthPaneTab, | ||||||||||
updateSavedQuery, | ||||||||||
formatQuery, | ||||||||||
} from 'src/SqlLab/actions/sqlLab'; | ||||||||||
import { | ||||||||||
STATE_TYPE_MAP, | ||||||||||
|
@@ -305,6 +306,10 @@ const SqlEditor: React.FC<Props> = ({ | |||||||||
[ctas, database, defaultQueryLimit, dispatch, queryEditor], | ||||||||||
); | ||||||||||
|
||||||||||
const formatCurrentQuery = useCallback(() => { | ||||||||||
dispatch(formatQuery(queryEditor)); | ||||||||||
}, [dispatch, queryEditor]); | ||||||||||
|
||||||||||
const stopQuery = useCallback(() => { | ||||||||||
if (latestQuery && ['running', 'pending'].indexOf(latestQuery.state) >= 0) { | ||||||||||
dispatch(postStopQuery(latestQuery)); | ||||||||||
|
@@ -384,8 +389,16 @@ const SqlEditor: React.FC<Props> = ({ | |||||||||
}), | ||||||||||
func: stopQuery, | ||||||||||
}, | ||||||||||
{ | ||||||||||
name: 'formatQuery', | ||||||||||
key: KeyboardShortcut.CTRL_SHIFT_F, | ||||||||||
descr: KEY_MAP[KeyboardShortcut.CTRL_SHIFT_F], | ||||||||||
func: () => { | ||||||||||
formatCurrentQuery(); | ||||||||||
}, | ||||||||||
Comment on lines
+396
to
+398
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
}, | ||||||||||
]; | ||||||||||
}, [dispatch, queryEditor.sql, startQuery, stopQuery]); | ||||||||||
}, [dispatch, queryEditor.sql, startQuery, stopQuery, formatCurrentQuery]); | ||||||||||
|
||||||||||
const hotkeys = useMemo(() => { | ||||||||||
// Get all hotkeys including ace editor hotkeys | ||||||||||
|
@@ -602,7 +615,7 @@ const SqlEditor: React.FC<Props> = ({ | |||||||||
? t('Schedule the query periodically') | ||||||||||
: t('You must run the query successfully first'); | ||||||||||
return ( | ||||||||||
<Menu css={{ width: theme.gridUnit * 44 }}> | ||||||||||
<Menu css={{ width: theme.gridUnit * 50 }}> | ||||||||||
<Menu.Item css={{ display: 'flex', justifyContent: 'space-between' }}> | ||||||||||
{' '} | ||||||||||
<span>{t('Autocomplete')}</span>{' '} | ||||||||||
|
@@ -622,6 +635,7 @@ const SqlEditor: React.FC<Props> = ({ | |||||||||
/> | ||||||||||
</Menu.Item> | ||||||||||
)} | ||||||||||
<Menu.Item onClick={formatCurrentQuery}>{t('Format SQL')}</Menu.Item> | ||||||||||
{!isEmpty(scheduledQueriesConf) && ( | ||||||||||
<Menu.Item> | ||||||||||
<ScheduleQueryButton | ||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,9 @@ | |
from urllib import parse | ||
|
||
import simplejson as json | ||
import sqlparse | ||
from flask import request, Response | ||
from flask_appbuilder import permission_name | ||
from flask_appbuilder.api import expose, protect, rison, safe | ||
from flask_appbuilder.models.sqla.interface import SQLAInterface | ||
from marshmallow import ValidationError | ||
|
@@ -46,6 +48,7 @@ | |
from superset.sqllab.schemas import ( | ||
EstimateQueryCostSchema, | ||
ExecutePayloadSchema, | ||
FormatQueryPayloadSchema, | ||
QueryExecutionResponseSchema, | ||
sql_lab_get_results_schema, | ||
SQLLabBootstrapSchema, | ||
|
@@ -78,6 +81,7 @@ class SqlLabRestApi(BaseSupersetApi): | |
|
||
estimate_model_schema = EstimateQueryCostSchema() | ||
execute_model_schema = ExecutePayloadSchema() | ||
format_model_schema = FormatQueryPayloadSchema() | ||
|
||
apispec_parameter_schemas = { | ||
"sql_lab_get_results_schema": sql_lab_get_results_schema, | ||
|
@@ -185,6 +189,54 @@ def estimate_query_cost(self) -> Response: | |
result = command.run() | ||
return self.response(200, result=result) | ||
|
||
@expose("/format/", methods=("POST",)) | ||
@statsd_metrics | ||
@protect() | ||
@permission_name("read") | ||
@event_logger.log_this_with_context( | ||
action=lambda self, *args, **kwargs: f"{self.__class__.__name__}" f".format", | ||
log_to_statsd=False, | ||
) | ||
def format(self) -> FlaskResponse: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe rename the endpoint to |
||
"""Format the SQL query. | ||
--- | ||
post: | ||
summary: Format SQL code | ||
requestBody: | ||
description: SQL query | ||
required: true | ||
content: | ||
application/json: | ||
schema: | ||
$ref: '#/components/schemas/FormatQueryPayloadSchema' | ||
responses: | ||
200: | ||
description: Format SQL result | ||
content: | ||
application/json: | ||
schema: | ||
type: object | ||
properties: | ||
result: | ||
type: string | ||
400: | ||
$ref: '#/components/responses/400' | ||
401: | ||
$ref: '#/components/responses/401' | ||
403: | ||
$ref: '#/components/responses/403' | ||
500: | ||
$ref: '#/components/responses/500' | ||
""" | ||
try: | ||
model = self.format_model_schema.load(request.json) | ||
result = sqlparse.format( | ||
model.get("sql", ""), reindent=True, keyword_case="upper" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the |
||
) | ||
return self.response(200, result=result) | ||
except ValidationError as error: | ||
return self.response_400(message=error.messages) | ||
|
||
@expose("/export/<string:client_id>/") | ||
@protect() | ||
@statsd_metrics | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why couldn't we just us a npm package to do this work vs. creating a whole new endpoint?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hughhhh We chose server-side formatting over client-side formatting for several reasons:
If you and other community members believe that an NPM package solution is better, I am happy to make changes.
@betodealmeida @eschutho @michael-s-molina do you have an opinion on the frontend approach?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There seems to be a number of pros with having this on the backend including: custom formatting, dialect specific formatting, and consistency throughout the application.
@villebro has experience working with frontend SQL formatting and ran into a number of issues.