-module(glsql@internal@validate). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/glsql/internal/validate.gleam"). -export([check/2]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC(false). -file("src/glsql/internal/validate.gleam", 211). ?DOC(false). -spec has_column(glsql@internal@ast:table(), binary()) -> boolean(). has_column(Table, Name) -> gleam@list:any( erlang:element(4, Table), fun(C) -> string:lowercase(erlang:element(2, C)) =:= string:lowercase(Name) end ). -file("src/glsql/internal/validate.gleam", 205). ?DOC(false). -spec find_table(glsql@internal@ast:schema_ast(), binary()) -> {ok, glsql@internal@ast:table()} | {error, nil}. find_table(Schema, Name) -> gleam@list:find( erlang:element(2, Schema), fun(T) -> string:lowercase(erlang:element(2, T)) =:= string:lowercase(Name) end ). -file("src/glsql/internal/validate.gleam", 159). ?DOC(false). -spec foreign_keys(glsql@internal@ast:table(), glsql@internal@ast:schema_ast()) -> list(glsql@internal@error:error()). foreign_keys(Table, Schema) -> Inline = gleam@list:flat_map( erlang:element(4, Table), fun(C) -> gleam@list:filter_map(erlang:element(4, C), fun(K) -> case K of {references, T, Cols, Pos} -> {ok, {T, Cols, Pos}}; _ -> {error, nil} end end) end ), Table_level = gleam@list:filter_map( erlang:element(5, Table), fun(K@1) -> case K@1 of {table_foreign_key, _, T@1, Cols@1, Pos@1} -> {ok, {T@1, Cols@1, Pos@1}}; _ -> {error, nil} end end ), _pipe = lists:append(Inline, Table_level), gleam@list:flat_map( _pipe, fun(Fk) -> {Ref_table, Ref_cols, Pos@2} = Fk, case find_table(Schema, Ref_table) of {error, nil} -> [{validation_error, <<<<"`"/utf8, Ref_table/binary>>/binary, "` is not a table in this schema"/utf8>>, Pos@2, none}]; {ok, Target} -> gleam@list:filter_map( Ref_cols, fun(Col) -> case has_column(Target, Col) of true -> {error, nil}; false -> {ok, {validation_error, <<<<<<<<"`"/utf8, Ref_table/binary>>/binary, "` has no column `"/utf8>>/binary, Col/binary>>/binary, "`"/utf8>>, Pos@2, none}} end end ) end end ). -file("src/glsql/internal/validate.gleam", 136). ?DOC(false). -spec reserved_names(glsql@internal@ast:table(), glsql@internal@config:config()) -> list(glsql@internal@error:error()). reserved_names(Table, Cfg) -> gleam@list:filter_map( erlang:element(4, Table), fun(C) -> Key = <<<<(erlang:element(2, Table))/binary, "."/utf8>>/binary, (erlang:element(2, C))/binary>>, Renamed = gleam@dict:has_key(erlang:element(6, Cfg), Key), case gleam@list:contains( [<<"as"/utf8>>, <<"assert"/utf8>>, <<"auto"/utf8>>, <<"case"/utf8>>, <<"const"/utf8>>, <<"delegate"/utf8>>, <<"derive"/utf8>>, <<"echo"/utf8>>, <<"else"/utf8>>, <<"fn"/utf8>>, <<"if"/utf8>>, <<"implement"/utf8>>, <<"import"/utf8>>, <<"let"/utf8>>, <<"macro"/utf8>>, <<"opaque"/utf8>>, <<"panic"/utf8>>, <<"pub"/utf8>>, <<"test"/utf8>>, <<"todo"/utf8>>, <<"type"/utf8>>, <<"use"/utf8>>], string:lowercase(erlang:element(2, C)) ) andalso not Renamed of true -> {ok, {validation_error, <<<<<<<<<<<<<<"`"/utf8, (erlang:element(2, C))/binary>>/binary, "` is a reserved word in Gleam, so it cannot be a field name. "/utf8>>/binary, "Add a rename to glsql.toml:\n\n [rename]\n \""/utf8>>/binary, Key/binary>>/binary, "\" = \""/utf8>>/binary, (erlang:element(2, C))/binary>>/binary, "_\""/utf8>>, erlang:element(5, C), none}}; false -> {error, nil} end end ). -file("src/glsql/internal/validate.gleam", 120). ?DOC(false). -spec empty_names(glsql@internal@ast:table()) -> list(glsql@internal@error:error()). empty_names(Table) -> Table_errors = case gleam@string:trim(erlang:element(2, Table)) =:= <<""/utf8>> of true -> [{validation_error, <<"This table has an empty name"/utf8>>, erlang:element(6, Table), none}]; false -> [] end, Column_errors = gleam@list:filter_map( erlang:element(4, Table), fun(C) -> case gleam@string:trim(erlang:element(2, C)) =:= <<""/utf8>> of true -> {ok, {validation_error, <<"This column has an empty name"/utf8>>, erlang:element(5, C), none}}; false -> {error, nil} end end ), lists:append(Table_errors, Column_errors). -file("src/glsql/internal/validate.gleam", 89). ?DOC(false). -spec multiple_primary_keys(glsql@internal@ast:table()) -> list(glsql@internal@error:error()). multiple_primary_keys(Table) -> Inline = gleam@list:filter( erlang:element(4, Table), fun(C) -> gleam@list:any(erlang:element(4, C), fun(K) -> case K of {primary_key, _} -> true; _ -> false end end) end ), Table_level = gleam@list:filter( erlang:element(5, Table), fun(K@1) -> case K@1 of {table_primary_key, _, _} -> true; _ -> false end end ), case (erlang:length(Inline) + erlang:length(Table_level)) > 1 of true -> [{validation_error, <<<<"`"/utf8, (erlang:element(2, Table))/binary>>/binary, "` declares more than one primary key"/utf8>>, erlang:element(6, Table), none}]; false -> [] end. -file("src/glsql/internal/validate.gleam", 66). ?DOC(false). -spec duplicate_columns(glsql@internal@ast:table()) -> list(glsql@internal@error:error()). duplicate_columns(Table) -> {Errors@1, _} = gleam@list:fold( erlang:element(4, Table), {[], gleam@set:new()}, fun(State, C) -> {Errors, Seen} = State, Key = string:lowercase(erlang:element(2, C)), case gleam@set:contains(Seen, Key) of true -> {[{validation_error, <<<<<<<<"`"/utf8, (erlang:element(2, Table))/binary>>/binary, "` has two columns named `"/utf8>>/binary, (erlang:element(2, C))/binary>>/binary, "`"/utf8>>, erlang:element(5, C), none} | Errors], Seen}; false -> {Errors, gleam@set:insert(Seen, Key)} end end ), lists:reverse(Errors@1). -file("src/glsql/internal/validate.gleam", 29). ?DOC(false). -spec check_table( glsql@internal@ast:table(), glsql@internal@ast:schema_ast(), glsql@internal@config:config() ) -> list(glsql@internal@error:error()). check_table(Table, Schema, Cfg) -> lists:append( [duplicate_columns(Table), multiple_primary_keys(Table), empty_names(Table), reserved_names(Table, Cfg), foreign_keys(Table, Schema)] ). -file("src/glsql/internal/validate.gleam", 43). ?DOC(false). -spec duplicate_tables(list(glsql@internal@ast:table())) -> list(glsql@internal@error:error()). duplicate_tables(Tables) -> {Errors@1, _} = gleam@list:fold( Tables, {[], gleam@set:new()}, fun(State, T) -> {Errors, Seen} = State, Key = string:lowercase(erlang:element(2, T)), case gleam@set:contains(Seen, Key) of true -> {[{validation_error, <<<<"The table `"/utf8, (erlang:element(2, T))/binary>>/binary, "` is defined more than once"/utf8>>, erlang:element(6, T), none} | Errors], Seen}; false -> {Errors, gleam@set:insert(Seen, Key)} end end ), lists:reverse(Errors@1). -file("src/glsql/internal/validate.gleam", 16). ?DOC(false). -spec check(glsql@internal@ast:schema_ast(), glsql@internal@config:config()) -> {ok, nil} | {error, list(glsql@internal@error:error())}. check(Schema, Cfg) -> Errors = lists:append( [duplicate_tables(erlang:element(2, Schema)), gleam@list:flat_map( erlang:element(2, Schema), fun(T) -> check_table(T, Schema, Cfg) end )] ), case Errors of [] -> {ok, nil}; _ -> {error, Errors} end.