Current section

Files

Jump to
sqlode src sqlode@internal@verify.erl
Raw

src/sqlode@internal@verify.erl

-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", 166).
?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,
<<<<Path/binary, ": "/utf8>>/binary,
(sqlode@internal@query_parser:error_to_string(Err))/binary>>}
end
end
).
-file("src/sqlode/internal/verify.gleam", 181).
?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) ->
<<<<Path/binary, ": "/utf8>>/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,
<<<<Path@1/binary, ": "/utf8>>/binary,
(simplifile:describe_error(Reason))/binary>>}
end end
) end
).
-file("src/sqlode/internal/verify.gleam", 113).
?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", 131).
?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", 198).
?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", 80).
?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", 73).
?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", 248).
?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", 233).
?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", 60).
?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", 269).
?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", 259).
?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", 273).
?DOC(false).
-spec error_to_string(verify_error()) -> binary().
error_to_string(Error) ->
{config_error, Inner} = Error,
sqlode@internal@config:error_to_string(Inner).