Native MongoDB driver for Swift, written in Swift. This library does not wrap around the mongoc driver. It uses:
- Strand for fetching server responses without blocking the thread
- Our own BSON library, which is also 100% native Swift
- Our own MD5, SHA1 and SCRAM libraries in 100% Swift
This can be found here.
We have our own Dash documentation for MongoKitten which can be found in the top-right corner of the Documentation.
- A MongoDB server
- Some basic knowledge of MongoDB or time to research about MongoDB
- The swift version described in
.swift-version
, see swiftenv.
We don't support any other version of swift with the constantly changing syntax. This required swift version changes constantly with newer versions of MongoKitten
and it's recommended to pin down the version in SPM.
Note: other versions of swift
and MongoDB
may or may not work. We do not support them.
The unit tests expect a test database. Run the Tools/testprep.sh script to import it.
Add MongoKitten
to your Package.swift:
import PackageDescription
let package = Package(
name: "MyApp",
dependencies: [
.Package(url: "https://github.com/OpenKitten/MongoKitten.git", majorVersion: 1, minor: 6)
]
)
Add MongoKitten
to your Cartfile:
github "OpenKitten/MongoKitten" ~> 1.1
For further instructions, see the Carthage readme.
Note that the tutorial below is written for Swift 3.0. Syntax may be a bit different.
Import the MongoKitten library:
import MongoKitten
Connect to your local MongoDB server using an URI:
let server: Server!
do {
server = try Server("mongodb://username:password@localhost:27017", automatically: true)
} catch {
// Unable to connect
print("MongoDB is not available on the given host and port")
}
Select a database to use for your application:
let database = server["mydatabase"]
And select your collections to use from the database:
let userCollection = database["users"]
let otherCollection = database["otherdata"]
In MongoKitten
we use our own BSON
library for working with MongoDB Documents.
You can create a simple user document like this:
var userDocument: Document = [
"username": "Joannis",
"password": "myPassword",
"age": 19,
"male": true
]
If you want to embed a variable you'll need to use the ~
prefix operator.
let niceBoolean = true
let testDocument: Document = [
"example": "data",
"userDocument": ~userDocument,
"niceBoolean": ~niceBoolean,
"embeddedDocument": [
"name": "Henk",
"male": false,
"age": 12,
"pets": ["dog", "dog", "cat", "cat"]
]
]
A Document is similar to a Dictionary. A document however has order and thus the position of elements doesn't change unless you tell it to.
A Document is therefore an array and a dictionary at the same time. With the minor difference that a Document can only hold BSON's Value
. The problem that arises it when you want to use native types from Swift like a String, Int or another Document (sub-document) and elements in there.
We fixed this with the use of subscripts and getters.
To get a value from the Document you can subscript it like this:
let username: Value = userDocument["username"]
Documents always return a value. When the value doesn't exist we'll return Value.nothing
.
If you want to get a specific value from the Document like a String we can return an optional String like this:
let username: String? = userDocument["username"].stringValue
However.. for an age you might want a String without receiving nil
in a case like this:
let age: String? = userDocument["age"].stringValue
We made this easier by converting it for you:
let age: String = userDocument["age"].string
However.. if the age would normally be .nothing
we'll now return an empty string ""
instead. So check for that!
Last but not least we'll also want to assign data using a subscript. Because subscript are prone to being ambiguous we had to use enums for assignment.
This would result in this:
userDocument["bool"] = .boolean(true)
userDocument["int32"] = .int32(10)
userDocument["int64"] = .int64(200)
userDocument["array"] = .array(["one", 2, "three"])
userDocument["binary"] = .binary(subtype: .generic, data: [0x00, 0x01, 0x02, 0x03, 0x04])
userDocument["date"] = .dateTime(NSDate())
userDocument["null"] = .null
userDocument["string"] = .string("hello")
userDocument["objectID"] = .objectId(try ObjectId("507f1f77bcf86cd799439011"))
Of course variables can still use the ~
operator:
let trueBool = true
userDocument["newBool"] = ~trueBool
Using the above document you can insert the data in the collection.
try userCollection.insert(userDocument)
In the collection's insert method you can also insert a group of Documents: [Document]
try otherCollection.insert([testDocument, testDocument, testDocument])
To find the Documents in the collection we'll want to use find
or findOne
on the collection. This returns a "cursor".
The find
and findOne
functions are used on a collection and don't require any parameters.
Adding parameters, however, helps finding the data you need. By providing no arguments we're selecting all data in the collection.
let resultUsers = try userCollection.find()
This returns a cursor that you can use to loop over users. MongoKitten
's Cursor
by default loads 10 Documents at a time from MongoDB
which is customizable to a bigger or smaller amount of Documents.
This allows us to provide a smaller delay when looping over data. This also allows the application to remove the cursor halfway through the Documents without downloading Documents that aren't being used.
Looping over the above results is easy:
for userDocument in resultUsers {
print(userDocument)
if userDocument["username"].stringValue == "harriebob" {
print(userDocument)
}
}
If you do want all Documents in one array you can use Array()
.
let otherResultUsers = try userCollection.find()
let allUserDocuments = Array(otherResultUsers)
But be careful.. a cursor contains the data only once.
let depletedExample = try userCollection.find()
// Contains data
let allUserDocuments = Array(depletedExample)
// Doesn't contain data
let noUserDocuments = Array(depletedExample)
We also have a query builder which can be easily used to create filters when searching for Documents.
let q: Query = "username" == "Joannis" && "age" > 18
let result = try userCollection.findOne(matching: q)
Or simpler:
let newResult = try userCollection.findOne(matching: "username" == "Joannis" && "age" > 18)
This comes in handy when looping over data:
for user in try userCollection.find(matching: "male" == true) {
print(user["username"].string)
}
Updating data is simple too. There is a multiple
argument for people who update more than one document at a time. This example only updates the first match:
try userCollection.update(matching: ["username": "Joannis"], to: ["username": "Robbert"])
Deleting is possible using a document and a query
// Delete using a document
try userCollection.remove(matching: ["username": "Robbert"])
// Make a GridFS Collection within the database 'mydatabase'
let gridFS = GridFS(in: server["mydatabase"])
// Find all bytes corresponding to this image
let data = NSData(contentsOfFile: "./myimage.jpg")!
// Store the file in GridFS with maximum 10000 bytes per chunk (255000 is the recommended default) and doesn't need to be set
// Store the ObjectID corresponding to the file in a constant variable
let objectID = try gridFS.store(data: data, named "myimage.jpg", withType: "image/jpeg", inChunksOf: 10000)
// Retrieve the file from GridFS
let file = try gridFS.findOne(byID: objectID)
// Get the bytes we need
let myImageData: [Byte] = file!.read(from: 1024, to: 1234)
Imagine running a video streaming site. One of your users uploads a video. This will be stored in GridFS within 255000-byte chunks.
Now one user starts watching the video. You'll load the video chunk-by-chunk without keeping all of the video's buffer in memory.
The user quits the video about 40% through the video. Let's say chunk 58 of 144 of your video. Now you'll want to start continuing the video where it left off without receving all the unnecessary chunks.
MongoKitten is licensed under the MIT license.
MongoKitten uses parts of CryptoSwift whose license is included below. Parts where CryptoSwift code is used have this license included in the file
Copyright (C) 2014 Marcin Krzyżanowski [email protected] This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required.
Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
This notice may not be removed or altered from any source or binary distribution.