Skip to content

Commit 4e30fe6

Browse files
committed
Format LANGUAGE SQL function bodies
1 parent e3fe34a commit 4e30fe6

3 files changed

Lines changed: 126 additions & 1 deletion

File tree

src/embed.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import { Printer } from "prettier";
22
import { Node } from "sql-parser-cst";
33
import { embedJs } from "./embedJs";
44
import { embedJson } from "./embedJson";
5+
import { embedSql } from "./embedSql";
56

67
export const embed: NonNullable<Printer<Node>["embed"]> = (...args) => {
7-
return embedJson(...args) || embedJs(...args);
8+
return embedJson(...args) || embedJs(...args) || embedSql(...args);
89
};

src/embedSql.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Printer } from "prettier";
2+
import { CreateFunctionStmt, Node, StringLiteral } from "sql-parser-cst";
3+
import {
4+
isAsClause,
5+
isCreateFunctionStmt,
6+
isLanguageClause,
7+
isStringLiteral,
8+
} from "./node_utils";
9+
import { hardline, indent, stripTrailingHardline } from "./print_utils";
10+
11+
export const embedSql: NonNullable<Printer<Node>["embed"]> = (path, options) => {
12+
const node = path.node;
13+
const parent = path.getParentNode(0);
14+
const grandParent = path.getParentNode(1);
15+
16+
if (
17+
isStringLiteral(node) &&
18+
isAsClause(parent) &&
19+
isCreateFunctionStmt(grandParent) &&
20+
grandParent.clauses.some(isSqlLanguageClause)
21+
) {
22+
return async (textToDoc) => {
23+
const quote = detectQuote(node);
24+
25+
if (quote) {
26+
const sql = await textToDoc(node.value, options);
27+
28+
return [
29+
quote,
30+
indent([hardline, stripTrailingHardline(sql)]),
31+
hardline,
32+
quote,
33+
];
34+
}
35+
};
36+
}
37+
38+
return null;
39+
};
40+
41+
const isSqlLanguageClause = (
42+
clause: CreateFunctionStmt["clauses"][0],
43+
): boolean => isLanguageClause(clause) && clause.name.name === "sql";
44+
45+
const detectQuote = (
46+
node: StringLiteral,
47+
): string | undefined => {
48+
const match = node.text.match(/^('|\$[^$]*\$)/);
49+
const quote = match?.[1];
50+
51+
if (quote && node.text.endsWith(quote)) {
52+
return quote;
53+
}
54+
55+
return undefined;
56+
};

test/ddl/function.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,74 @@ describe("function", () => {
202202
AS " return /'''|\\"\\"\\"/.test(x) "
203203
`);
204204
});
205+
206+
it(`formats dollar-quoted SQL function`, async () => {
207+
await testPostgresql(dedent`
208+
CREATE FUNCTION my_func()
209+
RETURNS INT64
210+
LANGUAGE sql
211+
AS $$
212+
SELECT 1;
213+
$$
214+
`);
215+
});
216+
217+
it(`reformats SQL in dollar-quoted SQL function`, async () => {
218+
expect(
219+
await pretty(
220+
dedent`
221+
CREATE FUNCTION my_func()
222+
RETURNS INT64
223+
LANGUAGE sql
224+
AS $body$SELECT 1;
225+
select 2$body$
226+
`,
227+
{ dialect: "postgresql" },
228+
),
229+
).toBe(dedent`
230+
CREATE FUNCTION my_func()
231+
RETURNS INT64
232+
LANGUAGE sql
233+
AS $body$
234+
SELECT 1;
235+
SELECT 2;
236+
$body$
237+
`);
238+
});
239+
240+
it(`formats single-quoted SQL function`, async () => {
241+
await testPostgresql(dedent`
242+
CREATE FUNCTION my_func()
243+
RETURNS TEXT
244+
LANGUAGE sql
245+
AS '
246+
SELECT ''foo'';
247+
'
248+
`);
249+
});
250+
251+
it(`reformats SQL in single-quoted SQL function`, async () => {
252+
expect(
253+
await pretty(
254+
dedent`
255+
CREATE FUNCTION my_func()
256+
RETURNS TEXT
257+
LANGUAGE sql
258+
AS 'SELECT ''foo'';
259+
select ''bar'''
260+
`,
261+
{ dialect: "postgresql" },
262+
),
263+
).toBe(dedent`
264+
CREATE FUNCTION my_func()
265+
RETURNS TEXT
266+
LANGUAGE sql
267+
AS '
268+
SELECT ''foo'';
269+
SELECT ''bar'';
270+
'
271+
`);
272+
});
205273
});
206274

207275
describe("drop function", () => {

0 commit comments

Comments
 (0)