Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Support for blessed hashes #12

Closed
wants to merge 1 commit into from

6 participants

@rodrigolive

Hi,
I don't know if blocking unkown-objects ("type (Class) unknown") has a specific purpose... but I don't see why not support blessed-hashes, when they are just hashes with a blessed flag. Makes it really easy to collapse objects into a collection.
cheers,
-rodrigo

@tamias

Note the inconsistent indenting. I believe you're using tabs where the source has been using spaces.

@ksyz

Are there any other issues or blockers, except formatting, to do not merge this patch for 2y?
Or similar functionality.

@friedo

There is a plan to implement a feature like this, but with a slightly different interface. For blessed things that we don't know how to deal with, we want to provide an option to either serialize it as a normal hash, or call a provided a serialization method.

@SailingYYC

I have to concur with Mike. I have a use case presently where being able to specify a method for serialization is required.

The internal hash structure of the class holds an original state and a modified state, the serialization method would provide a merged version of both states. Just using the internalized structure of the class would not be desirable.

Has their been any thoughts about deserialization of custom classes?

Most appreciated,
C.

@SailingYYC

As a thought, and pulling from the JSON module for blessed objects:

Upon encountering a blessed object, will check for the availability of the TO_JSON method on the object's class. If found, it will be called in scalar context and the resulting scalar will be encoded instead of the object. If no TO_JSON method is found, the value of allow_blessed will decide what to do.

The TO_JSON method may safely call die if it wants. If TO_JSON returns other blessed objects, those will be handled in the same way. TO_JSON must take care of not causing an endless recursion cycle (== crash) in this case. The name of TO_JSON was chosen because other methods called by the Perl core (== not by the user of the object) are usually in upper case letters and to avoid collisions with the to_json function or method.

So the MongoDB driver could as well use the TO_JSON method of a blessed object, or if figuring this would be contentious, TO_MONGODB, and if a dev desired the two to function the same just embed one call within the other.

C.

@friedo

That is the kind of thing I was thinking of, although we should probably call it TO_BSON. Or maybe to_bson. No need to yell about it.

The harder part is figuring out how to round-trip it. How does the driver know to instantiate a given class when retrieving docs from the DB? There would need to be some kind of registration system for inflation handlers. And if we're going to do that, we might as well make it separate, with deflation handlers as well. Then inflation and deflation can be decoupled from the object.

But it's the end of the day and maybe I'm out of good ideas.....

@SailingYYC

Using TO_BSON makes more sense than my suggestions. With respect to capitalization, I concur with the last sentence I quoted previously.

I'm caught between a rock and a hard place with respect to round-tripping... I love the idea of it, but I think it's a pink elephant, it looks cute and pretty from afar but is big, hairy and will kill you up close...

Looking at JSON, as another big project, they only provide one-way conversion via the TO_JSON method of a blessed object. A round-trip of a MongoDB::OID through JSON yields $i = {'$oid' => '000000000000000000000000' }; not a blessed MongoDB::OID, thus I convert it in my own object instantiation/validation.

Thinking about the MongoDB driver itself, which already has super handy round-tripping of DateTime's... Warning This will kill your application performance! Now imagine handling a massive variety of user provided inflation handlers.

With registering all the inflation and deflation handlers, you would either be required to store additional metadata with the deflated documents/sub-documents in Mongo, or have them register which documents/sub-documents each handler is used for. This I see as a major complication and it actually reduces, in my mind, the benefit of Mongo's "schema-less" nature by imposing all manner of constraints in the driver.

Well, my last hour of the day comments, the gist is I don't believe I see any major gains to handling the inflation side of the coin.

@SailingYYC

Not wanting to hack around on the C side of the equation, I've devised the following to encode an arbitrary hash/hashref/objref to a format suitable for pushing through to MongoDB driver. BTW, I hate horribly recursive code, so this flattens out that process but still has a mechanism to break-out incase of circular references.

It is assumed the input is a hash, hashref, or some other type of ref. If a blessed object does not posses a "TO_BSON" encode method, it is forced into a scalar, this is the same mechanism the JSON module uses.

The process also ensures a new and independent encoded structure (except MongoDB::OID and DateTime objects) is created so as to be idempotent to the original structure.

use Scalar::Util qw(blessed refaddr);
use constant {  ENC_MAX_ITERATIONS => 10240,    # Set to a resonable upper limit to prevent circular references in data structures.
                ENC_METHOD => 'TO_BSON' };      # Name of encoding method for objects / configurable?

sub encode_bson {
    my $method = ENC_METHOD;

    my $res = @_ ? @_ == 1 ? $_[0] : {@_} : {};
    my @process = (\$res); my $i; my $cycle;

    while ($i = shift @process) {
        die sprintf 'Exceeded maximum iterations (%s) encoding %s', ENC_MAX_ITERATIONS, $method
            if ++$cycle > ENC_MAX_ITERATIONS;

        my $ref = ref $$i;

        if ($ref eq 'HASH') {
            my (%h, $k, $v);
            while (($k, $v) = each %$$i) {
                $h{$k} = $v;
                push @process, \$h{$k} if ref $v;
            }
            $$i = \%h;

        } elsif ($ref eq 'ARRAY') {
            my @a;
            for (@$$i) {
                push @a, $_;
                push @process, \$a[-1] if ref $_;
            }
            $$i = \@a;

        } elsif ($ref =~ /MongoDB::OID|DateTime/) {
            # Don't alter the Objects, already handled by the driver.

        } elsif ($ref && blessed($$i) && $$i->can($method)) {
            my $v = $$i->$method;
            if (defined $v && ref $v) {
                die "$ref::$method method returned same object as was passed instead of a new one"
                    if refaddr($$i) eq refaddr($v);
                push @process, $i;
            }
            $$i = $v;

        } else { $$i = "$$i"; }
    }
    return $res;
}
@edaniels
Collaborator

Going to close this for now. In the future we may implement this and the most likely implementation will be something similar to JSON's TO_JSON approach. This will allow us to put the matter of encoding into the users' hands. With the provided pull request and other code given, we make assumptions about the way data is encoded which may cause us to run into unintended side effects.

@edaniels edaniels closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 16, 2011
  1. support blessed hashes

    mura authored
This page is out of date. Refresh to see the latest.
Showing with 23 additions and 4 deletions.
  1. +6 −0 perl_mongo.c
  2. +17 −4 t/types.t
View
6 perl_mongo.c
@@ -1266,6 +1266,12 @@ append_sv (buffer *buf, const char *key, SV *sv, stackette *stack, int is_insert
perl_mongo_serialize_bindata(buf, SvRV(sv));
}
}
+ else if (SvTYPE (SvRV (sv)) == SVt_PVHV ) { /* blessed hash */
+ set_type(buf, BSON_OBJECT);
+ perl_mongo_serialize_key(buf, key, is_insert);
+ /* don't add a _id to inner objs */
+ hv_to_bson (buf, sv, NO_PREP, stack, is_insert);
+ }
else {
croak ("type (%s) unhandled", HvNAME(SvSTASH(SvRV(sv))));
}
View
21 t/types.t
@@ -23,7 +23,7 @@ if ($@) {
plan skip_all => $@;
}
else {
- plan tests => 55;
+ plan tests => 56;
}
my $db = $conn->get_database('x');
@@ -277,15 +277,28 @@ SKIP: {
is($x->{y}, boolean::false);
}
-# unrecognized obj
+# check blessed obj + attribute
{
+ my $coll = $db->get_collection('test_collection');
+ $coll->drop;
+ # blessed hash
+ my $foo = bless { name=>'foo' }, 'Person';
+ $foo->{baz} = bless { name=>'boo', }, 'Something';
+ $coll->insert( $foo );
+ my $doc = $coll->find_one;
+ is( $doc->{baz}->{name}, 'boo', 'blessed hash-refs ok' );
+ $coll->drop;
+
+ # circularity
+ $foo->{circ} = $foo;
eval {
- $coll->insert({"x" => $coll});
+ $coll->insert( $foo );
};
- ok($@ =~ m/type \(MongoDB::Collection\) unhandled/, "can't insert a non-recognized obj: $@");
+ ok($@ =~ m/circular ref/, "can't insert a circular ref obj: $@");
}
+
END {
if ($db) {
$db->drop;
Something went wrong with that request. Please try again.