// sm_db.cpp // hacked in right now from engine_spidermonkey.cpp #include namespace mongo { // ------------ some defs needed --------------- JSObject * doCreateCollection( JSContext * cx , JSObject * db , const string& shortName ); // ------------ utils ------------------ bool isSpecialName( const string& name ){ static set names; if ( names.size() == 0 ){ names.insert( "_mongo" ); names.insert( "_db" ); names.insert( "_name" ); names.insert( "_fullName" ); names.insert( "_shortName" ); names.insert( "tojson" ); names.insert( "toJson" ); names.insert( "toString" ); } if ( name.length() == 0 ) return false; if ( name[0] == '_' ) return true; return names.count( name ) > 0; } // ------ cursor ------ JSBool internal_cursor_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ uassert( "no args to internal_cursor_constructor" , argc == 0 ); JS_SetPrivate( cx , obj , 0 ); // just for safety return JS_TRUE; } void internal_cursor_finalize( JSContext * cx , JSObject * obj ){ DBClientCursor * cursor = (DBClientCursor*)JS_GetPrivate( cx , obj ); if ( cursor ){ delete cursor; JS_SetPrivate( cx , obj , 0 ); } } JSBool internal_cursor_hasNext(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ DBClientCursor * cursor = (DBClientCursor*)JS_GetPrivate( cx , obj ); uassert( "no cursor in hasNext!" , cursor ); *rval = cursor->more() ? JSVAL_TRUE : JSVAL_FALSE; return JS_TRUE; } JSBool internal_cursor_next(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ DBClientCursor * cursor = (DBClientCursor*)JS_GetPrivate( cx , obj ); uassert( "no cursor in next!" , cursor ); Convertor c(cx); BSONObj n = cursor->next(); *rval = c.toval( &n ); return JS_TRUE; } JSFunctionSpec internal_cursor_functions[] = { { "hasNext" , internal_cursor_hasNext , 0 , 0 , JSPROP_READONLY | JSPROP_PERMANENT } , { "next" , internal_cursor_next , 0 , 0 , JSPROP_READONLY | JSPROP_PERMANENT } , { 0 } }; JSClass internal_cursor_class = { "InternalCursor" , JSCLASS_HAS_PRIVATE , JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, internal_cursor_finalize, JSCLASS_NO_OPTIONAL_MEMBERS }; // ------ mongo stuff ------ JSBool mongo_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ uassert( "mongo_constructor not implemented yet" , 0 ); throw -1; } JSBool mongo_local_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ Convertor c( cx ); DBClientBase * client = createDirectClient(); JS_SetPrivate( cx , obj , (void*)client ); jsval host = c.toval( "EMBEDDED" ); assert( JS_SetProperty( cx , obj , "host" , &host ) ); return JS_TRUE; } JSBool mongo_external_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ Convertor c( cx ); uassert( "0 or 1 args to Mongo" , argc <= 1 ); DBClientConnection * conn = new DBClientConnection( true ); string host = "127.0.0.1"; if ( argc > 0 ) host = c.toString( argv[0] ); string errmsg; if ( ! conn->connect( host , errmsg ) ){ JS_ReportError( cx , ((string)"couldn't connect: " + errmsg).c_str() ); return JS_FALSE; } JS_SetPrivate( cx , obj , (void*)conn ); jsval host_val = c.toval( host.c_str() ); assert( JS_SetProperty( cx , obj , "host" , &host_val ) ); return JS_TRUE; } void mongo_finalize( JSContext * cx , JSObject * obj ){ DBClientBase * client = (DBClientBase*)JS_GetPrivate( cx , obj ); if ( client ){ delete client; JS_SetPrivate( cx , obj , 0 ); } } JSClass mongo_class = { "Mongo" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE , JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, mongo_finalize, JSCLASS_NO_OPTIONAL_MEMBERS }; JSBool mongo_find(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ uassert( "mongo_find neesd 5 args" , argc == 5 ); DBClientConnection * conn = (DBClientConnection*)JS_GetPrivate( cx , obj ); uassert( "no connection!" , conn ); Convertor c( cx ); string ns = c.toString( argv[0] ); BSONObj q = c.toObject( argv[1] ); BSONObj f = c.toObject( argv[2] ); int nToReturn = c.toNumber( argv[3] ); int nToSkip = c.toNumber( argv[4] ); bool slaveOk = c.getBoolean( obj , "slaveOk" ); try { auto_ptr cursor = conn->query( ns , q , nToReturn , nToSkip , f.nFields() ? &f : 0 , slaveOk ? Option_SlaveOk : 0 ); JSObject * mycursor = JS_NewObject( cx , &internal_cursor_class , 0 , 0 ); JS_SetPrivate( cx , mycursor , cursor.release() ); *rval = OBJECT_TO_JSVAL( mycursor ); return JS_TRUE; } catch ( ... ){ JS_ReportError( cx , "error doing query" ); return JS_FALSE; } } JSBool mongo_update(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ uassert( "mongo_find needs at elast 3 args" , argc >= 3 ); uassert( "2nd param to update has to be an object" , JSVAL_IS_OBJECT( argv[1] ) ); uassert( "3rd param to update has to be an object" , JSVAL_IS_OBJECT( argv[2] ) ); DBClientConnection * conn = (DBClientConnection*)JS_GetPrivate( cx , obj ); uassert( "no connection!" , conn ); Convertor c( cx ); string ns = c.toString( argv[0] ); bool upsert = argc > 3 && c.toBoolean( argv[3] ); try { conn->update( ns , c.toObject( argv[1] ) , c.toObject( argv[2] ) , upsert ); return JS_TRUE; } catch ( ... ){ JS_ReportError( cx , "error doing update" ); return JS_FALSE; } } JSBool mongo_insert(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ uassert( "mongo_insert needs 2 args" , argc == 2 ); uassert( "2nd param to insert has to be an object" , JSVAL_IS_OBJECT( argv[1] ) ); DBClientConnection * conn = (DBClientConnection*)JS_GetPrivate( cx , obj ); uassert( "no connection!" , conn ); Convertor c( cx ); string ns = c.toString( argv[0] ); BSONObj o = c.toObject( argv[1] ); // TODO: add _id try { conn->insert( ns , o ); return JS_TRUE; } catch ( ... ){ JS_ReportError( cx , "error doing insert" ); return JS_FALSE; } } JSBool mongo_remove(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ uassert( "mongo_remove needs 2 arguments" , argc == 2 ); uassert( "2nd param to insert has to be an object" , JSVAL_IS_OBJECT( argv[1] ) ); DBClientConnection * conn = (DBClientConnection*)JS_GetPrivate( cx , obj ); uassert( "no connection!" , conn ); Convertor c( cx ); string ns = c.toString( argv[0] ); BSONObj o = c.toObject( argv[1] ); try { conn->remove( ns , o ); return JS_TRUE; } catch ( ... ){ JS_ReportError( cx , "error doing remove" ); return JS_FALSE; } } JSFunctionSpec mongo_functions[] = { { "find" , mongo_find , 0 , 0 , JSPROP_READONLY | JSPROP_PERMANENT } , { "update" , mongo_update , 0 , 0 , JSPROP_READONLY | JSPROP_PERMANENT } , { "insert" , mongo_insert , 0 , 0 , JSPROP_READONLY | JSPROP_PERMANENT } , { "remove" , mongo_remove , 0 , 0 , JSPROP_READONLY | JSPROP_PERMANENT } , { 0 } }; // ------------- db_collection ------------- JSBool db_collection_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ uassert( "db_collection_constructor wrong args" , argc == 4 ); assert( JS_SetProperty( cx , obj , "_mongo" , &(argv[0]) ) ); assert( JS_SetProperty( cx , obj , "_db" , &(argv[1]) ) ); assert( JS_SetProperty( cx , obj , "_shortName" , &(argv[2]) ) ); assert( JS_SetProperty( cx , obj , "_fullName" , &(argv[3]) ) ); return JS_TRUE; } JSBool db_collection_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ){ if ( flags & JSRESOLVE_ASSIGNING ) return JS_TRUE; Convertor c( cx ); string collname = c.toString( id ); if ( isSpecialName( collname ) ) return JS_TRUE; if ( obj == c.getGlobalPrototype( "DBCollection" ) ) return JS_TRUE; JSObject * proto = JS_GetPrototype( cx , obj ); if ( c.hasProperty( obj , collname.c_str() ) || ( proto && c.hasProperty( proto , collname.c_str() ) ) ) return JS_TRUE; string name = c.toString( c.getProperty( obj , "_shortName" ) ); name += "."; name += collname; jsval db = c.getProperty( obj , "_db" ); if ( ! JSVAL_IS_OBJECT( db ) ) return JS_TRUE; JSObject * coll = doCreateCollection( cx , JSVAL_TO_OBJECT( db ) , name ); c.setProperty( obj , collname.c_str() , OBJECT_TO_JSVAL( coll ) ); *objp = obj; return JS_TRUE; } JSClass db_collection_class = { "DBCollection" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE , JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, (JSResolveOp)(&db_collection_resolve) , JS_ConvertStub, JS_FinalizeStub, JSCLASS_NO_OPTIONAL_MEMBERS }; JSObject * doCreateCollection( JSContext * cx , JSObject * db , const string& shortName ){ Convertor c(cx); assert( c.hasProperty( db , "_mongo" ) ); assert( c.hasProperty( db , "_name" ) ); JSObject * coll = JS_NewObject( cx , &db_collection_class , 0 , 0 ); c.setProperty( coll , "_mongo" , c.getProperty( db , "_mongo" ) ); c.setProperty( coll , "_db" , OBJECT_TO_JSVAL( db ) ); c.setProperty( coll , "_shortName" , c.toval( shortName.c_str() ) ); string name = c.toString( c.getProperty( db , "_name" ) ); name += "." + shortName; c.setProperty( coll , "_fullName" , c.toval( name.c_str() ) ); return coll; } // -------------- DB --------------- JSBool db_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ uassert( "wrong number of arguments to DB" , argc == 2 ); assert( JS_SetProperty( cx , obj , "_mongo" , &(argv[0]) ) ); assert( JS_SetProperty( cx , obj , "_name" , &(argv[1]) ) ); return JS_TRUE; } JSBool db_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ){ if ( flags & JSRESOLVE_ASSIGNING ) return JS_TRUE; Convertor c( cx ); if ( obj == c.getGlobalPrototype( "DB" ) ) return JS_TRUE; string collname = c.toString( id ); if ( isSpecialName( collname ) ) return JS_TRUE; JSObject * proto = JS_GetPrototype( cx , obj ); if ( proto && c.hasProperty( proto , collname.c_str() ) ) return JS_TRUE; JSObject * coll = doCreateCollection( cx , obj , collname ); c.setProperty( obj , collname.c_str() , OBJECT_TO_JSVAL( coll ) ); *objp = obj; return JS_TRUE; } JSClass db_class = { "DB" , JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE , JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, (JSResolveOp)(&db_resolve) , JS_ConvertStub, JS_FinalizeStub, JSCLASS_NO_OPTIONAL_MEMBERS }; // -------------- object id ------------- JSBool object_id_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ Convertor c( cx ); OID oid; if ( argc == 0 ){ oid.init(); } else { uassert( "object_id_constructor can't take more than 1 param" , argc == 1 ); oid.init( c.toString( argv[0] ) ); } jsval v = c.toval( oid.str().c_str() ); assert( JS_SetProperty( cx , obj , "str" , &v ) ); return JS_TRUE; } JSClass object_id_class = { "ObjectId" , JSCLASS_HAS_PRIVATE , JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, JSCLASS_NO_OPTIONAL_MEMBERS }; JSBool object_id_tostring(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ Convertor c(cx); return *rval = c.getProperty( obj , "str" ); } JSFunctionSpec object_id_functions[] = { { "toString" , object_id_tostring , 0 , 0 , JSPROP_READONLY | JSPROP_PERMANENT } , { 0 } }; JSClass timestamp_class = { "Timestamp" , JSCLASS_HAS_PRIVATE , JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, JSCLASS_NO_OPTIONAL_MEMBERS }; JSClass minkey_class = { "MinKey" , JSCLASS_HAS_PRIVATE , JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, JSCLASS_NO_OPTIONAL_MEMBERS }; JSClass maxkey_class = { "MaxKey" , JSCLASS_HAS_PRIVATE , JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub , JS_ConvertStub, JS_FinalizeStub, JSCLASS_NO_OPTIONAL_MEMBERS }; // dbquery JSBool dbquery_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ uassert( "DDQuery needs at least 4 args" , argc >= 4 ); Convertor c(cx); c.setProperty( obj , "_mongo" , argv[0] ); c.setProperty( obj , "_db" , argv[1] ); c.setProperty( obj , "_collection" , argv[2] ); c.setProperty( obj , "_ns" , argv[3] ); if ( argc > 4 && JSVAL_IS_OBJECT( argv[4] ) ) c.setProperty( obj , "_query" , argv[4] ); else c.setProperty( obj , "_query" , OBJECT_TO_JSVAL( JS_NewObject( cx , 0 , 0 , 0 ) ) ); if ( argc > 5 && JSVAL_IS_OBJECT( argv[5] ) ) c.setProperty( obj , "_fields" , argv[5] ); else c.setProperty( obj , "_fields" , JSVAL_NULL ); if ( argc > 6 && JSVAL_IS_NUMBER( argv[6] ) ) c.setProperty( obj , "_limit" , argv[6] ); else c.setProperty( obj , "_limit" , JSVAL_ZERO ); if ( argc > 7 && JSVAL_IS_NUMBER( argv[7] ) ) c.setProperty( obj , "_skip" , argv[7] ); else c.setProperty( obj , "_skip" , JSVAL_ZERO ); c.setProperty( obj , "_cursor" , JSVAL_NULL ); c.setProperty( obj , "_numReturned" , JSVAL_ZERO ); c.setProperty( obj , "_special" , JSVAL_FALSE ); return JS_TRUE; } JSBool dbquery_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ){ if ( flags & JSRESOLVE_ASSIGNING ) return JS_TRUE; if ( ! JSVAL_IS_NUMBER( id ) ) return JS_TRUE; jsval val; assert( JS_CallFunctionName( cx , obj , "arrayAccess" , 1 , &id , &val ) ); Convertor c(cx); c.setProperty( obj , c.toString( id ).c_str() , val ); *objp = obj; return JS_TRUE; } JSClass dbquery_class = { "DBQuery" , JSCLASS_NEW_RESOLVE , JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, (JSResolveOp)(&dbquery_resolve) , JS_ConvertStub, JS_FinalizeStub, JSCLASS_NO_OPTIONAL_MEMBERS }; // thread class JSThreadConfig { public: JSThreadConfig( JSContext *cx, JSObject *obj, uintN argc, jsval *argv ) : started_(), done_(), cx_( cx ), obj_( obj ) { massert( "need at least one argument", argc > 0 ); massert( "first argument must be a function", JS_TypeOfValue( cx, argv[ 0 ] ) == JSTYPE_FUNCTION ); fun_ = argv[ 0 ]; f_ = JS_ValueToFunction( cx, fun_ ); argc_ = argc - 1; argv_.reset( new jsval[ argc_ ] ); for( uintN i = 0; i < argc_; ++i ) argv_[ i ] = argv[ i + 1 ]; JS_AddRoot( cx, &obj_ ); JS_AddRoot( cx, &fun_ ); for( uintN i = 0; i < argc_; ++i ) JS_AddRoot( cx, &argv_[ i ] ); } ~JSThreadConfig() { if ( started_ ) thread_->join(); // don't want to deal with cleaning up while thread is running JS_RemoveRoot( cx_, &obj_ ); JS_RemoveRoot( cx_, &fun_ ); for( uintN i = 0; i < argc_; ++i ) JS_RemoveRoot( cx_, &argv_[ i ] ); if ( done_ ) JS_RemoveRoot( scope_->context(), &returnData_ ); } void start() { massert( "Thread already started", !started_ ); scope_.reset( dynamic_cast< SMScope * >( globalScriptEngine->createScope() ) ); scope_->externalSetup( true ); // TODO install shell utils? JSThread jt( *this ); thread_.reset( new boost::thread( jt ) ); started_ = true; } void join() { if ( !done_ ) thread_->join(); } jsval returnData() { if ( !done_ ) join(); return returnData_; } private: class JSThread { public: JSThread( JSThreadConfig &config ) : config_( config ) {} void operator()() { JS_SetContextThread( config_.scope_->context() ); try { massert( "function call failure", JS_TRUE == JS_CallFunction( config_.scope_->context(), config_.obj_, config_.f_, config_.argc_, config_.argv_.get(), &config_.returnData_ ) ); JS_AddRoot( config_.scope_->context(), &config_.returnData_ ); config_.done_ = true; } catch ( ... ) { } JS_ClearContextThread( config_.scope_->context() ); } private: JSThreadConfig &config_; }; bool started_; bool done_; JSContext *cx_; JSObject *obj_; JSFunction *f_; jsval fun_; uintN argc_; boost::scoped_array< jsval > argv_; auto_ptr< boost::thread > thread_; auto_ptr< SMScope > scope_; jsval returnData_; }; void thread_finalize( JSContext * cx , JSObject * obj ){ JSThreadConfig * config = (JSThreadConfig*)JS_GetPrivate( cx , obj ); if ( config ){ delete config; JS_SetPrivate( cx , obj , 0 ); } } JSClass thread_class = { "Thread" , JSCLASS_HAS_PRIVATE , JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, thread_finalize, JSCLASS_NO_OPTIONAL_MEMBERS }; JSBool thread_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ){ JS_SetPrivate( cx , obj , (void*) new JSThreadConfig( cx, obj, argc, argv ) ); return JS_TRUE; } JSBool thread_start(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ JSThreadConfig * config = (JSThreadConfig*)JS_GetPrivate( cx , obj ); config->start(); return JS_TRUE; } JSBool thread_join(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ JSThreadConfig * config = (JSThreadConfig*)JS_GetPrivate( cx , obj ); config->join(); return JS_TRUE; } JSBool thread_returnData(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){ JSThreadConfig * config = (JSThreadConfig*)JS_GetPrivate( cx , obj ); *rval = config->returnData(); return JS_TRUE; } JSFunctionSpec thread_functions[] = { { "start" , thread_start , 0 , 0 , JSPROP_READONLY | JSPROP_PERMANENT } , { "join" , thread_join , 0 , 0 , JSPROP_READONLY | JSPROP_PERMANENT } , { "returnData" , thread_returnData , 0 , 0 , JSPROP_READONLY | JSPROP_PERMANENT } , { 0 } }; // ---- other stuff ---- void initMongoJS( SMScope * scope , JSContext * cx , JSObject * global , bool local, bool debug ){ assert( JS_InitClass( cx , global , 0 , &mongo_class , local ? mongo_local_constructor : mongo_external_constructor , 0 , 0 , mongo_functions , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , &object_id_class , object_id_constructor , 0 , 0 , object_id_functions , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , &db_class , db_constructor , 2 , 0 , 0 , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , &db_collection_class , db_collection_constructor , 4 , 0 , 0 , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , &internal_cursor_class , internal_cursor_constructor , 0 , 0 , internal_cursor_functions , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , &dbquery_class , dbquery_constructor , 0 , 0 , 0 , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , &thread_class , thread_constructor , 0 , 0 , thread_functions , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , ×tamp_class , 0 , 0 , 0 , 0 , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , &minkey_class , 0 , 0 , 0 , 0 , 0 , 0 ) ); assert( JS_InitClass( cx , global , 0 , &maxkey_class , 0 , 0 , 0 , 0 , 0 , 0 ) ); if ( !debug ) { scope->exec( jsconcatcode ); } } bool appendSpecialDBObject( Convertor * c , BSONObjBuilder& b , const string& name , JSObject * o ){ if ( JS_InstanceOf( c->_context , o , &object_id_class , 0 ) ){ OID oid; oid.init( c->getString( o , "str" ) ); b.append( name.c_str() , oid ); return true; } if ( JS_InstanceOf( c->_context , o , &minkey_class , 0 ) ){ b.appendMinKey( name.c_str() ); return true; } if ( JS_InstanceOf( c->_context , o , &maxkey_class , 0 ) ){ b.appendMaxKey( name.c_str() ); return true; } if ( JS_InstanceOf( c->_context , o , ×tamp_class , 0 ) ){ cout << "A: " << c->getNumber( o , "time" ) << endl; cout << "B: " << c->getNumber( o , "inc" ) << endl; b.appendTimestamp( name.c_str() , (unsigned long long)c->getNumber( o , "time" ) , (unsigned int )c->getNumber( o , "inc" ) ); return true; } { jsdouble d = js_DateGetMsecSinceEpoch( c->_context , o ); if ( d ){ b.appendDate( name.c_str() , d ); return true; } } return false; } }