Current section
40 Versions
Jump to
Current section
40 Versions
Compare versions
5
files changed
+234
additions
-55
deletions
| @@ -1,5 +1,5 @@ | |
| 1 1 | name = "parrot" |
| 2 | - version = "0.0.10" |
| 2 | + version = "0.0.11" |
| 3 3 | |
| 4 4 | |
| 5 5 | description = "🦜 type-safe SQL in gleam for sqlite, postgresql & mysql" |
| @@ -1,6 +1,6 @@ | |
| 1 1 | {<<"name">>, <<"parrot">>}. |
| 2 2 | {<<"app">>, <<"parrot">>}. |
| 3 | - {<<"version">>, <<"0.0.10">>}. |
| 3 | + {<<"version">>, <<"0.0.11">>}. |
| 4 4 | {<<"description">>, <<"🦜 type-safe SQL in gleam for sqlite, postgresql & mysql"/utf8>>}. |
| 5 5 | {<<"licenses">>, [<<"Apache-2.0">>]}. |
| 6 6 | {<<"build_tools">>, [<<"gleam">>]}. |
| @@ -8,65 +8,65 @@ | |
| 8 8 | {<<"Repository">>, <<"https://github.com/daniellionel01/parrot">>} |
| 9 9 | ]}. |
| 10 10 | {<<"requirements">>, [ |
| 11 | - {<<"gleam_stdlib">>, [ |
| 12 | - {<<"app">>, <<"gleam_stdlib">>}, |
| 11 | + {<<"shork">>, [ |
| 12 | + {<<"app">>, <<"shork">>}, |
| 13 13 | {<<"optional">>, false}, |
| 14 | - {<<"requirement">>, <<">= 0.34.0 and < 2.0.0">>} |
| 14 | + {<<"requirement">>, <<">= 1.2.0 and < 2.0.0">>} |
| 15 | + ]}, |
| 16 | + {<<"tom">>, [ |
| 17 | + {<<"app">>, <<"tom">>}, |
| 18 | + {<<"optional">>, false}, |
| 19 | + {<<"requirement">>, <<">= 1.1.1 and < 2.0.0">>} |
| 15 20 | ]}, |
| 16 21 | {<<"shellout">>, [ |
| 17 22 | {<<"app">>, <<"shellout">>}, |
| 18 23 | {<<"optional">>, false}, |
| 19 24 | {<<"requirement">>, <<">= 1.7.0 and < 2.0.0">>} |
| 20 25 | ]}, |
| 21 | - {<<"simplifile">>, [ |
| 22 | - {<<"app">>, <<"simplifile">>}, |
| 26 | + {<<"filepath">>, [ |
| 27 | + {<<"app">>, <<"filepath">>}, |
| 23 28 | {<<"optional">>, false}, |
| 24 | - {<<"requirement">>, <<">= 2.2.0 and < 3.0.0">>} |
| 25 | - ]}, |
| 26 | - {<<"pog">>, [ |
| 27 | - {<<"app">>, <<"pog">>}, |
| 28 | - {<<"optional">>, false}, |
| 29 | - {<<"requirement">>, <<">= 3.2.0 and < 4.0.0">>} |
| 30 | - ]}, |
| 31 | - {<<"shork">>, [ |
| 32 | - {<<"app">>, <<"shork">>}, |
| 33 | - {<<"optional">>, false}, |
| 34 | - {<<"requirement">>, <<">= 1.2.0 and < 2.0.0">>} |
| 29 | + {<<"requirement">>, <<">= 1.1.0 and < 2.0.0">>} |
| 35 30 | ]}, |
| 36 31 | {<<"gleam_time">>, [ |
| 37 32 | {<<"app">>, <<"gleam_time">>}, |
| 38 33 | {<<"optional">>, false}, |
| 39 34 | {<<"requirement">>, <<">= 1.2.0 and < 2.0.0">>} |
| 40 35 | ]}, |
| 41 | - {<<"exception">>, [ |
| 42 | - {<<"app">>, <<"exception">>}, |
| 36 | + {<<"gleam_stdlib">>, [ |
| 37 | + {<<"app">>, <<"gleam_stdlib">>}, |
| 43 38 | {<<"optional">>, false}, |
| 44 | - {<<"requirement">>, <<">= 2.0.0 and < 3.0.0">>} |
| 39 | + {<<"requirement">>, <<">= 0.34.0 and < 2.0.0">>} |
| 45 40 | ]}, |
| 46 | - {<<"filepath">>, [ |
| 47 | - {<<"app">>, <<"filepath">>}, |
| 41 | + {<<"simplifile">>, [ |
| 42 | + {<<"app">>, <<"simplifile">>}, |
| 48 43 | {<<"optional">>, false}, |
| 49 | - {<<"requirement">>, <<">= 1.1.0 and < 2.0.0">>} |
| 50 | - ]}, |
| 51 | - {<<"sqlight">>, [ |
| 52 | - {<<"app">>, <<"sqlight">>}, |
| 53 | - {<<"optional">>, false}, |
| 54 | - {<<"requirement">>, <<">= 1.0.1 and < 2.0.0">>} |
| 55 | - ]}, |
| 56 | - {<<"gleam_json">>, [ |
| 57 | - {<<"app">>, <<"gleam_json">>}, |
| 58 | - {<<"optional">>, false}, |
| 59 | - {<<"requirement">>, <<">= 3.0.1 and < 4.0.0">>} |
| 44 | + {<<"requirement">>, <<">= 2.2.0 and < 3.0.0">>} |
| 60 45 | ]}, |
| 61 46 | {<<"argv">>, [ |
| 62 47 | {<<"app">>, <<"argv">>}, |
| 63 48 | {<<"optional">>, false}, |
| 64 49 | {<<"requirement">>, <<">= 1.0.2 and < 2.0.0">>} |
| 65 50 | ]}, |
| 66 | - {<<"tom">>, [ |
| 67 | - {<<"app">>, <<"tom">>}, |
| 51 | + {<<"pog">>, [ |
| 52 | + {<<"app">>, <<"pog">>}, |
| 68 53 | {<<"optional">>, false}, |
| 69 | - {<<"requirement">>, <<">= 1.1.1 and < 2.0.0">>} |
| 54 | + {<<"requirement">>, <<">= 3.2.0 and < 4.0.0">>} |
| 55 | + ]}, |
| 56 | + {<<"gleam_json">>, [ |
| 57 | + {<<"app">>, <<"gleam_json">>}, |
| 58 | + {<<"optional">>, false}, |
| 59 | + {<<"requirement">>, <<">= 3.0.1 and < 4.0.0">>} |
| 60 | + ]}, |
| 61 | + {<<"exception">>, [ |
| 62 | + {<<"app">>, <<"exception">>}, |
| 63 | + {<<"optional">>, false}, |
| 64 | + {<<"requirement">>, <<">= 2.0.0 and < 3.0.0">>} |
| 65 | + ]}, |
| 66 | + {<<"sqlight">>, [ |
| 67 | + {<<"app">>, <<"sqlight">>}, |
| 68 | + {<<"optional">>, false}, |
| 69 | + {<<"requirement">>, <<">= 1.0.1 and < 2.0.0">>} |
| 70 70 | ]} |
| 71 71 | ]}. |
| 72 72 | {<<"files">>, [ |
| @@ -1,5 +1,5 @@ | |
| 1 1 | {application, parrot, [ |
| 2 | - {vsn, "0.0.10"}, |
| 2 | + {vsn, "0.0.11"}, |
| 3 3 | {applications, [argv, |
| 4 4 | exception, |
| 5 5 | filepath, |
| @@ -30,7 +30,7 @@ | |
| 30 30 | {param_timestamp, gleam@time@timestamp:timestamp()} | |
| 31 31 | {param_dynamic, gleam@dynamic:dynamic_()}. |
| 32 32 | |
| 33 | - -file("src/parrot.gleam", 40). |
| 33 | + -file("src/parrot.gleam", 42). |
| 34 34 | -spec err_to_string(parrot_error()) -> binary(). |
| 35 35 | err_to_string(Error) -> |
| 36 36 | case Error of |
| @@ -56,7 +56,7 @@ err_to_string(Error) -> | |
| 56 56 | <<"unknown engine: "/utf8, Engine/binary>> |
| 57 57 | end. |
| 58 58 | |
| 59 | - -file("src/parrot.gleam", 58). |
| 59 | + -file("src/parrot.gleam", 60). |
| 60 60 | -spec engine_decoder() -> gleam@dynamic@decode:decoder(engine()). |
| 61 61 | engine_decoder() -> |
| 62 62 | gleam@dynamic@decode:then( |
| @@ -76,7 +76,7 @@ engine_decoder() -> | |
| 76 76 | end end |
| 77 77 | ). |
| 78 78 | |
| 79 | - -file("src/parrot.gleam", 68). |
| 79 | + -file("src/parrot.gleam", 70). |
| 80 80 | -spec engine_to_sqlc_string(engine()) -> binary(). |
| 81 81 | engine_to_sqlc_string(Engine) -> |
| 82 82 | case Engine of |
| @@ -90,7 +90,7 @@ engine_to_sqlc_string(Engine) -> | |
| 90 90 | <<"sqlite"/utf8>> |
| 91 91 | end. |
| 92 92 | |
| 93 | - -file("src/parrot.gleam", 180). |
| 93 | + -file("src/parrot.gleam", 182). |
| 94 94 | -spec fetch_schema_mysql(binary()) -> {ok, binary()} | {error, parrot_error()}. |
| 95 95 | fetch_schema_mysql(Db) -> |
| 96 96 | _assert_subject = gleam_stdlib:uri_parse(Db), |
| @@ -102,7 +102,7 @@ fetch_schema_mysql(Db) -> | |
| 102 102 | value => _assert_fail, |
| 103 103 | module => <<"parrot"/utf8>>, |
| 104 104 | function => <<"fetch_schema_mysql"/utf8>>, |
| 105 | - line => 181}) |
| 105 | + line => 183}) |
| 106 106 | end, |
| 107 107 | Creds = case erlang:element(3, Conn) of |
| 108 108 | none -> |
| @@ -179,7 +179,7 @@ fetch_schema_mysql(Db) -> | |
| 179 179 | end |
| 180 180 | ). |
| 181 181 | |
| 182 | - -file("src/parrot.gleam", 223). |
| 182 | + -file("src/parrot.gleam", 225). |
| 183 183 | -spec fetch_schema_postgresql(binary()) -> {ok, binary()} | |
| 184 184 | {error, parrot_error()}. |
| 185 185 | fetch_schema_postgresql(Db) -> |
| @@ -197,7 +197,7 @@ fetch_schema_postgresql(Db) -> | |
| 197 197 | ), |
| 198 198 | gleam@result:replace_error(_pipe, pgdump_error). |
| 199 199 | |
| 200 | - -file("src/parrot.gleam", 241). |
| 200 | + -file("src/parrot.gleam", 243). |
| 201 201 | -spec fetch_schema_sqlite(binary()) -> {ok, list(binary())} | |
| 202 202 | {error, parrot_error()}. |
| 203 203 | fetch_schema_sqlite(Db) -> |
| @@ -240,7 +240,7 @@ SELECT sql | |
| 240 240 | end |
| 241 241 | ). |
| 242 242 | |
| 243 | - -file("src/parrot.gleam", 274). |
| 243 | + -file("src/parrot.gleam", 276). |
| 244 244 | -spec gen_sqlc_yaml(engine(), list(binary())) -> binary(). |
| 245 245 | gen_sqlc_yaml(Engine, Queries) -> |
| 246 246 | Result = <<<<<<<<" |
| @@ -267,7 +267,7 @@ sql: | |
| 267 267 | "/utf8>>, |
| 268 268 | gleam@string:trim(Result). |
| 269 269 | |
| 270 | - -file("src/parrot.gleam", 301). |
| 270 | + -file("src/parrot.gleam", 303). |
| 271 271 | ?DOC( |
| 272 272 | " Finds all `from/**/sql` directories and lists the full paths of the `*.sql`\n" |
| 273 273 | " files inside each one.\n" |
| @@ -286,7 +286,7 @@ walk(From) -> | |
| 286 286 | value => _assert_fail, |
| 287 287 | module => <<"parrot"/utf8>>, |
| 288 288 | function => <<"walk"/utf8>>, |
| 289 | - line => 304}) |
| 289 | + line => 306}) |
| 290 290 | end, |
| 291 291 | Files@1 = begin |
| 292 292 | gleam@list:filter_map( |
| @@ -329,7 +329,7 @@ walk(From) -> | |
| 329 329 | value => _assert_fail@1, |
| 330 330 | module => <<"parrot"/utf8>>, |
| 331 331 | function => <<"walk"/utf8>>, |
| 332 | - line => 319}) |
| 332 | + line => 321}) |
| 333 333 | end, |
| 334 334 | Directories = begin |
| 335 335 | gleam@list:filter_map( |
| @@ -353,7 +353,7 @@ walk(From) -> | |
| 353 353 | gleam@list:fold(_pipe, maps:new(), fun maps:merge/2) |
| 354 354 | end. |
| 355 355 | |
| 356 | - -file("src/parrot.gleam", 111). |
| 356 | + -file("src/parrot.gleam", 113). |
| 357 357 | -spec cmd_gen(engine(), binary()) -> {ok, nil} | {error, parrot_error()}. |
| 358 358 | cmd_gen(Engine, Db) -> |
| 359 359 | Files = walk(parrot@internal@project:src()), |
| @@ -442,7 +442,7 @@ cmd_gen(Engine, Db) -> | |
| 442 442 | message => <<"`panic` expression evaluated."/utf8>>, |
| 443 443 | module => <<"parrot"/utf8>>, |
| 444 444 | function => <<"cmd_gen"/utf8>>, |
| 445 | - line => 164}); |
| 445 | + line => 166}); |
| 446 446 | |
| 447 447 | {ok, _} -> |
| 448 448 | nil |
| @@ -455,9 +455,9 @@ cmd_gen(Engine, Db) -> | |
| 455 455 | {ok, nil} |
| 456 456 | end). |
| 457 457 | |
| 458 | - -file("src/parrot.gleam", 345). |
| 459 | - -spec datetime_decoder() -> gleam@dynamic@decode:decoder(gleam@time@timestamp:timestamp()). |
| 460 | - datetime_decoder() -> |
| 458 | + -file("src/parrot.gleam", 351). |
| 459 | + -spec datetime_string_decoder() -> gleam@dynamic@decode:decoder(gleam@time@timestamp:timestamp()). |
| 460 | + datetime_string_decoder() -> |
| 461 461 | _pipe = {decoder, fun gleam@dynamic@decode:decode_string/1}, |
| 462 462 | gleam@dynamic@decode:then( |
| 463 463 | _pipe, |
| @@ -475,7 +475,129 @@ datetime_decoder() -> | |
| 475 475 | end |
| 476 476 | ). |
| 477 477 | |
| 478 | - -file("src/parrot.gleam", 76). |
| 478 | + -file("src/parrot.gleam", 373). |
| 479 | + -spec date_decoder() -> gleam@dynamic@decode:decoder(gleam@time@calendar:date()). |
| 480 | + date_decoder() -> |
| 481 | + gleam@dynamic@decode:field( |
| 482 | + 0, |
| 483 | + {decoder, fun gleam@dynamic@decode:decode_int/1}, |
| 484 | + fun(Year) -> |
| 485 | + gleam@dynamic@decode:field( |
| 486 | + 1, |
| 487 | + begin |
| 488 | + _pipe = {decoder, fun gleam@dynamic@decode:decode_int/1}, |
| 489 | + gleam@dynamic@decode:then( |
| 490 | + _pipe, |
| 491 | + fun(Month) -> |
| 492 | + case gleam@time@calendar:month_from_int(Month) of |
| 493 | + {error, _} -> |
| 494 | + gleam@dynamic@decode:failure( |
| 495 | + january, |
| 496 | + <<"Month"/utf8>> |
| 497 | + ); |
| 498 | + |
| 499 | + {ok, Month@1} -> |
| 500 | + gleam@dynamic@decode:success(Month@1) |
| 501 | + end |
| 502 | + end |
| 503 | + ) |
| 504 | + end, |
| 505 | + fun(Month@2) -> |
| 506 | + gleam@dynamic@decode:field( |
| 507 | + 2, |
| 508 | + {decoder, fun gleam@dynamic@decode:decode_int/1}, |
| 509 | + fun(Day) -> |
| 510 | + gleam@dynamic@decode:success( |
| 511 | + {date, Year, Month@2, Day} |
| 512 | + ) |
| 513 | + end |
| 514 | + ) |
| 515 | + end |
| 516 | + ) |
| 517 | + end |
| 518 | + ). |
| 519 | + |
| 520 | + -file("src/parrot.gleam", 399). |
| 521 | + -spec seconds_decoder() -> gleam@dynamic@decode:decoder({integer(), integer()}). |
| 522 | + seconds_decoder() -> |
| 523 | + Int = begin |
| 524 | + _pipe = {decoder, fun gleam@dynamic@decode:decode_int/1}, |
| 525 | + gleam@dynamic@decode:map(_pipe, fun(I) -> {I, 0} end) |
| 526 | + end, |
| 527 | + Float = begin |
| 528 | + _pipe@1 = {decoder, fun gleam@dynamic@decode:decode_float/1}, |
| 529 | + gleam@dynamic@decode:map( |
| 530 | + _pipe@1, |
| 531 | + fun(F) -> |
| 532 | + Floored = math:floor(F), |
| 533 | + Seconds = erlang:round(Floored), |
| 534 | + Nanoseconds = erlang:round((F - Floored) * 1000000000.0), |
| 535 | + {Seconds, Nanoseconds} |
| 536 | + end |
| 537 | + ) |
| 538 | + end, |
| 539 | + gleam@dynamic@decode:one_of(Int, [Float]). |
| 540 | + |
| 541 | + -file("src/parrot.gleam", 390). |
| 542 | + -spec time_decoder() -> gleam@dynamic@decode:decoder(gleam@time@calendar:time_of_day()). |
| 543 | + time_decoder() -> |
| 544 | + gleam@dynamic@decode:field( |
| 545 | + 0, |
| 546 | + {decoder, fun gleam@dynamic@decode:decode_int/1}, |
| 547 | + fun(Hours) -> |
| 548 | + gleam@dynamic@decode:field( |
| 549 | + 1, |
| 550 | + {decoder, fun gleam@dynamic@decode:decode_int/1}, |
| 551 | + fun(Minutes) -> |
| 552 | + gleam@dynamic@decode:field( |
| 553 | + 2, |
| 554 | + seconds_decoder(), |
| 555 | + fun(_use0) -> |
| 556 | + {Seconds, Nanoseconds} = _use0, |
| 557 | + _pipe = {time_of_day, |
| 558 | + Hours, |
| 559 | + Minutes, |
| 560 | + Seconds, |
| 561 | + Nanoseconds}, |
| 562 | + gleam@dynamic@decode:success(_pipe) |
| 563 | + end |
| 564 | + ) |
| 565 | + end |
| 566 | + ) |
| 567 | + end |
| 568 | + ). |
| 569 | + |
| 570 | + -file("src/parrot.gleam", 365). |
| 571 | + -spec datetime_tuple_decoder() -> gleam@dynamic@decode:decoder(gleam@time@timestamp:timestamp()). |
| 572 | + datetime_tuple_decoder() -> |
| 573 | + gleam@dynamic@decode:field( |
| 574 | + 0, |
| 575 | + date_decoder(), |
| 576 | + fun(Date) -> |
| 577 | + gleam@dynamic@decode:field( |
| 578 | + 1, |
| 579 | + time_decoder(), |
| 580 | + fun(Time) -> |
| 581 | + _pipe = gleam@time@timestamp:from_calendar( |
| 582 | + Date, |
| 583 | + Time, |
| 584 | + {duration, 0, 0} |
| 585 | + ), |
| 586 | + gleam@dynamic@decode:success(_pipe) |
| 587 | + end |
| 588 | + ) |
| 589 | + end |
| 590 | + ). |
| 591 | + |
| 592 | + -file("src/parrot.gleam", 347). |
| 593 | + -spec datetime_decoder() -> gleam@dynamic@decode:decoder(gleam@time@timestamp:timestamp()). |
| 594 | + datetime_decoder() -> |
| 595 | + gleam@dynamic@decode:one_of( |
| 596 | + datetime_string_decoder(), |
| 597 | + [datetime_tuple_decoder()] |
| 598 | + ). |
| 599 | + |
| 600 | + -file("src/parrot.gleam", 78). |
| 479 601 | -spec main() -> {ok, nil} | {error, binary()}. |
| 480 602 | main() -> |
| 481 603 | case erlang:element(4, argv:load()) of |
| @@ -4,12 +4,14 @@ import gleam/bool | |
| 4 4 | import gleam/dict.{type Dict} |
| 5 5 | import gleam/dynamic |
| 6 6 | import gleam/dynamic/decode |
| 7 | + import gleam/float |
| 7 8 | import gleam/int |
| 8 9 | import gleam/io |
| 9 10 | import gleam/list |
| 10 11 | import gleam/option |
| 11 12 | import gleam/result |
| 12 13 | import gleam/string |
| 14 | + import gleam/time/calendar.{type Date, type TimeOfDay, Date} |
| 13 15 | import gleam/time/timestamp.{type Timestamp} |
| 14 16 | import gleam/uri |
| 15 17 | import parrot/codegen |
| @@ -343,6 +345,10 @@ pub type Param { | |
| 343 345 | } |
| 344 346 | |
| 345 347 | pub fn datetime_decoder() -> decode.Decoder(Timestamp) { |
| 348 | + decode.one_of(datetime_string_decoder(), or: [datetime_tuple_decoder()]) |
| 349 | + } |
| 350 | + |
| 351 | + fn datetime_string_decoder() -> decode.Decoder(Timestamp) { |
| 346 352 | decode.string |
| 347 353 | |> decode.then(fn(datetime_str) { |
| 348 354 | case timestamp.parse_rfc3339(datetime_str) { |
| @@ -355,3 +361,54 @@ pub fn datetime_decoder() -> decode.Decoder(Timestamp) { | |
| 355 361 | } |
| 356 362 | }) |
| 357 363 | } |
| 364 | + |
| 365 | + fn datetime_tuple_decoder() -> decode.Decoder(Timestamp) { |
| 366 | + use date <- decode.field(0, date_decoder()) |
| 367 | + use time <- decode.field(1, time_decoder()) |
| 368 | + |
| 369 | + timestamp.from_calendar(date:, time:, offset: calendar.utc_offset) |
| 370 | + |> decode.success() |
| 371 | + } |
| 372 | + |
| 373 | + fn date_decoder() -> decode.Decoder(Date) { |
| 374 | + use year <- decode.field(0, decode.int) |
| 375 | + use month <- decode.field( |
| 376 | + 1, |
| 377 | + decode.int |
| 378 | + |> decode.then(fn(month) { |
| 379 | + case calendar.month_from_int(month) { |
| 380 | + Error(_) -> decode.failure(calendar.January, "Month") |
| 381 | + Ok(month) -> decode.success(month) |
| 382 | + } |
| 383 | + }), |
| 384 | + ) |
| 385 | + use day <- decode.field(2, decode.int) |
| 386 | + |
| 387 | + decode.success(Date(year:, month:, day:)) |
| 388 | + } |
| 389 | + |
| 390 | + fn time_decoder() -> decode.Decoder(TimeOfDay) { |
| 391 | + use hours <- decode.field(0, decode.int) |
| 392 | + use minutes <- decode.field(1, decode.int) |
| 393 | + use #(seconds, nanoseconds) <- decode.field(2, seconds_decoder()) |
| 394 | + |
| 395 | + calendar.TimeOfDay(hours:, minutes:, seconds:, nanoseconds:) |
| 396 | + |> decode.success() |
| 397 | + } |
| 398 | + |
| 399 | + fn seconds_decoder() -> decode.Decoder(#(Int, Int)) { |
| 400 | + let int = { |
| 401 | + decode.int |
| 402 | + |> decode.map(fn(i) { #(i, 0) }) |
| 403 | + } |
| 404 | + let float = { |
| 405 | + decode.float |
| 406 | + |> decode.map(fn(f) { |
| 407 | + let floored = float.floor(f) |
| 408 | + let seconds = float.round(floored) |
| 409 | + let nanoseconds = float.round({ f -. floored } *. 1_000_000_000.0) |
| 410 | + #(seconds, nanoseconds) |
| 411 | + }) |
| 412 | + } |
| 413 | + decode.one_of(int, [float]) |
| 414 | + } |