{-# LANGUAGE TemplateHaskell #-}

-- |
-- Working example:
--
-- \$ curl -XPOST http://localhost:8080/v2/query -d @- <<EOF
-- {
--   "type":"bigquery_run_sql",
--   "args": {
--     "sql":"select 3 * 4 as foo, \"Hello, World!\" as bar",
--     "source":"chinook"
--   }
-- }
-- EOF
-- {"result_type":"TuplesOk","result":[["foo","bar"],["12","Hello, World!"]]}
module Hasura.Backends.BigQuery.DDL.RunSQL
  ( runSQL,
    runDatabaseInspection,
    BigQueryRunSQL,
  )
where

import Data.Aeson qualified as J
import Data.Aeson.TH (deriveJSON)
import Data.Aeson.Text (encodeToLazyText)
import Data.HashMap.Strict.InsOrd qualified as OMap
import Data.Text qualified as T
import Data.Text.Lazy qualified as LT
import Data.Vector qualified as V
import Hasura.Backends.BigQuery.Execute qualified as Execute
import Hasura.Backends.BigQuery.Source (BigQuerySourceConfig (..))
import Hasura.Base.Error
import Hasura.EncJSON
import Hasura.Prelude
import Hasura.RQL.DDL.Schema (RunSQLRes (..))
import Hasura.RQL.Types.Common
import Hasura.RQL.Types.Metadata
import Hasura.RQL.Types.SchemaCache
import Hasura.RQL.Types.SchemaCache.Build
import Hasura.SQL.Backend

data BigQueryRunSQL = BigQueryRunSQL
  { BigQueryRunSQL -> Text
_mrsSql :: Text,
    BigQueryRunSQL -> SourceName
_mrsSource :: SourceName
  }
  deriving (Int -> BigQueryRunSQL -> ShowS
[BigQueryRunSQL] -> ShowS
BigQueryRunSQL -> String
(Int -> BigQueryRunSQL -> ShowS)
-> (BigQueryRunSQL -> String)
-> ([BigQueryRunSQL] -> ShowS)
-> Show BigQueryRunSQL
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [BigQueryRunSQL] -> ShowS
$cshowList :: [BigQueryRunSQL] -> ShowS
show :: BigQueryRunSQL -> String
$cshow :: BigQueryRunSQL -> String
showsPrec :: Int -> BigQueryRunSQL -> ShowS
$cshowsPrec :: Int -> BigQueryRunSQL -> ShowS
Show, BigQueryRunSQL -> BigQueryRunSQL -> Bool
(BigQueryRunSQL -> BigQueryRunSQL -> Bool)
-> (BigQueryRunSQL -> BigQueryRunSQL -> Bool) -> Eq BigQueryRunSQL
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: BigQueryRunSQL -> BigQueryRunSQL -> Bool
$c/= :: BigQueryRunSQL -> BigQueryRunSQL -> Bool
== :: BigQueryRunSQL -> BigQueryRunSQL -> Bool
$c== :: BigQueryRunSQL -> BigQueryRunSQL -> Bool
Eq)

$(deriveJSON hasuraJSON ''BigQueryRunSQL)

runSQL ::
  (MonadIO m, CacheRWM m, MonadError QErr m, MetadataM m) =>
  BigQueryRunSQL ->
  m EncJSON
runSQL :: BigQueryRunSQL -> m EncJSON
runSQL = (RecordSet -> Value) -> BigQueryRunSQL -> m EncJSON
forall (m :: * -> *).
(MonadIO m, CacheRWM m, MonadError QErr m, MetadataM m) =>
(RecordSet -> Value) -> BigQueryRunSQL -> m EncJSON
runSQL_ RecordSet -> Value
recordSetAsHeaderAndRows

-- | The SQL query in the request is ignored
runDatabaseInspection ::
  (MonadIO m, CacheRWM m, MonadError QErr m, MetadataM m) =>
  BigQueryRunSQL ->
  m EncJSON
runDatabaseInspection :: BigQueryRunSQL -> m EncJSON
runDatabaseInspection (BigQueryRunSQL Text
_query SourceName
source) = do
  BigQuerySourceConfig {_scDatasets :: BigQuerySourceConfig -> [Text]
_scDatasets = [Text]
dataSets} <- SourceName -> m (SourceConfig 'BigQuery)
forall (b :: BackendType) (m :: * -> *).
(CacheRM m, MonadError QErr m, Backend b, MetadataM m) =>
SourceName -> m (SourceConfig b)
askSourceConfig @'BigQuery SourceName
source
  let queries :: [Text]
queries =
        [ Text
"SELECT *, ARRAY(SELECT as STRUCT * from "
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
dataSet
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
".INFORMATION_SCHEMA.COLUMNS WHERE table_name = t.table_name) as columns from "
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
dataSet
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
".INFORMATION_SCHEMA.TABLES as t"
          | Text
dataSet <- [Text]
dataSets
        ]
      query' :: Text
query' = Text -> [Text] -> Text
T.intercalate Text
" UNION ALL " [Text]
queries
  (RecordSet -> Value) -> BigQueryRunSQL -> m EncJSON
forall (m :: * -> *).
(MonadIO m, CacheRWM m, MonadError QErr m, MetadataM m) =>
(RecordSet -> Value) -> BigQueryRunSQL -> m EncJSON
runSQL_ RecordSet -> Value
recordSetAsSchema (Text -> SourceName -> BigQueryRunSQL
BigQueryRunSQL Text
query' SourceName
source)

runSQL_ ::
  (MonadIO m, CacheRWM m, MonadError QErr m, MetadataM m) =>
  (Execute.RecordSet -> J.Value) ->
  BigQueryRunSQL ->
  m EncJSON
runSQL_ :: (RecordSet -> Value) -> BigQueryRunSQL -> m EncJSON
runSQL_ RecordSet -> Value
f (BigQueryRunSQL Text
query SourceName
source) = do
  BigQuerySourceConfig
sourceConfig <- SourceName -> m (SourceConfig 'BigQuery)
forall (b :: BackendType) (m :: * -> *).
(CacheRM m, MonadError QErr m, Backend b, MetadataM m) =>
SourceName -> m (SourceConfig b)
askSourceConfig @'BigQuery SourceName
source
  Either ExecuteProblem RecordSet
result <-
    BigQueryConnection
-> BigQuery -> m (Either ExecuteProblem RecordSet)
forall (m :: * -> *).
MonadIO m =>
BigQueryConnection
-> BigQuery -> m (Either ExecuteProblem RecordSet)
Execute.streamBigQuery
      (BigQuerySourceConfig -> BigQueryConnection
_scConnection BigQuerySourceConfig
sourceConfig)
      BigQuery :: Text -> InsOrdHashMap ParameterName Parameter -> BigQuery
Execute.BigQuery {$sel:query:BigQuery :: Text
query = Text -> Text
LT.fromStrict Text
query, $sel:parameters:BigQuery :: InsOrdHashMap ParameterName Parameter
parameters = InsOrdHashMap ParameterName Parameter
forall a. Monoid a => a
mempty}
  case Either ExecuteProblem RecordSet
result of
    Left ExecuteProblem
executeProblem -> do
      let errorMessage :: Text
errorMessage = ShowDetails -> ExecuteProblem -> Text
Execute.executeProblemMessage ShowDetails
Execute.HideDetails ExecuteProblem
executeProblem
      QErr -> m EncJSON
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (Code -> Text -> QErr
err400 Code
BigQueryError Text
errorMessage) {qeInternal :: Maybe QErrExtra
qeInternal = QErrExtra -> Maybe QErrExtra
forall a. a -> Maybe a
Just (QErrExtra -> Maybe QErrExtra) -> QErrExtra -> Maybe QErrExtra
forall a b. (a -> b) -> a -> b
$ Value -> QErrExtra
ExtraInternal (Value -> QErrExtra) -> Value -> QErrExtra
forall a b. (a -> b) -> a -> b
$ ExecuteProblem -> Value
forall a. ToJSON a => a -> Value
J.toJSON ExecuteProblem
executeProblem}
    Right RecordSet
recordSet ->
      EncJSON -> m EncJSON
forall (f :: * -> *) a. Applicative f => a -> f a
pure
        ( RunSQLRes -> EncJSON
forall a. ToJSON a => a -> EncJSON
encJFromJValue
            (Text -> Value -> RunSQLRes
RunSQLRes Text
"TuplesOk" (RecordSet -> Value
f RecordSet
recordSet))
        )

recordSetAsHeaderAndRows :: Execute.RecordSet -> J.Value
recordSetAsHeaderAndRows :: RecordSet -> Value
recordSetAsHeaderAndRows Execute.RecordSet {Vector (InsOrdHashMap FieldNameText OutputValue)
$sel:rows:RecordSet :: RecordSet -> Vector (InsOrdHashMap FieldNameText OutputValue)
rows :: Vector (InsOrdHashMap FieldNameText OutputValue)
rows} = [[Value]] -> Value
forall a. ToJSON a => a -> Value
J.toJSON ([Value]
thead [Value] -> [[Value]] -> [[Value]]
forall a. a -> [a] -> [a]
: [[Value]]
tbody)
  where
    thead :: [Value]
thead =
      case Vector (InsOrdHashMap FieldNameText OutputValue)
rows Vector (InsOrdHashMap FieldNameText OutputValue)
-> Int -> Maybe (InsOrdHashMap FieldNameText OutputValue)
forall a. Vector a -> Int -> Maybe a
V.!? Int
0 of
        Maybe (InsOrdHashMap FieldNameText OutputValue)
Nothing -> []
        Just InsOrdHashMap FieldNameText OutputValue
row ->
          (FieldNameText -> Value) -> [FieldNameText] -> [Value]
forall a b. (a -> b) -> [a] -> [b]
map (Text -> Value
forall a. ToJSON a => a -> Value
J.toJSON (Text -> Value)
-> (FieldNameText -> Text) -> FieldNameText -> Value
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (FieldNameText -> Text
coerce :: Execute.FieldNameText -> Text)) (InsOrdHashMap FieldNameText OutputValue -> [FieldNameText]
forall k v. InsOrdHashMap k v -> [k]
OMap.keys InsOrdHashMap FieldNameText OutputValue
row)
    tbody :: [[J.Value]]
    tbody :: [[Value]]
tbody = (InsOrdHashMap FieldNameText OutputValue -> [Value])
-> [InsOrdHashMap FieldNameText OutputValue] -> [[Value]]
forall a b. (a -> b) -> [a] -> [b]
map ((OutputValue -> Value) -> [OutputValue] -> [Value]
forall a b. (a -> b) -> [a] -> [b]
map OutputValue -> Value
forall a. ToJSON a => a -> Value
J.toJSON ([OutputValue] -> [Value])
-> (InsOrdHashMap FieldNameText OutputValue -> [OutputValue])
-> InsOrdHashMap FieldNameText OutputValue
-> [Value]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. InsOrdHashMap FieldNameText OutputValue -> [OutputValue]
forall k v. InsOrdHashMap k v -> [v]
OMap.elems) (Vector (InsOrdHashMap FieldNameText OutputValue)
-> [InsOrdHashMap FieldNameText OutputValue]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList Vector (InsOrdHashMap FieldNameText OutputValue)
rows)

recordSetAsSchema :: Execute.RecordSet -> J.Value
recordSetAsSchema :: RecordSet -> Value
recordSetAsSchema rs :: RecordSet
rs@(Execute.RecordSet {Vector (InsOrdHashMap FieldNameText OutputValue)
rows :: Vector (InsOrdHashMap FieldNameText OutputValue)
$sel:rows:RecordSet :: RecordSet -> Vector (InsOrdHashMap FieldNameText OutputValue)
rows}) =
  RecordSet -> Value
recordSetAsHeaderAndRows (RecordSet -> Value) -> RecordSet -> Value
forall a b. (a -> b) -> a -> b
$
    RecordSet
rs
      { $sel:rows:RecordSet :: Vector (InsOrdHashMap FieldNameText OutputValue)
Execute.rows =
          (OutputValue -> OutputValue)
-> FieldNameText
-> InsOrdHashMap FieldNameText OutputValue
-> InsOrdHashMap FieldNameText OutputValue
forall k v.
(Eq k, Hashable k) =>
(v -> v) -> k -> InsOrdHashMap k v -> InsOrdHashMap k v
OMap.adjust
            (Text -> OutputValue
Execute.TextOutputValue (Text -> OutputValue)
-> (OutputValue -> Text) -> OutputValue -> OutputValue
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text
LT.toStrict (Text -> Text) -> (OutputValue -> Text) -> OutputValue -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> Text
forall a. ToJSON a => a -> Text
encodeToLazyText (Value -> Text) -> (OutputValue -> Value) -> OutputValue -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. OutputValue -> Value
forall a. ToJSON a => a -> Value
J.toJSON)
            (Text -> FieldNameText
Execute.FieldNameText Text
"columns")
            (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
<$> Vector (InsOrdHashMap FieldNameText OutputValue)
rows
      }