mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-18 13:02:11 +03:00
42e5205eb5
### Description This monster of a PR took way too long. As the title suggests, it reduces the schema context carried in the readers to the very strict minimum. In practice, that means that to build a source, we only require: - the global `SchemaContext` - the global `SchemaOptions` (soon to be renamed `SchemaSourceOptions`) - that source's `SourceInfo` Furthermore, _we no longer carry "default" customization options throughout the schema_. All customization information is extracted from the `SourceInfo`, when required. This prevents an entire category of bugs we had previously encountered, such as parts of the code using uninitialized / unupdated customization info. In turn, this meant that we could remove the explicit threading of the `SourceInfo` throughout the schema, since it is now always available through the reader context. Finally, this meant making a few adjustments to relay and actions as well, such as the introduction of a new separate "context" for actions, and a change to how we create some of the action-specific postgres scalar parsers. I'll highlight with review comments the areas of interest. PR-URL: https://github.com/hasura/graphql-engine-mono/pull/6709 GitOrigin-RevId: ea80fddcb24e2513779dd04b0b700a55f0028dd1
155 lines
5.7 KiB
Haskell
155 lines
5.7 KiB
Haskell
{-# LANGUAGE ApplicativeDo #-}
|
|
|
|
-- | MSSQL Schema IfMatched
|
|
--
|
|
-- This module contains the building blocks for parsing @if_matched@ clauses
|
|
-- (represented as 'IfMatched'), which in the MSSQL backend are used to
|
|
-- implement upsert functionality.
|
|
--
|
|
-- These are used by 'Hasura.Backends.MSSQL.Instances.Schema.backendInsertParser' to
|
|
-- construct a mssql-specific schema parser for insert (and upsert) mutations.
|
|
module Hasura.Backends.MSSQL.Schema.IfMatched
|
|
( ifMatchedFieldParser,
|
|
)
|
|
where
|
|
|
|
import Data.Has
|
|
import Data.Text.Extended
|
|
import Hasura.Backends.MSSQL.Types.Insert
|
|
import Hasura.Backends.MSSQL.Types.Internal (ScalarType (..))
|
|
import Hasura.GraphQL.Parser.Class
|
|
import Hasura.GraphQL.Schema.Backend
|
|
import Hasura.GraphQL.Schema.BoolExp
|
|
import Hasura.GraphQL.Schema.Common
|
|
import Hasura.GraphQL.Schema.Parser
|
|
( InputFieldsParser,
|
|
Kind (..),
|
|
Parser,
|
|
)
|
|
import Hasura.GraphQL.Schema.Parser qualified as P
|
|
import Hasura.GraphQL.Schema.Table
|
|
import Hasura.GraphQL.Schema.Typename
|
|
import Hasura.Name qualified as Name
|
|
import Hasura.Prelude
|
|
import Hasura.RQL.IR.BoolExp
|
|
import Hasura.RQL.IR.Value
|
|
import Hasura.RQL.Types.Backend
|
|
import Hasura.RQL.Types.Column
|
|
import Hasura.RQL.Types.SchemaCache
|
|
import Hasura.RQL.Types.Source
|
|
import Hasura.RQL.Types.SourceCustomization
|
|
import Hasura.RQL.Types.Table
|
|
import Hasura.SQL.Backend
|
|
import Language.GraphQL.Draft.Syntax qualified as G
|
|
|
|
-- | Field-parser for:
|
|
--
|
|
-- > if_matched: tablename_if_matched
|
|
-- >
|
|
-- > input tablename_if_matched {
|
|
-- > match_columns: [tablename_select_column!]
|
|
-- > update_columns: [tablename_update_columns!]
|
|
-- > where: tablename_bool_exp
|
|
-- > }
|
|
--
|
|
-- Note that the types ordinarily produced by this parser are only created if
|
|
-- the active role has /both/ select and update permissions to the table
|
|
-- @tablename@ defined /and/ these grant non-empty column permissions.
|
|
ifMatchedFieldParser ::
|
|
forall r m n.
|
|
( MonadBuildSchema 'MSSQL r m n,
|
|
AggregationPredicatesSchema 'MSSQL
|
|
) =>
|
|
TableInfo 'MSSQL ->
|
|
SchemaT r m (InputFieldsParser n (Maybe (IfMatched (UnpreparedValue 'MSSQL))))
|
|
ifMatchedFieldParser tableInfo = do
|
|
maybeObject <- ifMatchedObjectParser tableInfo
|
|
pure case maybeObject of
|
|
Nothing -> pure Nothing
|
|
Just object -> P.fieldOptional Name._if_matched (Just "upsert condition") object
|
|
|
|
-- | Parse a @tablename_if_matched@ object.
|
|
ifMatchedObjectParser ::
|
|
forall r m n.
|
|
( MonadBuildSchema 'MSSQL r m n,
|
|
AggregationPredicatesSchema 'MSSQL
|
|
) =>
|
|
TableInfo 'MSSQL ->
|
|
SchemaT r m (Maybe (Parser 'Input n (IfMatched (UnpreparedValue 'MSSQL))))
|
|
ifMatchedObjectParser tableInfo = runMaybeT do
|
|
-- Short-circuit if we don't have sufficient permissions.
|
|
sourceInfo :: SourceInfo 'MSSQL <- asks getter
|
|
roleName <- retrieve scRole
|
|
let customization = _siCustomization sourceInfo
|
|
mkTypename = runMkTypename $ _rscTypeNames customization
|
|
updatePerms <- hoistMaybe $ _permUpd $ getRolePermInfo roleName tableInfo
|
|
matchColumnsEnum <- MaybeT $ tableInsertMatchColumnsEnum tableInfo
|
|
lift do
|
|
updateColumnsEnum <- updateColumnsPlaceholderParser tableInfo
|
|
tableGQLName <- getTableGQLName tableInfo
|
|
let objectName = mkTypename $ tableGQLName <> Name.__if_matched
|
|
_imColumnPresets = partialSQLExpToUnpreparedValue <$> upiSet updatePerms
|
|
updateFilter = fmap partialSQLExpToUnpreparedValue <$> upiFilter updatePerms
|
|
objectDesc = G.Description $ "upsert condition type for table " <>> tableInfoName tableInfo
|
|
matchColumnsName = Name._match_columns
|
|
updateColumnsName = Name._update_columns
|
|
whereName = Name._where
|
|
whereExpParser <- boolExp tableInfo
|
|
pure $
|
|
P.object objectName (Just objectDesc) do
|
|
_imConditions <-
|
|
(\whereExp -> BoolAnd $ updateFilter : maybeToList whereExp)
|
|
<$> P.fieldOptional whereName Nothing whereExpParser
|
|
_imMatchColumns <-
|
|
P.fieldWithDefault matchColumnsName Nothing (G.VList []) (P.list matchColumnsEnum)
|
|
_imUpdateColumns <-
|
|
P.fieldWithDefault updateColumnsName Nothing (G.VList []) (P.list updateColumnsEnum) `P.bindFields` \cs ->
|
|
-- this can only happen if the placeholder was used
|
|
sequenceA cs `onNothing` parseError "erroneous column name"
|
|
|
|
pure $ IfMatched {..}
|
|
|
|
-- | Table insert_match_columns enum
|
|
--
|
|
-- Parser for an enum type that matches the columns that can be used
|
|
-- for insert match_columns for a given table.
|
|
-- Maps to the insert_match_columns object.
|
|
--
|
|
-- Return Nothing if there's no column the current user has "select"
|
|
-- permissions for.
|
|
tableInsertMatchColumnsEnum ::
|
|
forall r m n.
|
|
MonadBuildSourceSchema 'MSSQL r m n =>
|
|
TableInfo 'MSSQL ->
|
|
SchemaT r m (Maybe (Parser 'Both n (Column 'MSSQL)))
|
|
tableInsertMatchColumnsEnum tableInfo = do
|
|
sourceInfo :: SourceInfo 'MSSQL <- asks getter
|
|
let customization = _siCustomization sourceInfo
|
|
mkTypename = runMkTypename $ _rscTypeNames customization
|
|
tableGQLName <- getTableGQLName @'MSSQL tableInfo
|
|
columns <- tableSelectColumns tableInfo
|
|
let enumName = mkTypename $ tableGQLName <> Name.__insert_match_column
|
|
description =
|
|
Just $
|
|
G.Description $
|
|
"select match_columns of table " <>> tableInfoName tableInfo
|
|
pure $
|
|
P.enum enumName description
|
|
<$> nonEmpty
|
|
[ ( define $ ciName column,
|
|
ciColumn column
|
|
)
|
|
| column <- columns,
|
|
isMatchColumnValid column
|
|
]
|
|
where
|
|
define name =
|
|
P.Definition name (Just $ G.Description "column name") Nothing [] P.EnumValueInfo
|
|
|
|
-- | Check whether a column can be used for match_columns.
|
|
isMatchColumnValid :: ColumnInfo 'MSSQL -> Bool
|
|
isMatchColumnValid = \case
|
|
-- Unfortunately MSSQL does not support comparison for TEXT types.
|
|
ColumnInfo {ciType = ColumnScalar TextType} -> False
|
|
_ -> True
|