-module(galchemy@session@unit_of_work). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src\\galchemy\\session\\unit_of_work.gleam"). -export([new/1, field/2, identity/1, register_new/3, register_dirty/4, register_deleted/3, queries/1, discard_entity_changes/4, flush_plan/1]). -export_type([field_value/0, identity/0, pending_insert/0, pending_update/0, pending_delete/0, session/0, flush_plan/0, session_error/0]). -type field_value() :: {field_value, binary(), galchemy@ast@expression:sql_value()}. -type identity() :: {identity, list(field_value())}. -type pending_insert() :: {pending_insert, galchemy@schema@relation:table_ref(), list(field_value())}. -type pending_update() :: {pending_update, galchemy@schema@relation:table_ref(), identity(), list(field_value())}. -type pending_delete() :: {pending_delete, galchemy@schema@relation:table_ref(), identity()}. -type session() :: {session, galchemy@schema@model:schema_snapshot(), list(pending_insert()), list(pending_update()), list(pending_delete())}. -type flush_plan() :: {flush_plan, list(galchemy@ast@query:'query'()), list(galchemy@ast@query:'query'()), list(galchemy@ast@query:'query'())}. -type session_error() :: {unknown_table, galchemy@schema@relation:table_ref()} | {unknown_column, galchemy@schema@relation:table_ref(), binary()} | {empty_insert_values, galchemy@schema@relation:table_ref()} | {empty_changes, galchemy@schema@relation:table_ref()} | {empty_identity, galchemy@schema@relation:table_ref()}. -file("src\\galchemy\\session\\unit_of_work.gleam", 58). -spec new(galchemy@schema@model:schema_snapshot()) -> session(). new(Snapshot) -> {session, Snapshot, [], [], []}. -file("src\\galchemy\\session\\unit_of_work.gleam", 62). -spec field(binary(), galchemy@ast@expression:sql_value()) -> field_value(). field(Column, Value) -> {field_value, Column, Value}. -file("src\\galchemy\\session\\unit_of_work.gleam", 66). -spec identity(list(field_value())) -> identity(). identity(Fields) -> {identity, Fields}. -file("src\\galchemy\\session\\unit_of_work.gleam", 70). -spec register_new( session(), galchemy@schema@relation:table_ref(), list(field_value()) ) -> session(). register_new(Session, Table, Values) -> {session, erlang:element(2, Session), lists:append( erlang:element(3, Session), [{pending_insert, Table, Values}] ), erlang:element(4, Session), erlang:element(5, Session)}. -file("src\\galchemy\\session\\unit_of_work.gleam", 83). -spec register_dirty( session(), galchemy@schema@relation:table_ref(), identity(), list(field_value()) ) -> session(). register_dirty(Session, Table, Identity, Changes) -> {session, erlang:element(2, Session), erlang:element(3, Session), lists:append( erlang:element(4, Session), [{pending_update, Table, Identity, Changes}] ), erlang:element(5, Session)}. -file("src\\galchemy\\session\\unit_of_work.gleam", 97). -spec register_deleted( session(), galchemy@schema@relation:table_ref(), identity() ) -> session(). register_deleted(Session, Table, Identity) -> {session, erlang:element(2, Session), erlang:element(3, Session), erlang:element(4, Session), lists:append( erlang:element(5, Session), [{pending_delete, Table, Identity}] )}. -file("src\\galchemy\\session\\unit_of_work.gleam", 139). -spec queries(flush_plan()) -> list(galchemy@ast@query:'query'()). queries(Plan) -> _pipe = erlang:element(2, Plan), _pipe@1 = lists:append(_pipe, erlang:element(3, Plan)), lists:append(_pipe@1, erlang:element(4, Plan)). -file("src\\galchemy\\session\\unit_of_work.gleam", 407). -spec has_column(list(galchemy@schema@model:column_schema()), binary()) -> boolean(). has_column(Columns, Column_name) -> case Columns of [] -> false; [Column_schema | Rest] -> case erlang:element(2, Column_schema) =:= Column_name of true -> true; false -> has_column(Rest, Column_name) end end. -file("src\\galchemy\\session\\unit_of_work.gleam", 429). -spec find_table_schema_in( list(galchemy@schema@model:table_schema()), galchemy@schema@relation:table_ref() ) -> gleam@option:option(galchemy@schema@model:table_schema()). find_table_schema_in(Tables, Table) -> case Tables of [] -> none; [Table_schema | Rest] -> case (erlang:element(2, Table_schema) =:= erlang:element(2, Table)) andalso (erlang:element(3, Table_schema) =:= erlang:element( 3, Table )) of true -> {some, Table_schema}; false -> find_table_schema_in(Rest, Table) end end. -file("src\\galchemy\\session\\unit_of_work.gleam", 419). -spec find_table_schema( galchemy@schema@model:schema_snapshot(), galchemy@schema@relation:table_ref() ) -> {ok, galchemy@schema@model:table_schema()} | {error, session_error()}. find_table_schema(Snapshot, Table) -> case find_table_schema_in(erlang:element(2, Snapshot), Table) of {some, Table_schema} -> {ok, Table_schema}; none -> {error, {unknown_table, Table}} end. -file("src\\galchemy\\session\\unit_of_work.gleam", 446). -spec ast_table(galchemy@schema@relation:table_ref()) -> galchemy@ast@schema:table(). ast_table(Table) -> {table, {some, erlang:element(2, Table)}, erlang:element(3, Table), none}. -file("src\\galchemy\\session\\unit_of_work.gleam", 396). -spec column_meta_for( galchemy@schema@relation:table_ref(), galchemy@schema@model:table_schema(), binary() ) -> {ok, galchemy@ast@schema:column_meta()} | {error, session_error()}. column_meta_for(Table, Table_schema, Column_name) -> case has_column(erlang:element(4, Table_schema), Column_name) of true -> {ok, {column_meta, ast_table(Table), Column_name}}; false -> {error, {unknown_column, Table, Column_name}} end. -file("src\\galchemy\\session\\unit_of_work.gleam", 468). -spec order_inserts_for_tables( list(pending_insert()), list(galchemy@schema@relation:table_ref()), list(pending_insert()) ) -> list(pending_insert()). order_inserts_for_tables(Inserts, Table_order, Acc) -> case Table_order of [] -> Acc; [Table_ref | Rest] -> order_inserts_for_tables( Inserts, Rest, lists:append( Acc, gleam@list:filter( Inserts, fun(Insert_change) -> erlang:element(2, Insert_change) =:= Table_ref end ) ) ) end. -file("src\\galchemy\\session\\unit_of_work.gleam", 490). -spec contains_insert(list(pending_insert()), pending_insert()) -> boolean(). contains_insert(Inserts, Target) -> case Inserts of [] -> false; [Insert_change | Rest] -> case Insert_change =:= Target of true -> true; false -> contains_insert(Rest, Target) end end. -file("src\\galchemy\\session\\unit_of_work.gleam", 454). -spec order_inserts( list(pending_insert()), list(galchemy@schema@relation:table_ref()) ) -> list(pending_insert()). order_inserts(Inserts, Table_order) -> Ordered = order_inserts_for_tables(Inserts, Table_order, []), lists:append( Ordered, gleam@list:filter( Inserts, fun(Insert_change) -> not contains_insert(Ordered, Insert_change) end ) ). -file("src\\galchemy\\session\\unit_of_work.gleam", 516). -spec order_deletes_for_tables( list(pending_delete()), list(galchemy@schema@relation:table_ref()), list(pending_delete()) ) -> list(pending_delete()). order_deletes_for_tables(Deletes, Table_order, Acc) -> case Table_order of [] -> Acc; [Table_ref | Rest] -> order_deletes_for_tables( Deletes, Rest, lists:append( Acc, gleam@list:filter( Deletes, fun(Delete_change) -> erlang:element(2, Delete_change) =:= Table_ref end ) ) ) end. -file("src\\galchemy\\session\\unit_of_work.gleam", 538). -spec contains_delete(list(pending_delete()), pending_delete()) -> boolean(). contains_delete(Deletes, Target) -> case Deletes of [] -> false; [Delete_change | Rest] -> case Delete_change =:= Target of true -> true; false -> contains_delete(Rest, Target) end end. -file("src\\galchemy\\session\\unit_of_work.gleam", 502). -spec order_deletes( list(pending_delete()), list(galchemy@schema@relation:table_ref()) ) -> list(pending_delete()). order_deletes(Deletes, Table_order) -> Ordered = order_deletes_for_tables(Deletes, Table_order, []), lists:append( Ordered, gleam@list:filter( Deletes, fun(Delete_change) -> not contains_delete(Ordered, Delete_change) end ) ). -file("src\\galchemy\\session\\unit_of_work.gleam", 550). -spec discard_inserts( list(pending_insert()), galchemy@schema@relation:table_ref(), list(field_value()) ) -> list(pending_insert()). discard_inserts(Inserts, Table, Values) -> case Inserts of [] -> []; [Insert_change | Rest] -> case (erlang:element(2, Insert_change) =:= Table) andalso (erlang:element( 3, Insert_change ) =:= Values) of true -> discard_inserts(Rest, Table, Values); false -> [Insert_change | discard_inserts(Rest, Table, Values)] end end. -file("src\\galchemy\\session\\unit_of_work.gleam", 566). -spec discard_updates( list(pending_update()), galchemy@schema@relation:table_ref(), identity() ) -> list(pending_update()). discard_updates(Updates, Table, Identity) -> case Updates of [] -> []; [Update_change | Rest] -> case (erlang:element(2, Update_change) =:= Table) andalso (erlang:element( 3, Update_change ) =:= Identity) of true -> discard_updates(Rest, Table, Identity); false -> [Update_change | discard_updates(Rest, Table, Identity)] end end. -file("src\\galchemy\\session\\unit_of_work.gleam", 582). -spec discard_deletes( list(pending_delete()), galchemy@schema@relation:table_ref(), identity() ) -> list(pending_delete()). discard_deletes(Deletes, Table, Identity) -> case Deletes of [] -> []; [Delete_change | Rest] -> case (erlang:element(2, Delete_change) =:= Table) andalso (erlang:element( 3, Delete_change ) =:= Identity) of true -> discard_deletes(Rest, Table, Identity); false -> [Delete_change | discard_deletes(Rest, Table, Identity)] end end. -file("src\\galchemy\\session\\unit_of_work.gleam", 110). -spec discard_entity_changes( session(), galchemy@schema@relation:table_ref(), identity(), list(field_value()) ) -> session(). discard_entity_changes(Session, Table, Identity, Values) -> {session, erlang:element(2, Session), discard_inserts(erlang:element(3, Session), Table, Values), discard_updates(erlang:element(4, Session), Table, Identity), discard_deletes(erlang:element(5, Session), Table, Identity)}. -file("src\\galchemy\\session\\unit_of_work.gleam", 660). -spec find_relations_for_table( list(galchemy@schema@relation:table_relations()), galchemy@schema@relation:table_ref() ) -> gleam@option:option(galchemy@schema@relation:table_relations()). find_relations_for_table(Relations_by_table, Table) -> case Relations_by_table of [] -> none; [Table_relations | Rest] -> case erlang:element(2, Table_relations) =:= Table of true -> {some, Table_relations}; false -> find_relations_for_table(Rest, Table) end end. -file("src\\galchemy\\session\\unit_of_work.gleam", 637). -spec dependencies_resolved( galchemy@schema@relation:table_ref(), list(galchemy@schema@relation:table_ref()), list(galchemy@schema@relation:table_relations()) ) -> boolean(). dependencies_resolved(Table, Remaining, Relations_by_table) -> case find_relations_for_table(Relations_by_table, Table) of none -> true; {some, Table_relations} -> Dependencies = gleam@list:filter_map( erlang:element(3, Table_relations), fun(Next_relation) -> case erlang:element(4, Next_relation) of belongs_to -> {ok, erlang:element(5, Next_relation)}; has_many -> {error, nil} end end ), gleam@list:fold( Dependencies, true, fun(Acc, Dependency) -> Acc andalso not gleam@list:contains(Remaining, Dependency) end ) end. -file("src\\galchemy\\session\\unit_of_work.gleam", 607). -spec resolve_insert_order( list(galchemy@schema@relation:table_ref()), list(galchemy@schema@relation:table_relations()), list(galchemy@schema@relation:table_ref()) ) -> list(galchemy@schema@relation:table_ref()). resolve_insert_order(Remaining, Relations_by_table, Resolved) -> case Remaining of [] -> Resolved; _ -> Ready = gleam@list:filter( Remaining, fun(Table_ref) -> dependencies_resolved( Table_ref, Remaining, Relations_by_table ) end ), Blocked = gleam@list:filter( Remaining, fun(Table_ref@1) -> not gleam@list:contains(Ready, Table_ref@1) end ), case Ready of [] -> lists:append(Resolved, Remaining); _ -> resolve_insert_order( Blocked, Relations_by_table, lists:append(Resolved, Ready) ) end end. -file("src\\galchemy\\session\\unit_of_work.gleam", 598). -spec insert_table_order(galchemy@schema@model:schema_snapshot()) -> list(galchemy@schema@relation:table_ref()). insert_table_order(Snapshot) -> All_tables = gleam@list:map( erlang:element(2, Snapshot), fun(Table_schema) -> galchemy@schema@relation:table_ref( erlang:element(2, Table_schema), erlang:element(3, Table_schema) ) end ), resolve_insert_order( All_tables, galchemy@schema@relation:infer(Snapshot), [] ). -file("src\\galchemy\\session\\unit_of_work.gleam", 675). -spec result_try( {ok, IRX} | {error, IRY}, fun((IRX) -> {ok, ISB} | {error, IRY}) ) -> {ok, ISB} | {error, IRY}. result_try(Value, Next) -> case Value of {ok, Inner} -> Next(Inner); {error, Error} -> {error, Error} end. -file("src\\galchemy\\session\\unit_of_work.gleam", 348). -spec predicate_for_field( galchemy@schema@relation:table_ref(), galchemy@schema@model:table_schema(), field_value() ) -> {ok, galchemy@ast@expression:predicate()} | {error, session_error()}. predicate_for_field(Table, Table_schema, Field_value) -> result_try( column_meta_for(Table, Table_schema, erlang:element(2, Field_value)), fun(Column_meta) -> {ok, {comparison, {column_expr, Column_meta}, eq, {value_expr, erlang:element(3, Field_value)}}} end ). -file("src\\galchemy\\session\\unit_of_work.gleam", 324). -spec predicate_for_identity_rest( galchemy@schema@relation:table_ref(), galchemy@schema@model:table_schema(), list(field_value()), galchemy@ast@expression:predicate() ) -> {ok, galchemy@ast@expression:predicate()} | {error, session_error()}. predicate_for_identity_rest(Table, Table_schema, Fields, Acc) -> case Fields of [] -> {ok, Acc}; [Field_value | Rest] -> result_try( predicate_for_field(Table, Table_schema, Field_value), fun(Next_predicate) -> predicate_for_identity_rest( Table, Table_schema, Rest, {'and', Acc, Next_predicate} ) end ) end. -file("src\\galchemy\\session\\unit_of_work.gleam", 306). -spec predicate_for_identity( galchemy@schema@relation:table_ref(), galchemy@schema@model:table_schema(), identity() ) -> {ok, galchemy@ast@expression:predicate()} | {error, session_error()}. predicate_for_identity(Table, Table_schema, Identity) -> case erlang:element(2, Identity) of [] -> {error, {empty_identity, Table}}; [First | Rest] -> result_try( predicate_for_field(Table, Table_schema, First), fun(First_predicate) -> predicate_for_identity_rest( Table, Table_schema, Rest, First_predicate ) end ) end. -file("src\\galchemy\\session\\unit_of_work.gleam", 281). -spec delete_query(pending_delete(), galchemy@schema@model:schema_snapshot()) -> {ok, galchemy@ast@query:'query'()} | {error, session_error()}. delete_query(Delete_change, Snapshot) -> result_try( find_table_schema(Snapshot, erlang:element(2, Delete_change)), fun(Table_schema) -> result_try( predicate_for_identity( erlang:element(2, Delete_change), Table_schema, erlang:element(3, Delete_change) ), fun(Where_) -> {ok, {delete, {delete_query, ast_table(erlang:element(2, Delete_change)), {some, Where_}, []}}} end ) end ). -file("src\\galchemy\\session\\unit_of_work.gleam", 201). -spec build_delete_queries_loop( list(pending_delete()), galchemy@schema@model:schema_snapshot(), list(galchemy@ast@query:'query'()) ) -> {ok, list(galchemy@ast@query:'query'())} | {error, session_error()}. build_delete_queries_loop(Deletes, Snapshot, Acc) -> case Deletes of [] -> {ok, lists:reverse(Acc)}; [Delete_change | Rest] -> result_try( delete_query(Delete_change, Snapshot), fun(Next_query) -> build_delete_queries_loop( Rest, Snapshot, [Next_query | Acc] ) end ) end. -file("src\\galchemy\\session\\unit_of_work.gleam", 190). -spec build_delete_queries( session(), list(galchemy@schema@relation:table_ref()) ) -> {ok, list(galchemy@ast@query:'query'())} | {error, session_error()}. build_delete_queries(Session, Table_order) -> build_delete_queries_loop( order_deletes(erlang:element(5, Session), Table_order), erlang:element(2, Session), [] ). -file("src\\galchemy\\session\\unit_of_work.gleam", 374). -spec assignments_for_loop( galchemy@schema@relation:table_ref(), galchemy@schema@model:table_schema(), list(field_value()), list({galchemy@ast@schema:column_meta(), galchemy@ast@expression:expression()}) ) -> {ok, list({galchemy@ast@schema:column_meta(), galchemy@ast@expression:expression()})} | {error, session_error()}. assignments_for_loop(Table, Table_schema, Fields, Acc) -> case Fields of [] -> {ok, lists:reverse(Acc)}; [Field_value | Rest] -> result_try( column_meta_for( Table, Table_schema, erlang:element(2, Field_value) ), fun(Column_meta) -> assignments_for_loop( Table, Table_schema, Rest, [{Column_meta, {value_expr, erlang:element(3, Field_value)}} | Acc] ) end ) end. -file("src\\galchemy\\session\\unit_of_work.gleam", 366). -spec assignments_for( galchemy@schema@relation:table_ref(), galchemy@schema@model:table_schema(), list(field_value()) ) -> {ok, list({galchemy@ast@schema:column_meta(), galchemy@ast@expression:expression()})} | {error, session_error()}. assignments_for(Table, Table_schema, Fields) -> assignments_for_loop(Table, Table_schema, Fields, []). -file("src\\galchemy\\session\\unit_of_work.gleam", 215). -spec insert_query(pending_insert(), galchemy@schema@model:schema_snapshot()) -> {ok, galchemy@ast@query:'query'()} | {error, session_error()}. insert_query(Insert_change, Snapshot) -> case erlang:element(3, Insert_change) of [] -> {error, {empty_insert_values, erlang:element(2, Insert_change)}}; _ -> result_try( find_table_schema(Snapshot, erlang:element(2, Insert_change)), fun(Table_schema) -> result_try( assignments_for( erlang:element(2, Insert_change), Table_schema, erlang:element(3, Insert_change) ), fun(Assignments) -> {ok, {insert, {insert_query, ast_table( erlang:element(2, Insert_change) ), [Assignments], []}}} end ) end ) end. -file("src\\galchemy\\session\\unit_of_work.gleam", 156). -spec build_insert_queries_loop( list(pending_insert()), galchemy@schema@model:schema_snapshot(), list(galchemy@ast@query:'query'()) ) -> {ok, list(galchemy@ast@query:'query'())} | {error, session_error()}. build_insert_queries_loop(Inserts, Snapshot, Acc) -> case Inserts of [] -> {ok, lists:reverse(Acc)}; [Insert_change | Rest] -> result_try( insert_query(Insert_change, Snapshot), fun(Next_query) -> build_insert_queries_loop( Rest, Snapshot, [Next_query | Acc] ) end ) end. -file("src\\galchemy\\session\\unit_of_work.gleam", 145). -spec build_insert_queries( session(), list(galchemy@schema@relation:table_ref()) ) -> {ok, list(galchemy@ast@query:'query'())} | {error, session_error()}. build_insert_queries(Session, Table_order) -> build_insert_queries_loop( order_inserts(erlang:element(3, Session), Table_order), erlang:element(2, Session), [] ). -file("src\\galchemy\\session\\unit_of_work.gleam", 245). -spec update_query(pending_update(), galchemy@schema@model:schema_snapshot()) -> {ok, galchemy@ast@query:'query'()} | {error, session_error()}. update_query(Update_change, Snapshot) -> case erlang:element(4, Update_change) of [] -> {error, {empty_changes, erlang:element(2, Update_change)}}; _ -> result_try( find_table_schema(Snapshot, erlang:element(2, Update_change)), fun(Table_schema) -> result_try( assignments_for( erlang:element(2, Update_change), Table_schema, erlang:element(4, Update_change) ), fun(Assignments) -> result_try( predicate_for_identity( erlang:element(2, Update_change), Table_schema, erlang:element(3, Update_change) ), fun(Where_) -> {ok, {update, {update_query, ast_table( erlang:element( 2, Update_change ) ), Assignments, {some, Where_}, []}}} end ) end ) end ) end. -file("src\\galchemy\\session\\unit_of_work.gleam", 176). -spec build_update_queries_loop( list(pending_update()), galchemy@schema@model:schema_snapshot(), list(galchemy@ast@query:'query'()) ) -> {ok, list(galchemy@ast@query:'query'())} | {error, session_error()}. build_update_queries_loop(Updates, Snapshot, Acc) -> case Updates of [] -> {ok, lists:reverse(Acc)}; [Update_change | Rest] -> result_try( update_query(Update_change, Snapshot), fun(Next_query) -> build_update_queries_loop( Rest, Snapshot, [Next_query | Acc] ) end ) end. -file("src\\galchemy\\session\\unit_of_work.gleam", 170). -spec build_update_queries(session()) -> {ok, list(galchemy@ast@query:'query'())} | {error, session_error()}. build_update_queries(Session) -> build_update_queries_loop( erlang:element(4, Session), erlang:element(2, Session), [] ). -file("src\\galchemy\\session\\unit_of_work.gleam", 124). -spec flush_plan(session()) -> {ok, flush_plan()} | {error, session_error()}. flush_plan(Session) -> Insert_order = insert_table_order(erlang:element(2, Session)), Delete_order = lists:reverse(Insert_order), result_try( build_insert_queries(Session, Insert_order), fun(Insert_queries) -> result_try( build_update_queries(Session), fun(Update_queries) -> result_try( build_delete_queries(Session, Delete_order), fun(Delete_queries) -> {ok, {flush_plan, Insert_queries, Update_queries, Delete_queries}} end ) end ) end ).