Repository: hasura/graphql-engine
File: server/src-lib/Hasura/GraphQL/Resolve/Mutation.hs (Haskell)

Code before:
    args = _fArguments fld
    resolvedPreSetItems = Map.toList $ fmap partialSQLExpToUnresolvedVal preSetCols
    unresolvedPermFilter = fmapAnnBoolExp partialSQLExpToUnresolvedVal filterExp

    resolveUpdateOperator operator resolveAction =
      (operator,) <$> withArgM args operator resolveAction

    combineUpdateExpressions updateExps = do
      let allOperatorNames = map fst updateExps
          updateItems = mapMaybe (\(op, itemsM) -> (op,) <$> itemsM) updateExps
      -- Atleast any one of operator is expected or preset expressions shouldn't be empty
      if null updateItems && null resolvedPreSetItems then
        throwVE $ "atleast any one of " <> showNames allOperatorNames <> " is expected"
      else do
        let itemsWithOps = concatMap (\(op, items) -> map (second (op,)) items) updateItems
            itemTuples = OMap.toList $ OMap.groupTuples itemsWithOps
            columnsWithMultiOps = map (second (map fst . NESeq.toList)) $
                                  filter ((>1) . NESeq.length . snd) itemTuples
        -- A column shouldn't be present in more than one operator.
        -- If present, then generated UPDATE statement throws unexpected query error
        if null columnsWithMultiOps then
          pure $ resolvedPreSetItems <> map (second (snd . NESeq.head)) itemTuples
        else throwVE $ "column found in multiple operators; "
                       <> T.intercalate ". "
                          (map (\(col, ops) -> col <<> " in " <> showNames ops)
                               columnsWithMultiOps)


convertUpdate
  :: ( MonadReusability m, MonadError QErr m
     , MonadReader r m, Has FieldMap r
     , Has OrdByCtx r, Has SQLGenCtx r
     )
  => UpdOpCtx -- the update context
  -> Field -- the mutation field
  -> m RespTx
convertUpdate opCtx fld = do
  annUpdUnresolved <- convertUpdateP1 opCtx fld
  (annUpdResolved, prepArgs) <- withPrepArgs $ RU.traverseAnnUpd
                                resolveValPrep annUpdUnresolved
  strfyNum <- stringifyNum <$> asks getter
  let whenNonEmptyItems = return $ RU.updateQueryToTx strfyNum
                          (annUpdResolved, prepArgs)
      whenEmptyItems    = return $ return $
                          buildEmptyMutResp $ RU.uqp1MutFlds annUpdResolved
   -- if there are not set items then do not perform
   -- update and return empty mutation response
  bool whenNonEmptyItems whenEmptyItems $ null $ RU.uqp1SetExps annUpdResolved

convertDelete
  :: ( MonadReusability m, MonadError QErr m

Review comment (suggestion):
This generally looks good to me, but one suggestion I have to make this a little more data-/type-driven would be to take advantage of the result of `OMap.groupTuples` more aggressively. That operation produces a map with a shape like `InsOrdHashMap k (NESeq v)`, and from there you can very naturally write a function of type

```haskell
(MonadError QErr m) => k -> NESeq v -> m v
```

which raises an error if there are multiple elements and returns a single element otherwise. Then you can hand that function to `OMap.traverseWithKey` to get an `OMap k v` out, which is, in effect, the “parsed” representation of that input. 🙂 

Code after:
    UpdOpCtx tn _ colGNameMap filterExp preSetCols = opCtx
    args = _fArguments fld
    resolvedPreSetItems = Map.toList $ fmap partialSQLExpToUnresolvedVal preSetCols
    unresolvedPermFilter = fmapAnnBoolExp partialSQLExpToUnresolvedVal filterExp

    resolveUpdateOperator operator resolveAction =
      (operator,) <$> withArgM args operator resolveAction

    combineUpdateExpressions updateExps = do
      let allOperatorNames = map fst updateExps
          updateItems = mapMaybe (\(op, itemsM) -> (op,) <$> itemsM) updateExps
      -- Atleast any one of operator is expected or preset expressions shouldn't be empty
      if null updateItems && null resolvedPreSetItems then
        throwVE $ "atleast any one of " <> showNames allOperatorNames <> " is expected"
      else do
        let itemsWithOps = concatMap (\(op, items) -> map (second (op,)) items) updateItems
            validateMultiOps col items = do
              when (length items > 1) $ MV.dispute [(col, map fst $ toList items)]
              pure $ snd $ NESeq.head items
            eitherResult = MV.runValidate $ OMap.traverseWithKey validateMultiOps $
                           OMap.groupTuples itemsWithOps
        case eitherResult of
          -- A column shouldn't be present in more than one operator.
          -- If present, then generated UPDATE statement throws unexpected query error
          Left columnsWithMultiOps -> throwVE $
                                      "column found in multiple operators; "
                                      <> T.intercalate ". "
                                      (map (\(col, ops) -> col <<> " in " <> showNames ops)
                                       columnsWithMultiOps)
          Right items -> pure $ resolvedPreSetItems <> OMap.toList items

convertUpdate
  :: ( MonadReusability m, MonadError QErr m
     , MonadReader r m, Has FieldMap r
     , Has OrdByCtx r, Has SQLGenCtx r
     )
  => UpdOpCtx -- the update context
  -> Field -- the mutation field
  -> m RespTx
convertUpdate opCtx fld = do
  annUpdUnresolved <- convertUpdateP1 opCtx fld
  (annUpdResolved, prepArgs) <- withPrepArgs $ RU.traverseAnnUpd
                                resolveValPrep annUpdUnresolved
  strfyNum <- stringifyNum <$> asks getter
  let whenNonEmptyItems = return $ RU.updateQueryToTx strfyNum
                          (annUpdResolved, prepArgs)
      whenEmptyItems    = return $ return $
                          buildEmptyMutResp $ RU.uqp1MutFlds annUpdResolved
   -- if there are not set items then do not perform
   -- update and return empty mutation response
  bool whenNonEmptyItems whenEmptyItems $ null $ RU.uqp1SetExps annUpdResolved