Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gren.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"WebSocketServer.Connection",
"Sqlite",
"Sqlite.Decode",
"Sqlite.Decode.Row",
"Sqlite.Encode"
],
"gren-version": "0.6.0 <= v < 0.7.0",
Expand Down
59 changes: 30 additions & 29 deletions integration-tests/sqlite/src/Main.gren
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Test.Runner.Effectful as Effectful exposing (test, await, awaitError, con
import Sqlite
import Sqlite.Encode as Encode
import Sqlite.Decode as Decode exposing (Decoder)
import Sqlite.Decode.Row as Row
import Task exposing (Task)
import FileSystem
import FileSystem.Path as Path exposing (Path)
Expand Down Expand Up @@ -299,18 +300,18 @@ personEncoder p =
]


personDecoder : Decoder Person
personDecoder : Row.Decoder Person
personDecoder =
Decode.string "name" <| \name ->
Decode.string "role" <| \role ->
Decode.succeed { name = name, role = role }
Row.field "name" Decode.string <| \name ->
Row.field "role" Decode.string <| \role ->
Row.succeed { name = name, role = role }


badPersonDecoder : Decoder Person
badPersonDecoder : Row.Decoder Person
badPersonDecoder =
Decode.string "namee" <| \name ->
Decode.string "role_" <| \role ->
Decode.succeed { name = name, role = role }
Row.field "namee" Decode.string <| \name ->
Row.field "role_" Decode.string <| \role ->
Row.succeed { name = name, role = role }


insertPerson : Person -> Sqlite.Database -> Task Sqlite.Error Sqlite.ExecutionSummary
Expand Down Expand Up @@ -380,9 +381,9 @@ friendshipQ =
"""
, parameters = []
, rowDecoder =
Decode.string "person_a" <| \personA ->
Decode.string "person_b" <| \personB ->
Decode.succeed { personA = personA, personB = personB }
Row.field "person_a" Decode.string <| \personA ->
Row.field "person_b" Decode.string <| \personB ->
Row.succeed { personA = personA, personB = personB }
}


Expand Down Expand Up @@ -414,7 +415,7 @@ failDecoder db =
Sqlite.getOne db
{ query = "SELECT * FROM people WHERE name = :name"
, parameters = [ Encode.string "name" "Robin" ]
, rowDecoder = Decode.fail "Oopsy!"
, rowDecoder = Row.fail "Oopsy!"
}


Expand All @@ -424,9 +425,9 @@ badDecoder db =
{ query = "SELECT * FROM people WHERE name = :name"
, parameters = [ Encode.string "name" "Robin" ]
, rowDecoder =
Decode.int "name" <| \name ->
Decode.string "role" <| \role ->
Decode.succeed { name = name, role = role }
Row.field "name" Decode.int <| \name ->
Row.field "role" Decode.string <| \role ->
Row.succeed { name = name, role = role }
}


Expand Down Expand Up @@ -500,19 +501,19 @@ allFieldTypesEncoder row =
]


allFieldTypesDecoder : Decoder AllFieldTypes
allFieldTypesDecoder : Row.Decoder AllFieldTypes
allFieldTypesDecoder =
Decode.string "string_field" <| \stringField ->
Decode.int "int_field" <| \intField ->
Decode.float "float_field" <| \floatField ->
Decode.bool "bool_true" <| \boolTrue ->
Decode.bool "bool_false" <| \boolFalse ->
Decode.json (Json.Decode.array Json.Decode.string) "json_field" <| \jsonField ->
Decode.time "time_field" <| \timeField ->
Decode.time "time_with_millis_field" <| \timeWithMillisField ->
Decode.maybe Decode.string "maybe_just_string" <| \maybeJustString ->
Decode.maybe Decode.string "maybe_nothing_string" <| \maybeNothingString ->
Decode.succeed
Row.field "string_field" Decode.string <| \stringField ->
Row.field "int_field" Decode.int <| \intField ->
Row.field "float_field" Decode.float <| \floatField ->
Row.field "bool_true" Decode.bool <| \boolTrue ->
Row.field "bool_false" Decode.bool <| \boolFalse ->
Row.field "json_field" (Decode.json (Json.Decode.array Json.Decode.string)) <| \jsonField ->
Row.field "time_field" Decode.time <| \timeField ->
Row.field "time_with_millis_field" Decode.time <| \timeWithMillisField ->
Row.field "maybe_just_string" (Decode.maybe Decode.string) <| \maybeJustString ->
Row.field "maybe_nothing_string" (Decode.maybe Decode.string) <| \maybeNothingString ->
Row.succeed
{ stringField = stringField
, intField = intField
, floatField = floatField
Expand Down Expand Up @@ -612,6 +613,6 @@ allTimesQ =
{ query = "SELECT t FROM times ORDER BY rowid"
, parameters = []
, rowDecoder =
Decode.time "t" <| \t ->
Decode.succeed t
Row.field "t" Decode.time <| \t ->
Row.succeed t
}
3 changes: 2 additions & 1 deletion src/Sqlite.gren
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import FileSystem
import FileSystem.Path exposing (Path)
import Json.Decode as Decode
import Sqlite.Decode
import Sqlite.Decode.Row
import Sqlite.Encode
import Task exposing (Task)
import Gren.Kernel.Sqlite
Expand Down Expand Up @@ -71,7 +72,7 @@ close =
type alias Query value =
{ query : String
, parameters : Array Sqlite.Encode.Value
, rowDecoder : Sqlite.Decode.Decoder value
, rowDecoder : Sqlite.Decode.Row.Decoder value
}


Expand Down
127 changes: 54 additions & 73 deletions src/Sqlite/Decode.gren
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ module Sqlite.Decode exposing
-- Composing
, succeed
, fail

-- Helpers
, toJson
)

{-| Decode SQL results into Gren values.
Expand Down Expand Up @@ -66,51 +69,48 @@ type Decoder a

{-| Decode a string field.
-}
string : String -> (String -> Decoder a) -> Decoder a
string fieldName cont =
fieldHelper fieldName Json.Decode.string cont
string : Decoder String
string =
Json.Decode.string |> Decoder


{-| Decode an integer field.
-}
int : String -> (Int -> Decoder a) -> Decoder a
int fieldName cont =
fieldHelper fieldName Json.Decode.int cont
int : Decoder Int
int =
Json.Decode.int |> Decoder


{-| Decode a float field.
-}
float : String -> (Float -> Decoder a) -> Decoder a
float fieldName cont =
fieldHelper fieldName Json.Decode.float cont
float : Decoder Float
float =
Json.Decode.float |> Decoder


{-| Decode a boolean field.

Booleans in sqlite are stored as integers with 1 and 0 as True and False.
See <https://www.sqlite.org/datatype3.html#boolean_datatype>
-}
bool : String -> (Bool -> Decoder a) -> Decoder a
bool fieldName cont =
let
boolDecoder =
Json.Decode.int
|> Json.Decode.andThen
(\i ->
when i is
0 ->
Json.Decode.succeed False

1 ->
Json.Decode.succeed True

n ->
Json.Decode.fail <|
"Expected 0 or 1 in boolean field, got " ++
String.fromInt n
)
in
fieldHelper fieldName boolDecoder cont
bool : Decoder Bool
bool =
Json.Decode.int
|> Json.Decode.andThen
(\i ->
when i is
0 ->
Json.Decode.succeed False

1 ->
Json.Decode.succeed True

n ->
Json.Decode.fail <|
"Expected 0 or 1 in boolean field, got " ++
String.fromInt n
)
|> Decoder


{-| Decode a JSON field.
Expand All @@ -122,28 +122,25 @@ Use `json()` in your SELECT to ensure you get text regardless of how the JSON wa
{ query = "SELECT json(tags) as tags FROM items WHERE id = 1"
, parameters = []
, rowDecoder =
Sqlite.Decode.json (Json.Decode.array Json.Decode.string) "tags" <| \tags ->
Sqlite.Decode.succeed { tags = tags }
Row.column "tags" (Decode.json (Json.Decode.array Json.Decode.string)) <| \tags ->
Row.succeed { tags = tags }
}

See <https://www.sqlite.org/json1.html>
-}
json : Json.Decode.Decoder a -> String -> (a -> Decoder b) -> Decoder b
json jsonDecoder fieldName cont =
let
decoder =
Json.Decode.string
|> Json.Decode.andThen
(\str ->
when Json.Decode.decodeString jsonDecoder str is
Ok val ->
Json.Decode.succeed val

Err err ->
Json.Decode.fail (Json.Decode.errorToString err)
)
in
fieldHelper fieldName decoder cont
json : Json.Decode.Decoder a -> Decoder a
json jsonDecoder =
Json.Decode.string
|> Json.Decode.andThen
(\str ->
when Json.Decode.decodeString jsonDecoder str is
Ok val ->
Json.Decode.succeed val

Err err ->
Json.Decode.fail (Json.Decode.errorToString err)
)
|> Decoder


{-| Decode a Time.Posix value.
Expand All @@ -154,32 +151,24 @@ how both [Sqlite.Encode.time](Sqlite.Encode#time) and
which aligns with SQLite's `unixepoch` function.
See <https://sqlite.org/lang_datefunc.html>
-}
time : String -> (Time.Posix -> Decoder a) -> Decoder a
time fieldName cont =
fieldHelper fieldName
(Json.Decode.map
time : Decoder Time.Posix
time =
Json.Decode.float
|> Json.Decode.map
(\seconds -> Time.millisToPosix (Math.round (seconds * 1000.0)))
Json.Decode.float
)
cont
|> Decoder


{-| Decode a nullable field in the database.

The first parameter is the field decoder function for the type if the value is not null.
For example, to decode a nullable TEXT field:

Sqlite.Decode.maybe Decode.string "nickname" <| \maybeNickname ->
Sqlite.Decode.succeed maybeNickname
-}
maybe : (String -> (a -> Decoder b) -> Decoder b) -> String -> (Maybe a -> Decoder b) -> Decoder b
maybe decoderFn fieldName cont =
maybe : Decoder a -> Decoder (Maybe a)
maybe decoder =
Decoder <|
Json.Decode.oneOf
[ unwrap (decoderFn fieldName (\val -> cont (Just val)))
, Json.Decode.andThen
(\_ -> unwrap (cont Nothing))
(Json.Decode.field fieldName (Json.Decode.null {}))
[ unwrap decoder |> Json.Decode.map Just
, Json.Decode.succeed Nothing
]


Expand Down Expand Up @@ -218,14 +207,6 @@ toJson =
unwrap


fieldHelper : String -> Json.Decode.Decoder a -> (a -> Decoder b) -> Decoder b
fieldHelper fieldName jsonDecoder cont =
Decoder <|
Json.Decode.andThen
(\val -> unwrap (cont val))
(Json.Decode.field fieldName jsonDecoder)


unwrap : Decoder a -> Json.Decode.Decoder a
unwrap (Decoder d) =
d
37 changes: 37 additions & 0 deletions src/Sqlite/Decode/Row.gren
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module Sqlite.Decode.Row exposing
( Decoder
, field
, succeed
, fail
)


import Json.Decode
import Sqlite.Decode


type Decoder a
= Decoder (Json.Decode.Decoder a)


succeed : a -> Decoder a
succeed val =
Decoder (Json.Decode.succeed val)


fail : String -> Decoder a
fail reason =
Decoder (Json.Decode.fail reason)


field : String -> Sqlite.Decode.Decoder a -> (a -> Decoder b) -> Decoder b
field name decoder cont =
Decoder <|
Json.Decode.andThen
(\val -> unwrap (cont val))
(Json.Decode.field name (Sqlite.Decode.toJson decoder))


unwrap : Decoder a -> Json.Decode.Decoder a
unwrap (Decoder d) =
d
Loading