4a0718f575f47c0791c6447f7158c4128aedc0f2
[nit.git] / lib / mongodb / mongodb.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2015 Alexandre Terrasa <alexandre@moz-code.org>
4 #
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
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
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.
16
17 # MongoDB Nit Driver.
18 #
19 # This is actually a wrapper around the [MongoDB C Driver](http://api.mongodb.org/c/1.1.4/index.html).
20 #
21 # Usage:
22 #
23 # ~~~
24 # # Opens the connexion with the Mongo server.
25 # var client = new MongoClient("mongodb://localhost:27017/")
26 #
27 # # Retrieve a collection.
28 # var col = client.database("test").collection("test")
29 #
30 # # Insert a document in the collection.
31 # var doc = new JsonObject
32 # doc["foo"] = 10
33 # doc["bar"] = "bar"
34 # doc["baz"] = new JsonArray
35 # assert col.insert(doc)
36 #
37 # # Retrieve a document from the collection.
38 # var query = new JsonObject
39 # query["foo"] = 10
40 # var res = col.find(query)
41 # assert res["bar"] == "bar"
42 # ~~~
43 #
44 # TODO Get last Object_ID
45 module mongodb
46
47 import json
48 private import native_mongodb
49
50 in "C header" `{
51 #include <mongoc.h>
52 `}
53
54 # Everything inside MongoDB is manipulated as BSON Objects.
55 #
56 # See:
57 # * [Binary JSON spec](http://bsonspec.org/)
58 # * [Libbson](http://api.mongodb.org/libbson/1.1.4/)#
59 private class BSON
60 super Finalizable
61
62 # Native instance pointer.
63 var native: NativeBSON
64
65 # Is the native instance valid?
66 #
67 # This is set to false if the `native` is destroyed.
68 var is_alive = true
69
70 # Returns a new BSON object initialized from the content of `json`.
71 #
72 # ~~~
73 # intrude import mongodb
74 # var obj = new JsonObject
75 # obj["age"] = 10
76 # obj["name"] = "Rick"
77 # obj["ELS"] = new JsonArray
78 # var bson = new BSON.from_json(obj)
79 # assert bson.to_s == """{ "age" : 10, "name" : "Rick", "ELS" : [ ] }"""
80 # ~~~
81 init from_json(json: JsonObject) do
82 init(new NativeBSON.from_json_string(json.to_json.to_cstring))
83 end
84
85 # Returns a new BSON object parsed from `json_string`.
86 #
87 # If `json_string` is not a valid JSON string, this initializer returns NULL.
88 #
89 # ~~~
90 # intrude import mongodb
91 # var str = """{ "age" : 10, "name" : "Rick", "ELS" : [ ] }"""
92 # var bson = new BSON.from_json_string(str)
93 # assert bson.to_s == str
94 # ~~~
95 init from_json_string(json_string: String) do
96 init(new NativeBSON.from_json_string(json_string.to_cstring))
97 end
98
99 redef fun to_s do
100 assert is_alive
101 var ns = native.to_native_string
102 var res = ns.to_s_with_copy
103 ns.free # manual free of gc allocated NativeString
104 return res
105 end
106
107 # Returns a new JsonObject from `self`.
108 #
109 # ~~~
110 # intrude import mongodb
111 # var str = """{ "age" : 10, "name" : "Rick", "ELS" : [ ] }"""
112 # var bson = new BSON.from_json_string(str)
113 # var json = bson.to_json
114 # assert json["age"] == 10
115 # assert json["name"] == "Rick"
116 # assert json["ELS"].as(JsonArray).is_empty
117 # ~~~
118 fun to_json: JsonObject do
119 assert is_alive
120 return to_s.parse_json.as(JsonObject)
121 end
122
123 redef fun finalize do
124 if is_alive then
125 native.destroy
126 is_alive = false
127 end
128 end
129 end
130
131 redef class JsonObject
132 # Inits `self` from a BSON object.
133 private init from_bson(bson: BSON) do recover_with(bson.to_json)
134
135 # Returns a new BSON object from `self`.
136 private fun to_bson: BSON do return new BSON.from_json(self)
137 end
138
139 # An error returned by the mongoc client.
140 #
141 # Within the client, if a method returns `false` or `null` it's more likely that
142 # an error occured during the execution.
143 #
144 # See `MongoClient::last_error`.
145 class MongoError
146
147 private var native: BSONError
148
149 # Is the native instance valid?
150 #
151 # This is set to false if the `native` is destroyed.
152 private var is_alive = true
153
154 # Logical domain within a library that created the error.
155 fun domain: Int do
156 assert is_alive
157 return native.domain
158 end
159
160 # Domain specific error code.
161 fun code: Int do
162 assert is_alive
163 return native.code
164 end
165
166 # Human readable error message.
167 fun message: String do
168 assert is_alive
169 var ns = native.message
170 var res = ns.to_s_with_copy
171 ns.free
172 return res
173 end
174
175 redef fun to_s do return "{message} (code: {code})"
176 end
177
178 # The MongoClient is used to connect to the mongo server and send queries.
179 #
180 # Usage:
181 #
182 # ~~~
183 # var uri = "mongodb://localhost:27017/"
184 # var client = new MongoClient(uri)
185 # assert client.server_uri == uri
186 # ~~~
187 class MongoClient
188 super Finalizable
189
190 # Server URI.
191 var server_uri: String
192
193 private var native: NativeMongoClient is noinit
194
195 # Is the native instance valid?
196 #
197 # This is set to false if the `native` is destroyed.
198 private var is_alive = true
199
200 init do
201 native = new NativeMongoClient(server_uri.to_cstring)
202 end
203
204 # Gets server data.
205 #
206 # Returns `null` if an error occured. See `last_error`.
207 #
208 # ~~~
209 # var client = new MongoClient("mongodb://localhost:27017/")
210 # assert client.server_status["process"] == "mongod"
211 # ~~~
212 fun server_status: nullable JsonObject do
213 assert is_alive
214 var nbson = native.server_status
215 if nbson == null then return null
216 var bson = new BSON(nbson)
217 var res = new JsonObject.from_bson(bson)
218 return res
219 end
220
221 # Lists available database names.
222 #
223 # ~~~
224 # var client = new MongoClient("mongodb://localhost:27017/")
225 # var db = client.database("test")
226 # db.collection("test").insert(new JsonObject)
227 # assert client.database_names.has("test")
228 # ~~~
229 fun database_names: Array[String] do
230 assert is_alive
231 var res = new Array[String]
232 var nas = native.database_names
233 var i = 0
234 var name = nas[i]
235 while not name.address_is_null do
236 res.add name.to_s_with_copy
237 name.free
238 i += 1
239 name = nas[i]
240 end
241 return res
242 end
243
244 # Loads or creates a database from its `name`.
245 #
246 # Database are automatically created on the MongoDB server upon insertion of
247 # the first document into a collection.
248 # There is no need to create a database manually.
249 #
250 # ~~~
251 # var client = new MongoClient("mongodb://localhost:27017/")
252 # assert client.database("test").name == "test"
253 # ~~~
254 fun database(name: String): MongoDb do
255 assert is_alive
256 return new MongoDb(self, name)
257 end
258
259 # Close the connexion and destroy the instance.
260 #
261 # The reference should not be used beyond this point!
262 fun close do
263 assert is_alive
264 finalize
265 end
266
267 redef fun finalize do
268 if is_alive then
269 native.destroy
270 is_alive = false
271 end
272 end
273
274 # Last error raised by mongoc.
275 fun last_error: nullable MongoError do
276 var last_error = sys.last_mongoc_error
277 if last_error == null then return null
278 return new MongoError(last_error)
279 end
280 end
281
282 # A MongoDb database.
283 #
284 # Database are automatically created on the MongoDB server upon insertion of the
285 # first document into a collection.
286 # There is no need to create a database manually.
287 class MongoDb
288 super Finalizable
289
290 # `MongoClient` used to load this database.
291 var client: MongoClient
292
293 # The database name.
294 var name: String
295
296 private var native: NativeMongoDb is noinit
297
298 # Is the native instance valid?
299 #
300 # This is set to false if the `native` is destroyed.
301 private var is_alive = true
302
303 init do
304 native = new NativeMongoDb(client.native, name.to_cstring)
305 end
306
307 # Lists available collection names.
308 #
309 # Returns `null` if an error occured. See `Sys::last_mongoc_error`.
310 #
311 # ~~~
312 # var client = new MongoClient("mongodb://localhost:27017/")
313 # var db = client.database("test")
314 # db.collection("test").insert(new JsonObject)
315 # assert db.collection_names.has("test")
316 # ~~~
317 fun collection_names: Array[String] do
318 assert is_alive
319 var res = new Array[String]
320 var nas = native.collection_names
321 var i = 0
322 var name = nas[i]
323 while not name.address_is_null do
324 res.add name.to_s_with_copy
325 name.free
326 i += 1
327 name = nas[i]
328 end
329 return res
330 end
331
332 # Loads or creates a collection by its `name`.
333 #
334 # ~~~
335 # var client = new MongoClient("mongodb://localhost:27017/")
336 # var db = client.database("test")
337 # var col = db.collection("test")
338 # assert col.name == "test"
339 # ~~~
340 fun collection(name: String): MongoCollection do
341 assert is_alive
342 return new MongoCollection(self, name)
343 end
344
345 # Checks if a collection named `name` exists.
346 #
347 # ~~~
348 # var client = new MongoClient("mongodb://localhost:27017/")
349 # var db = client.database("test")
350 # assert not db.has_collection("qwerty")
351 # ~~~
352 fun has_collection(name: String): Bool do
353 assert is_alive
354 # TODO handle error
355 return native.has_collection(name.to_cstring)
356 end
357
358 # Drop `self`, returns false if an error occured.
359 fun drop: Bool do
360 assert is_alive
361 return native.drop
362 end
363
364 redef fun finalize do
365 if is_alive then
366 native.destroy
367 is_alive = false
368 end
369 end
370 end
371
372 # A Mongo collection.
373 #
374 # Collections are automatically created on the MongoDB server upon insertion of
375 # the first document.
376 # There is no need to create a database manually.
377 class MongoCollection
378 super Finalizable
379
380 # Database that collection belongs to.
381 var database: MongoDb
382
383 # Name of this collection.
384 var name: String
385
386 private var native: NativeMongoCollection is noinit
387
388 # Is the native instance valid?
389 #
390 # This is set to false if the `native` is destroyed.
391 private var is_alive = true
392
393 # Loads a collection.
394 #
395 # Call `MongoDb::collection` instead.
396 init do
397 native = new NativeMongoCollection(
398 database.client.native,
399 database.name.to_cstring,
400 name.to_cstring)
401 end
402
403 # Inserts a new document in the collection.
404 #
405 # If no _id element is found in document, then a new one be generated locally
406 # and added to the document.
407 #
408 # Returns `false` if an error occured. See `Sys::last_mongoc_error`.
409 #
410 # ~~~
411 # var client = new MongoClient("mongodb://localhost:27017/")
412 # var col = client.database("test").collection("test")
413 # var doc = new JsonObject
414 # doc["foo"] = 10
415 # doc["bar"] = "bar"
416 # doc["baz"] = new JsonArray
417 # assert col.insert(doc)
418 # ~~~
419 fun insert(doc: JsonObject): Bool do
420 assert is_alive
421 return native.insert(doc.to_bson.native)
422 end
423
424 # Inserts multiple documents in the collection.
425 #
426 # See `insert`.
427 fun insert_all(docs: Collection[JsonObject]): Bool do
428 assert is_alive
429 var res = true
430 for doc in docs do res = insert(doc) and res
431 return res
432 end
433
434 # Saves a new document in the collection.
435 #
436 # If the document has an `_id` field it will be updated.
437 # Otherwise it will be inserted.
438 #
439 # Returns `false` if an error occured. See `Sys::last_mongoc_error`.
440 #
441 # ~~~
442 # var client = new MongoClient("mongodb://localhost:27017/")
443 # var col = client.database("test").collection("test")
444 # var doc = new JsonObject
445 # doc["foo"] = 10
446 # doc["bar"] = "bar"
447 # doc["baz"] = new JsonArray
448 # assert col.save(doc) # will be inserted
449 # ~~~
450 fun save(doc: JsonObject): Bool do
451 assert is_alive
452 return native.save(doc.to_bson.native)
453 end
454
455 # Removes the first document that matches `selector`.
456 #
457 # Returns `false` if an error occured. See `Sys::last_mongoc_error`.
458 #
459 # ~~~
460 # var client = new MongoClient("mongodb://localhost:27017/")
461 # var col = client.database("test").collection("test")
462 # var sel = new JsonObject
463 # sel["foo"] = 10
464 # assert col.remove(sel)
465 # ~~~
466 fun remove(selector: JsonObject): Bool do
467 assert is_alive
468 return native.remove(selector.to_bson.native)
469 end
470
471 # Removes all the document that match `selector`.
472 #
473 # See `remove`.
474 fun remove_all(selector: JsonObject): Bool do
475 assert is_alive
476 return native.remove_all(selector.to_bson.native)
477 end
478
479 # Updates a document already existing in the collection.
480 #
481 # No upsert is done, see `save` instead.
482 #
483 # ~~~
484 # var client = new MongoClient("mongodb://localhost:27017/")
485 # var col = client.database("test").collection("test")
486 # var sel = new JsonObject
487 # sel["foo"] = 10
488 # var upd = new JsonObject
489 # upd["bar"] = "BAR"
490 # assert col.update(sel, upd)
491 # ~~~
492 fun update(selector: JsonObject, update: JsonObject): Bool do
493 assert is_alive
494 return native.update(
495 selector.to_bson.native,
496 update.to_bson.native)
497 end
498
499 # Updates all documents matching the `selector`.
500 #
501 # See `update`.
502 fun update_all(selector: JsonObject, update: JsonObject): Bool do
503 return native.update_all(
504 selector.to_bson.native,
505 update.to_bson.native)
506 end
507
508 # Counts the document matching `query`.
509 #
510 # Returns `-1` if an error occured. See `Sys::last_mongoc_error`.
511 #
512 # ~~~
513 # var client = new MongoClient("mongodb://localhost:27017/")
514 # var col = client.database("test").collection("test")
515 # var query = new JsonObject
516 # query["foo"] = 10
517 # assert col.count(query) > 0
518 # ~~~
519 fun count(query: JsonObject): Int do
520 assert is_alive
521 return native.count(query.to_bson.native)
522 end
523
524 # Finds the first document that matches `query`.
525 #
526 # Returns `null` if an error occured. See `Sys::last_mongoc_error`.
527 #
528 # ~~~
529 # var client = new MongoClient("mongodb://localhost:27017/")
530 # var col = client.database("test").collection("test")
531 # var query = new JsonObject
532 # query["foo"] = 10
533 # var doc = col.find(query)
534 # print doc or else "nullllllllllllllllll"
535 # print doc.to_json
536 # assert doc["foo"] == 10
537 # ~~~
538 fun find(query: JsonObject): nullable JsonObject do
539 assert is_alive
540 var c = native.find(query.to_bson.native)
541 if c == null then return null
542 var cursor = new MongoCursor(c)
543 if cursor.is_ok then
544 cursor.next
545 return cursor.item
546 end
547 return null
548 end
549
550 # Finds all the documents matching the `query`.
551 #
552 # ~~~
553 # var client = new MongoClient("mongodb://localhost:27017/")
554 # var col = client.database("test").collection("test")
555 # var query = new JsonObject
556 # query["foo"] = 10
557 # assert col.find_all(query).length > 0
558 # ~~~
559 fun find_all(query: JsonObject): Array[JsonObject] do
560 assert is_alive
561 var res = new Array[JsonObject]
562 var c = native.find(query.to_bson.native)
563 if c == null then return res
564 var cursor = new MongoCursor(c)
565 for item in cursor do res.add item
566 return res
567 end
568
569 # Retrieves statistics about the collection.
570 #
571 # Returns `null` if an error occured. See `Sys::last_mongoc_error`.
572 #
573 # ~~~
574 # var client = new MongoClient("mongodb://localhost:27017/")
575 # var col = client.database("test").collection("test")
576 # assert col.stats["ns"] == "test.test"
577 # ~~~
578 fun stats: nullable JsonObject do
579 assert is_alive
580 var bson = native.stats
581 if bson == null then return null
582 return new JsonObject.from_bson(new BSON(bson))
583 end
584
585 # Drops `self`, returns false if an error occured.
586 fun drop: Bool do
587 assert is_alive
588 return native.drop
589 end
590
591 # Moves `self` to another `database`.
592 #
593 # The database will also be updated internally so it is safe to continue using
594 # this collection after the move.
595 # Additional operations will occur on moved collection.
596 fun move(database: MongoDb): Bool do
597 assert is_alive
598 self.database = database
599 return native.rename(database.name.to_cstring, name.to_cstring)
600 end
601
602 # Renames `self`.
603 #
604 # The name of the collection will also be updated internally so it is safe
605 # to continue using this collection after the rename.
606 # Additional operations will occur on renamed collection.
607 fun rename(name: String): Bool do
608 assert is_alive
609 self.name = name
610 return native.rename(database.name.to_cstring, name.to_cstring)
611 end
612
613 redef fun finalize do
614 if is_alive then
615 native.destroy
616 is_alive = false
617 end
618 end
619 end
620
621 # A MongoDB query cursor.
622 #
623 # It wraps up the wire protocol negotation required to initiate a query and
624 # retreive an unknown number of documents.
625 class MongoCursor
626 super Finalizable
627 super Iterator[JsonObject]
628
629 private var native: NativeMongoCursor
630
631 # Is the native instance valid?
632 #
633 # This is set to false if the `native` is destroyed.
634 private var is_alive = true
635
636 init do next
637
638 redef fun is_ok do
639 assert is_alive
640 return native.more
641 end
642
643 redef fun next do
644 assert is_alive
645 native.next
646 end
647
648 redef fun item do
649 assert is_alive
650 return new JsonObject.from_bson(new BSON(native.current))
651 end
652
653 redef fun finalize do
654 if is_alive then
655 native.destroy
656 is_alive = false
657 end
658 end
659 end