Current section

Files

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

src/sqlode@internal@generate.erl

-module(sqlode@internal@generate).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/sqlode/internal/generate.gleam").
-export([disambiguate_param_names/1, apply_column_renames/2, generate_config/1, run/1, error_to_string/1]).
-export_type([generate_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 generate_error() :: {config_error, sqlode@internal@config:config_error()} |
{schema_read_error, binary(), binary()} |
{schema_parse_error, binary()} |
{query_read_error, binary(), binary()} |
{query_parse_error, binary(), binary()} |
{query_analysis_error, binary()} |
{no_queries_generated,
binary(),
integer(),
integer(),
list(binary()),
list(binary())} |
{duplicate_query_name, binary(), list(binary())} |
{normalized_query_name_collision, binary(), list(binary()), list(binary())} |
{unsupported_annotation, binary(), binary(), binary()} |
{invalid_out_path, binary()} |
{write_error, sqlode@internal@writer:write_error()} |
vendor_runtime_not_found |
{unsupported_array_for_engine, binary(), binary()} |
{unsupported_slice_for_engine, binary(), binary()}.
-file("src/sqlode/internal/generate.gleam", 81).
?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/generate.gleam", 66).
?DOC(false).
-spec resolve_config_paths(sqlode@internal@model:config(), binary()) -> sqlode@internal@model:config().
resolve_config_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/generate.gleam", 306).
?DOC(false).
-spec read_runtime_source() -> {ok, binary()} | {error, nil}.
read_runtime_source() ->
Candidates = [<<"src/sqlode/runtime.gleam"/utf8>>,
<<"build/packages/sqlode/src/sqlode/runtime.gleam"/utf8>>,
<<"build/dev/erlang/sqlode/src/sqlode/runtime.gleam"/utf8>>],
gleam@list:find_map(Candidates, fun(Path) -> case simplifile:read(Path) of
{ok, Content} ->
{ok, Content};
{error, _} ->
{error, nil}
end end).
-file("src/sqlode/internal/generate.gleam", 345).
?DOC(false).
-spec collect_referenced_tables(list(sqlode@internal@model:analyzed_query())) -> list(binary()).
collect_referenced_tables(Queries) ->
_pipe = Queries,
_pipe@1 = gleam@list:flat_map(
_pipe,
fun(Query) ->
gleam@list:flat_map(
erlang:element(4, Query),
fun(Item) -> case Item of
{scalar_result, Col} ->
case erlang:element(5, Col) of
{some, Name} ->
[Name];
none ->
[]
end;
{embedded_result, Embed} ->
[erlang:element(3, Embed)]
end end
)
end
),
gleam@list:unique(_pipe@1).
-file("src/sqlode/internal/generate.gleam", 397).
?DOC(false).
-spec enum_name(sqlode@internal@model:scalar_type()) -> {ok, binary()} |
{error, nil}.
enum_name(Scalar_type) ->
case Scalar_type of
{enum_type, Name} ->
{ok, Name};
{array_type, Inner} ->
enum_name(Inner);
_ ->
{error, nil}
end.
-file("src/sqlode/internal/generate.gleam", 362).
?DOC(false).
-spec collect_referenced_enums(
list(sqlode@internal@model:table()),
list(sqlode@internal@model:analyzed_query())
) -> list(binary()).
collect_referenced_enums(Retained_tables, Queries) ->
From_tables = begin
_pipe = Retained_tables,
_pipe@1 = gleam@list:flat_map(_pipe, fun(T) -> erlang:element(3, T) end),
gleam@list:filter_map(
_pipe@1,
fun(C) -> enum_name(erlang:element(3, C)) end
)
end,
From_query_columns = begin
_pipe@2 = Queries,
gleam@list:flat_map(
_pipe@2,
fun(Q) ->
gleam@list:flat_map(
erlang:element(4, Q),
fun(Item) -> case Item of
{scalar_result, Col} ->
case enum_name(erlang:element(3, Col)) of
{ok, Name} ->
[Name];
{error, nil} ->
[]
end;
{embedded_result, Embed} ->
gleam@list:filter_map(
erlang:element(4, Embed),
fun(C@1) ->
enum_name(erlang:element(3, C@1))
end
)
end end
)
end
)
end,
From_query_params = begin
_pipe@3 = Queries,
gleam@list:flat_map(
_pipe@3,
fun(Q@1) ->
gleam@list:filter_map(
erlang:element(3, Q@1),
fun(P) -> enum_name(erlang:element(4, P)) end
)
end
)
end,
_pipe@4 = lists:append(
From_tables,
lists:append(From_query_columns, From_query_params)
),
gleam@list:unique(_pipe@4).
-file("src/sqlode/internal/generate.gleam", 327).
?DOC(false).
-spec prune_catalog_to_used(
sqlode@internal@model:catalog(),
list(sqlode@internal@model:analyzed_query())
) -> sqlode@internal@model:catalog().
prune_catalog_to_used(Catalog, Queries) ->
Referenced_tables = collect_referenced_tables(Queries),
Retained_tables = gleam@list:filter(
erlang:element(2, Catalog),
fun(T) ->
gleam@list:contains(Referenced_tables, erlang:element(2, T))
end
),
Referenced_enums = collect_referenced_enums(Retained_tables, Queries),
Retained_enums = gleam@list:filter(
erlang:element(3, Catalog),
fun(E) ->
gleam@list:contains(Referenced_enums, erlang:element(2, E))
end
),
{catalog, Retained_tables, Retained_enums}.
-file("src/sqlode/internal/generate.gleam", 214).
?DOC(false).
-spec base_output_files(
sqlode@internal@naming:naming_context(),
sqlode@internal@model:sql_block(),
list(sqlode@internal@model:analyzed_query()),
sqlode@internal@model:catalog(),
gleam@dict:dict(binary(), binary())
) -> {ok, list(sqlode@internal@writer:generated_file())} |
{error, generate_error()}.
base_output_files(Naming_ctx, Block, Analyzed, Catalog, Table_matches) ->
{sql_block, _, _, _, _, Gleam, _} = Block,
{gleam_output, Out, _, _, _, _, _, _, _, _} = Gleam,
Has_row_types = gleam@list:any(
Analyzed,
fun(Query) ->
sqlode@internal@model:is_result_command(
erlang:element(4, erlang:element(2, Query))
)
andalso not gleam@list:is_empty(erlang:element(4, Query))
end
),
Has_models = Has_row_types orelse not gleam@list:is_empty(
erlang:element(2, Catalog)
),
Files = [{generated_file,
Out,
<<"params.gleam"/utf8>>,
sqlode@internal@codegen@params:render(
Naming_ctx,
Analyzed,
erlang:element(4, Gleam),
sqlode@internal@codegen@common:out_to_module_path(Out),
sqlode@internal@codegen@common:runtime_import_path(Gleam)
)},
{generated_file,
Out,
<<"queries.gleam"/utf8>>,
sqlode@internal@codegen@queries:render(
Naming_ctx,
Block,
Analyzed,
Table_matches,
erlang:element(6, Gleam)
)}],
Files_with_models = case Has_models of
true ->
Effective_catalog = case erlang:element(7, Gleam) of
true ->
prune_catalog_to_used(Catalog, Analyzed);
false ->
Catalog
end,
lists:append(
Files,
[{generated_file,
Out,
<<"models.gleam"/utf8>>,
sqlode@internal@codegen@models:render(
Naming_ctx,
Effective_catalog,
Analyzed,
Table_matches,
erlang:element(4, Gleam),
erlang:element(6, Gleam)
)}]
);
false ->
Files
end,
case erlang:element(8, Gleam) of
true ->
case read_runtime_source() of
{ok, Source} ->
{ok,
lists:append(
Files_with_models,
[{generated_file,
Out,
<<"runtime.gleam"/utf8>>,
Source}]
)};
{error, _} ->
{error, vendor_runtime_not_found}
end;
false ->
{ok, Files_with_models}
end.
-file("src/sqlode/internal/generate.gleam", 405).
?DOC(false).
-spec load_catalog(list(binary()), sqlode@internal@model:engine(), boolean()) -> {ok,
sqlode@internal@model:catalog()} |
{error, generate_error()}.
load_catalog(Paths, Engine, Strict_views) ->
gleam@result:'try'(
sqlode@internal@sql_paths:expand(
Paths,
fun(Path, Detail) -> {schema_read_error, Path, Detail} end
),
fun(Expanded) ->
gleam@result:'try'(
begin
_pipe = Expanded,
gleam@list:try_map(
_pipe,
fun(Path@1) -> _pipe@1 = simplifile:read(Path@1),
_pipe@2 = gleam@result:map(
_pipe@1,
fun(Content) -> {Path@1, Content} end
),
gleam@result:map_error(
_pipe@2,
fun(Error) ->
{schema_read_error,
Path@1,
<<"Failed to read schema file: "/utf8,
(simplifile:describe_error(Error))/binary>>}
end
) end
)
end,
fun(Entries) ->
gleam@result:'try'(
begin
_pipe@3 = sqlode@internal@schema_parser:parse_files_with_engine(
Entries,
Engine
),
gleam@result:map_error(
_pipe@3,
fun(Error@1) ->
{schema_parse_error,
sqlode@internal@schema_parser:error_to_string(
Error@1
)}
end
)
end,
fun(_use0) ->
{Catalog, Warnings} = _use0,
case {Strict_views, Warnings} of
{true, [_ | _]} ->
Detail@1 = <<"strict_views is enabled but the schema produced resolution warnings:\n "/utf8,
(gleam@string:join(
gleam@list:map(
Warnings,
fun sqlode@internal@schema_parser:warning_to_string/1
),
<<"\n "/utf8>>
))/binary>>,
{error, {schema_parse_error, Detail@1}};
{_, _} ->
gleam@list:each(
Warnings,
fun(W) ->
gleam_stdlib:println_error(
sqlode@internal@schema_parser:warning_to_string(
W
)
)
end
),
{ok, Catalog}
end
end
)
end
)
end
).
-file("src/sqlode/internal/generate.gleam", 496).
?DOC(false).
-spec wrap_validation_error(sqlode@internal@query_validation:validation_error()) -> generate_error().
wrap_validation_error(Err) ->
case Err of
{duplicate_name, Name, Paths} ->
{duplicate_query_name, Name, Paths};
{normalized_name_collision, Function_name, Names, Paths@1} ->
{normalized_query_name_collision, Function_name, Names, Paths@1};
{unsupported_annotation, Query_name, Command, Detail} ->
{unsupported_annotation, Query_name, Command, Detail};
{unsupported_array_for_engine, Query_name@1, Engine} ->
{unsupported_array_for_engine, Query_name@1, Engine};
{unsupported_slice_for_engine, Query_name@2, Engine@1} ->
{unsupported_slice_for_engine, Query_name@2, Engine@1}
end.
-file("src/sqlode/internal/generate.gleam", 488).
?DOC(false).
-spec validate_no_duplicate_query_names(
list(sqlode@internal@query_ir:tokenized_query())
) -> {ok, list(sqlode@internal@query_ir:tokenized_query())} |
{error, generate_error()}.
validate_no_duplicate_query_names(Queries) ->
_pipe = sqlode@internal@query_validation:validate_no_duplicate_names(
Queries
),
_pipe@1 = gleam@result:map_error(_pipe, fun wrap_validation_error/1),
gleam@result:map(_pipe@1, fun(_) -> Queries end).
-file("src/sqlode/internal/generate.gleam", 454).
?DOC(false).
-spec load_queries(
sqlode@internal@naming:naming_context(),
sqlode@internal@model:sql_block()
) -> {ok, list(sqlode@internal@query_ir:tokenized_query())} |
{error, generate_error()}.
load_queries(Naming_ctx, Block) ->
{sql_block, _, Engine, _, Queries, _, _} = Block,
gleam@result:'try'(
sqlode@internal@sql_paths:expand(
Queries,
fun(Path, Detail) -> {query_read_error, Path, Detail} end
),
fun(Expanded) -> _pipe = Expanded,
_pipe@3 = gleam@list:try_map(
_pipe,
fun(Path@1) ->
gleam@result:'try'(
begin
_pipe@1 = simplifile:read(Path@1),
gleam@result:map_error(
_pipe@1,
fun(Error) ->
{query_read_error,
Path@1,
<<"Failed to read query file: "/utf8,
(simplifile:describe_error(Error))/binary>>}
end
)
end,
fun(Content) ->
_pipe@2 = sqlode@internal@query_parser:parse_file(
Path@1,
Engine,
Naming_ctx,
Content
),
gleam@result:map_error(
_pipe@2,
fun(Error@1) ->
{query_parse_error,
Path@1,
sqlode@internal@query_parser:error_to_string(
Error@1
)}
end
)
end
)
end
),
_pipe@4 = gleam@result:map(_pipe@3, fun lists:append/1),
gleam@result:'try'(_pipe@4, fun validate_no_duplicate_query_names/1) end
).
-file("src/sqlode/internal/generate.gleam", 560).
?DOC(false).
-spec find_column_override(
binary(),
binary(),
list(sqlode@internal@model:type_override())
) -> {ok, {binary(), gleam@option:option(sqlode@internal@model:codec_hooks())}} |
{error, nil}.
find_column_override(Table_name, Column_name, Overrides) ->
gleam@list:find_map(Overrides, fun(Ovr) -> case Ovr of
{column_override, Table, Column, Gleam_type, Codec} ->
case (string:lowercase(Table) =:= string:lowercase(
Table_name
))
andalso (string:lowercase(Column) =:= string:lowercase(
Column_name
)) of
true ->
{ok, {Gleam_type, Codec}};
false ->
{error, nil}
end;
{db_type_override, _, _, _, _} ->
{error, nil}
end end).
-file("src/sqlode/internal/generate.gleam", 580).
?DOC(false).
-spec find_db_type_override(
sqlode@internal@model:scalar_type(),
boolean(),
list(sqlode@internal@model:type_override())
) -> {ok, {binary(), gleam@option:option(sqlode@internal@model:codec_hooks())}} |
{error, nil}.
find_db_type_override(Scalar_type, Is_nullable, Overrides) ->
Type_name = sqlode@internal@type_mapping:scalar_type_to_db_name(Scalar_type),
gleam@list:find_map(Overrides, fun(Ovr) -> case Ovr of
{db_type_override, Db_type, Gleam_type, Nullable, Codec} ->
case string:lowercase(Db_type) =:= Type_name of
true ->
case Nullable of
none ->
{ok, {Gleam_type, Codec}};
{some, N} ->
case N =:= Is_nullable of
true ->
{ok, {Gleam_type, Codec}};
false ->
{error, nil}
end
end;
false ->
{error, nil}
end;
{column_override, _, _, _, _} ->
{error, nil}
end end).
-file("src/sqlode/internal/generate.gleam", 655).
?DOC(false).
-spec parse_module_qualified_type(binary()) -> {gleam@option:option(binary()),
binary()}.
parse_module_qualified_type(Gleam_type) ->
case gleam@string:split_once(Gleam_type, <<"."/utf8>>) of
{ok, {Module_path, Type_name}} ->
{{some, Module_path}, Type_name};
{error, _} ->
{none, Gleam_type}
end.
-file("src/sqlode/internal/generate.gleam", 607).
?DOC(false).
-spec gleam_type_to_scalar(
binary(),
gleam@option:option(sqlode@internal@model:codec_hooks()),
sqlode@internal@model:scalar_type()
) -> sqlode@internal@model:scalar_type().
gleam_type_to_scalar(Gleam_type, Codec, Underlying) ->
case string:lowercase(Gleam_type) of
<<"int"/utf8>> ->
int_type;
<<"float"/utf8>> ->
float_type;
<<"bool"/utf8>> ->
bool_type;
<<"string"/utf8>> ->
string_type;
<<"bitarray"/utf8>> ->
bytes_type;
_ ->
{Module, Type_name} = parse_module_qualified_type(Gleam_type),
case Codec of
{some, _} ->
nil;
none ->
Underlying_name = sqlode@internal@type_mapping:scalar_type_to_gleam_type(
Underlying,
string_mapping
),
gleam_stdlib:println_error(
<<<<<<<<<<<<<<<<<<<<<<"Warning: custom gleam_type \""/utf8,
Gleam_type/binary>>/binary,
"\" will use the encoder/decoder for the underlying \""/utf8>>/binary,
Underlying_name/binary>>/binary,
"\" type. Ensure \""/utf8>>/binary,
Type_name/binary>>/binary,
"\" is defined as a transparent type alias (e.g., pub type "/utf8>>/binary,
Type_name/binary>>/binary,
" = "/utf8>>/binary,
Underlying_name/binary>>/binary,
"), or provide explicit `encode` / `decode` codec hooks "/utf8>>/binary,
"in `overrides.types` to support opaque types."/utf8>>
),
nil
end,
{custom_type, Type_name, Module, Underlying, Codec}
end.
-file("src/sqlode/internal/generate.gleam", 511).
?DOC(false).
-spec apply_type_overrides(
sqlode@internal@model:catalog(),
list(sqlode@internal@model:type_override())
) -> sqlode@internal@model:catalog().
apply_type_overrides(Catalog, Overrides) ->
case Overrides of
[] ->
Catalog;
_ ->
Tables = gleam@list:map(
erlang:element(2, Catalog),
fun(Table) ->
Columns = gleam@list:map(
erlang:element(3, Table),
fun(Col) ->
case find_column_override(
erlang:element(2, Table),
erlang:element(2, Col),
Overrides
) of
{ok, {Gleam_type, Codec}} ->
{column,
erlang:element(2, Col),
gleam_type_to_scalar(
Gleam_type,
Codec,
erlang:element(3, Col)
),
erlang:element(4, Col)};
{error, _} ->
case find_db_type_override(
erlang:element(3, Col),
erlang:element(4, Col),
Overrides
) of
{ok, {Gleam_type@1, Codec@1}} ->
{column,
erlang:element(2, Col),
gleam_type_to_scalar(
Gleam_type@1,
Codec@1,
erlang:element(3, Col)
),
erlang:element(4, Col)};
{error, _} ->
Col
end
end
end
),
{table, erlang:element(2, Table), Columns}
end
),
{catalog, Tables, erlang:element(3, Catalog)}
end.
-file("src/sqlode/internal/generate.gleam", 118).
?DOC(false).
-spec load_and_prepare_catalog(sqlode@internal@model:sql_block()) -> {ok,
sqlode@internal@model:catalog()} |
{error, generate_error()}.
load_and_prepare_catalog(Block) ->
gleam@result:'try'(
load_catalog(
erlang:element(4, Block),
erlang:element(3, Block),
erlang:element(9, erlang:element(6, Block))
),
fun(Raw_catalog) ->
{ok,
apply_type_overrides(
Raw_catalog,
erlang:element(2, erlang:element(7, Block))
)}
end
).
-file("src/sqlode/internal/generate.gleam", 680).
?DOC(false).
-spec disambiguate_query_params(sqlode@internal@model:analyzed_query()) -> sqlode@internal@model:analyzed_query().
disambiguate_query_params(Query) ->
Counts = gleam@list:fold(
erlang:element(3, Query),
maps:new(),
fun(Acc, P) ->
gleam@dict:upsert(
Acc,
erlang:element(3, P),
fun(Prev) -> case Prev of
{some, N} ->
N + 1;
none ->
1
end end
)
end
),
{_, Renamed_reversed} = gleam@list:fold(
erlang:element(3, Query),
{maps:new(), []},
fun(Acc@1, P@1) ->
{Seen, Out} = Acc@1,
case gleam_stdlib:map_get(Counts, erlang:element(3, P@1)) of
{ok, N@1} when N@1 > 1 ->
Occurrence = case gleam_stdlib:map_get(
Seen,
erlang:element(3, P@1)
) of
{ok, Prev@1} ->
Prev@1 + 1;
{error, _} ->
1
end,
New_seen = gleam@dict:insert(
Seen,
erlang:element(3, P@1),
Occurrence
),
New_name = case Occurrence of
1 ->
erlang:element(3, P@1);
_ ->
<<<<(erlang:element(3, P@1))/binary, "_"/utf8>>/binary,
(erlang:integer_to_binary(Occurrence))/binary>>
end,
Renamed = {query_param,
erlang:element(2, P@1),
New_name,
erlang:element(4, P@1),
erlang:element(5, P@1),
erlang:element(6, P@1)},
{New_seen, [Renamed | Out]};
_ ->
{Seen, [P@1 | Out]}
end
end
),
{analyzed_query,
erlang:element(2, Query),
lists:reverse(Renamed_reversed),
erlang:element(4, Query)}.
-file("src/sqlode/internal/generate.gleam", 674).
?DOC(false).
-spec disambiguate_param_names(list(sqlode@internal@model:analyzed_query())) -> list(sqlode@internal@model:analyzed_query()).
disambiguate_param_names(Queries) ->
gleam@list:map(Queries, fun disambiguate_query_params/1).
-file("src/sqlode/internal/generate.gleam", 743).
?DOC(false).
-spec find_column_rename(
binary(),
gleam@option:option(binary()),
list(sqlode@internal@model:column_rename())
) -> {ok, binary()} | {error, nil}.
find_column_rename(Column_name, Source_table, Renames) ->
gleam@list:find_map(
Renames,
fun(R) ->
Column_matches = string:lowercase(erlang:element(3, R)) =:= string:lowercase(
Column_name
),
Table_matches = case Source_table of
{some, Table} ->
string:lowercase(erlang:element(2, R)) =:= string:lowercase(
Table
);
none ->
false
end,
case Column_matches andalso Table_matches of
true ->
{ok, erlang:element(4, R)};
false ->
{error, nil}
end
end
).
-file("src/sqlode/internal/generate.gleam", 716).
?DOC(false).
-spec apply_column_renames(
list(sqlode@internal@model:analyzed_query()),
list(sqlode@internal@model:column_rename())
) -> list(sqlode@internal@model:analyzed_query()).
apply_column_renames(Queries, Renames) ->
case Renames of
[] ->
Queries;
_ ->
gleam@list:map(
Queries,
fun(Query) ->
Result_columns = gleam@list:map(
erlang:element(4, Query),
fun(Item) -> case Item of
{scalar_result, Col} ->
case find_column_rename(
erlang:element(2, Col),
erlang:element(5, Col),
Renames
) of
{ok, New_name} ->
{scalar_result,
{result_column,
New_name,
erlang:element(3, Col),
erlang:element(4, Col),
erlang:element(5, Col)}};
{error, _} ->
Item
end;
{embedded_result, _} ->
Item
end end
),
{analyzed_query,
erlang:element(2, Query),
erlang:element(3, Query),
Result_columns}
end
)
end.
-file("src/sqlode/internal/generate.gleam", 762).
?DOC(false).
-spec validate_out_path(binary()) -> {ok, nil} | {error, generate_error()}.
validate_out_path(Out) ->
Module_path = sqlode@internal@codegen@common:out_to_module_path(Out),
case gleam_stdlib:string_starts_with(Module_path, <<"/"/utf8>>) orelse gleam_stdlib:string_starts_with(
Module_path,
<<"."/utf8>>
) of
true ->
{error, {invalid_out_path, Out}};
false ->
{ok, nil}
end.
-file("src/sqlode/internal/generate.gleam", 772).
?DOC(false).
-spec validate_unsupported_annotations(
list(sqlode@internal@model:analyzed_query())
) -> {ok, nil} | {error, generate_error()}.
validate_unsupported_annotations(Queries) ->
_pipe = sqlode@internal@query_validation:validate_unsupported_annotations(
Queries
),
gleam@result:map_error(_pipe, fun wrap_validation_error/1).
-file("src/sqlode/internal/generate.gleam", 779).
?DOC(false).
-spec validate_array_engine_support(
sqlode@internal@model:engine(),
list(sqlode@internal@model:analyzed_query())
) -> {ok, nil} | {error, generate_error()}.
validate_array_engine_support(Engine, Queries) ->
_pipe = sqlode@internal@query_validation:validate_array_engine_support(
Engine,
Queries
),
gleam@result:map_error(_pipe, fun wrap_validation_error/1).
-file("src/sqlode/internal/generate.gleam", 129).
?DOC(false).
-spec load_and_analyze_queries(
sqlode@internal@naming:naming_context(),
sqlode@internal@model:sql_block(),
sqlode@internal@model:catalog()
) -> {ok,
{list(sqlode@internal@query_ir:tokenized_query()),
list(sqlode@internal@model:analyzed_query())}} |
{error, generate_error()}.
load_and_analyze_queries(Naming_ctx, Block, Catalog) ->
gleam@result:'try'(
load_queries(Naming_ctx, Block),
fun(Queries) ->
gleam@result:'try'(
begin
_pipe = sqlode@internal@query_analyzer:analyze_queries(
erlang:element(3, Block),
Catalog,
Naming_ctx,
Queries
),
gleam@result:map_error(
_pipe,
fun(Error) ->
{query_analysis_error,
sqlode@internal@query_analyzer:analysis_error_to_string(
Error,
erlang:element(3, Block)
)}
end
)
end,
fun(Analyzed) ->
gleam@result:'try'(
validate_unsupported_annotations(Analyzed),
fun(_use0) ->
nil = _use0,
gleam@result:'try'(
validate_array_engine_support(
erlang:element(3, Block),
Analyzed
),
fun(_use0@1) ->
nil = _use0@1,
Analyzed@1 = apply_column_renames(
Analyzed,
erlang:element(
3,
erlang:element(7, Block)
)
),
Analyzed@2 = disambiguate_param_names(
Analyzed@1
),
{ok, {Queries, Analyzed@2}}
end
)
end
)
end
)
end
).
-file("src/sqlode/internal/generate.gleam", 787).
?DOC(false).
-spec validate_native_annotations(list(sqlode@internal@model:analyzed_query())) -> {ok,
nil} |
{error, generate_error()}.
validate_native_annotations(Queries) ->
_pipe = sqlode@internal@query_validation:validate_native_annotations(
Queries
),
gleam@result:map_error(_pipe, fun wrap_validation_error/1).
-file("src/sqlode/internal/generate.gleam", 794).
?DOC(false).
-spec adapter_filename(sqlode@internal@model:engine()) -> binary().
adapter_filename(Engine) ->
case Engine of
postgre_s_q_l ->
<<"pog_adapter.gleam"/utf8>>;
s_q_lite ->
<<"sqlight_adapter.gleam"/utf8>>;
my_s_q_l ->
<<"mysql_adapter.gleam"/utf8>>
end.
-file("src/sqlode/internal/generate.gleam", 866).
?DOC(false).
-spec guard_result(boolean(), fun(() -> {ok, RKI} | {error, nil})) -> {ok, RKI} |
{error, nil}.
guard_result(Condition, Next) ->
case Condition of
true ->
Next();
false ->
{error, nil}
end.
-file("src/sqlode/internal/generate.gleam", 873).
?DOC(false).
-spec find_table(sqlode@internal@model:catalog(), binary()) -> {ok,
sqlode@internal@model:table()} |
{error, nil}.
find_table(Catalog, Table_name) ->
gleam@list:find(
erlang:element(2, Catalog),
fun(T) ->
string:lowercase(erlang:element(2, T)) =:= string:lowercase(
Table_name
)
end
).
-file("src/sqlode/internal/generate.gleam", 882).
?DOC(false).
-spec columns_match(
list(sqlode@internal@model:result_item()),
list(sqlode@internal@model:column())
) -> boolean().
columns_match(Result_columns, Table_columns) ->
case erlang:length(Result_columns) =:= erlang:length(Table_columns) of
false ->
false;
true ->
_pipe = gleam@list:zip(Result_columns, Table_columns),
gleam@list:all(
_pipe,
fun(Pair) ->
{Rc, Tc} = Pair,
case Rc of
{scalar_result,
{result_column, Name, Scalar_type, Nullable, _}} ->
((string:lowercase(Name) =:= string:lowercase(
erlang:element(2, Tc)
))
andalso (Scalar_type =:= erlang:element(3, Tc)))
andalso (Nullable =:= erlang:element(4, Tc));
{embedded_result, _} ->
false
end
end
)
end.
-file("src/sqlode/internal/generate.gleam", 817).
?DOC(false).
-spec try_match_query_to_table(
sqlode@internal@naming:naming_context(),
sqlode@internal@model:catalog(),
sqlode@internal@model:analyzed_query(),
boolean()
) -> {ok, {binary(), binary()}} | {error, nil}.
try_match_query_to_table(Naming_ctx, Catalog, Query, Emit_exact_table_names) ->
guard_result(
sqlode@internal@model:is_result_command(
erlang:element(4, erlang:element(2, Query))
),
fun() ->
Has_embed = gleam@list:any(
erlang:element(4, Query),
fun(Col) -> case Col of
{embedded_result, _} ->
true;
{scalar_result, _} ->
false
end end
),
guard_result(
not Has_embed,
fun() -> gleam@result:'try'(case erlang:element(4, Query) of
[{scalar_result,
{result_column, _, _, _, {some, Name}}} |
_] ->
{ok, Name};
_ ->
{error, nil}
end, fun(Table_name) ->
All_same_table = gleam@list:all(
erlang:element(4, Query),
fun(Col@1) -> case Col@1 of
{scalar_result,
{result_column, _, _, _, Src}} ->
Src =:= {some, Table_name};
{embedded_result, _} ->
false
end end
),
guard_result(
All_same_table,
fun() ->
gleam@result:'try'(
find_table(Catalog, Table_name),
fun(Table) ->
guard_result(
columns_match(
erlang:element(4, Query),
erlang:element(3, Table)
),
fun() ->
{ok,
{erlang:element(
3,
erlang:element(
2,
Query
)
),
sqlode@internal@naming:table_type_name(
Naming_ctx,
Table_name,
Emit_exact_table_names
)}}
end
)
end
)
end
)
end) end
)
end
).
-file("src/sqlode/internal/generate.gleam", 804).
?DOC(false).
-spec compute_table_matches(
sqlode@internal@naming:naming_context(),
sqlode@internal@model:catalog(),
list(sqlode@internal@model:analyzed_query()),
boolean()
) -> gleam@dict:dict(binary(), binary()).
compute_table_matches(Naming_ctx, Catalog, Queries, Emit_exact_table_names) ->
_pipe = Queries,
_pipe@1 = gleam@list:filter_map(
_pipe,
fun(Query) ->
try_match_query_to_table(
Naming_ctx,
Catalog,
Query,
Emit_exact_table_names
)
end
),
maps:from_list(_pipe@1).
-file("src/sqlode/internal/generate.gleam", 154).
?DOC(false).
-spec render_output_files(
sqlode@internal@naming:naming_context(),
sqlode@internal@model:sql_block(),
sqlode@internal@model:catalog(),
list(sqlode@internal@query_ir:tokenized_query()),
list(sqlode@internal@model:analyzed_query())
) -> {ok, list(sqlode@internal@writer:generated_file())} |
{error, generate_error()}.
render_output_files(Naming_ctx, Block, Catalog, Queries, Analyzed) ->
{sql_block, _, _, _, _, Gleam, _} = Block,
{gleam_output, Out, _, _, _, _, _, _, _, _} = Gleam,
case Analyzed of
[] ->
{error,
{no_queries_generated,
Out,
erlang:length(Queries),
erlang:length(erlang:element(2, Catalog)),
erlang:element(5, Block),
erlang:element(4, Block)}};
_ ->
Table_matches = compute_table_matches(
Naming_ctx,
Catalog,
Analyzed,
erlang:element(6, Gleam)
),
gleam@result:'try'(
base_output_files(
Naming_ctx,
Block,
Analyzed,
Catalog,
Table_matches
),
fun(Base_files) -> case erlang:element(3, Gleam) of
raw ->
{ok, Base_files};
native ->
gleam@result:'try'(
validate_native_annotations(Analyzed),
fun(_use0) ->
nil = _use0,
{ok,
lists:append(
Base_files,
[{generated_file,
Out,
adapter_filename(
erlang:element(3, Block)
),
sqlode@internal@codegen@adapter:render(
Naming_ctx,
Block,
Analyzed,
Table_matches
)}]
)}
end
)
end end
)
end.
-file("src/sqlode/internal/generate.gleam", 104).
?DOC(false).
-spec generate_sql_block(
sqlode@internal@naming:naming_context(),
sqlode@internal@model:sql_block()
) -> {ok, list(sqlode@internal@writer:generated_file())} |
{error, generate_error()}.
generate_sql_block(Naming_ctx, Block) ->
gleam@result:'try'(
validate_out_path(erlang:element(2, erlang:element(6, Block))),
fun(_use0) ->
nil = _use0,
gleam@result:'try'(
load_and_prepare_catalog(Block),
fun(Catalog) ->
gleam@result:'try'(
load_and_analyze_queries(Naming_ctx, Block, Catalog),
fun(_use0@1) ->
{Queries, Analyzed} = _use0@1,
render_output_files(
Naming_ctx,
Block,
Catalog,
Queries,
Analyzed
)
end
)
end
)
end
).
-file("src/sqlode/internal/generate.gleam", 92).
?DOC(false).
-spec generate_config(sqlode@internal@model:config()) -> {ok, list(binary())} |
{error, generate_error()}.
generate_config(Cfg) ->
Naming_ctx = sqlode@internal@naming:new(),
gleam@result:'try'(
begin
_pipe = erlang:element(3, Cfg),
_pipe@1 = gleam@list:try_map(
_pipe,
fun(_capture) -> generate_sql_block(Naming_ctx, _capture) end
),
gleam@result:map(_pipe@1, fun lists:append/1)
end,
fun(Files) -> _pipe@2 = sqlode@internal@writer:write_all(Files),
gleam@result:map_error(
_pipe@2,
fun(Field@0) -> {write_error, Field@0} end
) end
).
-file("src/sqlode/internal/generate.gleam", 55).
?DOC(false).
-spec run(binary()) -> {ok, list(binary())} | {error, generate_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) ->
Config_dir = filepath:directory_name(Config_path),
Resolved = resolve_config_paths(Cfg, Config_dir),
generate_config(Resolved)
end
).
-file("src/sqlode/internal/generate.gleam", 908).
?DOC(false).
-spec error_to_string(generate_error()) -> binary().
error_to_string(Error) ->
case Error of
{config_error, Inner} ->
sqlode@internal@config:error_to_string(Inner);
{schema_read_error, Path, Detail} ->
<<<<Path/binary, ": "/utf8>>/binary, Detail/binary>>;
{schema_parse_error, Detail@1} ->
Detail@1;
{query_read_error, Path@1, Detail@2} ->
<<<<Path@1/binary, ": "/utf8>>/binary, Detail@2/binary>>;
{query_parse_error, _, Detail@3} ->
Detail@3;
{query_analysis_error, Detail@4} ->
Detail@4;
{no_queries_generated,
Output,
Parsed_query_count,
Schema_table_count,
Query_paths,
Schema_paths} ->
<<<<<<<<<<<<<<<<<<<<<<<<"No queries were generated for output directory: "/utf8,
Output/binary>>/binary,
"\n Parsed queries: "/utf8>>/binary,
(erlang:integer_to_binary(
Parsed_query_count
))/binary>>/binary,
" (from "/utf8>>/binary,
(gleam@string:join(
Query_paths,
<<", "/utf8>>
))/binary>>/binary,
")"/utf8>>/binary,
"\n Schema tables: "/utf8>>/binary,
(erlang:integer_to_binary(Schema_table_count))/binary>>/binary,
" (from "/utf8>>/binary,
(gleam@string:join(Schema_paths, <<", "/utf8>>))/binary>>/binary,
")"/utf8>>/binary,
(case Parsed_query_count of
0 ->
<<"\n Hint: No queries were found. Ensure your query files contain annotations like '-- name: QueryName :one'"/utf8>>;
_ ->
case Schema_table_count of
0 ->
<<"\n Hint: No tables found in schema. Ensure your schema files contain CREATE TABLE statements"/utf8>>;
_ ->
<<"\n Hint: Queries were parsed but none produced output. Check that your schema defines the tables referenced in your queries"/utf8>>
end
end)/binary>>;
{duplicate_query_name, Name, Paths} ->
sqlode@internal@query_validation:error_to_string(
{duplicate_name, Name, Paths}
);
{normalized_query_name_collision, Function_name, Names, Paths@1} ->
sqlode@internal@query_validation:error_to_string(
{normalized_name_collision, Function_name, Names, Paths@1}
);
{unsupported_annotation, Query_name, Command, Detail@5} ->
sqlode@internal@query_validation:error_to_string(
{unsupported_annotation, Query_name, Command, Detail@5}
);
{invalid_out_path, Path@2} ->
<<<<"Invalid output path \""/utf8, Path@2/binary>>/binary,
"\": produces an invalid Gleam module path. Use a relative path under src/ (e.g., \"src/db\")"/utf8>>;
{write_error, Inner@1} ->
sqlode@internal@writer:error_to_string(Inner@1);
vendor_runtime_not_found ->
<<"vendor_runtime is enabled but the sqlode/runtime source file could not be found. Tried: src/sqlode/runtime.gleam, build/packages/sqlode/src/sqlode/runtime.gleam, build/dev/erlang/sqlode/src/sqlode/runtime.gleam"/utf8>>;
{unsupported_array_for_engine, Query_name@1, Engine} ->
sqlode@internal@query_validation:error_to_string(
{unsupported_array_for_engine, Query_name@1, Engine}
);
{unsupported_slice_for_engine, Query_name@2, Engine@1} ->
sqlode@internal@query_validation:error_to_string(
{unsupported_slice_for_engine, Query_name@2, Engine@1}
)
end.