chore: Use sql-formatter for formatting all SQL in the studio (#33071)

* Bump the sql-formatter dependency.

* Add a function formatQuery which is a wrapper around sql-formatter.

* Replace all formatSQL features to use the new function formatSql.

* Remove unneeded RQ for formatting.

* Small fix.
This commit is contained in:
Ivan Vasilov authored and GitHub committed 2025-01-27 13:04:31 +01:00
1 parent dbd6c3866d
commit 98ca85156a
15 files changed
+65 -221

No files matched your search

@@ -1,12 +1,12 @@
import Editor, { Monaco, OnChange, OnMount, useMonaco } from '@monaco-editor/react'
import { noop } from 'lodash'
import type { editor } from 'monaco-editor'
import { MutableRefObject, useEffect, useRef } from 'react'
import { cn } from 'ui'
import { Markdown } from 'components/interfaces/Markdown'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { formatQuery } from 'data/sql/format-sql-query'
import { noop } from 'lodash'
import { formatSql } from 'lib/formatSql'
// [Joshen] Is there a way we can just have one single MonacoEditor component that's shared across the dashboard?
// Feels like we're creating multiple copies of Editor. I'm keen to make this one the defacto as well so lets make sure
@@ -125,31 +125,17 @@ export const RLSCodeEditor = ({
onChange()
}, [value])
async function formatPgsql(value: any) {
try {
const formatted = await formatQuery({
projectRef: project?.ref!,
connectionString: project?.connectionString,
sql: value,
})
return formatted
} catch (error) {
console.error('formatPgsql error:', error)
return value
}
}
useEffect(() => {
if (monaco) {
// Enable pgsql format
const formatprovider = monaco.languages.registerDocumentFormattingEditProvider('pgsql', {
async provideDocumentFormattingEdits(model: any) {
const value = model.getValue()
const formatted = await formatPgsql(value)
const formatted = formatSql(value)
return [
{
range: model.getFullModelRange(),
text: formatted.result.trim(),
text: formatted.trim(),
},
]
},
@@ -1,3 +1,7 @@
import { Lightbulb } from 'lucide-react'
import { useEffect, useState } from 'react'
import { formatSql } from 'lib/formatSql'
import {
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
@@ -6,14 +10,11 @@ import {
CodeBlock,
cn,
} from 'ui'
import { QueryPanelContainer, QueryPanelSection } from './QueryPanel'
import {
QUERY_PERFORMANCE_REPORTS,
QUERY_PERFORMANCE_REPORT_TYPES,
} from './QueryPerformance.constants'
import { format } from 'sql-formatter'
import { useEffect, useState } from 'react'
import { QueryPanelContainer, QueryPanelSection } from './QueryPanel'
import { Lightbulb } from 'lucide-react'
interface QueryDetailProps {
reportType: QUERY_PERFORMANCE_REPORT_TYPES
@@ -33,15 +34,8 @@ export const QueryDetail = ({
useEffect(() => {
if (selectedRow !== undefined) {
try {
const formattedQuery = format(selectedRow['query'], {
language: 'postgresql',
keywordCase: 'lower',
})
setQuery(formattedQuery)
} catch (err) {
setQuery(selectedRow['query'])
}
const formattedQuery = formatSql(selectedRow['query'])
setQuery(formattedQuery)
}
}, [selectedRow])
@@ -1,5 +1,6 @@
import type { Monaco } from '@monaco-editor/react'
import { useQueryClient } from '@tanstack/react-query'
import { useCompletion } from 'ai/react'
import { AnimatePresence, motion } from 'framer-motion'
import { ChevronUp, Command, Loader2 } from 'lucide-react'
import dynamic from 'next/dynamic'
@@ -7,7 +8,6 @@ import { useRouter } from 'next/router'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { toast } from 'sonner'
import { useCompletion } from 'ai/react'
import { useParams } from 'common'
import { TelemetryActions } from 'common/telemetry-constants'
import { GridFooter } from 'components/ui/GridFooter'
@@ -17,7 +17,6 @@ import { constructHeaders } from 'data/fetchers'
import { lintKeys } from 'data/lint/keys'
import { useReadReplicasQuery } from 'data/read-replicas/replicas-query'
import { useExecuteSqlMutation } from 'data/sql/execute-sql-mutation'
import { useFormatQueryMutation } from 'data/sql/format-sql-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { isError } from 'data/utils/error-check'
@@ -26,10 +25,10 @@ import { useSchemasForAi } from 'hooks/misc/useSchemasForAi'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
import { BASE_PATH, IS_PLATFORM, LOCAL_STORAGE_KEYS } from 'lib/constants'
import { formatSql } from 'lib/formatSql'
import { detectOS, uuidv4 } from 'lib/helpers'
import { useProfile } from 'lib/profile'
import { wrapWithRoleImpersonation } from 'lib/role-impersonation'
import { format } from 'sql-formatter'
import { useAppStateSnapshot } from 'state/app-state'
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
import { isRoleImpersonationEnabled, useGetImpersonatedRole } from 'state/role-impersonation-state'
@@ -151,7 +150,6 @@ export const SQLEditor = () => {
const entityDefinitions = includeSchemaMetadata ? data?.map((def) => def.sql.trim()) : undefined
/* React query mutations */
const { mutate: formatQuery } = useFormatQueryMutation()
const { mutateAsync: generateSqlTitle } = useSqlTitleGenerateMutation()
const { mutate: sendEvent } = useSendEventMutation()
const { mutate: execute, isLoading: isExecuting } = useExecuteSqlMutation({
@@ -228,29 +226,20 @@ export const SQLEditor = () => {
const sql = snippet
? (selectedValue || editorRef.current?.getValue()) ?? snippet.snippet.content?.sql
: selectedValue || editorRef.current?.getValue()
formatQuery(
{
projectRef: project.ref,
connectionString: project.connectionString,
sql,
},
{
onSuccess: (res) => {
const editorModel = editorRef?.current?.getModel()
if (editorRef.current && editorModel) {
editorRef.current.executeEdits('apply-prettify-edit', [
{
text: res.result,
range: editorModel.getFullModelRange(),
},
])
snapV2.setSql(id, res.result)
}
const formattedSql = formatSql(sql)
const editorModel = editorRef?.current?.getModel()
if (editorRef.current && editorModel) {
editorRef.current.executeEdits('apply-prettify-edit', [
{
text: formattedSql,
range: editorModel.getFullModelRange(),
},
}
)
])
snapV2.setSql(id, formattedSql)
}
}
}, [formatQuery, id, isDiffOpen, project, snapV2])
}, [id, isDiffOpen, project, snapV2])
const executeQuery = useCallback(
async (force: boolean = false) => {
@@ -599,18 +588,8 @@ export const SQLEditor = () => {
const modified = promptState.beforeSelection + completion + promptState.afterSelection
if (isCompletionLoading) {
let formattedModified = modified
// Attempt to format the modified SQL in case the LLM left out indentation, etc
try {
formattedModified = format(
promptState.beforeSelection + completion + promptState.afterSelection,
{
language: 'postgresql',
keywordCase: 'lower',
}
)
} catch (error) {}
let formattedModified = formatSql(modified)
setSourceSqlDiff({
original,
@@ -1,13 +1,13 @@
import { AlertTriangle, User } from 'lucide-react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useEffectOnce } from 'react-use'
import { format } from 'sql-formatter'
import { IS_PLATFORM } from 'common'
import { useEntityDefinitionsQuery } from 'data/database/entity-definitions-query'
import { useOrgOptedIntoAi } from 'hooks/misc/useOrgOptedIntoAi'
import { useSchemasForAi } from 'hooks/misc/useSchemasForAi'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
import { formatSql } from 'lib/formatSql'
import { useAppStateSnapshot } from 'state/app-state'
import {
AiIconAnimation,
@@ -231,17 +231,6 @@ function Messages({
messages: ReturnType<typeof useAiChat>['messages']
handleReset: () => void
}) {
function formatAnswer(answer: string) {
try {
return format(answer, {
language: 'postgresql',
keywordCase: 'lower',
})
} catch {
return answer
}
}
const X_PADDING = 'px-4'
const UserAvatar = useCallback(() => {
@@ -279,7 +268,7 @@ function Messages({
const answer =
message.status === MessageStatus.Complete
? formatAnswer(unformattedAnswer)
? formatSql(unformattedAnswer)
: unformattedAnswer
const cantHelp = answer.replace(/^-- /, '') === "Sorry, I don't know how to help with that."
@@ -2,7 +2,6 @@ import Editor from '@monaco-editor/react'
import { useTheme } from 'next-themes'
import Link from 'next/link'
import { useMemo, useRef } from 'react'
import { format } from 'sql-formatter'
import { useParams } from 'common'
import Footer from 'components/grid/components/footer/Footer'
@@ -17,6 +16,7 @@ import {
isView,
isViewLike,
} from 'data/table-editor/table-editor-types'
import { formatSql } from 'lib/formatSql'
import { timeout } from 'lib/helpers'
import { Button } from 'ui'
@@ -61,19 +61,8 @@ const TableDefinition = ({ entity }: TableDefinitionProps) => {
? `create materialized view ${entity.schema}.${entity.name} as\n`
: ''
const formatDefinition = (value: string) => {
try {
return format(value, {
language: 'postgresql',
keywordCase: 'lower',
})
} catch (err) {
return value
}
}
const formattedDefinition = useMemo(
() => (definition ? formatDefinition(prepend + definition) : undefined),
() => (definition ? formatSql(prepend + definition) : undefined),
[definition]
)
@@ -4,8 +4,8 @@ import { editor } from 'monaco-editor'
import { MutableRefObject, useEffect, useRef, useState } from 'react'
import { Markdown } from 'components/interfaces/Markdown'
import { formatQuery } from 'data/sql/format-sql-query'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
import { formatSql } from 'lib/formatSql'
import { timeout } from 'lib/helpers'
import { cn } from 'ui'
import { Loading } from '../Loading'
@@ -94,21 +94,6 @@ const CodeEditor = ({
options
)
const formatPgsql = async (value: string) => {
try {
if (!project) throw new Error('No project')
const formatted = await formatQuery({
projectRef: project.ref,
connectionString: project.connectionString,
sql: value,
})
return formatted.result
} catch (error) {
console.error('formatPgsql error:', error)
return value
}
}
const onMount: OnMount = async (editor, monaco) => {
editorRef.current = editor
monacoRef.current = monaco
@@ -234,7 +219,7 @@ const CodeEditor = ({
const formatProvider = monaco.languages.registerDocumentFormattingEditProvider('pgsql', {
async provideDocumentFormattingEdits(model: any) {
const value = model.getValue()
const formatted = await formatPgsql(value)
const formatted = formatSql(value)
formatDocument.callback(formatted)
return [{ range: model.getFullModelRange(), text: formatted }]
},
+2 -16
View File
@@ -3,7 +3,7 @@ import { noop } from 'lodash'
import { useEffect, useRef } from 'react'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { formatQuery } from 'data/sql/format-sql-query'
import { formatSql } from 'lib/formatSql'
// [Joshen] We should deprecate this and use CodeEditor instead
@@ -37,7 +37,7 @@ const SqlEditor = ({
const formatprovider = monaco.languages.registerDocumentFormattingEditProvider('pgsql', {
async provideDocumentFormattingEdits(model: any) {
const value = model.getValue()
const formatted = await formatPgsql(value)
const formatted = formatSql(value)
return [
{
range: model.getFullModelRange(),
@@ -66,20 +66,6 @@ const SqlEditor = ({
}
}, [queryId])
async function formatPgsql(value: any) {
try {
const formatted = await formatQuery({
projectRef: project?.ref!,
connectionString: project?.connectionString,
sql: value,
})
return formatted
} catch (error) {
console.error('formatPgsql error:', error)
return value
}
}
const onMount = (editor: any, monaco: any) => {
editorRef.current = editor
-40
View File
@@ -1,40 +0,0 @@
import { useMutation, UseMutationOptions } from '@tanstack/react-query'
import { post } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
export type FormatQueryVariables = {
projectRef: string
sql: string
connectionString?: string
}
export async function formatQuery(
{ projectRef, connectionString, sql }: FormatQueryVariables,
signal?: AbortSignal
) {
let headers = new Headers()
if (connectionString) headers.set('x-connection-encrypted', connectionString)
const response = await post(
`${API_URL}/pg-meta/${projectRef}/query/format`,
{ query: sql },
{ headers: Object.fromEntries(headers), signal }
)
if (response.error) throw response.error
return { result: response }
}
type FormatQueryData = Awaited<ReturnType<typeof formatQuery>>
export const useFormatQueryMutation = ({
onSuccess,
...options
}: Omit<UseMutationOptions<FormatQueryData, unknown, FormatQueryVariables>, 'mutationFn'> = {}) => {
return useMutation<FormatQueryData, unknown, FormatQueryVariables>((args) => formatQuery(args), {
async onSuccess(data, variables, context) {
await onSuccess?.(data, variables, context)
},
...options,
})
}
+17
View File
@@ -0,0 +1,17 @@
import { format } from 'sql-formatter'
/**
* Util function for formatting SQL. It wraps the `sql-formatter` library with a preset format options so that the
* formatting is consistent across the app. It also has a try/catch block which returns the original SQL in case of
* an error.
*/
export const formatSql = (sql: string) => {
try {
return format(sql, {
language: 'postgresql',
keywordCase: 'lower',
})
} catch {
return sql
}
}
+1 -1
View File
@@ -115,7 +115,7 @@
"semver": "^6.3.1",
"shared-data": "workspace:*",
"sonner": "^1.5.0",
"sql-formatter": "^13.1.0",
"sql-formatter": "^15.0.0",
"sqlstring": "^2.3.2",
"tus-js-client": "^4.1.0",
"ui": "workspace:*",
+2 -20
View File
@@ -13,9 +13,9 @@ import { useDatabaseFunctionsQuery } from 'data/database-functions/database-func
import { useKeywordsQuery } from 'data/database/keywords-query'
import { useSchemasQuery } from 'data/database/schemas-query'
import { useTableColumnsQuery } from 'data/database/table-columns-query'
import { useFormatQueryMutation } from 'data/sql/format-sql-query'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { LOCAL_STORAGE_KEYS } from 'lib/constants'
import { formatSql } from 'lib/formatSql'
import { useAppStateSnapshot } from 'state/app-state'
import { SnippetWithContent, useSnippets, useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2'
import type { NextPageWithLayout } from 'types'
@@ -30,7 +30,6 @@ const SqlEditor: NextPageWithLayout = () => {
const snapV2 = useSqlEditorV2StateSnapshot()
const allSnippets = useSnippets(ref!)
const { mutateAsync: formatQuery } = useFormatQueryMutation()
const [intellisenseEnabled] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.SQL_EDITOR_INTELLISENSE,
@@ -55,21 +54,6 @@ const SqlEditor: NextPageWithLayout = () => {
}
}, [ref, data])
async function formatPgsql(value: string) {
try {
if (!project) throw new Error('No project')
const formatted = await formatQuery({
projectRef: project.ref,
connectionString: project.connectionString,
sql: value,
})
return formatted.result
} catch (error) {
console.error('formatPgsql error:', error)
return value
}
}
const { data: keywords, isSuccess: isKeywordsSuccess } = useKeywordsQuery(
{
projectRef: project?.ref,
@@ -100,8 +84,6 @@ const SqlEditor: NextPageWithLayout = () => {
)
const pgInfoRef = useRef<any>(null)
const formatPgsqlRef = useRef(formatPgsql)
formatPgsqlRef.current = formatPgsql
const isPgInfoReady =
intellisenseEnabled &&
@@ -140,7 +122,7 @@ const SqlEditor: NextPageWithLayout = () => {
const formatProvider = monaco.languages.registerDocumentFormattingEditProvider('pgsql', {
async provideDocumentFormattingEdits(model: any) {
const value = model.getValue()
const formatted = await formatPgsqlRef.current(value)
const formatted = formatSql(value)
if (id) snapV2.setSql(id, formatted)
return [{ range: model.getFullModelRange(), text: formatted }]
},
+1 -1
View File
@@ -30,7 +30,7 @@
"dotenv": "^16.3.1",
"libpg-query": "15.2.0",
"mdast-util-from-markdown": "^2.0.0",
"sql-formatter": "^15.0.2",
"sql-formatter": "^15.0.0",
"tsconfig": "workspace:*",
"typescript": "~5.5.0",
"vitest": "^3.0.0"
+1 -1
View File
@@ -37,7 +37,7 @@
"recharts": "^2.8.0",
"remark-gfm": "^4.0.0",
"sonner": "^1.5.0",
"sql-formatter": "^15.3.1",
"sql-formatter": "^15.0.0",
"sse.js": "^2.2.0",
"tsconfig": "workspace:*",
"ui": "workspace:*",
-1
View File
@@ -110,7 +110,6 @@
"config": "workspace:*",
"copyfiles": "^2.4.1",
"glob": "^8.1.0",
"sql-formatter": "^12.2.0",
"style-dictionary": "^3.7.1",
"tsconfig": "workspace:*",
"tsconfig-paths-webpack-plugin": "^4.0.1",
+9 -31
View File
@@ -803,8 +803,8 @@ importers:
specifier: ^1.5.0
version: 1.5.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
sql-formatter:
specifier: ^13.1.0
version: 13.1.0
specifier: ^15.0.0
version: 15.4.9
sqlstring:
specifier: ^2.3.2
version: 2.3.3
@@ -1286,8 +1286,8 @@ importers:
specifier: ^2.0.0
version: 2.0.0(supports-color@8.1.1)
sql-formatter:
specifier: ^15.0.2
version: 15.3.1
specifier: ^15.0.0
version: 15.4.9
tsconfig:
specifier: workspace:*
version: link:../tsconfig
@@ -1762,9 +1762,6 @@ importers:
glob:
specifier: ^8.1.0
version: 8.1.0
sql-formatter:
specifier: ^12.2.0
version: 12.2.0
style-dictionary:
specifier: ^3.7.1
version: 3.8.0
@@ -1871,8 +1868,8 @@ importers:
specifier: ^1.5.0
version: 1.5.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
sql-formatter:
specifier: ^15.3.1
version: 15.3.1
specifier: ^15.0.0
version: 15.4.9
sse.js:
specifier: ^2.2.0
version: 2.2.0
@@ -11509,20 +11506,12 @@ packages:
resolution: {integrity: sha512-6QumAdGHEnI5dXEq1d0aBRP876AyA9Wp/UE7wopKNA2Mp9sKGRKVqGgoWHk4dr0J0nceesC85Y0p36qmGoNqhw==}
hasBin: true
sql-formatter@12.2.0:
resolution: {integrity: sha512-wNsPUdOD6nnN9RUgHlNprQtm+iLW5LTOy/T0/2DDr2UeWSP8mvlQHrx6TY+IG1nfu5Kipq9GaOtS9SVq8s0Vig==}
hasBin: true
sql-formatter@13.1.0:
resolution: {integrity: sha512-/nZQXuN7KzipFNM20ko+dHY4kOr9rymSfZLUDED8rhx3m8OK5y74jcyN+y1L51ZqHqiB0kp40VdpZP99uWvQdA==}
hasBin: true
sql-formatter@14.0.0:
resolution: {integrity: sha512-VcHYMRvZqg3RNjjxNB/puT9O1hR5QLXTvgTaBtxXcvmRQwSnH9M+oW2Ti+uFuVVU8HoNlOjU2uKHv8c0FQNsdQ==}
hasBin: true
sql-formatter@15.3.1:
resolution: {integrity: sha512-L/dqan+Hrt0PpPdCbHcI9bdfOvqaQZR7v5c5SWMJ3bUGQSezK09Mm9q2I3B4iObjaq7FyoldIM+fDSmfzGRXCA==}
sql-formatter@15.4.9:
resolution: {integrity: sha512-5vmt2HlCAVozxsBZuXWkAki/KGawaK+b5GG5x+BtXOFVpN/8cqppblFUxHl4jxdA0cvo14lABhM+KBnrUapOlw==}
hasBin: true
sql-parser-cst@0.24.0:
@@ -25293,24 +25282,13 @@ snapshots:
argparse: 2.0.1
nearley: 2.20.1
sql-formatter@12.2.0:
dependencies:
argparse: 2.0.1
nearley: 2.20.1
sql-formatter@13.1.0:
dependencies:
argparse: 2.0.1
get-stdin: 8.0.0
nearley: 2.20.1
sql-formatter@14.0.0:
dependencies:
argparse: 2.0.1
get-stdin: 8.0.0
nearley: 2.20.1
sql-formatter@15.3.1:
sql-formatter@15.4.9:
dependencies:
argparse: 2.0.1
get-stdin: 8.0.0