Skip to content

Commit 0797407

Browse files
rakeshkkyshahidhk
authored andcommitted
respect the nullability of columns in generated schema (fix #256) (#276)
1 parent adf973d commit 0797407

File tree

10 files changed

+63
-44
lines changed

10 files changed

+63
-44
lines changed

server/src-lib/Hasura/GraphQL/Resolve/BoolExp.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ parseColExp nt n val = do
8080
fldInfo <- getFldInfo nt n
8181
case fldInfo of
8282
Left pgColInfo -> RA.AVCol pgColInfo <$> parseOpExps val
83-
Right (relInfo, permExp, _) -> do
83+
Right (relInfo, permExp, _, _) -> do
8484
relBoolExp <- parseBoolExp val
8585
return $ RA.AVRel relInfo relBoolExp permExp
8686

server/src-lib/Hasura/GraphQL/Resolve/Context.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import Hasura.SQL.Value
4242
import qualified Hasura.SQL.DML as S
4343

4444
type FieldMap
45-
= Map.HashMap (G.NamedType, G.Name) (Either PGColInfo (RelInfo, S.BoolExp, Maybe Int))
45+
= Map.HashMap (G.NamedType, G.Name) (Either PGColInfo (RelInfo, S.BoolExp, Maybe Int, Bool))
4646

4747
data OrdTy
4848
= OAsc
@@ -64,7 +64,7 @@ type OrdByResolveCtx
6464

6565
getFldInfo
6666
:: (MonadError QErr m, MonadReader r m, Has FieldMap r)
67-
=> G.NamedType -> G.Name -> m (Either PGColInfo (RelInfo, S.BoolExp, Maybe Int))
67+
=> G.NamedType -> G.Name -> m (Either PGColInfo (RelInfo, S.BoolExp, Maybe Int, Bool))
6868
getFldInfo nt n = do
6969
fldMap <- asks getter
7070
onNothing (Map.lookup (nt,n) fldMap) $

server/src-lib/Hasura/GraphQL/Resolve/Mutation.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ convertReturning ty selSet =
4646
case _fName fld of
4747
"__typename" -> return $ RR.RExp $ G.unName $ G.unNamedType ty
4848
_ -> do
49-
PGColInfo col colTy <- getPGColInfo ty $ _fName fld
49+
PGColInfo col colTy _ <- getPGColInfo ty $ _fName fld
5050
return $ RR.RCol (col, colTy)
5151

5252
convertMutResp

server/src-lib/Hasura/GraphQL/Resolve/Select.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ fromSelSet fldTy flds =
4242
_ -> do
4343
fldInfo <- getFldInfo fldTy fldName
4444
case fldInfo of
45-
Left (PGColInfo pgCol colTy) -> return (rqlFldName, RS.FCol (pgCol, colTy))
46-
Right (relInfo, tableFilter, tableLimit) -> do
45+
Left (PGColInfo pgCol colTy _) -> return (rqlFldName, RS.FCol (pgCol, colTy))
46+
Right (relInfo, tableFilter, tableLimit, _) -> do
4747
let relTN = riRTable relInfo
4848
relSelData <- fromField relTN tableFilter tableLimit fld
4949
let annRel = RS.AnnRel (riName relInfo) (riType relInfo)
@@ -93,7 +93,7 @@ parseOrderBy v = do
9393
-- return $ map convOrdByElem enums
9494
-- undefined
9595
where
96-
convOrdByElem (PGColInfo col _, ordTy, nullsOrd) =
96+
convOrdByElem (PGColInfo col _ _, ordTy, nullsOrd) =
9797
S.OrderByItem (Left col)
9898
(Just $ convOrdTy ordTy)
9999
(Just $ convNullsOrd nullsOrd)

server/src-lib/Hasura/GraphQL/Schema.hs

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ instance Monoid TyAgg where
8383
mempty = TyAgg Map.empty Map.empty Map.empty
8484
mappend = (<>)
8585

86-
type SelField = Either PGColInfo (RelInfo, S.BoolExp, Maybe Int)
86+
type SelField = Either PGColInfo (RelInfo, S.BoolExp, Maybe Int, Bool)
8787

8888
qualTableToName :: QualifiedTable -> G.Name
8989
qualTableToName = G.Name <$> \case
@@ -95,7 +95,7 @@ isValidTableName = isValidName . qualTableToName
9595

9696
isValidField :: FieldInfo -> Bool
9797
isValidField = \case
98-
FIColumn (PGColInfo col _) -> isColEligible col
98+
FIColumn (PGColInfo col _ _) -> isColEligible col
9999
FIRelationship (RelInfo rn _ _ remTab _) -> isRelEligible rn remTab
100100
where
101101
isColEligible = isValidName . G.Name . getPGColTxt
@@ -114,6 +114,14 @@ mkValidConstraints = filter isValid
114114
isValid (TableConstraint _ n) =
115115
isValidName $ G.Name $ getConstraintTxt n
116116

117+
isRelNullable :: FieldInfoMap -> RelInfo -> Bool
118+
isRelNullable fim ri = isNullable
119+
where
120+
lCols = map fst $ riMapping ri
121+
allCols = getCols fim
122+
lColInfos = flip filter allCols $ \ci -> pgiName ci `elem` lCols
123+
isNullable = any pgiIsNullable lColInfos
124+
117125
mkCompExpName :: PGColType -> G.Name
118126
mkCompExpName pgColTy =
119127
G.Name $ T.pack (show pgColTy) <> "_comparison_exp"
@@ -175,11 +183,14 @@ mkCompExpInp colTy =
175183
]
176184

177185
mkPGColFld :: PGColInfo -> ObjFldInfo
178-
mkPGColFld (PGColInfo colName colTy) =
186+
mkPGColFld (PGColInfo colName colTy isNullable) =
179187
ObjFldInfo Nothing n Map.empty ty
180188
where
181189
n = G.Name $ getPGColTxt colName
182-
ty = G.toGT $ mkScalarTy colTy
190+
ty = bool notNullTy nullTy isNullable
191+
scalarTy = mkScalarTy colTy
192+
notNullTy = G.toGT $ G.toNT scalarTy
193+
nullTy = G.toGT scalarTy
183194

184195
-- where: table_bool_exp
185196
-- limit: Int
@@ -211,17 +222,18 @@ array_relationship(
211222
object_relationship: remote_table
212223
213224
-}
214-
mkRelFld :: RelInfo -> ObjFldInfo
215-
mkRelFld (RelInfo rn rTy _ remTab _) = case rTy of
225+
mkRelFld :: RelInfo -> Bool -> ObjFldInfo
226+
mkRelFld (RelInfo rn rTy _ remTab _) isNullable = case rTy of
216227
ArrRel ->
217228
ObjFldInfo (Just "An array relationship") (G.Name $ getRelTxt rn)
218229
(fromInpValL $ mkSelArgs remTab)
219230
(G.toGT $ G.toNT $ G.toLT $ G.toNT relTabTy)
220231
ObjRel ->
221232
ObjFldInfo (Just "An object relationship") (G.Name $ getRelTxt rn)
222233
Map.empty
223-
(G.toGT relTabTy)
234+
objRelTy
224235
where
236+
objRelTy = bool (G.toGT $ G.toNT relTabTy) (G.toGT relTabTy) isNullable
225237
relTabTy = mkTableTy remTab
226238

227239
{-
@@ -239,8 +251,8 @@ mkTableObj
239251
mkTableObj tn allowedFlds =
240252
mkObjTyInfo (Just desc) (mkTableTy tn) $ mapFromL _fiName flds
241253
where
242-
flds = map (either mkPGColFld (mkRelFld . fst')) allowedFlds
243-
fst' (a, _, _) = a
254+
flds = map (either mkPGColFld mkRelFld') allowedFlds
255+
mkRelFld' (relInfo, _, _, isNullable) = mkRelFld relInfo isNullable
244256
desc = G.Description $
245257
"columns and relationships of " <>> tn
246258

@@ -341,13 +353,13 @@ mkBoolExpInp tn fields =
341353
]
342354

343355
mkFldExpInp = \case
344-
Left (PGColInfo colName colTy) ->
356+
Left (PGColInfo colName colTy _) ->
345357
mk (G.Name $ getPGColTxt colName) (mkCompExpTy colTy)
346-
Right (RelInfo relName _ _ remTab _, _, _) ->
358+
Right (RelInfo relName _ _ remTab _, _, _, _) ->
347359
mk (G.Name $ getRelTxt relName) (mkBoolExpTy remTab)
348360

349361
mkPGColInp :: PGColInfo -> InpValInfo
350-
mkPGColInp (PGColInfo colName colTy) =
362+
mkPGColInp (PGColInfo colName colTy _) =
351363
InpValInfo Nothing (G.Name $ getPGColTxt colName) $
352364
G.toGT $ mkScalarTy colTy
353365

@@ -747,7 +759,7 @@ mkOrdByCtx tn cols =
747759
mkOrdByEnumsOfCol
748760
:: PGColInfo
749761
-> [(G.Name, Text, (PGColInfo, OrdTy, NullsOrder))]
750-
mkOrdByEnumsOfCol colInfo@(PGColInfo col _) =
762+
mkOrdByEnumsOfCol colInfo@(PGColInfo col _ _) =
751763
[ ( colN <> "_asc"
752764
, "in the ascending order of " <> col <<> ", nulls last"
753765
, (colInfo, OAsc, NLast)
@@ -831,7 +843,7 @@ mkGCtxRole' tn insColsM selFldsM updColsM delPermM constraints =
831843

832844
nameFromSelFld = \case
833845
Left colInfo -> G.Name $ getPGColTxt $ pgiName colInfo
834-
Right (relInfo, _, _) -> G.Name $ getRelTxt $ riName relInfo
846+
Right (relInfo, _, _, _) -> G.Name $ getRelTxt $ riName relInfo
835847

836848
-- helper
837849
mkColFldMap ty = mapFromL ((ty,) . nameFromSelFld) . map Left
@@ -947,7 +959,11 @@ getSelFlds tableCache fields role selPermInfo =
947959
let remTableSelPermM =
948960
Map.lookup role (tiRolePermInfoMap remTableInfo) >>= _permSel
949961
return $ flip fmap remTableSelPermM $
950-
\rmSelPermM -> Right $ (relInfo, spiFilter rmSelPermM, spiLimit rmSelPermM)
962+
\rmSelPermM -> Right ( relInfo
963+
, spiFilter rmSelPermM
964+
, spiLimit rmSelPermM
965+
, isRelNullable fields relInfo
966+
)
951967
where
952968
allowedCols = spiCols selPermInfo
953969
getTabInfo tn =
@@ -1011,7 +1027,7 @@ mkGCtxMapTable tableCache (TableInfo tn _ fields rolePerms constraints) = do
10111027
allCols = map pgiName colInfos
10121028
selFlds = flip map (toValidFieldInfos fields) $ \case
10131029
FIColumn pgColInfo -> Left pgColInfo
1014-
FIRelationship relInfo -> Right (relInfo, noFilter, Nothing)
1030+
FIRelationship relInfo -> Right (relInfo, noFilter, Nothing, isRelNullable fields relInfo)
10151031
noFilter = S.BELit True
10161032
adminRootFlds =
10171033
getRootFldsRole' tn constraints fields (Just (tn, [])) (Just (noFilter, Nothing, []))

server/src-lib/Hasura/RQL/DDL/Schema/Diff.hs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,24 @@ module Hasura.RQL.DDL.Schema.Diff
1818
, getSchemaChangeDeps
1919
) where
2020

21+
import Hasura.Prelude
2122
import Hasura.RQL.Types
2223
import Hasura.SQL.Types
23-
import Hasura.Prelude
2424

25-
import qualified Database.PG.Query as Q
25+
import qualified Database.PG.Query as Q
2626

2727
import Data.Aeson.Casing
2828
import Data.Aeson.TH
2929

30-
import qualified Data.HashMap.Strict as M
31-
import qualified Data.HashSet as HS
30+
import qualified Data.HashMap.Strict as M
31+
import qualified Data.HashSet as HS
3232

3333
data PGColMeta
3434
= PGColMeta
3535
{ pcmColumnName :: !PGCol
3636
, pcmOrdinalPosition :: !Int
3737
, pcmDataType :: !PGColType
38+
, pcmIsNullable :: !Bool
3839
} deriving (Show, Eq)
3940

4041
$(deriveJSON (aesonDrop 3 snakeCase){omitNothingFields=True} ''PGColMeta)
@@ -80,7 +81,7 @@ fetchTableMeta = do
8081
(SELECT
8182
table_schema,
8283
table_name,
83-
json_agg((SELECT r FROM (SELECT column_name, udt_name AS data_type, ordinal_position) r)) as columns
84+
json_agg((SELECT r FROM (SELECT column_name, udt_name AS data_type, ordinal_position, is_nullable::boolean) r)) as columns
8485
FROM
8586
information_schema.columns
8687
GROUP BY
@@ -141,8 +142,8 @@ getTableDiff oldtm newtm =
141142

142143
existingCols = getOverlap pcmOrdinalPosition oldCols newCols
143144

144-
pcmToPci (PGColMeta colName _ colType)
145-
= PGColInfo colName colType
145+
pcmToPci (PGColMeta colName _ colType isNullable)
146+
= PGColInfo colName colType isNullable
146147

147148
alteredCols =
148149
flip map (filter (uncurry (/=)) existingCols) $ \(pcmo, pcmn) ->

server/src-lib/Hasura/RQL/DDL/Schema/Table.hs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ getTableInfo qt@(QualifiedTable sn tn) isSystemDefined = do
6262

6363
-- Fetch the column details
6464
colData <- Q.catchE defaultTxErrorHandler $ Q.listQ [Q.sql|
65-
SELECT column_name, to_json(udt_name)
65+
SELECT column_name, to_json(udt_name), is_nullable::boolean
6666
FROM information_schema.columns
6767
WHERE table_schema = $1
6868
AND table_name = $2
@@ -76,7 +76,8 @@ getTableInfo qt@(QualifiedTable sn tn) isSystemDefined = do
7676
AND table_name = $2
7777
|] (sn, tn) False
7878
return $ mkTableInfo qt isSystemDefined rawConstraints $
79-
map (fmap Q.getAltJ) colData
79+
flip map colData $ \(colName, Q.AltJ colTy, isNull)
80+
-> (colName, colTy, isNull)
8081

8182
newtype TrackTable
8283
= TrackTable
@@ -142,7 +143,7 @@ processTableChanges ti tableDiff = do
142143
delFldFromCache (fromPGCol droppedCol) tn
143144

144145
-- In the newly added columns check that there is no conflict with relationships
145-
forM_ addedCols $ \colInfo@(PGColInfo colName _) ->
146+
forM_ addedCols $ \colInfo@(PGColInfo colName _ _) ->
146147
case M.lookup (fromPGCol colName) $ tiFieldInfoMap ti of
147148
Just (FIRelationship _) ->
148149
throw400 AlreadyExists $ "cannot add column " <> colName
@@ -152,7 +153,7 @@ processTableChanges ti tableDiff = do
152153

153154
sc <- askSchemaCache
154155
-- for rest of the columns
155-
forM_ alteredCols $ \(PGColInfo oColName oColTy, nci@(PGColInfo nColName nColTy)) ->
156+
forM_ alteredCols $ \(PGColInfo oColName oColTy _, nci@(PGColInfo nColName nColTy _)) ->
156157
if | oColName /= nColName ->
157158
throw400 NotSupported $ "column renames are not yet supported : " <>
158159
tn <<> "." <>> oColName

server/src-lib/Hasura/RQL/DML/Internal.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ checkOnColExp :: (P1C m)
182182
=> SelPermInfo -> AnnValS -> m AnnValS
183183
checkOnColExp spi annVal =
184184
case annVal of
185-
AVCol pci@(PGColInfo cn _) opExps -> do
185+
AVCol pci@(PGColInfo cn _ _) opExps -> do
186186
checkSelOnCol spi cn
187187
return $ AVCol pci opExps
188188
AVRel relInfo nesAnn _ -> do

server/src-lib/Hasura/RQL/GBoolExp.hs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ parseOpExps
227227
-> PGColInfo
228228
-> Value
229229
-> m [OpExpG a]
230-
parseOpExps valParser cim (PGColInfo cn colTy) (Object o) =
230+
parseOpExps valParser cim (PGColInfo cn colTy _) (Object o) =
231231
forM (M.toList o) $ \(k, v) -> do
232232
op <- parseOp k
233233
case (op, v) of
@@ -245,7 +245,7 @@ parseOpExps valParser cim (PGColInfo cn colTy) (Object o) =
245245
"incompatible column types : " <> cn <<> ", " <>> pgCol
246246
return $ OECol colOp pgCol
247247
(Right _, _) -> throw400 UnexpectedPayload "expecting a string for column operator"
248-
parseOpExps valParser _ (PGColInfo _ colTy) val = do
248+
parseOpExps valParser _ (PGColInfo _ colTy _) val = do
249249
annValOp <- parseAnnOpExpG valParser REQ colTy val
250250
return [OEVal annValOp]
251251

@@ -330,9 +330,9 @@ annColExp
330330
annColExp valueParser colInfoMap (ColExp fieldName colVal) = do
331331
colInfo <- askFieldInfo colInfoMap fieldName
332332
case colInfo of
333-
FIColumn (PGColInfo _ PGJSON) ->
333+
FIColumn (PGColInfo _ PGJSON _) ->
334334
throwError (err400 UnexpectedPayload "JSON column can not be part of where clause")
335-
FIColumn (PGColInfo _ PGJSONB) ->
335+
FIColumn (PGColInfo _ PGJSONB _) ->
336336
throwError (err400 UnexpectedPayload "JSONB column can not be part of where clause")
337337
FIColumn pgi ->
338338
AVCol pgi <$> parseOpExps valueParser colInfoMap pgi colVal
@@ -356,7 +356,7 @@ convColRhs
356356
=> BoolExpBuilder m a
357357
-> S.Qual -> AnnValO a -> m (AnnValG S.BoolExp)
358358
convColRhs bExpBuilder tableQual annVal = case annVal of
359-
AVCol pci@(PGColInfo cn _) opExps -> do
359+
AVCol pci@(PGColInfo cn _ _) opExps -> do
360360
let qualColExp = S.SEQIden $ S.QIden tableQual (toIden cn)
361361
bExps <- forM opExps $ \case
362362
OEVal annOpValExp -> bExpBuilder qualColExp annOpValExp

server/src-lib/Hasura/RQL/Types/SchemaCache.hs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,9 @@ type QTemplateCache = M.HashMap TQueryName QueryTemplateInfo
179179

180180
data PGColInfo
181181
= PGColInfo
182-
{ pgiName :: !PGCol
183-
, pgiType :: !PGColType
182+
{ pgiName :: !PGCol
183+
, pgiType :: !PGColType
184+
, pgiIsNullable :: !Bool
184185
} deriving (Show, Eq)
185186

186187
$(deriveToJSON (aesonDrop 3 snakeCase) ''PGColInfo)
@@ -371,13 +372,13 @@ data TableInfo
371372
$(deriveToJSON (aesonDrop 2 snakeCase) ''TableInfo)
372373

373374
mkTableInfo :: QualifiedTable -> Bool -> [(ConstraintType, ConstraintName)]
374-
-> [(PGCol, PGColType)] -> TableInfo
375+
-> [(PGCol, PGColType, Bool)] -> TableInfo
375376
mkTableInfo tn isSystemDefined rawCons cols =
376377
TableInfo tn isSystemDefined colMap (M.fromList []) constraints
377378
where
378379
constraints = flip map rawCons $ uncurry TableConstraint
379380
colMap = M.fromList $ map f cols
380-
f (cn, ct) = (fromPGCol cn, FIColumn $ PGColInfo cn ct)
381+
f (cn, ct, b) = (fromPGCol cn, FIColumn $ PGColInfo cn ct b)
381382

382383
type TableCache = M.HashMap QualifiedTable TableInfo -- info of all tables
383384

0 commit comments

Comments
 (0)