@@ -747,16 +747,16 @@ function init(self, obj, doc, opts, prefix) {
747747 for ( let index = 0 ; index < len ; ++ index ) {
748748 i = keys [ index ] ;
749749 // avoid prototype pollution
750- if ( i === '__proto__' || i === 'constructor' ) {
751- return ;
750+ if ( specialProperties . has ( i ) ) {
751+ continue ;
752752 }
753753 path = prefix ? prefix + i : i ;
754754 schemaType = docSchema . path ( path ) ;
755755 // Should still work if not a model-level discriminator, but should not be
756756 // necessary. This is *only* to catch the case where we queried using the
757757 // base model and the discriminated model has a projection
758758 if ( docSchema . $isRootDiscriminator && ! self . $__isSelected ( path ) ) {
759- return ;
759+ continue ;
760760 }
761761
762762 const value = obj [ i ] ;
@@ -1720,7 +1720,7 @@ Document.prototype.$__set = function(pathToMark, path, options, constructing, pa
17201720 const last = next === l ;
17211721 cur += ( cur ? '.' + parts [ i ] : parts [ i ] ) ;
17221722 if ( specialProperties . has ( parts [ i ] ) ) {
1723- return ;
1723+ continue ;
17241724 }
17251725
17261726 if ( last ) {
@@ -2721,12 +2721,33 @@ function _getPathsToValidate(doc, pathsToValidate, pathsToSkip, isNestedValidate
27212721 function addToPaths ( p ) { paths . add ( p ) ; }
27222722
27232723 if ( ! isNestedValidate ) {
2724- // If we're validating a subdocument, all this logic will run anyway on the top-level document, so skip for subdocuments
2725- const subdocs = doc . $getAllSubdocs ( { useCache : true } ) ;
2724+ // If we're validating a subdocument, all this logic will run anyway on the top-level document, so skip for subdocuments.
2725+ // But only run for top-level subdocuments, because we're looking for subdocuments that are not modified at top-level but
2726+ // have a modified path. If that is the case, we will run validation on the top-level subdocument, and via that run validation
2727+ // on any subdocuments down to the modified path.
2728+ const topLevelSubdocs = [ ] ;
2729+ for ( const path of Object . keys ( doc . $__schema . paths ) ) {
2730+ const schemaType = doc . $__schema . path ( path ) ;
2731+ if ( schemaType . $isSingleNested ) {
2732+ const subdoc = doc . $get ( path ) ;
2733+ if ( subdoc ) {
2734+ topLevelSubdocs . push ( subdoc ) ;
2735+ }
2736+ } else if ( schemaType . $isMongooseDocumentArray ) {
2737+ const arr = doc . $get ( path ) ;
2738+ if ( arr && arr . length ) {
2739+ for ( const subdoc of arr ) {
2740+ if ( subdoc ) {
2741+ topLevelSubdocs . push ( subdoc ) ;
2742+ }
2743+ }
2744+ }
2745+ }
2746+ }
27262747 const modifiedPaths = doc . modifiedPaths ( ) ;
2727- for ( const subdoc of subdocs ) {
2748+ for ( const subdoc of topLevelSubdocs ) {
27282749 if ( subdoc . $basePath ) {
2729- const fullPathToSubdoc = subdoc . $isSingleNested ? subdoc . $ __pathRelativeToParent( ) : subdoc . $__fullPathWithIndexes ( ) ;
2750+ const fullPathToSubdoc = subdoc . $__pathRelativeToParent ( ) ;
27302751
27312752 // Remove child paths for now, because we'll be validating the whole
27322753 // subdoc.
@@ -2736,11 +2757,12 @@ function _getPathsToValidate(doc, pathsToValidate, pathsToSkip, isNestedValidate
27362757 paths . delete ( fullPathToSubdoc + '.' + modifiedPath ) ;
27372758 }
27382759
2760+ const subdocParent = subdoc . $parent ( ) ;
27392761 if ( doc . $isModified ( fullPathToSubdoc , null , modifiedPaths ) &&
27402762 // Avoid using isDirectModified() here because that does additional checks on whether the parent path
27412763 // is direct modified, which can cause performance issues re: gh-14897
2742- ! doc . $__ . activePaths . getStatePaths ( 'modify' ) . hasOwnProperty ( fullPathToSubdoc ) &&
2743- ! doc . $isDefault ( fullPathToSubdoc ) ) {
2764+ ! subdocParent . $__ . activePaths . getStatePaths ( 'modify' ) . hasOwnProperty ( fullPathToSubdoc ) &&
2765+ ! subdocParent . $isDefault ( fullPathToSubdoc ) ) {
27442766 paths . add ( fullPathToSubdoc ) ;
27452767
27462768 if ( doc . $__ . pathsToScopes == null ) {
0 commit comments