-- |
-- 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.Text (encodeToLazyText)
import Data.HashMap.Strict.InsOrd qualified as InsOrdHashMap
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 (BigQueryDataset (..), BigQuerySourceConfig (..))
import Hasura.Base.Error
import Hasura.EncJSON
import Hasura.Prelude
import Hasura.RQL.DDL.Schema (RunSQLRes (..))
import Hasura.RQL.Types.BackendType
import Hasura.RQL.Types.Common
import Hasura.RQL.Types.Metadata
import Hasura.RQL.Types.SchemaCache
import Hasura.RQL.Types.SchemaCache.Build

data BigQueryRunSQL = BigQueryRunSQL
  { BigQueryRunSQL -> Text
_mrsSql :: Text,
    BigQueryRunSQL -> SourceName
_mrsSource :: SourceName
  }
  deriving (BigQueryRunSQL -> BigQueryRunSQL -> Bool
(BigQueryRunSQL -> BigQueryRunSQL -> Bool)
-> (BigQueryRunSQL -> BigQueryRunSQL -> Bool) -> Eq BigQueryRunSQL
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: BigQueryRunSQL -> BigQueryRunSQL -> Bool
== :: BigQueryRunSQL -> BigQueryRunSQL -> Bool
$c/= :: BigQueryRunSQL -> BigQueryRunSQL -> Bool
/= :: BigQueryRunSQL -> BigQueryRunSQL -> Bool
Eq, (forall x. BigQueryRunSQL -> Rep BigQueryRunSQL x)
-> (forall x. Rep BigQueryRunSQL x -> BigQueryRunSQL)
-> Generic BigQueryRunSQL
forall x. Rep BigQueryRunSQL x -> BigQueryRunSQL
forall x. BigQueryRunSQL -> Rep BigQueryRunSQL x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. BigQueryRunSQL -> Rep BigQueryRunSQL x
from :: forall x. BigQueryRunSQL -> Rep BigQueryRunSQL x
$cto :: forall x. Rep BigQueryRunSQL x -> BigQueryRunSQL
to :: forall x. Rep BigQueryRunSQL x -> BigQueryRunSQL
Generic, 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
$cshowsPrec :: Int -> BigQueryRunSQL -> ShowS
showsPrec :: Int -> BigQueryRunSQL -> ShowS
$cshow :: BigQueryRunSQL -> String
show :: BigQueryRunSQL -> String
$cshowList :: [BigQueryRunSQL] -> ShowS
showList :: [BigQueryRunSQL] -> ShowS
Show)

instance J.FromJSON BigQueryRunSQL where
  parseJSON :: Value -> Parser BigQueryRunSQL
parseJSON = Options -> Value -> Parser BigQueryRunSQL
forall a.
(Generic a, GFromJSON Zero (Rep a)) =>
Options -> Value -> Parser a
J.genericParseJSON Options
hasuraJSON

instance J.ToJSON BigQueryRunSQL where
  toJSON :: BigQueryRunSQL -> Value
toJSON = Options -> BigQueryRunSQL -> Value
forall a.
(Generic a, GToJSON' Value Zero (Rep a)) =>
Options -> a -> Value
J.genericToJSON Options
hasuraJSON
  toEncoding :: BigQueryRunSQL -> Encoding
toEncoding = Options -> BigQueryRunSQL -> Encoding
forall a.
(Generic a, GToJSON' Encoding Zero (Rep a)) =>
Options -> a -> Encoding
J.genericToEncoding Options
hasuraJSON

runSQL ::
  (MonadIO m, CacheRWM m, MonadError QErr m, MetadataM m) =>
  BigQueryRunSQL ->
  m EncJSON
runSQL :: forall (m :: * -> *).
(MonadIO m, CacheRWM m, MonadError QErr m, MetadataM m) =>
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 :: forall (m :: * -> *).
(MonadIO m, CacheRWM m, MonadError QErr m, MetadataM m) =>
BigQueryRunSQL -> m EncJSON
runDatabaseInspection (BigQueryRunSQL Text
_query SourceName
source) = do
  BigQuerySourceConfig {_scDatasets :: BigQuerySourceConfig -> [BigQueryDataset]
_scDatasets = [BigQueryDataset]
dataSets} <- 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
<> BigQueryDataset -> Text
getBigQueryDataset BigQueryDataset
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
<> BigQueryDataset -> Text
getBigQueryDataset BigQueryDataset
dataSet
            Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
".INFORMATION_SCHEMA.TABLES as t"
          | BigQueryDataset
dataSet <- [BigQueryDataset]
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_ :: forall (m :: * -> *).
(MonadIO m, CacheRWM m, MonadError QErr m, MetadataM m) =>
(RecordSet -> Value) -> BigQueryRunSQL -> m EncJSON
runSQL_ RecordSet -> Value
f (BigQueryRunSQL Text
query SourceName
source) = do
  BigQuerySourceConfig
sourceConfig <- forall (b :: BackendType) (m :: * -> *).
(CacheRM m, MonadError QErr m, Backend b, MetadataM m) =>
SourceName -> m (SourceConfig b)
askSourceConfig @'BigQuery SourceName
source
  Either ExecuteProblem (Job, RecordSet)
result <-
    BigQueryConnection
-> BigQuery -> m (Either ExecuteProblem (Job, RecordSet))
forall (m :: * -> *).
MonadIO m =>
BigQueryConnection
-> BigQuery -> m (Either ExecuteProblem (Job, RecordSet))
Execute.streamBigQuery
      (BigQuerySourceConfig -> BigQueryConnection
_scConnection BigQuerySourceConfig
sourceConfig)
      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 (Job, 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 a. QErr -> m a
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 (Job, RecordSet)
recordSet ->
      EncJSON -> m EncJSON
forall a. a -> m a
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 ((Job, RecordSet) -> RecordSet
forall a b. (a, b) -> b
snd (Job, RecordSet)
recordSet)))
        )

recordSetAsHeaderAndRows :: Execute.RecordSet -> J.Value
recordSetAsHeaderAndRows :: RecordSet -> Value
recordSetAsHeaderAndRows Execute.RecordSet {Vector (InsOrdHashMap FieldNameText OutputValue)
rows :: Vector (InsOrdHashMap FieldNameText OutputValue)
$sel:rows:RecordSet :: RecordSet -> 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
forall a b. Coercible a b => a -> b
coerce :: Execute.FieldNameText -> Text)) (InsOrdHashMap FieldNameText OutputValue -> [FieldNameText]
forall k v. InsOrdHashMap k v -> [k]
InsOrdHashMap.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]
InsOrdHashMap.elems) (Vector (InsOrdHashMap FieldNameText OutputValue)
-> [InsOrdHashMap FieldNameText OutputValue]
forall a. Vector a -> [a]
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)
$sel:rows:RecordSet :: RecordSet -> Vector (InsOrdHashMap FieldNameText OutputValue)
rows :: 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
InsOrdHashMap.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
      }