mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-24 07:52:14 +03:00
342391f39d
This upgrades the version of Ormolu required by the HGE repository to v0.5.0.1, and reformats all code accordingly. Ormolu v0.5 reformats code that uses infix operators. This is mostly useful, adding newlines and indentation to make it clear which operators are applied first, but in some cases, it's unpleasant. To make this easier on the eyes, I had to do the following: * Add a few fixity declarations (search for `infix`) * Add parentheses to make precedence clear, allowing Ormolu to keep everything on one line * Rename `relevantEq` to `(==~)` in #6651 and set it to `infix 4` * Add a few _.ormolu_ files (thanks to @hallettj for helping me get started), mostly for Autodocodec operators that don't have explicit fixity declarations In general, I think these changes are quite reasonable. They mostly affect indentation. PR-URL: https://github.com/hasura/graphql-engine-mono/pull/6675 GitOrigin-RevId: cd47d87f1d089fb0bc9dcbbe7798dbceedcd7d83
161 lines
5.7 KiB
Haskell
161 lines
5.7 KiB
Haskell
-- | Postgres Execute Prepare
|
|
--
|
|
-- Deals with translating (session) variables to SQL expressions. Uses a state
|
|
-- monad to keep track of things like variables and generating fresh variable
|
|
-- names.
|
|
--
|
|
-- See 'Hasura.Backends.Postgres.Instances.Execute'.
|
|
module Hasura.Backends.Postgres.Execute.Prepare
|
|
( PlanVariables,
|
|
PrepArgMap,
|
|
PlanningSt (..),
|
|
ExecutionPlan,
|
|
ExecutionStep (..),
|
|
initPlanningSt,
|
|
prepareWithPlan,
|
|
prepareWithoutPlan,
|
|
withUserVars,
|
|
)
|
|
where
|
|
|
|
import Data.Aeson qualified as J
|
|
import Data.HashMap.Strict qualified as Map
|
|
import Data.IntMap qualified as IntMap
|
|
import Data.Text.Extended
|
|
import Database.PG.Query qualified as PG
|
|
import Hasura.Backends.Postgres.Connection.MonadTx
|
|
import Hasura.Backends.Postgres.SQL.DML qualified as S
|
|
import Hasura.Backends.Postgres.SQL.Value
|
|
import Hasura.Backends.Postgres.Translate.Column
|
|
import Hasura.Backends.Postgres.Types.Column
|
|
import Hasura.Base.Error
|
|
( Code (NotFound),
|
|
QErr,
|
|
throw400,
|
|
)
|
|
import Hasura.GraphQL.Execute.Backend
|
|
import Hasura.GraphQL.Parser.Names
|
|
import Hasura.Prelude
|
|
import Hasura.RQL.IR.Value
|
|
import Hasura.RQL.Types.Column
|
|
import Hasura.SQL.Backend
|
|
import Hasura.Session
|
|
( SessionVariables,
|
|
UserInfo (_uiSession),
|
|
getSessionVariableValue,
|
|
sessionVariableToText,
|
|
)
|
|
import Language.GraphQL.Draft.Syntax qualified as G
|
|
|
|
type PlanVariables = Map.HashMap G.Name Int
|
|
|
|
-- | The value is (PG.PrepArg, PGScalarValue) because we want to log the human-readable value of the
|
|
-- prepared argument and not the binary encoding in PG format
|
|
type PrepArgMap = IntMap.IntMap (PG.PrepArg, PGScalarValue)
|
|
|
|
data PlanningSt = PlanningSt
|
|
{ _psArgNumber :: Int,
|
|
_psVariables :: PlanVariables,
|
|
_psPrepped :: PrepArgMap
|
|
}
|
|
deriving stock (Eq, Show)
|
|
|
|
initPlanningSt :: PlanningSt
|
|
initPlanningSt = PlanningSt 2 Map.empty IntMap.empty
|
|
|
|
-- | If we're preparing a value with planning state, we favour referring to
|
|
-- values by their prepared argument index. If the value refers to a session
|
|
-- value, we look for it in prepared value (1) and access the particular keys
|
|
-- using the JSONB @->>@ accessor.
|
|
prepareWithPlan ::
|
|
( MonadState PlanningSt m,
|
|
MonadError QErr m
|
|
) =>
|
|
UserInfo ->
|
|
UnpreparedValue ('Postgres pgKind) ->
|
|
m S.SQLExp
|
|
prepareWithPlan userInfo = \case
|
|
UVParameter varInfoM ColumnValue {..} -> do
|
|
argNum <- maybe getNextArgNum (getVarArgNum . getName) varInfoM
|
|
addPrepArg argNum (binEncoder cvValue, cvValue)
|
|
return $ toPrepParam argNum (unsafePGColumnToBackend cvType)
|
|
UVSessionVar ty sessVar -> do
|
|
-- For queries, we need to make sure the session variables are passed. However,
|
|
-- we want to keep them as variables in the resulting SQL in order to keep
|
|
-- hitting query caching for similar queries.
|
|
_ <-
|
|
getSessionVariableValue sessVar (_uiSession userInfo)
|
|
`onNothing` throw400
|
|
NotFound
|
|
("missing session variable: " <>> sessionVariableToText sessVar)
|
|
let sessVarVal =
|
|
S.SEOpApp
|
|
(S.SQLOp "->>")
|
|
[currentSessionExp, S.SELit $ sessionVariableToText sessVar]
|
|
pure $ withTypeAnn ty sessVarVal
|
|
UVLiteral sqlExp -> pure sqlExp
|
|
UVSession -> pure currentSessionExp
|
|
where
|
|
currentSessionExp = S.SEPrep 1
|
|
|
|
-- | If we're /not/ using a prepared statement, substitution is pretty naïve:
|
|
-- we resolve session variable names, ignore parameter names, and substitute
|
|
-- into the 'S.SQLExp'.
|
|
prepareWithoutPlan ::
|
|
(MonadError QErr m) =>
|
|
UserInfo ->
|
|
UnpreparedValue ('Postgres pgKind) ->
|
|
m S.SQLExp
|
|
prepareWithoutPlan userInfo = \case
|
|
UVParameter _ cv -> pure $ toTxtValue cv
|
|
UVLiteral sqlExp -> pure sqlExp
|
|
UVSession -> pure $ sessionInfoJsonExp $ _uiSession userInfo
|
|
UVSessionVar ty sessVar -> do
|
|
let maybeSessionVariableValue =
|
|
getSessionVariableValue sessVar (_uiSession userInfo)
|
|
sessionVariableValue <-
|
|
fmap S.SELit
|
|
<$> onNothing maybeSessionVariableValue
|
|
$ throw400 NotFound
|
|
$ "missing session variable: " <>> sessionVariableToText sessVar
|
|
pure $ withTypeAnn ty sessionVariableValue
|
|
|
|
-- | The map of user session variables is always given the number (1) as its
|
|
-- variable argument number (see 'getVarArgNum'). If we want to refer to a
|
|
-- particular variable in this map, we use JSONB functions to interrogate
|
|
-- variable (1).
|
|
withUserVars :: SessionVariables -> PrepArgMap -> PrepArgMap
|
|
withUserVars usrVars list =
|
|
let usrVarsAsPgScalar = PGValJSON $ PG.JSON $ J.toJSON usrVars
|
|
prepArg = PG.toPrepVal (PG.ViaJSON usrVars)
|
|
in IntMap.insert 1 (prepArg, usrVarsAsPgScalar) list
|
|
|
|
-- | In prepared statements, we refer to variables by a number, not their name.
|
|
-- If the statement already refers to a variable, then we'll already have a
|
|
-- number for it, and so we just return that. Otherwise, we produce a new
|
|
-- number, and that will refer to the variable from now on.
|
|
getVarArgNum :: (MonadState PlanningSt m) => G.Name -> m Int
|
|
getVarArgNum var = do
|
|
PlanningSt curArgNum vars prepped <- get
|
|
Map.lookup var vars `onNothing` do
|
|
put $ PlanningSt (curArgNum + 1) (Map.insert var curArgNum vars) prepped
|
|
pure curArgNum
|
|
|
|
-- | Add a prepared argument to the prepared argument map. These are keyed by
|
|
-- the variable argument numbers, which can be computed using 'getVarArgNum'.
|
|
addPrepArg ::
|
|
(MonadState PlanningSt m) =>
|
|
Int ->
|
|
(PG.PrepArg, PGScalarValue) ->
|
|
m ()
|
|
addPrepArg argNum arg = modify \s ->
|
|
s {_psPrepped = IntMap.insert argNum arg (_psPrepped s)}
|
|
|
|
-- | Get '_psArgNumber' from inside the 'PlanningSt' and increment it for the
|
|
-- next operation. Think of this as a pure analogue to 'Data.Unique.newUnique'.
|
|
getNextArgNum :: (MonadState PlanningSt m) => m Int
|
|
getNextArgNum = state \s ->
|
|
( _psArgNumber s,
|
|
s {_psArgNumber = _psArgNumber s + 1}
|
|
)
|