Index: TODO =================================================================== diff -u -rd40717ac3710f2123cb9359e3d0442ad4ff3da73 -r2837e8ce08344ee3f82a7451109f14a4b7cb3395 --- TODO (.../TODO) (revision d40717ac3710f2123cb9359e3d0442ad4ff3da73) +++ TODO (.../TODO) (revision 2837e8ce08344ee3f82a7451109f14a4b7cb3395) @@ -2593,6 +2593,16 @@ - nsf:c: fix dispatch of setter without current method - extended regression tests +- nsf.c: added nsf::var::unset (provided so far just var::set) +- nx::mongo: + * added mongo::count + * obtain _id from mongo::insert + * added mongo::Object.delete method for embedded and + non-embedded objects + * handling of mongo-embedded objects when destroying objects + * simple bson pretty print function + * extended examples + TODO: - how to delete attributes? Index: library/mongodb/README =================================================================== diff -u -rec9e525c887a0ae430bdb35bef01f499b25d617f -r2837e8ce08344ee3f82a7451109f14a4b7cb3395 --- library/mongodb/README (.../README) (revision ec9e525c887a0ae430bdb35bef01f499b25d617f) +++ library/mongodb/README (.../README) (revision 2837e8ce08344ee3f82a7451109f14a4b7cb3395) @@ -8,7 +8,7 @@ Compile or obtain mongodb (the database). Compile or obtain the mongo-c-driver (client interface) -Assume, tcl is under /usr/local/ns/lib and the mongo-c-driver is under +Assume, Tcl is under /usr/local/ns/lib and the mongo-c-driver is under /usr/local/src/mongo-c-driver/, then configre the nsf interface via ./configure --with-tcl=/usr/local/ns/lib --with-nsf=../../ \ Index: library/mongodb/example-nsf-mongo.tcl =================================================================== diff -u -rc81b840c71a9f0de9d0a502e3e4ddfde57b81fdd -r2837e8ce08344ee3f82a7451109f14a4b7cb3395 --- library/mongodb/example-nsf-mongo.tcl (.../example-nsf-mongo.tcl) (revision c81b840c71a9f0de9d0a502e3e4ddfde57b81fdd) +++ library/mongodb/example-nsf-mongo.tcl (.../example-nsf-mongo.tcl) (revision 2837e8ce08344ee3f82a7451109f14a4b7cb3395) @@ -35,6 +35,9 @@ puts stderr "\nAge > 30" puts [join [::mongo::query $mongoConn tutorial.persons [list \$query object {age object {$gt int 30}}]] \n] +puts stderr "\nCount Age > 30" +puts [::mongo::count $mongoConn tutorial.persons {age object {$gt int 30}}] + puts stderr "\nArray 'a' contains 'x'" puts [join [::mongo::query $mongoConn tutorial.persons [list \$query object {a string "x"}]] \n] Index: library/mongodb/example-nx-bi.tcl =================================================================== diff -u -rec9e525c887a0ae430bdb35bef01f499b25d617f -r2837e8ce08344ee3f82a7451109f14a4b7cb3395 --- library/mongodb/example-nx-bi.tcl (.../example-nx-bi.tcl) (revision ec9e525c887a0ae430bdb35bef01f499b25d617f) +++ library/mongodb/example-nx-bi.tcl (.../example-nx-bi.tcl) (revision 2837e8ce08344ee3f82a7451109f14a4b7cb3395) @@ -1,5 +1,5 @@ # -# The Business Insider Data Model +# The "Business Insider" Data Model # # { title: 'Too Big to Fail', # author: 'John S.', @@ -22,7 +22,15 @@ # 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 @@ -32,13 +40,6 @@ # # Create the application classes # - -# TODO: -# - make embedded spec nicer -# - handle delete of mebedded obj -# - - - nx::mongo::Class create Comment { #:document "tutorial.bi" @@ -53,10 +54,8 @@ :attribute title:required :attribute author:required :attribute ts:required - :attribute comments:embedded,arg=::Comment,0..n { - #set :incremental 1 - } - :attribute tags:0..n + :attribute comments:embedded,arg=::Comment,0..n {set :incremental 1} + :attribute tags:0..n {set :incremental 1} } #puts stderr [Posting serialize] @@ -66,9 +65,46 @@ [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. +# +puts stderr ==== +$p save +puts stderr ==== -#puts [$p serialize] +# 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 +$p eval {unset :_id} -puts stderr ==== +# Now we want to remove e.g. the second comment (with the embedded +# replies). First get the corresponding 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. $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 + +# 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.... +$p comments add [Comment new -author "Gustaf N" -comment "This sounds pretty cool"] end +# ... and add another tag ... +$p tags add nx +# ... and save it +$p save +? {nx::mongo::db count tutorial.bi {}} 3 + puts stderr ====EXIT \ No newline at end of file Index: library/mongodb/mongoAPI.decls =================================================================== diff -u -rc81b840c71a9f0de9d0a502e3e4ddfde57b81fdd -r2837e8ce08344ee3f82a7451109f14a4b7cb3395 --- library/mongodb/mongoAPI.decls (.../mongoAPI.decls) (revision c81b840c71a9f0de9d0a502e3e4ddfde57b81fdd) +++ library/mongodb/mongoAPI.decls (.../mongoAPI.decls) (revision 2837e8ce08344ee3f82a7451109f14a4b7cb3395) @@ -17,6 +17,12 @@ {-argName "-port" -required 0 -nrargs 1 -type int} } +cmd count NsfMongoCount { + {-argName "conn" -required 1 -type tclobj} + {-argName "namespace" -required 1} + {-argName "query" -required 1 -type tclobj} +} + cmd index NsfMongoIndex { {-argName "conn" -required 1 -type tclobj} {-argName "namespace" -required 1} Index: library/mongodb/mongoAPI.h =================================================================== diff -u -rc81b840c71a9f0de9d0a502e3e4ddfde57b81fdd -r2837e8ce08344ee3f82a7451109f14a4b7cb3395 --- library/mongodb/mongoAPI.h (.../mongoAPI.h) (revision c81b840c71a9f0de9d0a502e3e4ddfde57b81fdd) +++ library/mongodb/mongoAPI.h (.../mongoAPI.h) (revision 2837e8ce08344ee3f82a7451109f14a4b7cb3395) @@ -13,6 +13,7 @@ }; static int NsfMongoCloseStub(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv []); static int NsfMongoConnectStub(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv []); +static int NsfMongoCountStub(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv []); static int NsfMongoIndexStub(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv []); static int NsfMongoInsertStub(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv []); static int NsfMongoQueryStub(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv []); @@ -21,6 +22,7 @@ static int NsfMongoClose(Tcl_Interp *interp, Tcl_Obj *conn); static int NsfMongoConnect(Tcl_Interp *interp, CONST char *withHost, int withPort); +static int NsfMongoCount(Tcl_Interp *interp, Tcl_Obj *conn, CONST char *namespace, Tcl_Obj *query); static int NsfMongoIndex(Tcl_Interp *interp, Tcl_Obj *conn, CONST char *namespace, Tcl_Obj *attributes, int withDropdups, int withUnique); static int NsfMongoInsert(Tcl_Interp *interp, Tcl_Obj *conn, CONST char *namespace, Tcl_Obj *values); static int NsfMongoQuery(Tcl_Interp *interp, Tcl_Obj *conn, CONST char *namespace, Tcl_Obj *query, int withLimit, int withSkip); @@ -30,6 +32,7 @@ enum { NsfMongoCloseIdx, NsfMongoConnectIdx, + NsfMongoCountIdx, NsfMongoIndexIdx, NsfMongoInsertIdx, NsfMongoQueryIdx, @@ -75,6 +78,27 @@ } static int +NsfMongoCountStub(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { + ParseContext pc; + (void)clientData; + + if (ArgumentParse(interp, objc, objv, NULL, objv[0], + method_definitions[NsfMongoCountIdx].paramDefs, + method_definitions[NsfMongoCountIdx].nrParameters, 1, + &pc) != TCL_OK) { + return TCL_ERROR; + } else { + Tcl_Obj *conn = (Tcl_Obj *)pc.clientData[0]; + CONST char *namespace = (CONST char *)pc.clientData[1]; + Tcl_Obj *query = (Tcl_Obj *)pc.clientData[2]; + + assert(pc.status == 0); + return NsfMongoCount(interp, conn, namespace, query); + + } +} + +static int NsfMongoIndexStub(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { ParseContext pc; (void)clientData; @@ -194,6 +218,11 @@ {"-host", 0, 1, Nsf_ConvertToString, NULL,NULL,NULL,NULL,NULL,NULL,NULL}, {"-port", 0, 1, Nsf_ConvertToInteger, NULL,NULL,NULL,NULL,NULL,NULL,NULL}} }, +{"::mongo::count", NsfMongoCountStub, 3, { + {"conn", NSF_ARG_REQUIRED, 0, Nsf_ConvertToTclobj, NULL,NULL,NULL,NULL,NULL,NULL,NULL}, + {"namespace", NSF_ARG_REQUIRED, 0, Nsf_ConvertToString, NULL,NULL,NULL,NULL,NULL,NULL,NULL}, + {"query", NSF_ARG_REQUIRED, 0, Nsf_ConvertToTclobj, NULL,NULL,NULL,NULL,NULL,NULL,NULL}} +}, {"::mongo::index", NsfMongoIndexStub, 5, { {"conn", NSF_ARG_REQUIRED, 0, Nsf_ConvertToTclobj, NULL,NULL,NULL,NULL,NULL,NULL,NULL}, {"namespace", NSF_ARG_REQUIRED, 0, Nsf_ConvertToString, NULL,NULL,NULL,NULL,NULL,NULL,NULL}, Index: library/mongodb/nsfmongo.c =================================================================== diff -u -rc81b840c71a9f0de9d0a502e3e4ddfde57b81fdd -r2837e8ce08344ee3f82a7451109f14a4b7cb3395 --- library/mongodb/nsfmongo.c (.../nsfmongo.c) (revision c81b840c71a9f0de9d0a502e3e4ddfde57b81fdd) +++ library/mongodb/nsfmongo.c (.../nsfmongo.c) (revision 2837e8ce08344ee3f82a7451109f14a4b7cb3395) @@ -482,6 +482,56 @@ } /* +cmd query NsfMongoCount { + {-argName "conn" -required 1 -type tclobj} + {-argName "namespace" -required 1} + {-argName "query" -required 1 -type tclobj} +} +*/ +static int +NsfMongoCount(Tcl_Interp *interp, Tcl_Obj *connObj, CONST char *namespace, Tcl_Obj *queryObj) { + int objc, result; + Tcl_Obj **objv; + mongo_connection *connPtr = MongoGetConn(connObj); + char *db, *collection; + int count, length; + bson query[1]; + + if (connPtr == NULL) { + return NsfObjErrType(interp, "", connObj, "connection", NULL); + } + + result = Tcl_ListObjGetElements(interp, queryObj, &objc, &objv); + if (result != TCL_OK || (objc % 3 != 0)) { + return NsfPrintError(interp, "%s: must contain a multiple of 3 elements", ObjStr(queryObj)); + } + + BsonAppendObjv(interp, query, objc, objv); + + length = strlen(namespace)+1; + db = ckalloc(length); + memcpy(db, namespace, length); + collection = strchr(db, '.'); + + if (collection != NULL) { + /* successful */ + *collection = '\0'; + collection ++; + count = mongo_count(connPtr, db, collection, query); + } else { + count = 0; + } + + bson_destroy( query ); + ckfree(db); + + Tcl_SetObjResult(interp, Tcl_NewIntObj(count)); + + return TCL_OK; +} + + +/* cmd index NsfMongoIndex { {-argName "conn" -required 1 -type tclobj} {-argName "namespace" -required 1} @@ -531,7 +581,7 @@ static int NsfMongoInsert(Tcl_Interp *interp, Tcl_Obj *connObj, CONST char *namespace, Tcl_Obj *valuesObj) { mongo_connection *connPtr = MongoGetConn(connObj); int i, objc, result; - Tcl_Obj **objv; + Tcl_Obj **objv, *resultObj; bson_buffer buf[1]; bson b[1]; @@ -557,6 +607,10 @@ bson_from_buffer( b, buf ); mongo_insert(connPtr, namespace, b); + + resultObj = BsonToList(interp, b->data, 0); + Tcl_SetObjResult(interp, resultObj); + bson_destroy(b); return TCL_OK; @@ -595,7 +649,7 @@ resultObj = Tcl_NewListObj(0, NULL); /* - * The la񓩌st field of mongo_find is options, semantics are described here + * The last field of mongo_find is options, semantics are described here * http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-OPQUERY */ cursor = mongo_find( connPtr, namespace, query, NULL, withLimit, withSkip, 0 ); Index: library/mongodb/nx-mongo.tcl =================================================================== diff -u -rec9e525c887a0ae430bdb35bef01f499b25d617f -r2837e8ce08344ee3f82a7451109f14a4b7cb3395 --- library/mongodb/nx-mongo.tcl (.../nx-mongo.tcl) (revision ec9e525c887a0ae430bdb35bef01f499b25d617f) +++ library/mongodb/nx-mongo.tcl (.../nx-mongo.tcl) (revision 2837e8ce08344ee3f82a7451109f14a4b7cb3395) @@ -15,6 +15,7 @@ ::nx::Object create ::nx::mongo::db { :public method connect {args} {set :mongoConn [::mongo::connect {*}$args]} + :public method count {args} {::mongo::count ${:mongoConn} {*}$args} :public method index {args} {::mongo::index ${:mongoConn} {*}$args} :public method insert {args} {::mongo::insert ${:mongoConn} {*}$args} :public method remove {args} {::mongo::remove ${:mongoConn} {*}$args} @@ -88,15 +89,34 @@ } } + :public method remove {object value} { + if {[:isMultivalued]} { + set values [::nsf::var::set $object ${:name}] + set p [lsearch $values $value] + if {$p < 0} { + error "$value not included in $object.$value ($values)" + } + set newValues [lreplace $values $p $p] + ::nsf::var::set $object ${:name} $newValues + } else { + error "remove just implemented for multivalued slots" + } + } + # + # Type converter for handling embedded objects. Makes sure to + # track "embedded in" relationship # - # :public method type=embedded {name value args} { 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]} { - ::nsf::var::set $value _embedded_in [list $s $name] - puts stderr [list ::nsf::var::set $value _embedded_in [list $s $name]] + ::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" } @@ -290,23 +310,80 @@ return $bson } + :method "bson pp_array" {{-indent 0} list} { + set result [list] + foreach {name type value} $list { + switch $type { + object { lappend result "\{ [:bson pp -indent $indent $value] \}" } + array { lappend result "\[ [:bson pp_array -indent $indent $value] \]" } + default { lappend result [list $value]} + } + } + return [join $result ", "] + } + + :method "bson pp" {{-indent 0} list} { + set result [list] + set nextIndent [expr {$indent + 2}] + foreach {name type value} $list { + set prefix "\n[string repeat { } $indent]$name: " + switch $type { + object { lappend result "$prefix\{ [:bson pp -indent $nextIndent $value] \}" } + array { lappend result "$prefix\[ [:bson pp_array -indent $nextIndent $value] \]" } + default { lappend result $prefix[list $value]} + } + } + return [join $result ", "] + } + # # embedded_in denotes that the object is embedded in another # object with a reference to the attribute # # :public method embedded_in {object attribute} { - # set :_embedded_in [list $object $attribute] + # set :__embedded_in [list $object $attribute] # $object $attribute add [self] end # } # + # destroy a mapped object from memory + # + :public method destroy {} { + if {[array exists :__contains]} { + # destroy embedded object + foreach o [array names :__contains] { + puts "[self] contains $o -> destroy" + $o destroy + } + } + if {[info exists :__embedded_in]} { + lassign ${:__embedded_in} parent att + ::nsf::var::unset $parent __contains([self]) + } + next + } + + # # delete the current object from the db # :public method delete {} { - set document [[:info class] document] - if {$document eq ""} { - set embeddedIn [...] + puts stderr "deleting [:serialize]" + 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" + :destroy } else { + puts "delete a non-embedded entry" + if {[info exists :_id]} { + set document [[:info class] document] + ::nx::mongo::db remove $document [list _id oid ${:_id}] + } else { + error "[self]: object does not contain an _id; it can't be delete from the mongo db." + } } } @@ -327,7 +404,9 @@ ::nx::mongo::db update $document [list _id oid ${:_id}] $bson } else { puts stderr "we have to insert $bson" - ::nx::mongo::db insert $document $bson + puts stderr [:bson pp $bson] + set r [::nx::mongo::db insert $document $bson] + set :_id [lindex $r 2] } } }