-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) -> <> 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, <>}, _ = 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.