Skip to content

Commit 9e57d25

Browse files
committed
database/semver_ord: Add remaining prerelease identifiers as final text item
1 parent b5df6fe commit 9e57d25

File tree

2 files changed

+18
-13
lines changed

2 files changed

+18
-13
lines changed

crates/crates_io_database/tests/semver_ord.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,16 @@ async fn test_jsonb_output() {
2626
};
2727

2828
insta::assert_snapshot!(check("0.0.0").await, @r#"[0, 0, 0, {}]"#);
29-
insta::assert_snapshot!(check("1.0.0-alpha.1").await, @r#"[1, 0, 0, [true, "alpha", false, 1, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]]"#);
29+
insta::assert_snapshot!(check("1.0.0-alpha.1").await, @r#"[1, 0, 0, [true, "alpha", false, 1, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, ""]]"#);
3030

31-
// https://crates.io/crates/cursed-trying-to-break-cargo/1.0.0-0.HDTV-BluRay.1020p.YTSUB.L33TRip.mkv – thanks @Gankra!
32-
insta::assert_snapshot!(check("1.0.0-0.HDTV-BluRay.1020p.YTSUB.L33TRip.mkv").await, @r#"[1, 0, 0, [false, 0, true, "HDTV-BluRay", true, "1020p", true, "YTSUB", true, "L33TRip", true, "mkv", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]]"#);
31+
// see https://crates.io/crates/cursed-trying-to-break-cargo/1.0.0-0.HDTV-BluRay.1020p.YTSUB.L33TRip.mkv – thanks @Gankra!
32+
insta::assert_snapshot!(check("1.0.0-0.HDTV-BluRay.1020p.YTSUB.L33TRip.mkv").await, @r#"[1, 0, 0, [false, 0, true, "HDTV-BluRay", true, "1020p", true, "YTSUB", true, "L33TRip", true, "mkv", null, null, null, null, null, null, null, null, ""]]"#);
3333

3434
// Invalid version string
3535
insta::assert_snapshot!(check("foo").await, @"[null, null, null, {}]");
3636

3737
// Version string with a lot of prerelease identifiers
38-
insta::assert_snapshot!(check("1.2.3-1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.end").await, @"[1, 2, 3, [false, 1, false, 2, false, 3, false, 4, false, 5, false, 6, false, 7, false, 8, false, 9, false, 10, false, 11, false, 12, false, 13, false, 14, false, 15, false, 16]]");
38+
insta::assert_snapshot!(check("1.2.3-1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.end").await, @r#"[1, 2, 3, [false, 1, false, 2, false, 3, false, 4, false, 5, false, 6, false, 7, false, 8, false, 9, false, 10, "11.12.13.14.15.16.17.end"]]"#);
3939
}
4040

4141
/// This test checks that the `semver_ord` function orders versions correctly.

migrations/2025-03-06-060640_semver_ord/up.sql

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@
22

33
create or replace function semver_ord(num varchar) returns jsonb as $$
44
declare
5-
-- We need to ensure that the array has the same length for all versions
6-
-- since shorter arrays have lower precedence in JSONB. Since we also need
7-
-- to add a boolean value for each part of the prerelease string, this
8-
-- results in us supporting up to 15 parts in the prerelease string.
9-
-- Everything beyond that will be ignored.
10-
prerelease_array_length constant int := 30;
5+
-- We need to ensure that the prerelease array has the same length for all
6+
-- versions since shorter arrays have lower precedence in JSONB. We store
7+
-- the first 10 parts of the prerelease string as pairs of booleans and
8+
-- numbers or text values, and then a final text item for the remaining
9+
-- parts.
10+
max_prerelease_parts constant int := 10;
1111

1212
-- We ignore the "build metadata" part of the semver string, since it has
1313
-- no impact on the version ordering.
1414
match_result text[] := regexp_match(num, '^(\d+).(\d+).(\d+)(?:-([0-9A-Za-z\-.]+))?');
1515

1616
prerelease jsonb;
17+
prerelease_parts text[];
1718
prerelease_part text;
1819
i int := 0;
1920
begin
@@ -22,11 +23,13 @@ begin
2223
-- prerelease specifiers should have lower precedence than those without.
2324
prerelease := json_build_object();
2425
else
25-
prerelease := to_jsonb(array_fill(NULL::bool, ARRAY[prerelease_array_length]));
26+
prerelease := to_jsonb(array_fill(NULL::bool, ARRAY[max_prerelease_parts * 2 + 1]));
2627

2728
-- Split prerelease string by `.` and "append" items to
2829
-- the `prerelease` array.
29-
foreach prerelease_part in array string_to_array(match_result[4], '.')
30+
prerelease_parts := string_to_array(match_result[4], '.');
31+
32+
foreach prerelease_part in array prerelease_parts[1:max_prerelease_parts + 1]
3033
loop
3134
-- Parse parts as numbers if they consist of only digits.
3235
if regexp_like(prerelease_part, '^\d+$') then
@@ -42,8 +45,10 @@ begin
4245

4346
-- Exit the loop if we have reached the maximum number of parts.
4447
i := i + 2;
45-
exit when i > prerelease_array_length;
48+
exit when i >= max_prerelease_parts * 2;
4649
end loop;
50+
51+
prerelease := jsonb_set(prerelease, array[(max_prerelease_parts * 2)::text], to_jsonb(array_to_string(prerelease_parts[max_prerelease_parts + 1:], '.')));
4752
end if;
4853

4954
-- Return an array with the major, minor, patch, and prerelease parts.

0 commit comments

Comments
 (0)