Index: TODO =================================================================== diff -u -r2837e8ce08344ee3f82a7451109f14a4b7cb3395 -rc9258caf4c18915bdf4d752ad879932c6da7d967 --- TODO (.../TODO) (revision 2837e8ce08344ee3f82a7451109f14a4b7cb3395) +++ TODO (.../TODO) (revision c9258caf4c18915bdf4d752ad879932c6da7d967) @@ -2602,6 +2602,9 @@ * handling of mongo-embedded objects when destroying objects * simple bson pretty print function * extended examples + * handle fetch of embedded objects + * added method count for mongo mapped classes + * improve documentation TODO: - how to delete attributes? Index: library/mongodb/example-nx-bi.tcl =================================================================== diff -u -r2837e8ce08344ee3f82a7451109f14a4b7cb3395 -rc9258caf4c18915bdf4d752ad879932c6da7d967 --- library/mongodb/example-nx-bi.tcl (.../example-nx-bi.tcl) (revision 2837e8ce08344ee3f82a7451109f14a4b7cb3395) +++ library/mongodb/example-nx-bi.tcl (.../example-nx-bi.tcl) (revision c9258caf4c18915bdf4d752ad879932c6da7d967) @@ -1,6 +1,11 @@ +# An example for the usage of the nx mongo object mapping based on the +# the real world # -# The "Business Insider" Data Model +# "Business Insider" Data Model # +# (see e.g. +# http://www.slideshare.net/mongodb/nosql-the-shift-to-a-nonrelational-world). +# # { title: 'Too Big to Fail', # author: 'John S.', # ts: Date('05-Nov-09 10:33'), @@ -14,34 +19,32 @@ # ], # tags: ['finance', 'economy'] # } - # -# This is an example how to use the nx mongo mapping. # -# Gustaf Neumann fecit, April 2011 # +# Gustaf Neumann fecit, May 2011 +# package require nx::mongo package require nx::serializer package require nx::test - -# TODO: -# - make embedded spec nicer -# - handle fetch of embedded -# - handle count() like find() - - # Establish connection to the database ::nx::mongo::db connect -# Make sure, we start always from scratch +# Make sure, we start always from scratch, so remove everything from +# the collection. nx::mongo::db remove tutorial.bi {} # -# Create the application classes +# Create the application classes based on the "Business Insider" data +# model. Note that instances of the class "Comment" can be embedded in +# a posting (attribute "comments") as well as in an comment itself +# (attribute "replies"). All comments are in this example multivalued +# and incremental (i.e. one can use slot methods "... add ..." and +# "... delete ..." to add values to the attributes). # nx::mongo::Class create Comment { - #:document "tutorial.bi" + # This class does not have a :document" spec, since it is embedded. :attribute author:required :attribute comment:required @@ -50,61 +53,91 @@ nx::mongo::Class create Posting { :document "tutorial.bi" - :index ts + :index tags :attribute title:required :attribute author:required :attribute ts:required :attribute comments:embedded,arg=::Comment,0..n {set :incremental 1} :attribute tags:0..n {set :incremental 1} } -#puts stderr [Posting serialize] -set p [Posting new -title "Too Big to Fail" -author "John S." -ts "05-Nov-09 10:33" -tags {finance economy} \ +# +# Build a composite Posting instance based on the example above. +# +set p [Posting new -title "Too Big to Fail" -author "John S." \ + -ts "05-Nov-09 10:33" -tags {finance economy} \ -comments [list \ [Comment new -author "Ian White" -comment "Great Article!"] \ [Comment new -author "Joe Smith" -comment "But how fast is it?" \ -replies [list [Comment new -author "Jane Smith" -comment "scalable?"]]] \ ]] # # When we save the item, the embedded objects (the comments and -# replies) are saved together with the entry. +# replies) are saved together with the posting in a compound document. # -puts stderr ==== $p save -puts stderr ==== # After saving the item, the main object contains an _id, such that a -# subsequent save operation does not create an additional item. For -# our little experiment here, we like to save multiple copies to see -# the results of our changes, and we remove the _id manually +# subsequent save operations do not create an additional entries in +# the database. For our little experiment here, we like to save +# multiple copies to see the results of our changes. Therefore we +# remove the _id manually: $p eval {unset :_id} +# We have two comments for the posting $p +? [list llength [$p comments]] 2 + # Now we want to remove e.g. the second comment (with the embedded -# replies). First get the corresponding object $c ... +# replies). First get this comment object $c ... set c [lindex [$p comments] 1] + # ... and delete it $c delete -# The delete operation on an embedded object removes it from the -# object lists, but the change is not automatically persisted, since -# there might be multiple changes in a complex document. Therefore we -# have to perform an save operation of the containing document. +# The delete operation destroy the embedded object and removes the +# referece to it in the comments attribute. +? [list llength [$p comments]] 1 + +# The delete operation does not automatically persist the change, +# since there might be multiple changes in a complex +# document. Therefore we have to perform an save operation of the +# containing document. $p save # Now, we have two postings in the database, the first with the two -# comments, the second one with just a single comment. -? {nx::mongo::db count tutorial.bi {}} 2 +# comments, the second one with just a single comment. +? {Posting count} 2 # Again, we want to continue with our test and remove the fresh _id as # well. $p eval {unset :_id} -# Add an additional comment at the end of the list of the comments.... +# We add an additional comment at the end of the list of the comments +# with the incremental operations (the slot is incremental) ... $p comments add [Comment new -author "Gustaf N" -comment "This sounds pretty cool"] end -# ... and add another tag ... + +# ... and we add another tag ... $p tags add nx -# ... and save it + +# ... and save everything $p save -? {nx::mongo::db count tutorial.bi {}} 3 +# We have now three entries in the database collection. +? {Posting count} 3 + +# Now fetch the first entry with the tag "nx" +set q [Posting find first -cond {tags = nx}] + +# The fetched entry should have the two comments: +? [list llength [$q comments]] 2 + +# We add jet another tag and save it +$q tags add nsf +$q save + +# We still have three entries in the database +? {Posting count} 3 + +puts stderr [$q bson pp [$q eval {:bson encode}]] + puts stderr ====EXIT \ No newline at end of file Index: library/mongodb/nx-mongo.tcl =================================================================== diff -u -r2837e8ce08344ee3f82a7451109f14a4b7cb3395 -rc9258caf4c18915bdf4d752ad879932c6da7d967 --- library/mongodb/nx-mongo.tcl (.../nx-mongo.tcl) (revision 2837e8ce08344ee3f82a7451109f14a4b7cb3395) +++ library/mongodb/nx-mongo.tcl (.../nx-mongo.tcl) (revision c9258caf4c18915bdf4d752ad879932c6da7d967) @@ -8,8 +8,11 @@ package provide nx::mongo 0.2 # todo: how to handle multiple connections; currently we have a single, global connection -# todo: handle embedded bson objects -# todo: handle named nx objects (e.g. attribute _oid?} +# todo: make embedded spec nicer +# todo: handle time stamps +# todo: handle remove for non-multivalued embedded objects +# todo: handle names of nx objects (e.g. attribute like __name) +# todo: handle classes von nx objects (e.g. attribute like __class) namespace eval ::nx::mongo { @@ -37,7 +40,6 @@ if {![info exists :mongotype]} { set :mongotype string if {[info exists :type]} { - puts stderr "type of ${:name} is ${:type}" switch -glob ${:type} { "boolean" - "integer" {set :mongotype ${:type}} @@ -46,7 +48,7 @@ #"::*" {set :mongotype object} } } - puts stderr "mongo type of ${:name} is ${:mongotype} [info exists :type]" + #puts stderr "mongo type of ${:name} is ${:mongotype} [info exists :type]" next } @@ -64,8 +66,16 @@ error "Attribute ${:name} should be multivalued, but it is not" } set result [list] - foreach {pos type v} $value {lappend result $v} + foreach {pos type v} $value {lappend result [:bson decode $type $v]} return $result + } elseif {$bsontype eq "object"} { + #puts stderr "*** we have an object '$value', [:serialize]" + if {${:type} eq "embedded" && [info exists :arg]} { + set value [${:arg} bson create $value] + #puts stderr "*** ${:arg} bson create ==> $value" + } else { + error "don't know how to decode object with value '$value'; [:serialize]" + } } return $value } @@ -107,18 +117,14 @@ # Type converter for handling embedded objects. Makes sure to # track "embedded in" relationship # - :public method type=embedded {name value args} { + :public method type=embedded {name value arg} { set s [:uplevel self] - puts stderr "assign $name '$value' args='$args' s=$s" - if {[::nsf::isobject $value] && [::nsf::is class $args] && [$value info has type $args]} { + #puts stderr "check $name '$value' arg='$arg' s=$s" + if {[::nsf::isobject $value] && [::nsf::is class $arg] && [$value info has type $arg]} { ::nsf::var::set $value __embedded_in [list $s $name] ::nsf::var::set $s __contains($value) 1 - puts stderr " - ::nsf::var::set $value __embedded_in [list $s $name] - ::nsf::var::set $s __contains($value) 1 -" } else { - error "value '$value' for attribute $name is not of type $args" + error "value '$value' for attribute $name is not of type $arg" } } } @@ -181,19 +187,28 @@ } lappend result \$orderby object $bson } - puts "Query: $result" + #puts "Query: $result" return $result } :method "bson parameter" {tuple} { set objParams [list] foreach {att type value} $tuple { set slot [:get slot $att] + #puts stderr "att $att type $type value $value => '$slot'" lappend objParams -$att [$slot bson decode $type $value] } return $objParams } + :public method "bson create" {{-name ""} tuple} { + if {$name ne ""} { + return [:create $name {*}[:bson parameter $tuple]] + } else { + return [:new {*}[:bson parameter $tuple]] + } + } + # # Overload method attribute to provide "::nx::mongo::Attribute" as a # default slot class @@ -219,28 +234,30 @@ $p save $p destroy } + + # + # The method "count" is similar to find, but returns just the + # number of tuples for the query. + # + :public method count {{-cond ""}} { + return [::nx::mongo::db count ${:document} $cond] + } # # The query interface consists currently of "find first" (returning # a single instance) and "find all" (returning a list of instances). # :public method "find first" { - -instance + {-instance ""} {-cond ""} {-orderby ""} } { - set fetched [::nx::mongo::db query ${:document} \ - [:bson query -cond $cond -orderby $orderby] \ - -limit 1] - puts "[join $fetched \n]" - foreach tuple $fetched { - if {[info exists instance]} { - set o [:uplevel [list [self] create $instance {*}[:bson parameter $tuple]]] - return $o - } else { - return [:uplevel [list [self] new {*}[:bson parameter $tuple]]] - } - } + set tuple [lindex [::nx::mongo::db query ${:document} \ + [:bson query -cond $cond -orderby $orderby] \ + -limit 1] 0] + #puts "find first fetched: $tuple" + if {$instance ne ""} {set instance [:uplevel [list ::nsf::qualify $instance]]} + return [:bson create -name $instance $tuple] } :public method "find all" { @@ -258,7 +275,7 @@ {*}$opts] puts "[join $fetched \n]" foreach tuple $fetched { - lappend result [:uplevel [list [self] new {*}[:bson parameter $tuple]]] + lappend result [:bson create $tuple] } return $result } @@ -367,14 +384,14 @@ # delete the current object from the db # :public method delete {} { - puts stderr "deleting [:serialize]" + puts stderr "[self] delete" if {[info exists :__embedded_in]} { puts "[self] is embedded in ${:__embedded_in}" lassign ${:__embedded_in} parent att set slot [[$parent info class] get slot $att] $slot remove $parent [self] - puts stderr [:serialize] - puts stderr "We must save parent $parent in mongo db" + #puts stderr [:serialize] + puts stderr "[self] must save parent $parent in db" :destroy } else { puts "delete a non-embedded entry" @@ -400,11 +417,10 @@ } else { set bson [:bson encode] if {[info exists :_id]} { - #puts stderr "we have to update $bson" + puts stderr "we have to update [:bson pp -indent 4 $bson]" ::nx::mongo::db update $document [list _id oid ${:_id}] $bson } else { - puts stderr "we have to insert $bson" - puts stderr [:bson pp $bson] + puts stderr "we have to insert [:bson pp -indent 4 $bson]" set r [::nx::mongo::db insert $document $bson] set :_id [lindex $r 2] }