Index: library/mongodb/README =================================================================== diff -u -r05b2776a0ecbc0453ae96bbfa9d94315e466f3f5 -r4940f1317b9827162d7a0d28c74da0758ffe2d29 --- library/mongodb/README (.../README) (revision 05b2776a0ecbc0453ae96bbfa9d94315e466f3f5) +++ library/mongodb/README (.../README) (revision 4940f1317b9827162d7a0d28c74da0758ffe2d29) @@ -3,15 +3,20 @@ Ingredients: https://github.com/mongodb/mongo - https://github.com/mongodb/mongo-c-driver-legacy + https://github.com/mongodb/mongo-c-driver The current version is tested with -- MongoDB v2.4.5 -- mongodb-c-driver-legacy v0.8.1 - git checkout v0.8.1 +- MongoDB v2.4.9 +- mongodb-c-driver v0.8.1 Compile or obtain mongodb (the database). Compile or obtain the mongo-c-driver (client interface) + git clone https://github.com/mongodb/mongo-c-driver + cd mongo-c-driver + git checkout v0.8.1 + make + make install + Install the mongo-c-driver Assume, Tcl is under /usr/local/ns/lib and the source of the @@ -20,15 +25,9 @@ command from nsf*/library/mongodb/ ./configure --with-tcl=/usr/local/ns/lib --with-nsf=../../ \ - --with-mongo=/usr/local/src/mongo-c-driver/src/,/usr/local/src/mongo-c-driver + --with-mongodb=/usr/local/src/mongo-c-driver/src/,/usr/local/src/mongo-c-driver -I had to add MONGO_HAVE_STDINT to the EXTRA_FLAGS -of library/mongodb/Makefile.in on MacOS X - EXTRA_CFLAGS = @PKG_CFLAGS@ -DMONGO_HAVE_STDINT - -TODO: remove the hardcoded define flag in Makefile.in - In order to run the sample script, * first start the mongdb (eg. mongod) Index: library/mongodb/configure =================================================================== diff -u -r05b2776a0ecbc0453ae96bbfa9d94315e466f3f5 -r4940f1317b9827162d7a0d28c74da0758ffe2d29 --- library/mongodb/configure (.../configure) (revision 05b2776a0ecbc0453ae96bbfa9d94315e466f3f5) +++ library/mongodb/configure (.../configure) (revision 4940f1317b9827162d7a0d28c74da0758ffe2d29) @@ -4832,7 +4832,7 @@ MONGO_LIB_DIR="`echo $with_mongodb |cut -f2 -d, -s`" fi if test -z "$MONGO_INC_DIR" ; then - gdbm_h_ok=1 + mongo_h_ok=0 MONGO_INC_SPEC="" else MONGO_INC_SPEC="-I${MONGO_INC_DIR}" @@ -4920,8 +4920,7 @@ done -#TEA_ADD_LIBS([$NSF_BUILD_STUB_LIB_SPEC $NSF_BUILD_LIB_SPEC $MONGO_LIB_SPEC -Wl,-rpath,${MONGO_LIB_DIR} -L${MONGO_LIB_DIR} -lmongoc -lbson]) -#TEA_ADD_LIBS([$NSF_BUILD_STUB_LIB_SPEC $NSF_BUILD_LIB_SPEC $MONGO_LIB_SPEC -L${MONGO_LIB_DIR} -lmongoc -lbson]) +#TEA_ADD_LIBS([$NSF_BUILD_STUB_LIB_SPEC $MONGO_LIB_SPEC -Wl,-rpath,${MONGO_LIB_DIR} -L${MONGO_LIB_DIR} -lmongoc -lbson]) vars="$NSF_BUILD_STUB_LIB_SPEC $MONGO_LIB_SPEC -L${MONGO_LIB_DIR} -lmongoc -lbson" for i in $vars; do Index: library/mongodb/configure.ac =================================================================== diff -u -r05b2776a0ecbc0453ae96bbfa9d94315e466f3f5 -r4940f1317b9827162d7a0d28c74da0758ffe2d29 --- library/mongodb/configure.ac (.../configure.ac) (revision 05b2776a0ecbc0453ae96bbfa9d94315e466f3f5) +++ library/mongodb/configure.ac (.../configure.ac) (revision 4940f1317b9827162d7a0d28c74da0758ffe2d29) @@ -91,7 +91,7 @@ MONGO_LIB_DIR="`echo $with_mongodb |cut -f2 -d, -s`" fi if test -z "$MONGO_INC_DIR" ; then - gdbm_h_ok=1 + mongo_h_ok=0 MONGO_INC_SPEC="" else MONGO_INC_SPEC="-I${MONGO_INC_DIR}" Index: library/mongodb/nx-mongo.tcl =================================================================== diff -u -rece9e8e60ebe51b69dcb0511110a030d7c71f59f -r4940f1317b9827162d7a0d28c74da0758ffe2d29 --- library/mongodb/nx-mongo.tcl (.../nx-mongo.tcl) (revision ece9e8e60ebe51b69dcb0511110a030d7c71f59f) +++ library/mongodb/nx-mongo.tcl (.../nx-mongo.tcl) (revision 4940f1317b9827162d7a0d28c74da0758ffe2d29) @@ -20,7 +20,7 @@ namespace eval ::nx::mongo { - set ::nx::mongo::log 1 + set ::nx::mongo::log 0 ::nx::Object create ::nx::mongo::db { :object property db @@ -66,6 +66,7 @@ # ::nx::MetaSlot create ::nx::mongo::Attribute -superclass ::nx::VariableSlot { :property mongotype + :property rep # # manage logging of mongo concerns @@ -102,12 +103,18 @@ # mapping. For now, this handles just the array notation. # :method "bson decode" {bsontype value} { - #puts stderr "bson decode of ${:name} /$bsontype/ '$value'" + #puts stderr "bson decode of ${:name} /$bsontype/ '$value'" if {$bsontype eq "array"} { if {![:isMultivalued]} { # We got an array, but the slot is not multivalued. Maybe # generating an error is too harsh, but for the mapping back, # we check for multivalued as well. + + # aaaaa + puts stderr [list bsontype $bsontype value $value] + #set result [list] + #foreach {pos type v} $value {lappend result [:bson decode $type $v]} + #puts stderr "[self] $result" error "Attribute ${:name} should be multivalued, but it is not" } set result [list] @@ -330,26 +337,46 @@ } :method "bson parameter" {tuple} { + # + # Translate bson tuple into a parameter values pairs suited as + # configure parameters + # #puts "bson parameter: <$tuple>" set objParams [list] foreach {att type value} $tuple { set slot [:get slot $att] - if {$slot eq ""} {error "could not obtain slot for <$att $op $value>"} + if {$slot eq ""} {error "could not obtain slot for <$att $type $value>"} lappend objParams -$att [$slot bson decode $type $value] } #puts "bson parameter <$tuple> => $objParams" return $objParams } - - :public method "bson create" {{-name ""} tuple} { - if {$name ne ""} { - return [:create $name {*}[:bson parameter $tuple]] - } else { - #puts "CREATE new [self] <$tuple>" - return [:new {*}[:bson parameter $tuple]] + + :method "bson setvalues" {tuple} { + # + # Translate bson tuple into a cmd to set instance values, which + # can be evaluated in the context of an object. + # + #puts "bson setvalues: <$tuple>" + set cmd "" + foreach {att type value} $tuple { + set slot [:get slot $att] + if {$slot eq ""} {error "could not obtain slot for <$att $type $value>"} + if {[nx::var exists $slot rep] && [nx::var set $slot rep] ne ""} { + set script [:bson rep decode [nx::var set $slot rep] $slot $att $type $value] + append cmd $script\n + } else { + append cmd "set [list :$att] [list [$slot bson decode $type $value]]\n" + } } + #puts "bson parameter <$tuple> => $objParams" + return $cmd } + :public method "bson create" {{-name ""} tuple} { + ::nsf::object::alloc [self] $name [:bson setvalues $tuple] + } + :method "bson pp_array" {{-indent 0} list} { set result [list] foreach {name type value} $list { @@ -381,15 +408,43 @@ # default slot class # :public method property { - {-incremental:switch} - spec - {-class ::nx::mongo::Attribute} - {initblock ""} - } { + {-accessor ""} + {-class ::nx::mongo::Attribute} + {-configurable:boolean true} + {-incremental:switch} + {-rep ""} + spec:parameter + {initblock ""} + } { regsub -all {,type=} $spec {,arg=} spec - next [list -class $class -incremental=$incremental $spec $initblock] + set result [next [list -accessor $accessor -class $class \ + -configurable $configurable -incremental=$incremental \ + $spec $initblock]] + lassign [::nx::MetaSlot parseParameterSpec $spec] name + [:info slots $name] configure -rep $rep + return $result } + :public method variable { + {-accessor "none"} + {-class ::nx::mongo::Attribute} + {-configurable:boolean false} + {-incremental:switch} + {-initblock ""} + {-rep ""} + spec:parameter + defaultValue:optional + } { + regsub -all {,type=} $spec {,arg=} spec + set result [next [list -accessor $accessor -class $class \ + -configurable $configurable -incremental=$incremental \ + -initblock $initblock $spec \ + {*}[expr {[info exists defaultValue] ? [list $defaultValue] : ""}]]] + lassign [::nx::MetaSlot parseParameterSpec $spec] name + [:info slots $name] configure -rep $rep + return $result + } + :public method pretty_variables {} { set vars {} foreach p [lmap handle [lsort [:info variables]] {::nx::Object info variable parameter $handle}] { @@ -539,8 +594,69 @@ :mixin add ::nx::mongo::Object :mongo_setup } + + # + :public method "bson rep encode array" {slot obj name} { + return [$slot bson encode -array [$obj eval [list array get :$name]]] + } + :public method "bson rep decode array" {slot name bsontype value} { + set result [list] + foreach {pos type v} $value {lappend result [$slot bson decode $type $v]} + return [list array set :$name $result] + } } + + # + # Allow special representations in MongoDB for instance variables. + # The methods + # + # bson rep encode .... + # bson rep decode .... + # + # allow for creating tailored methods to obtain + encode instance + # variables and for decode an setting these. The codecs + # (coder/decoder) are extensible on the application level by + # defining ensemble methods with the name of the codec as last part. + ::nx::mongo::Class eval { + # + # rep codec "array" + # + :public method "bson rep encode array" {slot obj name} { + set body {} + set c 0 + foreach {k v} [$obj eval [list array get :$name]] { + lappend body [incr c] object [list k string $k v string $v] + } + return [list array $body] + } + :public method "bson rep decode array" {slot name bsontype value} { + set av "" + foreach {pos type entry} $value { + lappend av [lindex $entry 2] [lindex $entry 5] + } + return "array set :$name [list $av]" + } + + # + # rep codec "dict" + # + :public method "bson rep encode dict" {slot obj name} { + set body {} + dict for {k v} [$obj eval [list set :$name]] { + lappend body $k string $v + } + return [list object $body] + } + :public method "bson rep decode dict" {slot name bsontype value} { + set result "" + foreach {k type v} $value { + lappend result $k $v + } + return "set :$name \[dict create $result\]" + } + } + ####################################################################### # The class mongo::Object provides methods for mongo objects (such as # "save") @@ -572,7 +688,11 @@ foreach var [:info vars] { set slot [$cls get slot $var] if {$slot ne ""} { - lappend bson $var {*}[$slot bson encode [set :$var]] + if {[nx::var exists $slot rep] && [nx::var set $slot rep] ne ""} { + lappend bson $var {*}[$cls bson rep encode [nx::var set $slot rep] $slot [self] $var] + } else { + lappend bson $var {*}[$slot bson encode [set :$var]] + } } } return $bson Index: library/mongodb/tests/nx-rep.test =================================================================== diff -u --- library/mongodb/tests/nx-rep.test (revision 0) +++ library/mongodb/tests/nx-rep.test (revision 4940f1317b9827162d7a0d28c74da0758ffe2d29) @@ -0,0 +1,153 @@ +# -*- tcl -*- +# +# An example for the usage of the nx mongo object mapping with +# input/export functions for variables not following the standard +# nx-mapping of properties. +# +# The MongoDB nx package supports special codec (encoding/decoding) +# functions which perform the mapping from instance variables to bson +# and vice versa. These special mappings can be defined via the +# attribute "rep" for :variable and :property. The predefined +# representations for "array" and "dict" are included here. The rep +# mechanism is extensible, users can define on the application layer +# their own representations. +# +package require nx::mongo +package require nx::serializer +package require nx::test + +#nsf::configure debug 2 + +# Establish connection to the database +::nx::mongo::db connect -db "tutorial" + +# Make sure, we start always from scratch, so remove everything from +# the collection. +nx::mongo::db drop collection foos + +###################################################################### +# nx-mongo has a built-in special representation for Tcl arrays which +# can be used via e.g. +# +# :variable -rep array a +# +# The "array" representation differs from a string representation by +# requiring a different method in tcl to read its values, and by +# saving the tcl array in mongoDB in a different notation. The chosen +# representation of the array in mongoDB is an array of key/value +# pairs. +# +# Similarly we provide here a "dict" mapping, which transforms a dict +# to a a form similar to a nested object. Just the first level of the +# dict is transformed. + +###################################################################### +# +# Extend ::nx::mongo::Class to handle rep codecs "array" and "dict" +# +::nx::mongo::Class eval { + # + # rep codec "array" + # + :public method "bson rep encode array" {slot obj name} { + set body {} + set c 0 + foreach {k v} [$obj eval [list array get :$name]] { + lappend body [incr c] object [list k string $k v string $v] + } + return [list array $body] + } + :public method "bson rep decode array" {slot name bsontype value} { + set av "" + foreach {pos type entry} $value { + lappend av [lindex $entry 2] [lindex $entry 5] + } + return "array set :$name [list $av]" + } + + # + # rep codec "dict" + # + :public method "bson rep encode dict" {slot obj name} { + set body {} + dict for {k v} [$obj eval [list set :$name]] { + lappend body $k string $v + } + return [list object $body] + } + :public method "bson rep decode dict" {slot name bsontype value} { + set result "" + foreach {k type v} $value { + lappend result $k $v + } + return "set :$name \[dict create $result\]" + } +} + +###################################################################### +# +# Define an application class Foo, using the rep type "array" for +# instance variable "a" and the rep type "dict" for instance variable +# "d". +# + +nx::mongo::Class create Foo { + :index tags + :property title + :property -incremental tags:0..n + :variable -rep array a + :variable -rep dict d + + :public method bar {} {return [lsort [array names :a]]} + :public method baz {key} {dict get ${:d} $key} +} + +###################################################################### +# Build a composite Posting instance based on the example above. +# +set p [Foo new -title "Hello World" { + :tags add a + set :a(1) a + set :a(2) b + set :d [dict create first_name Walter second_name White] +}] + +? {$p bar} "1 2" +? {$p baz first_name} "Walter" + +# +# When we save the item, the instances variables are transformed into +# a mongoDB representation, using the rep types defined above. +# +? {nx::mongo::db is_oid [$p save]} 1 + +$p destroy +? {nsf::is object $p} 0 + +# Now fetch the first entry +set q [Foo find first] + +? {$q bar} "1 2" +? {$q baz first_name} "Walter" + + +Foo show + +puts stderr ====EXIT +###################################################################### +# Output +###################################################################### +# +# { +# _id: 5301d307249bc51c00000000, +# d: { +# first_name: Walter, +# second_name: White }, +# a: [ { +# k: 1, +# v: a }, { +# k: 2, +# v: b } ], +# title: {Hello World}, +# tags: [ a ] +# } Index: library/mongodb/tests/nx-serialize.test =================================================================== diff -u --- library/mongodb/tests/nx-serialize.test (revision 0) +++ library/mongodb/tests/nx-serialize.test (revision 4940f1317b9827162d7a0d28c74da0758ffe2d29) @@ -0,0 +1,176 @@ +# -*- tcl -*- +# +# An example for using MongoDB as a persistence store. +# +# Gustaf Neumann fecit, Feb 2014 +# +package require nx::mongo +package require nx::serializer +package require nx::test + +#nsf::configure debug 2 + +###################################################################### +# Define sample methods to use MongoDB as a simple persistence store +# for nx. The provided methods are +# +# nx::Class "mongo persist" ?-db db? ?-collection name? ?-closure? +# nx::Class "mongo fetch" ?-db db? ?-collection name? +# +# where "mongo persist" stores/updates the direct or indirect +# instances in the specified collection and "mongo fetch" loads +# all instances from this collection +# +nx::Class public method "mongo persist" { + {-db "tutorial"} + {-collection "nx"} + {-closure:switch} + } { + set mongo_ns $db.$collection + set count(update) 0 + set count(insert) 0 + + foreach i [:info instances -closure=$closure] { + set isNew [string match "::nsf::__#*" $i] + set bson [list name string $i class string [$i info class] isNew string $isNew] + lappend bson definition string [$i serialize] + + if {[::nx::var exists $i _id]} { + #puts "we have to update " + ::nx::mongo::db update $mongo_ns [list _id oid [::nx::var set $i _id]] $bson + incr count(update) + } else { + #puts "we have to insert to $mongo_ns" + set r [::nx::mongo::db insert $mongo_ns $bson] + ::nx::var set $i _id [lindex $r 2] + incr count(insert) + } + } + puts "$count(insert) instances inserted, $count(update) instances updated in $mongo_ns" +} + +nx::Class public method "mongo fetch" { + {-db "tutorial"} + {-collection "nx"} + } { + set mongo_ns $db.$collection + + set result {} + set bson [::nx::mongo::db query $mongo_ns {}] + foreach obj $bson { + foreach {att type value} $obj { + switch $att { + _id {set _id $value} + class {set class $value} + definition {set definition $value} + name {set name $value} + } + } + eval $definition + $name eval [list set _id $_id] + ::nx::var set $name _id $_id + lappend result $name + } + return $result +} + +# +###################################################################### +# +# Sample usage of the two methods +# + +# Establish connection to the database +::nx::mongo::db connect -db "tutorial" + +# Make sure, we start always from scratch, so remove everything from +# the collection. +nx::mongo::db drop collection nx + +###################################################################### +# +# Define an arbitrary class +# + +nx::Class create Foo { + :property title + :property {count 1} + :public method ++ {} {incr :count} + :public method hasArray {} {array exists :a} + + :public object method counts {} { + foreach i [:info instances] {incr c [$i cget -count]} + return $c + } + :public object method countArrays {} { + foreach i [:info instances] {incr c [$i hasArray]} + return $c + } + +} + +###################################################################### +# +# Create an instance of Foo containing e.g. arrays or dicts as +# instance variables +# +Foo new -title t1 { + set :a(1) a + set :a(2) b + set :d [dict create x 100 y 101] + set :count 100 +} +Foo new -title t2 + +? {llength [Foo info instances]} 2 "Foo instances before persist" +? {Foo counts} 101 +? {Foo countArrays} 1 + +foreach i [Foo info instances] {$i ++} +? {Foo counts} 103 + +# +# Save all instances of Foo (inserts) +# +Foo mongo persist + +# +# Destroy all instances of Foo in memory +# +foreach i [Foo info instances] {$i destroy} +? {llength [Foo info instances]} 0 "Foo instances after destroy" + +# +# Load instances from MongoDB +# +::nx::Class mongo fetch + +? {llength [Foo info instances]} 2 "Foo instances after fetch" +? {Foo counts} 103 +? {Foo countArrays} 1 + +foreach i [Foo info instances] {$i ++} +? {Foo counts} 105 + +# +# create one more instance, also with an array +# +Foo new {set :a(x) foo} +? {Foo counts} 106 +? {Foo countArrays} 2 + +# +# Save all instances of Foo (updates) +# +Foo mongo persist + +foreach i [Foo info instances] {$i destroy} +? {llength [Foo info instances]} 0 "Foo instances after destroy" + +::nx::Class mongo fetch +? {Foo counts} 106 +? {Foo countArrays} 2 + + +exit +