You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user@couchdb.apache.org by Nils Breunese <N....@vpro.nl> on 2011/04/20 14:11:20 UTC

Dealing with sealed docs

Hello all,

I recently upgraded to CouchDB 1.0.2 for development and found some of our view map functions broke. Apparently it's no longer possible to modify data a doc in a view function before emitting. I found "Documents are now sealed before being passed to map functions." in the changelog for 1.0.2.

This is an example of a map function which no longer works under 1.0.2:

----
function(doc) {
  if(doc.type === 'schedule') {
    var events = doc.events;
    if (events) {
      events.forEach(function(event) {
        var broadcasters = event.broadcasters;
        if (broadcasters) {
          broadcasters.forEach(function(broadcaster) {
            if(broadcaster === 'VPRO') {
              event.channel = doc.channel;
              event.channelName = doc.channelName;
              emit(event.end, event);
            }
          });
        }
      });
    }
  }
}
----

A colleague suggested using this:

----
events.forEach(function(e) {
  var event = eval(uneval(e));
----

This creates a deep copy before modifying event properties. It works, but it looks ugly to me. Is this the way to go or is there a cleaner way?

Nils.
------------------------------------------------------------------------
 VPRO   www.vpro.nl
------------------------------------------------------------------------

Re: Dealing with sealed docs

Posted by Jan Lehnardt <ja...@apache.org>.
On 20 Apr 2011, at 16:44, Patrick Barnes wrote:

> Actually, he is...
> 
> if doc.events is an object, then the object might not actually be copied, but only a reference.

I understand that it is a reference, but my shell example supports my understanding of the copy on write semantics (which still may be wrong :)

Cheers
Jan
-- 


> 
> If you change
> var events = doc.events;
> to
> var events = clone doc.event;
> 
> Does that fix it?
> 
> On 20/04/2011 10:59 PM, Jan Lehnardt wrote:
>> 
>> On 20 Apr 2011, at 14:11, Nils Breunese wrote:
>> 
>>> Hello all,
>>> 
>>> I recently upgraded to CouchDB 1.0.2 for development and found some of our view map functions broke. Apparently it's no longer possible to modify data a doc in a view function before emitting. I found "Documents are now sealed before being passed to map functions." in the changelog for 1.0.2.
>>> 
>>> This is an example of a map function which no longer works under 1.0.2:
>>> 
>>> ----
>>> function(doc) {
>>>  if(doc.type === 'schedule') {
>>>    var events = doc.events;
>>>    if (events) {
>>>      events.forEach(function(event) {
>>>        var broadcasters = event.broadcasters;
>>>        if (broadcasters) {
>>>          broadcasters.forEach(function(broadcaster) {
>>>            if(broadcaster === 'VPRO') {
>>>              event.channel = doc.channel;
>>>              event.channelName = doc.channelName;
>>>              emit(event.end, event);
>>>            }
>>>          });
>>>        }
>>>      });
>>>    }
>>>  }
>>> }
>>> This creates a deep copy before modifying event properties. It works, but it looks ugly to me. Is this the way to go or is there a cleaner way?
>> 
>> I use JSON.parse(JSON.stringify(doc)) but that is essentially the same :)
>> 
>> The reason that doc modifications work were an artefact of a bug in Spidermonkey that finally got resolved. You shouldn't rely on being able to modify a doc in map functions.
>> 
>> That said, I'm surprised you get this error as you are not assigning anything do doc.events. Unless I got wrong how chaining in JS works, this shouldn't be a problem:
>> 
>> js>  var a = {a:1};
>> js>  seal(a);
>> js>  a.a = 2;
>> typein:4: Error: a.a is read-only
>> js>  var b = a.a;
>> js>  print(b);
>> 1
>> js>  b = 2
>> 2
>> js>
>> 
>> Cheers
>> Jan


Re: Dealing with sealed docs

Posted by Nils Breunese <N....@vpro.nl>.
Patrick Barnes wrote:

> My apologies - I have mis-remembered. No, there is no clone operator,
> but some functions will return a copy rather than a reference.
>
> I had a view that looked like:
> function(doc) {
>         if (doc.status=='active' && doc.doc_type=='group') {
>                 doc.path.pop();
>                 emit(doc.path, doc.display_name);
>         }
> }
> And it (on pre-1.0.2 couchdb) failed to generate the views properly,
> because doc.path.pop() was modifying the doc and the next views were
> receiving that modified version.
>
> In my case, changing the view to this fixed it:
> function(doc) {
>         if (doc.status=='active' && doc.doc_type=='group') {
>                 path = doc.path.slice(0);
>                 path.pop();
>                 emit(path, doc.display_name);
>         }
> }
>
> So doc.path.slice(0) copies the whole doc.path array for me so that I
> can modify it before emitting. No such neat method exists for objects
> unfortunately, but how about:
>
> function(doc) {
>   if(doc.type === 'schedule') {
>     var events = doc.events;
>     if (events) {
>       events.forEach(function(event) {
>         var broadcasters = event.broadcasters;
>         if (broadcasters) {
>           broadcasters.forEach(function(broadcaster) {
>             if(broadcaster === 'VPRO') {
> -              event.channel = doc.channel;
> -              event.channelName = doc.channelName;
> -              emit(event.end, event);
> +              var _event = {channel:doc.channel,
> channelName:doc.channelName};
> +              for (var k in event) _event[k]=event[k];
> +              emit(event.end, _event);
>             }
>           });
>         }
>       });
>     }
>   }
> }
>
> The members of _event are only references, but that should be okay
> because you're not modifying those, only adding two extra attributes to
> the new object created. (Not having to _copy_ every element should also
> make this approach much faster than using a generic 'clone' method)
>
> Hope that works. :-)

Thanks, I'm using this approach now and it seems to work.

Nils.
------------------------------------------------------------------------
 VPRO   www.vpro.nl
------------------------------------------------------------------------

Re: Dealing with sealed docs

Posted by Patrick Barnes <mr...@gmail.com>.
My apologies - I have mis-remembered. No, there is no clone operator, 
but some functions will return a copy rather than a reference.

I had a view that looked like:
function(doc) {
         if (doc.status=='active' && doc.doc_type=='group') {
                 doc.path.pop();
                 emit(doc.path, doc.display_name);
         }
}
And it (on pre-1.0.2 couchdb) failed to generate the views properly, 
because doc.path.pop() was modifying the doc and the next views were 
receiving that modified version.

In my case, changing the view to this fixed it:
function(doc) {
         if (doc.status=='active' && doc.doc_type=='group') {
                 path = doc.path.slice(0);
                 path.pop();
                 emit(path, doc.display_name);
         }
}

So doc.path.slice(0) copies the whole doc.path array for me so that I 
can modify it before emitting. No such neat method exists for objects 
unfortunately, but how about:

function(doc) {
   if(doc.type === 'schedule') {
     var events = doc.events;
     if (events) {
       events.forEach(function(event) {
         var broadcasters = event.broadcasters;
         if (broadcasters) {
           broadcasters.forEach(function(broadcaster) {
             if(broadcaster === 'VPRO') {
-              event.channel = doc.channel;
-              event.channelName = doc.channelName;
-              emit(event.end, event);
+              var _event = {channel:doc.channel, 
channelName:doc.channelName};
+              for (var k in event) _event[k]=event[k];
+              emit(event.end, _event);
             }
           });
         }
       });
     }
   }
}

The members of _event are only references, but that should be okay 
because you're not modifying those, only adding two extra attributes to 
the new object created. (Not having to _copy_ every element should also 
make this approach much faster than using a generic 'clone' method)

Hope that works. :-)

-Patrick

On 21/04/2011 1:00 AM, Hay (Husky) wrote:
> On Wed, Apr 20, 2011 at 4:44 PM, Patrick Barnes<mr...@gmail.com>  wrote:
>> If you change
>> var events = doc.events;
>> to
>> var events = clone doc.event;
>
> Would be great, but afaik there doesn't exist a 'clone' operator in
> Javascript. Virtually all JS frameworks provide a method (such as
> jQuery's 'extend' or Prototype's Object.clone), but in vanilla
> Javascript no such method exists.
>
> -- Hay
>

Re: Dealing with sealed docs

Posted by "Hay (Husky)" <hu...@gmail.com>.
On Wed, Apr 20, 2011 at 4:44 PM, Patrick Barnes <mr...@gmail.com> wrote:
> If you change
> var events = doc.events;
> to
> var events = clone doc.event;

Would be great, but afaik there doesn't exist a 'clone' operator in
Javascript. Virtually all JS frameworks provide a method (such as
jQuery's 'extend' or Prototype's Object.clone), but in vanilla
Javascript no such method exists.

-- Hay

Re: Dealing with sealed docs

Posted by Patrick Barnes <mr...@gmail.com>.
Actually, he is...

if doc.events is an object, then the object might not actually be 
copied, but only a reference.

If you change
var events = doc.events;
to
var events = clone doc.event;

Does that fix it?

On 20/04/2011 10:59 PM, Jan Lehnardt wrote:
>
> On 20 Apr 2011, at 14:11, Nils Breunese wrote:
>
>> Hello all,
>>
>> I recently upgraded to CouchDB 1.0.2 for development and found some of our view map functions broke. Apparently it's no longer possible to modify data a doc in a view function before emitting. I found "Documents are now sealed before being passed to map functions." in the changelog for 1.0.2.
>>
>> This is an example of a map function which no longer works under 1.0.2:
>>
>> ----
>> function(doc) {
>>   if(doc.type === 'schedule') {
>>     var events = doc.events;
>>     if (events) {
>>       events.forEach(function(event) {
>>         var broadcasters = event.broadcasters;
>>         if (broadcasters) {
>>           broadcasters.forEach(function(broadcaster) {
>>             if(broadcaster === 'VPRO') {
>>               event.channel = doc.channel;
>>               event.channelName = doc.channelName;
>>               emit(event.end, event);
>>             }
>>           });
>>         }
>>       });
>>     }
>>   }
>> }
>> This creates a deep copy before modifying event properties. It works, but it looks ugly to me. Is this the way to go or is there a cleaner way?
>
> I use JSON.parse(JSON.stringify(doc)) but that is essentially the same :)
>
> The reason that doc modifications work were an artefact of a bug in Spidermonkey that finally got resolved. You shouldn't rely on being able to modify a doc in map functions.
>
> That said, I'm surprised you get this error as you are not assigning anything do doc.events. Unless I got wrong how chaining in JS works, this shouldn't be a problem:
>
> js>  var a = {a:1};
> js>  seal(a);
> js>  a.a = 2;
> typein:4: Error: a.a is read-only
> js>  var b = a.a;
> js>  print(b);
> 1
> js>  b = 2
> 2
> js>
>
> Cheers
> Jan

Re: Dealing with sealed docs

Posted by Jan Lehnardt <ja...@apache.org>.
On 20 Apr 2011, at 14:11, Nils Breunese wrote:

> Hello all,
> 
> I recently upgraded to CouchDB 1.0.2 for development and found some of our view map functions broke. Apparently it's no longer possible to modify data a doc in a view function before emitting. I found "Documents are now sealed before being passed to map functions." in the changelog for 1.0.2.
> 
> This is an example of a map function which no longer works under 1.0.2:
> 
> ----
> function(doc) {
>  if(doc.type === 'schedule') {
>    var events = doc.events;
>    if (events) {
>      events.forEach(function(event) {
>        var broadcasters = event.broadcasters;
>        if (broadcasters) {
>          broadcasters.forEach(function(broadcaster) {
>            if(broadcaster === 'VPRO') {
>              event.channel = doc.channel;
>              event.channelName = doc.channelName;
>              emit(event.end, event);
>            }
>          });
>        }
>      });
>    }
>  }
> }
> ----
> 
> A colleague suggested using this:
> 
> ----
> events.forEach(function(e) {
>  var event = eval(uneval(e));
> ----
> 
> This creates a deep copy before modifying event properties. It works, but it looks ugly to me. Is this the way to go or is there a cleaner way?

I use JSON.parse(JSON.stringify(doc)) but that is essentially the same :)

The reason that doc modifications work were an artefact of a bug in Spidermonkey that finally got resolved. You shouldn't rely on being able to modify a doc in map functions.

That said, I'm surprised you get this error as you are not assigning anything do doc.events. Unless I got wrong how chaining in JS works, this shouldn't be a problem:

js> var a = {a:1};
js> seal(a);
js> a.a = 2;
typein:4: Error: a.a is read-only
js> var b = a.a;
js> print(b);
1
js> b = 2
2
js> 

Cheers
Jan
--