# var res = col.find(query)
# assert res["bar"] == "bar"
# ~~~
-#
-# TODO Get last Object_ID
module mongodb
import json
# * [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`.
#
# ~~~
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
# assert json["ELS"].as(JsonArray).is_empty
# ~~~
fun to_json: JsonObject do
- assert is_alive
- return to_s.parse_json.as(JsonObject)
- end
-
- redef fun finalize do
- if is_alive then
- native.destroy
- is_alive = false
+ var json = to_s.parse_json
+ if json isa JsonParseError then
+ print json.message
+ sys.exit 1
end
+ return json.as(JsonObject)
end
+
+ redef fun finalize_once do native.destroy
end
redef class JsonObject
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
ns.free
redef fun to_s do return "{message} (code: {code})"
end
+# MongoDB Object ID representation.
+#
+# For ObjectIDs, MongoDB uses the `ObjectId("hash")` notation.
+# This notation is replicated by the `to_s` service.
+#
+# 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
+
+ var native: BSONObjectId
+
+ # The unique ID as an MongoDB Object ID string.
+ fun id: String do return native.id
+
+ # Internal JSON representation of this Object ID.
+ #
+ # Something like `{"$oid": "5578e5dcf344225cc2378051"}`.
+ fun to_json: JsonObject do
+ var obj = new JsonObject
+ obj["$oid"] = id
+ return obj
+ end
+
+ # Formatted as `ObjectId("5578e5dcf344225cc2378051")`
+ redef fun to_s do return "ObjectId({id})"
+end
+
# The MongoClient is used to connect to the mongo server and send queries.
#
# Usage:
# 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.
#
# 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)
# assert client.database_names.has("test")
# ~~~
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
# var client = new MongoClient("mongodb://localhost:27017/")
# assert client.database("test").name == "test"
# ~~~
- 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
if last_error == null then return null
return new MongoError(last_error)
end
+
+ # Last auto generated id.
+ 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)
+ end
+
+ # Set the last generated id or `null` to unset once used.
+ private fun last_id=(id: nullable MongoObjectId) do
+ if id == null then
+ sys.last_mongoc_id = null
+ else
+ sys.last_mongoc_id = id.native
+ end
+ end
end
# A MongoDb database.
# 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
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.
#
# 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
# assert col.name == "test"
# ~~~
fun collection(name: String): MongoCollection do
- assert is_alive
return new MongoCollection(self, name)
end
# 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.
# 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
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.
name.to_cstring)
end
+ # Set the autogenerated last id if the `doc` does not contain one already.
+ private fun set_id(doc: JsonObject) do
+ var last_id = database.client.last_id
+ if last_id != null then
+ doc["_id"] = last_id.to_json
+ database.client.last_id = null
+ end
+ end
+
# Inserts a new document in the collection.
#
# If no _id element is found in document, then a new one be generated locally
# doc["bar"] = "bar"
# doc["baz"] = new JsonArray
# assert col.insert(doc)
+ # assert doc.has_key("_id")
# ~~~
fun insert(doc: JsonObject): Bool do
- assert is_alive
- return native.insert(doc.to_bson.native)
+ var res = native.insert(doc.to_bson.native)
+ if res then set_id(doc)
+ return res
end
# Inserts multiple documents in the collection.
#
# 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
# ~~~
# var client = new MongoClient("mongodb://localhost:27017/")
# var col = client.database("test").collection("test")
+ #
# var doc = new JsonObject
# doc["foo"] = 10
# doc["bar"] = "bar"
# doc["baz"] = new JsonArray
+ #
# assert col.save(doc) # will be inserted
+ # assert doc.has_key("_id")
+ #
+ # var id = doc["_id"]
+ # assert col.save(doc) # will be updated
+ # assert doc["_id"] == id
# ~~~
fun save(doc: JsonObject): Bool do
- assert is_alive
- return native.save(doc.to_bson.native)
+ var bson = doc.to_bson
+ var nat = bson.native
+ var res = native.save(nat)
+ if res then set_id(doc)
+ assert nat != self #FIXME used to avoid GC crashes
+ assert bson != self #FIXME used to avoid GC crashes
+ return res
end
# Removes the first document that matches `selector`.
# assert col.remove(sel)
# ~~~
fun remove(selector: JsonObject): Bool do
- assert is_alive
return native.remove(selector.to_bson.native)
end
#
# See `remove`.
fun remove_all(selector: JsonObject): Bool do
- assert is_alive
return native.remove_all(selector.to_bson.native)
end
# 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)
# assert col.count(query) > 0
# ~~~
fun count(query: JsonObject): Int do
- assert is_alive
return native.count(query.to_bson.native)
end
# Finds the first document that matches `query`.
#
+ # Params:
+ # * `skip` number of documents to skip
+ # * `limit` number of documents to return
+ #
# Returns `null` if an error occured. See `Sys::last_mongoc_error`.
#
# ~~~
# var doc = col.find(query)
# assert doc["foo"] == 10
# ~~~
- fun find(query: JsonObject): nullable JsonObject do
- assert is_alive
- var c = native.find(query.to_bson.native)
+ fun find(query: JsonObject, skip, limit: nullable Int): nullable JsonObject do
+ var q = new NativeBSON.from_json_string(query.to_json.to_cstring)
+ var s = skip or else 0
+ var l = limit or else 0
+ var c = native.find(q, s, l)
+ q.destroy
if c == null then return null
var cursor = new MongoCursor(c)
- if cursor.is_ok then
- cursor.next
- return cursor.item
+ if not cursor.is_ok then
+ return null
end
- return null
+ var item = cursor.item
+ assert cursor != self
+ return item
end
# Finds all the documents matching the `query`.
#
+ # Params:
+ # * `skip` number of documents to skip
+ # * `limit` number of documents to return
+ #
# ~~~
# var client = new MongoClient("mongodb://localhost:27017/")
# var col = client.database("test").collection("test")
# query["foo"] = 10
# assert col.find_all(query).length > 0
# ~~~
- fun find_all(query: JsonObject): Array[JsonObject] do
- assert is_alive
+ fun find_all(query: JsonObject, skip, limit: nullable Int): Array[JsonObject] do
+ 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)
+ 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
# assert col.stats["ns"] == "test.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`.
#
# 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
# 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.
# 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