{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE ExtendedDefaultRules #-}
{-# OPTIONS_GHC -fno-warn-type-defaults #-}

-- | Execute a Select query against the BigQuery REST API.
module Hasura.Backends.BigQuery.Execute
  ( executeSelect,
    runExecute,
    streamBigQuery,
    executeBigQuery,
    executeProblemMessage,
    insertDataset,
    deleteDataset,
    BigQuery (..),
    Execute,
    ExecuteProblem (..),
    FieldNameText (..),
    OutputValue (..),
    RecordSet (..),
    ShowDetails (..),
    Value (..),
  )
where

import Control.Applicative
import Control.Concurrent.Extended (sleep)
import Control.Monad.Except
import Control.Monad.Reader
import Data.Aeson ((.!=), (.:), (.:?), (.=))
import Data.Aeson qualified as Aeson
import Data.Aeson.Types qualified as Aeson
import Data.ByteString.Lazy qualified as BL
import Data.Foldable
import Data.HashMap.Strict.InsOrd qualified as OMap
import Data.Maybe
import Data.Text qualified as T
import Data.Text.Lazy qualified as LT
import Data.Text.Lazy.Builder qualified as LT
import Data.Text.Lazy.Encoding qualified as LT
import Data.Text.Read qualified as TR
import Data.Time
import Data.Time.Format.ISO8601 (iso8601Show)
import Data.Vector (Vector)
import Data.Vector qualified as V
import GHC.Generics
import Hasura.Backends.BigQuery.Connection
import Hasura.Backends.BigQuery.Source
import Hasura.Backends.BigQuery.ToQuery qualified as ToQuery
import Hasura.Backends.BigQuery.Types as BigQuery
import Hasura.Prelude hiding (head, state, tail)
import Network.HTTP.Simple
import Network.HTTP.Types

--------------------------------------------------------------------------------
-- Types

-- | A set of records produced by the database. These are joined
-- together. There are all sorts of optimizations possible here, from
-- using a matrix/flat vector, unboxed sums for Value, etc. Presently
-- we choose a naive implementation in the interest of getting other
-- work done.
data RecordSet = RecordSet
  { RecordSet -> Vector (InsOrdHashMap FieldNameText OutputValue)
rows :: Vector (InsOrdHashMap FieldNameText OutputValue),
    RecordSet -> Maybe [Text]
wantedFields :: Maybe [Text]
  }
  deriving (Int -> RecordSet -> ShowS
[RecordSet] -> ShowS
RecordSet -> String
(Int -> RecordSet -> ShowS)
-> (RecordSet -> String)
-> ([RecordSet] -> ShowS)
-> Show RecordSet
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [RecordSet] -> ShowS
$cshowList :: [RecordSet] -> ShowS
show :: RecordSet -> String
$cshow :: RecordSet -> String
showsPrec :: Int -> RecordSet -> ShowS
$cshowsPrec :: Int -> RecordSet -> ShowS
Show)

-- | As opposed to BigQuery.FieldName which is a qualified name, this
-- is just the unqualified text name itself.
newtype FieldNameText
  = FieldNameText Text
  deriving (Int -> FieldNameText -> ShowS
[FieldNameText] -> ShowS
FieldNameText -> String
(Int -> FieldNameText -> ShowS)
-> (FieldNameText -> String)
-> ([FieldNameText] -> ShowS)
-> Show FieldNameText
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [FieldNameText] -> ShowS
$cshowList :: [FieldNameText] -> ShowS
show :: FieldNameText -> String
$cshow :: FieldNameText -> String
showsPrec :: Int -> FieldNameText -> ShowS
$cshowsPrec :: Int -> FieldNameText -> ShowS
Show, Eq FieldNameText
Eq FieldNameText
-> (FieldNameText -> FieldNameText -> Ordering)
-> (FieldNameText -> FieldNameText -> Bool)
-> (FieldNameText -> FieldNameText -> Bool)
-> (FieldNameText -> FieldNameText -> Bool)
-> (FieldNameText -> FieldNameText -> Bool)
-> (FieldNameText -> FieldNameText -> FieldNameText)
-> (FieldNameText -> FieldNameText -> FieldNameText)
-> Ord FieldNameText
FieldNameText -> FieldNameText -> Bool
FieldNameText -> FieldNameText -> Ordering
FieldNameText -> FieldNameText -> FieldNameText
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: FieldNameText -> FieldNameText -> FieldNameText
$cmin :: FieldNameText -> FieldNameText -> FieldNameText
max :: FieldNameText -> FieldNameText -> FieldNameText
$cmax :: FieldNameText -> FieldNameText -> FieldNameText
>= :: FieldNameText -> FieldNameText -> Bool
$c>= :: FieldNameText -> FieldNameText -> Bool
> :: FieldNameText -> FieldNameText -> Bool
$c> :: FieldNameText -> FieldNameText -> Bool
<= :: FieldNameText -> FieldNameText -> Bool
$c<= :: FieldNameText -> FieldNameText -> Bool
< :: FieldNameText -> FieldNameText -> Bool
$c< :: FieldNameText -> FieldNameText -> Bool
compare :: FieldNameText -> FieldNameText -> Ordering
$ccompare :: FieldNameText -> FieldNameText -> Ordering
$cp1Ord :: Eq FieldNameText
Ord, FieldNameText -> FieldNameText -> Bool
(FieldNameText -> FieldNameText -> Bool)
-> (FieldNameText -> FieldNameText -> Bool) -> Eq FieldNameText
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: FieldNameText -> FieldNameText -> Bool
$c/= :: FieldNameText -> FieldNameText -> Bool
== :: FieldNameText -> FieldNameText -> Bool
$c== :: FieldNameText -> FieldNameText -> Bool
Eq, Int -> FieldNameText -> Int
FieldNameText -> Int
(Int -> FieldNameText -> Int)
-> (FieldNameText -> Int) -> Hashable FieldNameText
forall a. (Int -> a -> Int) -> (a -> Int) -> Hashable a
hash :: FieldNameText -> Int
$chash :: FieldNameText -> Int
hashWithSalt :: Int -> FieldNameText -> Int
$chashWithSalt :: Int -> FieldNameText -> Int
Hashable, Value -> Parser [FieldNameText]
Value -> Parser FieldNameText
(Value -> Parser FieldNameText)
-> (Value -> Parser [FieldNameText]) -> FromJSON FieldNameText
forall a.
(Value -> Parser a) -> (Value -> Parser [a]) -> FromJSON a
parseJSONList :: Value -> Parser [FieldNameText]
$cparseJSONList :: Value -> Parser [FieldNameText]
parseJSON :: Value -> Parser FieldNameText
$cparseJSON :: Value -> Parser FieldNameText
Aeson.FromJSON, ToJSONKeyFunction [FieldNameText]
ToJSONKeyFunction FieldNameText
ToJSONKeyFunction FieldNameText
-> ToJSONKeyFunction [FieldNameText] -> ToJSONKey FieldNameText
forall a.
ToJSONKeyFunction a -> ToJSONKeyFunction [a] -> ToJSONKey a
toJSONKeyList :: ToJSONKeyFunction [FieldNameText]
$ctoJSONKeyList :: ToJSONKeyFunction [FieldNameText]
toJSONKey :: ToJSONKeyFunction FieldNameText
$ctoJSONKey :: ToJSONKeyFunction FieldNameText
Aeson.ToJSONKey, String -> FieldNameText
(String -> FieldNameText) -> IsString FieldNameText
forall a. (String -> a) -> IsString a
fromString :: String -> FieldNameText
$cfromString :: String -> FieldNameText
IsString)

data OutputValue
  = DecimalOutputValue Decimal
  | BigDecimalOutputValue BigDecimal
  | IntegerOutputValue Int64
  | FloatOutputValue Float64
  | GeographyOutputValue Geography
  | TextOutputValue Text
  | TimestampOutputValue Timestamp
  | DateOutputValue Date
  | TimeOutputValue Time
  | DatetimeOutputValue Datetime
  | BytesOutputValue Base64
  | BoolOutputValue Bool
  | ArrayOutputValue (Vector OutputValue)
  | RecordOutputValue (InsOrdHashMap FieldNameText OutputValue)
  | NullOutputValue -- TODO: Consider implications.
  deriving (Int -> OutputValue -> ShowS
[OutputValue] -> ShowS
OutputValue -> String
(Int -> OutputValue -> ShowS)
-> (OutputValue -> String)
-> ([OutputValue] -> ShowS)
-> Show OutputValue
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [OutputValue] -> ShowS
$cshowList :: [OutputValue] -> ShowS
show :: OutputValue -> String
$cshow :: OutputValue -> String
showsPrec :: Int -> OutputValue -> ShowS
$cshowsPrec :: Int -> OutputValue -> ShowS
Show, OutputValue -> OutputValue -> Bool
(OutputValue -> OutputValue -> Bool)
-> (OutputValue -> OutputValue -> Bool) -> Eq OutputValue
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: OutputValue -> OutputValue -> Bool
$c/= :: OutputValue -> OutputValue -> Bool
== :: OutputValue -> OutputValue -> Bool
$c== :: OutputValue -> OutputValue -> Bool
Eq, (forall x. OutputValue -> Rep OutputValue x)
-> (forall x. Rep OutputValue x -> OutputValue)
-> Generic OutputValue
forall x. Rep OutputValue x -> OutputValue
forall x. OutputValue -> Rep OutputValue x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep OutputValue x -> OutputValue
$cfrom :: forall x. OutputValue -> Rep OutputValue x
Generic)

instance Hashable OutputValue

instance Aeson.ToJSON OutputValue where
  toJSON :: OutputValue -> Value
toJSON = \case
    OutputValue
NullOutputValue -> Value -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON Value
Aeson.Null
    DecimalOutputValue Decimal
i -> Decimal -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON Decimal
i
    BigDecimalOutputValue BigDecimal
i -> BigDecimal -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON BigDecimal
i
    FloatOutputValue Float64
i -> Float64 -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON Float64
i
    TextOutputValue Text
i -> Text -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON Text
i
    BytesOutputValue Base64
i -> Base64 -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON Base64
i
    DateOutputValue Date
i -> Date -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON Date
i
    TimestampOutputValue Timestamp
i -> Timestamp -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON Timestamp
i
    TimeOutputValue Time
i -> Time -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON Time
i
    DatetimeOutputValue Datetime
i -> Datetime -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON Datetime
i
    GeographyOutputValue Geography
i -> Geography -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON Geography
i
    BoolOutputValue Bool
i -> Bool -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON Bool
i
    IntegerOutputValue Int64
i -> Int64 -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON Int64
i
    ArrayOutputValue Vector OutputValue
vector -> Vector OutputValue -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON Vector OutputValue
vector
    RecordOutputValue InsOrdHashMap FieldNameText OutputValue
record -> InsOrdHashMap FieldNameText OutputValue -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON InsOrdHashMap FieldNameText OutputValue
record

data ExecuteReader = ExecuteReader
  { ExecuteReader -> BigQuerySourceConfig
sourceConfig :: BigQuerySourceConfig
  }

data ExecuteProblem
  = GetJobDecodeProblem String
  | CreateQueryJobDecodeProblem String
  | InsertDatasetDecodeProblem String
  | ExecuteRunBigQueryProblem BigQueryProblem
  | RESTRequestNonOK Status Aeson.Value
  deriving ((forall x. ExecuteProblem -> Rep ExecuteProblem x)
-> (forall x. Rep ExecuteProblem x -> ExecuteProblem)
-> Generic ExecuteProblem
forall x. Rep ExecuteProblem x -> ExecuteProblem
forall x. ExecuteProblem -> Rep ExecuteProblem x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep ExecuteProblem x -> ExecuteProblem
$cfrom :: forall x. ExecuteProblem -> Rep ExecuteProblem x
Generic)

-- | We use this to hide certain details from the front-end, while allowing
-- them in tests. We have not actually decided whether showing the details is
-- insecure, but until we decide otherwise, it's probably best to err on the side
-- of caution.
data ShowDetails = HideDetails | InsecurelyShowDetails

instance Aeson.ToJSON ExecuteProblem where
  toJSON :: ExecuteProblem -> Value
toJSON =
    [Pair] -> Value
Aeson.object ([Pair] -> Value)
-> (ExecuteProblem -> [Pair]) -> ExecuteProblem -> Value
forall b c a. (b -> c) -> (a -> b) -> a -> c
. \case
      GetJobDecodeProblem String
err -> [Key
"get_job_decode_problem" Key -> String -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
Aeson..= String
err]
      CreateQueryJobDecodeProblem String
err -> [Key
"create_query_job_decode_problem" Key -> String -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
Aeson..= String
err]
      ExecuteRunBigQueryProblem BigQueryProblem
problem -> [Key
"execute_run_bigquery_problem" Key -> BigQueryProblem -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
Aeson..= BigQueryProblem
problem]
      InsertDatasetDecodeProblem String
problem -> [Key
"insert_dataset__bigquery_problem" Key -> String -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
Aeson..= String
problem]
      RESTRequestNonOK Status
_ Value
resp -> [Key
"rest_request_non_ok" Key -> Value -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
Aeson..= Value
resp]

executeProblemMessage :: ShowDetails -> ExecuteProblem -> Text
executeProblemMessage :: ShowDetails -> ExecuteProblem -> Text
executeProblemMessage ShowDetails
showDetails = \case
  GetJobDecodeProblem String
err -> Text
"Fetching BigQuery job status, cannot decode HTTP response; " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> String -> Text
forall a. Show a => a -> Text
tshow String
err
  CreateQueryJobDecodeProblem String
err -> Text
"Creating BigQuery job, cannot decode HTTP response: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> String -> Text
forall a. Show a => a -> Text
tshow String
err
  ExecuteRunBigQueryProblem BigQueryProblem
_ -> Text
"Cannot execute BigQuery request"
  InsertDatasetDecodeProblem String
_ -> Text
"Cannot create BigQuery dataset"
  RESTRequestNonOK Status
status Value
body ->
    let summary :: Text
summary = Text
"BigQuery HTTP request failed with status " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall a. Show a => a -> Text
tshow (Status -> Int
statusCode Status
status) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> ByteString -> Text
forall a. Show a => a -> Text
tshow (Status -> ByteString
statusMessage Status
status)
     in case ShowDetails
showDetails of
          ShowDetails
HideDetails -> Text
summary
          ShowDetails
InsecurelyShowDetails -> Text
summary Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" and body:\n" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
LT.toStrict (ByteString -> Text
LT.decodeUtf8 (Value -> ByteString
forall a. ToJSON a => a -> ByteString
Aeson.encode Value
body))

-- | Execute monad; as queries are performed, the record sets are
-- stored in the map.
newtype Execute a = Execute
  { Execute a -> ReaderT ExecuteReader (ExceptT ExecuteProblem IO) a
unExecute :: ReaderT ExecuteReader (ExceptT ExecuteProblem IO) a
  }
  deriving
    ( a -> Execute b -> Execute a
(a -> b) -> Execute a -> Execute b
(forall a b. (a -> b) -> Execute a -> Execute b)
-> (forall a b. a -> Execute b -> Execute a) -> Functor Execute
forall a b. a -> Execute b -> Execute a
forall a b. (a -> b) -> Execute a -> Execute b
forall (f :: * -> *).
(forall a b. (a -> b) -> f a -> f b)
-> (forall a b. a -> f b -> f a) -> Functor f
<$ :: a -> Execute b -> Execute a
$c<$ :: forall a b. a -> Execute b -> Execute a
fmap :: (a -> b) -> Execute a -> Execute b
$cfmap :: forall a b. (a -> b) -> Execute a -> Execute b
Functor,
      Functor Execute
a -> Execute a
Functor Execute
-> (forall a. a -> Execute a)
-> (forall a b. Execute (a -> b) -> Execute a -> Execute b)
-> (forall a b c.
    (a -> b -> c) -> Execute a -> Execute b -> Execute c)
-> (forall a b. Execute a -> Execute b -> Execute b)
-> (forall a b. Execute a -> Execute b -> Execute a)
-> Applicative Execute
Execute a -> Execute b -> Execute b
Execute a -> Execute b -> Execute a
Execute (a -> b) -> Execute a -> Execute b
(a -> b -> c) -> Execute a -> Execute b -> Execute c
forall a. a -> Execute a
forall a b. Execute a -> Execute b -> Execute a
forall a b. Execute a -> Execute b -> Execute b
forall a b. Execute (a -> b) -> Execute a -> Execute b
forall a b c. (a -> b -> c) -> Execute a -> Execute b -> Execute c
forall (f :: * -> *).
Functor f
-> (forall a. a -> f a)
-> (forall a b. f (a -> b) -> f a -> f b)
-> (forall a b c. (a -> b -> c) -> f a -> f b -> f c)
-> (forall a b. f a -> f b -> f b)
-> (forall a b. f a -> f b -> f a)
-> Applicative f
<* :: Execute a -> Execute b -> Execute a
$c<* :: forall a b. Execute a -> Execute b -> Execute a
*> :: Execute a -> Execute b -> Execute b
$c*> :: forall a b. Execute a -> Execute b -> Execute b
liftA2 :: (a -> b -> c) -> Execute a -> Execute b -> Execute c
$cliftA2 :: forall a b c. (a -> b -> c) -> Execute a -> Execute b -> Execute c
<*> :: Execute (a -> b) -> Execute a -> Execute b
$c<*> :: forall a b. Execute (a -> b) -> Execute a -> Execute b
pure :: a -> Execute a
$cpure :: forall a. a -> Execute a
$cp1Applicative :: Functor Execute
Applicative,
      Applicative Execute
a -> Execute a
Applicative Execute
-> (forall a b. Execute a -> (a -> Execute b) -> Execute b)
-> (forall a b. Execute a -> Execute b -> Execute b)
-> (forall a. a -> Execute a)
-> Monad Execute
Execute a -> (a -> Execute b) -> Execute b
Execute a -> Execute b -> Execute b
forall a. a -> Execute a
forall a b. Execute a -> Execute b -> Execute b
forall a b. Execute a -> (a -> Execute b) -> Execute b
forall (m :: * -> *).
Applicative m
-> (forall a b. m a -> (a -> m b) -> m b)
-> (forall a b. m a -> m b -> m b)
-> (forall a. a -> m a)
-> Monad m
return :: a -> Execute a
$creturn :: forall a. a -> Execute a
>> :: Execute a -> Execute b -> Execute b
$c>> :: forall a b. Execute a -> Execute b -> Execute b
>>= :: Execute a -> (a -> Execute b) -> Execute b
$c>>= :: forall a b. Execute a -> (a -> Execute b) -> Execute b
$cp1Monad :: Applicative Execute
Monad,
      MonadReader ExecuteReader,
      Monad Execute
Monad Execute -> (forall a. IO a -> Execute a) -> MonadIO Execute
IO a -> Execute a
forall a. IO a -> Execute a
forall (m :: * -> *).
Monad m -> (forall a. IO a -> m a) -> MonadIO m
liftIO :: IO a -> Execute a
$cliftIO :: forall a. IO a -> Execute a
$cp1MonadIO :: Monad Execute
MonadIO,
      MonadError ExecuteProblem
    )

-- | Big query parameters must be accompanied by an explicit type
-- signature.
data BigQueryType
  = DECIMAL
  | INTEGER
  | FLOAT
  | BYTES
  | STRING
  | BOOL
  | ARRAY BigQueryType
  | GEOGRAPHY
  | DATE
  | TIMESTAMP
  | DATETIME
  | TIME
  | BIGDECIMAL
  deriving (Int -> BigQueryType -> ShowS
[BigQueryType] -> ShowS
BigQueryType -> String
(Int -> BigQueryType -> ShowS)
-> (BigQueryType -> String)
-> ([BigQueryType] -> ShowS)
-> Show BigQueryType
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [BigQueryType] -> ShowS
$cshowList :: [BigQueryType] -> ShowS
show :: BigQueryType -> String
$cshow :: BigQueryType -> String
showsPrec :: Int -> BigQueryType -> ShowS
$cshowsPrec :: Int -> BigQueryType -> ShowS
Show, BigQueryType -> BigQueryType -> Bool
(BigQueryType -> BigQueryType -> Bool)
-> (BigQueryType -> BigQueryType -> Bool) -> Eq BigQueryType
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: BigQueryType -> BigQueryType -> Bool
$c/= :: BigQueryType -> BigQueryType -> Bool
== :: BigQueryType -> BigQueryType -> Bool
$c== :: BigQueryType -> BigQueryType -> Bool
Eq)

data BigQuery = BigQuery
  { BigQuery -> Text
query :: LT.Text,
    BigQuery -> InsOrdHashMap ParameterName Parameter
parameters :: InsOrdHashMap ParameterName Parameter
  }
  deriving (Int -> BigQuery -> ShowS
[BigQuery] -> ShowS
BigQuery -> String
(Int -> BigQuery -> ShowS)
-> (BigQuery -> String) -> ([BigQuery] -> ShowS) -> Show BigQuery
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [BigQuery] -> ShowS
$cshowList :: [BigQuery] -> ShowS
show :: BigQuery -> String
$cshow :: BigQuery -> String
showsPrec :: Int -> BigQuery -> ShowS
$cshowsPrec :: Int -> BigQuery -> ShowS
Show)

data Parameter = Parameter
  { Parameter -> BigQueryType
typ :: BigQueryType,
    Parameter -> Value
value :: Value
  }
  deriving (Int -> Parameter -> ShowS
[Parameter] -> ShowS
Parameter -> String
(Int -> Parameter -> ShowS)
-> (Parameter -> String)
-> ([Parameter] -> ShowS)
-> Show Parameter
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Parameter] -> ShowS
$cshowList :: [Parameter] -> ShowS
show :: Parameter -> String
$cshow :: Parameter -> String
showsPrec :: Int -> Parameter -> ShowS
$cshowsPrec :: Int -> Parameter -> ShowS
Show)

newtype ParameterName
  = ParameterName LT.Text
  deriving (Int -> ParameterName -> ShowS
[ParameterName] -> ShowS
ParameterName -> String
(Int -> ParameterName -> ShowS)
-> (ParameterName -> String)
-> ([ParameterName] -> ShowS)
-> Show ParameterName
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [ParameterName] -> ShowS
$cshowList :: [ParameterName] -> ShowS
show :: ParameterName -> String
$cshow :: ParameterName -> String
showsPrec :: Int -> ParameterName -> ShowS
$cshowsPrec :: Int -> ParameterName -> ShowS
Show, [ParameterName] -> Value
[ParameterName] -> Encoding
ParameterName -> Value
ParameterName -> Encoding
(ParameterName -> Value)
-> (ParameterName -> Encoding)
-> ([ParameterName] -> Value)
-> ([ParameterName] -> Encoding)
-> ToJSON ParameterName
forall a.
(a -> Value)
-> (a -> Encoding)
-> ([a] -> Value)
-> ([a] -> Encoding)
-> ToJSON a
toEncodingList :: [ParameterName] -> Encoding
$ctoEncodingList :: [ParameterName] -> Encoding
toJSONList :: [ParameterName] -> Value
$ctoJSONList :: [ParameterName] -> Value
toEncoding :: ParameterName -> Encoding
$ctoEncoding :: ParameterName -> Encoding
toJSON :: ParameterName -> Value
$ctoJSON :: ParameterName -> Value
Aeson.ToJSON, Eq ParameterName
Eq ParameterName
-> (ParameterName -> ParameterName -> Ordering)
-> (ParameterName -> ParameterName -> Bool)
-> (ParameterName -> ParameterName -> Bool)
-> (ParameterName -> ParameterName -> Bool)
-> (ParameterName -> ParameterName -> Bool)
-> (ParameterName -> ParameterName -> ParameterName)
-> (ParameterName -> ParameterName -> ParameterName)
-> Ord ParameterName
ParameterName -> ParameterName -> Bool
ParameterName -> ParameterName -> Ordering
ParameterName -> ParameterName -> ParameterName
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: ParameterName -> ParameterName -> ParameterName
$cmin :: ParameterName -> ParameterName -> ParameterName
max :: ParameterName -> ParameterName -> ParameterName
$cmax :: ParameterName -> ParameterName -> ParameterName
>= :: ParameterName -> ParameterName -> Bool
$c>= :: ParameterName -> ParameterName -> Bool
> :: ParameterName -> ParameterName -> Bool
$c> :: ParameterName -> ParameterName -> Bool
<= :: ParameterName -> ParameterName -> Bool
$c<= :: ParameterName -> ParameterName -> Bool
< :: ParameterName -> ParameterName -> Bool
$c< :: ParameterName -> ParameterName -> Bool
compare :: ParameterName -> ParameterName -> Ordering
$ccompare :: ParameterName -> ParameterName -> Ordering
$cp1Ord :: Eq ParameterName
Ord, ParameterName -> ParameterName -> Bool
(ParameterName -> ParameterName -> Bool)
-> (ParameterName -> ParameterName -> Bool) -> Eq ParameterName
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: ParameterName -> ParameterName -> Bool
$c/= :: ParameterName -> ParameterName -> Bool
== :: ParameterName -> ParameterName -> Bool
$c== :: ParameterName -> ParameterName -> Bool
Eq, Int -> ParameterName -> Int
ParameterName -> Int
(Int -> ParameterName -> Int)
-> (ParameterName -> Int) -> Hashable ParameterName
forall a. (Int -> a -> Int) -> (a -> Int) -> Hashable a
hash :: ParameterName -> Int
$chash :: ParameterName -> Int
hashWithSalt :: Int -> ParameterName -> Int
$chashWithSalt :: Int -> ParameterName -> Int
Hashable)

data BigQueryField = BigQueryField
  { BigQueryField -> FieldNameText
name :: FieldNameText,
    BigQueryField -> BigQueryFieldType
typ :: BigQueryFieldType,
    BigQueryField -> Mode
mode :: Mode
  }
  deriving (Int -> BigQueryField -> ShowS
[BigQueryField] -> ShowS
BigQueryField -> String
(Int -> BigQueryField -> ShowS)
-> (BigQueryField -> String)
-> ([BigQueryField] -> ShowS)
-> Show BigQueryField
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [BigQueryField] -> ShowS
$cshowList :: [BigQueryField] -> ShowS
show :: BigQueryField -> String
$cshow :: BigQueryField -> String
showsPrec :: Int -> BigQueryField -> ShowS
$cshowsPrec :: Int -> BigQueryField -> ShowS
Show)

data BigQueryFieldType
  = FieldSTRING
  | FieldBYTES
  | FieldINTEGER
  | FieldFLOAT
  | FieldBOOL
  | FieldTIMESTAMP
  | FieldDATE
  | FieldTIME
  | FieldDATETIME
  | FieldGEOGRAPHY
  | FieldDECIMAL
  | FieldBIGDECIMAL
  | FieldSTRUCT (Vector BigQueryField)
  deriving (Int -> BigQueryFieldType -> ShowS
[BigQueryFieldType] -> ShowS
BigQueryFieldType -> String
(Int -> BigQueryFieldType -> ShowS)
-> (BigQueryFieldType -> String)
-> ([BigQueryFieldType] -> ShowS)
-> Show BigQueryFieldType
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [BigQueryFieldType] -> ShowS
$cshowList :: [BigQueryFieldType] -> ShowS
show :: BigQueryFieldType -> String
$cshow :: BigQueryFieldType -> String
showsPrec :: Int -> BigQueryFieldType -> ShowS
$cshowsPrec :: Int -> BigQueryFieldType -> ShowS
Show)

data Mode
  = Nullable
  | NotNullable
  | Repeated
  deriving (Int -> Mode -> ShowS
[Mode] -> ShowS
Mode -> String
(Int -> Mode -> ShowS)
-> (Mode -> String) -> ([Mode] -> ShowS) -> Show Mode
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Mode] -> ShowS
$cshowList :: [Mode] -> ShowS
show :: Mode -> String
$cshow :: Mode -> String
showsPrec :: Int -> Mode -> ShowS
$cshowsPrec :: Int -> Mode -> ShowS
Show)

data IsNullable
  = IsNullable
  | IsRequired

--------------------------------------------------------------------------------
-- Constants

-- | Delay between attempts to get job results if the job is incomplete.
streamDelaySeconds :: DiffTime
streamDelaySeconds :: DiffTime
streamDelaySeconds = DiffTime
1

bigQueryProjectUrl :: Text -> String
bigQueryProjectUrl :: Text -> String
bigQueryProjectUrl Text
projectId =
  String
"https://bigquery.googleapis.com/bigquery/v2/projects/" String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Text -> String
T.unpack Text
projectId

--------------------------------------------------------------------------------
-- Executing the planned actions forest

runExecute ::
  MonadIO m =>
  BigQuerySourceConfig ->
  Execute RecordSet ->
  m (Either ExecuteProblem RecordSet)
runExecute :: BigQuerySourceConfig
-> Execute RecordSet -> m (Either ExecuteProblem RecordSet)
runExecute BigQuerySourceConfig
sourceConfig Execute RecordSet
m =
  IO (Either ExecuteProblem RecordSet)
-> m (Either ExecuteProblem RecordSet)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO
    ( ExceptT ExecuteProblem IO RecordSet
-> IO (Either ExecuteProblem RecordSet)
forall e (m :: * -> *) a. ExceptT e m a -> m (Either e a)
runExceptT
        ( ReaderT ExecuteReader (ExceptT ExecuteProblem IO) RecordSet
-> ExecuteReader -> ExceptT ExecuteProblem IO RecordSet
forall r (m :: * -> *) a. ReaderT r m a -> r -> m a
runReaderT
            (Execute RecordSet
-> ReaderT ExecuteReader (ExceptT ExecuteProblem IO) RecordSet
forall a.
Execute a -> ReaderT ExecuteReader (ExceptT ExecuteProblem IO) a
unExecute (Execute RecordSet
m Execute RecordSet
-> (RecordSet -> Execute RecordSet) -> Execute RecordSet
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= RecordSet -> Execute RecordSet
getFinalRecordSet))
            (ExecuteReader :: BigQuerySourceConfig -> ExecuteReader
ExecuteReader {BigQuerySourceConfig
sourceConfig :: BigQuerySourceConfig
$sel:sourceConfig:ExecuteReader :: BigQuerySourceConfig
sourceConfig})
        )
    )

executeSelect :: Select -> Execute RecordSet
executeSelect :: Select -> Execute RecordSet
executeSelect Select
select = do
  BigQueryConnection
conn <- (ExecuteReader -> BigQueryConnection) -> Execute BigQueryConnection
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks (BigQuerySourceConfig -> BigQueryConnection
_scConnection (BigQuerySourceConfig -> BigQueryConnection)
-> (ExecuteReader -> BigQuerySourceConfig)
-> ExecuteReader
-> BigQueryConnection
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ExecuteReader -> BigQuerySourceConfig
sourceConfig)
  RecordSet
recordSet <-
    BigQueryConnection
-> BigQuery -> Execute (Either ExecuteProblem RecordSet)
forall (m :: * -> *).
MonadIO m =>
BigQueryConnection
-> BigQuery -> m (Either ExecuteProblem RecordSet)
streamBigQuery BigQueryConnection
conn (Select -> BigQuery
selectToBigQuery Select
select) Execute (Either ExecuteProblem RecordSet)
-> (Either ExecuteProblem RecordSet -> Execute RecordSet)
-> Execute RecordSet
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Either ExecuteProblem RecordSet -> Execute RecordSet
forall e (m :: * -> *) a. MonadError e m => Either e a -> m a
liftEither
  RecordSet -> Execute RecordSet
forall (f :: * -> *) a. Applicative f => a -> f a
pure RecordSet
recordSet {$sel:wantedFields:RecordSet :: Maybe [Text]
wantedFields = Select -> Maybe [Text]
selectFinalWantedFields Select
select}

-- | This is needed to strip out unneeded fields (join keys) in the
-- final query.  This is a relic of the data loader approach. A later
-- improvement would be to update the FromIr code to explicitly
-- reselect the query. But the purpose of this commit is to drop the
-- dataloader code and not modify the from IR code which is more
-- delicate.
getFinalRecordSet :: RecordSet -> Execute RecordSet
getFinalRecordSet :: RecordSet -> Execute RecordSet
getFinalRecordSet RecordSet
recordSet =
  RecordSet -> Execute RecordSet
forall (f :: * -> *) a. Applicative f => a -> f a
pure
    RecordSet
recordSet
      { $sel:rows:RecordSet :: Vector (InsOrdHashMap FieldNameText OutputValue)
rows =
          (InsOrdHashMap FieldNameText OutputValue
 -> InsOrdHashMap FieldNameText OutputValue)
-> Vector (InsOrdHashMap FieldNameText OutputValue)
-> Vector (InsOrdHashMap FieldNameText OutputValue)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap
            ( (FieldNameText -> OutputValue -> Bool)
-> InsOrdHashMap FieldNameText OutputValue
-> InsOrdHashMap FieldNameText OutputValue
forall k v.
(k -> v -> Bool) -> InsOrdHashMap k v -> InsOrdHashMap k v
OMap.filterWithKey
                ( \(FieldNameText Text
k) OutputValue
_ ->
                    Bool -> ([Text] -> Bool) -> Maybe [Text] -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
True (Text -> [Text] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem Text
k) (RecordSet -> Maybe [Text]
wantedFields RecordSet
recordSet)
                )
            )
            (RecordSet -> Vector (InsOrdHashMap FieldNameText OutputValue)
rows RecordSet
recordSet)
      }

--------------------------------------------------------------------------------
-- Make a big query from a select

selectToBigQuery :: Select -> BigQuery
selectToBigQuery :: Select -> BigQuery
selectToBigQuery Select
select =
  BigQuery :: Text -> InsOrdHashMap ParameterName Parameter -> BigQuery
BigQuery
    { $sel:query:BigQuery :: Text
query = Builder -> Text
LT.toLazyText Builder
query,
      $sel:parameters:BigQuery :: InsOrdHashMap ParameterName Parameter
parameters =
        [(ParameterName, Parameter)]
-> InsOrdHashMap ParameterName Parameter
forall k v. (Eq k, Hashable k) => [(k, v)] -> InsOrdHashMap k v
OMap.fromList
          ( ((Int, Value) -> (ParameterName, Parameter))
-> [(Int, Value)] -> [(ParameterName, Parameter)]
forall a b. (a -> b) -> [a] -> [b]
map
              ( \(Int
int, Value
value) ->
                  ( Text -> ParameterName
ParameterName (Builder -> Text
LT.toLazyText (Int -> Builder
ToQuery.paramName Int
int)),
                    Parameter :: BigQueryType -> Value -> Parameter
Parameter {$sel:typ:Parameter :: BigQueryType
typ = Value -> BigQueryType
valueType Value
value, Value
value :: Value
$sel:value:Parameter :: Value
value}
                  )
              )
              (InsOrdHashMap Int Value -> [(Int, Value)]
forall k v. InsOrdHashMap k v -> [(k, v)]
OMap.toList InsOrdHashMap Int Value
params)
          )
    }
  where
    (Builder
query, InsOrdHashMap Int Value
params) =
      Printer -> (Builder, InsOrdHashMap Int Value)
ToQuery.renderBuilderPretty (Select -> Printer
ToQuery.fromSelect Select
select)

--------------------------------------------------------------------------------
-- Type system

-- | Make a BigQuery type for the given value.
valueType :: Value -> BigQueryType
valueType :: Value -> BigQueryType
valueType =
  \case
    DecimalValue {} -> BigQueryType
DECIMAL
    BigDecimalValue {} -> BigQueryType
BIGDECIMAL
    IntegerValue {} -> BigQueryType
INTEGER
    FloatValue {} -> BigQueryType
FLOAT
    GeographyValue {} -> BigQueryType
GEOGRAPHY
    StringValue {} -> BigQueryType
STRING
    BytesValue {} -> BigQueryType
BYTES
    BoolValue {} -> BigQueryType
BOOL
    DatetimeValue {} -> BigQueryType
DATETIME
    TimeValue {} -> BigQueryType
TIME
    DateValue {} -> BigQueryType
DATE
    TimestampValue {} -> BigQueryType
TIMESTAMP
    ArrayValue Vector Value
values ->
      BigQueryType -> BigQueryType
ARRAY
        ( BigQueryType
-> (Value -> BigQueryType) -> Maybe Value -> BigQueryType
forall b a. b -> (a -> b) -> Maybe a -> b
maybe
            BigQueryType
STRING
            -- Above: If the array is null, it doesn't matter what type
            -- the element is. So we put STRING.
            Value -> BigQueryType
valueType
            (Vector Value
values Vector Value -> Int -> Maybe Value
forall a. Vector a -> Int -> Maybe a
V.!? Int
0)
            -- Above: We base the type from the first element. Later,
            -- we could add some kind of sanity check that they are all
            -- the same type.
        )
    Value
NullValue -> BigQueryType
STRING

-- Above: If the value is null, it doesn't matter what type
-- the element is. So we put STRING.

--------------------------------------------------------------------------------
-- JSON serialization

-- | Make a JSON representation of the type of the given value.
valueToBigQueryJson :: Value -> Aeson.Value
valueToBigQueryJson :: Value -> Value
valueToBigQueryJson = Value -> Value
go
  where
    go :: Value -> Value
go =
      \case
        Value
NullValue -> Value
Aeson.Null -- TODO: I haven't tested whether BigQuery is happy with this null value.
        DecimalValue Decimal
i -> [Pair] -> Value
Aeson.object [Key
"value" Key -> Decimal -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Decimal
i]
        BigDecimalValue BigDecimal
i -> [Pair] -> Value
Aeson.object [Key
"value" Key -> BigDecimal -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= BigDecimal
i]
        IntegerValue Int64
i -> [Pair] -> Value
Aeson.object [Key
"value" Key -> Int64 -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Int64
i]
        FloatValue Float64
i -> [Pair] -> Value
Aeson.object [Key
"value" Key -> Float64 -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Float64
i]
        TimestampValue Timestamp
i -> [Pair] -> Value
Aeson.object [Key
"value" Key -> Timestamp -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Timestamp
i]
        DateValue (Date Text
i) -> [Pair] -> Value
Aeson.object [Key
"value" Key -> Text -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Text
i]
        TimeValue (Time Text
i) -> [Pair] -> Value
Aeson.object [Key
"value" Key -> Text -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Text
i]
        DatetimeValue (Datetime Text
i) -> [Pair] -> Value
Aeson.object [Key
"value" Key -> Text -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Text
i]
        GeographyValue (Geography Text
i) -> [Pair] -> Value
Aeson.object [Key
"value" Key -> Text -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Text
i]
        StringValue Text
i -> [Pair] -> Value
Aeson.object [Key
"value" Key -> Value -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Text -> Value
Aeson.String Text
i]
        BytesValue Base64
i -> [Pair] -> Value
Aeson.object [Key
"value" Key -> Base64 -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Base64
i]
        BoolValue Bool
i ->
          [Pair] -> Value
Aeson.object
            [ Key
"value"
                Key -> Value -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Text -> Value
Aeson.String
                  ( if Bool
i
                      then Text
"true"
                      else Text
"false"
                  )
            ]
        ArrayValue Vector Value
vs ->
          [Pair] -> Value
Aeson.object [Key
"array_values" Key -> Value -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Array -> Value
Aeson.Array ((Value -> Value) -> Vector Value -> Array
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Value -> Value
go Vector Value
vs)]

--------------------------------------------------------------------------------
-- Execute a query as a job and stream the results into a record set

-- | TODO: WARNING: This function hasn't been tested on Big Data(tm),
-- and therefore I was unable to get BigQuery to produce paginated
-- results that would contain the 'pageToken' field in the JSON
-- response. Until that test has been done, we should consider this a
-- preliminary implementation.
streamBigQuery ::
  (MonadIO m) => BigQueryConnection -> BigQuery -> m (Either ExecuteProblem RecordSet)
streamBigQuery :: BigQueryConnection
-> BigQuery -> m (Either ExecuteProblem RecordSet)
streamBigQuery BigQueryConnection
conn BigQuery
bigquery = do
  Either ExecuteProblem Job
jobResult <- ExceptT ExecuteProblem m Job -> m (Either ExecuteProblem Job)
forall e (m :: * -> *) a. ExceptT e m a -> m (Either e a)
runExceptT (ExceptT ExecuteProblem m Job -> m (Either ExecuteProblem Job))
-> ExceptT ExecuteProblem m Job -> m (Either ExecuteProblem Job)
forall a b. (a -> b) -> a -> b
$ BigQueryConnection -> BigQuery -> ExceptT ExecuteProblem m Job
forall (m :: * -> *).
(MonadError ExecuteProblem m, MonadIO m) =>
BigQueryConnection -> BigQuery -> m Job
createQueryJob BigQueryConnection
conn BigQuery
bigquery
  case Either ExecuteProblem Job
jobResult of
    Right Job
job -> Maybe Text
-> Maybe RecordSet -> m (Either ExecuteProblem RecordSet)
loop Maybe Text
forall a. Maybe a
Nothing Maybe RecordSet
forall a. Maybe a
Nothing
      where
        loop :: Maybe Text
-> Maybe RecordSet -> m (Either ExecuteProblem RecordSet)
loop Maybe Text
pageToken Maybe RecordSet
mrecordSet = do
          Either ExecuteProblem JobResultsResponse
results <- BigQueryConnection
-> Job -> Fetch -> m (Either ExecuteProblem JobResultsResponse)
forall (m :: * -> *).
MonadIO m =>
BigQueryConnection
-> Job -> Fetch -> m (Either ExecuteProblem JobResultsResponse)
getJobResults BigQueryConnection
conn Job
job Fetch :: Maybe Text -> Fetch
Fetch {Maybe Text
$sel:pageToken:Fetch :: Maybe Text
pageToken :: Maybe Text
pageToken}
          case Either ExecuteProblem JobResultsResponse
results of
            Left ExecuteProblem
problem -> Either ExecuteProblem RecordSet
-> m (Either ExecuteProblem RecordSet)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (ExecuteProblem -> Either ExecuteProblem RecordSet
forall a b. a -> Either a b
Left ExecuteProblem
problem)
            Right
              ( JobComplete
                  JobResults
                    { $sel:pageToken:JobResults :: JobResults -> Maybe Text
pageToken = Maybe Text
mpageToken',
                      $sel:recordSet:JobResults :: JobResults -> RecordSet
recordSet = recordSet' :: RecordSet
recordSet'@RecordSet {$sel:rows:RecordSet :: RecordSet -> Vector (InsOrdHashMap FieldNameText OutputValue)
rows = Vector (InsOrdHashMap FieldNameText OutputValue)
rows'}
                    }
                ) -> do
                let extendedRecordSet :: RecordSet
extendedRecordSet =
                      case Maybe RecordSet
mrecordSet of
                        Maybe RecordSet
Nothing -> RecordSet
recordSet'
                        Just recordSet :: RecordSet
recordSet@RecordSet {Vector (InsOrdHashMap FieldNameText OutputValue)
rows :: Vector (InsOrdHashMap FieldNameText OutputValue)
$sel:rows:RecordSet :: RecordSet -> Vector (InsOrdHashMap FieldNameText OutputValue)
rows} ->
                          (RecordSet
recordSet {$sel:rows:RecordSet :: Vector (InsOrdHashMap FieldNameText OutputValue)
rows = Vector (InsOrdHashMap FieldNameText OutputValue)
rows Vector (InsOrdHashMap FieldNameText OutputValue)
-> Vector (InsOrdHashMap FieldNameText OutputValue)
-> Vector (InsOrdHashMap FieldNameText OutputValue)
forall a. Semigroup a => a -> a -> a
<> Vector (InsOrdHashMap FieldNameText OutputValue)
rows'})
                case Maybe Text
mpageToken' of
                  Maybe Text
Nothing -> Either ExecuteProblem RecordSet
-> m (Either ExecuteProblem RecordSet)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (RecordSet -> Either ExecuteProblem RecordSet
forall a b. b -> Either a b
Right RecordSet
extendedRecordSet)
                  Just Text
pageToken' ->
                    Maybe Text
-> Maybe RecordSet -> m (Either ExecuteProblem RecordSet)
loop (Text -> Maybe Text
forall (f :: * -> *) a. Applicative f => a -> f a
pure Text
pageToken') (RecordSet -> Maybe RecordSet
forall (f :: * -> *) a. Applicative f => a -> f a
pure RecordSet
extendedRecordSet)
            Right JobIncomplete {} -> do
              IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (DiffTime -> IO ()
sleep DiffTime
streamDelaySeconds)
              Maybe Text
-> Maybe RecordSet -> m (Either ExecuteProblem RecordSet)
loop Maybe Text
pageToken Maybe RecordSet
mrecordSet
    Left ExecuteProblem
e -> Either ExecuteProblem RecordSet
-> m (Either ExecuteProblem RecordSet)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (ExecuteProblem -> Either ExecuteProblem RecordSet
forall a b. a -> Either a b
Left ExecuteProblem
e)

-- | Execute a query without expecting any output (e.g. CREATE TABLE or INSERT)
executeBigQuery :: MonadIO m => BigQueryConnection -> BigQuery -> m (Either ExecuteProblem ())
executeBigQuery :: BigQueryConnection -> BigQuery -> m (Either ExecuteProblem ())
executeBigQuery BigQueryConnection
conn BigQuery
bigquery = do
  Either ExecuteProblem Job
jobResult <- ExceptT ExecuteProblem m Job -> m (Either ExecuteProblem Job)
forall e (m :: * -> *) a. ExceptT e m a -> m (Either e a)
runExceptT (ExceptT ExecuteProblem m Job -> m (Either ExecuteProblem Job))
-> ExceptT ExecuteProblem m Job -> m (Either ExecuteProblem Job)
forall a b. (a -> b) -> a -> b
$ BigQueryConnection -> BigQuery -> ExceptT ExecuteProblem m Job
forall (m :: * -> *).
(MonadError ExecuteProblem m, MonadIO m) =>
BigQueryConnection -> BigQuery -> m Job
createQueryJob BigQueryConnection
conn BigQuery
bigquery
  case Either ExecuteProblem Job
jobResult of
    Right Job
job -> Maybe Any -> m (Either ExecuteProblem ())
loop Maybe Any
forall a. Maybe a
Nothing
      where
        loop :: Maybe Any -> m (Either ExecuteProblem ())
loop Maybe Any
mrecordSet = do
          Either ExecuteProblem JobResultsResponse
results <- BigQueryConnection
-> Job -> Fetch -> m (Either ExecuteProblem JobResultsResponse)
forall (m :: * -> *).
MonadIO m =>
BigQueryConnection
-> Job -> Fetch -> m (Either ExecuteProblem JobResultsResponse)
getJobResults BigQueryConnection
conn Job
job Fetch :: Maybe Text -> Fetch
Fetch {$sel:pageToken:Fetch :: Maybe Text
pageToken = Maybe Text
forall a. Maybe a
Nothing}
          case Either ExecuteProblem JobResultsResponse
results of
            Left ExecuteProblem
problem -> Either ExecuteProblem () -> m (Either ExecuteProblem ())
forall (f :: * -> *) a. Applicative f => a -> f a
pure (ExecuteProblem -> Either ExecuteProblem ()
forall a b. a -> Either a b
Left ExecuteProblem
problem)
            Right (JobComplete JobResults
_) -> Either ExecuteProblem () -> m (Either ExecuteProblem ())
forall (f :: * -> *) a. Applicative f => a -> f a
pure (() -> Either ExecuteProblem ()
forall a b. b -> Either a b
Right ())
            Right JobIncomplete {} -> do
              IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (DiffTime -> IO ()
sleep DiffTime
streamDelaySeconds)
              Maybe Any -> m (Either ExecuteProblem ())
loop Maybe Any
mrecordSet
    Left ExecuteProblem
e -> Either ExecuteProblem () -> m (Either ExecuteProblem ())
forall (f :: * -> *) a. Applicative f => a -> f a
pure (ExecuteProblem -> Either ExecuteProblem ()
forall a b. a -> Either a b
Left ExecuteProblem
e)

--------------------------------------------------------------------------------
-- Querying results from a job

data JobResults = JobResults
  { JobResults -> Maybe Text
pageToken :: Maybe Text,
    JobResults -> RecordSet
recordSet :: RecordSet
  }
  deriving (Int -> JobResults -> ShowS
[JobResults] -> ShowS
JobResults -> String
(Int -> JobResults -> ShowS)
-> (JobResults -> String)
-> ([JobResults] -> ShowS)
-> Show JobResults
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [JobResults] -> ShowS
$cshowList :: [JobResults] -> ShowS
show :: JobResults -> String
$cshow :: JobResults -> String
showsPrec :: Int -> JobResults -> ShowS
$cshowsPrec :: Int -> JobResults -> ShowS
Show)

instance Aeson.FromJSON JobResults where
  parseJSON :: Value -> Parser JobResults
parseJSON =
    String
-> (Object -> Parser JobResults) -> Value -> Parser JobResults
forall a. String -> (Object -> Parser a) -> Value -> Parser a
Aeson.withObject
      String
"JobResults"
      ( \Object
o -> do
          RecordSet
recordSet <- Object -> Parser RecordSet
parseRecordSetPayload Object
o
          Maybe Text
pageToken <-
            (Maybe Text -> Maybe Text)
-> Parser (Maybe Text) -> Parser (Maybe Text)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap
              ( \Maybe Text
mtoken -> do
                  Text
token <- Maybe Text
mtoken
                  Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Bool
not (Text -> Bool
T.null Text
token))
                  Text -> Maybe Text
forall (f :: * -> *) a. Applicative f => a -> f a
pure Text
token
              )
              (Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"pageToken")
          JobResults -> Parser JobResults
forall (f :: * -> *) a. Applicative f => a -> f a
pure JobResults :: Maybe Text -> RecordSet -> JobResults
JobResults {Maybe Text
RecordSet
pageToken :: Maybe Text
recordSet :: RecordSet
$sel:recordSet:JobResults :: RecordSet
$sel:pageToken:JobResults :: Maybe Text
..}
      )

data JobResultsResponse
  = JobIncomplete
  | JobComplete JobResults
  deriving (Int -> JobResultsResponse -> ShowS
[JobResultsResponse] -> ShowS
JobResultsResponse -> String
(Int -> JobResultsResponse -> ShowS)
-> (JobResultsResponse -> String)
-> ([JobResultsResponse] -> ShowS)
-> Show JobResultsResponse
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [JobResultsResponse] -> ShowS
$cshowList :: [JobResultsResponse] -> ShowS
show :: JobResultsResponse -> String
$cshow :: JobResultsResponse -> String
showsPrec :: Int -> JobResultsResponse -> ShowS
$cshowsPrec :: Int -> JobResultsResponse -> ShowS
Show)

instance Aeson.FromJSON JobResultsResponse where
  parseJSON :: Value -> Parser JobResultsResponse
parseJSON Value
j =
    String
-> (Object -> Parser JobResultsResponse)
-> Value
-> Parser JobResultsResponse
forall a. String -> (Object -> Parser a) -> Value -> Parser a
Aeson.withObject
      String
"JobResultsResponse"
      ( \Object
o -> do
          Text
kind <- Object
o Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"kind"
          if Text
kind Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== (Text
"bigquery#getQueryResultsResponse" :: Text)
            then do
              Bool
complete <- Object
o Object -> Key -> Parser Bool
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"jobComplete"
              if Bool
complete
                then (JobResults -> JobResultsResponse)
-> Parser JobResults -> Parser JobResultsResponse
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap JobResults -> JobResultsResponse
JobComplete (Value -> Parser JobResults
forall a. FromJSON a => Value -> Parser a
Aeson.parseJSON Value
j)
                else JobResultsResponse -> Parser JobResultsResponse
forall (f :: * -> *) a. Applicative f => a -> f a
pure JobResultsResponse
JobIncomplete
            else String -> Parser JobResultsResponse
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String
"Invalid kind: " String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Text -> String
forall a. Show a => a -> String
show Text
kind)
      )
      Value
j

data Fetch = Fetch
  { Fetch -> Maybe Text
pageToken :: Maybe Text
  }
  deriving (Int -> Fetch -> ShowS
[Fetch] -> ShowS
Fetch -> String
(Int -> Fetch -> ShowS)
-> (Fetch -> String) -> ([Fetch] -> ShowS) -> Show Fetch
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Fetch] -> ShowS
$cshowList :: [Fetch] -> ShowS
show :: Fetch -> String
$cshow :: Fetch -> String
showsPrec :: Int -> Fetch -> ShowS
$cshowsPrec :: Int -> Fetch -> ShowS
Show)

-- | Get results of a job.
getJobResults ::
  (MonadIO m) =>
  BigQueryConnection ->
  Job ->
  Fetch ->
  m (Either ExecuteProblem JobResultsResponse)
getJobResults :: BigQueryConnection
-> Job -> Fetch -> m (Either ExecuteProblem JobResultsResponse)
getJobResults BigQueryConnection
conn Job {Text
$sel:jobId:Job :: Job -> Text
jobId :: Text
jobId, Text
$sel:location:Job :: Job -> Text
location :: Text
location} Fetch {Maybe Text
pageToken :: Maybe Text
$sel:pageToken:Fetch :: Fetch -> Maybe Text
pageToken} = ExceptT ExecuteProblem m JobResultsResponse
-> m (Either ExecuteProblem JobResultsResponse)
forall e (m :: * -> *) a. ExceptT e m a -> m (Either e a)
runExceptT (ExceptT ExecuteProblem m JobResultsResponse
 -> m (Either ExecuteProblem JobResultsResponse))
-> ExceptT ExecuteProblem m JobResultsResponse
-> m (Either ExecuteProblem JobResultsResponse)
forall a b. (a -> b) -> a -> b
$ do
  -- https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/get#query-parameters
  let url :: String
url =
        String
"GET " String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Text -> String
bigQueryProjectUrl (BigQueryConnection -> Text
_bqProjectId BigQueryConnection
conn)
          String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
"/queries/"
          String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Text -> String
T.unpack Text
jobId
          String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
"?alt=json&prettyPrint=false"
          String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
"&location="
          String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Text -> String
T.unpack Text
location
          String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
"&"
          String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Text -> String
T.unpack ([(Text, Text)] -> Text
encodeParams [(Text, Text)]
extraParameters)

      req :: Request
req =
        Request -> Request
jsonRequestHeader (String -> Request
parseRequest_ String
url)

      extraParameters :: [(Text, Text)]
extraParameters = [(Text, Text)]
pageTokenParam
        where
          pageTokenParam :: [(Text, Text)]
pageTokenParam =
            case Maybe Text
pageToken of
              Maybe Text
Nothing -> []
              Just Text
token -> [(Text
"pageToken", Text
token)]

      encodeParams :: [(Text, Text)] -> Text
encodeParams = Text -> [Text] -> Text
T.intercalate Text
"&" ([Text] -> Text)
-> ([(Text, Text)] -> [Text]) -> [(Text, Text)] -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Text, Text) -> Text) -> [(Text, Text)] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (\(Text
k, Text
v) -> Text
k Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"=" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
v)

  Response ByteString
resp <- BigQueryConnection
-> Request -> ExceptT ExecuteProblem m (Response ByteString)
forall (m :: * -> *).
(MonadError ExecuteProblem m, MonadIO m) =>
BigQueryConnection -> Request -> m (Response ByteString)
runBigQueryExcept BigQueryConnection
conn Request
req
  case Response ByteString -> Int
forall a. Response a -> Int
getResponseStatusCode Response ByteString
resp of
    Int
200 ->
      ByteString -> Either String JobResultsResponse
forall a. FromJSON a => ByteString -> Either String a
Aeson.eitherDecode (Response ByteString -> ByteString
forall a. Response a -> a
getResponseBody Response ByteString
resp)
        Either String JobResultsResponse
-> (String -> ExceptT ExecuteProblem m JobResultsResponse)
-> ExceptT ExecuteProblem m JobResultsResponse
forall (m :: * -> *) e a.
Applicative m =>
Either e a -> (e -> m a) -> m a
`onLeft` (ExecuteProblem -> ExceptT ExecuteProblem m JobResultsResponse
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (ExecuteProblem -> ExceptT ExecuteProblem m JobResultsResponse)
-> (String -> ExecuteProblem)
-> String
-> ExceptT ExecuteProblem m JobResultsResponse
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> ExecuteProblem
GetJobDecodeProblem)
    Int
_ ->
      ExecuteProblem -> ExceptT ExecuteProblem m JobResultsResponse
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (ExecuteProblem -> ExceptT ExecuteProblem m JobResultsResponse)
-> ExecuteProblem -> ExceptT ExecuteProblem m JobResultsResponse
forall a b. (a -> b) -> a -> b
$
        Status -> Value -> ExecuteProblem
RESTRequestNonOK
          (Response ByteString -> Status
forall a. Response a -> Status
getResponseStatus Response ByteString
resp)
          (Value -> ExecuteProblem) -> Value -> ExecuteProblem
forall a b. (a -> b) -> a -> b
$ ByteString -> Value
parseAsJsonOrText (ByteString -> Value) -> ByteString -> Value
forall a b. (a -> b) -> a -> b
$ Response ByteString -> ByteString
forall a. Response a -> a
getResponseBody Response ByteString
resp

--------------------------------------------------------------------------------
-- Creating jobs

data Job = Job
  { Job -> Text
state :: Text,
    Job -> Text
jobId :: Text,
    Job -> Text
location :: Text
  }
  deriving (Int -> Job -> ShowS
[Job] -> ShowS
Job -> String
(Int -> Job -> ShowS)
-> (Job -> String) -> ([Job] -> ShowS) -> Show Job
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Job] -> ShowS
$cshowList :: [Job] -> ShowS
show :: Job -> String
$cshow :: Job -> String
showsPrec :: Int -> Job -> ShowS
$cshowsPrec :: Int -> Job -> ShowS
Show)

instance Aeson.FromJSON Job where
  parseJSON :: Value -> Parser Job
parseJSON =
    String -> (Object -> Parser Job) -> Value -> Parser Job
forall a. String -> (Object -> Parser a) -> Value -> Parser a
Aeson.withObject
      String
"Job"
      ( \Object
o -> do
          Text
kind <- Object
o Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"kind"
          if Text
kind Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== (Text
"bigquery#job" :: Text)
            then do
              Text
state <- do
                Object
status <- Object
o Object -> Key -> Parser Object
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"status"
                Object
status Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"state"
              (Text
jobId, Text
location) <- do
                Object
ref <- Object
o Object -> Key -> Parser Object
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"jobReference"
                -- 'location' is needed in addition to 'jobId' to query a job's
                -- status
                (,) (Text -> Text -> (Text, Text))
-> Parser Text -> Parser (Text -> (Text, Text))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
ref Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"jobId" Parser (Text -> (Text, Text)) -> Parser Text -> Parser (Text, Text)
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
ref Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"location"
              Job -> Parser Job
forall (f :: * -> *) a. Applicative f => a -> f a
pure Job :: Text -> Text -> Text -> Job
Job {Text
state :: Text
$sel:state:Job :: Text
state, Text
jobId :: Text
$sel:jobId:Job :: Text
jobId, Text
location :: Text
$sel:location:Job :: Text
location}
            else String -> Parser Job
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String
"Invalid kind: " String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Text -> String
forall a. Show a => a -> String
show Text
kind)
      )

-- | Make a Request return `JSON`
jsonRequestHeader :: Request -> Request
jsonRequestHeader :: Request -> Request
jsonRequestHeader =
  HeaderName -> [ByteString] -> Request -> Request
setRequestHeader HeaderName
"Content-Type" [ByteString
"application/json"]

-- | Create a job asynchronously.
createQueryJob :: (MonadError ExecuteProblem m, MonadIO m) => BigQueryConnection -> BigQuery -> m Job
createQueryJob :: BigQueryConnection -> BigQuery -> m Job
createQueryJob BigQueryConnection
conn BigQuery {Text
InsOrdHashMap ParameterName Parameter
parameters :: InsOrdHashMap ParameterName Parameter
query :: Text
$sel:parameters:BigQuery :: BigQuery -> InsOrdHashMap ParameterName Parameter
$sel:query:BigQuery :: BigQuery -> Text
..} = do
  let url :: String
url =
        String
"POST " String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Text -> String
bigQueryProjectUrl (BigQueryConnection -> Text
_bqProjectId BigQueryConnection
conn)
          String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
"/jobs?alt=json&prettyPrint=false"

      req :: Request
req =
        Request -> Request
jsonRequestHeader (Request -> Request) -> Request -> Request
forall a b. (a -> b) -> a -> b
$
          ByteString -> Request -> Request
setRequestBodyLBS ByteString
body (Request -> Request) -> Request -> Request
forall a b. (a -> b) -> a -> b
$
            String -> Request
parseRequest_ String
url

      body :: ByteString
body =
        Value -> ByteString
forall a. ToJSON a => a -> ByteString
Aeson.encode
          ( [Pair] -> Value
Aeson.object
              [ Key
"configuration"
                  Key -> Value -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= [Pair] -> Value
Aeson.object
                    [ Key
"jobType" Key -> String -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= String
"QUERY",
                      Key
"query"
                        Key -> Value -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= [Pair] -> Value
Aeson.object
                          [ Key
"query" Key -> Text -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Text
query,
                            Key
"useLegacySql" Key -> Bool -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Bool
False, -- Important, it makes `quotes` work properly.
                            Key
"parameterMode" Key -> String -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= String
"NAMED",
                            Key
"queryParameters"
                              Key -> [Value] -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= ((ParameterName, Parameter) -> Value)
-> [(ParameterName, Parameter)] -> [Value]
forall a b. (a -> b) -> [a] -> [b]
map
                                ( \(ParameterName
name, Parameter {Value
BigQueryType
value :: Value
typ :: BigQueryType
$sel:value:Parameter :: Parameter -> Value
$sel:typ:Parameter :: Parameter -> BigQueryType
..}) ->
                                    [Pair] -> Value
Aeson.object
                                      [ Key
"name" Key -> Value -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= ParameterName -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON ParameterName
name,
                                        Key
"parameterType" Key -> Value -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= BigQueryType -> Value
forall a. ToJSON a => a -> Value
Aeson.toJSON BigQueryType
typ,
                                        Key
"parameterValue" Key -> Value -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Value -> Value
valueToBigQueryJson Value
value
                                      ]
                                )
                                (InsOrdHashMap ParameterName Parameter
-> [(ParameterName, Parameter)]
forall k v. InsOrdHashMap k v -> [(k, v)]
OMap.toList InsOrdHashMap ParameterName Parameter
parameters)
                          ]
                    ]
              ]
          )

  Response ByteString
resp <- BigQueryConnection -> Request -> m (Response ByteString)
forall (m :: * -> *).
(MonadError ExecuteProblem m, MonadIO m) =>
BigQueryConnection -> Request -> m (Response ByteString)
runBigQueryExcept BigQueryConnection
conn Request
req
  case Response ByteString -> Int
forall a. Response a -> Int
getResponseStatusCode Response ByteString
resp of
    Int
200 ->
      ByteString -> Either String Job
forall a. FromJSON a => ByteString -> Either String a
Aeson.eitherDecode (Response ByteString -> ByteString
forall a. Response a -> a
getResponseBody Response ByteString
resp)
        Either String Job -> (String -> m Job) -> m Job
forall (m :: * -> *) e a.
Applicative m =>
Either e a -> (e -> m a) -> m a
`onLeft` (ExecuteProblem -> m Job
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (ExecuteProblem -> m Job)
-> (String -> ExecuteProblem) -> String -> m Job
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> ExecuteProblem
CreateQueryJobDecodeProblem)
    Int
_ ->
      ExecuteProblem -> m Job
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (ExecuteProblem -> m Job) -> ExecuteProblem -> m Job
forall a b. (a -> b) -> a -> b
$
        Status -> Value -> ExecuteProblem
RESTRequestNonOK
          (Response ByteString -> Status
forall a. Response a -> Status
getResponseStatus Response ByteString
resp)
          (Value -> ExecuteProblem) -> Value -> ExecuteProblem
forall a b. (a -> b) -> a -> b
$ ByteString -> Value
parseAsJsonOrText (ByteString -> Value) -> ByteString -> Value
forall a b. (a -> b) -> a -> b
$ Response ByteString -> ByteString
forall a. Response a -> a
getResponseBody Response ByteString
resp

data Dataset = Dataset
  { Dataset -> Text
datasetId :: Text
  }
  deriving (Int -> Dataset -> ShowS
[Dataset] -> ShowS
Dataset -> String
(Int -> Dataset -> ShowS)
-> (Dataset -> String) -> ([Dataset] -> ShowS) -> Show Dataset
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Dataset] -> ShowS
$cshowList :: [Dataset] -> ShowS
show :: Dataset -> String
$cshow :: Dataset -> String
showsPrec :: Int -> Dataset -> ShowS
$cshowsPrec :: Int -> Dataset -> ShowS
Show)

instance Aeson.FromJSON Dataset where
  parseJSON :: Value -> Parser Dataset
parseJSON =
    String -> (Object -> Parser Dataset) -> Value -> Parser Dataset
forall a. String -> (Object -> Parser a) -> Value -> Parser a
Aeson.withObject
      String
"Dataset"
      ( \Object
o -> do
          Text
datasetId <- Object
o Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"id"
          Dataset -> Parser Dataset
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Text -> Dataset
Dataset Text
datasetId)
      )

-- | Delete a dataset
deleteDataset :: (MonadError ExecuteProblem m, MonadIO m) => BigQueryConnection -> Text -> m ()
deleteDataset :: BigQueryConnection -> Text -> m ()
deleteDataset BigQueryConnection
conn Text
datasetId = do
  let url :: String
url =
        String
"DELETE " String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Text -> String
bigQueryProjectUrl (BigQueryConnection -> Text
_bqProjectId BigQueryConnection
conn)
          String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
"/datasets/"
          String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Text -> String
T.unpack Text
datasetId

  let req :: Request
req = Request -> Request
jsonRequestHeader (String -> Request
parseRequest_ String
url)

  Response ByteString
resp <- BigQueryConnection -> Request -> m (Response ByteString)
forall (m :: * -> *).
(MonadError ExecuteProblem m, MonadIO m) =>
BigQueryConnection -> Request -> m (Response ByteString)
runBigQueryExcept BigQueryConnection
conn Request
req
  case Response ByteString -> Int
forall a. Response a -> Int
getResponseStatusCode Response ByteString
resp of
    Int
204 -> () -> m ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
    Int
_ ->
      ExecuteProblem -> m ()
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (ExecuteProblem -> m ()) -> ExecuteProblem -> m ()
forall a b. (a -> b) -> a -> b
$
        Status -> Value -> ExecuteProblem
RESTRequestNonOK
          (Response ByteString -> Status
forall a. Response a -> Status
getResponseStatus Response ByteString
resp)
          (Value -> ExecuteProblem) -> Value -> ExecuteProblem
forall a b. (a -> b) -> a -> b
$ ByteString -> Value
parseAsJsonOrText (ByteString -> Value) -> ByteString -> Value
forall a b. (a -> b) -> a -> b
$ Response ByteString -> ByteString
forall a. Response a -> a
getResponseBody Response ByteString
resp

-- | Run request and map errors into ExecuteProblem
runBigQueryExcept ::
  (MonadError ExecuteProblem m, MonadIO m) =>
  BigQueryConnection ->
  Request ->
  m (Response BL.ByteString)
runBigQueryExcept :: BigQueryConnection -> Request -> m (Response ByteString)
runBigQueryExcept BigQueryConnection
conn Request
req = do
  BigQueryConnection
-> Request -> m (Either BigQueryProblem (Response ByteString))
forall (m :: * -> *).
MonadIO m =>
BigQueryConnection
-> Request -> m (Either BigQueryProblem (Response ByteString))
runBigQuery BigQueryConnection
conn Request
req m (Either BigQueryProblem (Response ByteString))
-> (Either BigQueryProblem (Response ByteString)
    -> m (Response ByteString))
-> m (Response ByteString)
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
    Right Response ByteString
a -> Response ByteString -> m (Response ByteString)
forall (f :: * -> *) a. Applicative f => a -> f a
pure Response ByteString
a
    Left BigQueryProblem
e -> ExecuteProblem -> m (Response ByteString)
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (BigQueryProblem -> ExecuteProblem
ExecuteRunBigQueryProblem BigQueryProblem
e)

-- | Insert a new dataset
insertDataset :: (MonadError ExecuteProblem m, MonadIO m) => BigQueryConnection -> Text -> m Dataset
insertDataset :: BigQueryConnection -> Text -> m Dataset
insertDataset BigQueryConnection
conn Text
datasetId =
  do
    let url :: String
url =
          String
"POST " String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Text -> String
bigQueryProjectUrl (BigQueryConnection -> Text
_bqProjectId BigQueryConnection
conn)
            String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
"/datasets?alt=json&prettyPrint=false"

        req :: Request
req =
          Request -> Request
jsonRequestHeader (Request -> Request) -> Request -> Request
forall a b. (a -> b) -> a -> b
$
            ByteString -> Request -> Request
setRequestBodyLBS ByteString
body (Request -> Request) -> Request -> Request
forall a b. (a -> b) -> a -> b
$
              String -> Request
parseRequest_ String
url

        body :: ByteString
body =
          Value -> ByteString
forall a. ToJSON a => a -> ByteString
Aeson.encode
            ( [Pair] -> Value
Aeson.object
                [ Key
"id" Key -> Text -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Text
datasetId,
                  Key
"datasetReference"
                    Key -> Value -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= [Pair] -> Value
Aeson.object
                      [ Key
"datasetId" Key -> Text -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Text
datasetId,
                        Key
"projectId" Key -> Text -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= BigQueryConnection -> Text
_bqProjectId BigQueryConnection
conn
                      ]
                ]
            )

    Response ByteString
resp <- BigQueryConnection -> Request -> m (Response ByteString)
forall (m :: * -> *).
(MonadError ExecuteProblem m, MonadIO m) =>
BigQueryConnection -> Request -> m (Response ByteString)
runBigQueryExcept BigQueryConnection
conn Request
req
    case Response ByteString -> Int
forall a. Response a -> Int
getResponseStatusCode Response ByteString
resp of
      Int
200 ->
        ByteString -> Either String Dataset
forall a. FromJSON a => ByteString -> Either String a
Aeson.eitherDecode (Response ByteString -> ByteString
forall a. Response a -> a
getResponseBody Response ByteString
resp)
          Either String Dataset -> (String -> m Dataset) -> m Dataset
forall (m :: * -> *) e a.
Applicative m =>
Either e a -> (e -> m a) -> m a
`onLeft` (ExecuteProblem -> m Dataset
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (ExecuteProblem -> m Dataset)
-> (String -> ExecuteProblem) -> String -> m Dataset
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> ExecuteProblem
InsertDatasetDecodeProblem)
      Int
_ ->
        ExecuteProblem -> m Dataset
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (ExecuteProblem -> m Dataset) -> ExecuteProblem -> m Dataset
forall a b. (a -> b) -> a -> b
$
          Status -> Value -> ExecuteProblem
RESTRequestNonOK
            (Response ByteString -> Status
forall a. Response a -> Status
getResponseStatus Response ByteString
resp)
            (Value -> ExecuteProblem) -> Value -> ExecuteProblem
forall a b. (a -> b) -> a -> b
$ ByteString -> Value
parseAsJsonOrText (ByteString -> Value) -> ByteString -> Value
forall a b. (a -> b) -> a -> b
$ Response ByteString -> ByteString
forall a. Response a -> a
getResponseBody Response ByteString
resp

-- | Parse given @'ByteString' as JSON value. If not a valid JSON, encode to plain text.
parseAsJsonOrText :: BL.ByteString -> Aeson.Value
parseAsJsonOrText :: ByteString -> Value
parseAsJsonOrText ByteString
bytestring =
  Value -> Maybe Value -> Value
forall a. a -> Maybe a -> a
fromMaybe (Text -> Value
Aeson.String (Text -> Value) -> Text -> Value
forall a b. (a -> b) -> a -> b
$ ByteString -> Text
lbsToTxt ByteString
bytestring) (Maybe Value -> Value) -> Maybe Value -> Value
forall a b. (a -> b) -> a -> b
$ ByteString -> Maybe Value
forall a. FromJSON a => ByteString -> Maybe a
Aeson.decode ByteString
bytestring

--------------------------------------------------------------------------------
-- Consuming recordset from big query

parseRecordSetPayload :: Aeson.Object -> Aeson.Parser RecordSet
parseRecordSetPayload :: Object -> Parser RecordSet
parseRecordSetPayload Object
resp = do
  Maybe Object
mSchema <- Object
resp Object -> Key -> Parser (Maybe Object)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"schema"
  Vector BigQueryField
columns <- Parser (Vector BigQueryField)
-> (Object -> Parser (Vector BigQueryField))
-> Maybe Object
-> Parser (Vector BigQueryField)
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Vector BigQueryField -> Parser (Vector BigQueryField)
forall (f :: * -> *) a. Applicative f => a -> f a
pure Vector BigQueryField
forall a. Vector a
V.empty) (Object -> Key -> Parser (Vector BigQueryField)
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"fields") Maybe Object
mSchema :: Aeson.Parser (Vector BigQueryField)
  Array
rowsJSON <- (Maybe Array -> Array) -> Parser (Maybe Array) -> Parser Array
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Array -> Maybe Array -> Array
forall a. a -> Maybe a -> a
fromMaybe Array
forall a. Vector a
V.empty) (Object
resp Object -> Key -> Parser (Maybe Array)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"rows" :: Aeson.Parser (Maybe (Vector Aeson.Value)))
  Vector (InsOrdHashMap FieldNameText OutputValue)
rows <-
    (Int -> Value -> Parser (InsOrdHashMap FieldNameText OutputValue))
-> Array
-> Parser (Vector (InsOrdHashMap FieldNameText OutputValue))
forall (m :: * -> *) a b.
Monad m =>
(Int -> a -> m b) -> Vector a -> m (Vector b)
V.imapM
      (\Int
i Value
row -> Vector BigQueryField
-> Value -> Parser (InsOrdHashMap FieldNameText OutputValue)
parseRow Vector BigQueryField
columns Value
row Parser (InsOrdHashMap FieldNameText OutputValue)
-> JSONPathElement
-> Parser (InsOrdHashMap FieldNameText OutputValue)
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Int -> JSONPathElement
Aeson.Index Int
i)
      Array
rowsJSON
      Parser (Vector (InsOrdHashMap FieldNameText OutputValue))
-> JSONPathElement
-> Parser (Vector (InsOrdHashMap FieldNameText OutputValue))
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"rows"
  RecordSet -> Parser RecordSet
forall (f :: * -> *) a. Applicative f => a -> f a
pure RecordSet :: Vector (InsOrdHashMap FieldNameText OutputValue)
-> Maybe [Text] -> RecordSet
RecordSet {$sel:wantedFields:RecordSet :: Maybe [Text]
wantedFields = Maybe [Text]
forall a. Maybe a
Nothing, Vector (InsOrdHashMap FieldNameText OutputValue)
rows :: Vector (InsOrdHashMap FieldNameText OutputValue)
$sel:rows:RecordSet :: Vector (InsOrdHashMap FieldNameText OutputValue)
rows}

--------------------------------------------------------------------------------
-- Schema-driven JSON deserialization

parseRow :: Vector BigQueryField -> Aeson.Value -> Aeson.Parser (InsOrdHashMap FieldNameText OutputValue)
parseRow :: Vector BigQueryField
-> Value -> Parser (InsOrdHashMap FieldNameText OutputValue)
parseRow Vector BigQueryField
columnTypes Value
value = do
  OutputValue
result <- Vector BigQueryField -> Value -> Parser OutputValue
parseBigQueryRow Vector BigQueryField
columnTypes Value
value
  case OutputValue
result of
    RecordOutputValue InsOrdHashMap FieldNameText OutputValue
row -> InsOrdHashMap FieldNameText OutputValue
-> Parser (InsOrdHashMap FieldNameText OutputValue)
forall (f :: * -> *) a. Applicative f => a -> f a
pure InsOrdHashMap FieldNameText OutputValue
row
    OutputValue
_ -> String -> Parser (InsOrdHashMap FieldNameText OutputValue)
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String
"Expected a record when parsing a top-level row: " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Value -> String
forall a. Show a => a -> String
show Value
value)

-- | Parse a row, which at the top-level of the "rows" output has no
-- {"v":..} wrapper. But when appearing nestedly, does have the
-- wrapper. See 'parseBigQueryValue'.
parseBigQueryRow :: Vector BigQueryField -> Aeson.Value -> Aeson.Parser OutputValue
parseBigQueryRow :: Vector BigQueryField -> Value -> Parser OutputValue
parseBigQueryRow Vector BigQueryField
columnTypes =
  String
-> (Object -> Parser OutputValue) -> Value -> Parser OutputValue
forall a. String -> (Object -> Parser a) -> Value -> Parser a
Aeson.withObject
    String
"RECORD"
    ( \Object
o -> do
        Array
fields <- Object
o Object -> Key -> Parser Array
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"f" Parser Array -> JSONPathElement -> Parser Array
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"RECORD"
        Vector (FieldNameText, OutputValue)
values <-
          Vector (Parser (FieldNameText, OutputValue))
-> Parser (Vector (FieldNameText, OutputValue))
forall (t :: * -> *) (m :: * -> *) a.
(Traversable t, Monad m) =>
t (m a) -> m (t a)
sequence
            ( (Int
 -> BigQueryField -> Value -> Parser (FieldNameText, OutputValue))
-> Vector BigQueryField
-> Array
-> Vector (Parser (FieldNameText, OutputValue))
forall a b c.
(Int -> a -> b -> c) -> Vector a -> Vector b -> Vector c
V.izipWith
                ( \Int
i BigQueryField
typ Value
field ->
                    BigQueryField -> Value -> Parser (FieldNameText, OutputValue)
parseBigQueryField BigQueryField
typ Value
field Parser (FieldNameText, OutputValue)
-> JSONPathElement -> Parser (FieldNameText, OutputValue)
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Int -> JSONPathElement
Aeson.Index Int
i
                )
                Vector BigQueryField
columnTypes
                Array
fields
            )
            Parser (Vector (FieldNameText, OutputValue))
-> JSONPathElement -> Parser (Vector (FieldNameText, OutputValue))
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"f"
        OutputValue -> Parser OutputValue
forall (f :: * -> *) a. Applicative f => a -> f a
pure (InsOrdHashMap FieldNameText OutputValue -> OutputValue
RecordOutputValue ([(FieldNameText, OutputValue)]
-> InsOrdHashMap FieldNameText OutputValue
forall k v. (Eq k, Hashable k) => [(k, v)] -> InsOrdHashMap k v
OMap.fromList (Vector (FieldNameText, OutputValue)
-> [(FieldNameText, OutputValue)]
forall a. Vector a -> [a]
V.toList Vector (FieldNameText, OutputValue)
values)))
    )

parseBigQueryValue :: IsNullable -> BigQueryFieldType -> Aeson.Value -> Aeson.Parser OutputValue
parseBigQueryValue :: IsNullable -> BigQueryFieldType -> Value -> Parser OutputValue
parseBigQueryValue IsNullable
isNullable BigQueryFieldType
fieldType Value
object =
  case BigQueryFieldType
fieldType of
    FieldSTRUCT Vector BigQueryField
types ->
      IsNullable
-> (Value -> Parser OutputValue) -> Value -> Parser OutputValue
has_v IsNullable
isNullable (Vector BigQueryField -> Value -> Parser OutputValue
parseBigQueryRow Vector BigQueryField
types) Value
object Parser OutputValue -> JSONPathElement -> Parser OutputValue
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"RECORD"
    BigQueryFieldType
FieldDECIMAL ->
      IsNullable
-> (Value -> Parser OutputValue) -> Value -> Parser OutputValue
has_v IsNullable
isNullable ((Decimal -> OutputValue) -> Parser Decimal -> Parser OutputValue
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Decimal -> OutputValue
DecimalOutputValue (Parser Decimal -> Parser OutputValue)
-> (Value -> Parser Decimal) -> Value -> Parser OutputValue
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> Parser Decimal
forall a. FromJSON a => Value -> Parser a
Aeson.parseJSON) Value
object
        Parser OutputValue -> JSONPathElement -> Parser OutputValue
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"DECIMAL"
    BigQueryFieldType
FieldBIGDECIMAL ->
      IsNullable
-> (Value -> Parser OutputValue) -> Value -> Parser OutputValue
has_v IsNullable
isNullable ((BigDecimal -> OutputValue)
-> Parser BigDecimal -> Parser OutputValue
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap BigDecimal -> OutputValue
BigDecimalOutputValue (Parser BigDecimal -> Parser OutputValue)
-> (Value -> Parser BigDecimal) -> Value -> Parser OutputValue
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> Parser BigDecimal
forall a. FromJSON a => Value -> Parser a
Aeson.parseJSON) Value
object
        Parser OutputValue -> JSONPathElement -> Parser OutputValue
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"BIGDECIMAL"
    BigQueryFieldType
FieldINTEGER ->
      IsNullable
-> (Value -> Parser OutputValue) -> Value -> Parser OutputValue
has_v IsNullable
isNullable ((Int64 -> OutputValue) -> Parser Int64 -> Parser OutputValue
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Int64 -> OutputValue
IntegerOutputValue (Parser Int64 -> Parser OutputValue)
-> (Value -> Parser Int64) -> Value -> Parser OutputValue
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> Parser Int64
forall a. FromJSON a => Value -> Parser a
Aeson.parseJSON) Value
object
        Parser OutputValue -> JSONPathElement -> Parser OutputValue
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"INTEGER"
    BigQueryFieldType
FieldDATE ->
      IsNullable
-> (Value -> Parser OutputValue) -> Value -> Parser OutputValue
has_v IsNullable
isNullable ((Date -> OutputValue) -> Parser Date -> Parser OutputValue
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Date -> OutputValue
DateOutputValue (Parser Date -> Parser OutputValue)
-> (Value -> Parser Date) -> Value -> Parser OutputValue
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> Parser Date
forall a. FromJSON a => Value -> Parser a
Aeson.parseJSON) Value
object
        Parser OutputValue -> JSONPathElement -> Parser OutputValue
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"DATE"
    BigQueryFieldType
FieldTIME ->
      IsNullable
-> (Value -> Parser OutputValue) -> Value -> Parser OutputValue
has_v IsNullable
isNullable ((Time -> OutputValue) -> Parser Time -> Parser OutputValue
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Time -> OutputValue
TimeOutputValue (Parser Time -> Parser OutputValue)
-> (Value -> Parser Time) -> Value -> Parser OutputValue
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> Parser Time
forall a. FromJSON a => Value -> Parser a
Aeson.parseJSON) Value
object
        Parser OutputValue -> JSONPathElement -> Parser OutputValue
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"TIME"
    BigQueryFieldType
FieldDATETIME ->
      IsNullable
-> (Value -> Parser OutputValue) -> Value -> Parser OutputValue
has_v IsNullable
isNullable ((Datetime -> OutputValue) -> Parser Datetime -> Parser OutputValue
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Datetime -> OutputValue
DatetimeOutputValue (Parser Datetime -> Parser OutputValue)
-> (Value -> Parser Datetime) -> Value -> Parser OutputValue
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> Parser Datetime
forall a. FromJSON a => Value -> Parser a
Aeson.parseJSON) Value
object
        Parser OutputValue -> JSONPathElement -> Parser OutputValue
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"DATETIME"
    BigQueryFieldType
FieldTIMESTAMP ->
      IsNullable
-> (Value -> Parser OutputValue) -> Value -> Parser OutputValue
has_v IsNullable
isNullable ((Timestamp -> OutputValue)
-> Parser Timestamp -> Parser OutputValue
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Timestamp -> OutputValue
TimestampOutputValue (Parser Timestamp -> Parser OutputValue)
-> (Value -> Parser Timestamp) -> Value -> Parser OutputValue
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> Parser Timestamp
parseTimestamp) Value
object
        Parser OutputValue -> JSONPathElement -> Parser OutputValue
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"TIMESTAMP"
    BigQueryFieldType
FieldGEOGRAPHY ->
      IsNullable
-> (Value -> Parser OutputValue) -> Value -> Parser OutputValue
has_v IsNullable
isNullable ((Geography -> OutputValue)
-> Parser Geography -> Parser OutputValue
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Geography -> OutputValue
GeographyOutputValue (Parser Geography -> Parser OutputValue)
-> (Value -> Parser Geography) -> Value -> Parser OutputValue
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> Parser Geography
forall a. FromJSON a => Value -> Parser a
Aeson.parseJSON) Value
object
        Parser OutputValue -> JSONPathElement -> Parser OutputValue
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"GEOGRAPHY"
    BigQueryFieldType
FieldFLOAT ->
      IsNullable
-> (Value -> Parser OutputValue) -> Value -> Parser OutputValue
has_v IsNullable
isNullable ((Float64 -> OutputValue) -> Parser Float64 -> Parser OutputValue
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Float64 -> OutputValue
FloatOutputValue (Parser Float64 -> Parser OutputValue)
-> (Value -> Parser Float64) -> Value -> Parser OutputValue
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> Parser Float64
forall a. FromJSON a => Value -> Parser a
Aeson.parseJSON) Value
object
        Parser OutputValue -> JSONPathElement -> Parser OutputValue
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"FLOAT"
    BigQueryFieldType
FieldBOOL ->
      IsNullable
-> (Value -> Parser OutputValue) -> Value -> Parser OutputValue
has_v IsNullable
isNullable ((String -> OutputValue) -> Parser String -> Parser OutputValue
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Bool -> OutputValue
BoolOutputValue (Bool -> OutputValue) -> (String -> Bool) -> String -> OutputValue
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"true")) (Parser String -> Parser OutputValue)
-> (Value -> Parser String) -> Value -> Parser OutputValue
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> Parser String
forall a. FromJSON a => Value -> Parser a
Aeson.parseJSON) Value
object
        Parser OutputValue -> JSONPathElement -> Parser OutputValue
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"BOOL"
    BigQueryFieldType
FieldSTRING ->
      IsNullable
-> (Value -> Parser OutputValue) -> Value -> Parser OutputValue
has_v IsNullable
isNullable ((Text -> OutputValue) -> Parser Text -> Parser OutputValue
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Text -> OutputValue
TextOutputValue (Parser Text -> Parser OutputValue)
-> (Value -> Parser Text) -> Value -> Parser OutputValue
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> Parser Text
forall a. FromJSON a => Value -> Parser a
Aeson.parseJSON) Value
object
        Parser OutputValue -> JSONPathElement -> Parser OutputValue
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"STRING"
    BigQueryFieldType
FieldBYTES ->
      IsNullable
-> (Value -> Parser OutputValue) -> Value -> Parser OutputValue
has_v IsNullable
isNullable ((Base64 -> OutputValue) -> Parser Base64 -> Parser OutputValue
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Base64 -> OutputValue
BytesOutputValue (Parser Base64 -> Parser OutputValue)
-> (Value -> Parser Base64) -> Value -> Parser OutputValue
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> Parser Base64
forall a. FromJSON a => Value -> Parser a
Aeson.parseJSON) Value
object
        Parser OutputValue -> JSONPathElement -> Parser OutputValue
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"BYTES"

-- | Parse upstream timestamp value in epoch milliseconds and convert it to calendar date time format
-- https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#timestamp_type
parseTimestamp :: Aeson.Value -> Aeson.Parser Timestamp
parseTimestamp :: Value -> Parser Timestamp
parseTimestamp =
  (UTCTime -> Timestamp) -> Parser UTCTime -> Parser Timestamp
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Text -> Timestamp
Timestamp (Text -> Timestamp) -> (UTCTime -> Text) -> UTCTime -> Timestamp
forall b c a. (b -> c) -> (a -> b) -> a -> c
. UTCTime -> Text
utctimeToISO8601Text) (Parser UTCTime -> Parser Timestamp)
-> (Value -> Parser UTCTime) -> Value -> Parser Timestamp
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> (Text -> Parser UTCTime) -> Value -> Parser UTCTime
forall a. String -> (Text -> Parser a) -> Value -> Parser a
Aeson.withText String
"FieldTIMESTAMP" Text -> Parser UTCTime
textToUTCTime
  where
    textToUTCTime :: Text -> Aeson.Parser UTCTime
    textToUTCTime :: Text -> Parser UTCTime
textToUTCTime =
      (String -> Parser UTCTime)
-> ((NominalDiffTime, Text) -> Parser UTCTime)
-> Either String (NominalDiffTime, Text)
-> Parser UTCTime
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either String -> Parser UTCTime
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (UTCTime -> Parser UTCTime
forall (f :: * -> *) a. Applicative f => a -> f a
pure (UTCTime -> Parser UTCTime)
-> ((NominalDiffTime, Text) -> UTCTime)
-> (NominalDiffTime, Text)
-> Parser UTCTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (NominalDiffTime -> UTCTime -> UTCTime)
-> UTCTime -> NominalDiffTime -> UTCTime
forall a b c. (a -> b -> c) -> b -> a -> c
flip NominalDiffTime -> UTCTime -> UTCTime
addUTCTime (Day -> DiffTime -> UTCTime
UTCTime (Integer -> Int -> Int -> Day
fromGregorian Integer
1970 Int
0 Int
0) DiffTime
0) (NominalDiffTime -> UTCTime)
-> ((NominalDiffTime, Text) -> NominalDiffTime)
-> (NominalDiffTime, Text)
-> UTCTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (NominalDiffTime, Text) -> NominalDiffTime
forall a b. (a, b) -> a
fst)
        (Either String (NominalDiffTime, Text) -> Parser UTCTime)
-> (Text -> Either String (NominalDiffTime, Text))
-> Text
-> Parser UTCTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Text -> Either String (NominalDiffTime, Text)
forall a. Fractional a => Reader a
TR.rational :: TR.Reader NominalDiffTime)

    utctimeToISO8601Text :: UTCTime -> Text
    utctimeToISO8601Text :: UTCTime -> Text
utctimeToISO8601Text = String -> Text
T.pack (String -> Text) -> (UTCTime -> String) -> UTCTime -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. UTCTime -> String
forall t. ISO8601 t => t -> String
iso8601Show

parseBigQueryField :: BigQueryField -> Aeson.Value -> Aeson.Parser (FieldNameText, OutputValue)
parseBigQueryField :: BigQueryField -> Value -> Parser (FieldNameText, OutputValue)
parseBigQueryField BigQueryField {FieldNameText
name :: FieldNameText
$sel:name:BigQueryField :: BigQueryField -> FieldNameText
name, BigQueryFieldType
typ :: BigQueryFieldType
$sel:typ:BigQueryField :: BigQueryField -> BigQueryFieldType
typ, Mode
mode :: Mode
$sel:mode:BigQueryField :: BigQueryField -> Mode
mode} Value
value1 =
  case Mode
mode of
    Mode
Repeated ->
      ( do
          Array
values <- (Value -> Parser Array) -> Value -> Parser Array
forall a. (Value -> Parser a) -> Value -> Parser a
has_v_generic Value -> Parser Array
forall a. FromJSON a => Value -> Parser a
Aeson.parseJSON Value
value1
          Vector OutputValue
outputs <-
            (Int -> Value -> Parser OutputValue)
-> Array -> Parser (Vector OutputValue)
forall (m :: * -> *) a b.
Monad m =>
(Int -> a -> m b) -> Vector a -> m (Vector b)
V.imapM
              ( \Int
i Value
value2 ->
                  IsNullable -> BigQueryFieldType -> Value -> Parser OutputValue
parseBigQueryValue IsNullable
IsRequired BigQueryFieldType
typ Value
value2
                    Parser OutputValue -> JSONPathElement -> Parser OutputValue
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Int -> JSONPathElement
Aeson.Index Int
i
              )
              Array
values
          (FieldNameText, OutputValue) -> Parser (FieldNameText, OutputValue)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (FieldNameText
name, Vector OutputValue -> OutputValue
ArrayOutputValue Vector OutputValue
outputs)
      )
        Parser (FieldNameText, OutputValue)
-> JSONPathElement -> Parser (FieldNameText, OutputValue)
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"REPEATED"
    Mode
Nullable -> do
      OutputValue
output <-
        IsNullable -> BigQueryFieldType -> Value -> Parser OutputValue
parseBigQueryValue IsNullable
IsNullable BigQueryFieldType
typ Value
value1 Parser OutputValue -> JSONPathElement -> Parser OutputValue
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"NULLABLE"
      (FieldNameText, OutputValue) -> Parser (FieldNameText, OutputValue)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (FieldNameText
name, OutputValue
output)
    Mode
NotNullable -> do
      OutputValue
output <-
        IsNullable -> BigQueryFieldType -> Value -> Parser OutputValue
parseBigQueryValue IsNullable
IsRequired BigQueryFieldType
typ Value
value1 Parser OutputValue -> JSONPathElement -> Parser OutputValue
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"REQUIRED"
      (FieldNameText, OutputValue) -> Parser (FieldNameText, OutputValue)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (FieldNameText
name, OutputValue
output)

-- Every value, after the top-level row, is wrapped in this.
has_v ::
  IsNullable ->
  (Aeson.Value -> Aeson.Parser OutputValue) ->
  Aeson.Value ->
  Aeson.Parser OutputValue
has_v :: IsNullable
-> (Value -> Parser OutputValue) -> Value -> Parser OutputValue
has_v IsNullable
isNullable Value -> Parser OutputValue
f =
  String
-> (Object -> Parser OutputValue) -> Value -> Parser OutputValue
forall a. String -> (Object -> Parser a) -> Value -> Parser a
Aeson.withObject
    String
"HAS_V"
    ( \Object
o ->
        Object
o Object -> Key -> Parser Value
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"v" Parser Value -> (Value -> Parser OutputValue) -> Parser OutputValue
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Value
v ->
          case Value
v of
            Value
Aeson.Null
              | IsNullable
IsNullable <- IsNullable
isNullable -> OutputValue -> Parser OutputValue
forall (f :: * -> *) a. Applicative f => a -> f a
pure OutputValue
NullOutputValue
            Value
_ -> Value -> Parser OutputValue
f Value
v Parser OutputValue -> JSONPathElement -> Parser OutputValue
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"v"
    )

-- Every value, after the top-level row, is wrapped in this.
has_v_generic ::
  (Aeson.Value -> Aeson.Parser a) ->
  Aeson.Value ->
  Aeson.Parser a
has_v_generic :: (Value -> Parser a) -> Value -> Parser a
has_v_generic Value -> Parser a
f =
  String -> (Object -> Parser a) -> Value -> Parser a
forall a. String -> (Object -> Parser a) -> Value -> Parser a
Aeson.withObject
    String
"HAS_V"
    (\Object
o -> Object
o Object -> Key -> Parser Value
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"v" Parser Value -> (Value -> Parser a) -> Parser a
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Value
v -> (Value -> Parser a
f Value
v Parser a -> JSONPathElement -> Parser a
forall a. Parser a -> JSONPathElement -> Parser a
Aeson.<?> Key -> JSONPathElement
Aeson.Key Key
"v"))

--------------------------------------------------------------------------------
-- Generic JSON deserialization

instance Aeson.ToJSON BigQueryType where
  toJSON :: BigQueryType -> Value
toJSON =
    \case
      ARRAY BigQueryType
t -> [Pair] -> Value
Aeson.object [Key
"type" Key -> Text -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= (Text
"ARRAY" :: Text), Key
"arrayType" Key -> BigQueryType -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= BigQueryType
t]
      BigQueryType
DECIMAL -> Text -> Value
atomic Text
"NUMERIC"
      BigQueryType
BIGDECIMAL -> Text -> Value
atomic Text
"BIGNUMERIC"
      BigQueryType
INTEGER -> Text -> Value
atomic Text
"INTEGER"
      BigQueryType
DATE -> Text -> Value
atomic Text
"DATE"
      BigQueryType
TIME -> Text -> Value
atomic Text
"TIME"
      BigQueryType
DATETIME -> Text -> Value
atomic Text
"DATETIME"
      BigQueryType
TIMESTAMP -> Text -> Value
atomic Text
"TIMESTAMP"
      BigQueryType
FLOAT -> Text -> Value
atomic Text
"FLOAT"
      BigQueryType
GEOGRAPHY -> Text -> Value
atomic Text
"GEOGRAPHY"
      BigQueryType
STRING -> Text -> Value
atomic Text
"STRING"
      BigQueryType
BYTES -> Text -> Value
atomic Text
"BYTES"
      BigQueryType
BOOL -> Text -> Value
atomic Text
"BOOL"
    where
      atomic :: Text -> Value
atomic Text
ty = [Pair] -> Value
Aeson.object [Key
"type" Key -> Text -> Pair
forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= (Text
ty :: Text)]

instance Aeson.FromJSON BigQueryField where
  parseJSON :: Value -> Parser BigQueryField
parseJSON =
    String
-> (Object -> Parser BigQueryField)
-> Value
-> Parser BigQueryField
forall a. String -> (Object -> Parser a) -> Value -> Parser a
Aeson.withObject
      String
"BigQueryField"
      ( \Object
o -> do
          FieldNameText
name <- Object
o Object -> Key -> Parser FieldNameText
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"name"
          BigQueryFieldType
typ <-
            do
              Text
flag :: Text <- Object
o Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"type"
              if
                  | Text
flag Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"NUMERIC" Bool -> Bool -> Bool
|| Text
flag Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"DECIMAL" -> BigQueryFieldType -> Parser BigQueryFieldType
forall (f :: * -> *) a. Applicative f => a -> f a
pure BigQueryFieldType
FieldDECIMAL
                  | Text
flag Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"BIGNUMERIC" Bool -> Bool -> Bool
|| Text
flag Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"BIGDECIMAL" ->
                    BigQueryFieldType -> Parser BigQueryFieldType
forall (f :: * -> *) a. Applicative f => a -> f a
pure BigQueryFieldType
FieldBIGDECIMAL
                  | Text
flag Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"INT64" Bool -> Bool -> Bool
|| Text
flag Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"INTEGER" -> BigQueryFieldType -> Parser BigQueryFieldType
forall (f :: * -> *) a. Applicative f => a -> f a
pure BigQueryFieldType
FieldINTEGER
                  | Text
flag Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"FLOAT64" Bool -> Bool -> Bool
|| Text
flag Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"FLOAT" -> BigQueryFieldType -> Parser BigQueryFieldType
forall (f :: * -> *) a. Applicative f => a -> f a
pure BigQueryFieldType
FieldFLOAT
                  | Text
flag Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"BOOLEAN" Bool -> Bool -> Bool
|| Text
flag Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"BOOL" -> BigQueryFieldType -> Parser BigQueryFieldType
forall (f :: * -> *) a. Applicative f => a -> f a
pure BigQueryFieldType
FieldBOOL
                  | Text
flag Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"STRING" -> BigQueryFieldType -> Parser BigQueryFieldType
forall (f :: * -> *) a. Applicative f => a -> f a
pure BigQueryFieldType
FieldSTRING
                  | Text
flag Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"DATE" -> BigQueryFieldType -> Parser BigQueryFieldType
forall (f :: * -> *) a. Applicative f => a -> f a
pure BigQueryFieldType
FieldDATE
                  | Text
flag Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"TIME" -> BigQueryFieldType -> Parser BigQueryFieldType
forall (f :: * -> *) a. Applicative f => a -> f a
pure BigQueryFieldType
FieldTIME
                  | Text
flag Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"DATETIME" -> BigQueryFieldType -> Parser BigQueryFieldType
forall (f :: * -> *) a. Applicative f => a -> f a
pure BigQueryFieldType
FieldDATETIME
                  | Text
flag Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"TIMESTAMP" -> BigQueryFieldType -> Parser BigQueryFieldType
forall (f :: * -> *) a. Applicative f => a -> f a
pure BigQueryFieldType
FieldTIMESTAMP
                  | Text
flag Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"GEOGRAPHY" -> BigQueryFieldType -> Parser BigQueryFieldType
forall (f :: * -> *) a. Applicative f => a -> f a
pure BigQueryFieldType
FieldGEOGRAPHY
                  | Text
flag Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"BYTES" -> BigQueryFieldType -> Parser BigQueryFieldType
forall (f :: * -> *) a. Applicative f => a -> f a
pure BigQueryFieldType
FieldBYTES
                  | Text
flag Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"RECORD" Bool -> Bool -> Bool
|| Text
flag Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
"STRUCT" ->
                    do
                      Vector BigQueryField
fields <- Object
o Object -> Key -> Parser (Vector BigQueryField)
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"fields"
                      BigQueryFieldType -> Parser BigQueryFieldType
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Vector BigQueryField -> BigQueryFieldType
FieldSTRUCT Vector BigQueryField
fields)
                  | Bool
otherwise -> String -> Parser BigQueryFieldType
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String
"Unsupported field type: " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Text -> String
forall a. Show a => a -> String
show Text
flag)
          Mode
mode <- Object
o Object -> Key -> Parser (Maybe Mode)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"mode" Parser (Maybe Mode) -> Mode -> Parser Mode
forall a. Parser (Maybe a) -> a -> Parser a
.!= Mode
Nullable
          BigQueryField -> Parser BigQueryField
forall (f :: * -> *) a. Applicative f => a -> f a
pure BigQueryField :: FieldNameText -> BigQueryFieldType -> Mode -> BigQueryField
BigQueryField {Mode
BigQueryFieldType
FieldNameText
mode :: Mode
typ :: BigQueryFieldType
name :: FieldNameText
$sel:mode:BigQueryField :: Mode
$sel:typ:BigQueryField :: BigQueryFieldType
$sel:name:BigQueryField :: FieldNameText
..}
      )

instance Aeson.FromJSON Mode where
  parseJSON :: Value -> Parser Mode
parseJSON Value
j = do
    Text
s <- Value -> Parser Text
forall a. FromJSON a => Value -> Parser a
Aeson.parseJSON Value
j
    case Text
s :: Text of
      Text
"NULLABLE" -> Mode -> Parser Mode
forall (f :: * -> *) a. Applicative f => a -> f a
pure Mode
Nullable
      Text
"REPEATED" -> Mode -> Parser Mode
forall (f :: * -> *) a. Applicative f => a -> f a
pure Mode
Repeated
      Text
_ -> Mode -> Parser Mode
forall (f :: * -> *) a. Applicative f => a -> f a
pure Mode
NotNullable