Skip to content

Commit 098554b

Browse files
authored
fix(formatter): use postgres canolical order to sort function options (#683)
1 parent 5361e14 commit 098554b

167 files changed

Lines changed: 3014 additions & 2931 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/pgls_pretty_print/src/nodes/alter_function_stmt.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ pub(super) fn emit_alter_function_stmt(e: &mut EventEmitter, n: &AlterFunctionSt
3636
};
3737

3838
// Emit actions (function options like IMMUTABLE, SECURITY DEFINER, etc.)
39+
// Sort according to Postgres's canonical order
3940
if !n.actions.is_empty() {
4041
e.line(LineType::SoftOrSpace);
41-
emit_comma_separated_list(e, &n.actions, |node, e| {
42+
let sorted_actions = super::create_function_stmt::sort_function_options(&n.actions);
43+
emit_comma_separated_list(e, &sorted_actions, |node, e| {
4244
let def_elem = assert_node_variant!(DefElem, node);
4345
super::create_function_stmt::format_function_option(e, def_elem, dollar_hint);
4446
});

crates/pgls_pretty_print/src/nodes/create_function_stmt.rs

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,9 @@ pub(super) fn emit_create_function_stmt(e: &mut EventEmitter, n: &CreateFunction
9090
super::DollarQuoteHint::Function
9191
};
9292

93-
// Options
94-
for option in &n.options {
93+
// Options - sort according to Postgres's canonical order
94+
let sorted_options = sort_function_options(&n.options);
95+
for option in sorted_options {
9596
if let Some(pgls_query::NodeEnum::DefElem(def_elem)) = &option.node {
9697
e.line(LineType::Hard);
9798
format_function_option(e, def_elem, dollar_hint);
@@ -183,6 +184,53 @@ fn emit_function_parameter_list(e: &mut EventEmitter, params: &[&FunctionParamet
183184
}
184185
}
185186

187+
/// Returns the canonical order for a function option.
188+
/// Postgres's canonical order (as seen in pg_dump output) is:
189+
/// 1. LANGUAGE
190+
/// 2. WINDOW
191+
/// 3. IMMUTABLE / STABLE / VOLATILE (volatility)
192+
/// 4. LEAKPROOF / NOT LEAKPROOF
193+
/// 5. STRICT / CALLED ON NULL INPUT (strict)
194+
/// 6. SECURITY DEFINER / SECURITY INVOKER (security)
195+
/// 7. PARALLEL (parallel)
196+
/// 8. COST (cost)
197+
/// 9. ROWS (rows)
198+
/// 10. SUPPORT (support)
199+
/// 11. SET options (set)
200+
/// 12. AS (function body)
201+
fn option_order(defname: &str) -> usize {
202+
match defname.to_lowercase().as_str() {
203+
"language" => 0,
204+
"window" => 1,
205+
"volatility" => 2,
206+
"leakproof" => 3,
207+
"strict" => 4,
208+
"security" => 5,
209+
"parallel" => 6,
210+
"cost" => 7,
211+
"rows" => 8,
212+
"support" => 9,
213+
"set" => 10,
214+
"as" => 11,
215+
_ => 100, // Unknown options go last
216+
}
217+
}
218+
219+
/// Sort function options according to Postgres's canonical order.
220+
pub(super) fn sort_function_options(
221+
options: &[pgls_query::protobuf::Node],
222+
) -> Vec<pgls_query::protobuf::Node> {
223+
let mut sorted: Vec<pgls_query::protobuf::Node> = options.to_vec();
224+
sorted.sort_by_key(|node| {
225+
if let Some(pgls_query::NodeEnum::DefElem(def_elem)) = &node.node {
226+
option_order(&def_elem.defname)
227+
} else {
228+
100 // Non-DefElem nodes go last
229+
}
230+
});
231+
sorted
232+
}
233+
186234
pub(super) fn format_function_option(
187235
e: &mut EventEmitter,
188236
d: &pgls_query::protobuf::DefElem,

crates/pgls_pretty_print/src/normalize.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,14 +1205,63 @@ fn sql_value_to_func_call(
12051205
})
12061206
}
12071207

1208+
/// Returns the canonical order for a function option.
1209+
/// Postgres's canonical order (as seen in pg_dump output) is:
1210+
/// 1. LANGUAGE
1211+
/// 2. WINDOW
1212+
/// 3. IMMUTABLE / STABLE / VOLATILE (volatility)
1213+
/// 4. LEAKPROOF / NOT LEAKPROOF
1214+
/// 5. STRICT / CALLED ON NULL INPUT (strict)
1215+
/// 6. SECURITY DEFINER / SECURITY INVOKER (security)
1216+
/// 7. PARALLEL (parallel)
1217+
/// 8. COST (cost)
1218+
/// 9. ROWS (rows)
1219+
/// 10. SUPPORT (support)
1220+
/// 11. SET options (set)
1221+
/// 12. AS (function body)
1222+
fn option_order(defname: &str) -> usize {
1223+
match defname.to_lowercase().as_str() {
1224+
"language" => 0,
1225+
"window" => 1,
1226+
"volatility" => 2,
1227+
"leakproof" => 3,
1228+
"strict" => 4,
1229+
"security" => 5,
1230+
"parallel" => 6,
1231+
"cost" => 7,
1232+
"rows" => 8,
1233+
"support" => 9,
1234+
"set" => 10,
1235+
"as" => 11,
1236+
_ => 100, // Unknown options go last
1237+
}
1238+
}
1239+
1240+
/// Sort function options according to Postgres's canonical order.
1241+
fn sort_function_options(options: &mut [pgls_query::protobuf::Node]) {
1242+
options.sort_by_key(|node| {
1243+
if let Some(NodeEnum::DefElem(def_elem)) = &node.node {
1244+
option_order(&def_elem.defname)
1245+
} else {
1246+
100 // Non-DefElem nodes go last
1247+
}
1248+
});
1249+
}
1250+
12081251
/// Normalize function body strings by trimming whitespace.
12091252
///
12101253
/// The formatter emits function bodies on separate lines from the dollar-quote delimiters,
12111254
/// which adds leading/trailing newlines to the body. This normalization trims these
12121255
/// so that semantically equivalent bodies compare equal.
1256+
///
1257+
/// Also sorts function options to canonical order so that semantically equivalent
1258+
/// option orderings compare equal.
12131259
fn normalize_function_body(node: &mut NodeEnum) {
12141260
match node {
12151261
NodeEnum::CreateFunctionStmt(stmt) => {
1262+
// Sort options to canonical order
1263+
sort_function_options(&mut stmt.options);
1264+
12161265
for opt in &mut stmt.options {
12171266
if let Some(NodeEnum::DefElem(def)) = opt.node.as_mut()
12181267
&& def.defname.eq_ignore_ascii_case("as")
@@ -1242,6 +1291,10 @@ fn normalize_function_body(node: &mut NodeEnum) {
12421291
NodeEnum::InlineCodeBlock(block) => {
12431292
block.source_text = block.source_text.trim().to_string();
12441293
}
1294+
NodeEnum::AlterFunctionStmt(stmt) => {
1295+
// Sort actions to canonical order
1296+
sort_function_options(&mut stmt.actions);
1297+
}
12451298
_ => {}
12461299
}
12471300
}

crates/pgls_pretty_print/tests/snapshots/multi/tests__aggregates_100.snap

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1791,6 +1791,7 @@ create type avg_state as (total bigint, count bigint);
17911791

17921792
create or replace function avg_transfn(state avg_state, n int)
17931793
returns avg_state
1794+
language plpgsql
17941795
as $function$
17951796
declare new_state avg_state;
17961797
begin
@@ -1810,11 +1811,11 @@ begin
18101811

18111812
return null;
18121813
end
1813-
$function$
1814-
language plpgsql;
1814+
$function$;
18151815

18161816
create function avg_finalfn(state avg_state)
18171817
returns int
1818+
language plpgsql
18181819
as $function$
18191820
begin
18201821
if state is null then
@@ -1823,11 +1824,11 @@ begin
18231824
return state.total / state.count;
18241825
end if;
18251826
end
1826-
$function$
1827-
language plpgsql;
1827+
$function$;
18281828

18291829
create function sum_finalfn(state avg_state)
18301830
returns int
1831+
language plpgsql
18311832
as $function$
18321833
begin
18331834
if state is null then
@@ -1836,8 +1837,7 @@ begin
18361837
return state.total;
18371838
end if;
18381839
end
1839-
$function$
1840-
language plpgsql;
1840+
$function$;
18411841

18421842
create aggregate my_avg (int) (stype = avg_state, sfunc = avg_transfn, finalfunc = avg_finalfn);
18431843

@@ -1924,6 +1924,7 @@ begin;
19241924

19251925
create or replace function sum_transfn(state int, n int)
19261926
returns int
1927+
language plpgsql
19271928
as $function$
19281929
declare new_state int4;
19291930
begin
@@ -1941,11 +1942,11 @@ begin
19411942

19421943
return null;
19431944
end
1944-
$function$
1945-
language plpgsql;
1945+
$function$;
19461946

19471947
create function halfsum_finalfn(state int)
19481948
returns int
1949+
language plpgsql
19491950
as $function$
19501951
begin
19511952
if state is null then
@@ -1954,8 +1955,7 @@ begin
19541955
return state / 2;
19551956
end if;
19561957
end
1957-
$function$
1958-
language plpgsql;
1958+
$function$;
19591959

19601960
create aggregate my_sum (int) (stype = int, sfunc = sum_transfn);
19611961

@@ -1969,8 +1969,8 @@ begin;
19691969

19701970
create function balkifnull(bigint, int)
19711971
returns bigint
1972-
strict
19731972
language plpgsql
1973+
strict
19741974
as $function$
19751975
BEGIN
19761976
IF $1 IS NULL THEN
@@ -2136,9 +2136,9 @@ begin;
21362136

21372137
create function balkifnull(bigint, bigint)
21382138
returns bigint
2139-
parallel SAFE
2140-
strict
21412139
language plpgsql
2140+
strict
2141+
parallel SAFE
21422142
as $function$
21432143
BEGIN
21442144
IF $1 IS NULL THEN
@@ -2186,8 +2186,8 @@ $function$;
21862186
create function rwagg_finalfunc(x anyarray)
21872187
returns anyarray
21882188
language plpgsql
2189-
strict
21902189
immutable
2190+
strict
21912191
as $function$
21922192
DECLARE
21932193
res x%TYPE;
@@ -2209,8 +2209,8 @@ create aggregate rwagg (
22092209
create function eatarray(x real[])
22102210
returns real[]
22112211
language plpgsql
2212-
strict
22132212
immutable
2213+
strict
22142214
as $function$
22152215
BEGIN
22162216
x[1] := x[1] + 1;

crates/pgls_pretty_print/tests/snapshots/multi/tests__aggregates_80.snap

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2151,6 +2151,7 @@ create type avg_state as (total bigint, count bigint);
21512151

21522152
create or replace function avg_transfn(state avg_state, n int)
21532153
returns avg_state
2154+
language plpgsql
21542155
as $function$
21552156
declare new_state avg_state;
21562157
begin
@@ -2170,11 +2171,11 @@ begin
21702171

21712172
return null;
21722173
end
2173-
$function$
2174-
language plpgsql;
2174+
$function$;
21752175

21762176
create function avg_finalfn(state avg_state)
21772177
returns int
2178+
language plpgsql
21782179
as $function$
21792180
begin
21802181
if state is null then
@@ -2183,11 +2184,11 @@ begin
21832184
return state.total / state.count;
21842185
end if;
21852186
end
2186-
$function$
2187-
language plpgsql;
2187+
$function$;
21882188

21892189
create function sum_finalfn(state avg_state)
21902190
returns int
2191+
language plpgsql
21912192
as $function$
21922193
begin
21932194
if state is null then
@@ -2196,8 +2197,7 @@ begin
21962197
return state.total;
21972198
end if;
21982199
end
2199-
$function$
2200-
language plpgsql;
2200+
$function$;
22012201

22022202
create aggregate my_avg (
22032203
int
@@ -2316,6 +2316,7 @@ begin;
23162316

23172317
create or replace function sum_transfn(state int, n int)
23182318
returns int
2319+
language plpgsql
23192320
as $function$
23202321
declare new_state int4;
23212322
begin
@@ -2333,11 +2334,11 @@ begin
23332334

23342335
return null;
23352336
end
2336-
$function$
2337-
language plpgsql;
2337+
$function$;
23382338

23392339
create function halfsum_finalfn(state int)
23402340
returns int
2341+
language plpgsql
23412342
as $function$
23422343
begin
23432344
if state is null then
@@ -2346,8 +2347,7 @@ begin
23462347
return state / 2;
23472348
end if;
23482349
end
2349-
$function$
2350-
language plpgsql;
2350+
$function$;
23512351

23522352
create aggregate my_sum (int) (stype = int, sfunc = sum_transfn);
23532353

@@ -2371,8 +2371,8 @@ begin;
23712371

23722372
create function balkifnull(bigint, int)
23732373
returns bigint
2374-
strict
23752374
language plpgsql
2375+
strict
23762376
as $function$
23772377
BEGIN
23782378
IF $1 IS NULL THEN
@@ -2558,9 +2558,9 @@ begin;
25582558

25592559
create function balkifnull(bigint, bigint)
25602560
returns bigint
2561-
parallel SAFE
2562-
strict
25632561
language plpgsql
2562+
strict
2563+
parallel SAFE
25642564
as $function$
25652565
BEGIN
25662566
IF $1 IS NULL THEN
@@ -2608,8 +2608,8 @@ $function$;
26082608
create function rwagg_finalfunc(x anyarray)
26092609
returns anyarray
26102610
language plpgsql
2611-
strict
26122611
immutable
2612+
strict
26132613
as $function$
26142614
DECLARE
26152615
res x%TYPE;
@@ -2631,8 +2631,8 @@ create aggregate rwagg (
26312631
create function eatarray(x real[])
26322632
returns real[]
26332633
language plpgsql
2634-
strict
26352634
immutable
2635+
strict
26362636
as $function$
26372637
BEGIN
26382638
x[1] := x[1] + 1;

0 commit comments

Comments
 (0)