mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-17 20:41:49 +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
167 lines
7.0 KiB
Haskell
167 lines
7.0 KiB
Haskell
-- | Postgres DDL Table
|
||
--
|
||
-- Used to fill up the enum values field of 'Hasura.RQL.Types.Table.TableCoreInfoG'.
|
||
--
|
||
-- See 'Hasura.RQL.Types.Eventing.Backend'.
|
||
module Hasura.Backends.Postgres.DDL.Table
|
||
( fetchAndValidateEnumValues,
|
||
)
|
||
where
|
||
|
||
import Control.Monad.Trans.Control (MonadBaseControl)
|
||
import Control.Monad.Validate
|
||
import Data.HashMap.Strict qualified as Map
|
||
import Data.List (delete)
|
||
import Data.List.NonEmpty qualified as NE
|
||
import Data.Sequence qualified as Seq
|
||
import Data.Sequence.NonEmpty qualified as NESeq
|
||
import Data.Text.Extended
|
||
import Database.PG.Query qualified as PG
|
||
import Hasura.Backends.Postgres.Connection
|
||
import Hasura.Backends.Postgres.SQL.DML
|
||
import Hasura.Backends.Postgres.SQL.Types
|
||
import Hasura.Base.Error
|
||
import Hasura.Prelude
|
||
import Hasura.RQL.Types.Backend
|
||
import Hasura.RQL.Types.Column
|
||
import Hasura.RQL.Types.Table
|
||
import Hasura.SQL.Backend
|
||
import Hasura.SQL.Types
|
||
import Hasura.Server.Utils
|
||
import Language.GraphQL.Draft.Syntax qualified as G
|
||
|
||
data EnumTableIntegrityError (b :: BackendType)
|
||
= EnumTablePostgresError Text
|
||
| EnumTableMissingPrimaryKey
|
||
| EnumTableMultiColumnPrimaryKey [PGCol]
|
||
| EnumTableNonTextualPrimaryKey (RawColumnInfo b)
|
||
| EnumTableNoEnumValues
|
||
| EnumTableInvalidEnumValueNames (NE.NonEmpty Text)
|
||
| EnumTableNonTextualCommentColumn (RawColumnInfo b)
|
||
| EnumTableTooManyColumns [PGCol]
|
||
|
||
fetchAndValidateEnumValues ::
|
||
forall pgKind m.
|
||
(Backend ('Postgres pgKind), MonadIO m, MonadBaseControl IO m) =>
|
||
PGSourceConfig ->
|
||
QualifiedTable ->
|
||
Maybe (PrimaryKey ('Postgres pgKind) (RawColumnInfo ('Postgres pgKind))) ->
|
||
[RawColumnInfo ('Postgres pgKind)] ->
|
||
m (Either QErr EnumValues)
|
||
fetchAndValidateEnumValues pgSourceConfig tableName maybePrimaryKey columnInfos =
|
||
runExceptT $
|
||
either (throw400 ConstraintViolation . showErrors) pure =<< runValidateT fetchAndValidate
|
||
where
|
||
fetchAndValidate ::
|
||
(MonadIO n, MonadBaseControl IO n, MonadValidate [EnumTableIntegrityError ('Postgres pgKind)] n) =>
|
||
n EnumValues
|
||
fetchAndValidate = do
|
||
maybePrimaryKeyColumn <- tolerate validatePrimaryKey
|
||
maybeCommentColumn <- validateColumns maybePrimaryKeyColumn
|
||
case maybePrimaryKeyColumn of
|
||
Nothing -> refute mempty
|
||
Just primaryKeyColumn -> do
|
||
result <-
|
||
runPgSourceReadTx pgSourceConfig $
|
||
runValidateT $
|
||
fetchEnumValuesFromDb tableName primaryKeyColumn maybeCommentColumn
|
||
case result of
|
||
Left e -> (refute . pure . EnumTablePostgresError . qeError) e
|
||
Right (Left vErrs) -> refute vErrs
|
||
Right (Right r) -> pure r
|
||
where
|
||
validatePrimaryKey = case maybePrimaryKey of
|
||
Nothing -> refute [EnumTableMissingPrimaryKey]
|
||
Just primaryKey -> case _pkColumns primaryKey of
|
||
column NESeq.:<|| Seq.Empty -> case rciType column of
|
||
PGText -> pure column
|
||
_ -> refute [EnumTableNonTextualPrimaryKey column]
|
||
columns -> refute [EnumTableMultiColumnPrimaryKey $ map rciName (toList columns)]
|
||
|
||
validateColumns primaryKeyColumn = do
|
||
let nonPrimaryKeyColumns = maybe columnInfos (`delete` columnInfos) primaryKeyColumn
|
||
case nonPrimaryKeyColumns of
|
||
[] -> pure Nothing
|
||
[column] -> case rciType column of
|
||
PGText -> pure $ Just column
|
||
_ -> dispute [EnumTableNonTextualCommentColumn column] $> Nothing
|
||
columns -> dispute [EnumTableTooManyColumns $ map rciName columns] $> Nothing
|
||
|
||
showErrors :: [EnumTableIntegrityError ('Postgres pgKind)] -> Text
|
||
showErrors allErrors =
|
||
"the table " <> tableName <<> " cannot be used as an enum " <> reasonsMessage
|
||
where
|
||
reasonsMessage = makeReasonMessage allErrors showOne
|
||
|
||
showOne :: EnumTableIntegrityError ('Postgres pgKind) -> Text
|
||
showOne = \case
|
||
EnumTablePostgresError err -> "postgres error: " <> err
|
||
EnumTableMissingPrimaryKey -> "the table must have a primary key"
|
||
EnumTableMultiColumnPrimaryKey cols ->
|
||
"the table’s primary key must not span multiple columns ("
|
||
<> commaSeparated (sort cols)
|
||
<> ")"
|
||
EnumTableNonTextualPrimaryKey colInfo -> typeMismatch "primary key" colInfo PGText
|
||
EnumTableNoEnumValues -> "the table must have at least one row"
|
||
EnumTableInvalidEnumValueNames values ->
|
||
let pluralString = " are not valid GraphQL enum value names"
|
||
valuesString = case NE.reverse (NE.sort values) of
|
||
value NE.:| [] -> "value " <> value <<> " is not a valid GraphQL enum value name"
|
||
value2 NE.:| [value1] -> "values " <> value1 <<> " and " <> value2 <<> pluralString
|
||
lastValue NE.:| otherValues ->
|
||
"values "
|
||
<> commaSeparated (reverse otherValues)
|
||
<> ", and "
|
||
<> lastValue <<> pluralString
|
||
in "the " <> valuesString
|
||
EnumTableNonTextualCommentColumn colInfo -> typeMismatch "comment column" colInfo PGText
|
||
EnumTableTooManyColumns cols ->
|
||
"the table must have exactly one primary key and optionally one comment column, not "
|
||
<> tshow (length cols)
|
||
<> " columns ("
|
||
<> commaSeparated (sort cols)
|
||
<> ")"
|
||
where
|
||
typeMismatch description colInfo expected =
|
||
"the table’s "
|
||
<> description
|
||
<> " ("
|
||
<> rciName colInfo <<> ") must have type "
|
||
<> expected <<> ", not type " <>> rciType colInfo
|
||
|
||
fetchEnumValuesFromDb ::
|
||
forall pgKind m.
|
||
(MonadTx m, MonadValidate [EnumTableIntegrityError ('Postgres pgKind)] m) =>
|
||
QualifiedTable ->
|
||
RawColumnInfo ('Postgres pgKind) ->
|
||
Maybe (RawColumnInfo ('Postgres pgKind)) ->
|
||
m EnumValues
|
||
fetchEnumValuesFromDb tableName primaryKeyColumn maybeCommentColumn = do
|
||
let nullExtr = Extractor SENull Nothing
|
||
commentExtr = maybe nullExtr (mkExtr . rciName) maybeCommentColumn
|
||
query =
|
||
PG.fromBuilder $
|
||
toSQL
|
||
mkSelect
|
||
{ selFrom = Just $ mkSimpleFromExp tableName,
|
||
selExtr = [mkExtr (rciName primaryKeyColumn), commentExtr]
|
||
}
|
||
rawEnumValues <- liftTx $ PG.withQE defaultTxErrorHandler query () True
|
||
when (null rawEnumValues) $ dispute [EnumTableNoEnumValues]
|
||
let enumValues = flip map rawEnumValues $
|
||
\(enumValueText, comment) ->
|
||
case mkValidEnumValueName enumValueText of
|
||
Nothing -> Left enumValueText
|
||
Just enumValue -> Right (EnumValue enumValue, EnumValueInfo comment)
|
||
badNames = lefts enumValues
|
||
validEnums = rights enumValues
|
||
case NE.nonEmpty badNames of
|
||
Just someBadNames -> refute [EnumTableInvalidEnumValueNames someBadNames]
|
||
Nothing -> pure $ Map.fromList validEnums
|
||
where
|
||
-- https://graphql.github.io/graphql-spec/June2018/#EnumValue
|
||
mkValidEnumValueName name =
|
||
if name `elem` ["true", "false", "null"]
|
||
then Nothing
|
||
else G.mkName name
|