mirror of
https://github.com/Airsequel/AirGQL.git
synced 2025-07-30 09:47:01 +03:00
Implement introspection for by_pk queries
- Make column names containing "_by_pk" disallowed on the client side (I technically already did that on the server side with the previous commit, but this makes it nicer) - Implement the actual introspection logic - If a primary key has a "reserved" argument name (i.e. a name we use for built in airsequel arguments) like "filter" or "limit", then we rename that to pk from <name> to <name>_. This might technically still create collisions in the rare case where both <name> and <name>_ are columns that are part of the same primary key, but I don't think that matters in practice. The hasura playground doesn't seem to do any renaming (I tried it in the playground), so we could also not do that at all I guess...
This commit is contained in:
parent
8894f14002
commit
8febe6be43
2 changed files with 86 additions and 35 deletions
source/AirGQL
|
@ -30,7 +30,7 @@ import AirGQL.Introspection.Types (IntrospectionType)
|
|||
import AirGQL.Introspection.Types qualified as Type
|
||||
import AirGQL.Lib (
|
||||
AccessMode,
|
||||
ColumnEntry,
|
||||
ColumnEntry (isRowid, primary_key),
|
||||
GqlTypeName (full),
|
||||
TableEntry (columns, name),
|
||||
canRead,
|
||||
|
@ -41,6 +41,7 @@ import AirGQL.Lib (
|
|||
notnull,
|
||||
)
|
||||
import Control.Monad (MonadFail (fail))
|
||||
import Data.List qualified as List
|
||||
import Data.Text qualified as T
|
||||
import DoubleXEncoding (doubleXEncodeGql)
|
||||
import Language.GraphQL.Class (ToGraphQL (toGraphQL))
|
||||
|
@ -130,39 +131,80 @@ tableRowType table = do
|
|||
Type.object (doubleXEncodeGql table.name <> "_row") fields
|
||||
|
||||
|
||||
tableQueryCommonArgs :: TableEntry -> [Type.InputValue]
|
||||
tableQueryCommonArgs table =
|
||||
let
|
||||
fieldsWithOrderingTerm =
|
||||
table.columns <&> \columnEntry -> do
|
||||
let colName = columnEntry.column_name_gql
|
||||
Type.inputValue colName orderingTermType
|
||||
|
||||
orderType =
|
||||
Type.inputObject
|
||||
(doubleXEncodeGql table.name <> "_order_by")
|
||||
fieldsWithOrderingTerm
|
||||
& Type.withDescription
|
||||
( "Ordering options when selecting data from \""
|
||||
<> table.name
|
||||
<> "\"."
|
||||
)
|
||||
in
|
||||
[ Type.inputValue "filter" (filterType table)
|
||||
& Type.inputValueWithDescription "Filter to select specific rows"
|
||||
, Type.inputValue "order_by" (Type.list orderType)
|
||||
& Type.inputValueWithDescription "Columns used to sort the data"
|
||||
, Type.inputValue "limit" Type.typeInt
|
||||
& Type.inputValueWithDescription "Limit the number of returned rows"
|
||||
, Type.inputValue "offset" Type.typeInt
|
||||
& Type.inputValueWithDescription "The index to start returning rows from"
|
||||
]
|
||||
|
||||
|
||||
tableQueryField :: TableEntry -> Type.Field
|
||||
tableQueryField table = do
|
||||
let tableName = table.name
|
||||
tableQueryField table =
|
||||
Type.field
|
||||
(doubleXEncodeGql table.name)
|
||||
(Type.nonNull $ Type.list $ Type.nonNull $ tableRowType table)
|
||||
& Type.fieldWithDescription ("Rows from the table \"" <> table.name <> "\"")
|
||||
& Type.withArguments (tableQueryCommonArgs table)
|
||||
|
||||
let fieldsWithOrderingTerm =
|
||||
table.columns <&> \columnEntry -> do
|
||||
let colName = columnEntry.column_name_gql
|
||||
Type.inputValue colName orderingTermType
|
||||
|
||||
let orderType =
|
||||
Type.inputObject
|
||||
(doubleXEncodeGql tableName <> "_order_by")
|
||||
fieldsWithOrderingTerm
|
||||
& Type.withDescription
|
||||
( "Ordering options when selecting data from \""
|
||||
<> table.name
|
||||
<> "\"."
|
||||
)
|
||||
restrictedArgNames :: [Text]
|
||||
restrictedArgNames = ["limit", "offset", "order_by", "filter"]
|
||||
|
||||
|
||||
mkArgName :: Text -> Text
|
||||
mkArgName name = do
|
||||
let encoded = doubleXEncodeGql name
|
||||
if P.elem encoded restrictedArgNames
|
||||
then encoded <> "_"
|
||||
else encoded
|
||||
|
||||
|
||||
tableQueryByPk :: TableEntry -> Type.Field
|
||||
tableQueryByPk table = do
|
||||
let pks = List.filter (\col -> col.primary_key) table.columns
|
||||
|
||||
-- We filter out the rowid column, unless it is the only one
|
||||
let withoutRowid = case pks of
|
||||
[first] | first.isRowid -> [first]
|
||||
_ -> List.filter (\col -> P.not col.isRowid) pks
|
||||
|
||||
let pkArguments =
|
||||
withoutRowid <&> \column -> do
|
||||
let name = mkArgName column.column_name_gql
|
||||
Type.inputValue name $ Type.nonNull $ columnType column
|
||||
|
||||
Type.field
|
||||
(doubleXEncodeGql tableName)
|
||||
(Type.nonNull $ Type.list $ Type.nonNull $ tableRowType table)
|
||||
& Type.fieldWithDescription ("Rows from the table \"" <> tableName <> "\"")
|
||||
& Type.withArguments
|
||||
[ Type.inputValue "filter" (filterType table)
|
||||
& Type.inputValueWithDescription "Filter to select specific rows"
|
||||
, Type.inputValue "order_by" (Type.list orderType)
|
||||
& Type.inputValueWithDescription "Columns used to sort the data"
|
||||
, Type.inputValue "limit" Type.typeInt
|
||||
& Type.inputValueWithDescription "Limit the number of returned rows"
|
||||
, Type.inputValue "offset" Type.typeInt
|
||||
& Type.inputValueWithDescription "The index to start returning rows from"
|
||||
]
|
||||
(doubleXEncodeGql table.name <> "_by_pk")
|
||||
(tableRowType table)
|
||||
& Type.fieldWithDescription
|
||||
( "Rows from the table \""
|
||||
<> table.name
|
||||
<> "\", accessible by their primary key"
|
||||
)
|
||||
& Type.withArguments (tableQueryCommonArgs table)
|
||||
& Type.withArguments pkArguments
|
||||
|
||||
|
||||
mutationResponseType :: AccessMode -> TableEntry -> Type.IntrospectionType
|
||||
|
@ -318,7 +360,11 @@ getSchema accessMode tables = do
|
|||
let
|
||||
queryType =
|
||||
if canRead accessMode
|
||||
then tables <&> tableQueryField
|
||||
then
|
||||
P.fold
|
||||
[ tables <&> tableQueryField
|
||||
, tables <&> tableQueryByPk
|
||||
]
|
||||
else []
|
||||
|
||||
mutationType =
|
||||
|
|
|
@ -30,10 +30,6 @@ module AirGQL.Introspection.Types (
|
|||
deprecatedEnumValue,
|
||||
) where
|
||||
|
||||
import Data.HashMap.Strict (HashMap)
|
||||
import Data.HashMap.Strict qualified as HashMap
|
||||
import Language.GraphQL.Class (ToGraphQL (toGraphQL))
|
||||
import Language.GraphQL.Type qualified as Value
|
||||
import Protolude (
|
||||
Bool (False, True),
|
||||
Generic,
|
||||
|
@ -51,8 +47,14 @@ import Protolude (
|
|||
when,
|
||||
($),
|
||||
(&),
|
||||
(<>),
|
||||
)
|
||||
|
||||
import Data.HashMap.Strict (HashMap)
|
||||
import Data.HashMap.Strict qualified as HashMap
|
||||
import Language.GraphQL.Class (ToGraphQL (toGraphQL))
|
||||
import Language.GraphQL.Type qualified as Value
|
||||
|
||||
|
||||
data Schema = Schema
|
||||
{ description :: Maybe Text
|
||||
|
@ -334,7 +336,7 @@ field fieldName fieldType =
|
|||
|
||||
|
||||
withArguments :: [InputValue] -> Field -> Field
|
||||
withArguments argList (Field{..}) = Field{args = argList, ..}
|
||||
withArguments argList (Field{..}) = Field{args = args <> argList, ..}
|
||||
|
||||
|
||||
data InputValue = InputValue
|
||||
|
@ -448,6 +450,9 @@ typeBool :: IntrospectionType
|
|||
typeBool = scalar "Boolean"
|
||||
|
||||
|
||||
{-| Updates the `types` property of a schema to reference
|
||||
every type contained in other parts of the schema.
|
||||
-}
|
||||
collectSchemaTypes :: Schema -> Schema
|
||||
collectSchemaTypes schema = do
|
||||
let basic = [typeInt, typeString, typeBool, typeSchema]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue