-module(galchemy@schema@relation). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src\\galchemy\\schema\\relation.gleam"). -export([table_ref/2, pair/2, belongs_to/4, has_many/4, infer/1, for_table/3]). -export_type([table_ref/0, relation_kind/0, column_pair/0, relation/0, table_relations/0]). -type table_ref() :: {table_ref, binary(), binary()}. -type relation_kind() :: belongs_to | has_many. -type column_pair() :: {column_pair, binary(), binary()}. -type relation() :: {relation, binary(), binary(), relation_kind(), table_ref(), list(column_pair())}. -type table_relations() :: {table_relations, table_ref(), list(relation())}. -file("src\\galchemy\\schema\\relation.gleam", 34). -spec table_ref(binary(), binary()) -> table_ref(). table_ref(Schema, Name) -> {table_ref, Schema, Name}. -file("src\\galchemy\\schema\\relation.gleam", 38). -spec pair(binary(), binary()) -> column_pair(). pair(Local_column, Related_column) -> {column_pair, Local_column, Related_column}. -file("src\\galchemy\\schema\\relation.gleam", 42). -spec belongs_to(binary(), binary(), table_ref(), list(column_pair())) -> relation(). belongs_to(Name, Foreign_key_name, Related_table, Column_pairs) -> {relation, Name, Foreign_key_name, belongs_to, Related_table, Column_pairs}. -file("src\\galchemy\\schema\\relation.gleam", 57). -spec has_many(binary(), binary(), table_ref(), list(column_pair())) -> relation(). has_many(Name, Foreign_key_name, Related_table, Column_pairs) -> {relation, Name, Foreign_key_name, has_many, Related_table, Column_pairs}. -file("src\\galchemy\\schema\\relation.gleam", 130). -spec add_relation(list(table_relations()), table_ref(), relation()) -> list(table_relations()). add_relation(Relations_by_table, Target_table, Next_relation) -> gleam@list:map( Relations_by_table, fun(Table_relations) -> case erlang:element(2, Table_relations) =:= Target_table of true -> {table_relations, erlang:element(2, Table_relations), lists:append( erlang:element(3, Table_relations), [Next_relation] )}; false -> Table_relations end end ). -file("src\\galchemy\\schema\\relation.gleam", 159). -spec find_table_relations(list(table_relations()), table_ref()) -> gleam@option:option(table_relations()). find_table_relations(Relations_by_table, Target_table) -> case Relations_by_table of [] -> none; [Table_relations | Rest] -> case erlang:element(2, Table_relations) =:= Target_table of true -> {some, Table_relations}; false -> find_table_relations(Rest, Target_table) end end. -file("src\\galchemy\\schema\\relation.gleam", 148). -spec add_relation_if_present(list(table_relations()), table_ref(), relation()) -> list(table_relations()). add_relation_if_present(Relations_by_table, Target_table, Next_relation) -> case find_table_relations(Relations_by_table, Target_table) of {some, _} -> add_relation(Relations_by_table, Target_table, Next_relation); none -> Relations_by_table end. -file("src\\galchemy\\schema\\relation.gleam", 200). -spec dedupe_relation_name(binary(), list(binary()), integer()) -> binary(). dedupe_relation_name(Base_name, Used_names, Suffix) -> Candidate = <<<>/binary, (erlang:integer_to_binary(Suffix))/binary>>, case gleam@list:contains(Used_names, Candidate) of true -> dedupe_relation_name(Base_name, Used_names, Suffix + 1); false -> Candidate end. -file("src\\galchemy\\schema\\relation.gleam", 193). -spec next_relation_name(binary(), list(binary())) -> binary(). next_relation_name(Base_name, Used_names) -> case gleam@list:contains(Used_names, Base_name) of true -> dedupe_relation_name(Base_name, Used_names, 2); false -> Base_name end. -file("src\\galchemy\\schema\\relation.gleam", 174). -spec normalize_relation_names(table_relations()) -> table_relations(). normalize_relation_names(Table_relations) -> {Reversed_relations, _} = gleam@list:fold( erlang:element(3, Table_relations), {[], []}, fun(Acc, Relation) -> {Normalized, Used_names} = Acc, Normalized_name = next_relation_name( erlang:element(2, Relation), Used_names ), {[{relation, Normalized_name, erlang:element(3, Relation), erlang:element(4, Relation), erlang:element(5, Relation), erlang:element(6, Relation)} | Normalized], [Normalized_name | Used_names]} end ), {table_relations, erlang:element(2, Table_relations), lists:reverse(Reversed_relations)}. -file("src\\galchemy\\schema\\relation.gleam", 269). -spec zip_columns(list(binary()), list(binary())) -> list(column_pair()). zip_columns(Local_columns, Related_columns) -> gleam@list:map2( Local_columns, Related_columns, fun(Local, Related) -> pair(Local, Related) end ). -file("src\\galchemy\\schema\\relation.gleam", 296). -spec is_identifier_character(binary()) -> boolean(). is_identifier_character(Character) -> case Character of <<"a"/utf8>> -> true; <<"b"/utf8>> -> true; <<"c"/utf8>> -> true; <<"d"/utf8>> -> true; <<"e"/utf8>> -> true; <<"f"/utf8>> -> true; <<"g"/utf8>> -> true; <<"h"/utf8>> -> true; <<"i"/utf8>> -> true; <<"j"/utf8>> -> true; <<"k"/utf8>> -> true; <<"l"/utf8>> -> true; <<"m"/utf8>> -> true; <<"n"/utf8>> -> true; <<"o"/utf8>> -> true; <<"p"/utf8>> -> true; <<"q"/utf8>> -> true; <<"r"/utf8>> -> true; <<"s"/utf8>> -> true; <<"t"/utf8>> -> true; <<"u"/utf8>> -> true; <<"v"/utf8>> -> true; <<"w"/utf8>> -> true; <<"x"/utf8>> -> true; <<"y"/utf8>> -> true; <<"z"/utf8>> -> true; <<"0"/utf8>> -> true; <<"1"/utf8>> -> true; <<"2"/utf8>> -> true; <<"3"/utf8>> -> true; <<"4"/utf8>> -> true; <<"5"/utf8>> -> true; <<"6"/utf8>> -> true; <<"7"/utf8>> -> true; <<"8"/utf8>> -> true; <<"9"/utf8>> -> true; <<"_"/utf8>> -> true; _ -> false end. -file("src\\galchemy\\schema\\relation.gleam", 339). -spec collapse_underscores(binary()) -> binary(). collapse_underscores(Value) -> case gleam_stdlib:contains_string(Value, <<"__"/utf8>>) of true -> _pipe = Value, _pipe@1 = gleam@string:replace(_pipe, <<"__"/utf8>>, <<"_"/utf8>>), collapse_underscores(_pipe@1); false -> Value end. -file("src\\galchemy\\schema\\relation.gleam", 356). -spec trim_leading_underscores(binary()) -> binary(). trim_leading_underscores(Value) -> case gleam_stdlib:string_starts_with(Value, <<"_"/utf8>>) of true -> _pipe = Value, _pipe@1 = gleam@string:drop_start(_pipe, 1), trim_leading_underscores(_pipe@1); false -> Value end. -file("src\\galchemy\\schema\\relation.gleam", 367). -spec trim_trailing_underscores(binary()) -> binary(). trim_trailing_underscores(Value) -> case gleam_stdlib:string_ends_with(Value, <<"_"/utf8>>) of true -> _pipe = Value, _pipe@1 = gleam@string:drop_end(_pipe, 1), trim_trailing_underscores(_pipe@1); false -> Value end. -file("src\\galchemy\\schema\\relation.gleam", 350). -spec trim_edge_underscores(binary()) -> binary(). trim_edge_underscores(Value) -> _pipe = Value, _pipe@1 = trim_leading_underscores(_pipe), trim_trailing_underscores(_pipe@1). -file("src\\galchemy\\schema\\relation.gleam", 391). -spec is_digit(binary()) -> boolean(). is_digit(Character) -> case Character of <<"0"/utf8>> -> true; <<"1"/utf8>> -> true; <<"2"/utf8>> -> true; <<"3"/utf8>> -> true; <<"4"/utf8>> -> true; <<"5"/utf8>> -> true; <<"6"/utf8>> -> true; <<"7"/utf8>> -> true; <<"8"/utf8>> -> true; <<"9"/utf8>> -> true; _ -> false end. -file("src\\galchemy\\schema\\relation.gleam", 378). -spec prefix_if_needed(binary()) -> binary(). prefix_if_needed(Value) -> case gleam@string:to_graphemes(Value) of [First | _] -> case is_digit(First) of true -> <<"relation_"/utf8, Value/binary>>; false -> Value end; [] -> <<"relation"/utf8>> end. -file("src\\galchemy\\schema\\relation.gleam", 278). -spec sanitize_name(binary(), binary()) -> binary(). sanitize_name(Value, Fallback) -> Characters = gleam@string:to_graphemes(string:lowercase(Value)), Normalized = begin _pipe = gleam@list:fold( Characters, <<""/utf8>>, fun(Acc, Character) -> case is_identifier_character(Character) of true -> <>; false -> <> end end ), _pipe@1 = collapse_underscores(_pipe), trim_edge_underscores(_pipe@1) end, case Normalized of <<""/utf8>> -> sanitize_name(Fallback, <<"relation"/utf8>>); _ -> prefix_if_needed(Normalized) end. -file("src\\galchemy\\schema\\relation.gleam", 252). -spec singularize_table_name(binary(), binary()) -> binary(). singularize_table_name(Table_name, Fallback_table_name) -> Singular = case gleam_stdlib:string_ends_with(Table_name, <<"ies"/utf8>>) of true -> <<(gleam@string:drop_end(Table_name, 3))/binary, "y"/utf8>>; false -> case gleam_stdlib:string_ends_with(Table_name, <<"s"/utf8>>) of true -> gleam@string:drop_end(Table_name, 1); false -> Table_name end end, sanitize_name(Singular, Fallback_table_name). -file("src\\galchemy\\schema\\relation.gleam", 228). -spec column_name_to_relation_name(binary(), binary()) -> binary(). column_name_to_relation_name(Column_name, Fallback_table_name) -> case gleam_stdlib:string_ends_with(Column_name, <<"_id"/utf8>>) of true -> _pipe = Column_name, _pipe@1 = gleam@string:drop_end(_pipe, 3), sanitize_name(_pipe@1, Fallback_table_name); false -> case gleam_stdlib:string_ends_with(Column_name, <<"_uuid"/utf8>>) of true -> _pipe@2 = Column_name, _pipe@3 = gleam@string:drop_end(_pipe@2, 5), sanitize_name(_pipe@3, Fallback_table_name); false -> singularize_table_name( Fallback_table_name, Fallback_table_name ) end end. -file("src\\galchemy\\schema\\relation.gleam", 213). -spec base_belongs_to_name(galchemy@schema@model:foreign_key(), binary()) -> binary(). base_belongs_to_name(Foreign_key, _) -> case erlang:element(3, Foreign_key) of [Column_name] -> column_name_to_relation_name( Column_name, erlang:element(5, Foreign_key) ); _ -> singularize_table_name( erlang:element(5, Foreign_key), erlang:element(5, Foreign_key) ) end. -file("src\\galchemy\\schema\\relation.gleam", 97). -spec infer_table_relations( galchemy@schema@model:table_schema(), list(table_relations()) ) -> list(table_relations()). infer_table_relations(Table_schema, Acc) -> gleam@list:fold( erlang:element(7, Table_schema), Acc, fun(Inner_acc, Foreign_key) -> Source_table = table_ref( erlang:element(2, Table_schema), erlang:element(3, Table_schema) ), Target_table = table_ref( erlang:element(4, Foreign_key), erlang:element(5, Foreign_key) ), Outgoing_relation = belongs_to( base_belongs_to_name( Foreign_key, erlang:element(3, Table_schema) ), erlang:element(2, Foreign_key), Target_table, zip_columns( erlang:element(3, Foreign_key), erlang:element(6, Foreign_key) ) ), Incoming_relation = has_many( sanitize_name( erlang:element(3, Table_schema), <<"related"/utf8>> ), erlang:element(2, Foreign_key), Source_table, zip_columns( erlang:element(6, Foreign_key), erlang:element(3, Foreign_key) ) ), _pipe = Inner_acc, _pipe@1 = add_relation(_pipe, Source_table, Outgoing_relation), add_relation_if_present(_pipe@1, Target_table, Incoming_relation) end ). -file("src\\galchemy\\schema\\relation.gleam", 72). -spec infer(galchemy@schema@model:schema_snapshot()) -> list(table_relations()). infer(Snapshot) -> Base_relations = gleam@list:map( erlang:element(2, Snapshot), fun(Table_schema) -> {table_relations, table_ref( erlang:element(2, Table_schema), erlang:element(3, Table_schema) ), []} end ), _pipe = gleam@list:fold( erlang:element(2, Snapshot), Base_relations, fun(Acc, Table_schema@1) -> infer_table_relations(Table_schema@1, Acc) end ), gleam@list:map(_pipe, fun normalize_relation_names/1). -file("src\\galchemy\\schema\\relation.gleam", 89). -spec for_table(galchemy@schema@model:schema_snapshot(), binary(), binary()) -> gleam@option:option(table_relations()). for_table(Snapshot, Schema_name, Table_name) -> find_table_relations(infer(Snapshot), table_ref(Schema_name, Table_name)).