X-Git-Url: http://nitlanguage.org diff --git a/lib/mongodb/mongodb.nit b/lib/mongodb/mongodb.nit index 64e68a0..7db69e7 100644 --- a/lib/mongodb/mongodb.nit +++ b/lib/mongodb/mongodb.nit @@ -22,10 +22,15 @@ # # ~~~ # # Opens the connexion with the Mongo server. -# var client = new MongoClient("mongodb://localhost:27017/") +# var client = new MongoClient("mongodb://mongo:27017/") +# +# # Select the database. +# var db_suffix = "NIT_TESTING_ID".environ +# var db_name = "test_{db_suffix}" +# var db = client.database(db_name) # # # Retrieve a collection. -# var col = client.database("test").collection("test") +# var col = db.collection("test") # # # Insert a document in the collection. # var doc = new JsonObject @@ -42,6 +47,7 @@ # ~~~ module mongodb +import json::static import json private import native_mongodb @@ -55,16 +61,11 @@ in "C header" `{ # * [Binary JSON spec](http://bsonspec.org/) # * [Libbson](http://api.mongodb.org/libbson/1.1.4/)# private class BSON - super Finalizable + super FinalizableOnce # Native instance pointer. var native: NativeBSON - # Is the native instance valid? - # - # This is set to false if the `native` is destroyed. - var is_alive = true - # Returns a new BSON object initialized from the content of `json`. # # ~~~ @@ -95,10 +96,9 @@ private class BSON end redef fun to_s do - assert is_alive - var ns = native.to_native_string - var res = ns.to_s_with_copy - ns.free # manual free of gc allocated NativeString + var ns = native.to_c_string + var res = ns.to_s + ns.free # manual free of gc allocated CString return res end @@ -114,27 +114,20 @@ private class BSON # assert json["ELS"].as(JsonArray).is_empty # ~~~ fun to_json: JsonObject do - assert is_alive var json = to_s.parse_json if json isa JsonParseError then - print to_s print json.message sys.exit 1 end return json.as(JsonObject) end - redef fun finalize do - if is_alive then - native.destroy - is_alive = false - end - end + redef fun finalize_once do native.destroy end redef class JsonObject # Inits `self` from a BSON object. - private init from_bson(bson: BSON) do recover_with(bson.to_json) + private init from_bson(bson: BSON) do add_all(bson.to_json) # Returns a new BSON object from `self`. private fun to_bson: BSON do return new BSON.from_json(self) @@ -150,28 +143,16 @@ class MongoError private var native: BSONError - # Is the native instance valid? - # - # This is set to false if the `native` is destroyed. - private var is_alive = true - # Logical domain within a library that created the error. - fun domain: Int do - assert is_alive - return native.domain - end + fun domain: Int do return native.domain # Domain specific error code. - fun code: Int do - assert is_alive - return native.code - end + fun code: Int do return native.code # Human readable error message. fun message: String do - assert is_alive var ns = native.message - var res = ns.to_s_with_copy + var res = ns.to_s ns.free return res end @@ -187,9 +168,13 @@ end # Since the MongoDB notation is not JSON complient, the mongoc wrapper uses # a JSON based notation like `{"$oid": "hash"}`. # This is the notation returned by the `to_json` service. -private class MongoObjectId +class MongoObjectId + + private var native: BSONObjectId = new BSONObjectId - var native: BSONObjectId + private init with_native(native: BSONObjectId) do + self.native = native + end # The unique ID as an MongoDB Object ID string. fun id: String do return native.id @@ -212,37 +197,29 @@ end # Usage: # # ~~~ -# var uri = "mongodb://localhost:27017/" +# var uri = "mongodb://mongo:27017/" # var client = new MongoClient(uri) # assert client.server_uri == uri # ~~~ class MongoClient - super Finalizable + super FinalizableOnce # Server URI. var server_uri: String private var native: NativeMongoClient is noinit - # Is the native instance valid? - # - # This is set to false if the `native` is destroyed. - private var is_alive = true - - init do - native = new NativeMongoClient(server_uri.to_cstring) - end + init do native = new NativeMongoClient(server_uri.to_cstring) # Gets server data. # # Returns `null` if an error occured. See `last_error`. # # ~~~ - # var client = new MongoClient("mongodb://localhost:27017/") + # var client = new MongoClient("mongodb://mongo:27017/") # assert client.server_status["process"] == "mongod" # ~~~ fun server_status: nullable JsonObject do - assert is_alive var nbson = native.server_status if nbson == null then return null var bson = new BSON(nbson) @@ -253,20 +230,21 @@ class MongoClient # Lists available database names. # # ~~~ - # var client = new MongoClient("mongodb://localhost:27017/") - # var db = client.database("test") + # var client = new MongoClient("mongodb://mongo:27017/") + # var db_suffix = "NIT_TESTING_ID".environ + # var db_name = "test_{db_suffix}" + # var db = client.database(db_name) # db.collection("test").insert(new JsonObject) - # assert client.database_names.has("test") + # assert client.database_names.has(db_name) # ~~~ fun database_names: Array[String] do - assert is_alive var res = new Array[String] var nas = native.database_names if nas == null then return res var i = 0 var name = nas[i] while not name.address_is_null do - res.add name.to_s_with_copy + res.add name.to_s name.free i += 1 name = nas[i] @@ -281,28 +259,20 @@ class MongoClient # There is no need to create a database manually. # # ~~~ - # var client = new MongoClient("mongodb://localhost:27017/") - # assert client.database("test").name == "test" + # var client = new MongoClient("mongodb://mongo:27017/") + # var db_suffix = "NIT_TESTING_ID".environ + # var db_name = "test_{db_suffix}" + # var db = client.database(db_name) + # assert db.name == db_name # ~~~ - fun database(name: String): MongoDb do - assert is_alive - return new MongoDb(self, name) - end + fun database(name: String): MongoDb do return new MongoDb(self, name) # Close the connexion and destroy the instance. # # The reference should not be used beyond this point! - fun close do - assert is_alive - finalize - end + fun close do finalize_once - redef fun finalize do - if is_alive then - native.destroy - is_alive = false - end - end + redef fun finalize_once do native.destroy # Last error raised by mongoc. fun last_error: nullable MongoError do @@ -315,7 +285,7 @@ class MongoClient private fun last_id: nullable MongoObjectId do var last_id = sys.last_mongoc_id if last_id == null then return null - return new MongoObjectId(last_id) + return new MongoObjectId.with_native(last_id) end # Set the last generated id or `null` to unset once used. @@ -334,7 +304,7 @@ end # first document into a collection. # There is no need to create a database manually. class MongoDb - super Finalizable + super FinalizableOnce # `MongoClient` used to load this database. var client: MongoClient @@ -344,34 +314,28 @@ class MongoDb private var native: NativeMongoDb is noinit - # Is the native instance valid? - # - # This is set to false if the `native` is destroyed. - private var is_alive = true - - init do - native = new NativeMongoDb(client.native, name.to_cstring) - end + init do native = new NativeMongoDb(client.native, name.to_cstring) # Lists available collection names. # # Returns `null` if an error occured. See `Sys::last_mongoc_error`. # # ~~~ - # var client = new MongoClient("mongodb://localhost:27017/") - # var db = client.database("test") + # var client = new MongoClient("mongodb://mongo:27017/") + # var db_suffix = "NIT_TESTING_ID".environ + # var db_name = "test_{db_suffix}" + # var db = client.database(db_name) # db.collection("test").insert(new JsonObject) # assert db.collection_names.has("test") # ~~~ fun collection_names: Array[String] do - assert is_alive var res = new Array[String] var nas = native.collection_names if nas == null then return res var i = 0 var name = nas[i] while not name.address_is_null do - res.add name.to_s_with_copy + res.add name.to_s name.free i += 1 name = nas[i] @@ -382,41 +346,35 @@ class MongoDb # Loads or creates a collection by its `name`. # # ~~~ - # var client = new MongoClient("mongodb://localhost:27017/") - # var db = client.database("test") + # var client = new MongoClient("mongodb://mongo:27017/") + # var db_suffix = "NIT_TESTING_ID".environ + # var db_name = "test_{db_suffix}" + # var db = client.database(db_name) # var col = db.collection("test") # assert col.name == "test" # ~~~ fun collection(name: String): MongoCollection do - assert is_alive return new MongoCollection(self, name) end # Checks if a collection named `name` exists. # # ~~~ - # var client = new MongoClient("mongodb://localhost:27017/") - # var db = client.database("test") + # var client = new MongoClient("mongodb://mongo:27017/") + # var db_suffix = "NIT_TESTING_ID".environ + # var db_name = "test_{db_suffix}" + # var db = client.database(db_name) # assert not db.has_collection("qwerty") # ~~~ fun has_collection(name: String): Bool do - assert is_alive # TODO handle error return native.has_collection(name.to_cstring) end # Drop `self`, returns false if an error occured. - fun drop: Bool do - assert is_alive - return native.drop - end + fun drop: Bool do return native.drop - redef fun finalize do - if is_alive then - native.destroy - is_alive = false - end - end + redef fun finalize_once do native.destroy end # A Mongo collection. @@ -425,7 +383,7 @@ end # the first document. # There is no need to create a database manually. class MongoCollection - super Finalizable + super FinalizableOnce # Database that collection belongs to. var database: MongoDb @@ -435,11 +393,6 @@ class MongoCollection private var native: NativeMongoCollection is noinit - # Is the native instance valid? - # - # This is set to false if the `native` is destroyed. - private var is_alive = true - # Loads a collection. # # Call `MongoDb::collection` instead. @@ -467,8 +420,11 @@ class MongoCollection # Returns `false` if an error occured. See `Sys::last_mongoc_error`. # # ~~~ - # var client = new MongoClient("mongodb://localhost:27017/") - # var col = client.database("test").collection("test") + # var client = new MongoClient("mongodb://mongo:27017/") + # var db_suffix = "NIT_TESTING_ID".environ + # var db_name = "test_{db_suffix}" + # var db = client.database(db_name) + # var col = db.collection("test") # var doc = new JsonObject # doc["foo"] = 10 # doc["bar"] = "bar" @@ -477,7 +433,6 @@ class MongoCollection # assert doc.has_key("_id") # ~~~ fun insert(doc: JsonObject): Bool do - assert is_alive var res = native.insert(doc.to_bson.native) if res then set_id(doc) return res @@ -487,7 +442,6 @@ class MongoCollection # # See `insert`. fun insert_all(docs: Collection[JsonObject]): Bool do - assert is_alive var res = true for doc in docs do res = insert(doc) and res return res @@ -501,8 +455,11 @@ class MongoCollection # Returns `false` if an error occured. See `Sys::last_mongoc_error`. # # ~~~ - # var client = new MongoClient("mongodb://localhost:27017/") - # var col = client.database("test").collection("test") + # var client = new MongoClient("mongodb://mongo:27017/") + # var db_suffix = "NIT_TESTING_ID".environ + # var db_name = "test_{db_suffix}" + # var db = client.database(db_name) + # var col = db.collection("test") # # var doc = new JsonObject # doc["foo"] = 10 @@ -517,7 +474,6 @@ class MongoCollection # assert doc["_id"] == id # ~~~ fun save(doc: JsonObject): Bool do - assert is_alive var bson = doc.to_bson var nat = bson.native var res = native.save(nat) @@ -532,14 +488,16 @@ class MongoCollection # Returns `false` if an error occured. See `Sys::last_mongoc_error`. # # ~~~ - # var client = new MongoClient("mongodb://localhost:27017/") - # var col = client.database("test").collection("test") + # var client = new MongoClient("mongodb://mongo:27017/") + # var db_suffix = "NIT_TESTING_ID".environ + # var db_name = "test_{db_suffix}" + # var db = client.database(db_name) + # var col = db.collection("test") # var sel = new JsonObject # sel["foo"] = 10 # assert col.remove(sel) # ~~~ fun remove(selector: JsonObject): Bool do - assert is_alive return native.remove(selector.to_bson.native) end @@ -547,7 +505,6 @@ class MongoCollection # # See `remove`. fun remove_all(selector: JsonObject): Bool do - assert is_alive return native.remove_all(selector.to_bson.native) end @@ -556,8 +513,11 @@ class MongoCollection # No upsert is done, see `save` instead. # # ~~~ - # var client = new MongoClient("mongodb://localhost:27017/") - # var col = client.database("test").collection("test") + # var client = new MongoClient("mongodb://mongo:27017/") + # var db_suffix = "NIT_TESTING_ID".environ + # var db_name = "test_{db_suffix}" + # var db = client.database(db_name) + # var col = db.collection("test") # var sel = new JsonObject # sel["foo"] = 10 # var upd = new JsonObject @@ -565,7 +525,6 @@ class MongoCollection # assert col.update(sel, upd) # ~~~ fun update(selector: JsonObject, update: JsonObject): Bool do - assert is_alive return native.update( selector.to_bson.native, update.to_bson.native) @@ -585,14 +544,16 @@ class MongoCollection # Returns `-1` if an error occured. See `Sys::last_mongoc_error`. # # ~~~ - # var client = new MongoClient("mongodb://localhost:27017/") - # var col = client.database("test").collection("test") + # var client = new MongoClient("mongodb://mongo:27017/") + # var db_suffix = "NIT_TESTING_ID".environ + # var db_name = "test_{db_suffix}" + # var db = client.database(db_name) + # var col = db.collection("test") # var query = new JsonObject # query["foo"] = 10 # assert col.count(query) > 0 # ~~~ fun count(query: JsonObject): Int do - assert is_alive return native.count(query.to_bson.native) end @@ -605,15 +566,17 @@ class MongoCollection # Returns `null` if an error occured. See `Sys::last_mongoc_error`. # # ~~~ - # var client = new MongoClient("mongodb://localhost:27017/") - # var col = client.database("test").collection("test") + # var client = new MongoClient("mongodb://mongo:27017/") + # var db_suffix = "NIT_TESTING_ID".environ + # var db_name = "test_{db_suffix}" + # var db = client.database(db_name) + # var col = db.collection("test") # var query = new JsonObject # query["foo"] = 10 # var doc = col.find(query) # assert doc["foo"] == 10 # ~~~ fun find(query: JsonObject, skip, limit: nullable Int): nullable JsonObject do - assert is_alive var q = new NativeBSON.from_json_string(query.to_json.to_cstring) var s = skip or else 0 var l = limit or else 0 @@ -636,21 +599,65 @@ class MongoCollection # * `limit` number of documents to return # # ~~~ - # var client = new MongoClient("mongodb://localhost:27017/") - # var col = client.database("test").collection("test") + # var client = new MongoClient("mongodb://mongo:27017/") + # var db_suffix = "NIT_TESTING_ID".environ + # var db_name = "test_{db_suffix}" + # var db = client.database(db_name) + # var col = db.collection("test") # var query = new JsonObject # query["foo"] = 10 # assert col.find_all(query).length > 0 # ~~~ fun find_all(query: JsonObject, skip, limit: nullable Int): Array[JsonObject] do - assert is_alive var s = skip or else 0 var l = limit or else 0 var res = new Array[JsonObject] var c = native.find(query.to_bson.native, s, l) if c == null then return res var cursor = new MongoCursor(c) - for item in cursor do res.add item + while cursor.is_ok do + res.add cursor.item + cursor.next + end + return res + end + + # Applies an aggregation `pipeline` over the collection. + # + # ~~~ + # var client = new MongoClient("mongodb://mongo:27017/") + # var db_suffix = "NIT_TESTING_ID".environ + # var db_name = "test_{db_suffix}" + # var db = client.database(db_name) + # var col = db.collection("test_aggregate") + # + # col.drop + # + # col.insert("""{ "cust_id": "A123", "amount": 500, "status": "A"}""".parse_json.as(JsonObject)) + # col.insert("""{ "cust_id": "A123", "amount": 250, "status": "A"}""".parse_json.as(JsonObject)) + # col.insert("""{ "cust_id": "B212", "amount": 200, "status": "A"}""".parse_json.as(JsonObject)) + # col.insert("""{ "cust_id": "A123", "amount": 300, "status": "D"}""".parse_json.as(JsonObject)) + # + # var res = col.aggregate("""[ + # { "$match": { "status": "A" } }, + # { "$group": { "_id": "$cust_id", "total": { "$sum": "$amount" } } }, + # { "$sort" : { "_id": 1 } } + # ]""".parse_json.as(JsonArray)) + # + # assert res[0].to_json == """{"_id":"A123","total":750}""" + # assert res[1].to_json == """{"_id":"B212","total":200}""" + # ~~~ + fun aggregate(pipeline: JsonArray): Array[JsonObject] do + var q = new JsonObject + q["pipeline"] = pipeline + var res = new Array[JsonObject] + var c = native.aggregate(q.to_bson.native) + if c == null then return res + var cursor = new MongoCursor(c) + while cursor.is_ok do + res.add cursor.item + cursor.next + end return res end @@ -659,22 +666,21 @@ class MongoCollection # Returns `null` if an error occured. See `Sys::last_mongoc_error`. # # ~~~ - # var client = new MongoClient("mongodb://localhost:27017/") - # var col = client.database("test").collection("test") - # assert col.stats["ns"] == "test.test" + # var client = new MongoClient("mongodb://mongo:27017/") + # var db_suffix = "NIT_TESTING_ID".environ + # var db_name = "test_{db_suffix}" + # var db = client.database(db_name) + # var col = db.collection("test") + # assert col.stats["ns"] == "{db_name}.test" # ~~~ fun stats: nullable JsonObject do - assert is_alive var bson = native.stats if bson == null then return null return new JsonObject.from_bson(new BSON(bson)) end # Drops `self`, returns false if an error occured. - fun drop: Bool do - assert is_alive - return native.drop - end + fun drop: Bool do return native.drop # Moves `self` to another `database`. # @@ -682,7 +688,6 @@ class MongoCollection # this collection after the move. # Additional operations will occur on moved collection. fun move(database: MongoDb): Bool do - assert is_alive self.database = database return native.rename(database.name.to_cstring, name.to_cstring) end @@ -693,17 +698,11 @@ class MongoCollection # to continue using this collection after the rename. # Additional operations will occur on renamed collection. fun rename(name: String): Bool do - assert is_alive self.name = name return native.rename(database.name.to_cstring, name.to_cstring) end - redef fun finalize do - if is_alive then - native.destroy - is_alive = false - end - end + redef fun finalize_once do native.destroy end # A MongoDB query cursor. @@ -711,37 +710,20 @@ end # It wraps up the wire protocol negotation required to initiate a query and # retreive an unknown number of documents. class MongoCursor - super Finalizable + super FinalizableOnce super Iterator[JsonObject] private var native: NativeMongoCursor - # Is the native instance valid? - # - # This is set to false if the `native` is destroyed. - private var is_alive = true - init do next - redef fun is_ok do - assert is_alive - return native.more - end + redef var is_ok = true - redef fun next do - assert is_alive - native.next - end + redef fun next do is_ok = native.next redef fun item do - assert is_alive return new JsonObject.from_bson(new BSON(native.current)) end - redef fun finalize do - if is_alive then - native.destroy - is_alive = false - end - end + redef fun finalize_once do native.destroy end