-module(sqlode@internal@verify). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/sqlode/internal/verify.gleam"). -export([verify_config/1, run/1, report_to_string/1, error_to_string/1]). -export_type([report/0, finding/0, verify_error/0]). -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). -type report() :: {report, list(finding())}. -type finding() :: {finding, binary(), binary()}. -type verify_error() :: {config_error, sqlode@internal@config:config_error()}. -file("src/sqlode/internal/verify.gleam", 159). ?DOC(false). -spec parse_all_queries( list({binary(), binary()}), sqlode@internal@model:engine(), sqlode@internal@naming:naming_context() ) -> {ok, list(sqlode@internal@query_ir:tokenized_query())} | {error, binary()}. parse_all_queries(Entries, Engine, Naming_ctx) -> _pipe = Entries, gleam@list:try_fold( _pipe, [], fun(Acc, Entry) -> {Path, Content} = Entry, case sqlode@internal@query_parser:parse_file( Path, Engine, Naming_ctx, Content ) of {ok, Qs} -> {ok, lists:append(Acc, Qs)}; {error, Err} -> {error, <<<>/binary, (sqlode@internal@query_parser:error_to_string(Err))/binary>>} end end ). -file("src/sqlode/internal/verify.gleam", 174). ?DOC(false). -spec read_files(list(binary())) -> {ok, list({binary(), binary()})} | {error, binary()}. read_files(Paths) -> gleam@result:'try'( sqlode@internal@sql_paths:expand( Paths, fun(Path, Detail) -> <<<>/binary, Detail/binary>> end ), fun(Expanded) -> _pipe = Expanded, gleam@list:try_map( _pipe, fun(Path@1) -> case simplifile:read(Path@1) of {ok, Content} -> {ok, {Path@1, Content}}; {error, Reason} -> {error, <<<>/binary, (simplifile:describe_error(Reason))/binary>>} end end ) end ). -file("src/sqlode/internal/verify.gleam", 106). ?DOC(false). -spec load_catalog(sqlode@internal@model:sql_block()) -> {ok, sqlode@internal@model:catalog()} | {error, binary()}. load_catalog(Block) -> gleam@result:'try'( read_files(erlang:element(4, Block)), fun(Entries) -> case sqlode@internal@schema_parser:parse_files_with_engine( Entries, erlang:element(3, Block) ) of {ok, {Catalog, Warnings}} -> case {erlang:element(9, erlang:element(6, Block)), Warnings} of {true, [_ | _]} -> Formatted = begin _pipe = Warnings, _pipe@1 = gleam@list:map( _pipe, fun sqlode@internal@schema_parser:warning_to_string/1 ), gleam@string:join(_pipe@1, <<"\n "/utf8>>) end, {error, <<"strict_views policy rejects the schema:\n "/utf8, Formatted/binary>>}; {_, _} -> {ok, Catalog} end; {error, Error} -> {error, sqlode@internal@schema_parser:error_to_string(Error)} end end ). -file("src/sqlode/internal/verify.gleam", 124). ?DOC(false). -spec load_and_analyze( sqlode@internal@naming:naming_context(), sqlode@internal@model:sql_block(), sqlode@internal@model:catalog() ) -> {ok, list(sqlode@internal@model:analyzed_query())} | {error, binary()}. load_and_analyze(Naming_ctx, Block, Catalog) -> gleam@result:'try'( read_files(erlang:element(5, Block)), fun(Entries) -> gleam@result:'try'( parse_all_queries(Entries, erlang:element(3, Block), Naming_ctx), fun(Queries) -> gleam@result:'try'( begin _pipe = sqlode@internal@query_validation:validate_no_duplicate_names( Queries ), gleam@result:map_error( _pipe, fun sqlode@internal@query_validation:error_to_string/1 ) end, fun(_use0) -> nil = _use0, gleam@result:'try'( begin _pipe@1 = sqlode@internal@query_analyzer:analyze_queries( erlang:element(3, Block), Catalog, Naming_ctx, Queries ), gleam@result:map_error( _pipe@1, fun(_capture) -> sqlode@internal@query_analyzer:analysis_error_to_string( _capture, erlang:element(3, Block) ) end ) end, fun(Analyzed) -> gleam@result:'try'( begin _pipe@2 = sqlode@internal@query_validation:validate_unsupported_annotations( Analyzed ), gleam@result:map_error( _pipe@2, fun sqlode@internal@query_validation:error_to_string/1 ) end, fun(_use0@1) -> nil = _use0@1, gleam@result:'try'( begin _pipe@3 = sqlode@internal@query_validation:validate_array_engine_support( erlang:element(3, Block), Analyzed ), gleam@result:map_error( _pipe@3, fun sqlode@internal@query_validation:error_to_string/1 ) end, fun(_use0@2) -> nil = _use0@2, gleam@result:'try'( case erlang:element( 3, erlang:element( 6, Block ) ) of native -> _pipe@4 = sqlode@internal@query_validation:validate_native_annotations( Analyzed ), gleam@result:map_error( _pipe@4, fun sqlode@internal@query_validation:error_to_string/1 ); raw -> {ok, nil} end, fun(_use0@3) -> nil = _use0@3, Analyzed@1 = sqlode@internal@generate:apply_column_renames( Analyzed, erlang:element( 3, erlang:element( 7, Block ) ) ), Analyzed@2 = sqlode@internal@generate:disambiguate_param_names( Analyzed@1 ), {ok, Analyzed@2} end ) end ) end ) end ) end ) end ) end ). -file("src/sqlode/internal/verify.gleam", 191). ?DOC(false). -spec enforce_query_parameter_limit( binary(), list(sqlode@internal@model:analyzed_query()), gleam@option:option(integer()) ) -> list(finding()). enforce_query_parameter_limit(Block_out, Queries, Limit) -> case Limit of none -> []; {some, N} -> case N =< 0 of true -> []; false -> gleam@list:filter_map( Queries, fun(Q) -> Count = erlang:length(erlang:element(3, Q)), case Count > N of true -> {ok, {finding, Block_out, <<<<<<<<<<"query \""/utf8, (erlang:element( 2, erlang:element( 2, Q ) ))/binary>>/binary, "\" has "/utf8>>/binary, (erlang:integer_to_binary( Count ))/binary>>/binary, " inferred parameter(s), exceeds query_parameter_limit "/utf8>>/binary, (erlang:integer_to_binary(N))/binary>>}}; false -> {error, nil} end end ) end end. -file("src/sqlode/internal/verify.gleam", 73). ?DOC(false). -spec verify_block( sqlode@internal@naming:naming_context(), sqlode@internal@model:sql_block() ) -> list(finding()). verify_block(Naming_ctx, Block) -> Out = erlang:element(2, erlang:element(6, Block)), case load_catalog(Block) of {error, Detail} -> [{finding, Out, Detail}]; {ok, Catalog} -> case load_and_analyze(Naming_ctx, Block, Catalog) of {error, Detail@1} -> [{finding, Out, Detail@1}]; {ok, Analyzed} -> case Analyzed of [] -> [{finding, Out, <<"no queries were generated — query files are empty or contain no valid annotations"/utf8>>}]; _ -> enforce_query_parameter_limit( Out, Analyzed, erlang:element(10, erlang:element(6, Block)) ) end end end. -file("src/sqlode/internal/verify.gleam", 66). ?DOC(false). -spec verify_config(sqlode@internal@model:config()) -> report(). verify_config(Cfg) -> Naming_ctx = sqlode@internal@naming:new(), Findings = gleam@list:flat_map( erlang:element(3, Cfg), fun(Block) -> verify_block(Naming_ctx, Block) end ), {report, Findings}. -file("src/sqlode/internal/verify.gleam", 241). ?DOC(false). -spec resolve_path(binary(), binary()) -> binary(). resolve_path(Base_dir, Path) -> case filepath:is_absolute(Path) of true -> Path; false -> case filepath:expand(filepath:join(Base_dir, Path)) of {ok, Expanded} -> Expanded; {error, _} -> filepath:join(Base_dir, Path) end end. -file("src/sqlode/internal/verify.gleam", 226). ?DOC(false). -spec resolve_paths(sqlode@internal@model:config(), binary()) -> sqlode@internal@model:config(). resolve_paths(Cfg, Base_dir) -> Sql = gleam@list:map( erlang:element(3, Cfg), fun(Block) -> Schema = gleam@list:map( erlang:element(4, Block), fun(_capture) -> resolve_path(Base_dir, _capture) end ), Queries = gleam@list:map( erlang:element(5, Block), fun(_capture@1) -> resolve_path(Base_dir, _capture@1) end ), Gleam = begin _record = erlang:element(6, Block), {gleam_output, resolve_path( Base_dir, erlang:element(2, erlang:element(6, Block)) ), erlang:element(3, _record), erlang:element(4, _record), erlang:element(5, _record), erlang:element(6, _record), erlang:element(7, _record), erlang:element(8, _record), erlang:element(9, _record), erlang:element(10, _record)} end, {sql_block, erlang:element(2, Block), erlang:element(3, Block), Schema, Queries, Gleam, erlang:element(7, Block)} end ), {config, erlang:element(2, Cfg), Sql}. -file("src/sqlode/internal/verify.gleam", 53). ?DOC(false). -spec run(binary()) -> {ok, report()} | {error, verify_error()}. run(Config_path) -> gleam@result:'try'( begin _pipe = sqlode@internal@config:load(Config_path), gleam@result:map_error( _pipe, fun(Field@0) -> {config_error, Field@0} end ) end, fun(Cfg) -> Base_dir = filepath:directory_name(Config_path), Resolved = resolve_paths(Cfg, Base_dir), {ok, verify_config(Resolved)} end ). -file("src/sqlode/internal/verify.gleam", 262). ?DOC(false). -spec format_finding(finding()) -> binary(). format_finding(Finding) -> <<<<<<"["/utf8, (erlang:element(2, Finding))/binary>>/binary, "] "/utf8>>/binary, (erlang:element(3, Finding))/binary>>. -file("src/sqlode/internal/verify.gleam", 252). ?DOC(false). -spec report_to_string(report()) -> binary(). report_to_string(Report) -> case erlang:element(2, Report) of [] -> <<"All checks passed."/utf8>>; Findings -> _pipe = Findings, _pipe@1 = gleam@list:map(_pipe, fun format_finding/1), gleam@string:join(_pipe@1, <<"\n"/utf8>>) end. -file("src/sqlode/internal/verify.gleam", 266). ?DOC(false). -spec error_to_string(verify_error()) -> binary(). error_to_string(Error) -> {config_error, Inner} = Error, sqlode@internal@config:error_to_string(Inner).