1 module mongo;
2 
3 import mongoaux.definitions;
4 import std.traits;
5 
6 // TODO: Check date handling.
7 // TODO: Change exceptions. It should be possible to retry on failure.
8 // TODO: Add rest of unittests.
9 // TODO: Reply handling.
10 // TODO: Handle MongoDocCreated and MongoDocUpdated correctly.
11 // TODO: Remove the clients that used del from the pooler toDestroy to avoid
12 // consuming a lot of memory. Be careful not to remove it twice.
13 
14 
15 unittest {
16   // TODO: Check BSONs on this test are deleted.
17 
18   // Use a pool to insert values, find them and delete them.
19   auto pool = MongoPool("mongodb://localhost");
20   scope(exit) pool.unlock();
21   foreach(i; 0..4) {
22     auto client = pool.lock();
23     scope(exit) client.unlock(); 
24     // Not using the * would give compilation errors because indexes for
25     // pointers are for memory locations.
26     auto collection = (*(*client) ["newBase"])["newCollection"];
27 
28     if(collection.count) {
29       collection.drop();
30     }
31 
32     enum ExampleStringEnum : string {
33       A = "A",
34       B = "B"
35     }
36 
37     enum ExampleIntEnum {
38       A,
39       B
40     }
41 
42     struct ExampleStruct {
43       int intVal = 1;
44       long longVal = 2L;
45       double doubleVal = 3.0;
46       int [] arrayVal = [1,2,3];
47       int [][] multidimArray = [[1,2],[3,4]];
48       string stringVal = "Text";
49       int [string] aa;
50       ExampleStringEnum strEnum = ExampleStringEnum.A;
51       ExampleIntEnum intEnum = ExampleIntEnum.A;
52     }
53     // Insert empty BSON (it has only default values)
54     // It'll have an _id in Mongo.
55     auto toInsert = ExampleStruct();
56     collection.insert(toInsert);
57     assert(collection.count == 1);
58     // Check that it has default value when converted back to ExampleStruct
59     assert(collection.findOne.as!ExampleStruct == toInsert);
60     collection.deleteOne();
61     assert(collection.count == 0);
62 
63     // Add a non-default value.
64     toInsert.longVal = 5;
65     collection.insert(toInsert);
66     assert(collection.count == 1);
67     auto retValue = collection.findOne;
68     assert(retValue.key!long(`longVal`) == 5L);
69     // Check conversion again.
70     assert(retValue.as!ExampleStruct == toInsert);
71     // Be careful, this doesn't remove the value because deleteOne creates a
72     // query with all the values in ExampleStruct. Insert didn't insert default
73     // ones.
74     collection.deleteOne(toInsert);
75     assert(collection.count == 1);
76 
77     // Correct way: should have just the keys present in Mongo.
78     // In this specific case, deleteOne without arguments would work too.
79     struct CorrectQuery {
80       long longVal = 5;
81     }
82     collection.deleteOne(CorrectQuery());
83     assert(collection.count == 0);
84 
85     // Add array values.
86     toInsert.arrayVal = [5,6,7,8];
87     toInsert.multidimArray = [[7,8,9],[10,11,12]];
88     toInsert.aa = ["one" : 1];
89     collection.insert(toInsert);
90 
91     // Reuse retValue.
92     retValue.unlock();
93     retValue = collection.findOne;
94     assert(retValue.key!(int[])(`arrayVal`) == [5,6,7,8]);
95     assert(retValue.key!(int[][])(`multidimArray`) == [[7,8,9],[10,11,12]]);
96     assert(retValue.key!(int[string])(`aa`)["one"] == 1);
97     collection.deleteOne;
98 
99     // Empty arrays.
100     toInsert.arrayVal = [];
101     collection.insert(toInsert);
102 
103     // Reuse retValue.
104     retValue.unlock();
105     retValue = collection.findOne;
106     scope(exit) retValue.unlock();
107     assert(retValue.key!(int[])(`arrayVal`) == []);
108     assert(retValue.as!ExampleStruct == toInsert);
109 
110     collection.deleteOne;
111 
112     // Update.
113     
114     toInsert.intVal = 4;
115     collection.insert(toInsert);
116     struct Query {
117       struct I {
118         @("$eq") int equalsTo = 4;
119       }
120       I intVal;
121     }
122 
123     struct Update {
124       struct I {
125         int intVal = 2;
126       }
127       @("$set") I update;
128     }
129     auto upd = Update(); // Takes a ref so it can process UDAs.
130     collection.update(Query(), upd);
131     assert(collection.findOne!ExampleStruct.intVal == 2);
132   }
133 }
134 
135 unittest {
136   // Manually built BSONs.
137   auto pool = MongoPool("mongodb://localhost");
138   scope(exit) pool.unlock();
139   auto client = pool.lock();
140   scope(exit) client.unlock(); 
141   auto collection = (*(*client) ["newBase"])["newCollection"];
142 
143   if(collection.count){
144     collection.drop();
145   }
146 
147   auto toInsert = empty(); scope(exit) toInsert.unlock();
148   toInsert.append(`intVal`, 4);
149   toInsert.append(`longVal`, 9L);
150   toInsert.append(`boolVal`, true);
151   toInsert.append(`stringVal`, "Hello");
152   toInsert.append(`doubleVal`, 5.0);
153   auto subDocument = empty(); scope(exit) subDocument.unlock();
154   subDocument.append(`subIntVal`, 7);
155   toInsert.append(`subDoc`, subDocument);
156   import std.datetime;
157   // TODO: Change it to use just Unix timestamps.
158   toInsert.append(`dateVal`, Clock.currTime (UTC ()));
159   toInsert.append(`arrayVal`, [1, 2, 3, 4]);
160   collection.insert(toInsert);
161 
162   auto foundVal = collection.findOne(toInsert);
163   scope(exit) foundVal.unlock();
164   foreach (key, value; foundVal.byKeyValue) {
165     switch(key) {
166       case `_id`:
167         //writeln(value.as!bson_oid_t);
168         break;
169       case `intVal`:
170         assert(value.as!int == 4);
171         break;
172       case `longVal`:
173         assert(value.as!long == 9);
174         break;
175       case `boolVal`:
176         assert(value.as!bool == true);
177         break;
178       case `stringVal`:
179         assert(value.as!string == "Hello");
180         break;
181       case `doubleVal`:
182         import std.math : approxEqual;
183         assert(value.as!double.approxEqual(5.0));
184         break;
185       case `subDoc`:
186         /+
187         auto doc = value.as!BSON; scope(exit) doc.unlock();
188         writeln(doc);
189         +/
190         break;
191       case `dateVal`:
192         //writeln(value.as!DateTime);
193         break;
194       case `arrayVal`:
195         assert(value.as!(int[]) == [1,2,3,4]);
196         break;
197       default:
198         assert(0, text(`TODO: `, key));
199     }
200   }
201 }
202 
203 unittest {
204   // Nested documents.
205   struct Nested {
206     struct Internal {
207       int a = 0;
208     }
209     Internal internal;
210   }
211   auto pool = MongoPool("mongodb://localhost");
212   scope(exit) pool.unlock();
213   auto client = pool.lock();
214   scope(exit) client.unlock(); 
215   auto collection = (*(*client) ["newBase"])["newCollection"];
216 
217   if(collection.count){
218     collection.drop();
219   }
220   auto toInsert = Nested(Nested.Internal(4));
221   collection.insert(toInsert);
222   auto found = collection.findOne;
223   scope(exit) found.unlock();
224   assert(found.as!Nested == Nested(Nested.Internal(4)));
225 }
226 
227 unittest {
228   // Use object ids
229   auto pool = MongoPool("mongodb://localhost");
230   scope(exit) pool.unlock();
231   auto client = pool.lock();
232   scope(exit) client.unlock(); 
233   auto collection = (*(*client) ["newBase"])["newCollection"];
234 
235   if(collection.count) {
236     collection.drop();
237   }
238 
239   struct WithId {
240     int a;
241     bson_oid_t _id;
242   }
243   auto str = "aaaaaabbbbbbccccccdddddd";
244   auto toInsert = WithId(5, str.toId);
245   collection.insert(toInsert);
246   auto retValue = collection.findOne;
247   scope(exit) retValue.unlock();
248   assert(retValue.as!WithId == toInsert);
249   collection.deleteOne;
250   
251   struct WithStringId {
252     string _id;
253   }
254   auto toInsert2 = WithStringId("123456789012345678901234");
255   collection.insert(toInsert2);
256   auto retValue2 = collection.findOne;
257   scope(exit) retValue2.unlock();
258   assert(retValue2.as!WithStringId == toInsert2);
259 }
260 
261 unittest {
262   // Without any BSON.
263   struct ExampleStruct {
264     int a = 0;
265     long b = 1;
266   }
267   auto pool = MongoPool("mongodb://localhost");
268   scope(exit) pool.unlock();
269   auto client = pool.lock();
270   scope(exit) client.unlock(); 
271   auto collection = (*(*client) ["newBase"])["newCollection"];
272 
273   if(collection.count) {
274     collection.drop();
275   }
276 
277   auto toInsert = ExampleStruct(1,2);
278   collection.insert(toInsert);
279   assert(collection.findOne!ExampleStruct == toInsert);
280 
281   foreach(doc; collection.find!ExampleStruct) {
282     assert(doc == toInsert);
283   }
284 }
285 
286 unittest {
287   // Bulk operations.
288   struct ExampleStruct {
289     int a = 0;
290     long b = 1;
291     string _id;
292   }
293 
294   auto pool = MongoPool("mongodb://localhost");
295   scope(exit) pool.unlock();
296   auto client = pool.lock();
297   scope(exit) client.unlock(); 
298   auto collection = (*(*client) ["newBase"])["newCollection"];
299 
300   if(collection.count) {
301     collection.drop();
302   }
303 
304   ExampleStruct [] dataRange;
305   foreach(int i; 0..10) {
306     string id;
307     foreach(j; 0..24) {
308       id ~= i.to!string;
309     }
310     dataRange ~= ExampleStruct(i, i, id);
311   }
312 
313   collection.insert(dataRange);
314   assert(collection.count == 10);
315   uint i = 0;
316   // Note: Might fail if Mongo sends them in other order, that wouldn't be
317   // wrong.
318   foreach(element; collection.find!ExampleStruct) {
319     assert(element.a == i);
320     assert(element.b == i);
321     i++;
322   }
323 
324   dataRange ~= ExampleStruct(10,10, "0123456789abcdef01234567");
325   struct IdQuery {
326     string _id;
327   }
328   struct SetValue {
329     struct I {
330       int a = 0;
331       long b = 0;
332     }
333     @("$set") I val; // Equivalent to adding "$set" : {"a" : 0, "b" : 0} on a BSON
334   }
335 
336   struct Upsert {
337     bool upsert = true;
338   }
339   import std.algorithm : map;
340   auto queryRange = dataRange.map!(a => IdQuery(a._id));
341   auto updateRange = dataRange.map!(a => SetValue(SetValue.I(a.a + 1)));
342   //writeln(collection.find!ExampleStruct);
343   collection.update(queryRange, updateRange, Upsert());
344   //writeln(collection.find!ExampleStruct);
345   assert(collection.count == 11);
346 }
347 
348 unittest {
349   // MongoDocUpdated and MongoDocCreated.
350   struct ExampleStruct {
351     @MongoDocCreated long created;
352     @MongoDocUpdated long updated;
353   }
354 
355   auto pool = MongoPool("mongodb://localhost");
356   scope(exit) pool.unlock();
357   auto client = pool.lock();
358   scope(exit) client.unlock(); 
359   auto collection = (*(*client) ["newBase"])["newCollection"];
360 
361   if(collection.count) {
362     collection.drop();
363   }
364   
365   auto toInsert = ExampleStruct(0, 0);
366   collection.insert(toInsert);
367   assert(toInsert.created != 0 && toInsert.updated == 0);
368 }
369 
370 unittest {
371   // Get created's id.
372   struct ExampleStruct {
373     int a = -1;
374   }
375   auto pool = MongoPool("mongodb://localhost");
376   scope(exit) pool.unlock();
377   auto client = pool.lock();
378   scope(exit) client.unlock(); 
379   auto collection = (*(*client) ["newBase"])["newCollection"];
380 
381   if(collection.count) {
382     collection.drop();
383   }
384 
385   ExampleStruct [] dataRange;
386   foreach(int i; 0..10) {
387     dataRange ~= ExampleStruct(i);
388   }
389   auto ids = collection.insert(dataRange);
390   
391   struct Query {
392     bson_oid_t _id;
393   }
394 
395   struct Result {
396     bson_oid_t _id;
397     int a;
398   }
399   import std.algorithm;
400   import std.array;
401   foreach(i, id; ids.map!(a => Query(a)).array) {
402       assert(collection
403       .findOne!(Result, Query)(id)
404       ._id == id._id
405       , `Didn't find inserted ids for a range`);
406   }
407   collection.drop();
408 
409   // Single insert with autogenerated id.
410   auto toInsert = ExampleStruct(10);
411   auto id = collection.insert(toInsert);
412   assert(
413     collection.findOne!(Result)()._id == id
414     , `Didn't find inserted id`
415   );
416 
417   collection.drop();
418   // Returned should be the same as the manually inserted one.
419   auto toInsertManual = Result();
420   bson_oid_init(&toInsertManual._id, null);
421   assert(toInsertManual._id == collection.insert(toInsertManual));
422   collection.drop();
423 
424   // Also for ranges.
425   Result [] rangeWithIDs;
426   import std.range;
427   /// Generates 000000000000000000000000 111111111111111111111111...
428   auto ids2 = iota(10).map!(a => a.to!string.repeat(24).joiner.to!string.toId);
429   foreach(i; 0..10) {
430     rangeWithIDs ~= Result(ids[i], i.to!int);
431   }
432   assert (collection.insert(rangeWithIDs) == ids);
433 }
434 
435 unittest {
436   struct B {
437    string hello = "";
438    int boo;
439   }
440   struct A {
441     int blah;
442     B[] boos;
443     B[][string] assocBoos; 
444   }
445   struct WithoutBoo {
446     string hello = "";
447   }
448   struct AWithoutBoo {
449     WithoutBoo[] boos;
450     WithoutBoo[][string] assocBoos;
451   }
452   auto pool = MongoPool("mongodb://localhost");
453   scope(exit) pool.unlock();
454   auto client = pool.lock();
455   scope(exit) client.unlock(); 
456   auto collection = (*(*client) ["newBase"])["newCollection"];
457 
458   if(collection.count) {
459     collection.drop();
460   }
461   auto toInsert = A();
462   toInsert.assocBoos["a"] ~= B("b");
463   toInsert.boos ~= B("b2");
464   collection.insert(toInsert);
465   // If it finds extra fields in WithoutBoo (in this case boo), it would error out.
466   auto foundBSON = collection.findOne!AWithoutBoo;
467   assert(foundBSON.assocBoos["a"][0] == WithoutBoo("b"));
468   assert(foundBSON.boos[0] == WithoutBoo("b2"));
469   
470   // Insert a default substruct
471   toInsert.boos[0].hello = "";
472 }
473 
474 
475 import std.conv : to, text;
476 shared static this () {
477   mongoc_init();
478 }
479 // Warning: Can be called before class destructors.
480 shared static ~this () {
481   mongoc_cleanup();
482 }
483 
484 import std..string : toStringz;
485 import std.array : Appender;
486 /// Wrapper over mongoc_client_pool_t that allows getting a client with
487 /// lock().
488 /// After usage this should be freed with unlock(). Note that this.unlock isn't 
489 /// the reverse operation of this.lock.
490 /// Clients should use their unlock().
491 struct MongoPool {
492   mongoc_client_pool_t* pool  = null;
493   mongoc_uri_t*         uri   = null;
494   /// This is optional, allows cleaning when this.unlock() is called.
495   /// But it keeps growing.
496   debug Appender!(Client * []) toDestroy;
497   this(string connectionString) {
498     this.uri = mongoc_uri_new(connectionString.toStringz);
499 
500     // Warning: If this happens, the destructor will be called too.
501     if(this.uri is null)
502       throw new Exception("Invalid mongo uri");
503 
504     this.pool = mongoc_client_pool_new(this.uri);
505     if(!this.pool)
506       throw new Exception("Error creating pool");
507   }
508 
509   /// Deallocates resources.
510   auto unlock() {
511     debug {
512       foreach (ref client; toDestroy.data) {
513         if (!client.deleted) {
514           import std.stdio;
515           "Warning: Didn't manually delete a client".writeln;
516         }
517         client.unlock();
518       } 
519       toDestroy.clear();
520     }
521 
522     if (this.uri)
523       mongoc_uri_destroy(this.uri);
524     if (this.pool)
525       mongoc_client_pool_destroy(this.pool);
526     this.uri = null;
527     this.pool = null;
528   }
529 
530   Client * lock() {
531     auto mongocClient = mongoc_client_pool_pop(this.pool);
532     if(!mongocClient) {
533       throw new Exception(`Error creating client from pool`);
534     }
535     auto toReturn = new Client(mongocClient, this.pool);
536     debug {
537       toDestroy ~= toReturn;
538     }
539     return toReturn;
540   }
541 }
542 
543 struct Client {
544   mongoc_client_t * client;
545   Appender!(Database * []) toDestroy;
546   // Null if this wasn't created from a pooler.
547   mongoc_client_pool_t * parentPool = null; 
548   bool deleted = false; // Prevents deleting multiple times.
549   @disable this();
550   this(string uri) {
551     this.client = mongoc_client_new(uri.toStringz);
552     if(!client) {
553       throw new Exception(`Error creating client.`);
554     }
555   }
556   this(mongoc_client_t * client, mongoc_client_pool_t * pool) {
557     assert(client && pool);
558     this.parentPool = pool;
559     this.client     = client;
560   }
561   auto opIndex(string baseName) {
562     assert(client);
563     auto toReturn = new Database(
564       mongoc_client_get_database(client, baseName.toStringz)
565     );
566     toDestroy ~= toReturn;
567     return toReturn;
568   }
569   /// Deallocates resources.
570   /// If this has a parentPool then it doesn't destroy this.client
571   auto unlock() {
572     if(deleted) return;
573     assert(client);
574     foreach(ref database; toDestroy.data) {
575       database.unlock();
576     }
577     toDestroy.clear();
578     if(this.parentPool) {
579       mongoc_client_pool_push(this.parentPool, this.client);
580       parentPool = null;
581     } else {
582       mongoc_client_destroy(client);
583     }
584     client = null; 
585     deleted = true;
586   }
587 }
588 struct Database {
589   @disable this();
590   mongoc_database_t * database;
591   Appender!(Collection []) toDestroy;
592   this(mongoc_database_t * database) {
593     this.database = database;
594   }
595   auto opIndex(string collectionName) {
596     assert(database);
597     auto toReturn = Collection(
598       mongoc_database_get_collection(
599         database, collectionName.toStringz
600       )
601     );
602     toDestroy ~= toReturn;
603     return toReturn;
604   }
605   auto unlock() {
606     assert(database);
607     foreach(ref collection; toDestroy.data) {
608       collection.unlock();
609     }
610     toDestroy.clear;
611     mongoc_database_destroy(database);
612     database = null;
613   }
614 }
615 
616 // Used for automatic setting of fields when doing operations.
617 // Note: As of now it updates the MongoDocUpdated on findAndModify and
618 // update and MongoDocCreated on insert.
619 // Maybe one should use https://docs.mongodb.com/manual/reference/operator/update/setOnInsert/ and $set with $currentDate
620 enum MongoDocCreated;
621 enum MongoDocUpdated;
622 
623 struct Collection {
624   @disable this();
625   mongoc_collection_t * collection;
626   this(mongoc_collection_t * collection) {
627     this.collection = collection;
628   }
629   auto unlock() {
630     assert (collection);
631     mongoc_collection_destroy(collection);
632     collection = null;
633   }
634 
635   auto drop() {
636     assert(collection);
637     bson_error_t error;
638     if(!mongoc_collection_drop(collection, &error)) {
639       throw new Exception(text(`Error in drop: `, error));
640     }
641   }
642 
643   long count(T1 = BSON)(
644     T1 query = empty!T1()
645     , mongoc_query_flags_t flags = mongoc_query_flags_t.NONE
646     , long skip = 0
647     , long limit = 0
648     , const mongoc_read_prefs_t * readPrefs = null
649   ) {
650     assert(collection);
651     bson_error_t error;
652     long retValue = mongoc_collection_count(
653       collection
654       , flags
655       , ScopedBSON(query, false, false).data
656       , skip
657       , limit
658       , readPrefs
659       , &error
660     );
661     if (retValue == -1) {
662       throw new Exception(text(`Error in Collection.count: `, error.message));
663     } else {
664       return retValue;
665     }
666   }
667 
668   /// Convenience method for calling mongoc's insert with the upsert flag.
669   auto findAndModify(T1 = BSON, T2 = BSON, T3 = BSON, T4 = BSON)(
670     ref T1 query
671     , T2 update
672     , T3 sort = empty!T3()
673     , T4 fields = empty!T4()
674     , bool remove = false
675     , bool upsert = false
676     , bool _new = true
677     , BSON reply = empty()
678   ) {
679     // Note: this assumes every document is updated, not created.
680     processMongoDocUDAs!MongoDocUpdated(update);
681     bson_error_t error;
682     if(!mongoc_collection_find_and_modify(
683       this.collection
684       // Don't ignore defaults for all the parameters.
685       , ScopedBSON(query, false, false).data
686       , ScopedBSON(sort, false).data
687       , ScopedBSON(update, false).data
688       , ScopedBSON(fields, false).data
689       , remove
690       , upsert
691       , _new
692       , reply.data
693       , &error
694     )) {
695       throw new Exception(text(`Error on findAndModify: `, error));
696     }
697   }
698 
699   /// Takes either one query and document or
700   /// a range of queries and another of documents..
701   auto update(T1 = BSON, T2 = BSON, T3 = BSON)(
702     T1 queries
703     , ref T2 documents // ref so that MongoDocUpdated can be set.
704     , T3 options = empty!T3()
705   ){
706     static if(!isInputRange!T1) {
707       // Single document update.
708       alias query = queries;
709       alias document = documents;
710 
711       bson_error_t error;
712       processMongoDocUDAs!MongoDocUpdated(document);
713       if(!mongoc_collection_update_one(
714           this.collection
715           , ScopedBSON(query, false, false).data
716           , ScopedBSON(document, false, false).data
717           , ScopedBSON(options, false).data
718           , ScopedBSON(empty(), false).data //Reply
719           , &error
720       )) {
721         throw new Exception(text(`Error inserting: `, error.message));
722       }
723     } else {
724       // Queries and documents are ranges => Bulk update.
725       static assert(
726         isInputRange!T2
727         , `Both queries and documents should be ranges or single data`
728       );
729       
730       assert(this.collection);
731       mongoc_bulk_operation_t* bulk;
732       bulk = mongoc_collection_create_bulk_operation_with_opts(
733         this.collection
734         , null // opts
735       );
736       if(!bulk) throw new Exception(`Error creating bulk operation`);
737       scope(exit) mongoc_bulk_operation_destroy(bulk);
738       bson_error_t error;
739 
740       // Note: Name clash with empty.
741       import std.range : empty, front, popFront; 
742       foreach(ref document; documents) {
743         // Assumes just setting updated is enough.
744         processMongoDocUDAs!MongoDocUpdated(document);
745         assert(!queries.empty, `More documents than queries`);
746         if(!mongoc_bulk_operation_update_one_with_opts(
747           bulk
748           , ScopedBSON(queries.front, false, false).data
749           , ScopedBSON(document, false, false).data
750           , ScopedBSON(options, false).data
751           , &error
752         )) {
753           throw new Exception(text(
754             `Error adding document to bulk update `
755             , error.message)
756           );
757         }
758         queries.popFront();
759       }
760       assert(queries.empty, `More queries than documents`);
761       if(!mongoc_bulk_operation_execute(
762         bulk
763         , null /*reply that must be freed*/
764         , &error
765       )) {
766         throw new Exception(text(`Error executing bulk operation`, error));
767       }
768     }
769   }
770 
771   /// If toInsert is a range, then bulk inserts are used.
772   auto insert(T1, T2 = BSON)(
773     ref T1 toInsert
774     , T2 options = empty!T2()
775     , BSON reply = empty()
776   ) {
777     assert(this.collection);
778     static if(isForwardRange!T1) {
779       // Ranges are inserted in bulk.
780       import std.algorithm : count;
781       auto amount = count(toInsert);
782       assert(amount > 0);
783       bson_t* [] toSend = [];
784       import std.range.primitives : hasLength;
785       static if (hasLength!T1) {
786         toSend.reserve(toInsert.length);
787       }
788       Appender!(bson_oid_t []) toReturn;
789       // Possible optimization: Create array on the stack
790       // instead of allocating BSONs.
791       foreach(ref element; toInsert) {
792         processMongoDocUDAs!MongoDocCreated(element);
793         auto toAppend = element.toBSON;
794         if (!toAppend.data.hasID) {
795           bson_oid_t id;
796           bson_oid_init(&id, null);
797           toAppend.append(`_id`, id);
798           toReturn ~= id;
799         } else {
800           toReturn ~= findID(toAppend.data);
801         }
802         toSend ~= toAppend.data;
803       }
804       this.insertMany(toSend, options, reply);
805       foreach(toDestroy; toSend) {
806         bson_destroy(toDestroy);
807       }
808       return toReturn.data;
809     } else {
810       // Single document insertion.
811       bson_error_t error;
812       processMongoDocUDAs!MongoDocCreated(toInsert);
813       auto toInsertAsBSON = ScopedBSON(toInsert, true, false);
814       bson_oid_t toReturn;
815       if (!toInsertAsBSON.data.hasID) {
816         bson_oid_init(&toReturn, null);
817         toInsertAsBSON.append(`_id`, toReturn);
818       } else {
819         toReturn = findID(toInsertAsBSON.data);
820       }
821 
822       if(!mongoc_collection_insert_one(
823         this.collection
824         , toInsertAsBSON.data
825         , ScopedBSON(options, false).data
826         , reply.data
827         , &error
828       )) {
829         throw new Exception(text(`Error inserting: `, error));
830       }
831       return toReturn;
832     }
833   }
834 
835 
836   /// Useful if you already have an array of bson_t, otherwise it's better
837   /// to use insert with a range as it builds that array.
838   auto insertMany(T = BSON)(
839     bson_t * [] toInsert
840     , T options = empty!T()
841     , BSON reply = empty()
842   ) {
843     bson_error_t error;
844     assert(this.collection);
845     if(!mongoc_collection_insert_many(
846       this.collection
847       , toInsert.ptr
848       , toInsert.length
849       , ScopedBSON(options, false).data
850       , reply.data
851       , &error
852     )) {
853       throw new Exception(text(`Error bulk-inserting: `, error));
854     }
855   }
856 
857   /// Convenience function for getting just one element from a find.
858   /// Must be deleted with unlock().
859   ReturnType findOne(ReturnType = BSON, T1 = BSON, T2 = BSON)(
860     T1 filter = empty!T1()
861     , T2 options = empty!T2()
862     , const mongoc_read_prefs_t * readPrefs = null
863   ) {
864     auto cursor = this.find(filter, options, readPrefs);
865     scope(exit) cursor.unlock();
866     if(cursor.empty) {
867       throw new Exception(`Didn't find on findOne: ` ~ filter.to!string);
868     }
869     auto data = bson_copy(cursor.front.data);
870     if(!data) {
871       throw new Exception(`Couldn't allocate BSON`);
872     }
873     static if(is(ReturnType == BSON)) {
874       return BSON(data);
875     } else {
876       return(ScopedBSON(BSON(data)).as!ReturnType);
877     }
878   }
879 
880   auto find(ReturnType = BSON, T1 = BSON, T2 = BSON)(
881     T1 filter = empty!T1()
882     , T2 options = empty!T2()
883     , const mongoc_read_prefs_t * readPrefs = null
884   ) {
885     assert(this.collection);
886     return Cursor!ReturnType(mongoc_collection_find_with_opts(
887       this.collection
888       , ScopedBSON(filter, false, false).data
889       , ScopedBSON(options, false).data
890       , readPrefs
891     ));
892   }
893 
894   auto deleteOne(T1 = BSON,T2 = BSON)(
895     T1 selector = empty!T1()
896     , T2 options = empty!T2()
897     , BSON reply = empty()
898   ) {
899     assert(collection);
900     bson_error_t error;
901     if(!mongoc_collection_delete_one (
902       this.collection
903       , ScopedBSON(selector, false, false).data
904       , ScopedBSON(options).data
905       , reply.data
906       , &error
907     )) {
908       throw new Exception(text(`Couldn't delete `, error));
909     }
910   }
911 
912   /// Sets the fields with UDA of struct T with the current unix time.
913   /// Does nothing if T is a BSON.
914   void processMongoDocUDAs(alias UDA, T)(ref T val) {
915     static if(__traits(isPOD, T) && isAggregateType!T) {
916       alias created = getSymbolsByUDA!(T, UDA);
917       static assert(
918         created.length < 2
919         , `Are you sure you want several ` ~ UDA.stringof 
920           ~ ` symbols in ` ~ S.stringof ~ `?`
921       );
922       import std.datetime;
923       static if(created.length) {
924         static assert(is(typeof(created [0]) == long));
925         // Eg. val.insertedTime = Clock.currTime(UTC()).toUnixTime;
926         mixin(
927           `val.` ~ __traits(identifier, created [0]) 
928           ~ ` = Clock.currTime(UTC()).toUnixTime;`
929         );
930       }
931     } else {
932       static assert(
933         is(T == BSON)
934         , `Cannot process mongo UDAs for type ` ~ T.stringof
935       );
936     }
937   }
938 }
939 
940 enum MongoKeep;
941 
942 /// Checks whether the instance of 'Type' has the default value on 'field'
943 /// if it does and isn't marked with the MongoKeep UDA does nothing,
944 /// else appends that field to the BSON.
945 /// In case the field is a struct or array, it checks recursively.
946 bool checkIfDefault(string field, Type)(
947   Type instance
948   , ref BSON toAppendTo
949   , bool ignoreDefaults
950 ) {
951   auto instanceField = __traits(getMember, instance, field);
952 
953   mixin(`alias SubField = Type.` ~ field ~ `;`);
954   alias SubType = typeof(SubField);
955   // Save only the fields with non default values or the ones that
956   // have the @MongoKeep UDA.
957   if(
958       (!ignoreDefaults)
959       || instanceField != __traits(getMember, Type.init, field)
960       || hasUDA!(mixin(`Type.` ~ field), MongoKeep)
961     ) {
962     // Fields with string UDAs should append documents with the document key
963     // as a string, for example @("$set") int a = 3;
964     // would add a $set : { "a" : 3 }
965     toAppendTo.append(strUDA!(Type, field), instanceField, ignoreDefaults);
966     return true;
967   }
968   return false;
969 }
970 
971 /// Fills a BSON with the members from a struct.
972 /// if ignoreDefaults is true, then values with the default value aren't added.
973 /// this behavior is useful to toggle it on insertions but off for option
974 /// or query parameters.
975 bool fillBSON(Type)(Type instance, ref BSON toFill, bool ignoreDefaults = true) {
976   bool toReturn = false;
977   static foreach(field; FieldNameTuple!Type) {
978     toReturn |= checkIfDefault!field(instance, toFill, ignoreDefaults);
979   }
980   return toReturn;
981 }
982 
983 // Search for a string UDA and return it's value.
984 string strUDA (alias Type, string fieldName)() {
985   string toReturn = fieldName;
986   mixin(`alias FieldType = Type.` ~ fieldName ~ `;`);
987   static foreach(uda; __traits(getAttributes, mixin(`Type.` ~ fieldName))) {
988     static if(is(typeof(uda) == string)) {
989       toReturn = uda;
990     }
991   }
992   return toReturn;
993 }
994 
995 /// ignoreDefaults does nothing, is just for compatibility with the other toBSON.
996 auto ref toBSON(BSON ob, bool ignoreDefaults = true) {return ob;}
997 /// Converts a POD struct to a BSON object.
998 auto toBSON(Type)(Type instance, bool ignoreDefaults = true) {
999   static assert(!is(Type == BSON));
1000   static assert(
1001     __traits(isPOD, Type) && isAggregateType!Type
1002     , `bson(instance) is only implemented for POD structs`
1003   );
1004 
1005   auto toReturn = empty!(BSON, true)();
1006   instance.fillBSON(toReturn, ignoreDefaults);
1007   return toReturn;
1008 }
1009 
1010 /// A BSON that is destroyed if created from a struct on its destructor and
1011 /// just a wrapper over an existing BSON otherwise.
1012 struct ScopedBSON {
1013   BSON bson;
1014   bool deleteOnDestructor = false;
1015   @disable this();
1016   // unused is for consistency with the other constructor.
1017   this(ref BSON other, bool unused = true, bool allowNull = true){
1018     if (!other.data && !allowNull){
1019       this.bson = empty!(BSON, true);
1020       deleteOnDestructor = true;
1021     } else {
1022       this.bson = other;
1023     }
1024   }
1025   this(S)(S other, bool ignoreDefaults = true, bool unused = true){
1026     this.bson = other.toBSON(ignoreDefaults);
1027     deleteOnDestructor = true;
1028   }
1029   ~this(){
1030     if(deleteOnDestructor){
1031       bson.unlock();
1032     }
1033   }
1034   alias bson this;
1035 
1036 }
1037 
1038 /// Used for iterating mongoc_cursor_ts as input ranges.
1039 struct Cursor(ElementType = BSON){
1040   mongoc_cursor_t * cursor;
1041   @disable this();
1042   this(mongoc_cursor_t * cursor) {
1043     this._front = .empty();
1044     this.cursor = cursor;
1045     popFront ();
1046   }
1047   // Note: _front.unlock () shouldn't be called, mongoc sets it automatically.
1048   BSON _front;
1049   bool empty = false;
1050   auto popFront () {
1051     if(!mongoc_cursor_next(cursor, &_front.data)) {
1052       bson_error_t error;
1053       if (mongoc_cursor_error(this.cursor, &error)) {
1054         throw new Exception(`Cursor error`);
1055       } else {
1056         // Empty cursor.
1057         this.unlock();
1058       }
1059     }
1060   }
1061 
1062   ElementType front () {
1063     static if(is(ElementType == BSON)) {
1064       return _front;
1065     } else {
1066       return _front.as!ElementType;
1067     }
1068   }
1069 
1070   /// Called automatically on exhaustion, useful if not all the elements are
1071   /// desired.
1072   void unlock() {
1073     if(this.cursor) {
1074       mongoc_cursor_destroy(this.cursor);
1075       this.cursor = null;
1076       empty = true;
1077     }
1078   }
1079 }
1080 import std.range : isInputRange, isForwardRange;
1081 static assert(isInputRange!(Cursor!BSON));
1082 
1083 /// Creates a BSON from a JSON string.
1084 /// The BSON needs to be destroyed manually with unlock()
1085 BSON fromJSON(string json) {
1086   bson_error_t error;
1087   bson_t * data;
1088   data = bson_new_from_json(
1089     cast (const ubyte*) json
1090     , json.length.to!ssize_t
1091     , &error
1092   );
1093   if (!data) {
1094     throw new Exception(text(`Error converting JSON to BSON: `, error));
1095   }
1096   auto toReturn = BSON(data);
1097   return toReturn;
1098 }
1099 
1100 auto empty(Type = BSON, bool initialize = false)() {
1101   static if(is(Type == BSON)) {
1102     static if(!initialize) {
1103       return BSON(null);
1104     } else {
1105       auto toReturn = BSON(null);
1106       toReturn.initialize();
1107       return toReturn;
1108     }
1109   } else {
1110     return Type.init;
1111   }
1112 }
1113 
1114 bool hasID(const bson_t* val) {
1115   assert(val);
1116   return bson_has_field(val, `_id`.toStringz);
1117 }
1118 
1119 /// Returns the _id field of a bson_t, it must have it and be of type OID.
1120 bson_oid_t findID(const bson_t* val) {
1121   bson_iter_t idPtr;
1122   bool success = bson_iter_init_find(&idPtr, val, `_id`);
1123   assert(success, `Didn't find _id field but should have one`);
1124   assert(bson_iter_type(&idPtr) == bson_type_t.BSON_TYPE_OID);
1125   return *bson_iter_oid(&idPtr);
1126 }
1127 
1128 struct BSON {
1129   @disable this();
1130   // Could use a statically allocated bson_t but it gave problems with automatic
1131   // destruction on scope exits.
1132   bson_t * data = null; 
1133   this(bson_t * data) {
1134     this.data = data;
1135   }
1136   /// empty is used for creating Bsons without fields. Structs cannot have a
1137   /// constructor without parameters, so it's a workaround.
1138   this(Args...)(Args args) {
1139     static assert (
1140       args.length % 2 == 0
1141       , `BSON constructor requires an even number of parameters`
1142     );
1143     this.initialize();
1144     assert (0, `TODO`);
1145   }
1146   /// Used to new this.data.
1147   auto initialize() {
1148     this.data = bson_new ();
1149     if (!data) {
1150       throw new Exception(`Error creating Bson`);
1151     }
1152   }
1153   auto unlock() {
1154     if(this.data) {
1155       bson_destroy(data);
1156       this.data = null;
1157     }
1158   }
1159   string toString() const {
1160     if(!data) return `null`;
1161     auto str = bson_as_canonical_extended_json(data, null);
1162     if (!str) {
1163       throw new Exception(`Error creating string from BSON`);
1164     }
1165     scope(exit) bson_free(str);
1166     return str.to!string;
1167   }
1168 
1169   /// name must be ASCII
1170   void append(T)(string name, T val, bool ignoreSubDocDefaults = false) {
1171     if(!data) this.initialize();
1172     import std.ascii : isASCII;
1173     import std.algorithm : all;
1174     debug assert(name.all!isASCII);
1175     bool success = true;
1176     import std.datetime : SysTime;
1177     import std.range : isInputRange;
1178     // Abstracts the common pattern of append operations.
1179     bool appendOp(alias fun, Args...)(Args args){
1180       assert(data);
1181       return fun(
1182         data
1183         , name.toStringz
1184         , name.length.to!int
1185         , args
1186       );
1187     }
1188     static if(is(OriginalType!T == string)) {
1189       if(name == `_id`) { // Automatic conversion to bson_oid_t
1190         auto toAppend = val.toId;
1191         success = appendOp!bson_append_oid(
1192             &toAppend
1193         );
1194       } else {
1195         success = appendOp!bson_append_utf8(
1196           val.toStringz
1197           , val.length.to!int
1198         );
1199       }
1200     } else static if(is(T == SysTime)) {
1201       // 4 parameters but must cast to Unix time.
1202       success = appendOp!bson_append_date_time(
1203           val.toUnixTime
1204       );
1205     } else static if(is(T == BSON)) {
1206       success = appendOp!bson_append_document(val.data);
1207     } else static if(isInputRange!T) {
1208       // Append as array:
1209       BSON arr = empty!(BSON, true)(); 
1210       // TODO: Check if needed: //scope(exit) arr.unlock();
1211 
1212       success = appendOp!bson_append_array_begin(arr.data);
1213       foreach(i, element; val) {
1214         arr.append(i.to!string, element, ignoreSubDocDefaults);
1215       }
1216       success &= bson_append_array_end(
1217         data
1218         , arr.data
1219       );
1220     } else static if(isAssociativeArray!T && is(T Key: Key[Value], Value)){
1221       BSON arr = empty!(BSON, true)();
1222       success = appendOp!bson_append_array_begin(arr.data);
1223       foreach(key, value; val) {
1224         arr.append(key, value, ignoreSubDocDefaults);
1225       }
1226       success &= bson_append_array_end(
1227         data
1228         , arr.data
1229       );
1230 
1231     } else static if(is(T == bson_oid_t)){
1232       success = appendOp!bson_append_oid(&val);
1233     } else static if(isAggregateType!T && !is(T == bson_oid_t)) {
1234       BSON toAppend = val.toBSON(ignoreSubDocDefaults);
1235       success = appendOp!bson_append_document(toAppend.data);
1236       // TODO: Check if toAppend.unlock is needed.
1237     } else {
1238       // 4 parameter appends.
1239       static if(is(OriginalType!T == int)) {
1240         alias fun = bson_append_int32;
1241       } else static if(is(OriginalType!T == long)) {
1242         alias fun = bson_append_int64;
1243       } else static if(is(OriginalType!T == bool)) {
1244         alias fun = bson_append_bool;
1245       } else static if(is(OriginalType!T == double)) {
1246         alias fun = bson_append_double;
1247       } else {
1248         static assert(0, `Unrecognised type for appending to BSON `~ T.stringof);
1249       }
1250       success = appendOp!fun(val);
1251     }
1252     if (!success) {
1253       throw new Exception(`Error appending`);
1254     }
1255   }
1256 
1257   auto byKeyValue() {
1258     return BSONIter(data);
1259   }
1260   
1261   // Note, this does seem to be O(n).
1262   auto key(Type)(string key) {
1263     assert(data);
1264     bson_iter_t iterator;
1265     if(!bson_iter_init_find(&iterator, data, key.toStringz)) {
1266       throw new Exception(`Problem looking for key ` ~ key ~ ` in BSON`);
1267     }
1268     return bson_iter_value(&iterator).as!Type;
1269   
1270   }
1271   T as(T)() {
1272     static assert(
1273       __traits(isPOD, T) && isAggregateType!T
1274       , `BSON.as is made for POD structs. Check the code before using it with `
1275         ~ T.stringof
1276     );
1277     alias typeFields = FieldNameTuple!T;
1278     T toReturn;
1279     foreach(key, value; this.byKeyValue){
1280       outerSwitch: switch(key) {
1281         static foreach(field; typeFields) {
1282           case field:
1283             alias FieldType = typeof(mixin(`T.` ~ field));
1284             FieldType toAssign;
1285             static if (field == `_id` && is(FieldType == string)) {
1286               toAssign = value.as!bson_oid_t.fromId;
1287             } else {
1288               toAssign = value.as!FieldType;
1289             }
1290             enum fieldToAssign = `toReturn.` ~ field;
1291             mixin(fieldToAssign ~ ` = toAssign;`);
1292             break outerSwitch;
1293         }
1294         default:
1295           // _id is the only field that is allowed to be on the BSON
1296           // and not on the struct, if bo has some other field that the
1297           // struct doesn't, an exception is thrown.
1298           if(key != `_id`){
1299             throw new Exception (`Found member of BSON that is not in `
1300               ~ T.stringof ~ ` : ` ~ key);
1301           }
1302       }
1303     }
1304     return toReturn;
1305   }
1306   //alias data this;
1307 }
1308 
1309 /// Used just for 'as' function.
1310 struct DateTime {
1311   long value;
1312   alias value this;
1313 }
1314 
1315 /// Converts a bson_value_t to another type.
1316 /// If BSON is used as a type, it assumes it's a document.
1317 /// Make sure to unlock() that BSON.
1318 auto as(type)(bson_value_t * val) {
1319   assert(val);
1320   auto vval = val.value;
1321   auto vtype = val.value_type;
1322   static if(is(OriginalType!type == int)){
1323     assert(vtype == bson_type_t.BSON_TYPE_INT32);
1324     return to!type(vval.v_int32);
1325   } else static if(is(OriginalType!type == long)) {
1326     assert(vtype == bson_type_t.BSON_TYPE_INT64);
1327     return to!type(vval.v_int64);
1328   } else static if(is(OriginalType!type == bool)) {
1329     assert(vtype == bson_type_t.BSON_TYPE_BOOL);
1330     return to!type(vval.v_bool);
1331   } else static if(is(OriginalType!type == string)) {
1332     assert(vtype == bson_type_t.BSON_TYPE_UTF8);
1333     auto toReturn = vval.v_utf8.str[0..vval.v_utf8.len];
1334     return to!type(toReturn.to!string);
1335   } else static if(is(OriginalType!type == double)) {
1336     assert(vtype == bson_type_t.BSON_TYPE_DOUBLE);
1337     return to!type(vval.v_double);
1338   } else static if(is(type == bson_oid_t)) {
1339     assert(vtype == bson_type_t.BSON_TYPE_OID);
1340     return vval.v_oid;
1341   } else static if(is(type == BSON)) {
1342     assert(vtype == bson_type_t.BSON_TYPE_DOCUMENT);
1343     auto toReturn = bson_new_from_data(vval.v_doc.data, vval.v_doc.data_len);
1344     return BSON(toReturn);
1345   } else static if(is(type == DateTime)) {
1346     assert(vtype == bson_type_t.BSON_TYPE_DATE_TIME);
1347     return DateTime(vval.v_datetime);
1348   } else static if(isArray!type) {
1349     assert(vtype == bson_type_t.BSON_TYPE_ARRAY);
1350     auto asDoc = bson_new_from_data(vval.v_doc.data, vval.v_doc.data_len);
1351     auto asBSON = BSON(asDoc); scope(exit) asBSON.unlock();
1352     import std.array : Appender;
1353     import std.range : ElementType;
1354     Appender!type toReturn;
1355     foreach(element; BSONIter(asBSON.data)) {
1356       import std.algorithm : map;
1357       toReturn ~= element.value.as!(ElementType!type);
1358     }
1359     return toReturn.data;
1360   } else static if(isAssociativeArray!type && is(type Value: Value[Key], Key)) {
1361     assert(vtype == bson_type_t.BSON_TYPE_ARRAY);
1362     auto asDoc = bson_new_from_data(vval.v_doc.data, vval.v_doc.data_len);
1363     auto asBSON = BSON(asDoc); scope(exit) asBSON.unlock();
1364     type toReturn;
1365     foreach(key, value; BSONIter(asBSON.data)) {
1366       toReturn[key] = value.as!Value;
1367     }
1368     return toReturn;
1369   } else static if(isAggregateType!type) {
1370     assert(vtype == bson_type_t.BSON_TYPE_DOCUMENT);
1371     auto docBSON = val.as!BSON;
1372     scope(exit) docBSON.unlock();
1373     return docBSON.as!type;
1374   } else static assert (0, `TODO: as!` ~ type.stringof);
1375 }
1376 
1377 /// Empty if constructed with null.
1378 struct BSONIter {
1379   bson_iter_t iter;
1380   @disable this();
1381   bool empty = false;
1382   auto front() {
1383     assert(!empty);
1384     import std.typecons : Tuple;
1385     return Tuple!(string, `key`, bson_value_t *, `value`)(
1386       bson_iter_key(&iter).to!string
1387       , bson_iter_value(&iter)
1388     );
1389   }
1390   auto popFront () {
1391     assert(!empty);
1392     if(!bson_iter_next(&iter)) {
1393       this.empty = true;
1394     }
1395   }
1396   this(bson_t * toIterate) {
1397     if(!toIterate) {
1398       empty = true;
1399     } else {
1400       if(bson_iter_init (&iter, toIterate)) {
1401         popFront();
1402       } else {
1403         throw new Exception (`Failed to create BSON iterator`);
1404       }
1405     }
1406   }
1407 }
1408 
1409 /// Converts a string to an object id.
1410 bson_oid_t toId(string representation) {
1411   bson_oid_t toReturn;
1412   auto cstr = representation.toStringz;
1413   if(!bson_oid_is_valid(cstr, representation.length)) {
1414     throw new Exception(`Invalid string for _id conversion`);
1415   }
1416   bson_oid_init_from_string(&toReturn, cstr); 
1417   return toReturn;
1418 }
1419 
1420 string fromId(bson_oid_t id) {
1421   char[25] toInsertTo; //Includes \0
1422   bson_oid_to_string(&id, toInsertTo.ptr);
1423   return toInsertTo[0..24].to!string;
1424 }