diff --git a/migration/src/lib.rs b/migration/src/lib.rs index 3ccadf3c..d2c12302 100644 --- a/migration/src/lib.rs +++ b/migration/src/lib.rs @@ -76,6 +76,7 @@ mod m0000595_analysis_api_index; mod m0000600_remove_raise_notice_fns; mod m0000605_create_source_document; mod m0000610_improve_version_cmp_fns; +mod m0000620_parallel_unsafe_pg_fns; pub struct Migrator; #[async_trait::async_trait] @@ -158,6 +159,7 @@ impl MigratorTrait for Migrator { Box::new(m0000600_remove_raise_notice_fns::Migration), Box::new(m0000605_create_source_document::Migration), Box::new(m0000610_improve_version_cmp_fns::Migration), + Box::new(m0000620_parallel_unsafe_pg_fns::Migration), ] } } diff --git a/migration/src/m0000620_parallel_unsafe_pg_fns.rs b/migration/src/m0000620_parallel_unsafe_pg_fns.rs new file mode 100644 index 00000000..8836cfc2 --- /dev/null +++ b/migration/src/m0000620_parallel_unsafe_pg_fns.rs @@ -0,0 +1,209 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000620_parallel_unsafe_pg_fns/gitver_version_matches.sql" + )) + .await + .map(|_| ())?; + + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000620_parallel_unsafe_pg_fns/is_numeric.sql" + )) + .await + .map(|_| ())?; + + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000620_parallel_unsafe_pg_fns/maven_version_matches.sql" + )) + .await + .map(|_| ())?; + + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000620_parallel_unsafe_pg_fns/mavenver_cmp.sql" + )) + .await + .map(|_| ())?; + + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000620_parallel_unsafe_pg_fns/rpmver_cmp.sql" + )) + .await + .map(|_| ())?; + + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000620_parallel_unsafe_pg_fns/rpmver_version_matches.sql" + )) + .await + .map(|_| ())?; + + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000620_parallel_unsafe_pg_fns/semver_cmp.sql" + )) + .await + .map(|_| ())?; + + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000620_parallel_unsafe_pg_fns/semver_eq.sql" + )) + .await + .map(|_| ())?; + + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000620_parallel_unsafe_pg_fns/semver_gt.sql" + )) + .await + .map(|_| ())?; + + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000620_parallel_unsafe_pg_fns/semver_gte.sql" + )) + .await + .map(|_| ())?; + + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000620_parallel_unsafe_pg_fns/semver_lt.sql" + )) + .await + .map(|_| ())?; + + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000620_parallel_unsafe_pg_fns/semver_lte.sql" + )) + .await + .map(|_| ())?; + + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000620_parallel_unsafe_pg_fns/semver_version_matches.sql" + )) + .await + .map(|_| ())?; + + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000620_parallel_unsafe_pg_fns/version_matches.sql" + )) + .await + .map(|_| ())?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000510_create_maven_cmp_fns/version_matches.sql" + )) + .await + .map(|_| ())?; + manager + .get_connection() + .execute_unprepared(include_str!("m0000580_mark_fns/semver_version_matches.sql")) + .await + .map(|_| ())?; + manager + .get_connection() + .execute_unprepared(include_str!("m0000580_mark_fns/semver_lte.sql")) + .await + .map(|_| ())?; + manager + .get_connection() + .execute_unprepared(include_str!("m0000580_mark_fns/semver_lt.sql")) + .await + .map(|_| ())?; + manager + .get_connection() + .execute_unprepared(include_str!("m0000580_mark_fns/semver_gte.sql")) + .await + .map(|_| ())?; + manager + .get_connection() + .execute_unprepared(include_str!("m0000580_mark_fns/semver_gt.sql")) + .await + .map(|_| ())?; + manager + .get_connection() + .execute_unprepared(include_str!("m0000580_mark_fns/semver_eq.sql")) + .await + .map(|_| ())?; + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000610_improve_version_cmp_fns/semver_cmp.sql" + )) + .await + .map(|_| ())?; + manager + .get_connection() + .execute_unprepared(include_str!("m0000580_mark_fns/rpmver_version_matches.sql")) + .await + .map(|_| ())?; + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000480_create_rpmver_cmp_fns/rpmver_cmp.sql" + )) + .await + .map(|_| ())?; + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000610_improve_version_cmp_fns/mavenver_cmp.sql" + )) + .await + .map(|_| ())?; + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000510_create_maven_cmp_fns/maven_version_matches.sql" + )) + .await + .map(|_| ())?; + manager + .get_connection() + .execute_unprepared(include_str!("m0000580_mark_fns/is_numeric.sql")) + .await + .map(|_| ())?; + manager + .get_connection() + .execute_unprepared(include_str!( + "m0000485_create_gitver_cmp_fns/gitver_version_matches.sql" + )) + .await + .map(|_| ())?; + Ok(()) + } +} diff --git a/migration/src/m0000620_parallel_unsafe_pg_fns/gitver_version_matches.sql b/migration/src/m0000620_parallel_unsafe_pg_fns/gitver_version_matches.sql new file mode 100644 index 00000000..f4c92373 --- /dev/null +++ b/migration/src/m0000620_parallel_unsafe_pg_fns/gitver_version_matches.sql @@ -0,0 +1,28 @@ + +create or replace function gitver_version_matches(version_p text, range_p version_range) + returns bool +as +$$ +begin + if range_p.low_version is not null then + if range_p.low_inclusive then + if version_p = range_p.low_version then + return true; + end if; + end if; + end if; + + if range_p.high_version is not null then + if range_p.high_inclusive then + if version_p = range_p.high_version then + return true; + end if; + end if; + end if; + + return false; + +end +$$ + language plpgsql immutable; + diff --git a/migration/src/m0000620_parallel_unsafe_pg_fns/is_numeric.sql b/migration/src/m0000620_parallel_unsafe_pg_fns/is_numeric.sql new file mode 100644 index 00000000..d4d82234 --- /dev/null +++ b/migration/src/m0000620_parallel_unsafe_pg_fns/is_numeric.sql @@ -0,0 +1,10 @@ +create or replace function is_numeric(str text) + returns bool +as +$$ +begin + return str ~ e'^[0-9]+$'; +end + +$$ + language 'plpgsql' immutable; diff --git a/migration/src/m0000620_parallel_unsafe_pg_fns/maven_version_matches.sql b/migration/src/m0000620_parallel_unsafe_pg_fns/maven_version_matches.sql new file mode 100644 index 00000000..42d679b2 --- /dev/null +++ b/migration/src/m0000620_parallel_unsafe_pg_fns/maven_version_matches.sql @@ -0,0 +1,54 @@ + + +create or replace function maven_version_matches(version_p text, range_p version_range) + returns bool +as +$$ +declare + low_end integer; + high_end integer; +begin + if range_p.low_version is not null then + low_end := mavenver_cmp(version_p, range_p.low_version); + end if; + + if low_end is not null then + if range_p.low_inclusive then + if low_end < 0 then + return false; + end if; + else + if low_end <= 0 then + return false; + end if; + end if; + + end if; + + + if range_p.high_version is not null then + high_end := mavenver_cmp(version_p, range_p.high_version); + end if; + + if high_end is not null then + if range_p.high_inclusive then + if high_end > 0 then + return false; + end if; + else + if high_end >= 0 then + return false; + end if; + end if; + end if; + + if low_end is null and high_end is null then + return false; + end if; + + return true; + +end +$$ + language plpgsql immutable; + diff --git a/migration/src/m0000620_parallel_unsafe_pg_fns/mavenver_cmp.sql b/migration/src/m0000620_parallel_unsafe_pg_fns/mavenver_cmp.sql new file mode 100644 index 00000000..286f8f28 --- /dev/null +++ b/migration/src/m0000620_parallel_unsafe_pg_fns/mavenver_cmp.sql @@ -0,0 +1,117 @@ + +create or replace function mavenver_cmp(left_p text, right_p text) + returns integer +as +$$ +declare +left_parts text[]; + right_parts text[]; + + left_major bigint; + left_minor bigint; + left_revision bigint; + left_qualifier_or_build text; + left_qualifier text; + left_build bigint; + + left_cardinality integer; + + right_major bigint; + right_minor bigint; + right_revision bigint; + right_qualifier_or_build text; + right_qualifier text; + right_build bigint; + + right_cardinality integer; + + left_numeric bool; + right_numeric bool; + + cur integer; + +begin + left_qualifier_or_build = substring(left_p, E'-\\S+$'); + + left_parts = regexp_split_to_array(substring(left_p, E'^[^-]+'), E'\\.'); + left_major = left_parts[1]::bigint; + left_minor = coalesce(left_parts[2]::bigint, 0); + left_revision = coalesce(left_parts[3]::bigint, 0); + + right_qualifier_or_build = substring(right_p, E'-\\S+$'); + + right_parts = regexp_split_to_array(substring(right_p, E'^[^-]+'), E'\\.'); + right_major = right_parts[1]::bigint; + right_minor = coalesce(right_parts[2]::bigint, 0); + right_revision = coalesce(right_parts[3]::bigint, 0); + + if left_major > right_major then + return +1; + elsif left_major < right_major then + return -1; +end if; + + if left_minor > right_minor then + return +1; + elsif left_minor < right_minor then + return -1; +end if; + + if left_revision > right_revision then + return +1; + elsif left_revision < right_revision then + return -1; +end if; + + left_cardinality := greatest(cardinality(left_parts), 3); + right_cardinality := greatest(cardinality(right_parts), 3); + + if left_cardinality > right_cardinality then + return +1; + elsif left_cardinality < right_cardinality then + return -1; +end if; + + if left_qualifier_or_build is null and right_qualifier_or_build is null then + return 0; +end if; + + if left_qualifier_or_build is null then + return +1; +end if; + + if right_qualifier_or_build is null then + return -1; +end if; + + left_numeric := is_numeric(left_qualifier_or_build); + right_numeric := is_numeric(left_qualifier_or_build); + + if left_numeric and right_numeric then + left_build = left_qualifier_or_build::bigint; + right_build = right_qualifier_or_build::bigint; + if left_build < right_build then + return -1; + elseif left_build > right_build then + return +1; +else + return 0; +end if; +end if; + + left_qualifier = lower(left_qualifier_or_build); + right_qualifier = lower(right_qualifier_or_build); + + if left_qualifier < right_qualifier then + return -1; + elsif left_qualifier > right_qualifier then + return +1; +end if; + +return 0; +exception + when others then + return null; +end +$$ +language plpgsql immutable; diff --git a/migration/src/m0000620_parallel_unsafe_pg_fns/rpmver_cmp.sql b/migration/src/m0000620_parallel_unsafe_pg_fns/rpmver_cmp.sql new file mode 100644 index 00000000..8d4d98dd --- /dev/null +++ b/migration/src/m0000620_parallel_unsafe_pg_fns/rpmver_cmp.sql @@ -0,0 +1,63 @@ + +create or replace function rpmver_cmp(a text, b text) + returns integer as $$ +declare + a_segments text[]; + b_segments text[]; + a_len integer; + b_len integer; + a_seg text; + b_seg text; +begin + if a = b then return 0; end if; + a_segments := array(select (regexp_matches(a, '(\d+|[a-zA-Z]+|[~^])', 'g'))[1]); + b_segments := array(select (regexp_matches(b, '(\d+|[a-zA-Z]+|[~^])', 'g'))[1]); + a_len := array_length(a_segments, 1); + b_len := array_length(b_segments, 1); + for i in 1..coalesce(least(a_len, b_len) + 1, 0) loop + a_seg = a_segments[i]; + b_seg = b_segments[i]; + if a_seg ~ '^\d' then + if b_seg ~ '^\d' then + a_seg := ltrim(a_seg, '0'); + b_seg := ltrim(b_seg, '0'); + case + when length(a_seg) > length(b_seg) then return 1; + when length(a_seg) < length(b_seg) then return -1; + else null; -- equality -> fallthrough to string comparison + end case; + else + return 1; + end if; + elsif b_seg ~ '^\d' then + return -1; + elsif a_seg = '~' then + if b_seg != '~' then + return -1; + end if; + elsif b_seg = '~' then + return 1; + elsif a_seg = '^' then + if b_seg != '^' then + return 1; + end if; + elsif b_seg = '^' then + return -1; + end if; + if a_seg != b_seg then + if a_seg < b_seg then + return -1; + else + return 1; + end if; + end if; + end loop; + if b_segments[a_len + 1] = '~' then return 1; end if; + if a_segments[b_len + 1] = '~' then return -1; end if; + if b_segments[a_len + 1] = '^' then return -1; end if; + if a_segments[b_len + 1] = '^' then return 1; end if; + if a_len > b_len then return 1; end if; + if a_len < b_len then return -1; end if; + return 0; +end $$ +language plpgsql immutable; \ No newline at end of file diff --git a/migration/src/m0000620_parallel_unsafe_pg_fns/rpmver_version_matches.sql b/migration/src/m0000620_parallel_unsafe_pg_fns/rpmver_version_matches.sql new file mode 100644 index 00000000..634d3191 --- /dev/null +++ b/migration/src/m0000620_parallel_unsafe_pg_fns/rpmver_version_matches.sql @@ -0,0 +1,53 @@ + +create or replace function rpmver_version_matches(version_p text, range_p version_range) + returns bool +as +$$ +declare + low_end integer; + high_end integer; +begin + if range_p.low_version is not null then + low_end := rpmver_cmp(version_p, range_p.low_version); + end if; + + if low_end is not null then + if range_p.low_inclusive then + if low_end < 0 then + return false; + end if; + else + if low_end <= 0 then + return false; + end if; + end if; + + end if; + + + if range_p.high_version is not null then + high_end := rpmver_cmp(version_p, range_p.high_version); + end if; + + if high_end is not null then + if range_p.high_inclusive then + if high_end > 0 then + return false; + end if; + else + if high_end >= 0 then + return false; + end if; + end if; + end if; + + if low_end is null and high_end is null then + return false; + end if; + + return true; + +end +$$ + language 'plpgsql' immutable; + diff --git a/migration/src/m0000620_parallel_unsafe_pg_fns/semver_cmp.sql b/migration/src/m0000620_parallel_unsafe_pg_fns/semver_cmp.sql new file mode 100644 index 00000000..84effed4 --- /dev/null +++ b/migration/src/m0000620_parallel_unsafe_pg_fns/semver_cmp.sql @@ -0,0 +1,137 @@ +create or replace function semver_cmp(left_p text, right_p text) + returns integer +as +$$ +declare +left_parts text[]; + right_parts text[]; + + left_major bigint; + left_minor bigint; + left_patch bigint; + left_pre text; + left_build text; + + left_cardinality integer; + + right_major bigint; + right_minor bigint; + right_patch bigint; + right_pre text; + right_build text; + + right_cardinality integer; + + left_numeric bool; + right_numeric bool; + + cur integer; + +begin + left_parts = regexp_split_to_array(left_p, E'\\+'); + left_build = left_parts[2]; + + left_parts = regexp_split_to_array(left_parts[1], E'-'); + left_pre = left_parts[2]; + + left_parts = regexp_split_to_array(left_parts[1], E'\\.'); + left_major = left_parts[1]::decimal; + left_minor = left_parts[2]::decimal; + left_patch = left_parts[3]::decimal; + + right_parts = regexp_split_to_array(right_p, E'\\+'); + right_build = right_parts[2]; + + right_parts = regexp_split_to_array(right_parts[1], E'-'); + right_pre = right_parts[2]; + + right_parts = regexp_split_to_array(right_parts[1], E'\\.'); + right_major = right_parts[1]::decimal; + right_minor = right_parts[2]::decimal; + right_patch = right_parts[3]::decimal; + + if left_major > right_major then + return +1; + elsif left_major < right_major then + return -1; +end if; + + if left_minor > right_minor then + return +1; + elsif left_minor < right_minor then + return -1; +end if; + + if left_patch > right_patch then + return +1; + elsif left_patch < right_patch then + return -1; +end if; + + left_cardinality := greatest(cardinality(left_parts), 3); + right_cardinality := greatest(cardinality(right_parts), 3); + + if left_cardinality > right_cardinality then + return +1; + elsif left_cardinality < right_cardinality then + return -1; +end if; + + if left_pre is null and right_pre is not null then + return +1; + elsif left_pre is not null and right_pre is null then + return -1; + elsif left_pre is not null and right_pre is not null then + left_parts = regexp_split_to_array(left_pre, E'\\.'); + right_parts = regexp_split_to_array(right_pre, E'\\.'); + -- do the hard work + + cur := 0; + loop +cur := cur + 1; + + left_pre := left_parts[cur]; + right_pre := right_parts[cur]; + + if left_pre is null and right_pre is null then + return 0; +end if; + + if left_pre is null and right_pre is not null then + return -1; + elsif left_pre is not null and right_pre is null then + return +1; +end if; + + left_numeric := is_numeric(left_pre); + right_numeric := is_numeric(right_pre); + + if left_numeric and right_numeric then + if left_pre::bigint < right_pre::bigint then + return -1; + elsif left_pre::bigint > right_pre::bigint then + return +1; +end if; +else + if left_pre < right_pre then + return -1; + elsif left_pre > right_pre then + return +1; +end if; +end if; + + if cur > 10 then + exit; +end if; +end loop; +else + return 0; +end if; + +return null; +exception + when others then + return null; +end +$$ +language 'plpgsql' immutable; diff --git a/migration/src/m0000620_parallel_unsafe_pg_fns/semver_eq.sql b/migration/src/m0000620_parallel_unsafe_pg_fns/semver_eq.sql new file mode 100644 index 00000000..3ec85a4a --- /dev/null +++ b/migration/src/m0000620_parallel_unsafe_pg_fns/semver_eq.sql @@ -0,0 +1,12 @@ +create or replace function semver_eq(left_p text, right_p text) + returns bool +as +$$ +declare + cmp integer; +begin + cmp := semver_cmp(left_p, right_p); + return cmp = 0; +end +$$ + language 'plpgsql' immutable; diff --git a/migration/src/m0000620_parallel_unsafe_pg_fns/semver_gt.sql b/migration/src/m0000620_parallel_unsafe_pg_fns/semver_gt.sql new file mode 100644 index 00000000..17c59218 --- /dev/null +++ b/migration/src/m0000620_parallel_unsafe_pg_fns/semver_gt.sql @@ -0,0 +1,12 @@ +create or replace function semver_gt(left_p text, right_p text) + returns bool +as +$$ +declare + cmp integer; +begin + cmp := semver_cmp(left_p, right_p); + return cmp > 0; +end +$$ + language 'plpgsql' immutable; diff --git a/migration/src/m0000620_parallel_unsafe_pg_fns/semver_gte.sql b/migration/src/m0000620_parallel_unsafe_pg_fns/semver_gte.sql new file mode 100644 index 00000000..aaab40ae --- /dev/null +++ b/migration/src/m0000620_parallel_unsafe_pg_fns/semver_gte.sql @@ -0,0 +1,12 @@ +create or replace function semver_gte(left_p text, right_p text) + returns bool +as +$$ +declare + cmp integer; +begin + cmp := semver_cmp(left_p, right_p); + return cmp >= 0; +end +$$ + language 'plpgsql' immutable; diff --git a/migration/src/m0000620_parallel_unsafe_pg_fns/semver_lt.sql b/migration/src/m0000620_parallel_unsafe_pg_fns/semver_lt.sql new file mode 100644 index 00000000..ee97a9a7 --- /dev/null +++ b/migration/src/m0000620_parallel_unsafe_pg_fns/semver_lt.sql @@ -0,0 +1,12 @@ +create or replace function semver_lt(left_p text, right_p text) + returns bool +as +$$ +declare + cmp integer; +begin + cmp := semver_cmp(left_p, right_p); + return cmp < 0; +end +$$ + language 'plpgsql' immutable; diff --git a/migration/src/m0000620_parallel_unsafe_pg_fns/semver_lte.sql b/migration/src/m0000620_parallel_unsafe_pg_fns/semver_lte.sql new file mode 100644 index 00000000..9e15d4aa --- /dev/null +++ b/migration/src/m0000620_parallel_unsafe_pg_fns/semver_lte.sql @@ -0,0 +1,12 @@ +create or replace function semver_lte(left_p text, right_p text) + returns bool +as +$$ +declare + cmp integer; +begin + cmp := semver_cmp(left_p, right_p); + return cmp <= 0; +end +$$ + language 'plpgsql' immutable; diff --git a/migration/src/m0000620_parallel_unsafe_pg_fns/semver_version_matches.sql b/migration/src/m0000620_parallel_unsafe_pg_fns/semver_version_matches.sql new file mode 100644 index 00000000..de524b64 --- /dev/null +++ b/migration/src/m0000620_parallel_unsafe_pg_fns/semver_version_matches.sql @@ -0,0 +1,53 @@ + +create or replace function semver_version_matches(version_p text, range_p version_range) + returns bool +as +$$ +declare + low_end integer; + high_end integer; +begin + if range_p.low_version is not null then + low_end := semver_cmp(version_p, range_p.low_version); + end if; + + if low_end is not null then + if range_p.low_inclusive then + if low_end < 0 then + return false; + end if; + else + if low_end <= 0 then + return false; + end if; + end if; + + end if; + + + if range_p.high_version is not null then + high_end := semver_cmp(version_p, range_p.high_version); + end if; + + if high_end is not null then + if range_p.high_inclusive then + if high_end > 0 then + return false; + end if; + else + if high_end >= 0 then + return false; + end if; + end if; + end if; + + if low_end is null and high_end is null then + return false; + end if; + + return true; + +end +$$ + language 'plpgsql' immutable; + diff --git a/migration/src/m0000620_parallel_unsafe_pg_fns/version_matches.sql b/migration/src/m0000620_parallel_unsafe_pg_fns/version_matches.sql new file mode 100644 index 00000000..b11c02d7 --- /dev/null +++ b/migration/src/m0000620_parallel_unsafe_pg_fns/version_matches.sql @@ -0,0 +1,40 @@ +create or replace function version_matches(version_p text, range_p version_range) + returns bool +as +$$ +declare +begin + return case + when range_p.version_scheme_id = 'git' + -- Git is git, and hard. + then gitver_version_matches(version_p, range_p) + when range_p.version_scheme_id = 'semver' + -- Semver is semver + then semver_version_matches(version_p, range_p) + when range_p.version_scheme_id = 'gem' + -- RubyGems claims to be semver + then semver_version_matches(version_p, range_p) + when range_p.version_scheme_id = 'npm' + -- NPM claims to be semver + then semver_version_matches(version_p, range_p) + when range_p.version_scheme_id = 'golang' + -- Golang claims to be semver + then semver_version_matches(version_p, range_p) + when range_p.version_scheme_id = 'nuget' + -- NuGet claims to be semver + then semver_version_matches(version_p, range_p) + when range_p.version_scheme_id = 'generic' + -- Might was well try semver + then semver_version_matches(version_p, range_p) + when range_p.version_scheme_id = 'rpm' + -- Look at me! I'm an RPM! I'm special! + then rpmver_version_matches(version_p, range_p) + when range_p.version_scheme_id = 'maven' + -- Look at me! I'm a Maven! I'm kinda special! + then maven_version_matches(version_p, range_p) + else + false + end; +end +$$ + language plpgsql immutable;