Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested-subdocuments with discriminators aren't handled well #10702

Closed
mqualizz opened this issue Sep 9, 2021 · 3 comments
Closed

Nested-subdocuments with discriminators aren't handled well #10702

mqualizz opened this issue Sep 9, 2021 · 3 comments
Labels
confirmed-bug We've confirmed this is a bug in Mongoose and will fix it.
Milestone

Comments

@mqualizz
Copy link

mqualizz commented Sep 9, 2021

Do you want to request a feature or report a bug?
Reporting a bug

What is the current behavior?
Nested subdocuments with discriminators, whose parents also have discriminators, are borked. Fields not present in the "base" schema for the discriminators are stripped.

The issue seems to be in getEmbeddedDiscriminatorPath(), used by Document.prototype.$set in the loop where obj is populated from the source object:

Schema.prototype.pathType() returns adhocOrUndefined for properties that belong in the subclass (in the example below, this corresponds to returning adhokOrUndefined for GrandChild1.gc11). Then, about 30 lines below that, there
is this code:

        if (pathtype === 'adhocOrUndefined') {
          pathtype = getEmbeddedDiscriminatorPath(this, pathName, { typeOnly: true });
        }

The problem is that getEmbeddedDiscriminatorPath tries to find the target ("subclass") schema by doing a direct look-up using doc.schema.path(subPath) instead of using the discriminator key and discriminators map to find the correct schema.

If the current behavior is a bug, please provide the steps to reproduce.

Example:

    const GrandChildSchema1 = new mongoose.Schema({
        gc11: String,
        gc12: Number
    });


    const GrandChildSchema2 = new mongoose.Schema({
        gc21: Number,
        gc22: Number
    })

    const GrandChildBaseSchema = new mongoose.Schema({
    })

    const ChildSchema1 = new mongoose.Schema({
        str: String,
        num: Number,
        gc: GrandChildBaseSchema
    })

    const ChildSchema2 = new mongoose.Schema({
        anotherStr: String
    })

    const ChildBaseSchema = new mongoose.Schema({
    })

    const ParentSchema = new mongoose.Schema({
        top: String,
        c: ChildBaseSchema
    })


    ParentSchema.path<mongoose.Schema.Types.Subdocument>("c").discriminator("ChildSchema1", ChildSchema1);
    ParentSchema.path<mongoose.Schema.Types.Subdocument>("c").discriminator("ChildSchema2", ChildSchema2);

    ChildSchema1.path<mongoose.Schema.Types.Subdocument>("gc").discriminator("GrandChildSchema1", GrandChildSchema1);
    ChildSchema1.path<mongoose.Schema.Types.Subdocument>("gc").discriminator("GrandChildSchema2", GrandChildSchema2);

    const Parent = mongoose.model("ParentSchema", ParentSchema);

    const p = new Parent({
        top: "Top",
        c: {
            __t: "ChildSchema1",
            str: "string",
            num: 3,
            gc: {
                __t: "GrandChildSchema1",
                gc11: "eleventy",
                gc12: 110
            }
        }
    })

Results in:

{
  "_id": {
    "$oid": "613a85d8f480b9694062764d"
  },
  "top": "Top",
  "c": {
    "str": "string",
    "num": 3,
    "gc": {
      "_id": {
        "$oid": "613a85d8f480b9694062764f"
      },
      "__t": "GrandChildSchema1"
    },
    "_id": {
      "$oid": "613a85d8f480b9694062764e"
    },
    "__t": "ChildSchema1"
  },
  "__v": 0
}

What is the expected behavior?
values to be persisted.

What are the versions of Node.js, Mongoose and MongoDB you are using? Note that "latest" is not a version.
node: 14.17.6
mongoose: 6.0.5
mongodb: 5.0.2

@IslandRhythms
Copy link
Collaborator

IslandRhythms commented Sep 10, 2021

This is the output I am getting

[
  {
    _id: new ObjectId("613bb0e04f81ff86615b01cc"),
    top: 'Top',
    c: {
      str: 'string',
      num: 3,
      gc: [Object],
      _id: new ObjectId("613bb0e04f81ff86615b01cd"),
      __t: 'ChildSchema1'
    },
    __v: 0
  }
]

The modified script

import * as mongoose from 'mongoose';


const GrandChildSchema1 = new mongoose.Schema({
    gc11: String,
    gc12: Number
});


const GrandChildSchema2 = new mongoose.Schema({
    gc21: Number,
    gc22: Number
})

const GrandChildBaseSchema = new mongoose.Schema({
})

const ChildSchema1 = new mongoose.Schema({
    str: String,
    num: Number,
    gc: GrandChildBaseSchema
})

const ChildSchema2 = new mongoose.Schema({
    anotherStr: String
})

const ChildBaseSchema = new mongoose.Schema({
})

const ParentSchema = new mongoose.Schema({
    top: String,
    c: ChildBaseSchema
})


ParentSchema.path<mongoose.Schema.Types.Subdocument>("c").discriminator("ChildSchema1", ChildSchema1);
ParentSchema.path<mongoose.Schema.Types.Subdocument>("c").discriminator("ChildSchema2", ChildSchema2);

ChildSchema1.path<mongoose.Schema.Types.Subdocument>("gc").discriminator("GrandChildSchema1", GrandChildSchema1);
ChildSchema1.path<mongoose.Schema.Types.Subdocument>("gc").discriminator("GrandChildSchema2", GrandChildSchema2);

const Parent = mongoose.model("ParentSchema", ParentSchema);
async function run() {

    await mongoose.connect('mongodb://localhost:27017/');
    await mongoose.connection.dropDatabase();
    const p = await Parent.create({
        top: "Top",
        c: {
            __t: "ChildSchema1",
            str: "string",
            num: 3,
            gc: {
                __t: "GrandChildSchema1",
                gc11: "eleventy",
                gc12: 110
            }
        }
    });
    console.log(await Parent.find());
}

run();

@IslandRhythms IslandRhythms added the confirmed-bug We've confirmed this is a bug in Mongoose and will fix it. label Sep 10, 2021
@vkarpov15 vkarpov15 added this to the 6.0.7 milestone Sep 15, 2021
vkarpov15 added a commit that referenced this issue Sep 18, 2021
@vkarpov15
Copy link
Collaborator

Fix will be in 6.0.7. Also, one thing to note: you should pass clone: false to your discriminator() call to make sure you're adding nested discriminators to the right discriminator schema. See below.

ParentSchema.path('c').discriminator('Child1', ChildSchema1, { clone: false }); // Add 'clone: false' here...
ParentSchema.path('c').discriminator('Child2', Schema({ anotherStr: String }));

// to make sure that these changes affect `ParentSchema.path('c')`, otherwise Mongoose will use a clone of `ChildSchema1`.
ChildSchema1.path('gc').discriminator('GrandChild1', GrandChildSchema1);
ChildSchema1.path('gc').discriminator('GrandChild2', GrandChildSchema2);

@mqualizz
Copy link
Author

Awesome, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
confirmed-bug We've confirmed this is a bug in Mongoose and will fix it.
Projects
None yet
Development

No branches or pull requests

3 participants