/* * Experimental Interface to MongoDB based on nsf (Next Scripting * Framework) * * The example shows how to use the source code generator from nsf to * generate a c interface. * This implementation provides a low level interface based on tagged * elements to force / preserve the datatypes of mongodb. * * This code serves as well as an example how to use the source code * generator of nsf to provide a C interface for optional nsf * packages. * * -gustaf neumann March 27, 2011 */ #include #include #include #include "bson.h" #include "mongo.h" #include #include #include #include /* * Define the counters to generate nice symbols for pointer converter */ static int gridfsCount = 0; static int gridfileCount = 0; static int mongoCount = 0; static int cursorCount = 0; /*********************************************************************** * The following definitions should not be here, but they are included * to get compilation going for the time being. ***********************************************************************/ typedef void *NsfObject; #define PARSE_CONTEXT_PREALLOC 20 typedef struct { ClientData *clientData; int status; Tcl_Obj **objv; Tcl_Obj **full_objv; int *flags; ClientData clientData_static[PARSE_CONTEXT_PREALLOC]; Tcl_Obj *objv_static[PARSE_CONTEXT_PREALLOC+1]; int flags_static[PARSE_CONTEXT_PREALLOC+1]; int lastobjc; int objc; int varArgs; NsfObject *object; } ParseContext; #define NSF_ARG_REQUIRED 0x000001 #define nr_elements(arr) ((int) (sizeof(arr) / sizeof(arr[0]))) #define ObjStr(obj) (obj)->bytes ? (obj)->bytes : Tcl_GetString(obj) #ifdef UNUSED #elif defined(__GNUC__) # define UNUSED(x) UNUSED_ ## x __attribute__((unused)) #elif defined(__LCLINT__) # define UNUSED(x) /*@unused@*/ x #else # define UNUSED(x) x #endif #if defined(HAVE_STDINT_H) # define HAVE_INTPTR_T # define HAVE_UINTPTR_T #endif #if !defined(INT2PTR) && !defined(PTR2INT) # if defined(HAVE_INTPTR_T) || defined(intptr_t) # define INT2PTR(p) ((void *)(intptr_t)(p)) # define PTR2INT(p) ((int)(intptr_t)(p)) # else # define INT2PTR(p) ((void *)(p)) # define PTR2INT(p) ((int)(p)) # endif #endif #if !defined(UINT2PTR) && !defined(PTR2UINT) # if defined(HAVE_UINTPTR_T) || defined(uintptr_t) # define UINT2PTR(p) ((void *)(uintptr_t)(p)) # define PTR2UINT(p) ((unsigned int)(uintptr_t)(p)) # else # define UINT2PTR(p) ((void *)(p)) # define PTR2UINT(p) ((unsigned int)(p)) # endif #endif static int ArgumentParse(Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], NsfObject *obj, Tcl_Obj *procName, Nsf_Param CONST *paramPtr, int nrParameters, int serial, int doCheck, ParseContext *pc) { return Nsf_ArgumentParse(interp, objc, objv, (Nsf_Object *)obj, procName, paramPtr, nrParameters, serial, doCheck, (Nsf_ParseContext *)pc); } /*********************************************************************** * Include the generated mongo db api. ***********************************************************************/ #include "mongoAPI.h" /*********************************************************************** * Helper functions ***********************************************************************/ /* *---------------------------------------------------------------------- * * BsonToList -- * * Convert a bson structure to a tagged list. Each value field is * preceded by a tag denoting its bson type. * * Results: * Tagged list. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Obj * BsonToList(Tcl_Interp *interp, const char *data , int depth) { bson_iterator i; const char *key, *tag; char oidhex[25]; Tcl_Obj *resultObj, *elemObj; bson_iterator_from_buffer( &i , data ); resultObj = Tcl_NewListObj(0, NULL); while ( bson_iterator_next( &i ) ){ bson_type t = bson_iterator_type( &i ); if ( t == 0 ) break; key = bson_iterator_key( &i ); /*fprintf(stderr, "BsonToList: key %s t %d string %d\n", key, t, bson_string);*/ switch ( t ){ case BSON_INT: tag = "integer"; elemObj = Tcl_NewIntObj(bson_iterator_int( &i )); break; case BSON_LONG: tag = "long"; elemObj = Tcl_NewLongObj(bson_iterator_long( &i )); break; case BSON_DATE: tag = "date"; elemObj = Tcl_NewLongObj(bson_iterator_date( &i )); break; case BSON_DOUBLE: tag = "double"; elemObj = Tcl_NewDoubleObj(bson_iterator_double( &i )); break; case BSON_BOOL: tag = "boolean"; elemObj = Tcl_NewBooleanObj(bson_iterator_bool( &i )); break; case BSON_REGEX: tag = "regex"; elemObj = Tcl_NewStringObj(bson_iterator_regex( &i ), -1); break; case BSON_STRING: tag = "string"; elemObj = Tcl_NewStringObj(bson_iterator_string( &i ), -1); break; case BSON_NULL: tag = "null"; elemObj = Tcl_NewStringObj("null", 4); break; case BSON_OID: { tag = "oid"; bson_oid_to_string(bson_iterator_oid(&i), oidhex); elemObj = Tcl_NewStringObj(oidhex, -1); break; } case BSON_TIMESTAMP: { bson_timestamp_t ts; tag = "timestamp"; ts = bson_iterator_timestamp( &i ); elemObj = Tcl_NewListObj(0, NULL); Tcl_ListObjAppendElement(interp, elemObj, Tcl_NewIntObj(ts.t)); Tcl_ListObjAppendElement(interp, elemObj, Tcl_NewIntObj(ts.i)); break; } case BSON_OBJECT: case BSON_ARRAY: tag = t == BSON_OBJECT ? "object" : "array"; elemObj = BsonToList(interp, bson_iterator_value( &i ) , depth + 1 ); break; default: tag = "unknown"; elemObj = Tcl_NewStringObj("", 0); NsfLog(interp, NSF_LOG_WARN, "BsonToList: unknown type %d", t); } Tcl_ListObjAppendElement(interp, resultObj, Tcl_NewStringObj(key, -1)); Tcl_ListObjAppendElement(interp, resultObj, Tcl_NewStringObj(tag, -1)); Tcl_ListObjAppendElement(interp, resultObj, elemObj); } return resultObj; } /* *---------------------------------------------------------------------- * * BsonTagToType -- * * Convert a bson tag string to a bson_type. For the time being * we compare as little as possible characters. In the future we * might want to cache the bson tag in the Tcl_obj, maybe we can * use Tcl_GetIndexFromObj(); * * Results: * bson_type. * * Side effects: * None. * *---------------------------------------------------------------------- */ bson_type BsonTagToType(Tcl_Interp *interp, char *tag) { char firstChar = *tag; switch (firstChar) { case 'a': /* array */ return BSON_ARRAY; case 'b': /* bool */ return BSON_BOOL; case 'd': if (*(tag + 1) == 'a') /* date */ return BSON_DATE; if (*(tag + 1) == 'o') /* double */ return BSON_DOUBLE; case 'i': /* integer */ return BSON_INT; case 'l': /* long */ return BSON_LONG; case 'n': /* null */ return BSON_NULL; case 'o': if (*(tag + 1) == 'i') /* oid */ return BSON_OID; if (*(tag + 1) == 'b') /* object */ return BSON_OBJECT; break; case 'r': /* regex */ return BSON_REGEX; case 's': /* string */ return BSON_STRING; case 't': /* timestamp */ return BSON_TIMESTAMP; } NsfLog(interp, NSF_LOG_WARN, "BsonTagToType: Treat unknown tag '%s' as string", tag); return BSON_STRING; } /* *---------------------------------------------------------------------- * * BsonAppend -- * * append a tagged element to a bson buffer. * * Results: * Tcl result code. * * Side effects: * Value appended to bson buffer. * *---------------------------------------------------------------------- */ static int BsonAppend(Tcl_Interp *interp, bson *bbPtr, char *name, char *tag, Tcl_Obj *value) { int result = TCL_OK; bson_type t = BsonTagToType(interp, tag); /*fprintf(stderr, "BsonAppend: add name %s tag %s value '%s'\n", name, tag, ObjStr(value));*/ switch ( t ){ case BSON_STRING: bson_append_string(bbPtr, name, ObjStr(value)); break; case BSON_INT: { int v; result = Tcl_GetIntFromObj(interp, value, &v); if (result != TCL_OK) break; bson_append_int(bbPtr, name, v); break; } case BSON_DOUBLE: { double v; result = Tcl_GetDoubleFromObj(interp, value, &v); if (result != TCL_OK) break; bson_append_double(bbPtr, name, v); break; } case BSON_BOOL: { int v; result = Tcl_GetBooleanFromObj(interp, value, &v); if (result != TCL_OK) break; bson_append_bool(bbPtr, name, v); break; } case BSON_LONG: { long v; result = Tcl_GetLongFromObj(interp, value, &v); if (result != TCL_OK) break; bson_append_long(bbPtr, name, v); break; } case BSON_NULL: { bson_append_null(bbPtr, name); break; } case BSON_OID: { bson_oid_t v; bson_oid_from_string(&v, ObjStr(value)); bson_append_oid(bbPtr, name, &v); break; } case BSON_REGEX: { char *opts = ""; /* TODO: how to handle regex opts? */ bson_append_regex(bbPtr, name, ObjStr(value), opts); break; } case BSON_DATE: { long v; result = Tcl_GetLongFromObj(interp, value, &v); if (result != TCL_OK) break; bson_append_date(bbPtr, name, v); break; } case BSON_TIMESTAMP: { bson_timestamp_t v; Tcl_Obj **objv; int objc = 0; result = Tcl_ListObjGetElements(interp, value, &objc, &objv); if (result != TCL_OK || objc != 2) { return NsfPrintError(interp, "invalid timestamp: %s", ObjStr(value)); } result = Tcl_GetIntFromObj(interp, objv[0], &v.t); if (result == TCL_OK) { result = Tcl_GetIntFromObj(interp, objv[1], &v.i); } if (result != TCL_OK) break; bson_append_timestamp(bbPtr, name, &v); break; } case BSON_OBJECT: case BSON_ARRAY: { int i, objc; Tcl_Obj **objv; result = Tcl_ListObjGetElements(interp, value, &objc, &objv); if (result != TCL_OK || objc % 3 != 0) { return NsfPrintError(interp, "invalid %s value contain multiple of 3 elements %s", tag, ObjStr(value)); } if (t == BSON_OBJECT) { bson_append_start_object(bbPtr, name); } else { bson_append_start_array(bbPtr, name); } for (i = 0; i< objc; i += 3) { /*fprintf(stderr, "value %s, i %d, [0]: %s, [1]: %s, [2]: %s\n", ObjStr(value), i, ObjStr(objv[i]), ObjStr(objv[i+1]), ObjStr(objv[i+2]));*/ result = BsonAppend(interp, bbPtr, ObjStr(objv[i]), ObjStr(objv[i+1]), objv[i+2]); if (result != TCL_OK) break; } /* * finish_object works for arrays and objects */ bson_append_finish_object(bbPtr); break; } case BSON_BINDATA: case BSON_DBREF: case BSON_CODE: case BSON_SYMBOL: case BSON_CODEWSCOPE: return NsfPrintError(interp, "tag %s not handled yet", tag); break; case BSON_UNDEFINED: case BSON_EOO: break; /* no default here, to get the warning to the compilation log for the time being */ } return result; } /* *---------------------------------------------------------------------- * * BsonAppendObjv -- * * Append all elements of objv to an uninitialized bson buffer. * * Results: * Tcl result code. * * Side effects: * Value appended to bson buffer. * *---------------------------------------------------------------------- */ static int BsonAppendObjv(Tcl_Interp *interp, bson *bPtr, int objc, Tcl_Obj **objv) { int i; bson_init(bPtr); for (i = 0; i < objc; i += 3) { char *name = ObjStr(objv[i]); char *tag = ObjStr(objv[i+1]); Tcl_Obj *value = objv[i+2]; /*fprintf(stderr, "adding pair '%s' (%s) '%s'\n", name, tag, ObjStr(value));*/ BsonAppend(interp, bPtr, name, tag, value); } bson_finish(bPtr); return TCL_OK; } static char * ErrorMsg(int status) { switch (status) { case MONGO_CONN_NO_SOCKET: return "Could not create socket"; case MONGO_CONN_FAIL: return "An error occured while calling connect()"; case MONGO_CONN_ADDR_FAIL: return "An error occured while calling getaddrinfo()"; case MONGO_CONN_NOT_MASTER: return "Connected to a non-master node (read-only)"; case MONGO_CONN_BAD_SET_NAME: return "Given replica set name doesn't match this replica set"; case MONGO_CONN_NO_PRIMARY: return "Can't find primary in replica set"; case MONGO_IO_ERROR: return "An error occurred while reading or writing on the socket"; case MONGO_READ_SIZE_ERROR: return "The response is not the expected length"; case MONGO_COMMAND_FAILED: return "The command returned with 'ok' value of 0"; case MONGO_BSON_INVALID: return "BSON not valid for the specified op"; case MONGO_BSON_NOT_FINISHED: return "BSON object has not been finished"; default: return "Unknown error (maybe server not running)"; } } /*********************************************************************** * Define the api functions ***********************************************************************/ /* cmd close NsfMongoClose { {-argName "conn" -required 1 -type mongo -withObj 1} } */ static int NsfMongoClose(Tcl_Interp *interp, mongo *connPtr, Tcl_Obj *connObj) { if (connPtr) { mongo_destroy(connPtr); Nsf_PointerDelete(ObjStr(connObj), connPtr, 1); } return TCL_OK; } /* cmd connect NsfMongoConnect { {-argName "-replica-set" -required 0 -nrargs 1} {-argName "-server" -required 0 -nrargs 1 -type tclobj} {-argName "-timeout" -required 0 -nrargs 1 -type int32} } */ static int NsfMongoConnect(Tcl_Interp *interp, CONST char *replicaSet, Tcl_Obj *server, int withTimeout) { char channelName[80], *buffer = NULL; mongo_host_port host_port; int result, objc = 0; mongo *connPtr; int status; Tcl_Obj **objv; if (server) { result = Tcl_ListObjGetElements(interp, server, &objc, &objv); if (result != TCL_OK) { return NsfPrintError(interp, "The provided servers are not a well-formed list"); } } connPtr = (mongo *)ckalloc(sizeof(mongo)); if (objc == 0) { /* * No -server argument or an empty list was provided; use the * mongo default values. */ status = mongo_client( connPtr, "127.0.0.1", 27017 ); } else if (objc == 1 && replicaSet == NULL) { /* * A single element was provided to -server, we have no replica * set specified. */ mongo_parse_host(ObjStr(objv[0]), &host_port); status = mongo_client( connPtr, host_port.host, host_port.port ); if (buffer) {ckfree(buffer);} } else if (replicaSet) { /* * A list of 1 or more server was provided together with a replica * set. */ int i; mongo_replset_init( connPtr, replicaSet ); for (i = 0; i < objc; i++) { mongo_parse_host(ObjStr(objv[i]), &host_port); mongo_replset_add_seed(connPtr, host_port.host, host_port.port ); if (buffer) {ckfree(buffer);} } status = mongo_replset_connect( connPtr ); } else { ckfree((char *)connPtr); return NsfPrintError(interp, "A list of servers was provided, but not name for the replica set"); } /* * Process the status from either mongo_connect() or * mongo_replset_connect(). */ if (status != MONGO_OK) { ckfree((char *)connPtr); return NsfPrintError(interp, ErrorMsg(status)); } if (withTimeout > 0) { /* * setting connection timeout - measured in milliseconds */ if (mongo_set_op_timeout(connPtr, withTimeout) != MONGO_OK) { ckfree((char *)connPtr); return NsfPrintError(interp, "setting connection timeout failed"); } } /* * Make an entry in the symbol table and return entry name it as * result. */ Nsf_PointerAdd(interp, channelName, "mongo", connPtr); Tcl_SetObjResult(interp, Tcl_NewStringObj(channelName, -1)); return TCL_OK; } /* cmd run NsfMongoRunCmd { {-argName "conn" -required 1 -type mongo} {-argName "db" -required 1} {-argName "cmd" -required 1 -type tclobj} } */ static int NsfMongoRunCmd(Tcl_Interp *interp, mongo *connPtr, CONST char *db, Tcl_Obj *cmdObj) { int result, objc; Tcl_Obj **objv; bson cmd[1], out[1]; result = Tcl_ListObjGetElements(interp, cmdObj, &objc, &objv); if (result != TCL_OK || (objc % 3 != 0)) { return NsfPrintError(interp, "%s: must contain a multiple of 3 elements", ObjStr(cmdObj)); } BsonAppendObjv(interp, cmd, objc, objv); mongo_clear_errors( connPtr ); result = mongo_run_command( connPtr, db, cmd, out ); bson_destroy( cmd ); if (result != MONGO_OK) { return NsfPrintError(interp, "mongo::run: provided command returned an unknown error"); } Tcl_SetObjResult(interp, Tcl_NewIntObj(1)); return TCL_OK; } /* cmd query NsfMongoCount { {-argName "conn" -required 1 -type mongo} {-argName "namespace" -required 1} {-argName "query" -required 1 -type tclobj} } */ static int NsfMongoCount(Tcl_Interp *interp, mongo *connPtr, CONST char *namespace, Tcl_Obj *queryObj) { int objc, result; Tcl_Obj **objv; char *db, *collection; int count, length; bson query[1]; 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 mongo} {-argName "namespace" -required 1} {-argName "attributes" -required 1 -type tclobj} {-argName "-name" -required 0 -nrargs 1} {-argName "-background" -required 0 -nrargs 0} {-argName "-dropdups" -required 0 -nrargs 0} {-argName "-sparse" -required 0 -nrargs 0} {-argName "-unique" -required 0 -nrargs 0} } */ static int NsfMongoIndex(Tcl_Interp *interp, mongo *connPtr, CONST char *namespace, Tcl_Obj *attributesObj, CONST char *withName, int withBackground, int withDropdups, int withSparse, int withUnique) { bson_bool_t success; int objc, result, options = 0; Tcl_Obj **objv; bson keys[1], out[1]; result = Tcl_ListObjGetElements(interp, attributesObj, &objc, &objv); if (result != TCL_OK || (objc % 3 != 0)) { return NsfPrintError(interp, "%s: must contain a multiple of 3 elements", ObjStr(attributesObj)); } BsonAppendObjv(interp, keys, objc, objv); if (withBackground) {options |= MONGO_INDEX_BACKGROUND;} if (withDropdups) {options |= MONGO_INDEX_DROP_DUPS;} if (withSparse) {options |= MONGO_INDEX_SPARSE;} if (withUnique) {options |= MONGO_INDEX_UNIQUE;} success = mongo_create_index(connPtr, namespace, keys, withName, options, out); bson_destroy(keys); /* TODO: examples in mongo-client do not touch out; do we have to do something about it? */ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(success)); return TCL_OK; } /* cmd insert NsfMongoInsert { {-argName "conn" -required 1 -type mongo} {-argName "namespace" -required 1} {-argName "values" -required 1 -type tclobj} } */ static int NsfMongoInsert(Tcl_Interp *interp, mongo *connPtr, CONST char *namespace, Tcl_Obj *valuesObj) { int i, objc, result; Tcl_Obj **objv, *resultObj; bson b[1]; result = Tcl_ListObjGetElements(interp, valuesObj, &objc, &objv); if (result != TCL_OK || (objc % 3 != 0)) { return NsfPrintError(interp, "%s: must contain a multiple of 3 elements", ObjStr(valuesObj)); } //bson_init(buf); bson_init(b); bson_append_new_oid(b, "_id"); for (i = 0; i < objc; i += 3) { char *name = ObjStr(objv[i]); char *tag = ObjStr(objv[i+1]); Tcl_Obj *value = objv[i+2]; /*fprintf(stderr, "adding pair '%s' (%s) '%s'\n", name, tag, ObjStr(value));*/ BsonAppend(interp, b, name, tag, value); } bson_finish(b); /* for the time being, no write_concern (last arg of mongo_insert()) */ result = mongo_insert(connPtr, namespace, b, NULL); if (result == MONGO_ERROR) { result = NsfPrintError(interp, ErrorMsg(connPtr->err)); } else { resultObj = BsonToList(interp, b->data, 0); Tcl_SetObjResult(interp, resultObj); result = TCL_OK; } bson_destroy(b); return result; } /* cmd query NsfMongoQuery { {-argName "conn" -required 1 -type mongo} {-argName "namespace" -required 1} {-argName "query" -required 1 -type tclobj} {-argName "-atts" -required 0 -nrargs 1 -type tclobj} {-argName "-limit" -required 0 -type int32} {-argName "-skip" -required 0 -type int32} } */ static int NsfMongoQuery(Tcl_Interp *interp, mongo *connPtr, CONST char *namespace, Tcl_Obj *queryObj, Tcl_Obj *withAttsObj, int withLimit, int withSkip) { int objc1, objc2, result; Tcl_Obj **objv1, **objv2, *resultObj; mongo_cursor *cursor; bson query[1]; bson atts[1]; /*fprintf(stderr, "NsfMongoQuery: namespace %s withLimit %d withSkip %d\n", namespace, withLimit, withSkip);*/ result = Tcl_ListObjGetElements(interp, queryObj, &objc1, &objv1); if (result != TCL_OK || (objc1 % 3 != 0)) { return NsfPrintError(interp, "%s: must contain a multiple of 3 elements", ObjStr(queryObj)); } if (withAttsObj) { result = Tcl_ListObjGetElements(interp, withAttsObj, &objc2, &objv2); if (result != TCL_OK || (objc2 % 3 != 0)) { return NsfPrintError(interp, "%s: must contain a multiple of 3 elements", ObjStr(withAttsObj)); } } else { objc2 = 0; } /* fprintf(stderr, "query # %d, atts # %d\n", objc1, objc2); */ BsonAppendObjv(interp, query, objc1, objv1); BsonAppendObjv(interp, atts, objc2, objv2); resultObj = Tcl_NewListObj(0, NULL); /* * 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, atts, withLimit, withSkip, 0 ); while( mongo_cursor_next( cursor ) == MONGO_OK ) { Tcl_ListObjAppendElement(interp, resultObj, BsonToList(interp, (&cursor->current)->data, 0)); } mongo_cursor_destroy( cursor ); bson_destroy( query ); bson_destroy( atts ); Tcl_SetObjResult(interp, resultObj); return TCL_OK; } /* cmd remove NsfMongoRemove { {-argName "conn" -required 1 -type mongo} {-argName "namespace" -required 1} {-argName "condition" -required 1 -type tclobj} } */ static int NsfMongoRemove(Tcl_Interp *interp, mongo *connPtr, CONST char *namespace, Tcl_Obj *conditionObj) { int objc, result, status; Tcl_Obj **objv; bson query[1]; result = Tcl_ListObjGetElements(interp, conditionObj, &objc, &objv); if (result != TCL_OK || (objc % 3 != 0)) { return NsfPrintError(interp, "%s: must contain a multiple of 3 elements", ObjStr(conditionObj)); } BsonAppendObjv(interp, query, objc, objv); /* for the time being, no write_concern (last arg of mongo_remove()) */ status = mongo_remove(connPtr, namespace, query, NULL); Tcl_SetObjResult(interp, Tcl_NewIntObj(status == MONGO_OK)); bson_destroy(query); return TCL_OK; } /* cmd insert NsfMongoUpdate { {-argName "conn" -required 1 -type mongo} {-argName "namespace" -required 1} {-argName "cond" -required 1 -type tclobj} {-argName "values" -required 1 -type tclobj} {-argName "-upsert" -required 0 -nrargs 0} {-argName "-all" -required 0 -nrargs 0} } */ static int NsfMongoUpdate(Tcl_Interp *interp, mongo *connPtr, CONST char *namespace, Tcl_Obj *conditionObj, Tcl_Obj *valuesObj, int withUpsert, int withAll) { int objc, result, options = 0; Tcl_Obj **objv; bson cond[1], values[1]; result = Tcl_ListObjGetElements(interp, conditionObj, &objc, &objv); if (result != TCL_OK || (objc % 3 != 0)) { return NsfPrintError(interp, "%s: must contain a multiple of 3 elements", ObjStr(conditionObj)); } BsonAppendObjv(interp, cond, objc, objv); result = Tcl_ListObjGetElements(interp, valuesObj, &objc, &objv); if (result != TCL_OK || (objc % 3 != 0)) { bson_destroy(cond); return NsfPrintError(interp, "%s: must contain a multiple of 3 elements", ObjStr(valuesObj)); } BsonAppendObjv(interp, values, objc, objv); if (withUpsert) {options |= 1;} if (withAll) {options |= 2;} /* for the time being, no write_concern (last arg of mongo_update()) */ mongo_update(connPtr, namespace, cond, values, options, NULL); return TCL_OK; } /*********************************************************************** * Cursor interface ***********************************************************************/ /* cmd cursor::find NsfMongoCursorFind { {-argName "conn" -required 1 -type mongo} {-argName "namespace" -required 1} {-argName "query" -required 1 -type tclobj} {-argName "-atts" -required 0 -nrargs 1 -type tclobj} {-argName "-limit" -required 0 -type int32} {-argName "-skip" -required 0 -type int32} {-argName "-tailable" -required 0 -nrargs 0} {-argName "-awaitdata" -required 0 -nrargs 0} } */ static int NsfMongoCursorFind(Tcl_Interp *interp, mongo *connPtr, CONST char *namespace, Tcl_Obj *queryObj, Tcl_Obj *withAttsObj, int withLimit, int withSkip, int withTailable, int withAwaitdata) { int objc1, objc2, result, options = 0; Tcl_Obj **objv1, **objv2; char buffer[80]; mongo_cursor *cursor; bson query[1]; bson atts[1]; /*fprintf(stderr, "NsfMongoQuery: namespace %s withLimit %d withSkip %d\n", namespace, withLimit, withSkip);*/ result = Tcl_ListObjGetElements(interp, queryObj, &objc1, &objv1); if (result != TCL_OK || (objc1 % 3 != 0)) { return NsfPrintError(interp, "%s: must contain a multiple of 3 elements", ObjStr(queryObj)); } if (withAttsObj) { result = Tcl_ListObjGetElements(interp, withAttsObj, &objc2, &objv2); if (result != TCL_OK || (objc2 % 3 != 0)) { return NsfPrintError(interp, "%s: must contain a multiple of 3 elements", ObjStr(withAttsObj)); } } else { objc2 = 0; } BsonAppendObjv(interp, query, objc1, objv1); BsonAppendObjv(interp, atts, objc2, objv2); /* * The last field of mongo_find is options, semantics are described here * http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-OPQUERY */ if (withTailable) { options |= MONGO_TAILABLE; } if (withAwaitdata) { options |= MONGO_AWAIT_DATA; } cursor = mongo_find( connPtr, namespace, query, atts, withLimit, withSkip, options); if (cursor) { Nsf_PointerAdd(interp, buffer, "mongo_cursor", cursor); Tcl_SetObjResult(interp, Tcl_NewStringObj(buffer, -1)); } else { Tcl_ResetResult(interp); } bson_destroy( query ); bson_destroy( atts ); return TCL_OK; } /* cmd cursor::next NsfMongoCursorNext { {-argName "cursor" -required 1 -type mongo_cursor} } */ static int NsfMongoCursorNext(Tcl_Interp *interp, mongo_cursor *cursor) { int result; result = mongo_cursor_next( cursor ); if (result == MONGO_OK) { Tcl_SetObjResult(interp, BsonToList(interp, (&cursor->current)->data, 0)); } return TCL_OK; } /* cmd cursor::close NsfMongoCursorClose { {-argName "cursor" -required 1 -type mongo_cursor -withObj 1} } */ static int NsfMongoCursorClose(Tcl_Interp *interp, mongo_cursor *cursor, Tcl_Obj *cursorObj) { mongo_cursor_destroy( cursor ); Nsf_PointerDelete(ObjStr(cursorObj), cursor, 0); return TCL_OK; } /*********************************************************************** * GridFS interface ***********************************************************************/ /* cmd gridfs::open NsfMongoGridFSOpen { {-argName "conn" -required 1 -type mongo} {-argName "dbname" -required 1} {-argName "prefix" -required 1} } */ static int NsfMongoGridFSOpen(Tcl_Interp *interp, mongo *connPtr, CONST char *dbname, CONST char *prefix) { char buffer[80]; gridfs *gfsPtr; gfsPtr = (gridfs *)ckalloc(sizeof(gridfs)); gridfs_init(connPtr, dbname, prefix, gfsPtr); Nsf_PointerAdd(interp, buffer, "gridfs", gfsPtr); Tcl_SetObjResult(interp, Tcl_NewStringObj(buffer, -1)); return TCL_OK; } /* cmd gridfs::remove_file NsfMongoGridFSRemoveFile { {-argName "gfs" -required 1 -type tclobj} {-argName "filename" -required 1} } */ static int NsfMongoGridFSRemoveFile(Tcl_Interp *interp, gridfs *gridfsPtr, CONST char *filename) { int status; /* the current interfaces does not return a status ! */ status = gridfs_remove_filename(gridfsPtr, filename); Tcl_SetObjResult(interp, Tcl_NewIntObj(status == MONGO_OK)); return TCL_OK; } /* cmd gridfs::store_file NsfMongoGridFSStoreFile { {-argName "gfs" -required 1 -type gridfs} {-argName "filename" -required 1} {-argName "remotename" -required 1} {-argName "contenttype" -required 1} } */ static int NsfMongoGridFSStoreFile(Tcl_Interp *interp, gridfs *gridfsPtr, CONST char *filename, CONST char *remotename, CONST char *contenttype) { int flags = 0; // TODO: add/handle flags int result = gridfs_store_file(gridfsPtr, filename, remotename, contenttype, flags); /* currently, we do not get the bson structure; Tcl_SetObjResult(interp, BsonToList(interp, b.data, 0));*/ Tcl_SetObjResult(interp, Tcl_NewIntObj(result == MONGO_OK)); return TCL_OK; } /* cmd gridfs::close NsfMongoGridFSClose { {-argName "gfs" -required 1 -type gridfs -withObj 1} } */ static int NsfMongoGridFSClose(Tcl_Interp *interp, gridfs *gridfsPtr, Tcl_Obj *gridfsObj) { gridfs_destroy(gridfsPtr); Nsf_PointerDelete(ObjStr(gridfsObj), gridfsPtr, 1); return TCL_OK; } /*********************************************************************** * GridFile interface * * Currently offsets and sizes are limited to 32bit integers, we should * relax this later. ***********************************************************************/ /* cmd gridfile::close NsfMongoGridFileClose { {-argName "file" -required 1 -type gridfile -withObj 1} } */ static int NsfMongoGridFileClose(Tcl_Interp *interp, gridfile* gridFilePtr, Tcl_Obj *gridFileObj) { gridfile_destroy(gridFilePtr); Nsf_PointerDelete(ObjStr(gridFileObj), gridFilePtr, 1); return TCL_OK; } /* cmd gridfile::get_contentlength NsfMongoGridFileGetContentlength { {-argName "gridfile" -required 1 -type gridfile} } */ static int NsfMongoGridFileGetContentlength(Tcl_Interp *interp, gridfile* gridFilePtr) { gridfs_offset len; len = gridfile_get_contentlength(gridFilePtr); Tcl_SetObjResult(interp, Tcl_NewLongObj(len)); return TCL_OK; } /* cmd gridfile::get_contenttype NsfMongoGridFileGetContentType { {-argName "gridfile" -required 1 -type gridfile} } */ static int NsfMongoGridFileGetContentType(Tcl_Interp *interp, gridfile* gridFilePtr) { CONST char *contentType; contentType = gridfile_get_contenttype(gridFilePtr); Tcl_SetObjResult(interp, Tcl_NewStringObj(contentType, -1)); return TCL_OK; } /* cmd gridfile::get_metadata NsfMongoGridFileGetMetaData { {-argName "gridfile" -required 1 -type tclgridfile* gridFilePtrobj} } */ static int NsfMongoGridFileGetMetaData(Tcl_Interp *interp, gridfile* gridFilePtr) { bson b; bson_bool_t copyData = 0; // TODO: what does this gridfile_get_metadata(gridFilePtr, &b, copyData); Tcl_SetObjResult(interp, BsonToList(interp, b.data, 0)); return TCL_OK; } /* cmd gridfile::open NsfMongoGridFileOpen { {-argName "gfs" -required 1 -type gridfs} {-argName "filename" -required 1} } */ static int NsfMongoGridFileOpen(Tcl_Interp *interp, gridfs *gridfsPtr, CONST char *filename) { char buffer[80]; gridfile* gridFilePtr; int result; gridFilePtr = (gridfile *)ckalloc(sizeof(gridfile)); result = gridfs_find_filename(gridfsPtr, filename, gridFilePtr); if (result == MONGO_OK) { Nsf_PointerAdd(interp, buffer, "gridfile", gridFilePtr); Tcl_SetObjResult(interp, Tcl_NewStringObj(buffer, -1)); } else { ckfree((char *)gridFilePtr); Tcl_ResetResult(interp); } return TCL_OK; } /* cmd gridfile::read NsfMongoGridFileRead { {-argName "gridfile" -required 1 -type gridfile} {-argName "size" -required 1 -type int} } */ static int NsfMongoGridFileRead(Tcl_Interp *interp, gridfile *gridFilePtr, int size) { int readSize; char *buffer; buffer = ckalloc(size); readSize = gridfile_read_buffer(gridFilePtr, buffer, size); Tcl_SetObjResult(interp, Tcl_NewStringObj(buffer, readSize)); ckfree(buffer); return TCL_OK; } /* cmd "gridfile::seek" NsfMongoGridFileSeek { {-argName "file" -required 1 -type gridfile} {-argName "offset" -required 1 -type int32} } */ static int NsfMongoGridFileSeek(Tcl_Interp *interp, gridfile *gridFilePtr, int offset) { int pos; pos = gridfile_seek(gridFilePtr, offset); Tcl_SetObjResult(interp, Tcl_NewIntObj(pos)); return TCL_OK; } /*********************************************************************** * Finally, provide the necessary Tcl package interface. ***********************************************************************/ void Nsfmongo_Exit(ClientData clientData) { Tcl_Interp *interp = (Tcl_Interp *)clientData; NsfLog(interp,NSF_LOG_NOTICE, "Nsfmongo Exit"); } extern int Nsfmongo_Init(Tcl_Interp * interp) { int i; #ifdef USE_TCL_STUBS if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) { return TCL_ERROR; } # ifdef USE_NSF_STUBS if (Nsf_InitStubs(interp, "1.1", 0) == NULL) { return TCL_ERROR; } # endif #else if (Tcl_PkgRequire(interp, "Tcl", TCL_VERSION, 0) == NULL) { return TCL_ERROR; } #endif Tcl_PkgProvide(interp, "nsf::mongo", PACKAGE_VERSION); #ifdef PACKAGE_REQUIRE_FROM_SLAVE_INTERP_WORKS_NOW if (Tcl_PkgRequire(interp, "nsf", XOTCLVERSION, 0) == NULL) { return TCL_ERROR; } #endif Tcl_CreateExitHandler(Nsfmongo_Exit, interp); /* * register the pointer converter */ Nsf_PointerTypeRegister(interp, "gridfs", &gridfsCount); Nsf_PointerTypeRegister(interp, "gridfile", &gridfileCount); Nsf_PointerTypeRegister(interp, "mongo", &mongoCount); Nsf_PointerTypeRegister(interp, "mongo_cursor", &cursorCount); /* create all method commands (will use the namespaces above) */ for (i=0; i < nr_elements(method_definitions)-1; i++) { Tcl_CreateObjCommand(interp, method_definitions[i].methodName, method_definitions[i].proc, 0, 0); } Tcl_SetIntObj(Tcl_GetObjResult(interp), 1); return TCL_OK; } extern int Nsfmongo_SafeInit(interp) Tcl_Interp *interp; { return Nsfmongo_Init(interp); }