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
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