80c4d4c844196e83b3429c188af744e5ec035990
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.
167 private var native
: BSONObjectId = new BSONObjectId
169 private init with_native
(native
: BSONObjectId) do
173 # The unique ID as an MongoDB Object ID string.
174 fun id
: String do return native
.id
176 # Internal JSON representation of this Object ID.
178 # Something like `{"$oid": "5578e5dcf344225cc2378051"}`.
179 fun to_json
: JsonObject do
180 var obj
= new JsonObject
185 # Formatted as `ObjectId("5578e5dcf344225cc2378051")`
186 redef fun to_s
do return "ObjectId({id})"
189 # The MongoClient is used to connect to the mongo server and send queries.
194 # var uri = "mongodb://localhost:27017/"
195 # var client = new MongoClient(uri)
196 # assert client.server_uri == uri
199 super FinalizableOnce
202 var server_uri
: String
204 private var native
: NativeMongoClient is noinit
206 init do native
= new NativeMongoClient(server_uri
.to_cstring
)
210 # Returns `null` if an error occured. See `last_error`.
213 # var client = new MongoClient("mongodb://localhost:27017/")
214 # assert client.server_status["process"] == "mongod"
216 fun server_status
: nullable JsonObject do
217 var nbson
= native
.server_status
218 if nbson
== null then return null
219 var bson
= new BSON(nbson
)
220 var res
= new JsonObject.from_bson
(bson
)
224 # Lists available database names.
227 # var client = new MongoClient("mongodb://localhost:27017/")
228 # var db = client.database("test")
229 # db.collection("test").insert(new JsonObject)
230 # assert client.database_names.has("test")
232 fun database_names
: Array[String] do
233 var res
= new Array[String]
234 var nas
= native
.database_names
235 if nas
== null then return res
238 while not name
.address_is_null
do
239 res
.add name
.to_s_with_copy
247 # Loads or creates a database from its `name`.
249 # Database are automatically created on the MongoDB server upon insertion of
250 # the first document into a collection.
251 # There is no need to create a database manually.
254 # var client = new MongoClient("mongodb://localhost:27017/")
255 # assert client.database("test").name == "test"
257 fun database
(name
: String): MongoDb do return new MongoDb(self, name
)
259 # Close the connexion and destroy the instance.
261 # The reference should not be used beyond this point!
262 fun close
do finalize_once
264 redef fun finalize_once
do native
.destroy
266 # Last error raised by mongoc.
267 fun last_error
: nullable MongoError do
268 var last_error
= sys
.last_mongoc_error
269 if last_error
== null then return null
270 return new MongoError(last_error
)
273 # Last auto generated id.
274 private fun last_id
: nullable MongoObjectId do
275 var last_id
= sys
.last_mongoc_id
276 if last_id
== null then return null
277 return new MongoObjectId.with_native
(last_id
)
280 # Set the last generated id or `null` to unset once used.
281 private fun last_id
=(id
: nullable MongoObjectId) do
283 sys
.last_mongoc_id
= null
285 sys
.last_mongoc_id
= id
.native
290 # A MongoDb database.
292 # Database are automatically created on the MongoDB server upon insertion of the
293 # first document into a collection.
294 # There is no need to create a database manually.
296 super FinalizableOnce
298 # `MongoClient` used to load this database.
299 var client
: MongoClient
304 private var native
: NativeMongoDb is noinit
306 init do native
= new NativeMongoDb(client
.native
, name
.to_cstring
)
308 # Lists available collection names.
310 # Returns `null` if an error occured. See `Sys::last_mongoc_error`.
313 # var client = new MongoClient("mongodb://localhost:27017/")
314 # var db = client.database("test")
315 # db.collection("test").insert(new JsonObject)
316 # assert db.collection_names.has("test")
318 fun collection_names
: Array[String] do
319 var res
= new Array[String]
320 var nas
= native
.collection_names
321 if nas
== null then return res
324 while not name
.address_is_null
do
325 res
.add name
.to_s_with_copy
333 # Loads or creates a collection by its `name`.
336 # var client = new MongoClient("mongodb://localhost:27017/")
337 # var db = client.database("test")
338 # var col = db.collection("test")
339 # assert col.name == "test"
341 fun collection
(name
: String): MongoCollection do
342 return new MongoCollection(self, name
)
345 # Checks if a collection named `name` exists.
348 # var client = new MongoClient("mongodb://localhost:27017/")
349 # var db = client.database("test")
350 # assert not db.has_collection("qwerty")
352 fun has_collection
(name
: String): Bool do
354 return native
.has_collection
(name
.to_cstring
)
357 # Drop `self`, returns false if an error occured.
358 fun drop
: Bool do return native
.drop
360 redef fun finalize_once
do native
.destroy
363 # A Mongo collection.
365 # Collections are automatically created on the MongoDB server upon insertion of
366 # the first document.
367 # There is no need to create a database manually.
368 class MongoCollection
369 super FinalizableOnce
371 # Database that collection belongs to.
372 var database
: MongoDb
374 # Name of this collection.
377 private var native
: NativeMongoCollection is noinit
379 # Loads a collection.
381 # Call `MongoDb::collection` instead.
383 native
= new NativeMongoCollection(
384 database
.client
.native
,
385 database
.name
.to_cstring
,
389 # Set the autogenerated last id if the `doc` does not contain one already.
390 private fun set_id
(doc
: JsonObject) do
391 var last_id
= database
.client
.last_id
392 if last_id
!= null then
393 doc
["_id"] = last_id
.to_json
394 database
.client
.last_id
= null
398 # Inserts a new document in the collection.
400 # If no _id element is found in document, then a new one be generated locally
401 # and added to the document.
403 # Returns `false` if an error occured. See `Sys::last_mongoc_error`.
406 # var client = new MongoClient("mongodb://localhost:27017/")
407 # var col = client.database("test").collection("test")
408 # var doc = new JsonObject
411 # doc["baz"] = new JsonArray
412 # assert col.insert(doc)
413 # assert doc.has_key("_id")
415 fun insert
(doc
: JsonObject): Bool do
416 var res
= native
.insert
(doc
.to_bson
.native
)
417 if res
then set_id
(doc
)
421 # Inserts multiple documents in the collection.
424 fun insert_all
(docs
: Collection[JsonObject]): Bool do
426 for doc
in docs
do res
= insert
(doc
) and res
430 # Saves a new document in the collection.
432 # If the document has an `_id` field it will be updated.
433 # Otherwise it will be inserted.
435 # Returns `false` if an error occured. See `Sys::last_mongoc_error`.
438 # var client = new MongoClient("mongodb://localhost:27017/")
439 # var col = client.database("test").collection("test")
441 # var doc = new JsonObject
444 # doc["baz"] = new JsonArray
446 # assert col.save(doc) # will be inserted
447 # assert doc.has_key("_id")
449 # var id = doc["_id"]
450 # assert col.save(doc) # will be updated
451 # assert doc["_id"] == id
453 fun save
(doc
: JsonObject): Bool do
454 var bson
= doc
.to_bson
455 var nat
= bson
.native
456 var res
= native
.save
(nat
)
457 if res
then set_id
(doc
)
458 assert nat
!= self #FIXME used to avoid GC crashes
459 assert bson
!= self #FIXME used to avoid GC crashes
463 # Removes the first document that matches `selector`.
465 # Returns `false` if an error occured. See `Sys::last_mongoc_error`.
468 # var client = new MongoClient("mongodb://localhost:27017/")
469 # var col = client.database("test").collection("test")
470 # var sel = new JsonObject
472 # assert col.remove(sel)
474 fun remove
(selector
: JsonObject): Bool do
475 return native
.remove
(selector
.to_bson
.native
)
478 # Removes all the document that match `selector`.
481 fun remove_all
(selector
: JsonObject): Bool do
482 return native
.remove_all
(selector
.to_bson
.native
)
485 # Updates a document already existing in the collection.
487 # No upsert is done, see `save` instead.
490 # var client = new MongoClient("mongodb://localhost:27017/")
491 # var col = client.database("test").collection("test")
492 # var sel = new JsonObject
494 # var upd = new JsonObject
496 # assert col.update(sel, upd)
498 fun update
(selector
: JsonObject, update
: JsonObject): Bool do
499 return native
.update
(
500 selector
.to_bson
.native
,
501 update
.to_bson
.native
)
504 # Updates all documents matching the `selector`.
507 fun update_all
(selector
: JsonObject, update
: JsonObject): Bool do
508 return native
.update_all
(
509 selector
.to_bson
.native
,
510 update
.to_bson
.native
)
513 # Counts the document matching `query`.
515 # Returns `-1` if an error occured. See `Sys::last_mongoc_error`.
518 # var client = new MongoClient("mongodb://localhost:27017/")
519 # var col = client.database("test").collection("test")
520 # var query = new JsonObject
522 # assert col.count(query) > 0
524 fun count
(query
: JsonObject): Int do
525 return native
.count
(query
.to_bson
.native
)
528 # Finds the first document that matches `query`.
531 # * `skip` number of documents to skip
532 # * `limit` number of documents to return
534 # Returns `null` if an error occured. See `Sys::last_mongoc_error`.
537 # var client = new MongoClient("mongodb://localhost:27017/")
538 # var col = client.database("test").collection("test")
539 # var query = new JsonObject
541 # var doc = col.find(query)
542 # assert doc["foo"] == 10
544 fun find
(query
: JsonObject, skip
, limit
: nullable Int): nullable JsonObject do
545 var q
= new NativeBSON.from_json_string
(query
.to_json
.to_cstring
)
546 var s
= skip
or else 0
547 var l
= limit
or else 0
548 var c
= native
.find
(q
, s
, l
)
550 if c
== null then return null
551 var cursor
= new MongoCursor(c
)
552 if not cursor
.is_ok
then
555 var item
= cursor
.item
556 assert cursor
!= self
560 # Finds all the documents matching the `query`.
563 # * `skip` number of documents to skip
564 # * `limit` number of documents to return
567 # var client = new MongoClient("mongodb://localhost:27017/")
568 # var col = client.database("test").collection("test")
569 # var query = new JsonObject
571 # assert col.find_all(query).length > 0
573 fun find_all
(query
: JsonObject, skip
, limit
: nullable Int): Array[JsonObject] do
574 var s
= skip
or else 0
575 var l
= limit
or else 0
576 var res
= new Array[JsonObject]
577 var c
= native
.find
(query
.to_bson
.native
, s
, l
)
578 if c
== null then return res
579 var cursor
= new MongoCursor(c
)
580 while cursor
.is_ok
do
587 # Applies an aggregation `pipeline` over the collection.
590 # var client = new MongoClient("mongodb://localhost:27017/")
591 # var col = client.database("test").collection("test_aggregate")
595 # col.insert("""{ "cust_id": "A123", "amount": 500, "status": "A"}""".parse_json.as(JsonObject))
596 # col.insert("""{ "cust_id": "A123", "amount": 250, "status": "A"}""".parse_json.as(JsonObject))
597 # col.insert("""{ "cust_id": "B212", "amount": 200, "status": "A"}""".parse_json.as(JsonObject))
598 # col.insert("""{ "cust_id": "A123", "amount": 300, "status": "D"}""".parse_json.as(JsonObject))
600 # var res = col.aggregate("""[
601 # { "$match": { "status": "A" } },
602 # { "$group": { "_id": "$cust_id", "total": { "$sum": "$amount" } } }
603 # ]""".parse_json.as(JsonArray))
605 # assert res[0].to_json == """{"_id":"B212","total":200}"""
606 # assert res[1].to_json == """{"_id":"A123","total":750}"""
608 fun aggregate
(pipeline
: JsonArray): Array[JsonObject] do
609 var q
= new JsonObject
610 q
["pipeline"] = pipeline
611 var res
= new Array[JsonObject]
612 var c
= native
.aggregate
(q
.to_bson
.native
)
613 if c
== null then return res
614 var cursor
= new MongoCursor(c
)
615 while cursor
.is_ok
do
622 # Retrieves statistics about the collection.
624 # Returns `null` if an error occured. See `Sys::last_mongoc_error`.
627 # var client = new MongoClient("mongodb://localhost:27017/")
628 # var col = client.database("test").collection("test")
629 # assert col.stats["ns"] == "test.test"
631 fun stats
: nullable JsonObject do
632 var bson
= native
.stats
633 if bson
== null then return null
634 return new JsonObject.from_bson
(new BSON(bson
))
637 # Drops `self`, returns false if an error occured.
638 fun drop
: Bool do return native
.drop
640 # Moves `self` to another `database`.
642 # The database will also be updated internally so it is safe to continue using
643 # this collection after the move.
644 # Additional operations will occur on moved collection.
645 fun move
(database
: MongoDb): Bool do
646 self.database
= database
647 return native
.rename
(database
.name
.to_cstring
, name
.to_cstring
)
652 # The name of the collection will also be updated internally so it is safe
653 # to continue using this collection after the rename.
654 # Additional operations will occur on renamed collection.
655 fun rename
(name
: String): Bool do
657 return native
.rename
(database
.name
.to_cstring
, name
.to_cstring
)
660 redef fun finalize_once
do native
.destroy
663 # A MongoDB query cursor.
665 # It wraps up the wire protocol negotation required to initiate a query and
666 # retreive an unknown number of documents.
668 super FinalizableOnce
669 super Iterator[JsonObject]
671 private var native
: NativeMongoCursor
675 redef var is_ok
= true
677 redef fun next
do is_ok
= native
.next
680 return new JsonObject.from_bson
(new BSON(native
.current
))
683 redef fun finalize_once
do native
.destroy