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 # Returns a new BSON object initialized from the content of `json`.
66 # intrude import mongodb
67 # var obj = new JsonObject
69 # obj["name"] = "Rick"
70 # obj["ELS"] = new JsonArray
71 # var bson = new BSON.from_json(obj)
72 # assert bson.to_s == """{ "age" : 10, "name" : "Rick", "ELS" : [ ] }"""
74 init from_json
(json
: JsonObject) do
75 init(new NativeBSON.from_json_string
(json
.to_json
.to_cstring
))
78 # Returns a new BSON object parsed from `json_string`.
80 # If `json_string` is not a valid JSON string, this initializer returns NULL.
83 # intrude import mongodb
84 # var str = """{ "age" : 10, "name" : "Rick", "ELS" : [ ] }"""
85 # var bson = new BSON.from_json_string(str)
86 # assert bson.to_s == str
88 init from_json_string
(json_string
: String) do
89 init(new NativeBSON.from_json_string
(json_string
.to_cstring
))
93 var ns
= native
.to_native_string
94 var res
= ns
.to_s_with_copy
95 ns
.free
# manual free of gc allocated NativeString
99 # Returns a new JsonObject from `self`.
102 # intrude import mongodb
103 # var str = """{ "age" : 10, "name" : "Rick", "ELS" : [ ] }"""
104 # var bson = new BSON.from_json_string(str)
105 # var json = bson.to_json
106 # assert json["age"] == 10
107 # assert json["name"] == "Rick"
108 # assert json["ELS"].as(JsonArray).is_empty
110 fun to_json
: JsonObject do
111 var json
= to_s
.parse_json
112 if json
isa JsonParseError then
116 return json
.as(JsonObject)
119 redef fun finalize_once
do native
.destroy
122 redef class JsonObject
123 # Inits `self` from a BSON object.
124 private init from_bson
(bson
: BSON) do add_all
(bson
.to_json
)
126 # Returns a new BSON object from `self`.
127 private fun to_bson
: BSON do return new BSON.from_json
(self)
130 # An error returned by the mongoc client.
132 # Within the client, if a method returns `false` or `null` it's more likely that
133 # an error occured during the execution.
135 # See `MongoClient::last_error`.
138 private var native
: BSONError
140 # Logical domain within a library that created the error.
141 fun domain
: Int do return native
.domain
143 # Domain specific error code.
144 fun code
: Int do return native
.code
146 # Human readable error message.
147 fun message
: String do
148 var ns
= native
.message
149 var res
= ns
.to_s_with_copy
154 redef fun to_s
do return "{message} (code: {code})"
157 # MongoDB Object ID representation.
159 # For ObjectIDs, MongoDB uses the `ObjectId("hash")` notation.
160 # This notation is replicated by the `to_s` service.
162 # Since the MongoDB notation is not JSON complient, the mongoc wrapper uses
163 # a JSON based notation like `{"$oid": "hash"}`.
164 # This is the notation returned by the `to_json` service.
165 private class MongoObjectId
167 var native
: BSONObjectId
169 # The unique ID as an MongoDB Object ID string.
170 fun id
: String do return native
.id
172 # Internal JSON representation of this Object ID.
174 # Something like `{"$oid": "5578e5dcf344225cc2378051"}`.
175 fun to_json
: JsonObject do
176 var obj
= new JsonObject
181 # Formatted as `ObjectId("5578e5dcf344225cc2378051")`
182 redef fun to_s
do return "ObjectId({id})"
185 # The MongoClient is used to connect to the mongo server and send queries.
190 # var uri = "mongodb://localhost:27017/"
191 # var client = new MongoClient(uri)
192 # assert client.server_uri == uri
195 super FinalizableOnce
198 var server_uri
: String
200 private var native
: NativeMongoClient is noinit
202 init do native
= new NativeMongoClient(server_uri
.to_cstring
)
206 # Returns `null` if an error occured. See `last_error`.
209 # var client = new MongoClient("mongodb://localhost:27017/")
210 # assert client.server_status["process"] == "mongod"
212 fun server_status
: nullable JsonObject do
213 var nbson
= native
.server_status
214 if nbson
== null then return null
215 var bson
= new BSON(nbson
)
216 var res
= new JsonObject.from_bson
(bson
)
220 # Lists available database names.
223 # var client = new MongoClient("mongodb://localhost:27017/")
224 # var db = client.database("test")
225 # db.collection("test").insert(new JsonObject)
226 # assert client.database_names.has("test")
228 fun database_names
: Array[String] do
229 var res
= new Array[String]
230 var nas
= native
.database_names
231 if nas
== null then return res
234 while not name
.address_is_null
do
235 res
.add name
.to_s_with_copy
243 # Loads or creates a database from its `name`.
245 # Database are automatically created on the MongoDB server upon insertion of
246 # the first document into a collection.
247 # There is no need to create a database manually.
250 # var client = new MongoClient("mongodb://localhost:27017/")
251 # assert client.database("test").name == "test"
253 fun database
(name
: String): MongoDb do return new MongoDb(self, name
)
255 # Close the connexion and destroy the instance.
257 # The reference should not be used beyond this point!
258 fun close
do finalize_once
260 redef fun finalize_once
do native
.destroy
262 # Last error raised by mongoc.
263 fun last_error
: nullable MongoError do
264 var last_error
= sys
.last_mongoc_error
265 if last_error
== null then return null
266 return new MongoError(last_error
)
269 # Last auto generated id.
270 private fun last_id
: nullable MongoObjectId do
271 var last_id
= sys
.last_mongoc_id
272 if last_id
== null then return null
273 return new MongoObjectId(last_id
)
276 # Set the last generated id or `null` to unset once used.
277 private fun last_id
=(id
: nullable MongoObjectId) do
279 sys
.last_mongoc_id
= null
281 sys
.last_mongoc_id
= id
.native
286 # A MongoDb database.
288 # Database are automatically created on the MongoDB server upon insertion of the
289 # first document into a collection.
290 # There is no need to create a database manually.
292 super FinalizableOnce
294 # `MongoClient` used to load this database.
295 var client
: MongoClient
300 private var native
: NativeMongoDb is noinit
302 init do native
= new NativeMongoDb(client
.native
, name
.to_cstring
)
304 # Lists available collection names.
306 # Returns `null` if an error occured. See `Sys::last_mongoc_error`.
309 # var client = new MongoClient("mongodb://localhost:27017/")
310 # var db = client.database("test")
311 # db.collection("test").insert(new JsonObject)
312 # assert db.collection_names.has("test")
314 fun collection_names
: Array[String] do
315 var res
= new Array[String]
316 var nas
= native
.collection_names
317 if nas
== null then return res
320 while not name
.address_is_null
do
321 res
.add name
.to_s_with_copy
329 # Loads or creates a collection by its `name`.
332 # var client = new MongoClient("mongodb://localhost:27017/")
333 # var db = client.database("test")
334 # var col = db.collection("test")
335 # assert col.name == "test"
337 fun collection
(name
: String): MongoCollection do
338 return new MongoCollection(self, name
)
341 # Checks if a collection named `name` exists.
344 # var client = new MongoClient("mongodb://localhost:27017/")
345 # var db = client.database("test")
346 # assert not db.has_collection("qwerty")
348 fun has_collection
(name
: String): Bool do
350 return native
.has_collection
(name
.to_cstring
)
353 # Drop `self`, returns false if an error occured.
354 fun drop
: Bool do return native
.drop
356 redef fun finalize_once
do native
.destroy
359 # A Mongo collection.
361 # Collections are automatically created on the MongoDB server upon insertion of
362 # the first document.
363 # There is no need to create a database manually.
364 class MongoCollection
365 super FinalizableOnce
367 # Database that collection belongs to.
368 var database
: MongoDb
370 # Name of this collection.
373 private var native
: NativeMongoCollection is noinit
375 # Loads a collection.
377 # Call `MongoDb::collection` instead.
379 native
= new NativeMongoCollection(
380 database
.client
.native
,
381 database
.name
.to_cstring
,
385 # Set the autogenerated last id if the `doc` does not contain one already.
386 private fun set_id
(doc
: JsonObject) do
387 var last_id
= database
.client
.last_id
388 if last_id
!= null then
389 doc
["_id"] = last_id
.to_json
390 database
.client
.last_id
= null
394 # Inserts a new document in the collection.
396 # If no _id element is found in document, then a new one be generated locally
397 # and added to the document.
399 # Returns `false` if an error occured. See `Sys::last_mongoc_error`.
402 # var client = new MongoClient("mongodb://localhost:27017/")
403 # var col = client.database("test").collection("test")
404 # var doc = new JsonObject
407 # doc["baz"] = new JsonArray
408 # assert col.insert(doc)
409 # assert doc.has_key("_id")
411 fun insert
(doc
: JsonObject): Bool do
412 var res
= native
.insert
(doc
.to_bson
.native
)
413 if res
then set_id
(doc
)
417 # Inserts multiple documents in the collection.
420 fun insert_all
(docs
: Collection[JsonObject]): Bool do
422 for doc
in docs
do res
= insert
(doc
) and res
426 # Saves a new document in the collection.
428 # If the document has an `_id` field it will be updated.
429 # Otherwise it will be inserted.
431 # Returns `false` if an error occured. See `Sys::last_mongoc_error`.
434 # var client = new MongoClient("mongodb://localhost:27017/")
435 # var col = client.database("test").collection("test")
437 # var doc = new JsonObject
440 # doc["baz"] = new JsonArray
442 # assert col.save(doc) # will be inserted
443 # assert doc.has_key("_id")
445 # var id = doc["_id"]
446 # assert col.save(doc) # will be updated
447 # assert doc["_id"] == id
449 fun save
(doc
: JsonObject): Bool do
450 var bson
= doc
.to_bson
451 var nat
= bson
.native
452 var res
= native
.save
(nat
)
453 if res
then set_id
(doc
)
454 assert nat
!= self #FIXME used to avoid GC crashes
455 assert bson
!= self #FIXME used to avoid GC crashes
459 # Removes the first document that matches `selector`.
461 # Returns `false` if an error occured. See `Sys::last_mongoc_error`.
464 # var client = new MongoClient("mongodb://localhost:27017/")
465 # var col = client.database("test").collection("test")
466 # var sel = new JsonObject
468 # assert col.remove(sel)
470 fun remove
(selector
: JsonObject): Bool do
471 return native
.remove
(selector
.to_bson
.native
)
474 # Removes all the document that match `selector`.
477 fun remove_all
(selector
: JsonObject): Bool do
478 return native
.remove_all
(selector
.to_bson
.native
)
481 # Updates a document already existing in the collection.
483 # No upsert is done, see `save` instead.
486 # var client = new MongoClient("mongodb://localhost:27017/")
487 # var col = client.database("test").collection("test")
488 # var sel = new JsonObject
490 # var upd = new JsonObject
492 # assert col.update(sel, upd)
494 fun update
(selector
: JsonObject, update
: JsonObject): Bool do
495 return native
.update
(
496 selector
.to_bson
.native
,
497 update
.to_bson
.native
)
500 # Updates all documents matching the `selector`.
503 fun update_all
(selector
: JsonObject, update
: JsonObject): Bool do
504 return native
.update_all
(
505 selector
.to_bson
.native
,
506 update
.to_bson
.native
)
509 # Counts the document matching `query`.
511 # Returns `-1` if an error occured. See `Sys::last_mongoc_error`.
514 # var client = new MongoClient("mongodb://localhost:27017/")
515 # var col = client.database("test").collection("test")
516 # var query = new JsonObject
518 # assert col.count(query) > 0
520 fun count
(query
: JsonObject): Int do
521 return native
.count
(query
.to_bson
.native
)
524 # Finds the first document that matches `query`.
527 # * `skip` number of documents to skip
528 # * `limit` number of documents to return
530 # Returns `null` if an error occured. See `Sys::last_mongoc_error`.
533 # var client = new MongoClient("mongodb://localhost:27017/")
534 # var col = client.database("test").collection("test")
535 # var query = new JsonObject
537 # var doc = col.find(query)
538 # assert doc["foo"] == 10
540 fun find
(query
: JsonObject, skip
, limit
: nullable Int): nullable JsonObject do
541 var q
= new NativeBSON.from_json_string
(query
.to_json
.to_cstring
)
542 var s
= skip
or else 0
543 var l
= limit
or else 0
544 var c
= native
.find
(q
, s
, l
)
546 if c
== null then return null
547 var cursor
= new MongoCursor(c
)
548 if not cursor
.is_ok
then
551 var item
= cursor
.item
552 assert cursor
!= self
556 # Finds all the documents matching the `query`.
559 # * `skip` number of documents to skip
560 # * `limit` number of documents to return
563 # var client = new MongoClient("mongodb://localhost:27017/")
564 # var col = client.database("test").collection("test")
565 # var query = new JsonObject
567 # assert col.find_all(query).length > 0
569 fun find_all
(query
: JsonObject, skip
, limit
: nullable Int): Array[JsonObject] do
570 var s
= skip
or else 0
571 var l
= limit
or else 0
572 var res
= new Array[JsonObject]
573 var c
= native
.find
(query
.to_bson
.native
, s
, l
)
574 if c
== null then return res
575 var cursor
= new MongoCursor(c
)
576 while cursor
.is_ok
do
583 # Retrieves statistics about the collection.
585 # Returns `null` 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 # assert col.stats["ns"] == "test.test"
592 fun stats
: nullable JsonObject do
593 var bson
= native
.stats
594 if bson
== null then return null
595 return new JsonObject.from_bson
(new BSON(bson
))
598 # Drops `self`, returns false if an error occured.
599 fun drop
: Bool do return native
.drop
601 # Moves `self` to another `database`.
603 # The database will also be updated internally so it is safe to continue using
604 # this collection after the move.
605 # Additional operations will occur on moved collection.
606 fun move
(database
: MongoDb): Bool do
607 self.database
= database
608 return native
.rename
(database
.name
.to_cstring
, name
.to_cstring
)
613 # The name of the collection will also be updated internally so it is safe
614 # to continue using this collection after the rename.
615 # Additional operations will occur on renamed collection.
616 fun rename
(name
: String): Bool do
618 return native
.rename
(database
.name
.to_cstring
, name
.to_cstring
)
621 redef fun finalize_once
do native
.destroy
624 # A MongoDB query cursor.
626 # It wraps up the wire protocol negotation required to initiate a query and
627 # retreive an unknown number of documents.
629 super FinalizableOnce
630 super Iterator[JsonObject]
632 private var native
: NativeMongoCursor
636 redef var is_ok
= true
638 redef fun next
do is_ok
= native
.next
641 return new JsonObject.from_bson
(new BSON(native
.current
))
644 redef fun finalize_once
do native
.destroy