Current section

40 Versions

Jump to

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 + }