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 # # Select the database.
28 # var db_suffix = "NIT_TESTING_ID".environ
29 # var db_name = "test_{db_suffix}"
30 # var db = client.database(db_name)
32 # # Retrieve a collection.
33 # var col = db.collection("test")
35 # # Insert a document in the collection.
36 # var doc = new JsonObject
39 # doc["baz"] = new JsonArray
40 # assert col.insert(doc)
42 # # Retrieve a document from the collection.
43 # var query = new JsonObject
45 # var res = col.find(query)
46 # assert res["bar"] == "bar"
52 private import native_mongodb
58 # Everything inside MongoDB is manipulated as BSON Objects.
61 # * [Binary JSON spec](http://bsonspec.org/)
62 # * [Libbson](http://api.mongodb.org/libbson/1.1.4/)#
66 # Native instance pointer.
67 var native
: NativeBSON
69 # Returns a new BSON object initialized from the content of `json`.
72 # intrude import mongodb
73 # var obj = new JsonObject
75 # obj["name"] = "Rick"
76 # obj["ELS"] = new JsonArray
77 # var bson = new BSON.from_json(obj)
78 # assert bson.to_s == """{ "age" : 10, "name" : "Rick", "ELS" : [ ] }"""
80 init from_json
(json
: JsonObject) do
81 init(new NativeBSON.from_json_string
(json
.to_json
.to_cstring
))
84 # Returns a new BSON object parsed from `json_string`.
86 # If `json_string` is not a valid JSON string, this initializer returns NULL.
89 # intrude import mongodb
90 # var str = """{ "age" : 10, "name" : "Rick", "ELS" : [ ] }"""
91 # var bson = new BSON.from_json_string(str)
92 # assert bson.to_s == str
94 init from_json_string
(json_string
: String) do
95 init(new NativeBSON.from_json_string
(json_string
.to_cstring
))
99 var ns
= native
.to_c_string
101 ns
.free
# manual free of gc allocated CString
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
117 var json
= to_s
.parse_json
118 if json
isa JsonParseError then
122 return json
.as(JsonObject)
125 redef fun finalize_once
do native
.destroy
128 redef class JsonObject
129 # Inits `self` from a BSON object.
130 private init from_bson
(bson
: BSON) do add_all
(bson
.to_json
)
132 # Returns a new BSON object from `self`.
133 private fun to_bson
: BSON do return new BSON.from_json
(self)
136 # An error returned by the mongoc client.
138 # Within the client, if a method returns `false` or `null` it's more likely that
139 # an error occured during the execution.
141 # See `MongoClient::last_error`.
144 private var native
: BSONError
146 # Logical domain within a library that created the error.
147 fun domain
: Int do return native
.domain
149 # Domain specific error code.
150 fun code
: Int do return native
.code
152 # Human readable error message.
153 fun message
: String do
154 var ns
= native
.message
160 redef fun to_s
do return "{message} (code: {code})"
163 # MongoDB Object ID representation.
165 # For ObjectIDs, MongoDB uses the `ObjectId("hash")` notation.
166 # This notation is replicated by the `to_s` service.
168 # Since the MongoDB notation is not JSON complient, the mongoc wrapper uses
169 # a JSON based notation like `{"$oid": "hash"}`.
170 # This is the notation returned by the `to_json` service.
173 private var native
: BSONObjectId = new BSONObjectId
175 private init with_native
(native
: BSONObjectId) do
179 # The unique ID as an MongoDB Object ID string.
180 fun id
: String do return native
.id
182 # Internal JSON representation of this Object ID.
184 # Something like `{"$oid": "5578e5dcf344225cc2378051"}`.
185 fun to_json
: JsonObject do
186 var obj
= new JsonObject
191 # Formatted as `ObjectId("5578e5dcf344225cc2378051")`
192 redef fun to_s
do return "ObjectId({id})"
195 # The MongoClient is used to connect to the mongo server and send queries.
200 # var uri = "mongodb://localhost:27017/"
201 # var client = new MongoClient(uri)
202 # assert client.server_uri == uri
205 super FinalizableOnce
208 var server_uri
: String
210 private var native
: NativeMongoClient is noinit
212 init do native
= new NativeMongoClient(server_uri
.to_cstring
)
216 # Returns `null` if an error occured. See `last_error`.
219 # var client = new MongoClient("mongodb://localhost:27017/")
220 # assert client.server_status["process"] == "mongod"
222 fun server_status
: nullable JsonObject do
223 var nbson
= native
.server_status
224 if nbson
== null then return null
225 var bson
= new BSON(nbson
)
226 var res
= new JsonObject.from_bson
(bson
)
230 # Lists available database names.
233 # var client = new MongoClient("mongodb://localhost:27017/")
234 # var db_suffix = "NIT_TESTING_ID".environ
235 # var db_name = "test_{db_suffix}"
236 # var db = client.database(db_name)
237 # db.collection("test").insert(new JsonObject)
238 # assert client.database_names.has("test")
240 fun database_names
: Array[String] do
241 var res
= new Array[String]
242 var nas
= native
.database_names
243 if nas
== null then return res
246 while not name
.address_is_null
do
255 # Loads or creates a database from its `name`.
257 # Database are automatically created on the MongoDB server upon insertion of
258 # the first document into a collection.
259 # There is no need to create a database manually.
262 # var client = new MongoClient("mongodb://localhost:27017/")
263 # var db_suffix = "NIT_TESTING_ID".environ
264 # var db_name = "test_{db_suffix}"
265 # var db = client.database(db_name)
266 # assert db.name == db_name
268 fun database
(name
: String): MongoDb do return new MongoDb(self, name
)
270 # Close the connexion and destroy the instance.
272 # The reference should not be used beyond this point!
273 fun close
do finalize_once
275 redef fun finalize_once
do native
.destroy
277 # Last error raised by mongoc.
278 fun last_error
: nullable MongoError do
279 var last_error
= sys
.last_mongoc_error
280 if last_error
== null then return null
281 return new MongoError(last_error
)
284 # Last auto generated id.
285 private fun last_id
: nullable MongoObjectId do
286 var last_id
= sys
.last_mongoc_id
287 if last_id
== null then return null
288 return new MongoObjectId.with_native
(last_id
)
291 # Set the last generated id or `null` to unset once used.
292 private fun last_id
=(id
: nullable MongoObjectId) do
294 sys
.last_mongoc_id
= null
296 sys
.last_mongoc_id
= id
.native
301 # A MongoDb database.
303 # Database are automatically created on the MongoDB server upon insertion of the
304 # first document into a collection.
305 # There is no need to create a database manually.
307 super FinalizableOnce
309 # `MongoClient` used to load this database.
310 var client
: MongoClient
315 private var native
: NativeMongoDb is noinit
317 init do native
= new NativeMongoDb(client
.native
, name
.to_cstring
)
319 # Lists available collection names.
321 # Returns `null` if an error occured. See `Sys::last_mongoc_error`.
324 # var client = new MongoClient("mongodb://localhost:27017/")
325 # var db_suffix = "NIT_TESTING_ID".environ
326 # var db_name = "test_{db_suffix}"
327 # var db = client.database(db_name)
328 # db.collection("test").insert(new JsonObject)
329 # assert db.collection_names.has("test")
331 fun collection_names
: Array[String] do
332 var res
= new Array[String]
333 var nas
= native
.collection_names
334 if nas
== null then return res
337 while not name
.address_is_null
do
346 # Loads or creates a collection by its `name`.
349 # var client = new MongoClient("mongodb://localhost:27017/")
350 # var db_suffix = "NIT_TESTING_ID".environ
351 # var db_name = "test_{db_suffix}"
352 # var db = client.database(db_name)
353 # var col = db.collection("test")
354 # assert col.name == "test"
356 fun collection
(name
: String): MongoCollection do
357 return new MongoCollection(self, name
)
360 # Checks if a collection named `name` exists.
363 # var client = new MongoClient("mongodb://localhost:27017/")
364 # var db_suffix = "NIT_TESTING_ID".environ
365 # var db_name = "test_{db_suffix}"
366 # var db = client.database(db_name)
367 # assert not db.has_collection("qwerty")
369 fun has_collection
(name
: String): Bool do
371 return native
.has_collection
(name
.to_cstring
)
374 # Drop `self`, returns false if an error occured.
375 fun drop
: Bool do return native
.drop
377 redef fun finalize_once
do native
.destroy
380 # A Mongo collection.
382 # Collections are automatically created on the MongoDB server upon insertion of
383 # the first document.
384 # There is no need to create a database manually.
385 class MongoCollection
386 super FinalizableOnce
388 # Database that collection belongs to.
389 var database
: MongoDb
391 # Name of this collection.
394 private var native
: NativeMongoCollection is noinit
396 # Loads a collection.
398 # Call `MongoDb::collection` instead.
400 native
= new NativeMongoCollection(
401 database
.client
.native
,
402 database
.name
.to_cstring
,
406 # Set the autogenerated last id if the `doc` does not contain one already.
407 private fun set_id
(doc
: JsonObject) do
408 var last_id
= database
.client
.last_id
409 if last_id
!= null then
410 doc
["_id"] = last_id
.to_json
411 database
.client
.last_id
= null
415 # Inserts a new document in the collection.
417 # If no _id element is found in document, then a new one be generated locally
418 # and added to the document.
420 # Returns `false` if an error occured. See `Sys::last_mongoc_error`.
423 # var client = new MongoClient("mongodb://localhost:27017/")
424 # var db_suffix = "NIT_TESTING_ID".environ
425 # var db_name = "test_{db_suffix}"
426 # var db = client.database(db_name)
427 # var col = db.collection("test")
428 # var doc = new JsonObject
431 # doc["baz"] = new JsonArray
432 # assert col.insert(doc)
433 # assert doc.has_key("_id")
435 fun insert
(doc
: JsonObject): Bool do
436 var res
= native
.insert
(doc
.to_bson
.native
)
437 if res
then set_id
(doc
)
441 # Inserts multiple documents in the collection.
444 fun insert_all
(docs
: Collection[JsonObject]): Bool do
446 for doc
in docs
do res
= insert
(doc
) and res
450 # Saves a new document in the collection.
452 # If the document has an `_id` field it will be updated.
453 # Otherwise it will be inserted.
455 # Returns `false` if an error occured. See `Sys::last_mongoc_error`.
458 # var client = new MongoClient("mongodb://localhost:27017/")
459 # var db_suffix = "NIT_TESTING_ID".environ
460 # var db_name = "test_{db_suffix}"
461 # var db = client.database(db_name)
462 # var col = db.collection("test")
464 # var doc = new JsonObject
467 # doc["baz"] = new JsonArray
469 # assert col.save(doc) # will be inserted
470 # assert doc.has_key("_id")
472 # var id = doc["_id"]
473 # assert col.save(doc) # will be updated
474 # assert doc["_id"] == id
476 fun save
(doc
: JsonObject): Bool do
477 var bson
= doc
.to_bson
478 var nat
= bson
.native
479 var res
= native
.save
(nat
)
480 if res
then set_id
(doc
)
481 assert nat
!= self #FIXME used to avoid GC crashes
482 assert bson
!= self #FIXME used to avoid GC crashes
486 # Removes the first document that matches `selector`.
488 # Returns `false` if an error occured. See `Sys::last_mongoc_error`.
491 # var client = new MongoClient("mongodb://localhost:27017/")
492 # var db_suffix = "NIT_TESTING_ID".environ
493 # var db_name = "test_{db_suffix}"
494 # var db = client.database(db_name)
495 # var col = db.collection("test")
496 # var sel = new JsonObject
498 # assert col.remove(sel)
500 fun remove
(selector
: JsonObject): Bool do
501 return native
.remove
(selector
.to_bson
.native
)
504 # Removes all the document that match `selector`.
507 fun remove_all
(selector
: JsonObject): Bool do
508 return native
.remove_all
(selector
.to_bson
.native
)
511 # Updates a document already existing in the collection.
513 # No upsert is done, see `save` instead.
516 # var client = new MongoClient("mongodb://localhost:27017/")
517 # var db_suffix = "NIT_TESTING_ID".environ
518 # var db_name = "test_{db_suffix}"
519 # var db = client.database(db_name)
520 # var col = db.collection("test")
521 # var sel = new JsonObject
523 # var upd = new JsonObject
525 # assert col.update(sel, upd)
527 fun update
(selector
: JsonObject, update
: JsonObject): Bool do
528 return native
.update
(
529 selector
.to_bson
.native
,
530 update
.to_bson
.native
)
533 # Updates all documents matching the `selector`.
536 fun update_all
(selector
: JsonObject, update
: JsonObject): Bool do
537 return native
.update_all
(
538 selector
.to_bson
.native
,
539 update
.to_bson
.native
)
542 # Counts the document matching `query`.
544 # Returns `-1` if an error occured. See `Sys::last_mongoc_error`.
547 # var client = new MongoClient("mongodb://localhost:27017/")
548 # var db_suffix = "NIT_TESTING_ID".environ
549 # var db_name = "test_{db_suffix}"
550 # var db = client.database(db_name)
551 # var col = db.collection("test")
552 # var query = new JsonObject
554 # assert col.count(query) > 0
556 fun count
(query
: JsonObject): Int do
557 return native
.count
(query
.to_bson
.native
)
560 # Finds the first document that matches `query`.
563 # * `skip` number of documents to skip
564 # * `limit` number of documents to return
566 # Returns `null` if an error occured. See `Sys::last_mongoc_error`.
569 # var client = new MongoClient("mongodb://localhost:27017/")
570 # var db_suffix = "NIT_TESTING_ID".environ
571 # var db_name = "test_{db_suffix}"
572 # var db = client.database(db_name)
573 # var col = db.collection("test")
574 # var query = new JsonObject
576 # var doc = col.find(query)
577 # assert doc["foo"] == 10
579 fun find
(query
: JsonObject, skip
, limit
: nullable Int): nullable JsonObject do
580 var q
= new NativeBSON.from_json_string
(query
.to_json
.to_cstring
)
581 var s
= skip
or else 0
582 var l
= limit
or else 0
583 var c
= native
.find
(q
, s
, l
)
585 if c
== null then return null
586 var cursor
= new MongoCursor(c
)
587 if not cursor
.is_ok
then
590 var item
= cursor
.item
591 assert cursor
!= self
595 # Finds all the documents matching the `query`.
598 # * `skip` number of documents to skip
599 # * `limit` number of documents to return
602 # var client = new MongoClient("mongodb://localhost:27017/")
603 # var db_suffix = "NIT_TESTING_ID".environ
604 # var db_name = "test_{db_suffix}"
605 # var db = client.database(db_name)
606 # var col = db.collection("test")
607 # var query = new JsonObject
609 # assert col.find_all(query).length > 0
611 fun find_all
(query
: JsonObject, skip
, limit
: nullable Int): Array[JsonObject] do
612 var s
= skip
or else 0
613 var l
= limit
or else 0
614 var res
= new Array[JsonObject]
615 var c
= native
.find
(query
.to_bson
.native
, s
, l
)
616 if c
== null then return res
617 var cursor
= new MongoCursor(c
)
618 while cursor
.is_ok
do
625 # Applies an aggregation `pipeline` over the collection.
628 # var client = new MongoClient("mongodb://localhost:27017/")
629 # var db_suffix = "NIT_TESTING_ID".environ
630 # var db_name = "test_{db_suffix}"
631 # var db = client.database(db_name)
632 # var col = db.collection("test_aggregate")
636 # col.insert("""{ "cust_id": "A123", "amount": 500, "status": "A"}""".parse_json.as(JsonObject))
637 # col.insert("""{ "cust_id": "A123", "amount": 250, "status": "A"}""".parse_json.as(JsonObject))
638 # col.insert("""{ "cust_id": "B212", "amount": 200, "status": "A"}""".parse_json.as(JsonObject))
639 # col.insert("""{ "cust_id": "A123", "amount": 300, "status": "D"}""".parse_json.as(JsonObject))
641 # var res = col.aggregate("""[
642 # { "$match": { "status": "A" } },
643 # { "$group": { "_id": "$cust_id", "total": { "$sum": "$amount" } } }
644 # ]""".parse_json.as(JsonArray))
646 # assert res[0].to_json == """{"_id":"B212","total":200}"""
647 # assert res[1].to_json == """{"_id":"A123","total":750}"""
649 fun aggregate
(pipeline
: JsonArray): Array[JsonObject] do
650 var q
= new JsonObject
651 q
["pipeline"] = pipeline
652 var res
= new Array[JsonObject]
653 var c
= native
.aggregate
(q
.to_bson
.native
)
654 if c
== null then return res
655 var cursor
= new MongoCursor(c
)
656 while cursor
.is_ok
do
663 # Retrieves statistics about the collection.
665 # Returns `null` if an error occured. See `Sys::last_mongoc_error`.
668 # var client = new MongoClient("mongodb://localhost:27017/")
669 # var db_suffix = "NIT_TESTING_ID".environ
670 # var db_name = "test_{db_suffix}"
671 # var db = client.database(db_name)
672 # var col = db.collection("test")
673 # assert col.stats["ns"] == "{db_name}.test"
675 fun stats
: nullable JsonObject do
676 var bson
= native
.stats
677 if bson
== null then return null
678 return new JsonObject.from_bson
(new BSON(bson
))
681 # Drops `self`, returns false if an error occured.
682 fun drop
: Bool do return native
.drop
684 # Moves `self` to another `database`.
686 # The database will also be updated internally so it is safe to continue using
687 # this collection after the move.
688 # Additional operations will occur on moved collection.
689 fun move
(database
: MongoDb): Bool do
690 self.database
= database
691 return native
.rename
(database
.name
.to_cstring
, name
.to_cstring
)
696 # The name of the collection will also be updated internally so it is safe
697 # to continue using this collection after the rename.
698 # Additional operations will occur on renamed collection.
699 fun rename
(name
: String): Bool do
701 return native
.rename
(database
.name
.to_cstring
, name
.to_cstring
)
704 redef fun finalize_once
do native
.destroy
707 # A MongoDB query cursor.
709 # It wraps up the wire protocol negotation required to initiate a query and
710 # retreive an unknown number of documents.
712 super FinalizableOnce
713 super Iterator[JsonObject]
715 private var native
: NativeMongoCursor
719 redef var is_ok
= true
721 redef fun next
do is_ok
= native
.next
724 return new JsonObject.from_bson
(new BSON(native
.current
))
727 redef fun finalize_once
do native
.destroy