Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Saving Mongoose documents with empty sub-documents collections results in duplicate key error

I have two mongoose schemas:

var productSchema = new Schema({
    name: { type: String, required: true, unique: true },
    ...
});
...
var categorySchema = new Schema({
    ...
    products: [ProductSchema]
});

When I try to save categories

var categories = [
    {..., products: []},
    {..., products: []}
];

or even without products

var categories = [
    {...},
    {...}
];

I'm getting error

{ [MongoError: E11000 duplicate key error index: test.categories.$products.name_1  dup      key: { : undefined }]
name: 'MongoError',
err: 'E11000 duplicate key error index: test.categories.$products.name_1  dup key: { : undefined }',
 code: 11000,
 n: 0,
 lastOp: { _bsontype: 'Timestamp', low_: 6, high_: 1404282198 },
 ok: 1 }

It seems like mongoose is trying to save products with undefind names.

Mongoose log before getting error:

Mongoose: categories.insert({ __v: 0, products: [], _id: ObjectId("53b3c167d28a86102dec420a"), order: 1, description: 'Category 1', name: 'Cat 1' }) {}  
Mongoose: categories.insert({ __v: 0, products: [], _id: ObjectId("53b3c167d28a86102dec420b"), order: 2, description: 'Category 2', name: 'Cat 2' }) {}  

If I remove unique: true from the name property of productSchema two categories are added with empty products [] collections.

What am I doing wrong?

Thank you!

like image 583
Viktor Kireev Avatar asked Jul 02 '14 06:07

Viktor Kireev


1 Answers

This is pretty normal really. The empty array is essentially considered to be a "null" value for the "products.name" field, that of course violates the unique constraint on the index.

You could essentially "skip" any values of "name" that are in fact undefined, and you do this by adding the "sparse" property to the index. In the present schema path form:

var productSchema = new Schema({
  name: { type: String, required: true, unique: true, sparse: true }
});

var categorySchema = new Schema({
  products: [productSchema]
});

Now as long as there is no value in "name" there will be no problem unless of course it actually exists somewhere. Make sure to drop the index already created first though.

Just a note, be aware that what this is doing is making sure that the "products.name" values are unique for the "whole" collection. If you are just trying to make sure they are unique for a given category, then indexing is not your solution and you need to ensure that by other means.

like image 59
Neil Lunn Avatar answered Oct 20 '22 00:10

Neil Lunn