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]).
-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_db_not_found, binary()} |
{my_sql_db_not_found, binary()} |
{postgre_sql_db_not_found, binary()} |
no_queries_found |
mysqldump_error |
pgdump_error.
-type engine() :: s_qlite | my_sql | postgre_sql.
-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", 40).
-spec err_to_string(parrot_error()) -> binary().
err_to_string(Error) ->
case Error of
{my_sql_db_not_found, _} ->
<<"mysql db not found"/utf8>>;
{postgre_sql_db_not_found, _} ->
<<"postgresql db not found"/utf8>>;
{sqlit_db_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", 58).
-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_sql);
<<"psql"/utf8>> ->
gleam@dynamic@decode:success(postgre_sql);
_ ->
gleam@dynamic@decode:failure(s_qlite, <<"Driver"/utf8>>)
end end
).
-file("src/parrot.gleam", 68).
-spec engine_to_sqlc_string(engine()) -> binary().
engine_to_sqlc_string(Engine) ->
case Engine of
my_sql ->
<<"mysql"/utf8>>;
postgre_sql ->
<<"postgresql"/utf8>>;
s_qlite ->
<<"sqlite"/utf8>>
end.
-file("src/parrot.gleam", 180).
-spec fetch_schema_mysql(binary()) -> {ok, binary()} | {error, parrot_error()}.
fetch_schema_mysql(Db) ->
_assert_subject = gleam_stdlib:uri_parse(Db),
{ok, Conn} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
value => _assert_fail,
module => <<"parrot"/utf8>>,
function => <<"fetch_schema_mysql"/utf8>>,
line => 181})
end,
Creds = case erlang:element(3, Conn) 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_db_not_found, <<""/utf8>>}),
fun(_use0) ->
{User@2, Pass@1} = _use0,
Port@1 = case erlang:element(5, Conn) of
none ->
<<"3306"/utf8>>;
{some, Port} ->
erlang:integer_to_binary(Port)
end,
Host@1 = case erlang:element(4, Conn) of
none ->
<<"localhost"/utf8>>;
{some, Host} ->
Host
end,
Db@1 = gleam@string:replace(
erlang:element(6, Conn),
<<"/"/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", 223).
-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", 241).
-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_db_not_found, <<""/utf8>>}
)
end,
fun(Result) -> {ok, Result} end
)
end
).
-file("src/parrot.gleam", 274).
-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: 5d48e462aa8db371be5c9ce89a7494ad8e3baf5112e78386091313afd6930061
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", 301).
?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>> ->
_assert_subject = simplifile_erl:read_directory(From),
{ok, Files} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
value => _assert_fail,
module => <<"parrot"/utf8>>,
function => <<"walk"/utf8>>,
line => 304})
end,
Files@1 = begin
gleam@list:filter_map(
Files,
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@1}]);
_ ->
_assert_subject@1 = simplifile_erl:read_directory(From),
{ok, Files@2} = case _assert_subject@1 of
{ok, _} -> _assert_subject@1;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
value => _assert_fail@1,
module => <<"parrot"/utf8>>,
function => <<"walk"/utf8>>,
line => 319})
end,
Directories = begin
gleam@list:filter_map(
Files@2,
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", 111).
-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_sql ->
gleam@result:'try'(
fetch_schema_mysql(Db),
fun(Schema) -> {ok, Schema} end
);
postgre_sql ->
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>>,
module => <<"parrot"/utf8>>,
function => <<"cmd_gen"/utf8>>,
line => 164});
{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", 345).
-spec datetime_decoder() -> gleam@dynamic@decode:decoder(gleam@time@timestamp:timestamp()).
datetime_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", 76).
-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.