From 995087e9a01c0a231a99f9e29d1680b0f4269484 Mon Sep 17 00:00:00 2001 From: Pierre Mizrahi Date: Mon, 13 Feb 2023 14:05:56 +0100 Subject: [PATCH] repair single document upserts when using OP_MSG 780df80cfc0781a21a2c1e397de26c00c8878488 introduces support for the OP_MSG protocol. Unfortunately, the upsert and multi options of the update command still use flagBits to communicate the options, whereas they must be provided directly into the command document, alongside the "q" and "v" fields. This commit: - introduces a test for a single-document upsert that, if isolated, succeeds against the reference MongoDB 3.6 container, but fails against an official 6.0 image. - provides a patch that sets the appropriate options. The test is not perfect as the upsert operation is inherently racy and this difficult to test. A comfortable threadDelay has been inserted as a workaround to accomodate for medium workloads. --- Database/MongoDB/Internal/Protocol.hs | 5 ++++- test/QuerySpec.hs | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Database/MongoDB/Internal/Protocol.hs b/Database/MongoDB/Internal/Protocol.hs index 26d190c..df4e367 100644 --- a/Database/MongoDB/Internal/Protocol.hs +++ b/Database/MongoDB/Internal/Protocol.hs @@ -494,6 +494,9 @@ data FlagBit = | ExhaustAllowed -- ^ The client is prepared for multiple replies to this request using the moreToCome bit. deriving (Show, Eq, Enum) +uOptDoc :: UpdateOption -> Document +uOptDoc Upsert = ["upsert" =: True] +uOptDoc MultiUpdate = ["multi" =: True] {- OP_MSG header == 16 byte @@ -528,7 +531,7 @@ putOpMsg cmd requestId flagBit params = do putCString "documents" -- identifier mapM_ putDocument iDocuments -- payload Update{..} -> do - let doc = ["q" =: uSelector, "u" =: uUpdater] + let doc = ["q" =: uSelector, "u" =: uUpdater] <> concatMap uOptDoc uOptions (sec0, sec1Size) = prepSectionInfo uFullCollection diff --git a/test/QuerySpec.hs b/test/QuerySpec.hs index 4d7435e..e7aa1fa 100644 --- a/test/QuerySpec.hs +++ b/test/QuerySpec.hs @@ -4,6 +4,7 @@ module QuerySpec (spec) where import Data.String (IsString(..)) import TestImport +import Control.Concurrent (threadDelay) import Control.Exception import Control.Monad (forM_, when) import System.Environment (getEnv) @@ -87,6 +88,21 @@ spec = around withCleanDatabase $ do db (count $ select ["name" =: "Yankees", "league" =: "American"] "team") `shouldReturn` 1 _id `shouldBe` () + describe "upsert" $ do + it "upserts a document twice with the same spec" $ do + let q = select ["name" =: "jack"] "users" + db $ upsert q ["color" =: "blue", "name" =: "jack"] + -- since there is no way to ask for a ack, we must wait for "a sufficient time" + -- for the write to be visible + threadDelay 10000 + db (rest =<< find (select [] "users")) >>= print + db (count $ select ["name" =: "jack"] "users") `shouldReturn` 1 + db $ upsert q ["color" =: "red", "name" =: "jack"] + threadDelay 10000 + db (count $ select ["name" =: "jack"] "users") `shouldReturn` 1 + Just doc <- db $ findOne (select ["name" =: "jack"] "users") + doc !? "color" `shouldBe` Just "red" + describe "insertMany" $ do it "inserts documents to the collection and returns their _ids" $ do (_id1:_id2:_) <- db $ insertMany "team" [ ["name" =: "Yankees", "league" =: "American"]