Uploaded image for project: 'Mongoid'
  1. Mongoid
  2. MONGOID-3405

Sequence operator is sometimes used when the embedded collection isn't loaded

    XMLWordPrintable

    Details

    • Type: Task
    • Status: Closed
    • Priority: Major - P3
    • Resolution: Gone away
    • Affects Version/s: None
    • Fix Version/s: 12_01_17
    • Component/s: None
    • Labels:
      None

      Description

      Hi,

      I know we are waiting for a better support for positional operator "$" from MongoDB (using it for deeply nested collections, using it for multiple searches in a single update call), but I've got two issues I think should be tackled now.

      This revolves around inserts/updates of documents embedded in a root document that has been loaded with only() filter.

      Here's some code and it's output.

      # Setup
       
      require 'mongoid'
       
      Mongoid.configure {|c| c.connect_to "mongoid_test"}
       
      class RootDocument
        include Mongoid::Document
        embeds_many :level1_documents
      end
      RootDocument.delete_all
       
      class Level1Document
        include Mongoid::Document
        embedded_in :root_document
        embeds_many :level2_documents
       
        field :label, type: String
      end
       
      class Level2Document
        include Mongoid::Document
        embedded_in :level1_document
       
        field :label, type: String
      end
       
      def verbose
        old_logger = Moped.logger
        Moped.logger = Logger.new(STDOUT)
        yield
      ensure
        Moped.logger = old_logger
      end
       
      puts "Using Mongoid #{Mongoid::VERSION}"
       
      # Tests
       
      root = RootDocument.new
      root.save!
       
      root.level1_documents.create label: "Should not have any children"
       
      root = RootDocument.only(:_id).first
       
      level1_child = root.level1_documents.create label: "Should have two children"
       
      verbose do
        puts "Bad 1st level insert:"
        level1_child.level2_documents.build label: "Saved via root"
        root.save
      end
       
      verbose do
        puts "Good 1st level insert:"
        deep_child = level1_child.level2_documents.create label: "Saved directly"
        puts "Bad 2nd level update:"
        deep_child.set label: "Updated"
      end
       
      puts JSON.pretty_generate RootDocument.first.as_json
      

      Output:

      Using Mongoid 3.1.5
      Bad 1st level insert:
        MOPED: 127.0.0.1:27017 UPDATE       database=mongoid_test collection=root_documents selector={"_id"=>"528be98f66d9f19ce0000001"} update={"$pushAll"=>{"level1_documents.0.level2_documents"=>[{"_id"=>"528be98f66d9f19ce0000004", "label"=>"Saved via root"}]}} flags=[] (0.1671ms)
      Good 1st level insert:
        MOPED: 127.0.0.1:27017 UPDATE       database=mongoid_test collection=root_documents selector={"_id"=>"528be98f66d9f19ce0000001", "level1_documents._id"=>"528be98f66d9f19ce0000003"} update={"$push"=>{"level1_documents.$.level2_documents"=>{"_id"=>"528be98f66d9f19ce0000005", "label"=>"Saved directly"}}} flags=[] (0.1550ms)
      Bad 2nd level update:
        MOPED: 127.0.0.1:27017 UPDATE       database=mongoid_test collection=root_documents selector={"_id"=>"528be98f66d9f19ce0000001", "level1_documents._id"=>"528be98f66d9f19ce0000003", "level1_documents.0.level2_documents._id"=>"528be98f66d9f19ce0000005"} update={"$set"=>{"level1_documents.0.level2_documents.$.{:label=>\"Updated\"}"=>nil}} flags=[] (0.1500ms)
      {
        "_id": {"$oid": "528be98f66d9f19ce0000001"},
        "level1_documents": [
          {
            "_id": {"$oid": "528be98f66d9f19ce0000002"},
            "label": "Should not have any children",
            "level2_documents": [
              {
                "_id": {"$oid": "528be98f66d9f19ce0000004"},
                "label": "Saved via root"
              }
            ]
          },
          {
            "_id": {"$oid": "528be98f66d9f19ce0000003"},
            "label": "Should have two children",
            "level2_documents": [
              {
                "_id": {"$oid": "528be98f66d9f19ce0000005"},
                "label": "Saved directly"
              }
            ]
          }
        ]
      }
      

      So first issue is that the embedded insert performed via root.save call executes a different query than the call to child.collection.create. The first one uses a query with sequence operator .0. instead of positional operator .$. and since we don't know the real position of the child a wrong document is updated.

      With the current version of MongoDB this can certainly be fixed for updates to documents that are only one level deep.

      The broader problem here is that Mongoid has no safety net for calculating sequence position when root document is loaded with only fields filter. Since we cannot use the positional operator $ for documents that are nested deeper I think Mongoid should throw an error when it cannot reliably determine sequence position.

        Attachments

          Activity

            People

            • Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: