1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2015 Alexandre Terrasa <alexandre@moz-code.org>
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
19 # This is actually a wrapper around the [MongoDB C Driver](http://api.mongodb.org/c/1.1.4/index.html).
24 # # Opens the connexion with the Mongo server.
25 # var client = new MongoClient("mongodb://localhost:27017/")
27 # # Retrieve a collection.
28 # var col = client.database("test").collection("test")
30 # # Insert a document in the collection.
31 # var doc = new JsonObject
34 # doc["baz"] = new JsonArray
35 # assert col.insert(doc)
37 # # Retrieve a document from the collection.
38 # var query = new JsonObject
40 # var res = col.find(query)
41 # assert res["bar"] == "bar"
46 private import native_mongodb
52 # Everything inside MongoDB is manipulated as BSON Objects.
55 # * [Binary JSON spec](http://bsonspec.org/)
56 # * [Libbson](http://api.mongodb.org/libbson/1.1.4/)#
60 # Native instance pointer.
61 var native
: NativeBSON
63 # Is the native instance valid?
65 # This is set to false if the `native` is destroyed.
68 # Returns a new BSON object initialized from the content of `json`.
71 # intrude import mongodb
72 # var obj = new JsonObject
74 # obj["name"] = "Rick"
75 # obj["ELS"] = new JsonArray
76 # var bson = new BSON.from_json(obj)
77 # assert bson.to_s == """{ "age" : 10, "name" : "Rick", "ELS" : [ ] }"""
79 init from_json
(json
: JsonObject) do
80 init(new NativeBSON.from_json_string
(json
.to_json
.to_cstring
))
83 # Returns a new BSON object parsed from `json_string`.
85 # If `json_string` is not a valid JSON string, this initializer returns NULL.
88 # intrude import mongodb
89 # var str = """{ "age" : 10, "name" : "Rick", "ELS" : [ ] }"""
90 # var bson = new BSON.from_json_string(str)
91 # assert bson.to_s == str
93 init from_json_string
(json_string
: String) do
94 init(new NativeBSON.from_json_string
(json_string
.to_cstring
))
99 var ns
= native
.to_native_string
100 var res
= ns
.to_s_with_copy
101 ns
.free
# manual free of gc allocated NativeString
105 # Returns a new JsonObject from `self`.
108 # intrude import mongodb
109 # var str = """{ "age" : 10, "name" : "Rick", "ELS" : [ ] }"""
110 # var bson = new BSON.from_json_string(str)
111 # var json = bson.to_json
112 # assert json["age"] == 10
113 # assert json["name"] == "Rick"
114 # assert json["ELS"].as(JsonArray).is_empty
116 fun to_json
: JsonObject do
118 var json
= to_s
.parse_json
119 if json
isa JsonParseError then
124 return json
.as(JsonObject)
127 redef fun finalize
do
135 redef class JsonObject
136 # Inits `self` from a BSON object.
137 private init from_bson
(bson
: BSON) do recover_with
(bson
.to_json
)
139 # Returns a new BSON object from `self`.
140 private fun to_bson
: BSON do return new BSON.from_json
(self)
143 # An error returned by the mongoc client.
145 # Within the client, if a method returns `false` or `null` it's more likely that
146 # an error occured during the execution.
148 # See `MongoClient::last_error`.
151 private var native
: BSONError
153 # Is the native instance valid?
155 # This is set to false if the `native` is destroyed.
156 private var is_alive
= true
158 # Logical domain within a library that created the error.
164 # Domain specific error code.
170 # Human readable error message.
171 fun message
: String do
173 var ns
= native
.message
174 var res
= ns
.to_s_with_copy
179 redef fun to_s
do return "{message} (code: {code})"
182 # MongoDB Object ID representation.
184 # For ObjectIDs, MongoDB uses the `ObjectId("hash")` notation.
185 # This notation is replicated by the `to_s` service.
187 # Since the MongoDB notation is not JSON complient, the mongoc wrapper uses
188 # a JSON based notation like `{"$oid": "hash"}`.
189 # This is the notation returned by the `to_json` service.
190 private class MongoObjectId
192 var native
: BSONObjectId
194 # The unique ID as an MongoDB Object ID string.
195 fun id
: String do return native
.id
197 # Internal JSON representation of this Object ID.
199 # Something like `{"$oid": "5578e5dcf344225cc2378051"}`.
200 fun to_json
: JsonObject do
201 var obj
= new JsonObject
206 # Formatted as `ObjectId("5578e5dcf344225cc2378051")`
207 redef fun to_s
do return "ObjectId({id})"
210 # The MongoClient is used to connect to the mongo server and send queries.
215 # var uri = "mongodb://localhost:27017/"
216 # var client = new MongoClient(uri)
217 # assert client.server_uri == uri
223 var server_uri
: String
225 private var native
: NativeMongoClient is noinit
227 # Is the native instance valid?
229 # This is set to false if the `native` is destroyed.
230 private var is_alive
= true
233 native
= new NativeMongoClient(server_uri
.to_cstring
)
238 # Returns `null` if an error occured. See `last_error`.
241 # var client = new MongoClient("mongodb://localhost:27017/")
242 # assert client.server_status["process"] == "mongod"
244 fun server_status
: nullable JsonObject do
246 var nbson
= native
.server_status
247 if nbson
== null then return null
248 var bson
= new BSON(nbson
)
249 var res
= new JsonObject.from_bson
(bson
)
253 # Lists available database names.
256 # var client = new MongoClient("mongodb://localhost:27017/")
257 # var db = client.database("test")
258 # db.collection("test").insert(new JsonObject)
259 # assert client.database_names.has("test")
261 fun database_names
: Array[String] do
263 var res
= new Array[String]
264 var nas
= native
.database_names
265 if nas
== null then return res
268 while not name
.address_is_null
do
269 res
.add name
.to_s_with_copy
277 # Loads or creates a database from its `name`.
279 # Database are automatically created on the MongoDB server upon insertion of
280 # the first document into a collection.
281 # There is no need to create a database manually.
284 # var client = new MongoClient("mongodb://localhost:27017/")
285 # assert client.database("test").name == "test"
287 fun database
(name
: String): MongoDb do
289 return new MongoDb(self, name
)
292 # Close the connexion and destroy the instance.
294 # The reference should not be used beyond this point!
300 redef fun finalize
do
307 # Last error raised by mongoc.
308 fun last_error
: nullable MongoError do
309 var last_error
= sys
.last_mongoc_error
310 if last_error
== null then return null
311 return new MongoError(last_error
)
314 # Last auto generated id.
315 private fun last_id
: nullable MongoObjectId do
316 var last_id
= sys
.last_mongoc_id
317 if last_id
== null then return null
318 return new MongoObjectId(last_id
)
321 # Set the last generated id or `null` to unset once used.
322 private fun last_id
=(id
: nullable MongoObjectId) do
324 sys
.last_mongoc_id
= null
326 sys
.last_mongoc_id
= id
.native
331 # A MongoDb database.
333 # Database are automatically created on the MongoDB server upon insertion of the
334 # first document into a collection.
335 # There is no need to create a database manually.
339 # `MongoClient` used to load this database.
340 var client
: MongoClient
345 private var native
: NativeMongoDb is noinit
347 # Is the native instance valid?
349 # This is set to false if the `native` is destroyed.
350 private var is_alive
= true
353 native
= new NativeMongoDb(client
.native
, name
.to_cstring
)
356 # Lists available collection names.
358 # Returns `null` if an error occured. See `Sys::last_mongoc_error`.
361 # var client = new MongoClient("mongodb://localhost:27017/")
362 # var db = client.database("test")
363 # db.collection("test").insert(new JsonObject)
364 # assert db.collection_names.has("test")
366 fun collection_names
: Array[String] do
368 var res
= new Array[String]
369 var nas
= native
.collection_names
370 if nas
== null then return res
373 while not name
.address_is_null
do
374 res
.add name
.to_s_with_copy
382 # Loads or creates a collection by its `name`.
385 # var client = new MongoClient("mongodb://localhost:27017/")
386 # var db = client.database("test")
387 # var col = db.collection("test")
388 # assert col.name == "test"
390 fun collection
(name
: String): MongoCollection do
392 return new MongoCollection(self, name
)
395 # Checks if a collection named `name` exists.
398 # var client = new MongoClient("mongodb://localhost:27017/")
399 # var db = client.database("test")
400 # assert not db.has_collection("qwerty")
402 fun has_collection
(name
: String): Bool do
405 return native
.has_collection
(name
.to_cstring
)
408 # Drop `self`, returns false if an error occured.
414 redef fun finalize
do
422 # A Mongo collection.
424 # Collections are automatically created on the MongoDB server upon insertion of
425 # the first document.
426 # There is no need to create a database manually.
427 class MongoCollection
430 # Database that collection belongs to.
431 var database
: MongoDb
433 # Name of this collection.
436 private var native
: NativeMongoCollection is noinit
438 # Is the native instance valid?
440 # This is set to false if the `native` is destroyed.
441 private var is_alive
= true
443 # Loads a collection.
445 # Call `MongoDb::collection` instead.
447 native
= new NativeMongoCollection(
448 database
.client
.native
,
449 database
.name
.to_cstring
,
453 # Set the autogenerated last id if the `doc` does not contain one already.
454 private fun set_id
(doc
: JsonObject) do
455 var last_id
= database
.client
.last_id
456 if last_id
!= null then
457 doc
["_id"] = last_id
.to_json
458 database
.client
.last_id
= null
462 # Inserts a new document in the collection.
464 # If no _id element is found in document, then a new one be generated locally
465 # and added to the document.
467 # Returns `false` if an error occured. See `Sys::last_mongoc_error`.
470 # var client = new MongoClient("mongodb://localhost:27017/")
471 # var col = client.database("test").collection("test")
472 # var doc = new JsonObject
475 # doc["baz"] = new JsonArray
476 # assert col.insert(doc)
477 # assert doc.has_key("_id")
479 fun insert
(doc
: JsonObject): Bool do
481 var res
= native
.insert
(doc
.to_bson
.native
)
482 if res
then set_id
(doc
)
486 # Inserts multiple documents in the collection.
489 fun insert_all
(docs
: Collection[JsonObject]): Bool do
492 for doc
in docs
do res
= insert
(doc
) and res
496 # Saves a new document in the collection.
498 # If the document has an `_id` field it will be updated.
499 # Otherwise it will be inserted.
501 # Returns `false` if an error occured. See `Sys::last_mongoc_error`.
504 # var client = new MongoClient("mongodb://localhost:27017/")
505 # var col = client.database("test").collection("test")
507 # var doc = new JsonObject
510 # doc["baz"] = new JsonArray
512 # assert col.save(doc) # will be inserted
513 # assert doc.has_key("_id")
515 # var id = doc["_id"]
516 # assert col.save(doc) # will be updated
517 # assert doc["_id"] == id
519 fun save
(doc
: JsonObject): Bool do
521 var bson
= doc
.to_bson
522 var nat
= bson
.native
523 var res
= native
.save
(nat
)
524 if res
then set_id
(doc
)
525 assert nat
!= self #FIXME used to avoid GC crashes
526 assert bson
!= self #FIXME used to avoid GC crashes
530 # Removes the first document that matches `selector`.
532 # Returns `false` if an error occured. See `Sys::last_mongoc_error`.
535 # var client = new MongoClient("mongodb://localhost:27017/")
536 # var col = client.database("test").collection("test")
537 # var sel = new JsonObject
539 # assert col.remove(sel)
541 fun remove
(selector
: JsonObject): Bool do
543 return native
.remove
(selector
.to_bson
.native
)
546 # Removes all the document that match `selector`.
549 fun remove_all
(selector
: JsonObject): Bool do
551 return native
.remove_all
(selector
.to_bson
.native
)
554 # Updates a document already existing in the collection.
556 # No upsert is done, see `save` instead.
559 # var client = new MongoClient("mongodb://localhost:27017/")
560 # var col = client.database("test").collection("test")
561 # var sel = new JsonObject
563 # var upd = new JsonObject
565 # assert col.update(sel, upd)
567 fun update
(selector
: JsonObject, update
: JsonObject): Bool do
569 return native
.update
(
570 selector
.to_bson
.native
,
571 update
.to_bson
.native
)
574 # Updates all documents matching the `selector`.
577 fun update_all
(selector
: JsonObject, update
: JsonObject): Bool do
578 return native
.update_all
(
579 selector
.to_bson
.native
,
580 update
.to_bson
.native
)
583 # Counts the document matching `query`.
585 # Returns `-1` if an error occured. See `Sys::last_mongoc_error`.
588 # var client = new MongoClient("mongodb://localhost:27017/")
589 # var col = client.database("test").collection("test")
590 # var query = new JsonObject
592 # assert col.count(query) > 0
594 fun count
(query
: JsonObject): Int do
596 return native
.count
(query
.to_bson
.native
)
599 # Finds the first document that matches `query`.
602 # * `skip` number of documents to skip
603 # * `limit` number of documents to return
605 # Returns `null` if an error occured. See `Sys::last_mongoc_error`.
608 # var client = new MongoClient("mongodb://localhost:27017/")
609 # var col = client.database("test").collection("test")
610 # var query = new JsonObject
612 # var doc = col.find(query)
613 # assert doc["foo"] == 10
615 fun find
(query
: JsonObject, skip
, limit
: nullable Int): nullable JsonObject do
617 var q
= new NativeBSON.from_json_string
(query
.to_json
.to_cstring
)
618 var s
= skip
or else 0
619 var l
= limit
or else 0
620 var c
= native
.find
(q
, s
, l
)
622 if c
== null then return null
623 var cursor
= new MongoCursor(c
)
624 if not cursor
.is_ok
then
627 var item
= cursor
.item
628 assert cursor
!= self
632 # Finds all the documents matching the `query`.
635 # * `skip` number of documents to skip
636 # * `limit` number of documents to return
639 # var client = new MongoClient("mongodb://localhost:27017/")
640 # var col = client.database("test").collection("test")
641 # var query = new JsonObject
643 # assert col.find_all(query).length > 0
645 fun find_all
(query
: JsonObject, skip
, limit
: nullable Int): Array[JsonObject] do
647 var s
= skip
or else 0
648 var l
= limit
or else 0
649 var res
= new Array[JsonObject]
650 var c
= native
.find
(query
.to_bson
.native
, s
, l
)
651 if c
== null then return res
652 var cursor
= new MongoCursor(c
)
653 for item
in cursor
do res
.add item
657 # Retrieves statistics about the collection.
659 # Returns `null` if an error occured. See `Sys::last_mongoc_error`.
662 # var client = new MongoClient("mongodb://localhost:27017/")
663 # var col = client.database("test").collection("test")
664 # assert col.stats["ns"] == "test.test"
666 fun stats
: nullable JsonObject do
668 var bson
= native
.stats
669 if bson
== null then return null
670 return new JsonObject.from_bson
(new BSON(bson
))
673 # Drops `self`, returns false if an error occured.
679 # Moves `self` to another `database`.
681 # The database will also be updated internally so it is safe to continue using
682 # this collection after the move.
683 # Additional operations will occur on moved collection.
684 fun move
(database
: MongoDb): Bool do
686 self.database
= database
687 return native
.rename
(database
.name
.to_cstring
, name
.to_cstring
)
692 # The name of the collection will also be updated internally so it is safe
693 # to continue using this collection after the rename.
694 # Additional operations will occur on renamed collection.
695 fun rename
(name
: String): Bool do
698 return native
.rename
(database
.name
.to_cstring
, name
.to_cstring
)
701 redef fun finalize
do
709 # A MongoDB query cursor.
711 # It wraps up the wire protocol negotation required to initiate a query and
712 # retreive an unknown number of documents.
715 super Iterator[JsonObject]
717 private var native
: NativeMongoCursor
719 # Is the native instance valid?
721 # This is set to false if the `native` is destroyed.
722 private var is_alive
= true
738 return new JsonObject.from_bson
(new BSON(native
.current
))
741 redef fun finalize
do