Current section
Files
Jump to
Current section
Files
src/parrot.erl
-module(parrot).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/parrot.gleam").
-export([err_to_string/1, engine_to_sqlc_string/1, fetch_schema_mysql/1, fetch_schema_postgresql/1, fetch_schema_sqlite/1, gen_sqlc_yaml/2, walk/1, cmd_gen/2, datetime_decoder/0, main/0]).
-export_type([parrot_error/0, engine/0, param/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.
-type parrot_error() :: {unknown_engine, binary()} |
{sqlit_d_b_not_found, binary()} |
{my_sql_d_b_not_found, binary()} |
{postgre_sql_d_b_not_found, binary()} |
no_queries_found |
mysqldump_error |
pgdump_error.
-type engine() :: s_qlite | my_s_q_l | postgre_s_q_l.
-type param() :: {param_int, integer()} |
{param_string, binary()} |
{param_float, float()} |
{param_bool, boolean()} |
{param_bit_array, bitstring()} |
{param_timestamp, gleam@time@timestamp:timestamp()} |
{param_dynamic, gleam@dynamic:dynamic_()}.
-file("src/parrot.gleam", 42).
-spec err_to_string(parrot_error()) -> binary().
err_to_string(Error) ->
case Error of
{my_sql_d_b_not_found, _} ->
<<"mysql db not found"/utf8>>;
{postgre_sql_d_b_not_found, _} ->
<<"postgresql db not found"/utf8>>;
{sqlit_d_b_not_found, _} ->
<<"sqlite db not found"/utf8>>;
mysqldump_error ->
<<"there was an error with mysqldump"/utf8>>;
pgdump_error ->
<<"there was an error pg_dump"/utf8>>;
no_queries_found ->
<<"no queries were found to codegen"/utf8>>;
{unknown_engine, Engine} ->
<<"unknown engine: "/utf8, Engine/binary>>
end.
-file("src/parrot.gleam", 60).
-spec engine_decoder() -> gleam@dynamic@decode:decoder(engine()).
engine_decoder() ->
gleam@dynamic@decode:then(
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Variant) -> case Variant of
<<"sqlite"/utf8>> ->
gleam@dynamic@decode:success(s_qlite);
<<"mysql"/utf8>> ->
gleam@dynamic@decode:success(my_s_q_l);
<<"psql"/utf8>> ->
gleam@dynamic@decode:success(postgre_s_q_l);
_ ->
gleam@dynamic@decode:failure(s_qlite, <<"Driver"/utf8>>)
end end
).
-file("src/parrot.gleam", 70).
-spec engine_to_sqlc_string(engine()) -> binary().
engine_to_sqlc_string(Engine) ->
case Engine of
my_s_q_l ->
<<"mysql"/utf8>>;
postgre_s_q_l ->
<<"postgresql"/utf8>>;
s_qlite ->
<<"sqlite"/utf8>>
end.
-file("src/parrot.gleam", 182).
-spec fetch_schema_mysql(binary()) -> {ok, binary()} | {error, parrot_error()}.
fetch_schema_mysql(Db) ->
Conn@1 = case gleam_stdlib:uri_parse(Db) of
{ok, Conn} -> Conn;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"parrot"/utf8>>,
function => <<"fetch_schema_mysql"/utf8>>,
line => 183,
value => _assert_fail,
start => 4334,
'end' => 4369,
pattern_start => 4345,
pattern_end => 4353})
end,
Creds = case erlang:element(3, Conn@1) of
none ->
none;
{some, Userinfo} ->
case gleam@string:split(Userinfo, <<":"/utf8>>) of
[User] ->
{some, {User, <<""/utf8>>}};
[User@1, Pass] ->
{some, {User@1, Pass}};
_ ->
none
end
end,
gleam@result:'try'(
gleam@option:to_result(Creds, {my_sql_d_b_not_found, <<""/utf8>>}),
fun(_use0) ->
{User@2, Pass@1} = _use0,
Port@1 = case erlang:element(5, Conn@1) of
none ->
<<"3306"/utf8>>;
{some, Port} ->
erlang:integer_to_binary(Port)
end,
Host@1 = case erlang:element(4, Conn@1) of
none ->
<<"localhost"/utf8>>;
{some, Host} ->
Host
end,
Db@1 = gleam@string:replace(
erlang:element(6, Conn@1),
<<"/"/utf8>>,
<<""/utf8>>
),
gleam@result:'try'(
begin
_pipe = shellout:command(
<<"mysqldump"/utf8>>,
[<<"--no-data"/utf8>>,
<<"-u"/utf8>>,
User@2,
<<"-p"/utf8, Pass@1/binary>>,
<<"-h"/utf8>>,
Host@1,
<<"-P"/utf8>>,
Port@1,
Db@1],
<<"."/utf8>>,
[]
),
gleam@result:replace_error(_pipe, mysqldump_error)
end,
fun(Out) -> _pipe@1 = Out,
_pipe@2 = gleam@string:split(_pipe@1, <<"\n"/utf8>>),
_pipe@3 = gleam@list:filter(
_pipe@2,
fun(Line) ->
gleam_stdlib:contains_string(
Line,
<<"mysqldump:"/utf8>>
)
=:= false
end
),
_pipe@4 = gleam@string:join(_pipe@3, <<"\n"/utf8>>),
{ok, _pipe@4} end
)
end
).
-file("src/parrot.gleam", 225).
-spec fetch_schema_postgresql(binary()) -> {ok, binary()} |
{error, parrot_error()}.
fetch_schema_postgresql(Db) ->
_pipe = shellout:command(
<<"pg_dump"/utf8>>,
[<<"--no-privileges"/utf8>>,
<<"--no-acl"/utf8>>,
<<"--no-owner"/utf8>>,
<<"--schema-only"/utf8>>,
<<"--no-comments"/utf8>>,
<<"--encoding=utf8"/utf8>>,
Db],
<<"."/utf8>>,
[]
),
gleam@result:replace_error(_pipe, pgdump_error).
-file("src/parrot.gleam", 243).
-spec fetch_schema_sqlite(binary()) -> {ok, list(binary())} |
{error, parrot_error()}.
fetch_schema_sqlite(Db) ->
sqlight:with_connection(
Db,
fun(Conn) ->
Schema_decoder = begin
gleam@dynamic@decode:field(
0,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Sql) -> gleam@dynamic@decode:success(Sql) end
)
end,
Sql@1 = <<"
SELECT sql
FROM sqlite_master
WHERE type IN ('table', 'view', 'index', 'trigger')
AND name NOT LIKE 'sqlite_%'
AND sql IS NOT NULL
ORDER BY
CASE type
WHEN 'table' THEN 1
WHEN 'view' THEN 2
WHEN 'index' THEN 3
WHEN 'trigger' THEN 4
ELSE 5
END,
name;
"/utf8>>,
gleam@result:'try'(
begin
_pipe = sqlight:'query'(Sql@1, Conn, [], Schema_decoder),
gleam@result:replace_error(
_pipe,
{sqlit_d_b_not_found, <<""/utf8>>}
)
end,
fun(Result) -> {ok, Result} end
)
end
).
-file("src/parrot.gleam", 276).
-spec gen_sqlc_yaml(engine(), list(binary())) -> binary().
gen_sqlc_yaml(Engine, Queries) ->
Result = <<<<<<<<"
version: \"2\"
plugins:
- name: jsonb
wasm:
url: https://github.com/daniellionel01/sqlc-gen-json/releases/download/v1.0.0/sqlc-gen-json.wasm
sha256: ffbd8cfaecc971d8cdf145591eac28731ffb50b7348131868ce66cc0e3192b7e
sql:
- schema: schema.sql
queries: ["/utf8,
(gleam@string:join(Queries, <<", "/utf8>>))/binary>>/binary,
"]
engine: "/utf8>>/binary,
(engine_to_sqlc_string(Engine))/binary>>/binary,
"
codegen:
- out: .
plugin: jsonb
options:
indent: \" \"
filename: queries.json
"/utf8>>,
gleam@string:trim(Result).
-file("src/parrot.gleam", 303).
?DOC(
" Finds all `from/**/sql` directories and lists the full paths of the `*.sql`\n"
" files inside each one.\n"
" https://github.com/giacomocavalieri/squirrel/blob/main/src/squirrel.gleam\n"
).
-spec walk(binary()) -> gleam@dict:dict(binary(), list(binary())).
walk(From) ->
case filepath:base_name(From) of
<<"sql"/utf8>> ->
Files@1 = case simplifile_erl:read_directory(From) of
{ok, Files} -> Files;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"parrot"/utf8>>,
function => <<"walk"/utf8>>,
line => 306,
value => _assert_fail,
start => 7347,
'end' => 7401,
pattern_start => 7358,
pattern_end => 7367})
end,
Files@2 = begin
gleam@list:filter_map(
Files@1,
fun(File) ->
gleam@result:'try'(
filepath:extension(File),
fun(Extension) ->
gleam@bool:guard(
Extension /= <<"sql"/utf8>>,
{error, nil},
fun() ->
File_name = filepath:join(From, File),
case simplifile_erl:is_file(File_name) of
{ok, true} ->
{ok, File_name};
{ok, false} ->
{error, nil};
{error, _} ->
{error, nil}
end
end
)
end
)
end
)
end,
maps:from_list([{From, Files@2}]);
_ ->
Files@4 = case simplifile_erl:read_directory(From) of
{ok, Files@3} -> Files@3;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"parrot"/utf8>>,
function => <<"walk"/utf8>>,
line => 321,
value => _assert_fail@1,
start => 7856,
'end' => 7910,
pattern_start => 7867,
pattern_end => 7876})
end,
Directories = begin
gleam@list:filter_map(
Files@4,
fun(File@1) ->
File_name@1 = filepath:join(From, File@1),
case simplifile_erl:is_directory(File_name@1) of
{ok, true} ->
{ok, File_name@1};
{ok, false} ->
{error, nil};
{error, _} ->
{error, nil}
end
end
)
end,
_pipe = gleam@list:map(Directories, fun walk/1),
gleam@list:fold(_pipe, maps:new(), fun maps:merge/2)
end.
-file("src/parrot.gleam", 113).
-spec cmd_gen(engine(), binary()) -> {ok, nil} | {error, parrot_error()}.
cmd_gen(Engine, Db) ->
Files = walk(parrot@internal@project:src()),
Queries = begin
_pipe = Files,
_pipe@1 = maps:to_list(_pipe),
_pipe@2 = gleam@list:map(
_pipe@1,
fun(File) ->
{_, Files@1} = File,
gleam@list:map(
Files@1,
fun(File@1) ->
File@2 = case File@1 of
<<"./"/utf8, Rest/binary>> ->
Rest;
X ->
X
end,
filepath:join(<<"../.."/utf8>>, File@2)
end
)
end
),
gleam@list:flatten(_pipe@2)
end,
Sqlc_dir = filepath:join(
parrot@internal@project:root(),
<<"build/.parrot/"/utf8>>
),
Schema_file = filepath:join(Sqlc_dir, <<"schema.sql"/utf8>>),
Sqlc_file = filepath:join(Sqlc_dir, <<"sqlc.yaml"/utf8>>),
Queries_file = filepath:join(Sqlc_dir, <<"queries.json"/utf8>>),
_ = simplifile:create_directory_all(Sqlc_dir),
Sqlc_yaml = gen_sqlc_yaml(Engine, Queries),
_ = simplifile:write(Sqlc_file, Sqlc_yaml),
gleam@result:'try'(case Engine of
my_s_q_l ->
gleam@result:'try'(
fetch_schema_mysql(Db),
fun(Schema) -> {ok, Schema} end
);
postgre_s_q_l ->
gleam@result:'try'(
fetch_schema_postgresql(Db),
fun(Schema@1) -> {ok, Schema@1} end
);
s_qlite ->
gleam@result:'try'(
fetch_schema_sqlite(Db),
fun(Schema@2) ->
Sql@1 = begin
_pipe@3 = Schema@2,
_pipe@4 = gleam@list:map(
_pipe@3,
fun gleam@string:trim/1
),
_pipe@5 = gleam@list:map(
_pipe@4,
fun(Sql) -> <<Sql/binary, ";"/utf8>> end
),
gleam@string:join(_pipe@5, <<"\n"/utf8>>)
end,
{ok, Sql@1}
end
)
end, fun(Schema_sql) ->
_ = simplifile:write(Schema_file, Schema_sql),
case shellout:command(
<<"sqlc"/utf8>>,
[<<"generate"/utf8>>],
Sqlc_dir,
[]
) of
{error, {_, Err}} ->
gleam_stdlib:println(
parrot@internal@colored:red(
<<"could not call `sqlc generate`:\n"/utf8,
Err/binary>>
)
),
erlang:error(#{gleam_error => panic,
message => <<"`panic` expression evaluated."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"parrot"/utf8>>,
function => <<"cmd_gen"/utf8>>,
line => 166});
{ok, _} ->
nil
end,
Project_name = parrot@internal@project:project_name(),
Config = {config,
Queries_file,
<<Project_name/binary, "/sql.gleam"/utf8>>},
_ = parrot@codegen:codegen_from_config(Config),
{ok, nil}
end).
-file("src/parrot.gleam", 351).
-spec datetime_string_decoder() -> gleam@dynamic@decode:decoder(gleam@time@timestamp:timestamp()).
datetime_string_decoder() ->
_pipe = {decoder, fun gleam@dynamic@decode:decode_string/1},
gleam@dynamic@decode:then(
_pipe,
fun(Datetime_str) ->
case gleam@time@timestamp:parse_rfc3339(Datetime_str) of
{ok, Ts} ->
gleam@dynamic@decode:success(Ts);
{error, _} ->
gleam@dynamic@decode:failure(
gleam@time@timestamp:from_unix_seconds(0),
<<"Invalid datetime format"/utf8>>
)
end
end
).
-file("src/parrot.gleam", 373).
-spec date_decoder() -> gleam@dynamic@decode:decoder(gleam@time@calendar:date()).
date_decoder() ->
gleam@dynamic@decode:field(
0,
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Year) ->
gleam@dynamic@decode:field(
1,
begin
_pipe = {decoder, fun gleam@dynamic@decode:decode_int/1},
gleam@dynamic@decode:then(
_pipe,
fun(Month) ->
case gleam@time@calendar:month_from_int(Month) of
{error, _} ->
gleam@dynamic@decode:failure(
january,
<<"Month"/utf8>>
);
{ok, Month@1} ->
gleam@dynamic@decode:success(Month@1)
end
end
)
end,
fun(Month@2) ->
gleam@dynamic@decode:field(
2,
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Day) ->
gleam@dynamic@decode:success(
{date, Year, Month@2, Day}
)
end
)
end
)
end
).
-file("src/parrot.gleam", 399).
-spec seconds_decoder() -> gleam@dynamic@decode:decoder({integer(), integer()}).
seconds_decoder() ->
Int = begin
_pipe = {decoder, fun gleam@dynamic@decode:decode_int/1},
gleam@dynamic@decode:map(_pipe, fun(I) -> {I, 0} end)
end,
Float = begin
_pipe@1 = {decoder, fun gleam@dynamic@decode:decode_float/1},
gleam@dynamic@decode:map(
_pipe@1,
fun(F) ->
Floored = math:floor(F),
Seconds = erlang:round(Floored),
Nanoseconds = erlang:round((F - Floored) * 1000000000.0),
{Seconds, Nanoseconds}
end
)
end,
gleam@dynamic@decode:one_of(Int, [Float]).
-file("src/parrot.gleam", 390).
-spec time_decoder() -> gleam@dynamic@decode:decoder(gleam@time@calendar:time_of_day()).
time_decoder() ->
gleam@dynamic@decode:field(
0,
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Hours) ->
gleam@dynamic@decode:field(
1,
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Minutes) ->
gleam@dynamic@decode:field(
2,
seconds_decoder(),
fun(_use0) ->
{Seconds, Nanoseconds} = _use0,
_pipe = {time_of_day,
Hours,
Minutes,
Seconds,
Nanoseconds},
gleam@dynamic@decode:success(_pipe)
end
)
end
)
end
).
-file("src/parrot.gleam", 365).
-spec datetime_tuple_decoder() -> gleam@dynamic@decode:decoder(gleam@time@timestamp:timestamp()).
datetime_tuple_decoder() ->
gleam@dynamic@decode:field(
0,
date_decoder(),
fun(Date) ->
gleam@dynamic@decode:field(
1,
time_decoder(),
fun(Time) ->
_pipe = gleam@time@timestamp:from_calendar(
Date,
Time,
{duration, 0, 0}
),
gleam@dynamic@decode:success(_pipe)
end
)
end
).
-file("src/parrot.gleam", 347).
-spec datetime_decoder() -> gleam@dynamic@decode:decoder(gleam@time@timestamp:timestamp()).
datetime_decoder() ->
gleam@dynamic@decode:one_of(
datetime_string_decoder(),
[datetime_tuple_decoder()]
).
-file("src/parrot.gleam", 78).
-spec main() -> {ok, nil} | {error, binary()}.
main() ->
case erlang:element(4, argv:load()) of
[<<"gen"/utf8>>, Engine_arg, Db] ->
Engine = gleam@dynamic@decode:run(
gleam_stdlib:identity(Engine_arg),
engine_decoder()
),
Result = case Engine of
{error, _} ->
{error, {unknown_engine, Engine_arg}};
{ok, Engine@1} ->
cmd_gen(Engine@1, Db)
end,
case Result of
{error, Err} ->
gleam_stdlib:println(
<<"Error! "/utf8, (err_to_string(Err))/binary>>
),
{ok, nil};
{ok, _} ->
gleam_stdlib:println(<<"SQL successfully generated!"/utf8>>),
{ok, nil}
end;
[<<"help"/utf8>>] ->
gleam_stdlib:println(
<<"Usage:
gleam run -m parrot help
gleam run -m parrot gen [sqlite | mysql | postgresql] [database]
"/utf8>>
),
{ok, nil};
_ ->
gleam_stdlib:println(
<<"Usage:
gleam run -m parrot help
gleam run -m parrot gen [sqlite | mysql | postgresql] [database]
"/utf8>>
),
{ok, nil}
end.