module AirGQL.Servant.SqlQuery (
  getAffectedTables,
  sqlQueryPostHandler,
)
where

import Protolude (
  Applicative (pure),
  Either (Left, Right),
  Maybe (Just, Nothing),
  MonadIO (liftIO),
  Semigroup ((<>)),
  otherwise,
  show,
  when,
  ($),
  (&),
  (*),
  (-),
  (/=),
  (<&>),
  (>),
 )
import Protolude qualified as P

import Data.Aeson.Key qualified as Key
import Data.Aeson.KeyMap qualified as KeyMap
import Data.Text (Text)
import Data.Text qualified as T
import Data.Time (diffUTCTime, getCurrentTime, nominalDiffTimeToSeconds)
import Database.SQLite.Simple qualified as SS
import Language.SQL.SimpleSQL.Parse (ParseError (peFormattedError))
import Language.SQL.SimpleSQL.Syntax (Statement (CreateTable))
import Servant.Server qualified as Servant
import System.Timeout (timeout)

import AirGQL.Config (defaultConfig, sqlTimeoutTime)
import AirGQL.Lib (
  SQLPost (query),
  TableEntryRaw (sql, tbl_name),
  getTables,
  lintTableCreationCode,
  parseSql,
  sqlDataToAesonValue,
 )
import AirGQL.Types.PragmaConf (PragmaConf, getSQLitePragmas)
import AirGQL.Types.SqlQueryPostResult (
  SqlQueryPostResult (
    SqlQueryPostResult,
    affectedTables,
    columns,
    errors,
    rows,
    runtimeSeconds
  ),
  resultWithErrors,
 )
import AirGQL.Utils (
  getMainDbPath,
  throwErr400WithMsg,
  withRetryConn,
 )


getAffectedTables :: [TableEntryRaw] -> [TableEntryRaw] -> [Text]
getAffectedTables :: [TableEntryRaw] -> [TableEntryRaw] -> [Text]
getAffectedTables [TableEntryRaw]
pre [TableEntryRaw]
post =
  let
    loop :: [TableEntryRaw] -> [TableEntryRaw] -> [Text]
loop [TableEntryRaw]
left [TableEntryRaw]
right = do
      case ([TableEntryRaw]
left, [TableEntryRaw]
right) of
        ([], [TableEntryRaw]
_) -> [TableEntryRaw]
right [TableEntryRaw] -> (TableEntryRaw -> Text) -> [Text]
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> TableEntryRaw -> Text
tbl_name
        ([TableEntryRaw]
_, []) -> [TableEntryRaw]
left [TableEntryRaw] -> (TableEntryRaw -> Text) -> [Text]
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> TableEntryRaw -> Text
tbl_name
        (TableEntryRaw
headLeft : [TableEntryRaw]
tailLeft, TableEntryRaw
headRight : [TableEntryRaw]
tailRight) ->
          case Text -> Text -> Ordering
forall a. Ord a => a -> a -> Ordering
P.compare TableEntryRaw
headLeft.tbl_name TableEntryRaw
headRight.tbl_name of
            Ordering
P.LT -> TableEntryRaw
headLeft.tbl_name Text -> [Text] -> [Text]
forall a. a -> [a] -> [a]
: [TableEntryRaw] -> [TableEntryRaw] -> [Text]
loop [TableEntryRaw]
tailLeft [TableEntryRaw]
right
            Ordering
P.GT -> TableEntryRaw
headRight.tbl_name Text -> [Text] -> [Text]
forall a. a -> [a] -> [a]
: [TableEntryRaw] -> [TableEntryRaw] -> [Text]
loop [TableEntryRaw]
left [TableEntryRaw]
tailRight
            Ordering
P.EQ
              | TableEntryRaw
headLeft.sql Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
/= TableEntryRaw
headRight.sql ->
                  TableEntryRaw
headLeft.tbl_name Text -> [Text] -> [Text]
forall a. a -> [a] -> [a]
: [TableEntryRaw] -> [TableEntryRaw] -> [Text]
loop [TableEntryRaw]
tailLeft [TableEntryRaw]
tailRight
              | Bool
otherwise ->
                  [TableEntryRaw] -> [TableEntryRaw] -> [Text]
loop [TableEntryRaw]
tailLeft [TableEntryRaw]
tailRight
  in
    [TableEntryRaw] -> [TableEntryRaw] -> [Text]
loop
      ((TableEntryRaw -> Text) -> [TableEntryRaw] -> [TableEntryRaw]
forall o a. Ord o => (a -> o) -> [a] -> [a]
P.sortOn TableEntryRaw -> Text
tbl_name [TableEntryRaw]
pre)
      ((TableEntryRaw -> Text) -> [TableEntryRaw] -> [TableEntryRaw]
forall o a. Ord o => (a -> o) -> [a] -> [a]
P.sortOn TableEntryRaw -> Text
tbl_name [TableEntryRaw]
post)


sqlQueryPostHandler
  :: PragmaConf
  -> Text
  -> SQLPost
  -> Servant.Handler SqlQueryPostResult
sqlQueryPostHandler :: PragmaConf -> Text -> SQLPost -> Handler SqlQueryPostResult
sqlQueryPostHandler PragmaConf
pragmaConf Text
dbId SQLPost
sqlPost = do
  let Int
maxSqlQueryLength :: P.Int = Int
100_000

  Bool -> Handler () -> Handler ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Text -> Int
T.length SQLPost
sqlPost.query Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
maxSqlQueryLength) (Handler () -> Handler ()) -> Handler () -> Handler ()
forall a b. (a -> b) -> a -> b
$ do
    Text -> Handler ()
forall a. Text -> Handler a
throwErr400WithMsg (Text -> Handler ()) -> Text -> Handler ()
forall a b. (a -> b) -> a -> b
$
      Text
"SQL query is too long ("
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall a b. (Show a, StringConv String b) => a -> b
show (Text -> Int
T.length SQLPost
sqlPost.query)
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" characters, maximum is "
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall a b. (Show a, StringConv String b) => a -> b
show Int
maxSqlQueryLength
        Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")"

  [Text]
validationErrors <- IO [Text] -> Handler [Text]
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO [Text] -> Handler [Text]) -> IO [Text] -> Handler [Text]
forall a b. (a -> b) -> a -> b
$ case Text -> Either ParseError Statement
parseSql SQLPost
sqlPost.query of
    Left ParseError
error -> [Text] -> IO [Text]
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure [String -> Text
T.pack ParseError
error.peFormattedError]
    Right statement :: Statement
statement@(CreateTable [Name]
_ [TableElement]
_) ->
      String -> (Connection -> IO [Text]) -> IO [Text]
forall a. String -> (Connection -> IO a) -> IO a
SS.withConnection (Text -> String
getMainDbPath Text
dbId) ((Connection -> IO [Text]) -> IO [Text])
-> (Connection -> IO [Text]) -> IO [Text]
forall a b. (a -> b) -> a -> b
$ \Connection
conn ->
        Maybe Connection -> Statement -> IO [Text]
lintTableCreationCode (Connection -> Maybe Connection
forall a. a -> Maybe a
Just Connection
conn) Statement
statement
    Either ParseError Statement
_ -> [Text] -> IO [Text]
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure []

  case [Text]
validationErrors of
    [] -> do
      let
        dbFilePath :: String
dbFilePath = Text -> String
getMainDbPath Text
dbId
        microsecondsPerSecond :: Int
microsecondsPerSecond = Int
1000000 :: P.Int

        timeoutTimeMicroseconds :: Int
timeoutTimeMicroseconds =
          Config
defaultConfig.sqlTimeoutTime
            Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
microsecondsPerSecond

      [Query]
sqlitePragmas <- IO [Query] -> Handler [Query]
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO [Query] -> Handler [Query]) -> IO [Query] -> Handler [Query]
forall a b. (a -> b) -> a -> b
$ PragmaConf -> IO [Query]
getSQLitePragmas PragmaConf
pragmaConf

      let
        performSqlOperations :: IO
  (Either
     Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
performSqlOperations =
          String
-> (Connection
    -> IO
         (Either
            Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
-> IO
     (Either
        Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
forall a. String -> (Connection -> IO a) -> IO a
withRetryConn String
dbFilePath ((Connection
  -> IO
       (Either
          Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
 -> IO
      (Either
         Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
-> (Connection
    -> IO
         (Either
            Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
-> IO
     (Either
        Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
forall a b. (a -> b) -> a -> b
$ \Connection
conn -> do
            [TableEntryRaw]
preTables <- Connection -> IO [TableEntryRaw]
getTables Connection
conn

            [Query] -> (Query -> IO ()) -> IO ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
t a -> (a -> f b) -> f ()
P.for_ [Query]
sqlitePragmas ((Query -> IO ()) -> IO ()) -> (Query -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ Connection -> Query -> IO ()
SS.execute_ Connection
conn
            Connection -> Query -> IO ()
SS.execute_ Connection
conn Query
"PRAGMA foreign_keys = True"

            let query :: Query
query = Text -> Query
SS.Query SQLPost
sqlPost.query

            [Text]
columnNames <- Connection -> Query -> (Statement -> IO [Text]) -> IO [Text]
forall a. Connection -> Query -> (Statement -> IO a) -> IO a
SS.withStatement Connection
conn Query
query ((Statement -> IO [Text]) -> IO [Text])
-> (Statement -> IO [Text]) -> IO [Text]
forall a b. (a -> b) -> a -> b
$ \Statement
statement -> do
              ColumnIndex
numCols <- Statement -> IO ColumnIndex
SS.columnCount Statement
statement
              [ColumnIndex] -> (ColumnIndex -> IO Text) -> IO [Text]
forall (t :: * -> *) (f :: * -> *) a b.
(Traversable t, Applicative f) =>
t a -> (a -> f b) -> f (t b)
P.for [ColumnIndex
0 .. (ColumnIndex
numCols ColumnIndex -> ColumnIndex -> ColumnIndex
forall a. Num a => a -> a -> a
- ColumnIndex
1)] ((ColumnIndex -> IO Text) -> IO [Text])
-> (ColumnIndex -> IO Text) -> IO [Text]
forall a b. (a -> b) -> a -> b
$ Statement -> ColumnIndex -> IO Text
SS.columnName Statement
statement

            Maybe [[SQLData]]
tableRowsMb :: Maybe [[SS.SQLData]] <-
              Int -> IO [[SQLData]] -> IO (Maybe [[SQLData]])
forall a. Int -> IO a -> IO (Maybe a)
timeout Int
timeoutTimeMicroseconds (IO [[SQLData]] -> IO (Maybe [[SQLData]]))
-> IO [[SQLData]] -> IO (Maybe [[SQLData]])
forall a b. (a -> b) -> a -> b
$ Connection -> Query -> IO [[SQLData]]
forall r. FromRow r => Connection -> Query -> IO [r]
SS.query_ Connection
conn Query
query
            Int
changes <- Connection -> IO Int
SS.changes Connection
conn

            [TableEntryRaw]
postTables <- Connection -> IO [TableEntryRaw]
getTables Connection
conn

            Either
  Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
-> IO
     (Either
        Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either
   Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
 -> IO
      (Either
         Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
-> Either
     Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
-> IO
     (Either
        Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
forall a b. (a -> b) -> a -> b
$ case Maybe [[SQLData]]
tableRowsMb of
              Just [[SQLData]]
tableRows ->
                ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
-> Either
     Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
forall a b. b -> Either a b
Right ([Text]
columnNames, [[SQLData]]
tableRows, Int
changes, [TableEntryRaw]
preTables, [TableEntryRaw]
postTables)
              Maybe [[SQLData]]
Nothing -> Text
-> Either
     Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
forall a b. a -> Either a b
Left Text
"Sql query execution timed out"

      UTCTime
startTime <- IO UTCTime -> Handler UTCTime
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO UTCTime
getCurrentTime
      Either
  Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
sqlResults <-
        IO
  (Either
     Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
-> Handler
     (Either
        Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO
   (Either
      Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
 -> Handler
      (Either
         Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
-> IO
     (Either
        Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
-> Handler
     (Either
        Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
forall a b. (a -> b) -> a -> b
$
          IO
  (Either
     Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
-> [Handler
      (Either
         Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))]
-> IO
     (Either
        Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
forall a. IO a -> [Handler a] -> IO a
P.catches
            IO
  (Either
     Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
performSqlOperations
            [ (SQLError
 -> IO
      (Either
         Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
-> Handler
     (Either
        Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
forall a e. Exception e => (e -> IO a) -> Handler a
P.Handler ((SQLError
  -> IO
       (Either
          Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
 -> Handler
      (Either
         Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
-> (SQLError
    -> IO
         (Either
            Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
-> Handler
     (Either
        Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
forall a b. (a -> b) -> a -> b
$
                \(SQLError
error :: SS.SQLError) -> Either
  Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
-> IO
     (Either
        Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either
   Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
 -> IO
      (Either
         Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
-> Either
     Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
-> IO
     (Either
        Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
forall a b. (a -> b) -> a -> b
$ Text
-> Either
     Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
forall a b. a -> Either a b
Left (Text
 -> Either
      Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
-> Text
-> Either
     Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
forall a b. (a -> b) -> a -> b
$ SQLError -> Text
forall a b. (Show a, StringConv String b) => a -> b
show SQLError
error
            , (ResultError
 -> IO
      (Either
         Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
-> Handler
     (Either
        Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
forall a e. Exception e => (e -> IO a) -> Handler a
P.Handler ((ResultError
  -> IO
       (Either
          Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
 -> Handler
      (Either
         Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
-> (ResultError
    -> IO
         (Either
            Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
-> Handler
     (Either
        Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
forall a b. (a -> b) -> a -> b
$
                \(ResultError
error :: SS.ResultError) -> Either
  Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
-> IO
     (Either
        Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either
   Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
 -> IO
      (Either
         Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
-> Either
     Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
-> IO
     (Either
        Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
forall a b. (a -> b) -> a -> b
$ Text
-> Either
     Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
forall a b. a -> Either a b
Left (Text
 -> Either
      Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
-> Text
-> Either
     Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
forall a b. (a -> b) -> a -> b
$ ResultError -> Text
forall a b. (Show a, StringConv String b) => a -> b
show ResultError
error
            , (FormatError
 -> IO
      (Either
         Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
-> Handler
     (Either
        Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
forall a e. Exception e => (e -> IO a) -> Handler a
P.Handler ((FormatError
  -> IO
       (Either
          Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
 -> Handler
      (Either
         Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
-> (FormatError
    -> IO
         (Either
            Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
-> Handler
     (Either
        Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
forall a b. (a -> b) -> a -> b
$
                \(FormatError
error :: SS.FormatError) -> Either
  Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
-> IO
     (Either
        Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either
   Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
 -> IO
      (Either
         Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])))
-> Either
     Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
-> IO
     (Either
        Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
forall a b. (a -> b) -> a -> b
$ Text
-> Either
     Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
forall a b. a -> Either a b
Left (Text
 -> Either
      Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw]))
-> Text
-> Either
     Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
forall a b. (a -> b) -> a -> b
$ FormatError -> Text
forall a b. (Show a, StringConv String b) => a -> b
show FormatError
error
            ]
      UTCTime
endTime <- IO UTCTime -> Handler UTCTime
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO UTCTime
getCurrentTime

      let measuredTime :: Pico
measuredTime =
            NominalDiffTime -> Pico
nominalDiffTimeToSeconds
              (UTCTime -> UTCTime -> NominalDiffTime
diffUTCTime UTCTime
endTime UTCTime
startTime)

      case Either
  Text ([Text], [[SQLData]], Int, [TableEntryRaw], [TableEntryRaw])
sqlResults of
        Left Text
error ->
          SqlQueryPostResult -> Handler SqlQueryPostResult
forall a. a -> Handler a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (SqlQueryPostResult -> Handler SqlQueryPostResult)
-> SqlQueryPostResult -> Handler SqlQueryPostResult
forall a b. (a -> b) -> a -> b
$ Pico -> [Text] -> SqlQueryPostResult
resultWithErrors Pico
measuredTime [Text
error]
        Right ([Text]
columnNames, [[SQLData]]
tableRows, Int
changes, [TableEntryRaw]
preTables, [TableEntryRaw]
postTables) -> do
          -- TODO: Use GQL error format {"message": "…", "code": …, …} instead
          let
            keys :: [Key]
keys = [Text]
columnNames [Text] -> (Text -> Key) -> [Key]
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> Text -> Key
Key.fromText

            rowList :: [KeyMap Value]
rowList =
              [[SQLData]]
tableRows
                [[SQLData]] -> ([SQLData] -> KeyMap Value) -> [KeyMap Value]
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \[SQLData]
row ->
                  [SQLData]
row
                    [SQLData] -> (SQLData -> Value) -> [Value]
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> Text -> SQLData -> Value
sqlDataToAesonValue Text
""
                    [Value] -> ([Value] -> [(Key, Value)]) -> [(Key, Value)]
forall a b. a -> (a -> b) -> b
& [Key] -> [Value] -> [(Key, Value)]
forall a b. [a] -> [b] -> [(a, b)]
P.zip [Key]
keys
                    [(Key, Value)] -> ([(Key, Value)] -> KeyMap Value) -> KeyMap Value
forall a b. a -> (a -> b) -> b
& [(Key, Value)] -> KeyMap Value
forall v. [(Key, v)] -> KeyMap v
KeyMap.fromList

            affectedTables :: [Text]
affectedTables =
              if Int
changes Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
0
                then [TableEntryRaw]
postTables [TableEntryRaw] -> (TableEntryRaw -> Text) -> [Text]
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> TableEntryRaw -> Text
tbl_name
                else [TableEntryRaw] -> [TableEntryRaw] -> [Text]
getAffectedTables [TableEntryRaw]
preTables [TableEntryRaw]
postTables

          SqlQueryPostResult -> Handler SqlQueryPostResult
forall a. a -> Handler a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (SqlQueryPostResult -> Handler SqlQueryPostResult)
-> SqlQueryPostResult -> Handler SqlQueryPostResult
forall a b. (a -> b) -> a -> b
$
            SqlQueryPostResult
              { $sel:rows:SqlQueryPostResult :: [KeyMap Value]
rows = [KeyMap Value]
rowList
              , $sel:columns:SqlQueryPostResult :: [Text]
columns = [Text]
columnNames
              , $sel:runtimeSeconds:SqlQueryPostResult :: Pico
runtimeSeconds = Pico
measuredTime
              , $sel:affectedTables:SqlQueryPostResult :: [Text]
affectedTables = [Text]
affectedTables
              , $sel:errors:SqlQueryPostResult :: [Text]
errors = []
              }
    [Text]
_ ->
      SqlQueryPostResult -> Handler SqlQueryPostResult
forall a. a -> Handler a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (SqlQueryPostResult -> Handler SqlQueryPostResult)
-> SqlQueryPostResult -> Handler SqlQueryPostResult
forall a b. (a -> b) -> a -> b
$
        Pico -> [Text] -> SqlQueryPostResult
resultWithErrors
          Pico
0
          [Text]
validationErrors