-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 => <>, 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 => <>, 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 => <>, 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) -> <> 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 => <>, module => <<"parrot"/utf8>>, function => <<"cmd_gen"/utf8>>, line => 166}); {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", 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.