diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2bbea83 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,61 @@ +name: CI + +on: + push: + pull_request: + +env: + MONGOC_VERSION: 2.5.5 + +jobs: + linux: + name: Linux (Swift 6.4, MongoDB 8) + runs-on: ubuntu-24.04 + container: swift:6.4-noble + services: + mongo: + image: mongo:8 + env: + MONGODB_URI: mongodb://mongo:27017 + steps: + - uses: actions/checkout@v7 + + - name: Install build dependencies + run: | + apt-get update + apt-get install -y --no-install-recommends build-essential cmake curl pkg-config libssl-dev libsasl2-dev libzstd-dev + + - name: Cache libmongoc + id: mongoc-cache + uses: actions/cache@v6 + with: + path: /opt/mongoc + key: mongoc-${{ env.MONGOC_VERSION }}-noble + + - name: Build libmongoc + if: steps.mongoc-cache.outputs.cache-hit != 'true' + run: | + curl -fsSL https://github.com/mongodb/mongo-c-driver/releases/download/$MONGOC_VERSION/mongo-c-driver-$MONGOC_VERSION.tar.gz | tar xz + cmake -S mongo-c-driver-$MONGOC_VERSION -B mongoc-build \ + -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/opt/mongoc \ + -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF + cmake --build mongoc-build --parallel + cmake --install mongoc-build + + - name: Test + run: | + export PKG_CONFIG_PATH=$(dirname $(find /opt/mongoc -name mongoc2.pc)) + export LD_LIBRARY_PATH=$(dirname $(find /opt/mongoc -name 'libmongoc2.so' | head -1)) + swift test + + macos: + name: macOS (build + server-independent tests) + runs-on: macos-15 + steps: + - uses: actions/checkout@v7 + + - name: Install libmongoc + run: brew install mongo-c-driver + + - name: Build and run server-independent tests + run: swift test --filter "testBSON|testNewObjectId|Phase4Tests.test(Codable|Encodes|Decod|ObjectId)" diff --git a/.jazzy.yaml b/.jazzy.yaml deleted file mode 100644 index 9a18a52..0000000 --- a/.jazzy.yaml +++ /dev/null @@ -1,8 +0,0 @@ -module: PerfectMongoDB -author: PerfectlySoft -author_url: https://perfect.org -github_url: https://github.com/PerfectlySoft/Perfect-MongoDB -copyright: '© 2016 PerfectlySoft Inc. and the Perfect project authors' -theme: fullwidth -xcodebuild_arguments: [-target, MongoDB, -toolchain, org.swift.3020160620a] -clean: true diff --git a/Documentation/modernization-plan.md b/Documentation/modernization-plan.md new file mode 100644 index 0000000..f48ac6d --- /dev/null +++ b/Documentation/modernization-plan.md @@ -0,0 +1,123 @@ +# Perfect-MongoDB modernization plan + +Status: **Released as 4.0.0 on 2026-09-29.** Phases 1, 2 and 4 are done; the Atlas `mongodb+srv://`/TLS check was deferred. The pre-4.0 code is on the `legacy` branch. 36 tests pass against MongoDB 8.3 locally and on Linux (`Scripts/test-linux.sh`). The 26 from Phases 1-2 also pass in CI on Linux (Swift 6.4, MongoDB 8, libmongoc 2.5.5 built from source). Captured 2026-09-27 from a research session so work can be picked up later on a laptop. + +Decision on 2026-09-27: **target libmongoc 2.x only.** Homebrew's `mongo-c-driver` is now 2.x; 1.x survives only as the deprecated, keg-only `mongo-c-driver@1`, which Homebrew disables on 2027-04-01. Phase 3's 2.x work was therefore folded into Phase 1. The catch: Linux distributions that still ship 1.x (Ubuntu's `libmongoc-dev` is 1.26) need libmongoc 2 built from source until they package it. + +The work belongs in [`PerfectlySoft/Perfect-MongoDB`](https://github.com/PerfectlySoft/Perfect-MongoDB) (or a new repo, if you decide that), not in Perfect-CRUD. + +## Current state (as read, not compiled) + +**Perfect-MongoDB**: about 2,800 lines of Swift plus about 900 lines of tests. Last real change was March 2019, for `swift-tools-version:4.1`. + +| File | Covers | +|---|---| +| `MongoClient.swift`, `MongoConnectionPool.swift` | Connecting, pooling (`MongoClientPool`), listing databases, server status | +| `MongoDatabase.swift` | Collections, create, drop | +| `MongoCollection.swift` | Insert/update/delete/save, find, count, aggregate, indexes, bulk ops, find-and-modify | +| `MongoCursor.swift` | Iterating results | +| `BSON.swift` | Building and reading documents, JSON conversion | +| `MongoGridFS.swift` | File storage (legacy GridFS API) | +| `MongoJSONConvertables.swift` | `Date: JSONConvertible` (the only use of PerfectLib) | + +Dependencies: `PerfectSideRepos/Perfect-CMongo` and `PerfectSideRepos/Perfect-CBSON` (small system-library packages linking `libmongoc-1.0` / `libbson-1.0` via pkg-config); `PerfectlySoft/PerfectLib` 3.x only for `JSONConvertible`. `Perfect-mongo-c` and `Perfect-mongo-c-linux` are older Swift 3-era wrappers nothing uses; archive them. Related repos to review later: `Perfect-Session-MongoDB`, `Perfect-Turnstile-MongoDB`. + +## Deprecated libmongoc calls (replace first) + +These have been deprecated since the 1.x releases. I believe 2.x removes them, but I haven't checked that against the 2.x headers yet. + +| Current call | Replacement | +|---|---| +| `mongoc_collection_find` | `mongoc_collection_find_with_opts` | +| `mongoc_collection_insert` | `mongoc_collection_insert_one` | +| `mongoc_collection_update` | `mongoc_collection_update_one` / `_update_many` / `_replace_one` | +| `mongoc_collection_remove` | `mongoc_collection_delete_one` / `_delete_many` | +| `mongoc_collection_save` | `replace_one` with `upsert: true` | +| `mongoc_collection_count` | `mongoc_collection_count_documents` / `estimated_document_count` | +| `mongoc_collection_find_and_modify` | `mongoc_collection_find_and_modify_with_opts` | +| `mongoc_collection_create_index` | `mongoc_collection_create_indexes_with_opts` (or `createIndexes` command) | +| `mongoc_collection_get_last_error` | Use the reply document from each `_with_opts` call | +| `mongoc_client_get_database_names` | `mongoc_client_get_database_names_with_opts` | +| `mongoc_database_get_collection_names` | `mongoc_database_get_collection_names_with_opts` | +| `mongoc_client_get_server_status` | Run the `serverStatus` command | +| `mongoc_gridfs_file_get_md5` / `_set_md5` | Remove (MD5 dropped from the GridFS spec). Consider moving to `mongoc_gridfs_bucket_t` | + +All replacements exist in libmongoc 1.x (Ubuntu ships 1.26), so this step needs no 2.x dependency. + +**Checked against the 2.5.5 headers (Phase 1):** 2.x removed `find`, `count`, `save`, `create_index` (and the `mongoc_index_opt_*` structs), `get_last_error`, `get_server_status`, `get_collection_names`, plus `mongoc_collection_command`, `_stats`, `_validate`, `_create_bulk_operation`, `mongoc_gridfs_find`, `bson_as_json` / `bson_array_as_json` and `MONGOC_QUERY_SLAVE_OK`. Those are replaced. 2.x **still ships** `mongoc_collection_insert` / `_update` / `_remove` / `_find_and_modify`, `mongoc_client_get_database_names` and the GridFS MD5 accessors, so those remain for Phase 2. + +## Bugs noticed on a first read + +1. `MongoClientPool.tryPopClient()` calls `mongoc_client_pool_try_pop`, then passes the returned **client** into `mongoc_client_pool_pop`, which expects the **pool**. It should just wrap the client from `try_pop`. +2. `MongoClientPool.init(uri:)` never calls `mongoc_uri_destroy` on the `mongoc_uri_t` it creates (memory leak). It also doesn't check for a failed URI parse. + +## Plan + +### Phase 1: builds on Swift 6, API unchanged +- [x] `swift-tools-version: 6.0`; macOS 12+. +- [x] Move the C wrappers into this repo as system-library targets. They keep their old module names, `PerfectCMongo` and `PerfectCBSON`, so direct imports still work. Drop the `PerfectSideRepos` dependencies. +- [x] Remove the PerfectLib dependency. `Date.jsonEncodedString()` stays public; code that needs the `JSONConvertible` conformance can add `extension Date: JSONConvertible {}`. +- [x] Delete the checked-in jazzy `docs/`, `.jazzy.yaml` and `LinuxMain.swift` / `XCTestManifests.swift`. +- [x] Compiled in Swift 5 language mode first, then turned on Swift 6 mode. +- [x] Keep all public type and method names so existing users' code still compiles. +- [x] (From Phase 3) Build against libmongoc 2.x (`mongoc2` / `bson2`) and replace the removed calls. The legacy translation lives in `Sources/PerfectCMongo/shim.h`: `$query`/`$orderby` unwrapping, query flags to find options, save as insert or upsert-replace. +- [x] Run the full test suite against a local `mongod` (8.3.11). Added tests for `createIndex`, `stats` and legacy `$query`/`$orderby` finds. +- [x] Call `mongoc_init()` once before creating the first client or pool. libmongoc 2 no longer initializes itself when loaded, so without it every `MongoClient(uri:)` failed with "Could not parse URI". + +Phase 1 behaviour changes: +- `getLastError()` is deprecated and returns an empty document, because libmongoc 2 no longer tracks it. +- `command()` returns the command's reply as a one-document cursor. Its `fields`/`skip`/`limit`/`batchSize` parameters never applied to commands. +- `count()` now uses `countDocuments`, which gives an exact count instead of the old metadata estimate. +- `MongoIndexOptions` ignores `dropDups` (removed in MongoDB 3.0) and `storageOptions` (it was never actually sent before). +- `MongoQueryFlag.slaveOk` is deprecated in favour of `secondaryOk`, which maps to a `secondaryPreferred` read preference. + +### Phase 2: current C APIs, bug fixes, CI +- [x] Move writes to the current APIs: `insert` → `insert_one`; `update` → `update_one` / `update_many`, or `replace_one` when given a replacement document (legacy update accepted both); `remove` → `delete_one` / `delete_many`; `findAndModify` → `find_and_modify_with_opts`; bulk insert/update → `*_with_opts`. The `noValidate` flags map to `validate: false`. Also moved: `get_database_names` → `_with_opts`, `bson_append_array_begin` → `bson_append_array_unsafe_begin`, and Swift's deprecated `String(validatingUTF8:)`. The build now has no deprecation warnings. +- [x] Fix the two pool bugs. `tryPopClient()` wraps the client from `try_pop`. `init(uri:)` frees its URI and traps with the parse error on an invalid URI; before, libmongoc crashed on a failed assertion instead. +- [x] Pooled clients go back to the pool when they're released, instead of being destroyed. Each popped client keeps its pool alive. +- [x] `distinct()` leak fixed. +- [x] Ownership: databases keep their client alive; collections keep their client or database; cursors keep their collection; `GridFS` keeps its client; `GridFile`s from `list`/`search`/`upload` keep their `GridFS`. +- [x] README rewritten: current requirements, building libmongoc 2 on Linux, example. `README.zh_CN.md` is translated from it (2026-09-29), and each links to the other. +- [x] GitHub Actions (`.github/workflows/ci.yml`): Linux job in the `swift:6.4-noble` container builds libmongoc 2.5.5 from source (cached), runs the full suite against a `mongo:8` service (tests read `MONGODB_URI`). The macOS job does a Homebrew build plus the BSON tests. Green since run 36366712375. Getting there fixed a Linux-only build error, which means the package hadn't built on Linux under current Swift: glibc's `fwrite`/`fclose` need a non-optional `FILE*`. It also fixed a pointer that outlived its buffer in GridFS `download(to:)`. +- [x] Tests added for every update form, bulk writes, `findAndModify`, the pool, and a collection outliving its client variable. +- [ ] Test `mongodb+srv://` and TLS connection strings against Atlas. Needs an Atlas cluster. +- [ ] GridFS MD5: libmongoc 2 still supports it and it isn't deprecated there, so it's unchanged. Revisit if moving to `mongoc_gridfs_bucket_t`. + +### Phase 3: libmongoc 2.x +Folded into Phase 1: the package now targets 2.x only. Linux CI builds libmongoc 2 from source (Phase 2). + +### Phase 4: Swift-native API (added alongside the old one) +- [x] Ownership and `Sendable`: `MongoClientPool` is `final` and `@unchecked Sendable`, since libmongoc's pool is thread-safe. It's the only shared object. Clients, collections and cursors stay non-`Sendable` and are used by one piece of work at a time. `BSON.OID`, `BSONEncoder`, `BSONDecoder` and the new `MongoError` are `Sendable`. +- [x] async (`MongoAsync.swift`): `pool.withClient { client in ... }` pops a client, runs the body on a dedicated concurrent `DispatchQueue` behind a checked continuation, and pushes the client back even on throw. That's the same design as Perfect-CRUD's `withCRUDExecutor`. There are no per-method `async` overloads, so nothing collides with the sync names. +- [x] Codable (`BSONCodable.swift`): `BSONEncoder`/`BSONDecoder` read and write libbson directly through an intermediate tree. I wrote them fresh rather than porting `mongo-swift-driver`, which is built on its own pure-Swift BSON type. `Date` maps to datetime, `Data` to binary, `UUID` to binary subtype 4 and `BSON.OID` to ObjectId. `Int` and 64-bit integers become int64, smaller integers int32. Decoding accepts any BSON number that converts exactly. Unsupported BSON types (decimal128, regex, timestamp) throw a descriptive `typeMismatch`. `BSON.OID` is also `Codable` as its hex string for other coders. +- [x] Typed collection API (`MongoCollectionCodable.swift`): `insert(_:)`, `insert(contentsOf:)`, `find(_:filter:options:)`, `findOne`, `replaceOne`, `updateOne`, `updateMany`, `deleteOne`, `deleteMany`, `countDocuments`. They throw `MongoError` or `DecodingError` and use the `*_with_opts` APIs directly. +- [x] `AsyncSequence` cursor: `pool.find(User.self, database:collection:filter:options:batchSize:)` returns `MongoFindSequence`. One pooled client is held per iteration, documents are fetched and decoded in batches (default 100) per hop to the blocking queue, cancellation is checked between batches, and the client goes back to the pool when the loop ends, throws or breaks early. This is safe here but wasn't in Perfect-CRUD, because a libmongoc client can move between threads as long as only one uses it at a time. +- [x] Tests (`Phase4Tests.swift`, 9 tests): Codable round trip over every supported type, native BSON types in the output, numeric conversions and errors, typed CRUD, duplicate-key `MongoError`, 20 concurrent `withClient` tasks on a 4-client pool, and the find sequence (250 docs, early break releasing clients, decoding errors). Also passes under Thread Sanitizer; only the Swift code is instrumented, not libmongoc. +- Decision: **no deprecations yet.** The typed API is a parallel layer rather than a one-for-one replacement, and deprecating the old calls now would flood existing users with warnings before anyone has used the new ones. Revisit once the community requesters have tried it. +- [x] `MongoClientPool(validatingURI:)` throws `MongoError` for a bad URI or rejected options, using `mongoc_client_pool_new_with_error`. `init(uri:)` keeps its non-throwing signature and traps with that error. +- [x] macOS CI also runs the server-independent Codable tests (12 tests instead of 7). +- [ ] Not done, and worth doing on request: change streams (`watch`), aggregation into Codable types, transactions/sessions, and an async GridFS. + +## Open questions + +1. Update `PerfectlySoft/Perfect-MongoDB` in place (preferred) or start a new repo? +2. Do the community requesters have code on the old API? If so, Phases 1–2 must not break it. +3. What they use MongoDB for (existing data, Atlas, change streams, GridFS) decides what Phase 4 does first. +4. Minimum libmongoc: support 1.x only, or require 2.x once Linux distributions ship it? + +## Laptop setup + +```sh +# macOS +brew install mongo-c-driver # libmongoc + libbson; check which major version it installs +# Test server +docker run -d --name mongo -p 27017:27017 mongo:8 +# Swift 6 toolchain: Xcode 16+/26 +git clone https://github.com/PerfectlySoft/Perfect-MongoDB +cd Perfect-MongoDB && git checkout -b swift6-modernization +pkg-config --modversion libmongoc-1.0 mongoc2 # see which is installed +``` + +### Kickoff prompt for Claude Code + +> Read `Documentation/modernization-plan.md`. We're working in Perfect-MongoDB on branch `swift6-modernization`. Start Phase 1: bring the package to Swift 6 tools, move the Perfect-CMongo/Perfect-CBSON module maps in as system-library targets, drop PerfectLib, and keep the public API unchanged. Build and run the tests against the local `mongo` container after each step. diff --git a/Package.swift b/Package.swift index f018977..1799972 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:4.1 +// swift-tools-version:6.0 // Package.swift // Perfect-MongoDB // @@ -21,16 +21,38 @@ import PackageDescription let package = Package( name: "PerfectMongoDB", + platforms: [ + .macOS(.v12) + ], products: [ .library(name: "PerfectMongoDB", targets: ["PerfectMongoDB"]) ], - dependencies: [ - .package(url: "https://github.com/PerfectSideRepos/Perfect-CMongo.git", from: "0.1.0"), - .package(url: "https://github.com/PerfectSideRepos/Perfect-CBSON.git", from: "0.0.0"), - .package(url: "https://github.com/PerfectlySoft/PerfectLib.git", from: "3.0.0") - ], targets: [ - .target(name: "PerfectMongoDB", dependencies: ["PerfectLib"]), - .testTarget(name: "PerfectMongoDBTests", dependencies: ["PerfectMongoDB"]) + .systemLibrary( + name: "PerfectCBSON", + pkgConfig: "bson2", + providers: [ + .brew(["mongo-c-driver"]), + .apt(["libbson-dev"]) + ] + ), + .systemLibrary( + name: "PerfectCMongo", + pkgConfig: "mongoc2", + providers: [ + .brew(["mongo-c-driver"]), + .apt(["libmongoc-dev"]) + ] + ), + .target( + name: "PerfectMongoDB", + dependencies: ["PerfectCBSON", "PerfectCMongo"], + swiftSettings: [.swiftLanguageMode(.v6)] + ), + .testTarget( + name: "PerfectMongoDBTests", + dependencies: ["PerfectMongoDB"], + swiftSettings: [.swiftLanguageMode(.v6)] + ) ] ) diff --git a/README.md b/README.md index c989129..81a4384 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,126 @@ -Perfect - MongoDB Connector [简体中文](README.zh_CN.md) -=========================== +# Perfect MongoDB -

- - Get Involed with Perfect! - -

- -

- - Star Perfect On Github - - - Stack Overflow - - - Follow Perfect on Twitter - - - Join the Perfect Slack - -

+[简体中文](README.zh_CN.md)

- - Swift 4.0 - - - Platforms OS X | Linux - - - License Apache - - - PerfectlySoft Twitter - - - Slack Status - + Swift 6 + Platforms macOS 12+ | Linux + License Apache 2.0

-NOTE: This package is deprecated in favour of the official [Mongo Swift Driver](https://github.com/mongodb/mongo-swift-driver). We advise users to switch to that package if possible. - --- +A Swift wrapper around MongoDB's C driver, [libmongoc](https://github.com/mongodb/mongo-c-driver). It covers +clients and connection pools, databases, collections, cursors, BSON documents and GridFS. + +MongoDB stopped developing its official server-side Swift driver in 2023. libmongoc is the driver MongoDB +still maintains and keeps compliant with its specifications (server discovery, retryable writes, +authentication, TLS), so this package builds on it. + +**Version 4 modernizes this package for Swift 6.** It builds in Swift 6 language mode against libmongoc 2.x, +keeps the existing public API, and adds Codable and async/await APIs. A few behaviour changes, forced by +libmongoc 2, are listed in [Documentation/modernization-plan.md](Documentation/modernization-plan.md). + +The pre-4.0 version (Swift 4/5, libmongoc 1.x) is preserved on the [`legacy`](../../tree/legacy) branch and +the 3.x tags. + +```swift +dependencies: [ + .package(url: "https://github.com/PerfectlySoft/Perfect-MongoDB.git", from: "4.0.0") +], +targets: [ + .target(name: "MyTarget", dependencies: [ + .product(name: "PerfectMongoDB", package: "Perfect-MongoDB") + ]) +] +``` -This project provides a Swift wrapper around the mongo-c client library, -enabling access to MongoDB servers. +## Requirements -This package builds with Swift Package Manager and is part of the -[Perfect](https://github.com/PerfectlySoft/Perfect) project. It was written to -be stand-alone and so does not require PerfectLib or any other components. +libmongoc **2.x** must be installed where pkg-config can find `mongoc2` and `bson2`. -Ensure you have installed and activated the latest Swift 4.0 tool chain. +**macOS** ([Homebrew](https://brew.sh)): -macOS Build Notes ----------------- +```sh +brew install mongo-c-driver +``` -This package requires the [Homebrew](http://brew.sh) build of mongo-c. +On Apple silicon, if SwiftPM can't find the library, set `PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig`. -To install Homebrew: +**Linux:** most distributions still package libmongoc 1.x (for example Ubuntu's `libmongoc-dev`), which +this version doesn't support. Build libmongoc 2 from source, as the CI workflow in +`.github/workflows/ci.yml` does: -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +```sh +apt-get install cmake libssl-dev libsasl2-dev libzstd-dev +curl -fsSL https://github.com/mongodb/mongo-c-driver/releases/download/2.5.5/mongo-c-driver-2.5.5.tar.gz | tar xz +cmake -S mongo-c-driver-2.5.5 -B build -DCMAKE_BUILD_TYPE=Release -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF +cmake --build build --parallel && sudo cmake --install build +``` -To install mongo-c: +## Example -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -brew install mongo-c-driver -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +```swift +import PerfectMongoDB -Linux Build Notes ------------------ +let client = try MongoClient(uri: "mongodb://localhost") +let users = client.getDatabase(name: "app").getCollection(name: "users")! -Ensure that you have installed components below: +_ = users.insert(document: try BSON(json: #"{"name": "Ada", "age": 36}"#)) +if let cursor = users.find(query: try BSON(json: #"{"age": {"$gt": 30}}"#)) { + for user in cursor { + print(user.asString) + } +} ``` -apt-get install libmongoc-dev libbson-dev libssl-dev + +### Codable and async/await + +`BSONEncoder` and `BSONDecoder` map `Codable` types to BSON documents, keeping dates, binary data, UUIDs and +ObjectIds as native BSON types. Collections have typed, throwing methods built on them. For concurrency, +share one `MongoClientPool`. `withClient` runs blocking driver work off Swift's cooperative thread pool, +and `find` streams results as an `AsyncSequence`: + +```swift +struct User: Codable, Sendable { + var _id: BSON.OID + var name: String + var age: Int + var joined: Date +} + +let pool = try MongoClientPool(validatingURI: "mongodb://localhost") + +try await pool.withClient { client in + let users = client.getCollection(databaseName: "app", collectionName: "users") + try users.insert(User(_id: BSON.OID(), name: "Ada", age: 36, joined: Date())) + try users.updateOne(filter: try BSON(json: #"{"name": "Ada"}"#), + update: try BSON(json: #"{"$inc": {"age": 1}}"#)) +} + +for try await user in pool.find(User.self, database: "app", collection: "users", + filter: try BSON(json: #"{"age": {"$gt": 30}}"#)) { + print(user.name) +} ``` -Building --------- +A `MongoClient` and the collections and cursors made from it are not thread-safe: use each from one task at +a time, which `withClient` and `find` do for you. + +## Testing -Add this project as a dependency in your Package.swift file. +The tests expect a MongoDB server on `mongodb://localhost`: -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.package(url: "https://github.com/PerfectlySoft/Perfect-MongoDB.git", from: "3.0.0") -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +```sh +mongod --dbpath /tmp/mongo-test +swift test +``` + +To run the suite on Linux locally the way CI does (Swift 6.4, libmongoc 2.5.5, MongoDB 8), use Apple's +[`container`](https://github.com/apple/container) tool: + +```sh +container system start # once +Scripts/test-linux.sh # extra arguments go to swift test, e.g. --filter Phase4Tests +``` -Documentation --------- -Further information can be found at [perfect.org](http://www.perfect.org/docs/MongoDB.html). +The first run compiles libmongoc into a cached volume (under a minute); later runs take seconds. diff --git a/README.zh_CN.md b/README.zh_CN.md index ab9cf89..e5f299d 100644 --- a/README.zh_CN.md +++ b/README.zh_CN.md @@ -1,97 +1,123 @@ -Perfect - MongoDB 数据库连接器 [English](README.md) -=========================== +# Perfect MongoDB -

- - Get Involed with Perfect! - -

+[English](README.md)

- - Star Perfect On Github - - - Stack Overflow - - - Follow Perfect on Twitter - - - Join the Perfect Slack - + Swift 6 + Platforms macOS 12+ | Linux + License Apache 2.0

-

- - Swift 3.0 - - - Platforms OS X | Linux - - - License Apache - - - PerfectlySoft Twitter - - - Slack Status - -

+本项目是 MongoDB 官方 C 语言驱动 [libmongoc](https://github.com/mongodb/mongo-c-driver) 的 Swift 封装, +涵盖客户端与连接池、数据库、集合、游标、BSON 文档以及 GridFS。 +MongoDB 已于 2023 年停止开发官方的服务器端 Swift 驱动。libmongoc 是 MongoDB 仍在维护、并持续符合其驱动规范 +(服务器发现、可重试写入、身份验证、TLS)的驱动,因此本项目基于它构建。 -本项目封装了 mongo-c 客户端函数库,因此可以使用 Swift 访问 MongoDB 服务器。 +**4.0 版本针对 Swift 6 对本项目进行了现代化改造。** 它在 Swift 6 语言模式下基于 libmongoc 2.x 编译,保留了 +现有公共 API,并新增了 Codable 与 async/await API。由 libmongoc 2 导致的少数行为变化列于 +[Documentation/modernization-plan.md](Documentation/modernization-plan.md)(英文)。 -本项目是 -[Perfect](https://github.com/PerfectlySoft/Perfect) 软件体系的一部分,但是可以独立运行,不依赖于 PerfectLib 基本库。 -请确保您已经正确安装了最新版本的 Swift 3.0 工具链。 +4.0 之前的版本(Swift 4/5、libmongoc 1.x)保留在 [`legacy`](../../tree/legacy) 分支以及 3.x 标签中。 +```swift +dependencies: [ + .package(url: "https://github.com/PerfectlySoft/Perfect-MongoDB.git", from: "4.0.0") +], +targets: [ + .target(name: "MyTarget", dependencies: [ + .product(name: "PerfectMongoDB", package: "Perfect-MongoDB") + ]) +] +``` +## 环境要求 +必须安装 libmongoc **2.x**,并确保 pkg-config 能找到 `mongoc2` 和 `bson2`。 -## 问题报告 +**macOS**([Homebrew](https://brew.sh)): -我们正在过渡到 JIRA 程序错误管理系统,因此 GitHub 的问题报告功能就被禁用了。 +```sh +brew install mongo-c-driver +``` -如果您发现任何问题,或有任何意见和建议,请在我们的 JIRA 工作台指出 [http://jira.perfect.org:8080/servicedesk/customer/portal/1](http://jira.perfect.org:8080/servicedesk/customer/portal/1)。 +在 Apple 芯片的 Mac 上,如果 SwiftPM 找不到该库,请设置 `PKG_CONFIG_PATH=/opt/homebrew/lib/pkgconfig`。 -目前的问题清单请查阅 [http://jira.perfect.org:8080/projects/ISS/issues](http://jira.perfect.org:8080/projects/ISS/issues) +**Linux:** 大多数发行版仍只提供 libmongoc 1.x(例如 Ubuntu 的 `libmongoc-dev`),本版本不支持。请参照 +`.github/workflows/ci.yml` 中 CI 的做法,从源码编译 libmongoc 2: -macOS 注意事项 ----------------- +```sh +apt-get install cmake libssl-dev libsasl2-dev libzstd-dev +curl -fsSL https://github.com/mongodb/mongo-c-driver/releases/download/2.5.5/mongo-c-driver-2.5.5.tar.gz | tar xz +cmake -S mongo-c-driver-2.5.5 -B build -DCMAKE_BUILD_TYPE=Release -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF +cmake --build build --parallel && sudo cmake --install build +``` -本程序依赖于 [Homebrew](http://brew.sh) 发行的 mongo-c 函数库。 +## 示例 -如果您要安装 Homebrew: +```swift +import PerfectMongoDB -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +let client = try MongoClient(uri: "mongodb://localhost") +let users = client.getDatabase(name: "app").getCollection(name: "users")! -安装 mongo-c 的方法: +_ = users.insert(document: try BSON(json: #"{"name": "Ada", "age": 36}"#)) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -brew install mongo-c-driver -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +if let cursor = users.find(query: try BSON(json: #"{"age": {"$gt": 30}}"#)) { + for user in cursor { + print(user.asString) + } +} +``` -Linux 注意事项 ------------------ +### Codable 与 async/await -请确定以下函数库已经预装: +`BSONEncoder` 和 `BSONDecoder` 在 `Codable` 类型与 BSON 文档之间转换,日期、二进制数据、UUID 和 ObjectId +都保留为原生 BSON 类型。集合提供了基于它们的类型化、可抛出错误的方法。并发场景下请共享同一个 +`MongoClientPool`:`withClient` 会把阻塞的驱动调用放到 Swift 协作线程池之外执行,`find` 则以 `AsyncSequence` +的形式流式返回结果: -``` -apt-get install libmongoc-dev libbson-dev libssl-dev +```swift +struct User: Codable, Sendable { + var _id: BSON.OID + var name: String + var age: Int + var joined: Date +} + +let pool = try MongoClientPool(validatingURI: "mongodb://localhost") + +try await pool.withClient { client in + let users = client.getCollection(databaseName: "app", collectionName: "users") + try users.insert(User(_id: BSON.OID(), name: "Ada", age: 36, joined: Date())) + try users.updateOne(filter: try BSON(json: #"{"name": "Ada"}"#), + update: try BSON(json: #"{"$inc": {"age": 1}}"#)) +} + +for try await user in pool.find(User.self, database: "app", collection: "users", + filter: try BSON(json: #"{"age": {"$gt": 30}}"#)) { + print(user.name) +} ``` -编译 --------- +`MongoClient` 以及由它创建的集合和游标都不是线程安全的:同一时间只能在一个任务中使用,`withClient` 和 +`find` 会替你保证这一点。 -请在您的 Package.swift 文件下增加以下内容: +## 测试 -```swift -.Package(url:"https://github.com/PerfectlySoft/Perfect-MongoDB.git", majorVersion: 3) +测试需要在 `mongodb://localhost` 上运行的 MongoDB 服务器: + +```sh +mongod --dbpath /tmp/mongo-test +swift test +``` + +如需像 CI 一样在本地的 Linux 环境中运行测试(Swift 6.4、libmongoc 2.5.5、MongoDB 8),请使用 Apple 的 +[`container`](https://github.com/apple/container) 工具: + +```sh +container system start # 只需执行一次 +Scripts/test-linux.sh # 额外参数会传给 swift test,例如 --filter Phase4Tests ``` -## 更多信息 -关于 Perfect 软件函数库的更多信息,请访问官网: [perfect.org](http://www.perfect.org/docs/MongoDB.html). +首次运行会把 libmongoc 编译到一个缓存卷中(不到一分钟),之后每次运行只需几秒钟。 diff --git a/Scripts/test-linux.sh b/Scripts/test-linux.sh new file mode 100755 index 0000000..171d1d5 --- /dev/null +++ b/Scripts/test-linux.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# Runs the test suite on Linux locally with Apple's `container` tool, matching CI: +# Swift 6.4 on Ubuntu 24.04, libmongoc 2.5.5 built from source, MongoDB 8. +# +# Scripts/test-linux.sh # full suite +# Scripts/test-linux.sh --filter Phase4 # extra arguments go to `swift test` +# +# Needs `container system start` to have been run once. The first run compiles +# libmongoc into the `perfect-mongodb-mongoc` volume (a few minutes); later runs reuse +# it. Linux build products live in the `perfect-mongodb-build` volume, separate from +# the macOS .build directory. No image build (`container build`) is involved. + +set -euo pipefail + +SWIFT_IMAGE="docker.io/library/swift:6.4-noble" +MONGO_IMAGE="docker.io/library/mongo:8" +MONGOC_VERSION="2.5.5" +MONGOC_VOLUME="perfect-mongodb-mongoc" +BUILD_VOLUME="perfect-mongodb-build" +MONGO="perfect-mongodb-test-mongo" +ROOT="$(cd "$(dirname "$0")/.." && pwd)" + +for volume in "$MONGOC_VOLUME" "$BUILD_VOLUME"; do + container volume inspect "$volume" >/dev/null 2>&1 || container volume create "$volume" >/dev/null +done + +# One-time: build libmongoc into the volume. The marker file records the version. +if ! container run --rm --volume "$MONGOC_VOLUME:/opt/mongoc" "$SWIFT_IMAGE" \ + test -f "/opt/mongoc/.version-$MONGOC_VERSION" >/dev/null 2>&1; then + echo "Building libmongoc $MONGOC_VERSION into the $MONGOC_VOLUME volume (first run only)..." + container run --rm --cpus 4 --memory 4G --volume "$MONGOC_VOLUME:/opt/mongoc" "$SWIFT_IMAGE" bash -euc " + apt-get update -qq + apt-get install -y -qq --no-install-recommends build-essential cmake curl libssl-dev libsasl2-dev libzstd-dev >/dev/null + rm -rf /opt/mongoc/* + curl -fsSL https://github.com/mongodb/mongo-c-driver/releases/download/$MONGOC_VERSION/mongo-c-driver-$MONGOC_VERSION.tar.gz | tar xz -C /tmp + cmake -S /tmp/mongo-c-driver-$MONGOC_VERSION -B /tmp/mongoc-build -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/opt/mongoc -DENABLE_TESTS=OFF -DENABLE_EXAMPLES=OFF >/dev/null + cmake --build /tmp/mongoc-build --parallel 4 >/dev/null + cmake --install /tmp/mongoc-build >/dev/null + touch /opt/mongoc/.version-$MONGOC_VERSION + " +fi + +cleanup() { + container stop "$MONGO" >/dev/null 2>&1 || true +} +trap cleanup EXIT +cleanup +container run --detach --rm --name "$MONGO" "$MONGO_IMAGE" >/dev/null +MONGO_IP="$(container inspect "$MONGO" | python3 -c 'import json, sys; print(json.load(sys.stdin)[0]["status"]["networks"][0]["ipv4Address"].split("/")[0])')" +echo "MongoDB at $MONGO_IP" + +container run --rm \ + --cpus 4 --memory 6G \ + --volume "$ROOT:/work" \ + --volume "$BUILD_VOLUME:/build" \ + --volume "$MONGOC_VOLUME:/opt/mongoc" \ + --workdir /work \ + --env "MONGODB_URI=mongodb://$MONGO_IP:27017" \ + "$SWIFT_IMAGE" \ + bash -c ' + export PKG_CONFIG_PATH=$(dirname $(find /opt/mongoc -name mongoc2.pc)) + export LD_LIBRARY_PATH=$(dirname $(find /opt/mongoc -name "libmongoc2.so" | head -1)) + for i in $(seq 1 30); do (echo > /dev/tcp/'"$MONGO_IP"'/27017) 2>/dev/null && break; sleep 1; done + swift test --scratch-path /build "$@" + ' bash "$@" diff --git a/Sources/PerfectCBSON/module.modulemap b/Sources/PerfectCBSON/module.modulemap new file mode 100644 index 0000000..b51b64c --- /dev/null +++ b/Sources/PerfectCBSON/module.modulemap @@ -0,0 +1,5 @@ +module PerfectCBSON [system] { + header "shim.h" + link "bson2" + export * +} diff --git a/Sources/PerfectCBSON/shim.h b/Sources/PerfectCBSON/shim.h new file mode 100644 index 0000000..42ee4b4 --- /dev/null +++ b/Sources/PerfectCBSON/shim.h @@ -0,0 +1,2 @@ +#define BCON_H_ +#include diff --git a/Sources/PerfectCMongo/module.modulemap b/Sources/PerfectCMongo/module.modulemap new file mode 100644 index 0000000..cd6e79a --- /dev/null +++ b/Sources/PerfectCMongo/module.modulemap @@ -0,0 +1,5 @@ +module PerfectCMongo [system] { + header "shim.h" + link "mongoc2" + export * +} diff --git a/Sources/PerfectCMongo/shim.h b/Sources/PerfectCMongo/shim.h new file mode 100644 index 0000000..0f9087d --- /dev/null +++ b/Sources/PerfectCMongo/shim.h @@ -0,0 +1,295 @@ +#include +#include + +static bool _mongoc_cursor_next(mongoc_cursor_t *cursor, const void **bson) +{ + const bson_t *bson2 = NULL; + bool r = mongoc_cursor_next(cursor, &bson2); + *bson = bson2; + return r; +} + +// libmongoc 2.x removed the legacy query APIs (mongoc_collection_find, _count, _save). +// These helpers keep Perfect-MongoDB's legacy-style parameters working on top of the +// current *_with_opts APIs, following what libmongoc 1.x did internally. + +// Split a legacy query document into a filter and find options. +// { $query: {...}, $orderby: {...}, $hint: ... } becomes filter {...} and opts { sort: {...}, hint: ... }. +// A query without $query is copied into filter unchanged. +static void _perfect_split_legacy_query(const bson_t *query, bson_t *filter, bson_t *opts) +{ + bson_iter_t iter; + if (!query || !bson_iter_init_find(&iter, query, "$query")) { + if (query) { + bson_concat(filter, query); + } + return; + } + bson_iter_init(&iter, query); + while (bson_iter_next(&iter)) { + const char *key = bson_iter_key(&iter); + if (strcmp(key, "$query") == 0) { + if (BSON_ITER_HOLDS_DOCUMENT(&iter)) { + uint32_t len = 0; + const uint8_t *data = NULL; + bson_t sub; + bson_iter_document(&iter, &len, &data); + if (bson_init_static(&sub, data, len)) { + bson_concat(filter, &sub); + } + } + } else if (strcmp(key, "$orderby") == 0) { + bson_append_iter(opts, "sort", -1, &iter); + } else if (strcmp(key, "$showDiskLoc") == 0) { + bson_append_iter(opts, "showRecordId", -1, &iter); + } else if (strcmp(key, "$explain") == 0 || strcmp(key, "$snapshot") == 0) { + // no longer supported by the server + } else if (key[0] == '$') { + bson_append_iter(opts, key + 1, -1, &iter); + } else { + bson_append_iter(filter, key, -1, &iter); + } + } +} + +// Convert legacy query flags to find options. Returns true when MONGOC_QUERY_SECONDARY_OK is set. +static bool _perfect_flags_to_opts(mongoc_query_flags_t flags, bson_t *opts) +{ + if (flags & MONGOC_QUERY_TAILABLE_CURSOR) { + BSON_APPEND_BOOL(opts, "tailable", true); + } + if (flags & MONGOC_QUERY_OPLOG_REPLAY) { + BSON_APPEND_BOOL(opts, "oplogReplay", true); + } + if (flags & MONGOC_QUERY_NO_CURSOR_TIMEOUT) { + BSON_APPEND_BOOL(opts, "noCursorTimeout", true); + } + if (flags & MONGOC_QUERY_AWAIT_DATA) { + BSON_APPEND_BOOL(opts, "awaitData", true); + } + if (flags & MONGOC_QUERY_EXHAUST) { + BSON_APPEND_BOOL(opts, "exhaust", true); + } + if (flags & MONGOC_QUERY_PARTIAL) { + BSON_APPEND_BOOL(opts, "allowPartialResults", true); + } + return (flags & MONGOC_QUERY_SECONDARY_OK) != 0; +} + +static mongoc_cursor_t *_perfect_collection_find(mongoc_collection_t *collection, + mongoc_query_flags_t flags, + uint32_t skip, + int32_t limit, + uint32_t batch_size, + const bson_t *query, + const bson_t *fields) +{ + bson_t filter = BSON_INITIALIZER; + bson_t opts = BSON_INITIALIZER; + mongoc_read_prefs_t *prefs = NULL; + mongoc_cursor_t *cursor; + + _perfect_split_legacy_query(query, &filter, &opts); + if (_perfect_flags_to_opts(flags, &opts)) { + prefs = mongoc_read_prefs_new(MONGOC_READ_SECONDARY_PREFERRED); + } + if (skip) { + BSON_APPEND_INT64(&opts, "skip", skip); + } + if (limit) { + BSON_APPEND_INT64(&opts, "limit", limit); + } + if (batch_size) { + BSON_APPEND_INT64(&opts, "batchSize", batch_size); + } + if (fields && !bson_empty(fields)) { + BSON_APPEND_DOCUMENT(&opts, "projection", fields); + } + cursor = mongoc_collection_find_with_opts(collection, &filter, &opts, prefs); + if (prefs) { + mongoc_read_prefs_destroy(prefs); + } + bson_destroy(&filter); + bson_destroy(&opts); + return cursor; +} + +static int64_t _perfect_collection_count(mongoc_collection_t *collection, + mongoc_query_flags_t flags, + const bson_t *query, + int64_t skip, + int64_t limit, + bson_error_t *error) +{ + bson_t filter = BSON_INITIALIZER; + bson_t find_opts = BSON_INITIALIZER; + bson_t opts = BSON_INITIALIZER; + mongoc_read_prefs_t *prefs = NULL; + int64_t count; + + _perfect_split_legacy_query(query, &filter, &find_opts); + if (_perfect_flags_to_opts(flags, &find_opts)) { + prefs = mongoc_read_prefs_new(MONGOC_READ_SECONDARY_PREFERRED); + } + if (skip) { + BSON_APPEND_INT64(&opts, "skip", skip); + } + if (limit) { + BSON_APPEND_INT64(&opts, "limit", limit); + } + count = mongoc_collection_count_documents(collection, &filter, &opts, prefs, NULL, error); + if (prefs) { + mongoc_read_prefs_destroy(prefs); + } + bson_destroy(&filter); + bson_destroy(&find_opts); + bson_destroy(&opts); + return count; +} + +static mongoc_gridfs_file_list_t *_perfect_gridfs_find(mongoc_gridfs_t *gridfs, const bson_t *query) +{ + bson_t filter = BSON_INITIALIZER; + bson_t opts = BSON_INITIALIZER; + mongoc_gridfs_file_list_t *list; + + _perfect_split_legacy_query(query, &filter, &opts); + list = mongoc_gridfs_find_with_opts(gridfs, &filter, &opts); + bson_destroy(&filter); + bson_destroy(&opts); + return list; +} + +// True when the first key starts with '$', i.e. an update-operator document like { $set: ... } +// rather than a replacement document. +static bool _perfect_is_update_document(const bson_t *update) +{ + bson_iter_t iter; + return bson_iter_init(&iter, update) && bson_iter_next(&iter) && bson_iter_key(&iter)[0] == '$'; +} + +static bool _perfect_collection_insert(mongoc_collection_t *collection, + mongoc_insert_flags_t flags, + const bson_t *document, + bson_error_t *error) +{ + bson_t opts = BSON_INITIALIZER; + bool ret; + + if (flags & MONGOC_INSERT_NO_VALIDATE) { + BSON_APPEND_BOOL(&opts, "validate", false); + } + ret = mongoc_collection_insert_one(collection, document, &opts, NULL, error); + bson_destroy(&opts); + return ret; +} + +// Legacy update accepted either an update-operator document or a replacement document. +static bool _perfect_collection_update(mongoc_collection_t *collection, + mongoc_update_flags_t flags, + const bson_t *selector, + const bson_t *update, + bson_error_t *error) +{ + bson_t opts = BSON_INITIALIZER; + bool ret; + + if (flags & MONGOC_UPDATE_UPSERT) { + BSON_APPEND_BOOL(&opts, "upsert", true); + } + if (flags & MONGOC_UPDATE_NO_VALIDATE) { + BSON_APPEND_BOOL(&opts, "validate", false); + } + if (!_perfect_is_update_document(update)) { + ret = mongoc_collection_replace_one(collection, selector, update, &opts, NULL, error); + } else if (flags & MONGOC_UPDATE_MULTI_UPDATE) { + ret = mongoc_collection_update_many(collection, selector, update, &opts, NULL, error); + } else { + ret = mongoc_collection_update_one(collection, selector, update, &opts, NULL, error); + } + bson_destroy(&opts); + return ret; +} + +static bool _perfect_collection_remove(mongoc_collection_t *collection, + mongoc_remove_flags_t flags, + const bson_t *selector, + bson_error_t *error) +{ + if (flags & MONGOC_REMOVE_SINGLE_REMOVE) { + return mongoc_collection_delete_one(collection, selector, NULL, NULL, error); + } + return mongoc_collection_delete_many(collection, selector, NULL, NULL, error); +} + +// Legacy bulk update: updates every matching document, or replaces one for a replacement document. +static bool _perfect_bulk_operation_update(mongoc_bulk_operation_t *bulk, + const bson_t *selector, + const bson_t *update, + bson_error_t *error) +{ + if (!_perfect_is_update_document(update)) { + return mongoc_bulk_operation_replace_one_with_opts(bulk, selector, update, NULL, error); + } + return mongoc_bulk_operation_update_many_with_opts(bulk, selector, update, NULL, error); +} + +static bool _perfect_collection_find_and_modify(mongoc_collection_t *collection, + const bson_t *query, + const bson_t *sort, + const bson_t *update, + const bson_t *fields, + bool _remove, + bool upsert, + bool _new, + bson_t *reply, + bson_error_t *error) +{ + bson_t empty = BSON_INITIALIZER; + mongoc_find_and_modify_opts_t *opts = mongoc_find_and_modify_opts_new(); + int flags = MONGOC_FIND_AND_MODIFY_NONE; + bool ret; + + if (sort) { + mongoc_find_and_modify_opts_set_sort(opts, sort); + } + if (update) { + mongoc_find_and_modify_opts_set_update(opts, update); + } + if (fields) { + mongoc_find_and_modify_opts_set_fields(opts, fields); + } + if (_remove) { + flags |= MONGOC_FIND_AND_MODIFY_REMOVE; + } + if (upsert) { + flags |= MONGOC_FIND_AND_MODIFY_UPSERT; + } + if (_new) { + flags |= MONGOC_FIND_AND_MODIFY_RETURN_NEW; + } + mongoc_find_and_modify_opts_set_flags(opts, (mongoc_find_and_modify_flags_t)flags); + ret = mongoc_collection_find_and_modify_with_opts(collection, query ? query : &empty, opts, reply, error); + mongoc_find_and_modify_opts_destroy(opts); + bson_destroy(&empty); + return ret; +} + +// Legacy save: insert when the document has no _id, otherwise replace (upserting) by _id. +static bool _perfect_collection_save(mongoc_collection_t *collection, const bson_t *document, bson_error_t *error) +{ + bson_iter_t iter; + bson_t selector = BSON_INITIALIZER; + bson_t opts = BSON_INITIALIZER; + bool ret; + + if (!bson_iter_init_find(&iter, document, "_id")) { + return mongoc_collection_insert_one(collection, document, NULL, NULL, error); + } + bson_append_iter(&selector, "_id", 3, &iter); + BSON_APPEND_BOOL(&opts, "upsert", true); + ret = mongoc_collection_replace_one(collection, &selector, document, &opts, NULL, error); + bson_destroy(&selector); + bson_destroy(&opts); + return ret; +} diff --git a/Sources/PerfectMongoDB/BSON.swift b/Sources/PerfectMongoDB/BSON.swift index e89c04f..8ee199b 100644 --- a/Sources/PerfectMongoDB/BSON.swift +++ b/Sources/PerfectMongoDB/BSON.swift @@ -91,7 +91,7 @@ public class BSON: CustomStringConvertible { guard let doc = bson_new_from_json(json, json.utf8.count, &error) else { let message = withUnsafePointer(to: &error.message) { $0.withMemoryRebound(to: CChar.self, capacity: 0) { - String(validatingUTF8: $0) ?? "Unknown error while parsing JSON" + String(validatingCString: $0) ?? "Unknown error while parsing JSON" } } throw BSONError.syntaxError(message) @@ -159,25 +159,25 @@ public class BSON: CustomStringConvertible { */ public var asString: String { var length = 0 - guard let doc = self.doc, let data = bson_as_json(toOpaque(doc), &length) else { + guard let doc = self.doc, let data = bson_as_legacy_extended_json(toOpaque(doc), &length) else { return "" } defer { bson_free(data) } - return String(validatingUTF8: data) ?? "" + return String(validatingCString: data) ?? "" } /** like asString() but for outermost arrays. */ public var asArrayString: String { var length = 0 - guard let doc = self.doc, let data = bson_array_as_json(toOpaque(doc), &length) else { + guard let doc = self.doc, let data = bson_array_as_legacy_extended_json(toOpaque(doc), &length) else { return "" } defer { bson_free(data) } - return String(validatingUTF8: data) ?? "" + return String(validatingCString: data) ?? "" } /** @@ -472,7 +472,7 @@ public class BSON: CustomStringConvertible { guard let doc = self.doc, let cdoc = child.doc else { return false } - return bson_append_array_begin(toOpaque(doc), k, -1, toOpaque(cdoc)) + return bson_append_array_unsafe_begin(toOpaque(doc), k, -1, toOpaque(cdoc)) } /** @@ -536,7 +536,7 @@ public class BSON: CustomStringConvertible { } /// Represents a BSON OID. - public struct OID: CustomStringConvertible { + public struct OID: CustomStringConvertible, Sendable { var oid: bson_oid_t public var description: String { let up = UnsafeMutablePointer.allocate(capacity: 25) @@ -622,7 +622,7 @@ class NoDestroyBSON: BSON { private func ptr2Str(_ ptr: UnsafeMutablePointer!, length: Int) -> String? { var ary = Array(UnsafeBufferPointer(start: ptr, count: Int(length))) ary.append(0) - return String(validatingUTF8: ary) + return ary.withUnsafeBufferPointer { String(validatingCString: $0.baseAddress!) } } extension BSON { @@ -751,8 +751,8 @@ extension BSON { case .regex: let regex = value.pointee.value.v_regex - let rstr = String(validatingUTF8: regex.regex) - let ostr = String(validatingUTF8: regex.options) + let rstr = String(validatingCString: regex.regex) + let ostr = String(validatingCString: regex.options) double = 0.0 string = "/\(rstr ?? "")/\(ostr ?? "")" @@ -815,7 +815,7 @@ extension BSON { guard let c = bson_iter_key(toOpaque(&cpy)) else { return nil } - return String(validatingUTF8: c) + return String(validatingCString: c) } /// If the current value is an narray or document, this returns an iterator /// which can be used to walk it. diff --git a/Sources/PerfectMongoDB/BSONCodable.swift b/Sources/PerfectMongoDB/BSONCodable.swift new file mode 100644 index 0000000..38c7511 --- /dev/null +++ b/Sources/PerfectMongoDB/BSONCodable.swift @@ -0,0 +1,900 @@ +// +// BSONCodable.swift +// PerfectMongoDB +// +// Codable support: BSONEncoder turns an Encodable value into a BSON document and +// BSONDecoder turns a document back into a Decodable value, reading and writing +// libbson directly so BSON types survive the round trip: +// +// Date <-> datetime (millisecond precision) +// Data <-> binary (subtype 0) +// UUID <-> binary (subtype 4) +// BSON.OID <-> ObjectId +// Int, Int64, UInt32 -> int64; smaller integers -> int32; Float, Double -> double +// +// Decoding accepts any BSON number for any Swift numeric type, as long as the value +// converts exactly. Both sides go through an intermediate tree (`BSONNode`), which +// keeps nested containers and superEncoder/superDecoder simple. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the Perfect.org open source project +// +// Copyright (c) 2015 - 2026 PerfectlySoft Inc. and the Perfect project authors +// Licensed under Apache License v2.0 +// +// See http://perfect.org/licensing.html for license information +// +//===----------------------------------------------------------------------===// +// + +import Foundation +import PerfectCBSON + +// MARK: - Intermediate tree + +enum BSONPrimitive { + case null + case bool(Bool) + case int32(Int32) + case int64(Int64) + case double(Double) + case string(String) + case dateTime(Int64) + case binary(Data, bson_subtype_t) + case oid(bson_oid_t) + /// A BSON type with no Codable mapping (decimal128, regex, timestamp, ...), by type name. + case unsupported(String) +} + +final class BSONDocumentStorage { + private(set) var keys: [String] = [] + private var boxes: [String: BSONNodeBox] = [:] + + /// Returns the box for **key**, adding it if new. Re-encoding a key replaces its value in place. + func box(forKey key: String) -> BSONNodeBox { + if let existing = boxes[key] { + return existing + } + let box = BSONNodeBox() + keys.append(key) + boxes[key] = box + return box + } + + var entries: [(String, BSONNodeBox)] { + keys.map { ($0, boxes[$0]!) } + } +} + +final class BSONArrayStorage { + private(set) var boxes: [BSONNodeBox] = [] + + func append() -> BSONNodeBox { + let box = BSONNodeBox() + boxes.append(box) + return box + } +} + +enum BSONEncodedNode { + case primitive(BSONPrimitive) + case document(BSONDocumentStorage) + case array(BSONArrayStorage) +} + +final class BSONNodeBox { + var node: BSONEncodedNode? +} + +/// A decoded document, array or value. +indirect enum BSONNode { + case primitive(BSONPrimitive) + case document([(String, BSONNode)]) + case array([BSONNode]) +} + +// MARK: - Errors + +/// An error reported by MongoDB or libmongoc from the throwing (Codable and async) API. +public struct MongoError: Error, CustomStringConvertible, Sendable { + public let domain: UInt32 + public let code: UInt32 + public let message: String + + public init(domain: UInt32, code: UInt32, message: String) { + self.domain = domain + self.code = code + self.message = message + } + + init(_ error: bson_error_t) { + var error = error + let message = withUnsafePointer(to: &error.message) { + $0.withMemoryRebound(to: CChar.self, capacity: 1) { + String(validatingCString: $0) ?? "unknown error" + } + } + self.init(domain: error.domain, code: error.code, message: message) + } + + init(_ message: String) { + self.init(domain: 1, code: 1, message: message) + } + + public var description: String { + "MongoError(\(domain), \(code)): \(message)" + } +} + +extension MongoResult { + /// Throws the error case; returns the result otherwise. + func get() throws -> MongoResult { + if case .error(let domain, let code, let message) = self { + throw MongoError(domain: domain, code: code, message: message) + } + return self + } +} + +// MARK: - ObjectId + +extension BSON.OID: Hashable, Codable { + public static func == (lhs: BSON.OID, rhs: BSON.OID) -> Bool { + var l = lhs.oid, r = rhs.oid + return bson_oid_equal(&l, &r) + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(description) + } + + /// An ObjectId from its 24-character hex string, or nil if **string** isn't one. + public init?(validating string: String) { + guard bson_oid_is_valid(string, string.utf8.count) else { + return nil + } + self.init(string) + } + + /// Outside BSONEncoder/BSONDecoder (for example with JSONEncoder), an ObjectId is its hex string. + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let string = try container.decode(String.self) + guard let oid = BSON.OID(validating: string) else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "'\(string)' is not an ObjectId") + } + self = oid + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(description) + } +} + +// MARK: - Encoder + +/// Encodes Encodable values as BSON documents. +public struct BSONEncoder: Sendable { + /// Contextual information for custom `encode(to:)` implementations. + public var userInfo: [CodingUserInfoKey: any Sendable] = [:] + + public init() {} + + /// Encodes **value**, which must encode as a keyed container (a struct, class or dictionary). + public func encode(_ value: T) throws -> BSON { + let box = BSONNodeBox() + try _BSONEncoder.encodeValue(value, into: box, codingPath: [], userInfo: userInfo) + guard case .document(let storage)? = box.node else { + throw EncodingError.invalidValue(value, .init(codingPath: [], debugDescription: "Top-level \(T.self) did not encode as a document.")) + } + guard let doc = bson_new() else { + throw MongoError("bson_new failed") + } + let bson = BSON(rawBson: fromOpaque(doc)) + for (key, child) in storage.entries { + try Self.write(child.node, key: key, into: doc) + } + return bson + } + + private static func write(_ node: BSONEncodedNode?, key: String, into doc: UnsafeMutablePointer) throws { + let ok: Bool + switch node { + case .none: + ok = bson_append_null(doc, key, -1) + case .primitive(let primitive)?: + ok = write(primitive, key: key, into: doc) + case .document(let storage)?: + let child = UnsafeMutablePointer.allocate(capacity: 1) + defer { child.deallocate() } + guard bson_append_document_begin(doc, key, -1, child) else { + throw MongoError("could not start document '\(key)'") + } + for (childKey, box) in storage.entries { + try write(box.node, key: childKey, into: child) + } + ok = bson_append_document_end(doc, child) + case .array(let storage)?: + let child = UnsafeMutablePointer.allocate(capacity: 1) + defer { child.deallocate() } + guard bson_append_array_unsafe_begin(doc, key, -1, child) else { + throw MongoError("could not start array '\(key)'") + } + for (index, box) in storage.boxes.enumerated() { + try write(box.node, key: String(index), into: child) + } + ok = bson_append_array_end(doc, child) + } + guard ok else { + throw MongoError("could not append '\(key)': document exceeds the maximum BSON size") + } + } + + private static func write(_ primitive: BSONPrimitive, key: String, into doc: UnsafeMutablePointer) -> Bool { + switch primitive { + case .null: + return bson_append_null(doc, key, -1) + case .bool(let value): + return bson_append_bool(doc, key, -1, value) + case .int32(let value): + return bson_append_int32(doc, key, -1, value) + case .int64(let value): + return bson_append_int64(doc, key, -1, value) + case .double(let value): + return bson_append_double(doc, key, -1, value) + case .string(let value): + return bson_append_utf8(doc, key, -1, value, Int32(value.utf8.count)) + case .dateTime(let millis): + return bson_append_date_time(doc, key, -1, millis) + case .binary(let data, let subtype): + return data.withUnsafeBytes { + bson_append_binary(doc, key, -1, subtype, $0.bindMemory(to: UInt8.self).baseAddress, UInt32(data.count)) + } + case .oid(var oid): + return bson_append_oid(doc, key, -1, &oid) + case .unsupported: + return false + } + } +} + +final class _BSONEncoder: Encoder { + let box: BSONNodeBox + let codingPath: [CodingKey] + let sendableUserInfo: [CodingUserInfoKey: any Sendable] + var userInfo: [CodingUserInfoKey: Any] { sendableUserInfo } + + init(box: BSONNodeBox, codingPath: [CodingKey], userInfo: [CodingUserInfoKey: any Sendable]) { + self.box = box + self.codingPath = codingPath + self.sendableUserInfo = userInfo + } + + /// The BSON value for types with a native BSON representation, or nil to use their Encodable conformance. + static func specialPrimitive(_ value: Any) -> BSONPrimitive? { + switch value { + case let date as Date: + return .dateTime(Int64((date.timeIntervalSince1970 * 1000).rounded(.down))) + case let data as Data: + return .binary(data, BSON_SUBTYPE_BINARY) + case let uuid as UUID: + let data = withUnsafeBytes(of: uuid.uuid) { Data($0) } + return .binary(data, BSON_SUBTYPE_UUID) + case let oid as BSON.OID: + return .oid(oid.oid) + default: + return nil + } + } + + static func encodeValue(_ value: T, into box: BSONNodeBox, codingPath: [CodingKey], userInfo: [CodingUserInfoKey: any Sendable]) throws { + if let primitive = specialPrimitive(value) { + box.node = .primitive(primitive) + return + } + try value.encode(to: _BSONEncoder(box: box, codingPath: codingPath, userInfo: userInfo)) + if box.node == nil { + // A type that encoded nothing at all, e.g. an empty struct. + box.node = .document(BSONDocumentStorage()) + } + } + + func documentStorage() -> BSONDocumentStorage { + if case .document(let storage)? = box.node { + return storage + } + let storage = BSONDocumentStorage() + box.node = .document(storage) + return storage + } + + func arrayStorage() -> BSONArrayStorage { + if case .array(let storage)? = box.node { + return storage + } + let storage = BSONArrayStorage() + box.node = .array(storage) + return storage + } + + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer { + KeyedEncodingContainer(BSONKeyedEncodingContainer(encoder: self, storage: documentStorage(), codingPath: codingPath)) + } + + func unkeyedContainer() -> UnkeyedEncodingContainer { + BSONUnkeyedEncodingContainer(encoder: self, storage: arrayStorage(), codingPath: codingPath) + } + + func singleValueContainer() -> SingleValueEncodingContainer { + BSONSingleValueEncodingContainer(encoder: self) + } +} + +/// Integer and floating-point encodings shared by the three container types. +private enum BSONNumber { + static func integer(_ value: T, codingPath: [CodingKey]) throws -> BSONPrimitive { + if T.bitWidth < 32 || T.self == Int32.self { + return .int32(Int32(value)) + } + guard let value = Int64(exactly: value) else { + throw EncodingError.invalidValue(value, .init(codingPath: codingPath, debugDescription: "\(value) does not fit in a BSON int64.")) + } + return .int64(value) + } +} + +struct BSONKeyedEncodingContainer: KeyedEncodingContainerProtocol { + let encoder: _BSONEncoder + let storage: BSONDocumentStorage + let codingPath: [CodingKey] + + private func set(_ primitive: BSONPrimitive, _ key: Key) { + storage.box(forKey: key.stringValue).node = .primitive(primitive) + } + + mutating func encodeNil(forKey key: Key) throws { set(.null, key) } + mutating func encode(_ value: Bool, forKey key: Key) throws { set(.bool(value), key) } + mutating func encode(_ value: String, forKey key: Key) throws { set(.string(value), key) } + mutating func encode(_ value: Double, forKey key: Key) throws { set(.double(value), key) } + mutating func encode(_ value: Float, forKey key: Key) throws { set(.double(Double(value)), key) } + mutating func encode(_ value: Int, forKey key: Key) throws { set(try BSONNumber.integer(value, codingPath: codingPath + [key]), key) } + mutating func encode(_ value: Int8, forKey key: Key) throws { set(try BSONNumber.integer(value, codingPath: codingPath + [key]), key) } + mutating func encode(_ value: Int16, forKey key: Key) throws { set(try BSONNumber.integer(value, codingPath: codingPath + [key]), key) } + mutating func encode(_ value: Int32, forKey key: Key) throws { set(try BSONNumber.integer(value, codingPath: codingPath + [key]), key) } + mutating func encode(_ value: Int64, forKey key: Key) throws { set(try BSONNumber.integer(value, codingPath: codingPath + [key]), key) } + mutating func encode(_ value: UInt, forKey key: Key) throws { set(try BSONNumber.integer(value, codingPath: codingPath + [key]), key) } + mutating func encode(_ value: UInt8, forKey key: Key) throws { set(try BSONNumber.integer(value, codingPath: codingPath + [key]), key) } + mutating func encode(_ value: UInt16, forKey key: Key) throws { set(try BSONNumber.integer(value, codingPath: codingPath + [key]), key) } + mutating func encode(_ value: UInt32, forKey key: Key) throws { set(try BSONNumber.integer(value, codingPath: codingPath + [key]), key) } + mutating func encode(_ value: UInt64, forKey key: Key) throws { set(try BSONNumber.integer(value, codingPath: codingPath + [key]), key) } + + mutating func encode(_ value: T, forKey key: Key) throws { + try _BSONEncoder.encodeValue(value, into: storage.box(forKey: key.stringValue), codingPath: codingPath + [key], userInfo: encoder.sendableUserInfo) + } + + mutating func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer { + superEncoder(forKey: key).container(keyedBy: keyType) + } + + mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer { + superEncoder(forKey: key).unkeyedContainer() + } + + mutating func superEncoder() -> Encoder { + _BSONEncoder(box: storage.box(forKey: "super"), codingPath: codingPath + [BSONKey(stringValue: "super")], userInfo: encoder.sendableUserInfo) + } + + mutating func superEncoder(forKey key: Key) -> Encoder { + _BSONEncoder(box: storage.box(forKey: key.stringValue), codingPath: codingPath + [key], userInfo: encoder.sendableUserInfo) + } +} + +struct BSONUnkeyedEncodingContainer: UnkeyedEncodingContainer { + let encoder: _BSONEncoder + let storage: BSONArrayStorage + let codingPath: [CodingKey] + var count: Int { storage.boxes.count } + + private var nextKey: CodingKey { BSONKey(intValue: count) } + + private func append(_ primitive: BSONPrimitive) { + storage.append().node = .primitive(primitive) + } + + mutating func encodeNil() throws { append(.null) } + mutating func encode(_ value: Bool) throws { append(.bool(value)) } + mutating func encode(_ value: String) throws { append(.string(value)) } + mutating func encode(_ value: Double) throws { append(.double(value)) } + mutating func encode(_ value: Float) throws { append(.double(Double(value))) } + mutating func encode(_ value: Int) throws { append(try BSONNumber.integer(value, codingPath: codingPath + [nextKey])) } + mutating func encode(_ value: Int8) throws { append(try BSONNumber.integer(value, codingPath: codingPath + [nextKey])) } + mutating func encode(_ value: Int16) throws { append(try BSONNumber.integer(value, codingPath: codingPath + [nextKey])) } + mutating func encode(_ value: Int32) throws { append(try BSONNumber.integer(value, codingPath: codingPath + [nextKey])) } + mutating func encode(_ value: Int64) throws { append(try BSONNumber.integer(value, codingPath: codingPath + [nextKey])) } + mutating func encode(_ value: UInt) throws { append(try BSONNumber.integer(value, codingPath: codingPath + [nextKey])) } + mutating func encode(_ value: UInt8) throws { append(try BSONNumber.integer(value, codingPath: codingPath + [nextKey])) } + mutating func encode(_ value: UInt16) throws { append(try BSONNumber.integer(value, codingPath: codingPath + [nextKey])) } + mutating func encode(_ value: UInt32) throws { append(try BSONNumber.integer(value, codingPath: codingPath + [nextKey])) } + mutating func encode(_ value: UInt64) throws { append(try BSONNumber.integer(value, codingPath: codingPath + [nextKey])) } + + mutating func encode(_ value: T) throws { + let key = nextKey + try _BSONEncoder.encodeValue(value, into: storage.append(), codingPath: codingPath + [key], userInfo: encoder.sendableUserInfo) + } + + mutating func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer { + superEncoder().container(keyedBy: keyType) + } + + mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { + superEncoder().unkeyedContainer() + } + + mutating func superEncoder() -> Encoder { + let key = nextKey + return _BSONEncoder(box: storage.append(), codingPath: codingPath + [key], userInfo: encoder.sendableUserInfo) + } +} + +struct BSONSingleValueEncodingContainer: SingleValueEncodingContainer { + let encoder: _BSONEncoder + var codingPath: [CodingKey] { encoder.codingPath } + + private func set(_ primitive: BSONPrimitive) { + encoder.box.node = .primitive(primitive) + } + + mutating func encodeNil() throws { set(.null) } + mutating func encode(_ value: Bool) throws { set(.bool(value)) } + mutating func encode(_ value: String) throws { set(.string(value)) } + mutating func encode(_ value: Double) throws { set(.double(value)) } + mutating func encode(_ value: Float) throws { set(.double(Double(value))) } + mutating func encode(_ value: Int) throws { set(try BSONNumber.integer(value, codingPath: codingPath)) } + mutating func encode(_ value: Int8) throws { set(try BSONNumber.integer(value, codingPath: codingPath)) } + mutating func encode(_ value: Int16) throws { set(try BSONNumber.integer(value, codingPath: codingPath)) } + mutating func encode(_ value: Int32) throws { set(try BSONNumber.integer(value, codingPath: codingPath)) } + mutating func encode(_ value: Int64) throws { set(try BSONNumber.integer(value, codingPath: codingPath)) } + mutating func encode(_ value: UInt) throws { set(try BSONNumber.integer(value, codingPath: codingPath)) } + mutating func encode(_ value: UInt8) throws { set(try BSONNumber.integer(value, codingPath: codingPath)) } + mutating func encode(_ value: UInt16) throws { set(try BSONNumber.integer(value, codingPath: codingPath)) } + mutating func encode(_ value: UInt32) throws { set(try BSONNumber.integer(value, codingPath: codingPath)) } + mutating func encode(_ value: UInt64) throws { set(try BSONNumber.integer(value, codingPath: codingPath)) } + + mutating func encode(_ value: T) throws { + try _BSONEncoder.encodeValue(value, into: encoder.box, codingPath: codingPath, userInfo: encoder.sendableUserInfo) + } +} + +struct BSONKey: CodingKey { + let stringValue: String + let intValue: Int? + + init(stringValue: String) { + self.stringValue = stringValue + self.intValue = nil + } + + init(intValue: Int) { + self.stringValue = String(intValue) + self.intValue = intValue + } +} + +// MARK: - Decoder + +/// Decodes Decodable values from BSON documents. +public struct BSONDecoder: Sendable { + /// Contextual information for custom `init(from:)` implementations. + public var userInfo: [CodingUserInfoKey: any Sendable] = [:] + + public init() {} + + public func decode(_ type: T.Type, from document: BSON) throws -> T { + guard let doc = document.doc else { + throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "The BSON document is closed.")) + } + return try decode(type, from: UnsafePointer(doc)) + } + + func decode(_ type: T.Type, from doc: UnsafePointer) throws -> T { + var iter = bson_iter_t() + guard bson_iter_init(&iter, doc) else { + throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Invalid BSON document.")) + } + let root = BSONNode.document(try Self.readDocument(&iter)) + return try _BSONDecoder.decodeValue(type, from: root, codingPath: [], userInfo: userInfo) + } + + private static func readDocument(_ iter: inout bson_iter_t) throws -> [(String, BSONNode)] { + var entries: [(String, BSONNode)] = [] + while bson_iter_next(&iter) { + let key = String(cString: bson_iter_key(&iter)) + entries.append((key, try readValue(&iter))) + } + return entries + } + + private static func readValue(_ iter: inout bson_iter_t) throws -> BSONNode { + let type = bson_iter_type(&iter) + switch type { + case BSON_TYPE_DOCUMENT, BSON_TYPE_ARRAY: + var child = bson_iter_t() + guard bson_iter_recurse(&iter, &child) else { + throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Invalid nested BSON.")) + } + let entries = try readDocument(&child) + return type == BSON_TYPE_ARRAY ? .array(entries.map { $0.1 }) : .document(entries) + case BSON_TYPE_NULL, BSON_TYPE_UNDEFINED: + return .primitive(.null) + case BSON_TYPE_BOOL: + return .primitive(.bool(bson_iter_bool(&iter))) + case BSON_TYPE_INT32: + return .primitive(.int32(bson_iter_int32(&iter))) + case BSON_TYPE_INT64: + return .primitive(.int64(bson_iter_int64(&iter))) + case BSON_TYPE_DOUBLE: + return .primitive(.double(bson_iter_double(&iter))) + case BSON_TYPE_UTF8: + var length: UInt32 = 0 + let pointer = bson_iter_utf8(&iter, &length) + let bytes = UnsafeRawBufferPointer(start: pointer, count: Int(length)) + return .primitive(.string(String(decoding: bytes, as: UTF8.self))) + case BSON_TYPE_DATE_TIME: + return .primitive(.dateTime(bson_iter_date_time(&iter))) + case BSON_TYPE_OID: + return .primitive(.oid(bson_iter_oid(&iter).pointee)) + case BSON_TYPE_BINARY: + var subtype = BSON_SUBTYPE_BINARY + var length: UInt32 = 0 + var pointer: UnsafePointer? = nil + bson_iter_binary(&iter, &subtype, &length, &pointer) + let data = pointer.map { Data(bytes: $0, count: Int(length)) } ?? Data() + return .primitive(.binary(data, subtype)) + default: + let name = typeName(type) + return .primitive(.unsupported(name)) + } + } + + private static func typeName(_ type: bson_type_t) -> String { + switch type { + case BSON_TYPE_DECIMAL128: return "decimal128" + case BSON_TYPE_TIMESTAMP: return "timestamp" + case BSON_TYPE_REGEX: return "regex" + case BSON_TYPE_CODE, BSON_TYPE_CODEWSCOPE: return "JavaScript code" + case BSON_TYPE_SYMBOL: return "symbol" + case BSON_TYPE_DBPOINTER: return "DBPointer" + case BSON_TYPE_MINKEY: return "minKey" + case BSON_TYPE_MAXKEY: return "maxKey" + default: return "BSON type 0x\(String(type.rawValue, radix: 16))" + } + } +} + +final class _BSONDecoder: Decoder { + let node: BSONNode + let codingPath: [CodingKey] + let sendableUserInfo: [CodingUserInfoKey: any Sendable] + var userInfo: [CodingUserInfoKey: Any] { sendableUserInfo } + + init(node: BSONNode, codingPath: [CodingKey], userInfo: [CodingUserInfoKey: any Sendable]) { + self.node = node + self.codingPath = codingPath + self.sendableUserInfo = userInfo + } + + static func decodeValue(_ type: T.Type, from node: BSONNode, codingPath: [CodingKey], userInfo: [CodingUserInfoKey: any Sendable]) throws -> T { + if let special = try specialValue(type, from: node, codingPath: codingPath) { + return special + } + return try T(from: _BSONDecoder(node: node, codingPath: codingPath, userInfo: userInfo)) + } + + /// Decodes types with a native BSON representation; nil means use their Decodable conformance. + private static func specialValue(_ type: T.Type, from node: BSONNode, codingPath: [CodingKey]) throws -> T? { + func mismatch(_ expected: String) -> DecodingError { + .typeMismatch(T.self, .init(codingPath: codingPath, debugDescription: "Expected \(expected), found \(node.typeDescription).")) + } + if T.self == Date.self { + guard case .primitive(.dateTime(let millis)) = node else { throw mismatch("a BSON datetime") } + return (Date(timeIntervalSince1970: Double(millis) / 1000) as! T) + } + if T.self == Data.self { + guard case .primitive(.binary(let data, _)) = node else { throw mismatch("BSON binary data") } + return (data as! T) + } + if T.self == UUID.self { + guard case .primitive(.binary(let data, let subtype)) = node, + subtype == BSON_SUBTYPE_UUID || subtype == BSON_SUBTYPE_UUID_DEPRECATED, data.count == 16 else { + throw mismatch("a BSON UUID (binary subtype 4)") + } + var uuid: uuid_t = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + withUnsafeMutableBytes(of: &uuid) { $0.copyBytes(from: data) } + return (UUID(uuid: uuid) as! T) + } + if T.self == BSON.OID.self { + switch node { + case .primitive(.oid(let oid)): + return (BSON.OID(oid: oid) as! T) + case .primitive(.string(let string)): + guard let oid = BSON.OID(validating: string) else { throw mismatch("an ObjectId") } + return (oid as! T) + default: + throw mismatch("an ObjectId") + } + } + return nil + } + + func container(keyedBy type: Key.Type) throws -> KeyedDecodingContainer { + guard case .document(let entries) = node else { + throw DecodingError.typeMismatch([String: Any].self, .init(codingPath: codingPath, debugDescription: "Expected a document, found \(node.typeDescription).")) + } + return KeyedDecodingContainer(BSONKeyedDecodingContainer(decoder: self, entries: entries)) + } + + func unkeyedContainer() throws -> UnkeyedDecodingContainer { + guard case .array(let values) = node else { + throw DecodingError.typeMismatch([Any].self, .init(codingPath: codingPath, debugDescription: "Expected an array, found \(node.typeDescription).")) + } + return BSONUnkeyedDecodingContainer(decoder: self, values: values) + } + + func singleValueContainer() throws -> SingleValueDecodingContainer { + BSONSingleValueDecodingContainer(decoder: self) + } +} + +extension BSONNode { + var typeDescription: String { + switch self { + case .document: return "a document" + case .array: return "an array" + case .primitive(let primitive): + switch primitive { + case .null: return "null" + case .bool: return "a boolean" + case .int32: return "an int32" + case .int64: return "an int64" + case .double: return "a double" + case .string: return "a string" + case .dateTime: return "a datetime" + case .binary: return "binary data" + case .oid: return "an ObjectId" + case .unsupported(let name): return "\(name) (not supported by BSONDecoder)" + } + } + } + + var isNull: Bool { + if case .primitive(.null) = self { + return true + } + return false + } + + func decodePrimitive(_ type: T.Type, codingPath: [CodingKey]) throws -> T { + func mismatch() -> DecodingError { + if isNull { + return .valueNotFound(T.self, .init(codingPath: codingPath, debugDescription: "Expected \(T.self), found null.")) + } + return .typeMismatch(T.self, .init(codingPath: codingPath, debugDescription: "Expected \(T.self), found \(typeDescription).")) + } + func inexact(_ value: Any) -> DecodingError { + .dataCorrupted(.init(codingPath: codingPath, debugDescription: "\(value) does not fit exactly in \(T.self).")) + } + guard case .primitive(let primitive) = self else { + throw mismatch() + } + switch T.self { + case is Bool.Type: + guard case .bool(let value) = primitive else { throw mismatch() } + return value as! T + case is String.Type: + guard case .string(let value) = primitive else { throw mismatch() } + return value as! T + case is Double.Type, is Float.Type: + let value: Double + switch primitive { + case .double(let v): value = v + case .int32(let v): value = Double(v) + case .int64(let v): + guard let v = Double(exactly: v) else { throw inexact(v) } + value = v + default: throw mismatch() + } + if T.self == Float.self { + return Float(value) as! T + } + return value as! T + default: + guard let integerType = T.self as? any FixedWidthInteger.Type else { + throw mismatch() + } + func convert(_ target: I.Type) throws -> I { + switch primitive { + case .int32(let v): + guard let result = I(exactly: v) else { throw inexact(v) } + return result + case .int64(let v): + guard let result = I(exactly: v) else { throw inexact(v) } + return result + case .double(let v): + guard let result = I(exactly: v) else { throw inexact(v) } + return result + default: + throw mismatch() + } + } + return try convert(integerType) as! T + } + } +} + +struct BSONKeyedDecodingContainer: KeyedDecodingContainerProtocol { + let decoder: _BSONDecoder + let values: [String: BSONNode] + let allKeys: [Key] + var codingPath: [CodingKey] { decoder.codingPath } + + init(decoder: _BSONDecoder, entries: [(String, BSONNode)]) { + self.decoder = decoder + var values: [String: BSONNode] = [:] + for (key, value) in entries where values[key] == nil { + values[key] = value + } + self.values = values + self.allKeys = entries.compactMap { Key(stringValue: $0.0) } + } + + func contains(_ key: Key) -> Bool { + values[key.stringValue] != nil + } + + private func node(_ key: Key) throws -> BSONNode { + guard let node = values[key.stringValue] else { + throw DecodingError.keyNotFound(key, .init(codingPath: codingPath, debugDescription: "No value for key '\(key.stringValue)'.")) + } + return node + } + + func decodeNil(forKey key: Key) throws -> Bool { + try node(key).isNull + } + + func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool { try node(key).decodePrimitive(type, codingPath: codingPath + [key]) } + func decode(_ type: String.Type, forKey key: Key) throws -> String { try node(key).decodePrimitive(type, codingPath: codingPath + [key]) } + func decode(_ type: Double.Type, forKey key: Key) throws -> Double { try node(key).decodePrimitive(type, codingPath: codingPath + [key]) } + func decode(_ type: Float.Type, forKey key: Key) throws -> Float { try node(key).decodePrimitive(type, codingPath: codingPath + [key]) } + func decode(_ type: Int.Type, forKey key: Key) throws -> Int { try node(key).decodePrimitive(type, codingPath: codingPath + [key]) } + func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 { try node(key).decodePrimitive(type, codingPath: codingPath + [key]) } + func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 { try node(key).decodePrimitive(type, codingPath: codingPath + [key]) } + func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 { try node(key).decodePrimitive(type, codingPath: codingPath + [key]) } + func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 { try node(key).decodePrimitive(type, codingPath: codingPath + [key]) } + func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt { try node(key).decodePrimitive(type, codingPath: codingPath + [key]) } + func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 { try node(key).decodePrimitive(type, codingPath: codingPath + [key]) } + func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 { try node(key).decodePrimitive(type, codingPath: codingPath + [key]) } + func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 { try node(key).decodePrimitive(type, codingPath: codingPath + [key]) } + func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 { try node(key).decodePrimitive(type, codingPath: codingPath + [key]) } + + func decode(_ type: T.Type, forKey key: Key) throws -> T { + try _BSONDecoder.decodeValue(type, from: try node(key), codingPath: codingPath + [key], userInfo: decoder.sendableUserInfo) + } + + func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer { + try _BSONDecoder(node: try node(key), codingPath: codingPath + [key], userInfo: decoder.sendableUserInfo).container(keyedBy: type) + } + + func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { + try _BSONDecoder(node: try node(key), codingPath: codingPath + [key], userInfo: decoder.sendableUserInfo).unkeyedContainer() + } + + func superDecoder() throws -> Decoder { + let key = BSONKey(stringValue: "super") + return _BSONDecoder(node: values["super"] ?? .primitive(.null), codingPath: codingPath + [key], userInfo: decoder.sendableUserInfo) + } + + func superDecoder(forKey key: Key) throws -> Decoder { + _BSONDecoder(node: values[key.stringValue] ?? .primitive(.null), codingPath: codingPath + [key], userInfo: decoder.sendableUserInfo) + } +} + +struct BSONUnkeyedDecodingContainer: UnkeyedDecodingContainer { + let decoder: _BSONDecoder + let values: [BSONNode] + var currentIndex = 0 + var codingPath: [CodingKey] { decoder.codingPath } + var count: Int? { values.count } + var isAtEnd: Bool { currentIndex >= values.count } + + init(decoder: _BSONDecoder, values: [BSONNode]) { + self.decoder = decoder + self.values = values + } + + private var currentKey: CodingKey { BSONKey(intValue: currentIndex) } + + private mutating func next(_ type: T.Type) throws -> BSONNode { + guard !isAtEnd else { + throw DecodingError.valueNotFound(type, .init(codingPath: codingPath + [currentKey], debugDescription: "Unkeyed container is at end.")) + } + defer { currentIndex += 1 } + return values[currentIndex] + } + + private mutating func primitive(_ type: T.Type) throws -> T { + let path = codingPath + [currentKey] + return try next(type).decodePrimitive(type, codingPath: path) + } + + mutating func decodeNil() throws -> Bool { + guard !isAtEnd else { + throw DecodingError.valueNotFound(Any?.self, .init(codingPath: codingPath + [currentKey], debugDescription: "Unkeyed container is at end.")) + } + if values[currentIndex].isNull { + currentIndex += 1 + return true + } + return false + } + + mutating func decode(_ type: Bool.Type) throws -> Bool { try primitive(type) } + mutating func decode(_ type: String.Type) throws -> String { try primitive(type) } + mutating func decode(_ type: Double.Type) throws -> Double { try primitive(type) } + mutating func decode(_ type: Float.Type) throws -> Float { try primitive(type) } + mutating func decode(_ type: Int.Type) throws -> Int { try primitive(type) } + mutating func decode(_ type: Int8.Type) throws -> Int8 { try primitive(type) } + mutating func decode(_ type: Int16.Type) throws -> Int16 { try primitive(type) } + mutating func decode(_ type: Int32.Type) throws -> Int32 { try primitive(type) } + mutating func decode(_ type: Int64.Type) throws -> Int64 { try primitive(type) } + mutating func decode(_ type: UInt.Type) throws -> UInt { try primitive(type) } + mutating func decode(_ type: UInt8.Type) throws -> UInt8 { try primitive(type) } + mutating func decode(_ type: UInt16.Type) throws -> UInt16 { try primitive(type) } + mutating func decode(_ type: UInt32.Type) throws -> UInt32 { try primitive(type) } + mutating func decode(_ type: UInt64.Type) throws -> UInt64 { try primitive(type) } + + mutating func decode(_ type: T.Type) throws -> T { + let path = codingPath + [currentKey] + return try _BSONDecoder.decodeValue(type, from: try next(type), codingPath: path, userInfo: decoder.sendableUserInfo) + } + + mutating func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer { + try superDecoder().container(keyedBy: type) + } + + mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { + try superDecoder().unkeyedContainer() + } + + mutating func superDecoder() throws -> Decoder { + let path = codingPath + [currentKey] + return _BSONDecoder(node: try next(Any.self), codingPath: path, userInfo: decoder.sendableUserInfo) + } +} + +struct BSONSingleValueDecodingContainer: SingleValueDecodingContainer { + let decoder: _BSONDecoder + var codingPath: [CodingKey] { decoder.codingPath } + + func decodeNil() -> Bool { decoder.node.isNull } + func decode(_ type: Bool.Type) throws -> Bool { try decoder.node.decodePrimitive(type, codingPath: codingPath) } + func decode(_ type: String.Type) throws -> String { try decoder.node.decodePrimitive(type, codingPath: codingPath) } + func decode(_ type: Double.Type) throws -> Double { try decoder.node.decodePrimitive(type, codingPath: codingPath) } + func decode(_ type: Float.Type) throws -> Float { try decoder.node.decodePrimitive(type, codingPath: codingPath) } + func decode(_ type: Int.Type) throws -> Int { try decoder.node.decodePrimitive(type, codingPath: codingPath) } + func decode(_ type: Int8.Type) throws -> Int8 { try decoder.node.decodePrimitive(type, codingPath: codingPath) } + func decode(_ type: Int16.Type) throws -> Int16 { try decoder.node.decodePrimitive(type, codingPath: codingPath) } + func decode(_ type: Int32.Type) throws -> Int32 { try decoder.node.decodePrimitive(type, codingPath: codingPath) } + func decode(_ type: Int64.Type) throws -> Int64 { try decoder.node.decodePrimitive(type, codingPath: codingPath) } + func decode(_ type: UInt.Type) throws -> UInt { try decoder.node.decodePrimitive(type, codingPath: codingPath) } + func decode(_ type: UInt8.Type) throws -> UInt8 { try decoder.node.decodePrimitive(type, codingPath: codingPath) } + func decode(_ type: UInt16.Type) throws -> UInt16 { try decoder.node.decodePrimitive(type, codingPath: codingPath) } + func decode(_ type: UInt32.Type) throws -> UInt32 { try decoder.node.decodePrimitive(type, codingPath: codingPath) } + func decode(_ type: UInt64.Type) throws -> UInt64 { try decoder.node.decodePrimitive(type, codingPath: codingPath) } + + func decode(_ type: T.Type) throws -> T { + try _BSONDecoder.decodeValue(type, from: decoder.node, codingPath: codingPath, userInfo: decoder.sendableUserInfo) + } +} diff --git a/Sources/PerfectMongoDB/MongoAsync.swift b/Sources/PerfectMongoDB/MongoAsync.swift new file mode 100644 index 0000000..77105f3 --- /dev/null +++ b/Sources/PerfectMongoDB/MongoAsync.swift @@ -0,0 +1,213 @@ +// +// MongoAsync.swift +// PerfectMongoDB +// +// async/await on top of the blocking libmongoc API. +// +// libmongoc calls block on network I/O, so they must never run on Swift's cooperative +// thread pool. As in Perfect-CRUD's AsyncExecution.swift, blocking work is dispatched to +// a dedicated concurrent queue behind a checked continuation: the caller's task stays +// suspended, and no extra unstructured Task is created per call. +// +// Thread-safety model: MongoClientPool is the only object shared across tasks. A client, +// and every database, collection and cursor made from it, is used by one piece of work at +// a time -- inside one `withClient` body, or by one async iterator whose `next()` calls +// never overlap. Nothing in the type system stops a caller from capturing the client out +// of `withClient` and using it afterwards; don't. +// +// Cancellation is best-effort, as in Perfect-CRUD: a blocking libmongoc call already in +// flight can't be interrupted. `MongoFindSequence` checks for cancellation between batches. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the Perfect.org open source project +// +// Copyright (c) 2015 - 2026 PerfectlySoft Inc. and the Perfect project authors +// Licensed under Apache License v2.0 +// +// See http://perfect.org/licensing.html for license information +// +//===----------------------------------------------------------------------===// +// + +import Dispatch +import PerfectCMongo + +enum MongoBlockingQueue { + static let shared = DispatchQueue(label: "PerfectMongoDB.blocking", qos: .userInitiated, attributes: .concurrent) +} + +func withMongoExecutor(_ body: @Sendable @escaping () throws -> T) async throws -> T { + try await withCheckedThrowingContinuation { continuation in + MongoBlockingQueue.shared.async { + do { + continuation.resume(returning: try body()) + } catch { + continuation.resume(throwing: error) + } + } + } +} + +public extension MongoClientPool { + /// Pops a client, runs **body** with it off the cooperative thread pool, and pushes the + /// client back, even if **body** throws. Waits for a free client when the pool is at its + /// maximum size. + /// + /// ```swift + /// let adults = try await pool.withClient { client in + /// try client.getCollection(databaseName: "app", collectionName: "users") + /// .find(User.self, filter: try BSON(json: #"{"age": {"$gte": 18}}"#)) + /// } + /// ``` + func withClient(_ body: @Sendable @escaping (MongoClient) throws -> T) async throws -> T { + try await withMongoExecutor { + let client = self.popClient() + defer { + self.pushClient(client) + } + return try body(client) + } + } + + /// Streams the documents in **collection** matching **filter**, decoded as **type**. + /// One pooled client is held for the whole iteration and returned to the pool when the + /// sequence ends, throws, or its iterator is released. + /// + /// ```swift + /// for try await user in pool.find(User.self, database: "app", collection: "users") { + /// print(user.name) + /// } + /// ``` + /// + /// - parameter batchSize: documents fetched and decoded per hop to the blocking queue. + func find(_ type: T.Type, + database: String, + collection: String, + filter: BSON = BSON(), + options: BSON? = nil, + batchSize: Int = 100, + decoder: BSONDecoder = BSONDecoder()) -> MongoFindSequence { + MongoFindSequence(pool: self, + database: database, + collection: collection, + filter: filter.asBytes, + options: options?.asBytes, + batchSize: max(1, batchSize), + decoder: decoder) + } +} + +/// An `AsyncSequence` of decoded documents from a find on a pooled client. +/// Create one with `MongoClientPool.find(_:database:collection:filter:options:batchSize:decoder:)`. +/// Each iteration runs its own query. +public struct MongoFindSequence: AsyncSequence, Sendable { + let pool: MongoClientPool + let database: String + let collection: String + let filter: [UInt8] + let options: [UInt8]? + let batchSize: Int + let decoder: BSONDecoder + + public func makeAsyncIterator() -> Iterator { + Iterator(state: MongoFindCursorState(sequence: self)) + } + + public struct Iterator: AsyncIteratorProtocol { + let state: MongoFindCursorState + var buffer: [Element] = [] + var index = 0 + + public mutating func next() async throws -> Element? { + if index == buffer.count { + try Task.checkCancellation() + let state = self.state + buffer = try await withMongoExecutor { try state.fetchBatch() } + index = 0 + if buffer.isEmpty { + return nil + } + } + defer { + index += 1 + } + return buffer[index] + } + } +} + +/// The libmongoc cursor behind one `MongoFindSequence` iteration. +/// +/// `@unchecked Sendable` because it moves between blocking work items, but it is only ever +/// touched by one at a time: an async iterator's `next()` calls never overlap. +final class MongoFindCursorState: @unchecked Sendable { + private let sequence: MongoFindSequence + private var client: MongoClient? + private var collection: MongoCollection? + private var cursor: OpaquePointer? + private var finished = false + + init(sequence: MongoFindSequence) { + self.sequence = sequence + } + + deinit { + finish() + } + + /// The next batch of up to `batchSize` documents; empty when the cursor is exhausted. + func fetchBatch() throws -> [Element] { + guard !finished else { + return [] + } + do { + let cursor = try openCursor() + var batch: [Element] = [] + var document: UnsafePointer? = nil + while batch.count < sequence.batchSize, mongoc_cursor_next(cursor, &document), let document { + batch.append(try sequence.decoder.decode(Element.self, from: document)) + } + if batch.count < sequence.batchSize { + var error = bson_error_t() + if mongoc_cursor_error(cursor, &error) { + throw MongoError(error) + } + finish() + } + return batch + } catch { + finish() + throw error + } + } + + private func openCursor() throws -> OpaquePointer { + if let cursor { + return cursor + } + let client = sequence.pool.popClient() + self.client = client + let collection = client.getCollection(databaseName: sequence.database, collectionName: sequence.collection) + self.collection = collection + let filter = BSON(bytes: sequence.filter) + let options = sequence.options.map { BSON(bytes: $0) } + guard let ptr = collection.ptr, + let cursor = mongoc_collection_find_with_opts(ptr, toOpaque(filter.doc), toOpaque(options?.doc), nil) else { + throw MongoError("find failed") + } + self.cursor = cursor + return cursor + } + + private func finish() { + finished = true + if let cursor { + mongoc_cursor_destroy(cursor) + self.cursor = nil + } + collection = nil + // Released pooled clients return to the pool. + client = nil + } +} diff --git a/Sources/PerfectMongoDB/MongoClient.swift b/Sources/PerfectMongoDB/MongoClient.swift index 415ddbe..44ab991 100644 --- a/Sources/PerfectMongoDB/MongoClient.swift +++ b/Sources/PerfectMongoDB/MongoClient.swift @@ -33,7 +33,7 @@ public enum MongoResult { var vError = error let message = withUnsafePointer(to: &vError.message) { return $0.withMemoryRebound(to: CChar.self, capacity: 0) { - String(validatingUTF8: $0) ?? "unknown error" + String(validatingCString: $0) ?? "unknown error" } } return .error(error.domain, error.code, message) @@ -49,6 +49,10 @@ public enum MongoClientError: Error { case initError(String) } +/// libmongoc 2 no longer initializes itself when loaded; mongoc_init() must run once +/// before any client, pool or URI is created. Globals are initialized lazily and exactly once. +let mongocInitialized: Void = mongoc_init() + public class MongoClient { var ptr = OpaquePointer(bitPattern: 0) @@ -65,22 +69,31 @@ public class MongoClient { * */ public init(uri: String) throws { + mongocInitialized guard let ptr = mongoc_client_new(uri) else { throw MongoClientError.initError("Could not parse URI '\(uri)'") } self.ptr = ptr } - init(pointer: OpaquePointer?) { + /// The pool a popped client belongs to. Pooled clients go back to the pool instead of being destroyed. + var pool: MongoClientPool? + + init(pointer: OpaquePointer?, pool: MongoClientPool? = nil) { ptr = pointer + self.pool = pool } - + deinit { close() } - /// terminate current Mongo Client connection + /// terminate current Mongo Client connection, or return it to its pool if it was popped from one public func close() { + if let pool = self.pool { + pool.pushClient(self) + return + } guard let ptr = self.ptr else { return } @@ -125,7 +138,12 @@ public class MongoClient { guard let doc = bson.doc else { return .error(1, 1, "Invalid BSON doc") } - guard mongoc_client_get_server_status(self.ptr, readPrefs, toOpaque(doc), &error) else { + let command = BSON() + defer { + command.close() + } + command.append(key: "serverStatus", int: 1) + guard mongoc_client_command_simple(self.ptr, "admin", toOpaque(command.doc), readPrefs, toOpaque(doc), &error) else { return Result.fromError(error) } return .replyDoc(bson) @@ -138,12 +156,12 @@ public class MongoClient { */ public func databaseNames() -> [String] { var ret = [String]() - guard let names = mongoc_client_get_database_names(self.ptr, nil) else { + guard let names = mongoc_client_get_database_names_with_opts(self.ptr, nil, nil) else { return ret } var curr = names while let currPtr = curr[0] { - ret.append(String(validatingUTF8: currPtr) ?? "") + ret.append(String(validatingCString: currPtr) ?? "") curr = curr.successor() } bson_strfreev(names) diff --git a/Sources/PerfectMongoDB/MongoCollection.swift b/Sources/PerfectMongoDB/MongoCollection.swift index b866dff..99cac09 100644 --- a/Sources/PerfectMongoDB/MongoCollection.swift +++ b/Sources/PerfectMongoDB/MongoCollection.swift @@ -65,7 +65,7 @@ public enum MongoUpdateFlag: Int { } /// Struct used to access Mongo Query options -public struct MongoQueryFlag: OptionSet { +public struct MongoQueryFlag: OptionSet, Sendable { public let rawValue: Int var queryFlags: mongoc_query_flags_t { @@ -82,7 +82,9 @@ public struct MongoQueryFlag: OptionSet { public static let none = MongoQueryFlag(MONGOC_QUERY_NONE) public static let tailableCursor = MongoQueryFlag(MONGOC_QUERY_TAILABLE_CURSOR) - public static let slaveOk = MongoQueryFlag(MONGOC_QUERY_SLAVE_OK) + public static let secondaryOk = MongoQueryFlag(MONGOC_QUERY_SECONDARY_OK) + @available(*, deprecated, renamed: "secondaryOk") + public static let slaveOk = secondaryOk public static let opLogReplay = MongoQueryFlag(MONGOC_QUERY_OPLOG_REPLAY) public static let noCursorTimeout = MongoQueryFlag(MONGOC_QUERY_NO_CURSOR_TIMEOUT) public static let awaitData = MongoQueryFlag(MONGOC_QUERY_AWAIT_DATA) @@ -107,133 +109,119 @@ public enum MongoRemoveFlag: Int { /// class to manage Mongo Geospatial indexing options public class MongoIndexOptionsGeo { - var rawOpt = UnsafeMutablePointer.allocate(capacity: 1) + let twodSphereVersion: UInt8? + let twodBitsPrecision: UInt8? + let twodLocationMin: Double? + let twodLocationMax: Double? + let haystackBucketSize: Double? public init(twodSphereVersion: UInt8? = nil, twodBitsPrecision: UInt8? = nil, twodLocationMin: Double? = nil, twodLocationMax: Double? = nil, haystackBucketSize: Double? = nil) { - mongoc_index_opt_geo_init(rawOpt) + self.twodSphereVersion = twodSphereVersion + self.twodBitsPrecision = twodBitsPrecision + self.twodLocationMin = twodLocationMin + self.twodLocationMax = twodLocationMax + self.haystackBucketSize = haystackBucketSize + } + + func append(to opts: BSON) { if let twodSphereVersion = twodSphereVersion { - rawOpt.pointee.twod_sphere_version = twodSphereVersion + opts.append(key: "2dsphereIndexVersion", int32: Int32(twodSphereVersion)) } if let twodBitsPrecision = twodBitsPrecision { - rawOpt.pointee.twod_bits_precision = twodBitsPrecision + opts.append(key: "bits", int32: Int32(twodBitsPrecision)) } if let twodLocationMin = twodLocationMin { - rawOpt.pointee.twod_location_min = twodLocationMin + opts.append(key: "min", double: twodLocationMin) } if let twodLocationMax = twodLocationMax { - rawOpt.pointee.twod_location_max = twodLocationMax + opts.append(key: "max", double: twodLocationMax) } if let haystackBucketSize = haystackBucketSize { - rawOpt.pointee.haystack_bucket_size = haystackBucketSize + opts.append(key: "bucketSize", double: haystackBucketSize) } } - - deinit { - rawOpt.deinitialize(count: 1) - rawOpt.deallocate() - } } /// class to manage Mongo indexing options +/// +/// `dropDups` and `storageOptions` are accepted for source compatibility but ignored: +/// MongoDB 3.0 removed `dropDups`, and the storage options were never sent to the server. public class MongoIndexOptions { - - var rawOpt = mongoc_index_opt_t() - - // who knows what the default options are. - // guard against the case where these values were set to something in the defaults. - // we don't want to free a pointer which isn't ours - var nameNil: Bool, defLangNil: Bool, langOverNil: Bool - var weightsDoc: BSON? - var geoOptions: MongoIndexOptionsGeo? - var storageOptions: UnsafeMutablePointer? + let name: String? + let background: Bool? + let unique: Bool? + let sparse: Bool? + let expireAfterSeconds: Int32? + let v: Int32? + let defaultLanguage: String? + let languageOverride: String? + let weights: BSON? + let geoOptions: MongoIndexOptionsGeo? public init(name: String? = nil, background: Bool? = nil, unique: Bool? = nil, dropDups: Bool? = nil, sparse: Bool? = nil, expireAfterSeconds: Int32? = nil, v: Int32? = nil, defaultLanguage: String? = nil, languageOverride: String? = nil, weights: BSON? = nil, geoOptions: MongoIndexOptionsGeo? = nil, storageOptions: MongoIndexStorageOptionType? = nil) { - mongoc_index_opt_init(&self.rawOpt) - - self.nameNil = self.rawOpt.name == nil - self.defLangNil = self.rawOpt.default_language == nil - self.langOverNil = self.rawOpt.language_override == nil + self.name = name + self.background = background + self.unique = unique + self.sparse = sparse + self.expireAfterSeconds = expireAfterSeconds + self.v = v + self.defaultLanguage = defaultLanguage + self.languageOverride = languageOverride + self.weights = weights + self.geoOptions = geoOptions + } + /// The index model options document for mongoc_index_model_new. + func optionsDocument(keys: BSON) -> BSON { + let opts = BSON() if let name = name { - self.nameNil = true - self.rawOpt.name = UnsafePointer(strdup(name)) + opts.append(key: "name", string: name) + } else if let kdoc = keys.doc, let generated = mongoc_collection_keys_to_index_string(toOpaque(kdoc)) { + opts.append(key: "name", string: String(cString: generated)) + bson_free(generated) } if let background = background { - self.rawOpt.background = background + opts.append(key: "background", bool: background) } if let unique = unique { - self.rawOpt.unique = unique - } - if let dropDups = dropDups { - self.rawOpt.drop_dups = dropDups + opts.append(key: "unique", bool: unique) } if let sparse = sparse { - self.rawOpt.sparse = sparse + opts.append(key: "sparse", bool: sparse) } if let expireAfterSeconds = expireAfterSeconds { - self.rawOpt.expire_after_seconds = expireAfterSeconds + opts.append(key: "expireAfterSeconds", int32: expireAfterSeconds) } if let v = v { - self.rawOpt.v = v + opts.append(key: "v", int32: v) } if let defaultLanguage = defaultLanguage { - self.defLangNil = true - self.rawOpt.default_language = UnsafePointer(strdup(defaultLanguage)) + opts.append(key: "default_language", string: defaultLanguage) } if let languageOverride = languageOverride { - self.langOverNil = true - self.rawOpt.language_override = UnsafePointer(strdup(languageOverride)) + opts.append(key: "language_override", string: languageOverride) } if let weights = weights { - self.weightsDoc = weights // reference this so the ptr doesn't disappear beneath us - self.rawOpt.weights = toOpaque(weights.doc) //UnsafePointer(weights.doc!) - } - if let geoOptions = geoOptions { - self.geoOptions = geoOptions - self.rawOpt.geo_options = geoOptions.rawOpt - } - if let storageOptions = storageOptions { - self.storageOptions = UnsafeMutablePointer.allocate(capacity: 1) - self.storageOptions!.pointee.type = Int32(storageOptions.rawValue) - } - } - - deinit { - if self.nameNil && self.rawOpt.name != nil { - free(UnsafeMutableRawPointer(mutating: self.rawOpt.name)) - } - if self.defLangNil && self.rawOpt.default_language != nil { - free(UnsafeMutableRawPointer(mutating: self.rawOpt.default_language)) - } - if self.langOverNil && self.rawOpt.language_override != nil { - free(UnsafeMutableRawPointer(mutating: self.rawOpt.language_override)) - } - if self.storageOptions != nil { - self.storageOptions!.deallocate() + opts.append(key: "weights", document: weights) } + geoOptions?.append(to: opts) + return opts } } /// Enum for storage options public enum MongoIndexStorageOptionType: UInt32 { case mmapV1, wiredTiger - - var mongoType: UInt32 { - switch self { - case .mmapV1: - return MONGOC_INDEX_STORAGE_OPT_MMAPV1.rawValue - case .wiredTiger: - return MONGOC_INDEX_STORAGE_OPT_WIREDTIGER.rawValue - } - } } /// The MongoCollection class public class MongoCollection { var ptr = OpaquePointer(bitPattern: 0) + /// The client or database this collection came from, kept alive while the collection is in use. + let owner: AnyObject? /// Result Status enum for a MongoDB event public typealias Result = MongoResult @@ -248,10 +236,12 @@ public class MongoCollection { */ public init(client: MongoClient, databaseName: String, collectionName: String) { self.ptr = mongoc_client_get_collection(client.ptr, databaseName, collectionName) + self.owner = client } - init(rawPtr: OpaquePointer?) { + init(rawPtr: OpaquePointer?, owner: AnyObject?) { self.ptr = rawPtr + self.owner = owner } deinit { @@ -283,7 +273,7 @@ public class MongoCollection { return .error(1, 1, "Invalid collection") } var error = bson_error_t() - let res = mongoc_collection_insert(ptr, flag.mongoFlag, toOpaque(doc), nil, &error) + let res = _perfect_collection_insert(ptr, flag.mongoFlag, toOpaque(doc), &error) guard res == true else { return Result.fromError(error) } @@ -301,7 +291,7 @@ public class MongoCollection { guard let ptr = self.ptr else { return .error(1, 1, "Invalid collection") } - let bulk = mongoc_collection_create_bulk_operation(ptr, true, nil) + let bulk = mongoc_collection_create_bulk_operation_with_opts(ptr, nil) var error = bson_error_t() var reply = bson_t() defer { @@ -312,9 +302,9 @@ public class MongoCollection { guard let doc = document.doc else { return .error(1, 1, "Invalid document") } - mongoc_bulk_operation_insert(bulk, toOpaque(doc)) - // no need to destroy because "public func close()" does it - // bson_destroy (doc) + guard mongoc_bulk_operation_insert_with_opts(bulk, toOpaque(doc), nil, &error) else { + return Result.fromError(error) + } } guard mongoc_bulk_operation_execute(bulk, toOpaque(&reply), &error) != 0 else { return Result.fromError(error) @@ -347,7 +337,7 @@ public class MongoCollection { return .error(1, 1, "Invalid collection") } var error = bson_error_t() - let res = mongoc_collection_update(ptr, flag.mongoFlag, toOpaque(sdoc), toOpaque(udoc), nil, &error) + let res = _perfect_collection_update(ptr, flag.mongoFlag, toOpaque(sdoc), toOpaque(udoc), &error) guard res == true else { return Result.fromError(error) } @@ -388,7 +378,7 @@ public class MongoCollection { guard let ptr = self.ptr else { return .error(1, 1, "Invalid collection") } - let bulk = mongoc_collection_create_bulk_operation(ptr, true, nil) + let bulk = mongoc_collection_create_bulk_operation_with_opts(ptr, nil) var error = bson_error_t() var reply = bson_t() defer { @@ -402,15 +392,9 @@ public class MongoCollection { guard let udoc = update.update.doc else { return .error(1, 1, "Invalid update document") } - mongoc_bulk_operation_update(bulk, toOpaque(sdoc), toOpaque(udoc), false) - // mongoc_bulk_operation_update_one(bulk, sdoc, udoc, true) - // mongoc_bulk_operation_update_one_with_opts(bulk, sdoc, udoc, nil, &error) - //mongoc_bulk_operation_update_many_with_opts(bulk, sdoc, udoc, nil, &error) - // mongoc_bulk_operation_replace_one(bulk, sdoc, udoc, false) - // Remongoc_bulk_operation_replace_one_with_opts(bulk, sdoc, udoc, nil, &error) - // no need to destroy because "public func close()" does it - // bson_destroy(sdoc) - // bson_destroy(udoc) + guard _perfect_bulk_operation_update(bulk, toOpaque(sdoc), toOpaque(udoc), &error) else { + return Result.fromError(error) + } } guard mongoc_bulk_operation_execute(bulk, toOpaque(&reply), &error) == 1 else { return Result.fromError(error) @@ -434,7 +418,7 @@ public class MongoCollection { return .error(1, 1, "Invalid collection") } var error = bson_error_t() - let res = mongoc_collection_remove(ptr, flag.mongoFlag, toOpaque(sdoc), nil, &error) + let res = _perfect_collection_remove(ptr, flag.mongoFlag, toOpaque(sdoc), &error) guard res == true else { return Result.fromError(error) } @@ -456,7 +440,7 @@ public class MongoCollection { return .error(1, 1, "Invalid collection") } var error = bson_error_t() - let res = mongoc_collection_save(ptr, toOpaque(sdoc), nil, &error) + let res = _perfect_collection_save(ptr, toOpaque(sdoc), &error) guard res == true else { return Result.fromError(error) } @@ -493,7 +477,7 @@ public class MongoCollection { guard let ptr = self.ptr else { return "" } - return String(validatingUTF8: mongoc_collection_get_name(ptr)) ?? "" + return String(validatingCString: mongoc_collection_get_name(ptr)) ?? "" } /** @@ -504,22 +488,29 @@ public class MongoCollection { * - returns: BSON document describing the relationship between the collection and its physical representation */ public func validate(full: Bool = false) -> Result { - let bson = BSON() + let command = BSON() defer { - bson.close() + command.close() } - bson.append(key: "full", bool: full) - let odoc = bson.doc - + command.append(key: "validate", string: self.name()) + command.append(key: "full", bool: full) + return runCommand(command) + } + + /// Runs **command** against this collection's database and returns the reply document. + func runCommand(_ command: BSON) -> Result { guard let ptr = self.ptr else { return .error(1, 1, "Invalid collection") + } + guard let cdoc = command.doc else { + return .error(1, 1, "Invalid command document") } var error = bson_error_t() let reply = BSON() guard let rdoc = reply.doc else { return .error(1, 1, "Invalid reply document") } - let res = mongoc_collection_validate(ptr, toOpaque(odoc), toOpaque(rdoc), &error) + let res = mongoc_collection_command_simple(ptr, toOpaque(cdoc), nil, toOpaque(rdoc), &error) guard res == true else { return Result.fromError(error) } @@ -543,19 +534,15 @@ public class MongoCollection { guard let odoc = options.doc else { return .error(1, 1, "Invalid options document") } - guard let ptr = self.ptr else { - return .error(1, 1, "Invalid collection") - } - var error = bson_error_t() - let reply = BSON() - guard let rdoc = reply.doc else { - return .error(1, 1, "Invalid reply document") - } - let res = mongoc_collection_stats(ptr, toOpaque(odoc), toOpaque(rdoc), &error) - guard res == true else { - return Result.fromError(error) + let command = BSON() + defer { + command.close() } - return .replyDoc(reply) + command.append(key: "collStats", string: self.name()) + guard let cdoc = command.doc, bson_concat(toOpaque(cdoc), toOpaque(odoc)) else { + return .error(1, 1, "Invalid options document") + } + return runCommand(command) } /** @@ -579,11 +566,11 @@ public class MongoCollection { guard let qdoc = query.doc else { return nil } - let cursor = mongoc_collection_find(ptr, flags.queryFlags, UInt32(skip), UInt32(limit), UInt32(batchSize), toOpaque(qdoc), toOpaque(fields?.doc), nil) + let cursor = _perfect_collection_find(ptr, flags.queryFlags, UInt32(skip), Int32(limit), UInt32(batchSize), toOpaque(qdoc), toOpaque(fields?.doc)) guard cursor != nil else { return nil } - return MongoCursor(rawPtr: cursor) + return MongoCursor(rawPtr: cursor, owner: self) } /** @@ -602,7 +589,7 @@ public class MongoCollection { // guard cursor != nil else { // return nil // } -// return MongoCursor(rawPtr: cursor) +// return MongoCursor(rawPtr: cursor, owner: self) // } /** @@ -620,8 +607,19 @@ public class MongoCollection { guard let kdoc = keys.doc else { return .error(1, 1, "Invalid keys document") } + let opts = options.optionsDocument(keys: keys) + defer { + opts.close() + } + guard let model = mongoc_index_model_new(toOpaque(kdoc), toOpaque(opts.doc)) else { + return .error(1, 1, "Invalid index options") + } + defer { + mongoc_index_model_destroy(model) + } + var models: [OpaquePointer?] = [model] var error = bson_error_t() - let res = mongoc_collection_create_index(ptr, toOpaque(kdoc), &options.rawOpt, &error) + let res = mongoc_collection_create_indexes_with_opts(ptr, &models, 1, nil, nil, &error) guard res == true else { return Result.fromError(error) } @@ -683,7 +681,7 @@ public class MongoCollection { return .error(1, 1, "Invalid query document") } var error = bson_error_t() - let ires = mongoc_collection_count(ptr, flags.queryFlags, toOpaque(qdoc), Int64(skip), Int64(limit), nil, &error) + let ires = _perfect_collection_count(ptr, flags.queryFlags, toOpaque(qdoc), Int64(skip), Int64(limit), &error) guard ires != -1 else { return Result.fromError(error) } @@ -715,7 +713,7 @@ public class MongoCollection { guard let rdoc = reply.doc else { return .error(1, 1, "Invalid reply document") } - let res = mongoc_collection_find_and_modify(ptr, toOpaque(query?.doc), toOpaque(sort?.doc), toOpaque(update?.doc), toOpaque(fields?.doc), remove, upsert, new, toOpaque(rdoc), &error) + let res = _perfect_collection_find_and_modify(ptr, toOpaque(query?.doc), toOpaque(sort?.doc), toOpaque(update?.doc), toOpaque(fields?.doc), remove, upsert, new, toOpaque(rdoc), &error) guard res == true else { return Result.fromError(error) } @@ -727,13 +725,9 @@ public class MongoCollection { * * - returns: BSON document with description of last transaction status */ + @available(*, deprecated, message: "libmongoc 2 no longer records the last error on the collection; this always returns an empty document. Check the Result returned by each operation instead.") public func getLastError() -> BSON { - guard let ptr = self.ptr else { - return BSON() - } - let reply = mongoc_collection_get_last_error(ptr) - return NoDestroyBSON(rawBson: UnsafeMutablePointer(mutating: reply)) - //return NoDestroyBSON(rawBson: fromOpaque(reply)) + return BSON() } /** @@ -767,7 +761,7 @@ public class MongoCollection { return nil } - return NoDestroyBSON(document: result) + return result } /** @@ -783,17 +777,15 @@ public class MongoCollection { * - returns: A cursor to the command execution result documents. */ public func command(command: BSON, fields: BSON? = nil, flags: MongoQueryFlag = MongoQueryFlag.none, skip: Int = 0, limit: Int = 0, batchSize: Int = 0) -> MongoCursor? { - guard let ptr = self.ptr else { - return nil - } - guard let cdoc = command.doc else { + // libmongoc 2 has no cursor-returning command API. The command's reply is returned + // as a one-document cursor; fields, skip, limit and batchSize never applied to commands. + guard self.ptr != nil, command.doc != nil else { return nil } - let cursor = mongoc_collection_command(ptr, flags.queryFlags, UInt32(skip), UInt32(limit), UInt32(batchSize), toOpaque(cdoc), toOpaque(fields?.doc), nil) - guard cursor != nil else { - return nil + guard case .replyDoc(let reply) = runCommand(command) else { + return MongoCursor(documents: []) } - return MongoCursor(rawPtr: cursor) + return MongoCursor(documents: [reply]) } /** @@ -819,6 +811,6 @@ public class MongoCollection { return nil } - return MongoCursor(rawPtr: cursor) + return MongoCursor(rawPtr: cursor, owner: self) } } diff --git a/Sources/PerfectMongoDB/MongoCollectionCodable.swift b/Sources/PerfectMongoDB/MongoCollectionCodable.swift new file mode 100644 index 0000000..92889ec --- /dev/null +++ b/Sources/PerfectMongoDB/MongoCollectionCodable.swift @@ -0,0 +1,189 @@ +// +// MongoCollectionCodable.swift +// PerfectMongoDB +// +// Typed, throwing collection operations built on BSONEncoder/BSONDecoder. These sit +// alongside the original BSON/MongoResult API; filters and options are BSON documents +// as in the MongoDB manual, e.g. `try BSON(json: #"{"age": {"$gt": 30}}"#)`. +// +//===----------------------------------------------------------------------===// +// +// This source file is part of the Perfect.org open source project +// +// Copyright (c) 2015 - 2026 PerfectlySoft Inc. and the Perfect project authors +// Licensed under Apache License v2.0 +// +// See http://perfect.org/licensing.html for license information +// +//===----------------------------------------------------------------------===// +// + +import PerfectCMongo + +public extension MongoCollection { + /// Inserts **value** as a new document. + func insert(_ value: T, encoder: BSONEncoder = BSONEncoder()) throws { + let ptr = try validPointer() + let document = try encoder.encode(value) + var error = bson_error_t() + guard mongoc_collection_insert_one(ptr, toOpaque(document.doc), nil, nil, &error) else { + throw MongoError(error) + } + } + + /// Inserts **values** in one ordered bulk write. + func insert(contentsOf values: [T], encoder: BSONEncoder = BSONEncoder()) throws { + guard !values.isEmpty else { + return + } + let ptr = try validPointer() + let documents = try values.map { try encoder.encode($0) } + var pointers: [UnsafePointer?] = documents.map { toOpaque($0.doc) } + var error = bson_error_t() + guard mongoc_collection_insert_many(ptr, &pointers, pointers.count, nil, nil, &error) else { + throw MongoError(error) + } + withExtendedLifetime(documents) {} + } + + /// Returns every document matching **filter**, decoded as **type**. + /// **options** takes find options such as `sort`, `projection`, `skip` and `limit`. + func find(_ type: T.Type, filter: BSON = BSON(), options: BSON? = nil, decoder: BSONDecoder = BSONDecoder()) throws -> [T] { + let ptr = try validPointer() + guard let cursor = mongoc_collection_find_with_opts(ptr, toOpaque(filter.doc), toOpaque(options?.doc), nil) else { + throw MongoError("find failed") + } + defer { + mongoc_cursor_destroy(cursor) + } + var results: [T] = [] + var document: UnsafePointer? = nil + while mongoc_cursor_next(cursor, &document), let document { + results.append(try decoder.decode(type, from: document)) + } + var error = bson_error_t() + if mongoc_cursor_error(cursor, &error) { + throw MongoError(error) + } + return results + } + + /// Returns the first document matching **filter**, decoded as **type**, or nil if none match. + func findOne(_ type: T.Type, filter: BSON = BSON(), options: BSON? = nil, decoder: BSONDecoder = BSONDecoder()) throws -> T? { + let limited = BSON() + defer { + limited.close() + } + if let options, let odoc = options.doc { + bson_concat(toOpaque(limited.doc), toOpaque(odoc)) + } + limited.append(key: "limit", int: 1) + return try find(type, filter: filter, options: limited, decoder: decoder).first + } + + /// Replaces the first document matching **filter** with **value**, inserting it when + /// **upsert** is true and nothing matches. Returns the number of documents matched. + @discardableResult + func replaceOne(filter: BSON, with value: T, upsert: Bool = false, encoder: BSONEncoder = BSONEncoder()) throws -> Int { + let ptr = try validPointer() + let replacement = try encoder.encode(value) + let opts = BSON() + defer { + opts.close() + } + opts.append(key: "upsert", bool: upsert) + return try writeCount("matchedCount") { reply, error in + mongoc_collection_replace_one(ptr, toOpaque(filter.doc), toOpaque(replacement.doc), toOpaque(opts.doc), reply, error) + } + } + + /// Applies the update operators in **update** (e.g. `{"$set": {...}}`) to the first + /// document matching **filter**. Returns the number of documents matched. + @discardableResult + func updateOne(filter: BSON, update: BSON, upsert: Bool = false) throws -> Int { + let ptr = try validPointer() + let opts = BSON() + defer { + opts.close() + } + opts.append(key: "upsert", bool: upsert) + return try writeCount("matchedCount") { reply, error in + mongoc_collection_update_one(ptr, toOpaque(filter.doc), toOpaque(update.doc), toOpaque(opts.doc), reply, error) + } + } + + /// Applies the update operators in **update** to every document matching **filter**. + /// Returns the number of documents matched. + @discardableResult + func updateMany(filter: BSON, update: BSON, upsert: Bool = false) throws -> Int { + let ptr = try validPointer() + let opts = BSON() + defer { + opts.close() + } + opts.append(key: "upsert", bool: upsert) + return try writeCount("matchedCount") { reply, error in + mongoc_collection_update_many(ptr, toOpaque(filter.doc), toOpaque(update.doc), toOpaque(opts.doc), reply, error) + } + } + + /// Deletes the first document matching **filter**. Returns the number deleted (0 or 1). + @discardableResult + func deleteOne(filter: BSON) throws -> Int { + let ptr = try validPointer() + return try writeCount("deletedCount") { reply, error in + mongoc_collection_delete_one(ptr, toOpaque(filter.doc), nil, reply, error) + } + } + + /// Deletes every document matching **filter**. Returns the number deleted. + @discardableResult + func deleteMany(filter: BSON) throws -> Int { + let ptr = try validPointer() + return try writeCount("deletedCount") { reply, error in + mongoc_collection_delete_many(ptr, toOpaque(filter.doc), nil, reply, error) + } + } + + /// The number of documents matching **filter**. + func countDocuments(filter: BSON = BSON()) throws -> Int { + let ptr = try validPointer() + var error = bson_error_t() + let count = mongoc_collection_count_documents(ptr, toOpaque(filter.doc), nil, nil, nil, &error) + guard count >= 0 else { + throw MongoError(error) + } + return Int(count) + } +} + +extension MongoCollection { + func validPointer() throws -> OpaquePointer { + guard let ptr = self.ptr else { + throw MongoError("Invalid collection") + } + return ptr + } + + /// Runs a write that fills a reply document and returns the reply's integer **field**. + func writeCount(_ field: String, _ write: (UnsafeMutablePointer, UnsafeMutablePointer) -> Bool) throws -> Int { + let reply = UnsafeMutablePointer.allocate(capacity: 1) + defer { + reply.deallocate() + } + var error = bson_error_t() + let ok = write(reply, &error) + // libmongoc always initializes the reply, even on failure. + defer { + bson_destroy(reply) + } + guard ok else { + throw MongoError(error) + } + var iter = bson_iter_t() + guard bson_iter_init_find(&iter, reply, field) else { + return 0 + } + return Int(bson_iter_as_int64(&iter)) + } +} diff --git a/Sources/PerfectMongoDB/MongoConnectionPool.swift b/Sources/PerfectMongoDB/MongoConnectionPool.swift index 081d69f..d778274 100644 --- a/Sources/PerfectMongoDB/MongoConnectionPool.swift +++ b/Sources/PerfectMongoDB/MongoConnectionPool.swift @@ -19,8 +19,10 @@ import PerfectCMongo -/// Allows connection pooling. This class is thread-safe. -public class MongoClientPool { +/// Allows connection pooling. This class is thread-safe: libmongoc's client pool +/// synchronizes popping and pushing, so one pool can be shared across tasks and threads. +/// The clients it hands out are not; use each from one task at a time. +public final class MongoClientPool: @unchecked Sendable { var ptr = OpaquePointer(bitPattern: 0) /** @@ -28,10 +30,32 @@ public class MongoClientPool { * * - parameter uri: String uri to connect client pool */ - public init(uri: String) { - - let uriPointer = mongoc_uri_new(uri) - ptr = mongoc_client_pool_new(uriPointer) + /// Traps with the error if **uri** is not a valid MongoDB connection string. + /// Use `init(validatingURI:)` to handle that case instead. + public convenience init(uri: String) { + do { + try self.init(validatingURI: uri) + } catch { + fatalError("MongoClientPool: \(error)") + } + } + + /// Creates a pool, throwing `MongoError` if **uri** is not a valid MongoDB connection + /// string or its options (for example TLS settings) are rejected. Connecting happens + /// later, when a client is first used. + public init(validatingURI uri: String) throws { + mongocInitialized + var error = bson_error_t() + guard let uriPointer = mongoc_uri_new_with_error(uri, &error) else { + throw MongoError(error) + } + defer { + mongoc_uri_destroy(uriPointer) + } + guard let pool = mongoc_client_pool_new_with_error(uriPointer, &error) else { + throw MongoError(error) + } + ptr = pool } deinit { @@ -46,20 +70,21 @@ public class MongoClientPool { * - returns: nil if no client connection is currently queued for reuse. */ public func tryPopClient() -> MongoClient? { - let clientPointer = mongoc_client_pool_try_pop(ptr) - if clientPointer != nil { - return MongoClient(pointer: mongoc_client_pool_pop(clientPointer)) + guard let clientPointer = mongoc_client_pool_try_pop(ptr) else { + return nil } - return nil + return MongoClient(pointer: clientPointer, pool: self) } - + /** * Pop a client connection from the connection pool. * + * A popped client goes back to the pool when you push it or when it is released. + * * - returns: MongoClient from connection pool */ public func popClient() -> MongoClient { - return MongoClient(pointer: mongoc_client_pool_pop(ptr)) + return MongoClient(pointer: mongoc_client_pool_pop(ptr), pool: self) } /** @@ -68,8 +93,12 @@ public class MongoClientPool { * - parameter client: MongoClient to be pushed back into pool */ public func pushClient(_ client: MongoClient) { - mongoc_client_pool_push(ptr, client.ptr) + guard let clientPointer = client.ptr else { + return + } + mongoc_client_pool_push(ptr, clientPointer) client.ptr = nil + client.pool = nil } /** diff --git a/Sources/PerfectMongoDB/MongoCursor.swift b/Sources/PerfectMongoDB/MongoCursor.swift index 9d5e1a2..76ec98d 100644 --- a/Sources/PerfectMongoDB/MongoCursor.swift +++ b/Sources/PerfectMongoDB/MongoCursor.swift @@ -36,8 +36,19 @@ public class MongoCursor: Sequence, IteratorProtocol { return "[\(results.joined(separator: ","))]" } - init(rawPtr: OpaquePointer?) { + /// Documents served in place of a libmongoc cursor, for command replies. + var documents: [BSON] = [] + + /// The collection this cursor came from, kept alive while the cursor is in use. + var owner: AnyObject? + + init(rawPtr: OpaquePointer?, owner: AnyObject? = nil) { self.ptr = rawPtr + self.owner = owner + } + + init(documents: [BSON]) { + self.documents = documents } deinit { @@ -50,12 +61,13 @@ public class MongoCursor: Sequence, IteratorProtocol { mongoc_cursor_destroy(self.ptr!) self.ptr = nil } + documents.removeAll() } /// - returns: next document if available, else nil public func next() -> BSON? { guard let ptr = self.ptr else { - return nil + return documents.isEmpty ? nil : documents.removeFirst() } var bson = UnsafeRawPointer(nil as OpaquePointer?) diff --git a/Sources/PerfectMongoDB/MongoDatabase.swift b/Sources/PerfectMongoDB/MongoDatabase.swift index e9a4fb9..cef0c7e 100644 --- a/Sources/PerfectMongoDB/MongoDatabase.swift +++ b/Sources/PerfectMongoDB/MongoDatabase.swift @@ -23,6 +23,8 @@ import PerfectCMongo public class MongoDatabase { var ptr = OpaquePointer(bitPattern: 0) + /// The client this database came from, kept alive while the database is in use. + let client: MongoClient public typealias Result = MongoResult @@ -36,6 +38,7 @@ public class MongoDatabase { */ public init(client: MongoClient, databaseName: String) { self.ptr = mongoc_client_get_database(client.ptr, databaseName) + self.client = client } deinit { @@ -67,7 +70,7 @@ public class MongoDatabase { guard let ptr = self.ptr else { return "" } - return String(validatingUTF8: mongoc_database_get_name(ptr)) ?? "" + return String(validatingCString: mongoc_database_get_name(ptr)) ?? "" } /** @@ -86,7 +89,7 @@ public class MongoDatabase { guard let col = mongoc_database_create_collection(ptr, collectionName, toOpaque(options?.doc), &error) else { return Result.fromError(error) } - return .replyCollection(MongoCollection(rawPtr: col)) + return .replyCollection(MongoCollection(rawPtr: col, owner: self)) } /** @@ -101,7 +104,7 @@ public class MongoDatabase { return nil } let col = mongoc_database_get_collection(ptr, collectionName) - return MongoCollection(rawPtr: col) + return MongoCollection(rawPtr: col, owner: self) } /// - returns: String Array of current database collections' names @@ -110,12 +113,12 @@ public class MongoDatabase { guard let ptr = self.ptr else { return ret } - guard let names = mongoc_database_get_collection_names(ptr, nil) else { + guard let names = mongoc_database_get_collection_names_with_opts(ptr, nil, nil) else { return ret } var curr = names while let pointee = curr.pointee { - ret.append(String(validatingUTF8: pointee) ?? "") + ret.append(String(validatingCString: pointee) ?? "") curr = curr.successor() } bson_strfreev(names) diff --git a/Sources/PerfectMongoDB/MongoGridFS.swift b/Sources/PerfectMongoDB/MongoGridFS.swift index 4b69a2e..dcb8f6f 100644 --- a/Sources/PerfectMongoDB/MongoGridFS.swift +++ b/Sources/PerfectMongoDB/MongoGridFS.swift @@ -26,6 +26,9 @@ public class GridFile { /// inner pointer of gridfs file handle private var _fp: OpaquePointer? + + /// The GridFS this file came from, kept alive while the file is in use. + var owner: AnyObject? /// error info for internal usage var error = bson_error_t() @@ -183,7 +186,9 @@ public class GridFile { @discardableResult public func download(to: String) throws -> Int { // open the local file to write in binary - let fp = fopen(to, "wb") + guard let fp = fopen(to, "wb") else { + throw MongoClientError.initError("gridfs.file.write(\(to)) cannot open destination") + } // create a new file on gridfs let stream = mongoc_stream_gridfs_new(_fp) // check result @@ -192,12 +197,12 @@ public class GridFile { var iov = mongoc_iovec_t() // set transfer buffer to 4k, as default in network traffic iov.iov_len = 4096 - // safely alloc a well managed 4k buffer without worrying about GC - var bytes = [UInt8](repeating:0, count: iov.iov_len) - // assign the buffer to iov structur - let _ = bytes.withUnsafeMutableBufferPointer { - iov.iov_base = unsafeBitCast($0.baseAddress, to: UnsafeMutableRawPointer.self) + // a 4k transfer buffer that stays valid for the whole loop + let buffer = UnsafeMutableRawPointer.allocate(byteCount: iov.iov_len, alignment: 1) + defer { + buffer.deallocate() } + iov.iov_base = buffer // bytes to go var total = 0 // verify the R/W operation @@ -339,6 +344,9 @@ public class GridFS { /// mongoc_gridfs_t for handle the api private var handle: OpaquePointer? + + /// The client this GridFS came from, kept alive while the GridFS is in use. + private let client: MongoClient /// error structure for internal usage var error = bson_error_t() @@ -351,6 +359,7 @@ public class GridFS { /// - throws: /// MongoClientError, if failed to get the expected handle public init(client: MongoClient, database: String, prefix: String? = nil) throws { + self.client = client /// get gridfs handle from a mongo client handle = mongoc_client_get_gridfs(client.ptr, database, prefix, &error) guard handle != nil else { @@ -393,9 +402,9 @@ public class GridFS { // perform actually query var plist: OpaquePointer? if filter == nil { - plist = mongoc_gridfs_find(handle, toOpaque(query.doc)) + plist = _perfect_gridfs_find(handle, toOpaque(query.doc)) } else { - plist = mongoc_gridfs_find(handle, toOpaque(filter?.doc)) + plist = _perfect_gridfs_find(handle, toOpaque(filter?.doc)) } guard plist != nil else { @@ -419,6 +428,7 @@ public class GridFS { // construct a grid file object from the mongoc_grid_file_t pointer do { let f = try GridFile(file) + f.owner = self // add the new file object to the array ret.append(f) } catch (let e) { @@ -503,7 +513,9 @@ public class GridFS { // upload the file let save = mongoc_gridfs_file_save(file) if save { - return try GridFile(file) + let f = try GridFile(file) + f.owner = self + return f } else { mongoc_gridfs_file_destroy(file) throw MongoClientError.initError("gridfs.upload(\(from)): destination \(to) failed to save") @@ -535,7 +547,9 @@ public class GridFS { /// MongoClientError if failed or not found @discardableResult public func search(name: String) throws -> GridFile { - return try GridFile(gridFS: handle, from: name) + let f = try GridFile(gridFS: handle, from: name) + f.owner = self + return f } /// delete a file from the server diff --git a/Sources/PerfectMongoDB/MongoJSONConvertables.swift b/Sources/PerfectMongoDB/MongoJSONConvertables.swift index 3bc0e40..bdac07b 100644 --- a/Sources/PerfectMongoDB/MongoJSONConvertables.swift +++ b/Sources/PerfectMongoDB/MongoJSONConvertables.swift @@ -18,11 +18,14 @@ // import Foundation -import PerfectLib -/// This file is meant for MongoDB specific JSONConvertible types +/// MongoDB extended JSON encodings for Foundation types. +/// +/// These used to conform to PerfectLib's `JSONConvertible`. The package no longer +/// depends on PerfectLib; code that needs the conformance can restore it with +/// `extension Date: JSONConvertible {}`. -extension Date: JSONConvertible { +extension Date { public func jsonEncodedString() throws -> String { return "{\"$date\":{\"$numberLong\":\"\(Int64(self.timeIntervalSince1970 * 1000))\"}}" } diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift deleted file mode 100644 index f7167eb..0000000 --- a/Tests/LinuxMain.swift +++ /dev/null @@ -1,6 +0,0 @@ -import XCTest -@testable import PerfectMongoDBTests - -XCTMain([ - testCase(PerfectMongoDBTests.allTests), -]) diff --git a/Tests/PerfectMongoDBTests/PerfectMongoDBTests.swift b/Tests/PerfectMongoDBTests/PerfectMongoDBTests.swift index 0414c78..5d6ba43 100644 --- a/Tests/PerfectMongoDBTests/PerfectMongoDBTests.swift +++ b/Tests/PerfectMongoDBTests/PerfectMongoDBTests.swift @@ -21,6 +21,9 @@ import Foundation import XCTest @testable import PerfectMongoDB +/// Server for the tests; set MONGODB_URI to use one other than localhost. +let testURI = ProcessInfo.processInfo.environment["MONGODB_URI"] ?? "mongodb://localhost" + class PerfectMongoDBTests: XCTestCase { func testBSONFromJSON() { let json = "{\"id\":1,\"first_name\":\"Kimberly\",\"last_name\":\"Gonzales\",\"email\":\"kgonzales0@usnews.com\",\"country\":\"France\",\"ip_address\":\"164.55.182.176\",\"ip_address0\":\"Turquoise\",\"ip_address1\":\"Euro\",\"ip_address2\":\"1qttm1nWiNDfpwuaYuoj7S7TXxUWxauBt\",\"ip_address3\":\"Demivee\",\"ip_address4\":false,\"ip_address5\":\"6/27/2015\"}" @@ -242,7 +245,7 @@ class PerfectMongoDBTests: XCTestCase { } func testClientConnect() { - let client = try! MongoClient(uri: "mongodb://localhost") + let client = try! MongoClient(uri: testURI) let status = client.serverStatus() switch status { case .error(let domain, let code, let message): @@ -261,7 +264,7 @@ class PerfectMongoDBTests: XCTestCase { } func testClientGetDatabase() { - let client = try! MongoClient(uri: "mongodb://localhost") + let client = try! MongoClient(uri: testURI) let db = client.getDatabase(name: "test") XCTAssert(db.name() == "test") db.close() @@ -269,7 +272,7 @@ class PerfectMongoDBTests: XCTestCase { } func testDBCreateCollection() { - let client = try! MongoClient(uri: "mongodb://localhost") + let client = try! MongoClient(uri: testURI) let db = client.getDatabase(name: "test") XCTAssert(db.name() == "test") @@ -295,7 +298,7 @@ class PerfectMongoDBTests: XCTestCase { } func testClientGetDatabaseNames() { - let client = try! MongoClient(uri: "mongodb://localhost") + let client = try! MongoClient(uri: testURI) let db = client.getDatabase(name: "test") XCTAssert(db.name() == "test") @@ -341,7 +344,7 @@ class PerfectMongoDBTests: XCTestCase { } func testGetCollection() { - let client = try! MongoClient(uri: "mongodb://localhost") + let client = try! MongoClient(uri: testURI) let db = client.getDatabase(name: "test") guard let col = db.getCollection(name: "testcollection") else { XCTAssert(false, "Collection was nil") @@ -354,7 +357,7 @@ class PerfectMongoDBTests: XCTestCase { } func testDeleteDoc() { - let client = try! MongoClient(uri: "mongodb://localhost") + let client = try! MongoClient(uri: testURI) let db = client.getDatabase(name: "test") XCTAssert(db.name() == "test") @@ -402,7 +405,7 @@ class PerfectMongoDBTests: XCTestCase { func testCollectionFind() { - let client = try! MongoClient(uri: "mongodb://localhost") + let client = try! MongoClient(uri: testURI) let db = client.getDatabase(name: "test") XCTAssert(db.name() == "test") @@ -491,7 +494,7 @@ class PerfectMongoDBTests: XCTestCase { let collectionName = "testdistinctcollection" let attributeName = "attribute" - let client = try! MongoClient(uri: "mongodb://localhost") + let client = try! MongoClient(uri: testURI) let db = client.getDatabase(name: "test") XCTAssert(db.name() == "test") @@ -550,7 +553,7 @@ class PerfectMongoDBTests: XCTestCase { } func testUpdate() { - let client = try! MongoClient(uri: "mongodb://localhost") + let client = try! MongoClient(uri: testURI) let db = client.getDatabase(name: "test") XCTAssert(db.name() == "test") @@ -643,7 +646,7 @@ class PerfectMongoDBTests: XCTestCase { } func testGridFS() { - let client = try! MongoClient(uri: "mongodb://localhost") + let client = try! MongoClient(uri: testURI) var gridfs: GridFS do { gridfs = try client.gridFS(database: "test") @@ -666,7 +669,7 @@ class PerfectMongoDBTests: XCTestCase { } let secret:[UInt8] = [65, 66, 67, 68, 0] // "ABCD\0" - var fp = fopen("/tmp/secret.txt", "wb") + var fp = fopen("/tmp/secret.txt", "wb")! fwrite(secret, 1, secret.count, fp) fclose(fp) @@ -684,7 +687,7 @@ class PerfectMongoDBTests: XCTestCase { } var secret2:[UInt8] = [0, 0, 0, 0, 0] - fp = fopen("/tmp/secret2.txt", "rb") + fp = fopen("/tmp/secret2.txt", "rb")! let _ = secret2.withUnsafeMutableBufferPointer{ p in fread(p.baseAddress, 1, 5, fp) } @@ -701,7 +704,7 @@ class PerfectMongoDBTests: XCTestCase { let local = "/tmp/base128.dat" let sz = 134217728 / 3 // 128MB / 3 let buffer = [UInt8](repeating: 66, count:sz) - fp = fopen(local, "wb") + fp = fopen(local, "wb")! fwrite(buffer, 1, sz, fp) fclose(fp) let remote = "base128.dat" @@ -739,7 +742,7 @@ class PerfectMongoDBTests: XCTestCase { do { for i in 0...20 { let toUpload = "upload\(i).bin" - fp = fopen("/tmp/\(toUpload)", "wb") + fp = fopen("/tmp/\(toUpload)", "wb")! fwrite(buffer, 1, sz, fp) fclose(fp) let fx = try gridfs.upload(from: "/tmp/\(toUpload)", to: toUpload) @@ -766,7 +769,7 @@ class PerfectMongoDBTests: XCTestCase { let groupsCollectionName = "testaggregate.groups" let usersCollectionName = "testaggregate.users" - let client = try! MongoClient(uri: "mongodb://localhost") + let client = try! MongoClient(uri: testURI) let db = client.getDatabase(name: "test") XCTAssert(db.name() == "test") @@ -872,29 +875,192 @@ class PerfectMongoDBTests: XCTestCase { let objectId = BSON.OID.newObjectId() XCTAssertTrue(objectId.count == 24, "Should generate valid ObjectId") } -} -extension PerfectMongoDBTests { - static var allTests : [(String, (PerfectMongoDBTests) -> () throws -> ())] { - return [ - ("testBSONFromJSON", testBSONFromJSON), - ("testBSONAppend", testBSONAppend), - ("testBSONHasFields", testBSONHasFields), - ("testBSONIterate", testBSONIterate), - ("testBSONCompare", testBSONCompare), - ("testClientConnect", testClientConnect), - ("testClientConnectFail", testClientConnectFail), - ("testClientGetDatabase", testClientGetDatabase), - ("testDBCreateCollection", testDBCreateCollection), - ("testClientGetDatabaseNames", testClientGetDatabaseNames), - ("testGetCollection", testGetCollection), - ("testDeleteDoc", testDeleteDoc), - ("testCollectionFind", testCollectionFind), - ("testCollectionDistinct", testCollectionDistinct), - ("testGridFS", testGridFS), - ("testNewObjectIdGeneration", testNewObjectIdGeneration) - ] - } + // Returns the client too: a MongoCollection doesn't keep its client alive. + private func freshCollection(_ name: String) -> (MongoClient, MongoCollection) { + let client = try! MongoClient(uri: testURI) + let db = client.getDatabase(name: "test") + if let old = db.getCollection(name: name) { + _ = old.drop() + } + guard case .replyCollection(let collection) = db.createCollection(name: name, options: nil) else { + fatalError("could not create \(name)") + } + for a in 1...3 { + let doc = BSON() + doc.append(key: "a", int: a) + doc.append(key: "b", string: "value \(a)") + guard case .success = collection.insert(document: doc) else { + fatalError("insert failed") + } + } + return (client, collection) + } + + func testFindLegacyQueryModifiers() { + let (client, collection) = freshCollection("testlegacyfind") + defer { withExtendedLifetime(client) {} } + defer { _ = collection.drop() } + + let orderby = BSON() + orderby.append(key: "a", int: -1) + let query = BSON() + query.append(key: "$query", document: BSON()) + query.append(key: "$orderby", document: orderby) + let fields = BSON() + fields.append(key: "b", int: 0) + + guard let cursor = collection.find(query: query, fields: fields, skip: 1, limit: 1) else { + return XCTFail("find returned nil") + } + let docs = cursor.map { $0.asString } + XCTAssertEqual(docs.count, 1) + XCTAssert(docs.first?.contains("\"a\" : 2") == true, "\(docs)") + XCTAssert(docs.first?.contains("\"b\"") == false, "projection not applied: \(docs)") + } + + func testCreateIndexAndStats() { + let (client, collection) = freshCollection("testindexstats") + defer { withExtendedLifetime(client) {} } + defer { _ = collection.drop() } + + let keys = BSON() + keys.append(key: "a", int: 1) + guard case .success = collection.createIndex(keys: keys, options: MongoIndexOptions(unique: true)) else { + return XCTFail("createIndex failed") + } + let duplicate = BSON() + duplicate.append(key: "a", int: 1) + guard case .error = collection.insert(document: duplicate) else { + return XCTFail("unique index not enforced") + } + guard case .success = collection.dropIndex(name: "a_1") else { + return XCTFail("generated index name should be a_1") + } + + guard case .replyDoc(let stats) = collection.stats(options: BSON()) else { + return XCTFail("stats failed") + } + XCTAssert(stats.asString.contains("\"count\" : 3"), stats.asString) + } + + private func count(_ collection: MongoCollection, _ json: String = "{}") -> Int { + guard case .replyInt(let n) = collection.count(query: try! BSON(json: json)) else { + XCTFail("count failed") + return -1 + } + return n + } + + func testUpdateForms() { + let (client, collection) = freshCollection("testupdateforms") + defer { withExtendedLifetime(client) {} } + defer { _ = collection.drop() } + + // update-operator document, single + guard case .success = collection.update(selector: try! BSON(json: "{}"), update: try! BSON(json: "{\"$set\": {\"c\": 1}}")) else { + return XCTFail("update one failed") + } + XCTAssertEqual(count(collection, "{\"c\": 1}"), 1) + // update-operator document, multi + guard case .success = collection.update(selector: try! BSON(json: "{}"), update: try! BSON(json: "{\"$set\": {\"c\": 2}}"), flag: .multiUpdate) else { + return XCTFail("update many failed") + } + XCTAssertEqual(count(collection, "{\"c\": 2}"), 3) + // replacement document + guard case .success = collection.update(selector: try! BSON(json: "{\"a\": 1}"), update: try! BSON(json: "{\"a\": 1, \"replaced\": true}")) else { + return XCTFail("replace failed") + } + XCTAssertEqual(count(collection, "{\"replaced\": true, \"c\": {\"$exists\": false}}"), 1) + // upsert + guard case .success = collection.update(selector: try! BSON(json: "{\"a\": 9}"), update: try! BSON(json: "{\"$set\": {\"b\": \"new\"}}"), flag: .upsert) else { + return XCTFail("upsert failed") + } + XCTAssertEqual(count(collection), 4) + // remove single, then all matching + guard case .success = collection.remove(selector: try! BSON(json: "{\"c\": 2}"), flag: .singleRemove) else { + return XCTFail("remove one failed") + } + XCTAssertEqual(count(collection, "{\"c\": 2}"), 1) + guard case .success = collection.remove(selector: try! BSON(json: "{}")) else { + return XCTFail("remove all failed") + } + XCTAssertEqual(count(collection), 0) + } + + func testBulkWrites() { + let (client, collection) = freshCollection("testbulkwrites") + defer { withExtendedLifetime(client) {} } + defer { _ = collection.drop() } + + let docs = (4...6).map { try! BSON(json: "{\"a\": \($0)}") } + guard case .success = collection.insert(documents: docs) else { + return XCTFail("bulk insert failed") + } + XCTAssertEqual(count(collection), 6) + let updates: [(selector: BSON, update: BSON)] = [ + (try! BSON(json: "{\"a\": {\"$gt\": 3}}"), try! BSON(json: "{\"$set\": {\"bulk\": true}}")), + (try! BSON(json: "{\"a\": 1}"), try! BSON(json: "{\"a\": 1, \"swapped\": true}")) + ] + guard case .success = collection.update(updates: updates) else { + return XCTFail("bulk update failed") + } + XCTAssertEqual(count(collection, "{\"bulk\": true}"), 3) + XCTAssertEqual(count(collection, "{\"swapped\": true}"), 1) + } + + func testFindAndModify() { + let (client, collection) = freshCollection("testfindandmodify") + defer { withExtendedLifetime(client) {} } + defer { _ = collection.drop() } + + guard case .replyDoc(let updated) = collection.findAndModify(query: try! BSON(json: "{\"a\": 2}"), sort: nil, update: try! BSON(json: "{\"$inc\": {\"a\": 10}}"), fields: nil, remove: false, upsert: false, new: true) else { + return XCTFail("findAndModify update failed") + } + XCTAssert(updated.asString.contains("\"a\" : 12"), updated.asString) + + guard case .replyDoc(let removed) = collection.findAndModify(query: nil, sort: try! BSON(json: "{\"a\": -1}"), update: nil, fields: nil, remove: true, upsert: false, new: false) else { + return XCTFail("findAndModify remove failed") + } + XCTAssert(removed.asString.contains("\"a\" : 12"), removed.asString) + XCTAssertEqual(count(collection), 2) + } + + func testClientPool() { + let pool = MongoClientPool(uri: testURI) + guard let first = pool.tryPopClient() else { + return XCTFail("tryPopClient returned nil") + } + guard case .replyDoc = first.serverStatus() else { + return XCTFail("pooled client unusable") + } + pool.pushClient(first) + do { + // released without pushClient: goes back to the pool instead of being destroyed + let dropped = pool.popClient() + XCTAssertEqual(dropped.databaseNames().isEmpty, false) + } + var ran = false + pool.executeBlock { client in + if case .replyDoc = client.serverStatus() { + ran = true + } + } + XCTAssert(ran) + } + + func testCollectionOutlivesClientVariable() { + func makeCollection() -> MongoCollection { + let client = try! MongoClient(uri: testURI) + return client.getDatabase(name: "test").getCollection(name: "testoutlives")! + } + let collection = makeCollection() + defer { _ = collection.drop() } + guard case .success = collection.insert(document: try! BSON(json: "{\"a\": 1}")) else { + return XCTFail("insert through orphaned collection failed") + } + XCTAssertEqual(collection.find()?.map { $0 }.count, 1) + } } extension BSON { diff --git a/Tests/PerfectMongoDBTests/Phase4Tests.swift b/Tests/PerfectMongoDBTests/Phase4Tests.swift new file mode 100644 index 0000000..bc57278 --- /dev/null +++ b/Tests/PerfectMongoDBTests/Phase4Tests.swift @@ -0,0 +1,265 @@ +// +// Phase4Tests.swift +// PerfectMongoDBTests +// +// Codable BSON, the typed collection API, and async/await. +// + +import Foundation +import XCTest +@testable import PerfectMongoDB + +private struct Address: Codable, Equatable, Sendable { + var city: String + var zip: String? +} + +private enum Role: String, Codable, Sendable { + case admin, member +} + +private struct Everything: Codable, Equatable, Sendable { + var id: BSON.OID + var name: String + var flag: Bool + var int: Int + var int8: Int8 + var int16: Int16 + var int32: Int32 + var int64: Int64 + var uint8: UInt8 + var uint32: UInt32 + var uint64: UInt64 + var float: Float + var double: Double + var date: Date + var data: Data + var uuid: UUID + var role: Role + var address: Address + var tags: [String] + var matrix: [[Int]] + var scores: [String: Int] + var nickname: String? + var missing: String? + var history: [Address] + + enum CodingKeys: String, CodingKey { + case id = "_id" + case name, flag, int, int8, int16, int32, int64, uint8, uint32, uint64, float, double + case date, data, uuid, role, address, tags, matrix, scores, nickname, missing, history + } + + static func sample(_ n: Int = 1) -> Everything { + Everything(id: BSON.OID(), name: "Ada \(n)", flag: true, int: n, int8: -8, int16: 1600, int32: -32, + int64: 1 << 40, uint8: 255, uint32: 4_000_000_000, uint64: 1 << 50, float: 1.5, double: 2.25, + date: Date(timeIntervalSince1970: 1_700_000_000.123), data: Data([0, 1, 2, 255]), uuid: UUID(), + role: .admin, address: Address(city: "Telluride", zip: nil), tags: ["a", "b"], + matrix: [[1, 2], [3]], scores: ["math": 90], nickname: "A", missing: nil, + history: [Address(city: "Ouray", zip: "81427")]) + } +} + +private struct Person: Codable, Equatable, Sendable { + var name: String + var age: Int +} + +final class Phase4Tests: XCTestCase { + + // MARK: Codable + + func testCodableRoundTrip() throws { + let value = Everything.sample() + let document = try BSONEncoder().encode(value) + let decoded = try BSONDecoder().decode(Everything.self, from: document) + XCTAssertEqual(decoded, value) + } + + func testEncodesNativeBSONTypes() throws { + let value = Everything.sample() + let json = try BSONEncoder().encode(value).asString + XCTAssert(json.contains("\"_id\" : { \"$oid\" : \"\(value.id)\" }"), json) + XCTAssert(json.contains("\"date\" : { \"$date\" : 1700000000123 }"), json) + XCTAssert(json.contains("\"$type\" : \"04\""), "UUID should be binary subtype 4: \(json)") + XCTAssert(json.contains("\"int64\" : 1099511627776"), json) + XCTAssertFalse(json.contains("\"missing\""), "nil optionals are omitted: \(json)") + XCTAssert(json.contains("\"address\" : { \"city\" : \"Telluride\" }"), json) + } + + func testDecodeNumericConversions() throws { + let doc = try BSON(json: #"{"a": {"$numberInt": "7"}, "b": 3.0, "c": {"$numberLong": "12"}}"#) + struct Numbers: Decodable { var a: Int64; var b: Int; var c: Double } + let numbers = try BSONDecoder().decode(Numbers.self, from: doc) + XCTAssertEqual(numbers.a, 7) + XCTAssertEqual(numbers.b, 3) + XCTAssertEqual(numbers.c, 12) + + struct Small: Decodable { var b: Int } + XCTAssertThrowsError(try BSONDecoder().decode(Small.self, from: try BSON(json: #"{"b": 3.5}"#))) { error in + guard case DecodingError.dataCorrupted = error else { return XCTFail("\(error)") } + } + struct Tiny: Decodable { var b: UInt8 } + XCTAssertThrowsError(try BSONDecoder().decode(Tiny.self, from: try BSON(json: #"{"b": 300}"#))) + } + + func testDecodingErrors() throws { + XCTAssertThrowsError(try BSONDecoder().decode(Person.self, from: try BSON(json: #"{"name": "x"}"#))) { error in + guard case DecodingError.keyNotFound(let key, _) = error else { return XCTFail("\(error)") } + XCTAssertEqual(key.stringValue, "age") + } + XCTAssertThrowsError(try BSONDecoder().decode(Person.self, from: try BSON(json: #"{"name": "x", "age": "old"}"#))) { error in + guard case DecodingError.typeMismatch = error else { return XCTFail("\(error)") } + } + XCTAssertThrowsError(try BSONDecoder().decode(Person.self, from: try BSON(json: #"{"name": null, "age": 1}"#))) { error in + guard case DecodingError.valueNotFound = error else { return XCTFail("\(error)") } + } + XCTAssertThrowsError(try BSONEncoder().encode(42), "a bare Int is not a document") + } + + func testObjectIdOutsideBSON() throws { + let oid = BSON.OID() + let json = try JSONEncoder().encode([oid]) + XCTAssertEqual(String(decoding: json, as: UTF8.self), "[\"\(oid)\"]") + XCTAssertEqual(try JSONDecoder().decode([BSON.OID].self, from: json), [oid]) + XCTAssertNil(BSON.OID(validating: "not an id")) + } + + // MARK: Typed collection API + + private func typedCollection(_ name: String) throws -> (MongoClient, MongoCollection) { + let client = try MongoClient(uri: testURI) + let collection = client.getCollection(databaseName: "test", collectionName: name) + _ = collection.drop() + return (client, collection) + } + + func testTypedCollectionOperations() throws { + let (client, collection) = try typedCollection("phase4typed") + defer { withExtendedLifetime(client) {} } + defer { _ = collection.drop() } + + let sample = Everything.sample() + try collection.insert(sample) + XCTAssertEqual(try collection.findOne(Everything.self), sample) + + try collection.insert(contentsOf: (1...5).map { Person(name: "p\($0)", age: $0 * 10) }) + let byAgeDesc = try collection.find(Person.self, filter: try BSON(json: #"{"age": {"$gte": 20}}"#), options: try BSON(json: #"{"sort": {"age": -1}}"#)) + XCTAssertEqual(byAgeDesc.map(\.age), [50, 40, 30, 20]) + XCTAssertEqual(try collection.countDocuments(filter: try BSON(json: #"{"age": {"$exists": true}}"#)), 5) + + XCTAssertEqual(try collection.updateMany(filter: try BSON(json: #"{"age": {"$lt": 30}}"#), update: try BSON(json: #"{"$inc": {"age": 1}}"#)), 2) + XCTAssertEqual(try collection.findOne(Person.self, filter: try BSON(json: #"{"name": "p1"}"#))?.age, 11) + XCTAssertEqual(try collection.updateOne(filter: try BSON(json: #"{"name": "p2"}"#), update: try BSON(json: #"{"$set": {"age": 99}}"#)), 1) + + XCTAssertEqual(try collection.replaceOne(filter: try BSON(json: #"{"name": "p9"}"#), with: Person(name: "p9", age: 9)), 0) + try collection.replaceOne(filter: try BSON(json: #"{"name": "p9"}"#), with: Person(name: "p9", age: 9), upsert: true) + XCTAssertEqual(try collection.findOne(Person.self, filter: try BSON(json: #"{"name": "p9"}"#)), Person(name: "p9", age: 9)) + + XCTAssertEqual(try collection.deleteOne(filter: try BSON(json: #"{"name": "p9"}"#)), 1) + XCTAssertEqual(try collection.deleteMany(filter: try BSON(json: #"{"age": {"$exists": true}}"#)), 5) + XCTAssertNil(try collection.findOne(Person.self, filter: try BSON(json: #"{"name": "p1"}"#))) + } + + func testTypedWriteErrorsThrow() throws { + let (client, collection) = try typedCollection("phase4errors") + defer { withExtendedLifetime(client) {} } + defer { _ = collection.drop() } + + let sample = Everything.sample() + try collection.insert(sample) + XCTAssertThrowsError(try collection.insert(sample)) { error in + guard let error = error as? MongoError else { return XCTFail("\(error)") } + XCTAssertEqual(error.code, 11000, "duplicate key: \(error)") + } + XCTAssertThrowsError(try collection.find(Person.self)) { error in + guard case DecodingError.keyNotFound = error else { return XCTFail("\(error)") } + } + } + + // MARK: async + + private func poolURI(maxPoolSize: Int) -> String { + testURI + (testURI.contains("?") ? "&" : "/?") + "maxPoolSize=\(maxPoolSize)" + } + + func testPoolValidatingURI() async throws { + XCTAssertThrowsError(try MongoClientPool(validatingURI: "mongoib//typo")) { error in + guard let error = error as? MongoError else { return XCTFail("\(error)") } + XCTAssertFalse(error.message.isEmpty, "\(error)") + } + XCTAssertThrowsError(try MongoClientPool(validatingURI: "mongodb://localhost/?tls=true&tlsInsecure=true&tlsAllowInvalidCertificates=true"), + "conflicting TLS options should be rejected") + let pool = try MongoClientPool(validatingURI: testURI) + let count = try await pool.withClient { client in + try client.getCollection(databaseName: "test", collectionName: "phase4validating").countDocuments() + } + XCTAssertEqual(count, 0) + } + + func testWithClientConcurrently() async throws { + let pool = MongoClientPool(uri: poolURI(maxPoolSize: 4)) + try await pool.withClient { client in + _ = client.getCollection(databaseName: "test", collectionName: "phase4async").drop() + } + try await withThrowingTaskGroup(of: Void.self) { group in + for n in 1...20 { + group.addTask { + try await pool.withClient { client in + try client.getCollection(databaseName: "test", collectionName: "phase4async") + .insert(Person(name: "task\(n)", age: n)) + } + } + } + try await group.waitForAll() + } + let count = try await pool.withClient { client in + try client.getCollection(databaseName: "test", collectionName: "phase4async").countDocuments() + } + XCTAssertEqual(count, 20) + try await pool.withClient { client in + _ = client.getCollection(databaseName: "test", collectionName: "phase4async").drop() + } + } + + func testFindSequence() async throws { + let pool = MongoClientPool(uri: poolURI(maxPoolSize: 2)) + try await pool.withClient { client in + let collection = client.getCollection(databaseName: "test", collectionName: "phase4sequence") + _ = collection.drop() + try collection.insert(contentsOf: (0..<250).map { Person(name: "n\($0)", age: $0) }) + } + + var ages: [Int] = [] + for try await person in pool.find(Person.self, database: "test", collection: "phase4sequence", + options: try BSON(json: #"{"sort": {"age": 1}}"#), batchSize: 100) { + ages.append(person.age) + } + XCTAssertEqual(ages, Array(0..<250)) + + // Leaving a loop early releases the iterator and returns its client to the pool. + for _ in 0..<5 { + for try await _ in pool.find(Person.self, database: "test", collection: "phase4sequence", batchSize: 10) { + break + } + } + let first = pool.tryPopClient() + let second = pool.tryPopClient() + XCTAssertNotNil(first, "a pooled client leaked") + XCTAssertNotNil(second, "a pooled client leaked") + first.map(pool.pushClient) + second.map(pool.pushClient) + + // Decoding errors surface from the loop. + struct Wrong: Decodable, Sendable { var missing: String } + do { + for try await _ in pool.find(Wrong.self, database: "test", collection: "phase4sequence") {} + XCTFail("expected a decoding error") + } catch is DecodingError { + } + + try await pool.withClient { client in + _ = client.getCollection(databaseName: "test", collectionName: "phase4sequence").drop() + } + } +} diff --git a/Tests/PerfectMongoDBTests/XCTestManifests.swift b/Tests/PerfectMongoDBTests/XCTestManifests.swift deleted file mode 100644 index 485ae02..0000000 --- a/Tests/PerfectMongoDBTests/XCTestManifests.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// XCTestManifests.swift -// -// Created by Kyle Jessup on 2015-10-19. -// Copyright © 2015 PerfectlySoft. All rights reserved. -// -//===----------------------------------------------------------------------===// -// -// This source file is part of the Perfect.org open source project -// -// Copyright (c) 2015 - 2016 PerfectlySoft Inc. and the Perfect project authors -// Licensed under Apache License v2.0 -// -// See http://perfect.org/licensing.html for license information -// -//===----------------------------------------------------------------------===// -// - -import XCTest - -#if !os(OSX) -public func allTests() -> [XCTestCaseEntry] { - return [ - testCase(PerfectMongoDBTests.allTests) - ] -} -#endif diff --git a/docs/Classes.html b/docs/Classes.html deleted file mode 100644 index 512d77d..0000000 --- a/docs/Classes.html +++ /dev/null @@ -1,363 +0,0 @@ - - - - Classes Reference - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

Classes

-

The following classes are available globally.

- -
-
-
-
    -
  • -
    - - - - MongoIndexOptionsGeo - -
    -
    -
    -
    -
    -
    -

    class to manage Mongo Geospatial indexing options

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public class MongoIndexOptionsGeo
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - MongoIndexOptions - -
    -
    -
    -
    -
    -
    -

    class to manage Mongo indexing options

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public class MongoIndexOptions
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - MongoCollection - -
    -
    -
    -
    -
    -
    -

    The MongoCollection class

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public class MongoCollection
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - MongoDatabase - -
    -
    -
    -
    -
    -
    -

    MongoDatabase is an open-source document database that provides high performance, high availability, and automatic scaling.

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public class MongoDatabase
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - BSON - -
    -
    -
    -
    -
    -
    -

    BSON class

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public class BSON: CustomStringConvertible
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - MongoClientPool - -
    -
    -
    -
    -
    -
    -

    Allows connection pooling. This class is thread-safe.

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public class MongoClientPool
    - -
    -
    -
    -
    -
  • -
-
-
- -
-
-
    -
  • -
    - - - - MongoCursor - -
    -
    -
    -
    -
    -
    -

    The Mongo Cursor interface

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public class MongoCursor
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/Classes/BSON.html b/docs/Classes/BSON.html deleted file mode 100644 index 3f74ea0..0000000 --- a/docs/Classes/BSON.html +++ /dev/null @@ -1,1559 +0,0 @@ - - - - BSON Class Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

BSON

-
-
-
public class BSON: CustomStringConvertible
- -
-
-

BSON class

- -
-
-
-
    -
  • -
    - - - - description - -
    -
    -
    -
    -
    -
    -

    Return JSON representation of current BSON contents as a String

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var description: String
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init() - -
    -
    -
    -
    -
    -
    -

    Allocates a new doc structure. Call the various append() functions to add fields to the bson. You can iterate the doc at any time using a bson_iter_t and bson_iter_init().

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init()
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init(bytes:) - -
    -
    -
    -
    -
    -
    -

    Creates a new doc structure using the data provided. bytes should contain bytes that can be copied into the new doc structure. - * - *- parameter bytes: A byte array containing a serialized bson document.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(bytes: [UInt8])
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init(json:) - -
    -
    -
    -
    -
    -
    -

    Creates a new doc structure using the data provided. json should contain bytes that can be copied into the new doc structure.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(json: String) throws
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - json - - -
    -

    A string containing a json data.

    - -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - init(document:) - -
    -
    -
    -
    -
    -
    -

    Creates a new doc by copying the provided bson doc.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(document: BSON)
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - document - - -
    -

    An existing bson document.

    - -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - close() - -
    -
    -
    -
    -
    -
    -

    close, destroy and release the current BSON document

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func close()
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - asString - -
    -
    -
    -
    -
    -
    -

    Creates a new string containing current document in extended JSON format.

    - -

    See http://docs.mongodb.org/manual/reference/mongodb-extended-json/ for - more information on extended JSON.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var asString: String
    - -
    -
    -
    -

    Return Value

    -

    String

    - -
    -
    -
    -
  • -
  • -
    - - - - asArrayString - -
    -
    -
    -
    -
    -
    -

    like asString() but for outermost arrays.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var asArrayString: String
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - asBytes - -
    -
    -
    -
    -
    -
    -

    asBytes:

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var asBytes: [UInt8]
    - -
    -
    -
    -

    Return Value

    -

    A byte array from current BSON document

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:document:) - -
    -
    -
    -
    -
    -
    -

    Appends a new field to self.doc of the type BSON_TYPE_DOCUMENT. The documents contents will be copied into self.doc.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, document: BSON) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - document - - -
    -

    Existing BSON document.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:) - -
    -
    -
    -
    -
    -
    -

    Appends a new field to self.doc with NULL for the value.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:oid:) - -
    -
    -
    -
    -
    -
    -

    Appends a new field to the self.doc of type BSON_TYPE_OID using the contents of - oid.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, oid: bson_oid_t) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - oid - - -
    -

    bson_oid_t.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:int:) - -
    -
    -
    -
    -
    -
    -

    Appends a new field of type BSON_TYPE_INT64 to self.doc .

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, int: Int) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - int - - -
    -

    The Int 64-bit integer value.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:int32:) - -
    -
    -
    -
    -
    -
    -

    Appends a new field of type BSON_TYPE_INT32 to self.doc .

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, int32: Int32) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - int32 - - -
    -

    The Int32 32-bit integer value.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:dateTime:) - -
    -
    -
    -
    -
    -
    -

    Appends a new field to self.doc of type BSON_TYPE_DATE_TIME.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, dateTime: Int64) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - dateTime - - -
    -

    The number of milliseconds elapsed since UNIX epoch.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if sucessful; otherwise false.

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:time:) - -
    -
    -
    -
    -
    -
    -

    Appends a BSON_TYPE_DATE_TIME field to self.doc using the time_t @value for the - number of seconds since UNIX epoch in UTC.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, time: time_t) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - time - - -
    -

    A time_t.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:double:) - -
    -
    -
    -
    -
    -
    -

    Appends a new field to self.doc of the type BSON_TYPE_DOUBLE.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, double: Double) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - double - - -
    -

    The double to be appended

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:bool:) - -
    -
    -
    -
    -
    -
    -

    Appends a new field to self.doc of type BSON_TYPE_BOOL.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, bool: Bool) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - bool - - -
    -

    The boolean value.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:string:) - -
    -
    -
    -
    -
    -
    -

    Appends a new field to self.doc using @key as the key and @string as the UTF-8 - encoded value.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, string: String) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - string - - -
    -

    A UTF-8 encoded string.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:bytes:) - -
    -
    -
    -
    -
    -
    -

    Appends a bytes buffer to the BSON document.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, bytes: [UInt8]) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - bytes - - -
    -

    The bytes to append

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Appends a new field to self.doc of type BSON_TYPE_REGEX. @regex should - be the regex string. @options should contain the options for the regex.

    - -

    Valid options for @options are:

    - -

    ‘i’ for case-insensitive. - ’m’ for multiple matching. - ‘x’ for verbose mode. - 'l’ to make \w and \W locale dependent. - ’s’ for dotall mode (’.’ matches everything) - 'u’ to make \w and \W match unicode.

    - -

    For more information on what comprimises a BSON regex, see bsonspec.org.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, regex: String, options: String) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - - - - - -
    - - key - - -
    -

    The key of the field.

    - -
    -
    - - regex - - -
    -

    The regex to append to the bson.

    - -
    -
    - - options - - -
    -

    Options for @regex.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - countKeys() - -
    -
    -
    -
    -
    -
    -

    Counts the number of elements found in self.doc. -- returns: Int value of keys count

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func countKeys() -> Int
    - -
    -
    -
    -

    Return Value

    -

    Int value of keys count

    - -
    -
    -
    -
  • -
  • -
    - - - - hasField(key:) - -
    -
    -
    -
    -
    -
    -

    Checks to see if self.doc contains a field named @key.

    - -

    This function is case-sensitive.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func hasField(key key: String) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - key - - -
    -

    The key to lookup.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if @key exists in self.doc; otherwise false.

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Appends a new field named key to self.doc, the field is, however, - incomplete. @child will be initialized so that you may add fields to the - child array. Child will use a memory buffer owned by self.doc and - therefore grow the parent buffer as additional space is used. This allows - a single malloc’d buffer to be used when building arrays which can help - reduce memory fragmentation.

    - -

    The type of @child will be BSON_TYPE_ARRAY and therefore the keys inside - of it MUST be 0, 1, etc.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func appendArrayBegin(key key: String, child: BSON) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - child - - -
    -

    A location to an uninitialized bson_t.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - appendArrayEnd(_:) - -
    -
    -
    -
    -
    -
    -

    Finishes the appending of an array to self.doc. child is considered - disposed after this call and should not be used any further.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func appendArrayEnd(child: BSON) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - child - - -
    -

    A bson document supplied to appendArrayBegin().

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Appends a BSON array to self.doc. BSON arrays are like documents where the - key is the string version of the index. For example, the first item of the - array would have the key 0. The second item would have the index 1.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func appendArray(key key: String, array: BSON) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - array - - -
    -

    A bson document containing the array.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - concat(_:) - -
    -
    -
    -
    -
    -
    -

    Concatenate src with self.doc

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func concat(src: BSON) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - src - - -
    -

    BSON doc to be concatenated.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/Classes/MongoClient.html b/docs/Classes/MongoClient.html deleted file mode 100644 index 98b1e4e..0000000 --- a/docs/Classes/MongoClient.html +++ /dev/null @@ -1,386 +0,0 @@ - - - - MongoClient Class Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoClient

-

Undocumented

- -
-
-
-
    -
  • -
    - - - - Result - -
    -
    -
    -
    -
    -
    -

    Result Status enum for a MongoDB event

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public typealias Result = MongoResult
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init(uri:) - -
    -
    -
    -
    -
    -
    -

    Create new Mongo Client connection

    - -
    -

    Throws

    - MongoClientError Could not parse URI if nil response - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(uri: String) throws
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - close() - -
    -
    -
    -
    -
    -
    -

    terminate current Mongo Client connection

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func close()
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Return the specified MongoCollection from the specified database using current connection

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func getCollection(databaseName: String, collectionName: String) -> MongoCollection
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - databaseName - - -
    -

    String name of database to be used

    - -
    -
    - - collectionName - - -
    -

    String name of collection to be retrieved

    - -
    -
    -
    -
    -

    Return Value

    -

    MongoCollection from specified database

    - -
    -
    -
    -
  • -
  • -
    - - - - getDatabase(name:) - -
    -
    -
    -
    -
    -
    -

    Return the named database as a MongoDatabase object

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func getDatabase(name databaseName: String) -> MongoDatabase
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - name - - -
    -

    String name of database to be retrieved

    - -
    -
    -
    -
    -

    Return Value

    -

    a MongoDatabase object

    - -
    -
    -
    -
  • -
  • -
    - - - - serverStatus() - -
    -
    -
    -
    -
    -
    -

    Get current Mongo server status

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func serverStatus() -> Result
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - databaseNames() - -
    -
    -
    -
    -
    -
    -

    Build String Array of current database names

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func databaseNames() -> [String]
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/Classes/MongoClientPool.html b/docs/Classes/MongoClientPool.html deleted file mode 100644 index 0df156c..0000000 --- a/docs/Classes/MongoClientPool.html +++ /dev/null @@ -1,339 +0,0 @@ - - - - MongoClientPool Class Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoClientPool

-
-
-
public class MongoClientPool
- -
-
-

Allows connection pooling. This class is thread-safe.

- -
-
-
-
    -
  • -
    - - - - init(uri:) - -
    -
    -
    -
    -
    -
    -

    create new ClientPool with provided String uri

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(uri: String)
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - uri - - -
    -

    String uri to connect client pool

    - -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - tryPopClient() - -
    -
    -
    -
    -
    -
    -

    Try to pop a client connection from the connection pool.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func tryPopClient() -> MongoClient?
    - -
    -
    -
    -

    Return Value

    -

    nil if no client connection is currently queued for reuse.

    - -
    -
    -
    -
  • -
  • -
    - - - - popClient() - -
    -
    -
    -
    -
    -
    -

    Pop a client connection from the connection pool.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func popClient() -> MongoClient
    - -
    -
    -
    -

    Return Value

    -

    MongoClient from connection pool

    - -
    -
    -
    -
  • -
  • -
    - - - - pushClient(_:) - -
    -
    -
    -
    -
    -
    -

    Pushes back popped client connection.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func pushClient(_ client: MongoClient)
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - client - - -
    -

    MongoClient to be pushed back into pool

    - -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - executeBlock(_:) - -
    -
    -
    -
    -
    -
    -

    Automatically pops a client, makes it available within the block and pushes it back.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func executeBlock(@noescape block: (client: MongoClient) -> Void)
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - block - - -
    -

    block to be executed with popped client

    - -
    -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/Classes/MongoCollection.html b/docs/Classes/MongoCollection.html deleted file mode 100644 index f493fcf..0000000 --- a/docs/Classes/MongoCollection.html +++ /dev/null @@ -1,1288 +0,0 @@ - - - - MongoCollection Class Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoCollection

-
-
-
public class MongoCollection
- -
-
-

The MongoCollection class

- -
-
-
-
    -
  • -
    - - - - Result - -
    -
    -
    -
    -
    -
    -

    Result Status enum for a MongoDB event

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public typealias Result = MongoResult
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    obtain access to a specified database and collection using the MongoClient

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(client: MongoClient, databaseName: String, collectionName: String)
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - - - - - -
    - - client - - -
    -

    the MongoClient to be used

    - -
    -
    - - databaseName - - -
    -

    String database name

    - -
    -
    - - collectionName - - -
    -

    String collection name

    - -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - close() - -
    -
    -
    -
    -
    -
    -

    close connection to the current collection

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func close()
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Insert document into the current collection returning a result status

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func insert(document document: BSON, flag: MongoInsertFlag = .None) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - document - - -
    -

    BSON document to be inserted

    - -
    -
    - - flag - - -
    -

    Optional MongoInsertFlag defaults to .None

    - -
    -
    -
    -
    -

    Return Value

    -

    Result object with status of insert

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Update the document found using selector with the update document returning a result status

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func update(update: BSON, selector: BSON, flag: MongoUpdateFlag = .None) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - - - - - -
    - - update - - -
    -

    BSON document to be used to update

    - -
    -
    - - selector - - -
    -

    BSON document with selection criteria

    - -
    -
    - - flag - - -
    -

    Optional MongoUpdateFlag defaults to .None

    - -
    -
    -
    -
    -

    Return Value

    -

    Result object with status of update

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Remove the document found using selector returning a result status

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func remove(selector selector: BSON, flag: MongoRemoveFlag = .None) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - selector - - -
    -

    BSON document with selection criteria

    - -
    -
    - - flag - - -
    -

    Optional MongoRemoveFlag defaults to .None

    - -
    -
    -
    -
    -

    Return Value

    -

    Result object with status of removal

    - -
    -
    -
    -
  • -
  • -
    - - - - save(document:) - -
    -
    -
    -
    -
    -
    -

    Updates document returning a result status

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func save(document document: BSON) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - document - - -
    -

    BSON document to be saved

    - -
    -
    -
    -
    -

    Return Value

    -

    Result object with status of save

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Renames the collection using newDbName and newCollectionName, with option to drop existing collection immediately instead of after the move, returning a result status

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func rename(newDbName: String, newCollectionName: String, dropExisting: Bool) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - - - - - -
    - - newDbName - - -
    -

    String name for db after move

    - -
    -
    - - newCollectionName - - -
    -

    String name for collection after move

    - -
    -
    - - dropExisting - - -
    -

    Bool option to drop existing collection immediately instead of after move

    - -
    -
    -
    -
    -

    Return Value

    -

    Result object with status of renaming

    - -
    -
    -
    -
  • -
  • -
    - - - - name() - -
    -
    -
    -
    -
    -
    -

    The collection name as a String

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func name() -> String
    - -
    -
    -
    -

    Return Value

    -

    String the name of the current collection

    - -
    -
    -
    -
  • -
  • -
    - - - - validate(_:) - -
    -
    -
    -
    -
    -
    -

    Validates a collection. The method scans a collection’s data structures for correctness and returns a single document that describes the relationship between the logical collection and the physical representation of the data.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func validate(options: BSON) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - options - - -
    -

    Optional. Specify true to enable a full validation and to return full statistics. MongoDB disables full validation by default because it is a potentially resource-intensive operation.

    - -
    -
    -
    -
    -

    Return Value

    -

    BSON document describing the relationship between the collection and its physical representation

    - -
    -
    -
    -
  • -
  • -
    - - - - stats(_:) - -
    -
    -
    -
    -
    -
    -

    Returns statistics about the collection formatted according to the options document.

    - -
      -
    • The options document can contain the following fields and values:

    • -
    • scale: number, Optional. The scale used in the output to display the sizes of items. By default, output displays sizes in bytes. To display kilobytes rather than bytes, specify a scale value of 1024.

    • -
    • indexDetails: boolean, Optional. If true, stats() returns index details in addition to the collection stats. Only works for WiredTiger storage engine. Defaults to false.

    • -
    • indexDetailsKey: document, Optional. If indexDetails is true, you can use indexDetailsKey to filter index details by specifying the index key specification. Only the index that exactly matches indexDetailsKey will be returned. If no match is found, indexDetails will display statistics for all indexes.

    • -
    • indexDetailsName: string, Optional. If indexDetails is true, you can use indexDetailsName to filter index details by specifying the index name. Only the index name that exactly matches indexDetailsName will be returned. If no match is found, indexDetails will display statistics for all indexes.

    • -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func stats(options: BSON) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - options - - -
    -

    a BSON document defining the format of return.

    - -
    -
    -
    -
    -

    Return Value

    -

    BSON document with formatted statistics or Results error document

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Selects documents in a collection and returns a cursor to the selected documents.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func find(query: BSON, fields: BSON? = nil, flags: MongoQueryFlag = MongoQueryFlag.None, skip: Int = 0, limit: Int = 0, batchSize: Int = 0) -> MongoCursor?
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - query - - -
    -

    Optional. Specifies selection filter using query operators. To return all documents in a collection, omit this- Parameter or pass an empty document ({}).

    - -
    -
    - - fields - - -
    -

    Optional. Specifies the fields to return in the documents that match the query filter. To return all fields in the matching documents, omit this parameter.

    - -
    -
    - - flags - - -
    -

    Optional. set queryFlags for the current search

    - -
    -
    - - skip - - -
    -

    Optional. Skip the supplied number of records.

    - -
    -
    - - limit - - -
    -

    Optional. return no more than the supplied number of records.

    - -
    -
    - - batchSize - - -
    -

    Optional. Change number of automatically iterated documents.

    - -
    -
    -
    -
    -

    Return Value

    -

    A cursor to the documents that match the query criteria. When the find() method “returns documents,” the method is actually returning a cursor to the documents.

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Creates indexes on collections.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func createIndex(keys: BSON, options: MongoIndexOptions) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - keys - - -
    -

    A document that conains the field and value pairs where the field is the index key and the value describes the type of index for that field. For an ascending index on a field, specify a value of 1; for descending index, specify a value of -1.

    - -
    -
    - - options - - -
    -

    Optional. A document that contains a set of options that controls the creation of the index. see MongoIndexOptions for details.

    - -
    -
    -
    -
    -

    Return Value

    -

    a Result status

    - -
    -
    -
    -
  • -
  • -
    - - - - dropIndex(_:) - -
    -
    -
    -
    -
    -
    -

    Drops or removes the specified index from a collection.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func dropIndex(name: String) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - index - - -
    -

    Specifies the index to drop, either by name or by the index specification document.

    - -
    -
    -
    -
    -

    Return Value

    -

    a Result status

    - -
    -
    -
    -
  • -
  • -
    - - - - drop() - -
    -
    -
    -
    -
    -
    -

    Removes a collection from the database. The method also removes any indexes associated with the dropped collection.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func drop() -> Result
    - -
    -
    -
    -

    Return Value

    -

    a Result status

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    The count of documents that would match a find() query.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func count(query: BSON, fields: BSON? = nil, flags: MongoQueryFlag = MongoQueryFlag.None, skip: Int = 0, limit: Int = 0, batchSize: Int = 0) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - query - - -
    -

    The query selection criteria.

    - -
    -
    - - fields - - -
    -

    Optional. Specifies the fields to return in the documents that match the query filter. To return all fields in the matching documents, omit this parameter.

    - -
    -
    - - flags - - -
    -

    Optional. set queryFlags for the current search

    - -
    -
    - - skip - - -
    -

    Optional. Skip the supplied number of records.

    - -
    -
    - - limit - - -
    -

    Optional. return no more than the supplied number of records.

    - -
    -
    - - batchSize - - -
    -

    Optional. Change number of automatically iterated documents.

    - -
    -
    -
    -
    -

    Return Value

    -

    the count of documents that would match a find() query. The count() method does not perform the find() operation but instead counts and returns the number of results that match a query.

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Modifies and returns a single document.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func findAndModify(query: BSON, sort: BSON, update: BSON, fields: BSON, remove: Bool, upsert: Bool, new: Bool) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - query - - -
    -

    Optional. The selection criteria for the modification. The query field employs the same query selectors as used in the db.collection.find() method. Although the query may match multiple documents, findAndModify() will only select one document to modify.

    - -
    -
    - - sort - - -
    -

    Optional. Determines which document the operation modifies if the query selects multiple documents. findAndModify() modifies the first document in the sort order specified by this argument.

    - -
    -
    - - update - - -
    -

    Must specify either the remove or the update field. Performs an update of the selected document. The update field employs the same update operators or field: value specifications to modify the selected document.

    - -
    -
    - - fields - - -
    -

    Optional. A subset of fields to return. The fields document specifies an inclusion of a field with 1, as in: fields: { : 1, : 1, … }.

    - -
    -
    - - remove - - -
    -

    Must specify either the remove or the update field. Removes the document specified in the query field. Set this to true to remove the selected document . The default is false.

    - -
    -
    - - upsert - - -
    -

    Optional. Used in conjunction with the update field. When true, findAndModify() creates a new document if no document matches the query, or if documents match the query, findAndModify() performs an update. To avoid multiple upserts, ensure that the query fields are uniquely indexed. The default is false.

    - -
    -
    - - new - - -
    -

    Optional. When true, returns the modified document rather than the original. The findAndModify() method ignores the new option for remove operations. The default is false.

    - -
    -
    -
    -
    -

    Return Value

    -

    Modifies and returns a single document. By default, the returned document does not include the modifications made on the update. To return the document with the modifications made on the update, use the new option.

    - -
    -
    -
    -
  • -
  • -
    - - - - getLastError() - -
    -
    -
    -
    -
    -
    -

    A BSON document with description of last transaction status

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func getLastError() -> BSON
    - -
    -
    -
    -

    Return Value

    -

    BSON document with description of last transaction status

    - -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/Classes/MongoCursor.html b/docs/Classes/MongoCursor.html deleted file mode 100644 index 0f23f86..0000000 --- a/docs/Classes/MongoCursor.html +++ /dev/null @@ -1,192 +0,0 @@ - - - - MongoCursor Class Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoCursor

-
-
-
public class MongoCursor
- -
-
-

The Mongo Cursor interface

- -
-
-
-
    -
  • -
    - - - - close() - -
    -
    -
    -
    -
    -
    -

    Close and destroy current cursor

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func close()
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - next() - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func next() -> BSON?
    - -
    -
    -
    -

    Return Value

    -

    next document if available, else nil

    - -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/Classes/MongoDatabase.html b/docs/Classes/MongoDatabase.html deleted file mode 100644 index 498659f..0000000 --- a/docs/Classes/MongoDatabase.html +++ /dev/null @@ -1,394 +0,0 @@ - - - - MongoDatabase Class Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoDatabase

-
-
-
public class MongoDatabase
- -
-
-

MongoDatabase is an open-source document database that provides high performance, high availability, and automatic scaling.

- -
-
-
-
    -
  • - -
    -
    -
    -
    -
    -

    Get reference to named database using provided MongoClient instance

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(client: MongoClient, databaseName: String)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - close() - -
    -
    -
    -
    -
    -
    -

    Close connection to database

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func close()
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - drop() - -
    -
    -
    -
    -
    -
    -

    Drops the current database, deleting the associated data files

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func drop() -> Result
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - name() - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func name() -> String
    - -
    -
    -
    -

    Return Value

    -

    a string, the name of the current database

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Create new Collection

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func createCollection(name collectionName: String, options: BSON) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - name - - -
    -

    String, name of collection to be created

    - -
    -
    - - options - - -
    -

    BSON document listing options for new collection

    - -
    -
    -
    -
    -

    Return Value

    -

    MongoCollection

    - -
    -
    -
    -
  • -
  • -
    - - - - getCollection(name:) - -
    -
    -
    -
    -
    -
    -

    MongoCollection referenced by name

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func getCollection(name collectionName: String) -> MongoCollection
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - name - - -
    -

    String collection name

    - -
    -
    -
    -
    -

    Return Value

    -

    MongoCollection

    - -
    -
    -
    -
  • -
  • -
    - - - - collectionNames() - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func collectionNames() -> [String]
    - -
    -
    -
    -

    Return Value

    -

    String Array of current database collections’ names

    - -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/Classes/MongoIndexOptions.html b/docs/Classes/MongoIndexOptions.html deleted file mode 100644 index 047c511..0000000 --- a/docs/Classes/MongoIndexOptions.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - MongoIndexOptions Class Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- - -
- - - diff --git a/docs/Classes/MongoIndexOptionsGeo.html b/docs/Classes/MongoIndexOptionsGeo.html deleted file mode 100644 index f6d13bc..0000000 --- a/docs/Classes/MongoIndexOptionsGeo.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - MongoIndexOptionsGeo Class Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- - -
- - - diff --git a/docs/Enums.html b/docs/Enums.html deleted file mode 100644 index 0ed13ea..0000000 --- a/docs/Enums.html +++ /dev/null @@ -1,331 +0,0 @@ - - - - Enums Reference - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

Enums

-

The following enums are available globally.

- -
-
-
-
    -
  • -
    - - - - MongoInsertFlag - -
    -
    -
    -
    -
    -
    -

    Enum of flags for insertion options

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoInsertFlag: Int
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - MongoUpdateFlag - -
    -
    -
    -
    -
    -
    -

    Enum of flags for update options

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoUpdateFlag: Int
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - MongoRemoveFlag - -
    -
    -
    -
    -
    -
    -

    Enum of flags for remove options

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoRemoveFlag: Int
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Enum for storage options

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoIndexStorageOptionType: UInt32
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - BSONError - -
    -
    -
    -
    -
    -
    -

    BSON error enum

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum BSONError: ErrorProtocol
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - MongoResult - -
    -
    -
    -
    -
    -
    -

    Result Status for a MongoDB event

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoResult
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - MongoClientError - -
    -
    -
    -
    -
    -
    -

    ErrorType for MongoClient error reporting

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoClientError: ErrorProtocol
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/Enums/BSONError.html b/docs/Enums/BSONError.html deleted file mode 100644 index 897da13..0000000 --- a/docs/Enums/BSONError.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - BSONError Enum Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

BSONError

-
-
-
public enum BSONError: ErrorProtocol
- -
-
-

BSON error enum

- -
-
-
-
    -
  • -
    - - - - SyntaxError - -
    -
    -
    -
    -
    -
    -

    The JSON data was malformed.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case SyntaxError(String)
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/Enums/MongoClientError.html b/docs/Enums/MongoClientError.html deleted file mode 100644 index db5e6c7..0000000 --- a/docs/Enums/MongoClientError.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - MongoClientError Enum Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoClientError

-
-
-
public enum MongoClientError: ErrorProtocol
- -
-
-

ErrorType for MongoClient error reporting

- -
-
-
-
    -
  • -
    - - - - InitError - -
    -
    -
    -
    -
    -
    -

    returns error string

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case InitError(String)
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/Enums/MongoIndexStorageOptionType.html b/docs/Enums/MongoIndexStorageOptionType.html deleted file mode 100644 index 5d697ed..0000000 --- a/docs/Enums/MongoIndexStorageOptionType.html +++ /dev/null @@ -1,188 +0,0 @@ - - - - MongoIndexStorageOptionType Enum Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoIndexStorageOptionType

-
-
-
public enum MongoIndexStorageOptionType: UInt32
- -
-
-

Enum for storage options

- -
-
-
-
    -
  • -
    - - - - MMapV1 - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoIndexStorageOptionType: UInt32
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - WiredTiger - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoIndexStorageOptionType: UInt32
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/Enums/MongoInsertFlag.html b/docs/Enums/MongoInsertFlag.html deleted file mode 100644 index faee7c6..0000000 --- a/docs/Enums/MongoInsertFlag.html +++ /dev/null @@ -1,223 +0,0 @@ - - - - MongoInsertFlag Enum Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoInsertFlag

-
-
-
public enum MongoInsertFlag: Int
- -
-
-

Enum of flags for insertion options

- -
-
-
-
    -
  • -
    - - - - None - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoInsertFlag: Int
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - ContinueOnError - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoInsertFlag: Int
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - NoValidate - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoInsertFlag: Int
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/Enums/MongoRemoveFlag.html b/docs/Enums/MongoRemoveFlag.html deleted file mode 100644 index b20d88a..0000000 --- a/docs/Enums/MongoRemoveFlag.html +++ /dev/null @@ -1,192 +0,0 @@ - - - - MongoRemoveFlag Enum Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoRemoveFlag

-
-
-
public enum MongoRemoveFlag: Int
- -
-
-

Enum of flags for remove options

- -
-
-
-
    -
  • -
    - - - - None - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoRemoveFlag: Int
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - SingleRemove - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoRemoveFlag: Int
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/Enums/MongoResult.html b/docs/Enums/MongoResult.html deleted file mode 100644 index 5b06ef7..0000000 --- a/docs/Enums/MongoResult.html +++ /dev/null @@ -1,285 +0,0 @@ - - - - MongoResult Enum Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoResult

-
-
-
public enum MongoResult
- -
-
-

Result Status for a MongoDB event

- -
-
-
-
    -
  • -
    - - - - Success - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoResult
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - Error - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoResult
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - ReplyDoc - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoResult
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - ReplyInt - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoResult
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - ReplyCollection - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoResult
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/Enums/MongoUpdateFlag.html b/docs/Enums/MongoUpdateFlag.html deleted file mode 100644 index cedcb36..0000000 --- a/docs/Enums/MongoUpdateFlag.html +++ /dev/null @@ -1,254 +0,0 @@ - - - - MongoUpdateFlag Enum Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoUpdateFlag

-
-
-
public enum MongoUpdateFlag: Int
- -
-
-

Enum of flags for update options

- -
-
-
-
    -
  • -
    - - - - None - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoUpdateFlag: Int
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - Upsert - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoUpdateFlag: Int
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - MultiUpdate - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoUpdateFlag: Int
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - NoValidate - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoUpdateFlag: Int
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/Functions.html b/docs/Functions.html deleted file mode 100644 index 7266450..0000000 --- a/docs/Functions.html +++ /dev/null @@ -1,191 +0,0 @@ - - - - Functions Reference - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

Functions

-

The following functions are available globally.

- -
-
-
-
    -
  • -
    - - - - ==(_:_:) - -
    -
    -
    -
    -
    -
    -

    compare two BSON documents for equality

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func ==(lhs: BSON, rhs: BSON) -> Bool
    - -
    -
    -
    -

    Return Value

    -

    BOOL.

    - -
    -
    -
    -
  • -
  • -
    - - - - <(_:_:) - -
    -
    -
    -
    -
    -
    -

    compare two BSON documents for sort priority

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func <(lhs: BSON, rhs: BSON) -> Bool
    - -
    -
    -
    -

    Return Value

    -

    true if lhs sorts above rhs, false otherwise.

    - -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/Structs.html b/docs/Structs.html deleted file mode 100644 index 24f36a4..0000000 --- a/docs/Structs.html +++ /dev/null @@ -1,155 +0,0 @@ - - - - Structs Reference - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

Structs

-

The following structs are available globally.

- -
-
-
-
    -
  • -
    - - - - MongoQueryFlag - -
    -
    -
    -
    -
    -
    -

    Struct used to access Mongo Query options

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public struct MongoQueryFlag: OptionSet
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/Structs/MongoQueryFlag.html b/docs/Structs/MongoQueryFlag.html deleted file mode 100644 index c8a3a54..0000000 --- a/docs/Structs/MongoQueryFlag.html +++ /dev/null @@ -1,188 +0,0 @@ - - - - MongoQueryFlag Struct Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoQueryFlag

-
-
-
public struct MongoQueryFlag: OptionSet
- -
-
-

Struct used to access Mongo Query options

- -
-
-
-
    -
  • -
    - - - - rawValue - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public struct MongoQueryFlag: OptionSet
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init(rawValue:) - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public struct MongoQueryFlag: OptionSet
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/css/highlight.css b/docs/css/highlight.css deleted file mode 100644 index d0db0e1..0000000 --- a/docs/css/highlight.css +++ /dev/null @@ -1,200 +0,0 @@ -/* Credit to https://gist.github.com/wataru420/2048287 */ -.highlight { - /* Comment */ - /* Error */ - /* Keyword */ - /* Operator */ - /* Comment.Multiline */ - /* Comment.Preproc */ - /* Comment.Single */ - /* Comment.Special */ - /* Generic.Deleted */ - /* Generic.Deleted.Specific */ - /* Generic.Emph */ - /* Generic.Error */ - /* Generic.Heading */ - /* Generic.Inserted */ - /* Generic.Inserted.Specific */ - /* Generic.Output */ - /* Generic.Prompt */ - /* Generic.Strong */ - /* Generic.Subheading */ - /* Generic.Traceback */ - /* Keyword.Constant */ - /* Keyword.Declaration */ - /* Keyword.Pseudo */ - /* Keyword.Reserved */ - /* Keyword.Type */ - /* Literal.Number */ - /* Literal.String */ - /* Name.Attribute */ - /* Name.Builtin */ - /* Name.Class */ - /* Name.Constant */ - /* Name.Entity */ - /* Name.Exception */ - /* Name.Function */ - /* Name.Namespace */ - /* Name.Tag */ - /* Name.Variable */ - /* Operator.Word */ - /* Text.Whitespace */ - /* Literal.Number.Float */ - /* Literal.Number.Hex */ - /* Literal.Number.Integer */ - /* Literal.Number.Oct */ - /* Literal.String.Backtick */ - /* Literal.String.Char */ - /* Literal.String.Doc */ - /* Literal.String.Double */ - /* Literal.String.Escape */ - /* Literal.String.Heredoc */ - /* Literal.String.Interpol */ - /* Literal.String.Other */ - /* Literal.String.Regex */ - /* Literal.String.Single */ - /* Literal.String.Symbol */ - /* Name.Builtin.Pseudo */ - /* Name.Variable.Class */ - /* Name.Variable.Global */ - /* Name.Variable.Instance */ - /* Literal.Number.Integer.Long */ } - .highlight .c { - color: #999988; - font-style: italic; } - .highlight .err { - color: #a61717; - background-color: #e3d2d2; } - .highlight .k { - color: #000000; - font-weight: bold; } - .highlight .o { - color: #000000; - font-weight: bold; } - .highlight .cm { - color: #999988; - font-style: italic; } - .highlight .cp { - color: #999999; - font-weight: bold; } - .highlight .c1 { - color: #999988; - font-style: italic; } - .highlight .cs { - color: #999999; - font-weight: bold; - font-style: italic; } - .highlight .gd { - color: #000000; - background-color: #ffdddd; } - .highlight .gd .x { - color: #000000; - background-color: #ffaaaa; } - .highlight .ge { - color: #000000; - font-style: italic; } - .highlight .gr { - color: #aa0000; } - .highlight .gh { - color: #999999; } - .highlight .gi { - color: #000000; - background-color: #ddffdd; } - .highlight .gi .x { - color: #000000; - background-color: #aaffaa; } - .highlight .go { - color: #888888; } - .highlight .gp { - color: #555555; } - .highlight .gs { - font-weight: bold; } - .highlight .gu { - color: #aaaaaa; } - .highlight .gt { - color: #aa0000; } - .highlight .kc { - color: #000000; - font-weight: bold; } - .highlight .kd { - color: #000000; - font-weight: bold; } - .highlight .kp { - color: #000000; - font-weight: bold; } - .highlight .kr { - color: #000000; - font-weight: bold; } - .highlight .kt { - color: #445588; } - .highlight .m { - color: #009999; } - .highlight .s { - color: #d14; } - .highlight .na { - color: #008080; } - .highlight .nb { - color: #0086B3; } - .highlight .nc { - color: #445588; - font-weight: bold; } - .highlight .no { - color: #008080; } - .highlight .ni { - color: #800080; } - .highlight .ne { - color: #990000; - font-weight: bold; } - .highlight .nf { - color: #990000; } - .highlight .nn { - color: #555555; } - .highlight .nt { - color: #000080; } - .highlight .nv { - color: #008080; } - .highlight .ow { - color: #000000; - font-weight: bold; } - .highlight .w { - color: #bbbbbb; } - .highlight .mf { - color: #009999; } - .highlight .mh { - color: #009999; } - .highlight .mi { - color: #009999; } - .highlight .mo { - color: #009999; } - .highlight .sb { - color: #d14; } - .highlight .sc { - color: #d14; } - .highlight .sd { - color: #d14; } - .highlight .s2 { - color: #d14; } - .highlight .se { - color: #d14; } - .highlight .sh { - color: #d14; } - .highlight .si { - color: #d14; } - .highlight .sx { - color: #d14; } - .highlight .sr { - color: #009926; } - .highlight .s1 { - color: #d14; } - .highlight .ss { - color: #990073; } - .highlight .bp { - color: #999999; } - .highlight .vc { - color: #008080; } - .highlight .vg { - color: #008080; } - .highlight .vi { - color: #008080; } - .highlight .il { - color: #009999; } diff --git a/docs/css/jazzy.css b/docs/css/jazzy.css deleted file mode 100644 index d6d65b7..0000000 --- a/docs/css/jazzy.css +++ /dev/null @@ -1,332 +0,0 @@ -html, body, div, span, h1, h3, h4, p, a, code, em, img, ul, li, table, tbody, tr, td { - background: transparent; - border: 0; - margin: 0; - outline: 0; - padding: 0; - vertical-align: baseline; } - -body { - background-color: #f2f2f2; - font-family: Helvetica, freesans, Arial, sans-serif; - font-size: 14px; - -webkit-font-smoothing: subpixel-antialiased; - word-wrap: break-word; } - -h1, h2, h3 { - margin-top: 0.8em; - margin-bottom: 0.3em; - font-weight: 100; - color: black; } - -h1 { - font-size: 2.5em; } - -h2 { - font-size: 2em; - border-bottom: 1px solid #e2e2e2; } - -h4 { - font-size: 13px; - line-height: 1.5; - margin-top: 21px; } - -h5 { - font-size: 1.1em; } - -h6 { - font-size: 1.1em; - color: #777; } - -.section-name { - color: gray; - display: block; - font-family: Helvetica; - font-size: 22px; - font-weight: 100; - margin-bottom: 15px; } - -pre, code { - font: 0.95em Menlo, monospace; - color: #777; - word-wrap: normal; } - -p code, li code { - background-color: #eee; - padding: 2px 4px; - border-radius: 4px; } - -a { - color: #0088cc; - text-decoration: none; } - -ul { - padding-left: 15px; } - -li { - line-height: 1.8em; } - -img { - max-width: 100%; } - -blockquote { - margin-left: 0; - padding: 0 10px; - border-left: 4px solid #ccc; } - -.content-wrapper { - margin: 0 auto; - width: 980px; } - -header { - font-size: 0.85em; - line-height: 26px; - background-color: #414141; - position: fixed; - width: 100%; - z-index: 1; } - header img { - padding-right: 6px; - vertical-align: -4px; - height: 16px; } - header a { - color: #fff; } - header p { - float: left; - color: #999; } - header .header-right { - float: right; - margin-left: 16px; } - -#breadcrumbs { - background-color: #f2f2f2; - height: 27px; - padding-top: 17px; - position: fixed; - width: 100%; - z-index: 1; - margin-top: 26px; } - #breadcrumbs #carat { - height: 10px; - margin: 0 5px; } - -.sidebar { - background-color: #f9f9f9; - border: 1px solid #e2e2e2; - overflow-y: auto; - overflow-x: hidden; - position: fixed; - top: 70px; - bottom: 0; - width: 230px; - word-wrap: normal; } - -.nav-groups { - list-style-type: none; - background: #fff; - padding-left: 0; } - -.nav-group-name { - border-bottom: 1px solid #e2e2e2; - font-size: 1.1em; - font-weight: 100; - padding: 15px 0 15px 20px; } - .nav-group-name > a { - color: #333; } - -.nav-group-tasks { - margin-top: 5px; } - -.nav-group-task { - font-size: 0.9em; - list-style-type: none; - white-space: nowrap; } - .nav-group-task a { - color: #888; } - -.main-content { - background-color: #fff; - border: 1px solid #e2e2e2; - margin-left: 246px; - position: absolute; - overflow: hidden; - padding-bottom: 60px; - top: 70px; - width: 734px; } - .main-content p, .main-content a, .main-content code, .main-content em, .main-content ul, .main-content table, .main-content blockquote { - margin-bottom: 1em; } - .main-content p { - line-height: 1.8em; } - .main-content section .section:first-child { - margin-top: 0; - padding-top: 0; } - .main-content section .task-group-section .task-group:first-of-type { - padding-top: 10px; } - .main-content section .task-group-section .task-group:first-of-type .section-name { - padding-top: 15px; } - -.section { - padding: 0 25px; } - -.highlight { - background-color: #eee; - padding: 10px 12px; - border: 1px solid #e2e2e2; - border-radius: 4px; - overflow-x: auto; } - -.declaration .highlight { - overflow-x: initial; - padding: 0 40px 40px 0; - margin-bottom: -25px; - background-color: transparent; - border: none; } - -.section-name { - margin: 0; - margin-left: 18px; } - -.task-group-section { - padding-left: 6px; - border-top: 1px solid #e2e2e2; } - -.task-group { - padding-top: 0px; } - -.task-name-container a[name]:before { - content: ""; - display: block; - padding-top: 70px; - margin: -70px 0 0; } - -.item { - padding-top: 8px; - width: 100%; - list-style-type: none; } - .item a[name]:before { - content: ""; - display: block; - padding-top: 70px; - margin: -70px 0 0; } - .item code { - background-color: transparent; - padding: 0; } - .item .token { - padding-left: 3px; - margin-left: 15px; - font-size: 11.9px; } - .item .declaration-note { - font-size: .85em; - color: gray; - font-style: italic; } - -.pointer-container { - border-bottom: 1px solid #e2e2e2; - left: -23px; - padding-bottom: 13px; - position: relative; - width: 110%; } - -.pointer { - background: #f9f9f9; - border-left: 1px solid #e2e2e2; - border-top: 1px solid #e2e2e2; - height: 12px; - left: 21px; - top: -7px; - -webkit-transform: rotate(45deg); - -moz-transform: rotate(45deg); - -o-transform: rotate(45deg); - transform: rotate(45deg); - position: absolute; - width: 12px; } - -.height-container { - display: none; - left: -25px; - padding: 0 25px; - position: relative; - width: 100%; - overflow: hidden; } - .height-container .section { - background: #f9f9f9; - border-bottom: 1px solid #e2e2e2; - left: -25px; - position: relative; - width: 100%; - padding-top: 10px; - padding-bottom: 5px; } - -.aside, .language { - padding: 6px 12px; - margin: 12px 0; - border-left: 5px solid #dddddd; - overflow-y: hidden; } - .aside .aside-title, .language .aside-title { - font-size: 9px; - letter-spacing: 2px; - text-transform: uppercase; - padding-bottom: 0; - margin: 0; - color: #aaa; - -webkit-user-select: none; } - .aside p:last-child, .language p:last-child { - margin-bottom: 0; } - -.language { - border-left: 5px solid #cde9f4; } - .language .aside-title { - color: #4b8afb; } - -.aside-warning { - border-left: 5px solid #ff6666; } - .aside-warning .aside-title { - color: #ff0000; } - -.graybox { - border-collapse: collapse; - width: 100%; } - .graybox p { - margin: 0; - word-break: break-word; - min-width: 50px; } - .graybox td { - border: 1px solid #e2e2e2; - padding: 5px 25px 5px 10px; - vertical-align: middle; } - .graybox tr td:first-of-type { - text-align: right; - padding: 7px; - vertical-align: top; - word-break: normal; - width: 40px; } - -.slightly-smaller { - font-size: 0.9em; } - -#footer { - position: absolute; - bottom: 10px; - margin-left: 25px; } - #footer p { - margin: 0; - color: #aaa; - font-size: 0.8em; } - -html.dash header, html.dash #breadcrumbs, html.dash .sidebar { - display: none; } -html.dash .main-content { - width: 980px; - margin-left: 0; - border: none; - width: 100%; - top: 0; - padding-bottom: 0; } -html.dash .height-container { - display: block; } -html.dash .item .token { - margin-left: 0; } -html.dash .content-wrapper { - width: auto; } -html.dash #footer { - position: static; } diff --git a/docs/docsets/.docset/Contents/Info.plist b/docs/docsets/.docset/Contents/Info.plist deleted file mode 100644 index 4fcbfbd..0000000 --- a/docs/docsets/.docset/Contents/Info.plist +++ /dev/null @@ -1,20 +0,0 @@ - - - - - CFBundleIdentifier - com.jazzy. - CFBundleName - - DocSetPlatformFamily - jazzy - isDashDocset - - dashIndexFilePath - index.html - isJavaScriptEnabled - - DashDocSetFamily - dashtoc - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Classes.html b/docs/docsets/.docset/Contents/Resources/Documents/Classes.html deleted file mode 100644 index 512d77d..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Classes.html +++ /dev/null @@ -1,363 +0,0 @@ - - - - Classes Reference - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

Classes

-

The following classes are available globally.

- -
-
-
-
    -
  • -
    - - - - MongoIndexOptionsGeo - -
    -
    -
    -
    -
    -
    -

    class to manage Mongo Geospatial indexing options

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public class MongoIndexOptionsGeo
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - MongoIndexOptions - -
    -
    -
    -
    -
    -
    -

    class to manage Mongo indexing options

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public class MongoIndexOptions
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - MongoCollection - -
    -
    -
    -
    -
    -
    -

    The MongoCollection class

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public class MongoCollection
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - MongoDatabase - -
    -
    -
    -
    -
    -
    -

    MongoDatabase is an open-source document database that provides high performance, high availability, and automatic scaling.

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public class MongoDatabase
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - BSON - -
    -
    -
    -
    -
    -
    -

    BSON class

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public class BSON: CustomStringConvertible
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - MongoClientPool - -
    -
    -
    -
    -
    -
    -

    Allows connection pooling. This class is thread-safe.

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public class MongoClientPool
    - -
    -
    -
    -
    -
  • -
-
-
- -
-
-
    -
  • -
    - - - - MongoCursor - -
    -
    -
    -
    -
    -
    -

    The Mongo Cursor interface

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public class MongoCursor
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Classes/BSON.html b/docs/docsets/.docset/Contents/Resources/Documents/Classes/BSON.html deleted file mode 100644 index 3f74ea0..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Classes/BSON.html +++ /dev/null @@ -1,1559 +0,0 @@ - - - - BSON Class Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

BSON

-
-
-
public class BSON: CustomStringConvertible
- -
-
-

BSON class

- -
-
-
-
    -
  • -
    - - - - description - -
    -
    -
    -
    -
    -
    -

    Return JSON representation of current BSON contents as a String

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var description: String
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init() - -
    -
    -
    -
    -
    -
    -

    Allocates a new doc structure. Call the various append() functions to add fields to the bson. You can iterate the doc at any time using a bson_iter_t and bson_iter_init().

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init()
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init(bytes:) - -
    -
    -
    -
    -
    -
    -

    Creates a new doc structure using the data provided. bytes should contain bytes that can be copied into the new doc structure. - * - *- parameter bytes: A byte array containing a serialized bson document.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(bytes: [UInt8])
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init(json:) - -
    -
    -
    -
    -
    -
    -

    Creates a new doc structure using the data provided. json should contain bytes that can be copied into the new doc structure.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(json: String) throws
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - json - - -
    -

    A string containing a json data.

    - -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - init(document:) - -
    -
    -
    -
    -
    -
    -

    Creates a new doc by copying the provided bson doc.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(document: BSON)
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - document - - -
    -

    An existing bson document.

    - -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - close() - -
    -
    -
    -
    -
    -
    -

    close, destroy and release the current BSON document

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func close()
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - asString - -
    -
    -
    -
    -
    -
    -

    Creates a new string containing current document in extended JSON format.

    - -

    See http://docs.mongodb.org/manual/reference/mongodb-extended-json/ for - more information on extended JSON.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var asString: String
    - -
    -
    -
    -

    Return Value

    -

    String

    - -
    -
    -
    -
  • -
  • -
    - - - - asArrayString - -
    -
    -
    -
    -
    -
    -

    like asString() but for outermost arrays.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var asArrayString: String
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - asBytes - -
    -
    -
    -
    -
    -
    -

    asBytes:

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public var asBytes: [UInt8]
    - -
    -
    -
    -

    Return Value

    -

    A byte array from current BSON document

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:document:) - -
    -
    -
    -
    -
    -
    -

    Appends a new field to self.doc of the type BSON_TYPE_DOCUMENT. The documents contents will be copied into self.doc.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, document: BSON) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - document - - -
    -

    Existing BSON document.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:) - -
    -
    -
    -
    -
    -
    -

    Appends a new field to self.doc with NULL for the value.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:oid:) - -
    -
    -
    -
    -
    -
    -

    Appends a new field to the self.doc of type BSON_TYPE_OID using the contents of - oid.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, oid: bson_oid_t) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - oid - - -
    -

    bson_oid_t.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:int:) - -
    -
    -
    -
    -
    -
    -

    Appends a new field of type BSON_TYPE_INT64 to self.doc .

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, int: Int) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - int - - -
    -

    The Int 64-bit integer value.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:int32:) - -
    -
    -
    -
    -
    -
    -

    Appends a new field of type BSON_TYPE_INT32 to self.doc .

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, int32: Int32) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - int32 - - -
    -

    The Int32 32-bit integer value.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:dateTime:) - -
    -
    -
    -
    -
    -
    -

    Appends a new field to self.doc of type BSON_TYPE_DATE_TIME.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, dateTime: Int64) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - dateTime - - -
    -

    The number of milliseconds elapsed since UNIX epoch.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if sucessful; otherwise false.

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:time:) - -
    -
    -
    -
    -
    -
    -

    Appends a BSON_TYPE_DATE_TIME field to self.doc using the time_t @value for the - number of seconds since UNIX epoch in UTC.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, time: time_t) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - time - - -
    -

    A time_t.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:double:) - -
    -
    -
    -
    -
    -
    -

    Appends a new field to self.doc of the type BSON_TYPE_DOUBLE.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, double: Double) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - double - - -
    -

    The double to be appended

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:bool:) - -
    -
    -
    -
    -
    -
    -

    Appends a new field to self.doc of type BSON_TYPE_BOOL.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, bool: Bool) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - bool - - -
    -

    The boolean value.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:string:) - -
    -
    -
    -
    -
    -
    -

    Appends a new field to self.doc using @key as the key and @string as the UTF-8 - encoded value.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, string: String) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - string - - -
    -

    A UTF-8 encoded string.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - append(key:bytes:) - -
    -
    -
    -
    -
    -
    -

    Appends a bytes buffer to the BSON document.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, bytes: [UInt8]) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - bytes - - -
    -

    The bytes to append

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Appends a new field to self.doc of type BSON_TYPE_REGEX. @regex should - be the regex string. @options should contain the options for the regex.

    - -

    Valid options for @options are:

    - -

    ‘i’ for case-insensitive. - ’m’ for multiple matching. - ‘x’ for verbose mode. - 'l’ to make \w and \W locale dependent. - ’s’ for dotall mode (’.’ matches everything) - 'u’ to make \w and \W match unicode.

    - -

    For more information on what comprimises a BSON regex, see bsonspec.org.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func append(key key: String, regex: String, options: String) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - - - - - -
    - - key - - -
    -

    The key of the field.

    - -
    -
    - - regex - - -
    -

    The regex to append to the bson.

    - -
    -
    - - options - - -
    -

    Options for @regex.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - countKeys() - -
    -
    -
    -
    -
    -
    -

    Counts the number of elements found in self.doc. -- returns: Int value of keys count

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func countKeys() -> Int
    - -
    -
    -
    -

    Return Value

    -

    Int value of keys count

    - -
    -
    -
    -
  • -
  • -
    - - - - hasField(key:) - -
    -
    -
    -
    -
    -
    -

    Checks to see if self.doc contains a field named @key.

    - -

    This function is case-sensitive.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func hasField(key key: String) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - key - - -
    -

    The key to lookup.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if @key exists in self.doc; otherwise false.

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Appends a new field named key to self.doc, the field is, however, - incomplete. @child will be initialized so that you may add fields to the - child array. Child will use a memory buffer owned by self.doc and - therefore grow the parent buffer as additional space is used. This allows - a single malloc’d buffer to be used when building arrays which can help - reduce memory fragmentation.

    - -

    The type of @child will be BSON_TYPE_ARRAY and therefore the keys inside - of it MUST be 0, 1, etc.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func appendArrayBegin(key key: String, child: BSON) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - child - - -
    -

    A location to an uninitialized bson_t.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - appendArrayEnd(_:) - -
    -
    -
    -
    -
    -
    -

    Finishes the appending of an array to self.doc. child is considered - disposed after this call and should not be used any further.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func appendArrayEnd(child: BSON) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - child - - -
    -

    A bson document supplied to appendArrayBegin().

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Appends a BSON array to self.doc. BSON arrays are like documents where the - key is the string version of the index. For example, the first item of the - array would have the key 0. The second item would have the index 1.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func appendArray(key key: String, array: BSON) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - key - - -
    -

    The key for the field.

    - -
    -
    - - array - - -
    -

    A bson document containing the array.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
  • -
    - - - - concat(_:) - -
    -
    -
    -
    -
    -
    -

    Concatenate src with self.doc

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func concat(src: BSON) -> Bool
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - src - - -
    -

    BSON doc to be concatenated.

    - -
    -
    -
    -
    -

    Return Value

    -

    true if successful; false if append would overflow max size.

    - -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoClient.html b/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoClient.html deleted file mode 100644 index 98b1e4e..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoClient.html +++ /dev/null @@ -1,386 +0,0 @@ - - - - MongoClient Class Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoClient

-

Undocumented

- -
-
-
-
    -
  • -
    - - - - Result - -
    -
    -
    -
    -
    -
    -

    Result Status enum for a MongoDB event

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public typealias Result = MongoResult
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init(uri:) - -
    -
    -
    -
    -
    -
    -

    Create new Mongo Client connection

    - -
    -

    Throws

    - MongoClientError Could not parse URI if nil response - -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(uri: String) throws
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - close() - -
    -
    -
    -
    -
    -
    -

    terminate current Mongo Client connection

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func close()
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Return the specified MongoCollection from the specified database using current connection

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func getCollection(databaseName: String, collectionName: String) -> MongoCollection
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - databaseName - - -
    -

    String name of database to be used

    - -
    -
    - - collectionName - - -
    -

    String name of collection to be retrieved

    - -
    -
    -
    -
    -

    Return Value

    -

    MongoCollection from specified database

    - -
    -
    -
    -
  • -
  • -
    - - - - getDatabase(name:) - -
    -
    -
    -
    -
    -
    -

    Return the named database as a MongoDatabase object

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func getDatabase(name databaseName: String) -> MongoDatabase
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - name - - -
    -

    String name of database to be retrieved

    - -
    -
    -
    -
    -

    Return Value

    -

    a MongoDatabase object

    - -
    -
    -
    -
  • -
  • -
    - - - - serverStatus() - -
    -
    -
    -
    -
    -
    -

    Get current Mongo server status

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func serverStatus() -> Result
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - databaseNames() - -
    -
    -
    -
    -
    -
    -

    Build String Array of current database names

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func databaseNames() -> [String]
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoClientPool.html b/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoClientPool.html deleted file mode 100644 index 0df156c..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoClientPool.html +++ /dev/null @@ -1,339 +0,0 @@ - - - - MongoClientPool Class Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoClientPool

-
-
-
public class MongoClientPool
- -
-
-

Allows connection pooling. This class is thread-safe.

- -
-
-
-
    -
  • -
    - - - - init(uri:) - -
    -
    -
    -
    -
    -
    -

    create new ClientPool with provided String uri

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(uri: String)
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - uri - - -
    -

    String uri to connect client pool

    - -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - tryPopClient() - -
    -
    -
    -
    -
    -
    -

    Try to pop a client connection from the connection pool.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func tryPopClient() -> MongoClient?
    - -
    -
    -
    -

    Return Value

    -

    nil if no client connection is currently queued for reuse.

    - -
    -
    -
    -
  • -
  • -
    - - - - popClient() - -
    -
    -
    -
    -
    -
    -

    Pop a client connection from the connection pool.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func popClient() -> MongoClient
    - -
    -
    -
    -

    Return Value

    -

    MongoClient from connection pool

    - -
    -
    -
    -
  • -
  • -
    - - - - pushClient(_:) - -
    -
    -
    -
    -
    -
    -

    Pushes back popped client connection.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func pushClient(_ client: MongoClient)
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - client - - -
    -

    MongoClient to be pushed back into pool

    - -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - executeBlock(_:) - -
    -
    -
    -
    -
    -
    -

    Automatically pops a client, makes it available within the block and pushes it back.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func executeBlock(@noescape block: (client: MongoClient) -> Void)
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - block - - -
    -

    block to be executed with popped client

    - -
    -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoCollection.html b/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoCollection.html deleted file mode 100644 index f493fcf..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoCollection.html +++ /dev/null @@ -1,1288 +0,0 @@ - - - - MongoCollection Class Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoCollection

-
-
-
public class MongoCollection
- -
-
-

The MongoCollection class

- -
-
-
-
    -
  • -
    - - - - Result - -
    -
    -
    -
    -
    -
    -

    Result Status enum for a MongoDB event

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public typealias Result = MongoResult
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    obtain access to a specified database and collection using the MongoClient

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(client: MongoClient, databaseName: String, collectionName: String)
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - - - - - -
    - - client - - -
    -

    the MongoClient to be used

    - -
    -
    - - databaseName - - -
    -

    String database name

    - -
    -
    - - collectionName - - -
    -

    String collection name

    - -
    -
    -
    -
    -
    -
  • -
  • -
    - - - - close() - -
    -
    -
    -
    -
    -
    -

    close connection to the current collection

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func close()
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Insert document into the current collection returning a result status

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func insert(document document: BSON, flag: MongoInsertFlag = .None) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - document - - -
    -

    BSON document to be inserted

    - -
    -
    - - flag - - -
    -

    Optional MongoInsertFlag defaults to .None

    - -
    -
    -
    -
    -

    Return Value

    -

    Result object with status of insert

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Update the document found using selector with the update document returning a result status

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func update(update: BSON, selector: BSON, flag: MongoUpdateFlag = .None) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - - - - - -
    - - update - - -
    -

    BSON document to be used to update

    - -
    -
    - - selector - - -
    -

    BSON document with selection criteria

    - -
    -
    - - flag - - -
    -

    Optional MongoUpdateFlag defaults to .None

    - -
    -
    -
    -
    -

    Return Value

    -

    Result object with status of update

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Remove the document found using selector returning a result status

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func remove(selector selector: BSON, flag: MongoRemoveFlag = .None) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - selector - - -
    -

    BSON document with selection criteria

    - -
    -
    - - flag - - -
    -

    Optional MongoRemoveFlag defaults to .None

    - -
    -
    -
    -
    -

    Return Value

    -

    Result object with status of removal

    - -
    -
    -
    -
  • -
  • -
    - - - - save(document:) - -
    -
    -
    -
    -
    -
    -

    Updates document returning a result status

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func save(document document: BSON) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - document - - -
    -

    BSON document to be saved

    - -
    -
    -
    -
    -

    Return Value

    -

    Result object with status of save

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Renames the collection using newDbName and newCollectionName, with option to drop existing collection immediately instead of after the move, returning a result status

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func rename(newDbName: String, newCollectionName: String, dropExisting: Bool) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - - - - - -
    - - newDbName - - -
    -

    String name for db after move

    - -
    -
    - - newCollectionName - - -
    -

    String name for collection after move

    - -
    -
    - - dropExisting - - -
    -

    Bool option to drop existing collection immediately instead of after move

    - -
    -
    -
    -
    -

    Return Value

    -

    Result object with status of renaming

    - -
    -
    -
    -
  • -
  • -
    - - - - name() - -
    -
    -
    -
    -
    -
    -

    The collection name as a String

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func name() -> String
    - -
    -
    -
    -

    Return Value

    -

    String the name of the current collection

    - -
    -
    -
    -
  • -
  • -
    - - - - validate(_:) - -
    -
    -
    -
    -
    -
    -

    Validates a collection. The method scans a collection’s data structures for correctness and returns a single document that describes the relationship between the logical collection and the physical representation of the data.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func validate(options: BSON) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - options - - -
    -

    Optional. Specify true to enable a full validation and to return full statistics. MongoDB disables full validation by default because it is a potentially resource-intensive operation.

    - -
    -
    -
    -
    -

    Return Value

    -

    BSON document describing the relationship between the collection and its physical representation

    - -
    -
    -
    -
  • -
  • -
    - - - - stats(_:) - -
    -
    -
    -
    -
    -
    -

    Returns statistics about the collection formatted according to the options document.

    - -
      -
    • The options document can contain the following fields and values:

    • -
    • scale: number, Optional. The scale used in the output to display the sizes of items. By default, output displays sizes in bytes. To display kilobytes rather than bytes, specify a scale value of 1024.

    • -
    • indexDetails: boolean, Optional. If true, stats() returns index details in addition to the collection stats. Only works for WiredTiger storage engine. Defaults to false.

    • -
    • indexDetailsKey: document, Optional. If indexDetails is true, you can use indexDetailsKey to filter index details by specifying the index key specification. Only the index that exactly matches indexDetailsKey will be returned. If no match is found, indexDetails will display statistics for all indexes.

    • -
    • indexDetailsName: string, Optional. If indexDetails is true, you can use indexDetailsName to filter index details by specifying the index name. Only the index name that exactly matches indexDetailsName will be returned. If no match is found, indexDetails will display statistics for all indexes.

    • -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func stats(options: BSON) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - options - - -
    -

    a BSON document defining the format of return.

    - -
    -
    -
    -
    -

    Return Value

    -

    BSON document with formatted statistics or Results error document

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Selects documents in a collection and returns a cursor to the selected documents.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func find(query: BSON, fields: BSON? = nil, flags: MongoQueryFlag = MongoQueryFlag.None, skip: Int = 0, limit: Int = 0, batchSize: Int = 0) -> MongoCursor?
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - query - - -
    -

    Optional. Specifies selection filter using query operators. To return all documents in a collection, omit this- Parameter or pass an empty document ({}).

    - -
    -
    - - fields - - -
    -

    Optional. Specifies the fields to return in the documents that match the query filter. To return all fields in the matching documents, omit this parameter.

    - -
    -
    - - flags - - -
    -

    Optional. set queryFlags for the current search

    - -
    -
    - - skip - - -
    -

    Optional. Skip the supplied number of records.

    - -
    -
    - - limit - - -
    -

    Optional. return no more than the supplied number of records.

    - -
    -
    - - batchSize - - -
    -

    Optional. Change number of automatically iterated documents.

    - -
    -
    -
    -
    -

    Return Value

    -

    A cursor to the documents that match the query criteria. When the find() method “returns documents,” the method is actually returning a cursor to the documents.

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Creates indexes on collections.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func createIndex(keys: BSON, options: MongoIndexOptions) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - keys - - -
    -

    A document that conains the field and value pairs where the field is the index key and the value describes the type of index for that field. For an ascending index on a field, specify a value of 1; for descending index, specify a value of -1.

    - -
    -
    - - options - - -
    -

    Optional. A document that contains a set of options that controls the creation of the index. see MongoIndexOptions for details.

    - -
    -
    -
    -
    -

    Return Value

    -

    a Result status

    - -
    -
    -
    -
  • -
  • -
    - - - - dropIndex(_:) - -
    -
    -
    -
    -
    -
    -

    Drops or removes the specified index from a collection.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func dropIndex(name: String) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - index - - -
    -

    Specifies the index to drop, either by name or by the index specification document.

    - -
    -
    -
    -
    -

    Return Value

    -

    a Result status

    - -
    -
    -
    -
  • -
  • -
    - - - - drop() - -
    -
    -
    -
    -
    -
    -

    Removes a collection from the database. The method also removes any indexes associated with the dropped collection.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func drop() -> Result
    - -
    -
    -
    -

    Return Value

    -

    a Result status

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    The count of documents that would match a find() query.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func count(query: BSON, fields: BSON? = nil, flags: MongoQueryFlag = MongoQueryFlag.None, skip: Int = 0, limit: Int = 0, batchSize: Int = 0) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - query - - -
    -

    The query selection criteria.

    - -
    -
    - - fields - - -
    -

    Optional. Specifies the fields to return in the documents that match the query filter. To return all fields in the matching documents, omit this parameter.

    - -
    -
    - - flags - - -
    -

    Optional. set queryFlags for the current search

    - -
    -
    - - skip - - -
    -

    Optional. Skip the supplied number of records.

    - -
    -
    - - limit - - -
    -

    Optional. return no more than the supplied number of records.

    - -
    -
    - - batchSize - - -
    -

    Optional. Change number of automatically iterated documents.

    - -
    -
    -
    -
    -

    Return Value

    -

    the count of documents that would match a find() query. The count() method does not perform the find() operation but instead counts and returns the number of results that match a query.

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Modifies and returns a single document.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func findAndModify(query: BSON, sort: BSON, update: BSON, fields: BSON, remove: Bool, upsert: Bool, new: Bool) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - query - - -
    -

    Optional. The selection criteria for the modification. The query field employs the same query selectors as used in the db.collection.find() method. Although the query may match multiple documents, findAndModify() will only select one document to modify.

    - -
    -
    - - sort - - -
    -

    Optional. Determines which document the operation modifies if the query selects multiple documents. findAndModify() modifies the first document in the sort order specified by this argument.

    - -
    -
    - - update - - -
    -

    Must specify either the remove or the update field. Performs an update of the selected document. The update field employs the same update operators or field: value specifications to modify the selected document.

    - -
    -
    - - fields - - -
    -

    Optional. A subset of fields to return. The fields document specifies an inclusion of a field with 1, as in: fields: { : 1, : 1, … }.

    - -
    -
    - - remove - - -
    -

    Must specify either the remove or the update field. Removes the document specified in the query field. Set this to true to remove the selected document . The default is false.

    - -
    -
    - - upsert - - -
    -

    Optional. Used in conjunction with the update field. When true, findAndModify() creates a new document if no document matches the query, or if documents match the query, findAndModify() performs an update. To avoid multiple upserts, ensure that the query fields are uniquely indexed. The default is false.

    - -
    -
    - - new - - -
    -

    Optional. When true, returns the modified document rather than the original. The findAndModify() method ignores the new option for remove operations. The default is false.

    - -
    -
    -
    -
    -

    Return Value

    -

    Modifies and returns a single document. By default, the returned document does not include the modifications made on the update. To return the document with the modifications made on the update, use the new option.

    - -
    -
    -
    -
  • -
  • -
    - - - - getLastError() - -
    -
    -
    -
    -
    -
    -

    A BSON document with description of last transaction status

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func getLastError() -> BSON
    - -
    -
    -
    -

    Return Value

    -

    BSON document with description of last transaction status

    - -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoCursor.html b/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoCursor.html deleted file mode 100644 index 0f23f86..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoCursor.html +++ /dev/null @@ -1,192 +0,0 @@ - - - - MongoCursor Class Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoCursor

-
-
-
public class MongoCursor
- -
-
-

The Mongo Cursor interface

- -
-
-
-
    -
  • -
    - - - - close() - -
    -
    -
    -
    -
    -
    -

    Close and destroy current cursor

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func close()
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - next() - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func next() -> BSON?
    - -
    -
    -
    -

    Return Value

    -

    next document if available, else nil

    - -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoDatabase.html b/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoDatabase.html deleted file mode 100644 index 498659f..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoDatabase.html +++ /dev/null @@ -1,394 +0,0 @@ - - - - MongoDatabase Class Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoDatabase

-
-
-
public class MongoDatabase
- -
-
-

MongoDatabase is an open-source document database that provides high performance, high availability, and automatic scaling.

- -
-
-
-
    -
  • - -
    -
    -
    -
    -
    -

    Get reference to named database using provided MongoClient instance

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public init(client: MongoClient, databaseName: String)
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - close() - -
    -
    -
    -
    -
    -
    -

    Close connection to database

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func close()
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - drop() - -
    -
    -
    -
    -
    -
    -

    Drops the current database, deleting the associated data files

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func drop() -> Result
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - name() - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func name() -> String
    - -
    -
    -
    -

    Return Value

    -

    a string, the name of the current database

    - -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Create new Collection

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func createCollection(name collectionName: String, options: BSON) -> Result
    - -
    -
    -
    -

    Parameters

    - - - - - - - - - - - -
    - - name - - -
    -

    String, name of collection to be created

    - -
    -
    - - options - - -
    -

    BSON document listing options for new collection

    - -
    -
    -
    -
    -

    Return Value

    -

    MongoCollection

    - -
    -
    -
    -
  • -
  • -
    - - - - getCollection(name:) - -
    -
    -
    -
    -
    -
    -

    MongoCollection referenced by name

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func getCollection(name collectionName: String) -> MongoCollection
    - -
    -
    -
    -

    Parameters

    - - - - - - - -
    - - name - - -
    -

    String collection name

    - -
    -
    -
    -
    -

    Return Value

    -

    MongoCollection

    - -
    -
    -
    -
  • -
  • -
    - - - - collectionNames() - -
    -
    -
    -
    -
    -
    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func collectionNames() -> [String]
    - -
    -
    -
    -

    Return Value

    -

    String Array of current database collections’ names

    - -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoIndexOptions.html b/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoIndexOptions.html deleted file mode 100644 index 047c511..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoIndexOptions.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - MongoIndexOptions Class Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- - -
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoIndexOptionsGeo.html b/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoIndexOptionsGeo.html deleted file mode 100644 index f6d13bc..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Classes/MongoIndexOptionsGeo.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - MongoIndexOptionsGeo Class Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- - -
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Enums.html b/docs/docsets/.docset/Contents/Resources/Documents/Enums.html deleted file mode 100644 index 0ed13ea..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Enums.html +++ /dev/null @@ -1,331 +0,0 @@ - - - - Enums Reference - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

Enums

-

The following enums are available globally.

- -
-
-
-
    -
  • -
    - - - - MongoInsertFlag - -
    -
    -
    -
    -
    -
    -

    Enum of flags for insertion options

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoInsertFlag: Int
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - MongoUpdateFlag - -
    -
    -
    -
    -
    -
    -

    Enum of flags for update options

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoUpdateFlag: Int
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - MongoRemoveFlag - -
    -
    -
    -
    -
    -
    -

    Enum of flags for remove options

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoRemoveFlag: Int
    - -
    -
    -
    -
    -
  • -
  • - -
    -
    -
    -
    -
    -

    Enum for storage options

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoIndexStorageOptionType: UInt32
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - BSONError - -
    -
    -
    -
    -
    -
    -

    BSON error enum

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum BSONError: ErrorProtocol
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - MongoResult - -
    -
    -
    -
    -
    -
    -

    Result Status for a MongoDB event

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoResult
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - MongoClientError - -
    -
    -
    -
    -
    -
    -

    ErrorType for MongoClient error reporting

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoClientError: ErrorProtocol
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Enums/BSONError.html b/docs/docsets/.docset/Contents/Resources/Documents/Enums/BSONError.html deleted file mode 100644 index 897da13..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Enums/BSONError.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - BSONError Enum Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

BSONError

-
-
-
public enum BSONError: ErrorProtocol
- -
-
-

BSON error enum

- -
-
-
-
    -
  • -
    - - - - SyntaxError - -
    -
    -
    -
    -
    -
    -

    The JSON data was malformed.

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case SyntaxError(String)
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Enums/MongoClientError.html b/docs/docsets/.docset/Contents/Resources/Documents/Enums/MongoClientError.html deleted file mode 100644 index db5e6c7..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Enums/MongoClientError.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - MongoClientError Enum Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoClientError

-
-
-
public enum MongoClientError: ErrorProtocol
- -
-
-

ErrorType for MongoClient error reporting

- -
-
-
-
    -
  • -
    - - - - InitError - -
    -
    -
    -
    -
    -
    -

    returns error string

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    case InitError(String)
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Enums/MongoIndexStorageOptionType.html b/docs/docsets/.docset/Contents/Resources/Documents/Enums/MongoIndexStorageOptionType.html deleted file mode 100644 index 5d697ed..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Enums/MongoIndexStorageOptionType.html +++ /dev/null @@ -1,188 +0,0 @@ - - - - MongoIndexStorageOptionType Enum Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoIndexStorageOptionType

-
-
-
public enum MongoIndexStorageOptionType: UInt32
- -
-
-

Enum for storage options

- -
-
-
-
    -
  • -
    - - - - MMapV1 - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoIndexStorageOptionType: UInt32
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - WiredTiger - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoIndexStorageOptionType: UInt32
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Enums/MongoInsertFlag.html b/docs/docsets/.docset/Contents/Resources/Documents/Enums/MongoInsertFlag.html deleted file mode 100644 index faee7c6..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Enums/MongoInsertFlag.html +++ /dev/null @@ -1,223 +0,0 @@ - - - - MongoInsertFlag Enum Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoInsertFlag

-
-
-
public enum MongoInsertFlag: Int
- -
-
-

Enum of flags for insertion options

- -
-
-
-
    -
  • -
    - - - - None - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoInsertFlag: Int
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - ContinueOnError - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoInsertFlag: Int
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - NoValidate - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoInsertFlag: Int
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Enums/MongoRemoveFlag.html b/docs/docsets/.docset/Contents/Resources/Documents/Enums/MongoRemoveFlag.html deleted file mode 100644 index b20d88a..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Enums/MongoRemoveFlag.html +++ /dev/null @@ -1,192 +0,0 @@ - - - - MongoRemoveFlag Enum Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoRemoveFlag

-
-
-
public enum MongoRemoveFlag: Int
- -
-
-

Enum of flags for remove options

- -
-
-
-
    -
  • -
    - - - - None - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoRemoveFlag: Int
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - SingleRemove - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoRemoveFlag: Int
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Enums/MongoResult.html b/docs/docsets/.docset/Contents/Resources/Documents/Enums/MongoResult.html deleted file mode 100644 index 5b06ef7..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Enums/MongoResult.html +++ /dev/null @@ -1,285 +0,0 @@ - - - - MongoResult Enum Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoResult

-
-
-
public enum MongoResult
- -
-
-

Result Status for a MongoDB event

- -
-
-
-
    -
  • -
    - - - - Success - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoResult
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - Error - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoResult
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - ReplyDoc - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoResult
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - ReplyInt - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoResult
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - ReplyCollection - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoResult
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Enums/MongoUpdateFlag.html b/docs/docsets/.docset/Contents/Resources/Documents/Enums/MongoUpdateFlag.html deleted file mode 100644 index cedcb36..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Enums/MongoUpdateFlag.html +++ /dev/null @@ -1,254 +0,0 @@ - - - - MongoUpdateFlag Enum Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoUpdateFlag

-
-
-
public enum MongoUpdateFlag: Int
- -
-
-

Enum of flags for update options

- -
-
-
-
    -
  • -
    - - - - None - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoUpdateFlag: Int
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - Upsert - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoUpdateFlag: Int
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - MultiUpdate - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoUpdateFlag: Int
    - -
    -
    -
    -
    -
  • -
-
-
-
    -
  • -
    - - - - NoValidate - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public enum MongoUpdateFlag: Int
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Functions.html b/docs/docsets/.docset/Contents/Resources/Documents/Functions.html deleted file mode 100644 index 7266450..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Functions.html +++ /dev/null @@ -1,191 +0,0 @@ - - - - Functions Reference - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

Functions

-

The following functions are available globally.

- -
-
-
-
    -
  • -
    - - - - ==(_:_:) - -
    -
    -
    -
    -
    -
    -

    compare two BSON documents for equality

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func ==(lhs: BSON, rhs: BSON) -> Bool
    - -
    -
    -
    -

    Return Value

    -

    BOOL.

    - -
    -
    -
    -
  • -
  • -
    - - - - <(_:_:) - -
    -
    -
    -
    -
    -
    -

    compare two BSON documents for sort priority

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public func <(lhs: BSON, rhs: BSON) -> Bool
    - -
    -
    -
    -

    Return Value

    -

    true if lhs sorts above rhs, false otherwise.

    - -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Structs.html b/docs/docsets/.docset/Contents/Resources/Documents/Structs.html deleted file mode 100644 index 24f36a4..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Structs.html +++ /dev/null @@ -1,155 +0,0 @@ - - - - Structs Reference - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

Structs

-

The following structs are available globally.

- -
-
-
-
    -
  • -
    - - - - MongoQueryFlag - -
    -
    -
    -
    -
    -
    -

    Struct used to access Mongo Query options

    - - See more -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public struct MongoQueryFlag: OptionSet
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/Structs/MongoQueryFlag.html b/docs/docsets/.docset/Contents/Resources/Documents/Structs/MongoQueryFlag.html deleted file mode 100644 index c8a3a54..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/Structs/MongoQueryFlag.html +++ /dev/null @@ -1,188 +0,0 @@ - - - - MongoQueryFlag Struct Reference - - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
-

MongoQueryFlag

-
-
-
public struct MongoQueryFlag: OptionSet
- -
-
-

Struct used to access Mongo Query options

- -
-
-
-
    -
  • -
    - - - - rawValue - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public struct MongoQueryFlag: OptionSet
    - -
    -
    -
    -
    -
  • -
  • -
    - - - - init(rawValue:) - -
    -
    -
    -
    -
    -
    -

    Undocumented

    - -
    -
    -

    Declaration

    -
    -

    Swift

    -
    public struct MongoQueryFlag: OptionSet
    - -
    -
    -
    -
    -
  • -
-
-
-
- -
-
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/css/highlight.css b/docs/docsets/.docset/Contents/Resources/Documents/css/highlight.css deleted file mode 100644 index d0db0e1..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/css/highlight.css +++ /dev/null @@ -1,200 +0,0 @@ -/* Credit to https://gist.github.com/wataru420/2048287 */ -.highlight { - /* Comment */ - /* Error */ - /* Keyword */ - /* Operator */ - /* Comment.Multiline */ - /* Comment.Preproc */ - /* Comment.Single */ - /* Comment.Special */ - /* Generic.Deleted */ - /* Generic.Deleted.Specific */ - /* Generic.Emph */ - /* Generic.Error */ - /* Generic.Heading */ - /* Generic.Inserted */ - /* Generic.Inserted.Specific */ - /* Generic.Output */ - /* Generic.Prompt */ - /* Generic.Strong */ - /* Generic.Subheading */ - /* Generic.Traceback */ - /* Keyword.Constant */ - /* Keyword.Declaration */ - /* Keyword.Pseudo */ - /* Keyword.Reserved */ - /* Keyword.Type */ - /* Literal.Number */ - /* Literal.String */ - /* Name.Attribute */ - /* Name.Builtin */ - /* Name.Class */ - /* Name.Constant */ - /* Name.Entity */ - /* Name.Exception */ - /* Name.Function */ - /* Name.Namespace */ - /* Name.Tag */ - /* Name.Variable */ - /* Operator.Word */ - /* Text.Whitespace */ - /* Literal.Number.Float */ - /* Literal.Number.Hex */ - /* Literal.Number.Integer */ - /* Literal.Number.Oct */ - /* Literal.String.Backtick */ - /* Literal.String.Char */ - /* Literal.String.Doc */ - /* Literal.String.Double */ - /* Literal.String.Escape */ - /* Literal.String.Heredoc */ - /* Literal.String.Interpol */ - /* Literal.String.Other */ - /* Literal.String.Regex */ - /* Literal.String.Single */ - /* Literal.String.Symbol */ - /* Name.Builtin.Pseudo */ - /* Name.Variable.Class */ - /* Name.Variable.Global */ - /* Name.Variable.Instance */ - /* Literal.Number.Integer.Long */ } - .highlight .c { - color: #999988; - font-style: italic; } - .highlight .err { - color: #a61717; - background-color: #e3d2d2; } - .highlight .k { - color: #000000; - font-weight: bold; } - .highlight .o { - color: #000000; - font-weight: bold; } - .highlight .cm { - color: #999988; - font-style: italic; } - .highlight .cp { - color: #999999; - font-weight: bold; } - .highlight .c1 { - color: #999988; - font-style: italic; } - .highlight .cs { - color: #999999; - font-weight: bold; - font-style: italic; } - .highlight .gd { - color: #000000; - background-color: #ffdddd; } - .highlight .gd .x { - color: #000000; - background-color: #ffaaaa; } - .highlight .ge { - color: #000000; - font-style: italic; } - .highlight .gr { - color: #aa0000; } - .highlight .gh { - color: #999999; } - .highlight .gi { - color: #000000; - background-color: #ddffdd; } - .highlight .gi .x { - color: #000000; - background-color: #aaffaa; } - .highlight .go { - color: #888888; } - .highlight .gp { - color: #555555; } - .highlight .gs { - font-weight: bold; } - .highlight .gu { - color: #aaaaaa; } - .highlight .gt { - color: #aa0000; } - .highlight .kc { - color: #000000; - font-weight: bold; } - .highlight .kd { - color: #000000; - font-weight: bold; } - .highlight .kp { - color: #000000; - font-weight: bold; } - .highlight .kr { - color: #000000; - font-weight: bold; } - .highlight .kt { - color: #445588; } - .highlight .m { - color: #009999; } - .highlight .s { - color: #d14; } - .highlight .na { - color: #008080; } - .highlight .nb { - color: #0086B3; } - .highlight .nc { - color: #445588; - font-weight: bold; } - .highlight .no { - color: #008080; } - .highlight .ni { - color: #800080; } - .highlight .ne { - color: #990000; - font-weight: bold; } - .highlight .nf { - color: #990000; } - .highlight .nn { - color: #555555; } - .highlight .nt { - color: #000080; } - .highlight .nv { - color: #008080; } - .highlight .ow { - color: #000000; - font-weight: bold; } - .highlight .w { - color: #bbbbbb; } - .highlight .mf { - color: #009999; } - .highlight .mh { - color: #009999; } - .highlight .mi { - color: #009999; } - .highlight .mo { - color: #009999; } - .highlight .sb { - color: #d14; } - .highlight .sc { - color: #d14; } - .highlight .sd { - color: #d14; } - .highlight .s2 { - color: #d14; } - .highlight .se { - color: #d14; } - .highlight .sh { - color: #d14; } - .highlight .si { - color: #d14; } - .highlight .sx { - color: #d14; } - .highlight .sr { - color: #009926; } - .highlight .s1 { - color: #d14; } - .highlight .ss { - color: #990073; } - .highlight .bp { - color: #999999; } - .highlight .vc { - color: #008080; } - .highlight .vg { - color: #008080; } - .highlight .vi { - color: #008080; } - .highlight .il { - color: #009999; } diff --git a/docs/docsets/.docset/Contents/Resources/Documents/css/jazzy.css b/docs/docsets/.docset/Contents/Resources/Documents/css/jazzy.css deleted file mode 100644 index d6d65b7..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/css/jazzy.css +++ /dev/null @@ -1,332 +0,0 @@ -html, body, div, span, h1, h3, h4, p, a, code, em, img, ul, li, table, tbody, tr, td { - background: transparent; - border: 0; - margin: 0; - outline: 0; - padding: 0; - vertical-align: baseline; } - -body { - background-color: #f2f2f2; - font-family: Helvetica, freesans, Arial, sans-serif; - font-size: 14px; - -webkit-font-smoothing: subpixel-antialiased; - word-wrap: break-word; } - -h1, h2, h3 { - margin-top: 0.8em; - margin-bottom: 0.3em; - font-weight: 100; - color: black; } - -h1 { - font-size: 2.5em; } - -h2 { - font-size: 2em; - border-bottom: 1px solid #e2e2e2; } - -h4 { - font-size: 13px; - line-height: 1.5; - margin-top: 21px; } - -h5 { - font-size: 1.1em; } - -h6 { - font-size: 1.1em; - color: #777; } - -.section-name { - color: gray; - display: block; - font-family: Helvetica; - font-size: 22px; - font-weight: 100; - margin-bottom: 15px; } - -pre, code { - font: 0.95em Menlo, monospace; - color: #777; - word-wrap: normal; } - -p code, li code { - background-color: #eee; - padding: 2px 4px; - border-radius: 4px; } - -a { - color: #0088cc; - text-decoration: none; } - -ul { - padding-left: 15px; } - -li { - line-height: 1.8em; } - -img { - max-width: 100%; } - -blockquote { - margin-left: 0; - padding: 0 10px; - border-left: 4px solid #ccc; } - -.content-wrapper { - margin: 0 auto; - width: 980px; } - -header { - font-size: 0.85em; - line-height: 26px; - background-color: #414141; - position: fixed; - width: 100%; - z-index: 1; } - header img { - padding-right: 6px; - vertical-align: -4px; - height: 16px; } - header a { - color: #fff; } - header p { - float: left; - color: #999; } - header .header-right { - float: right; - margin-left: 16px; } - -#breadcrumbs { - background-color: #f2f2f2; - height: 27px; - padding-top: 17px; - position: fixed; - width: 100%; - z-index: 1; - margin-top: 26px; } - #breadcrumbs #carat { - height: 10px; - margin: 0 5px; } - -.sidebar { - background-color: #f9f9f9; - border: 1px solid #e2e2e2; - overflow-y: auto; - overflow-x: hidden; - position: fixed; - top: 70px; - bottom: 0; - width: 230px; - word-wrap: normal; } - -.nav-groups { - list-style-type: none; - background: #fff; - padding-left: 0; } - -.nav-group-name { - border-bottom: 1px solid #e2e2e2; - font-size: 1.1em; - font-weight: 100; - padding: 15px 0 15px 20px; } - .nav-group-name > a { - color: #333; } - -.nav-group-tasks { - margin-top: 5px; } - -.nav-group-task { - font-size: 0.9em; - list-style-type: none; - white-space: nowrap; } - .nav-group-task a { - color: #888; } - -.main-content { - background-color: #fff; - border: 1px solid #e2e2e2; - margin-left: 246px; - position: absolute; - overflow: hidden; - padding-bottom: 60px; - top: 70px; - width: 734px; } - .main-content p, .main-content a, .main-content code, .main-content em, .main-content ul, .main-content table, .main-content blockquote { - margin-bottom: 1em; } - .main-content p { - line-height: 1.8em; } - .main-content section .section:first-child { - margin-top: 0; - padding-top: 0; } - .main-content section .task-group-section .task-group:first-of-type { - padding-top: 10px; } - .main-content section .task-group-section .task-group:first-of-type .section-name { - padding-top: 15px; } - -.section { - padding: 0 25px; } - -.highlight { - background-color: #eee; - padding: 10px 12px; - border: 1px solid #e2e2e2; - border-radius: 4px; - overflow-x: auto; } - -.declaration .highlight { - overflow-x: initial; - padding: 0 40px 40px 0; - margin-bottom: -25px; - background-color: transparent; - border: none; } - -.section-name { - margin: 0; - margin-left: 18px; } - -.task-group-section { - padding-left: 6px; - border-top: 1px solid #e2e2e2; } - -.task-group { - padding-top: 0px; } - -.task-name-container a[name]:before { - content: ""; - display: block; - padding-top: 70px; - margin: -70px 0 0; } - -.item { - padding-top: 8px; - width: 100%; - list-style-type: none; } - .item a[name]:before { - content: ""; - display: block; - padding-top: 70px; - margin: -70px 0 0; } - .item code { - background-color: transparent; - padding: 0; } - .item .token { - padding-left: 3px; - margin-left: 15px; - font-size: 11.9px; } - .item .declaration-note { - font-size: .85em; - color: gray; - font-style: italic; } - -.pointer-container { - border-bottom: 1px solid #e2e2e2; - left: -23px; - padding-bottom: 13px; - position: relative; - width: 110%; } - -.pointer { - background: #f9f9f9; - border-left: 1px solid #e2e2e2; - border-top: 1px solid #e2e2e2; - height: 12px; - left: 21px; - top: -7px; - -webkit-transform: rotate(45deg); - -moz-transform: rotate(45deg); - -o-transform: rotate(45deg); - transform: rotate(45deg); - position: absolute; - width: 12px; } - -.height-container { - display: none; - left: -25px; - padding: 0 25px; - position: relative; - width: 100%; - overflow: hidden; } - .height-container .section { - background: #f9f9f9; - border-bottom: 1px solid #e2e2e2; - left: -25px; - position: relative; - width: 100%; - padding-top: 10px; - padding-bottom: 5px; } - -.aside, .language { - padding: 6px 12px; - margin: 12px 0; - border-left: 5px solid #dddddd; - overflow-y: hidden; } - .aside .aside-title, .language .aside-title { - font-size: 9px; - letter-spacing: 2px; - text-transform: uppercase; - padding-bottom: 0; - margin: 0; - color: #aaa; - -webkit-user-select: none; } - .aside p:last-child, .language p:last-child { - margin-bottom: 0; } - -.language { - border-left: 5px solid #cde9f4; } - .language .aside-title { - color: #4b8afb; } - -.aside-warning { - border-left: 5px solid #ff6666; } - .aside-warning .aside-title { - color: #ff0000; } - -.graybox { - border-collapse: collapse; - width: 100%; } - .graybox p { - margin: 0; - word-break: break-word; - min-width: 50px; } - .graybox td { - border: 1px solid #e2e2e2; - padding: 5px 25px 5px 10px; - vertical-align: middle; } - .graybox tr td:first-of-type { - text-align: right; - padding: 7px; - vertical-align: top; - word-break: normal; - width: 40px; } - -.slightly-smaller { - font-size: 0.9em; } - -#footer { - position: absolute; - bottom: 10px; - margin-left: 25px; } - #footer p { - margin: 0; - color: #aaa; - font-size: 0.8em; } - -html.dash header, html.dash #breadcrumbs, html.dash .sidebar { - display: none; } -html.dash .main-content { - width: 980px; - margin-left: 0; - border: none; - width: 100%; - top: 0; - padding-bottom: 0; } -html.dash .height-container { - display: block; } -html.dash .item .token { - margin-left: 0; } -html.dash .content-wrapper { - width: auto; } -html.dash #footer { - position: static; } diff --git a/docs/docsets/.docset/Contents/Resources/Documents/img/carat.png b/docs/docsets/.docset/Contents/Resources/Documents/img/carat.png deleted file mode 100755 index 29d2f7f..0000000 Binary files a/docs/docsets/.docset/Contents/Resources/Documents/img/carat.png and /dev/null differ diff --git a/docs/docsets/.docset/Contents/Resources/Documents/img/dash.png b/docs/docsets/.docset/Contents/Resources/Documents/img/dash.png deleted file mode 100755 index 6f694c7..0000000 Binary files a/docs/docsets/.docset/Contents/Resources/Documents/img/dash.png and /dev/null differ diff --git a/docs/docsets/.docset/Contents/Resources/Documents/img/gh.png b/docs/docsets/.docset/Contents/Resources/Documents/img/gh.png deleted file mode 100755 index 628da97..0000000 Binary files a/docs/docsets/.docset/Contents/Resources/Documents/img/gh.png and /dev/null differ diff --git a/docs/docsets/.docset/Contents/Resources/Documents/index.html b/docs/docsets/.docset/Contents/Resources/Documents/index.html deleted file mode 100644 index 9a2d529..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/index.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - Reference - - - - - - - - - -
-
-

Docs (100% documented)

-
-
-
- -
-
- -
-
-
- -

Authors

- -
-
- -
-
- - - diff --git a/docs/docsets/.docset/Contents/Resources/Documents/js/jazzy.js b/docs/docsets/.docset/Contents/Resources/Documents/js/jazzy.js deleted file mode 100755 index 4ff9455..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/js/jazzy.js +++ /dev/null @@ -1,40 +0,0 @@ -window.jazzy = {'docset': false} -if (typeof window.dash != 'undefined') { - document.documentElement.className += ' dash' - window.jazzy.docset = true -} -if (navigator.userAgent.match(/xcode/i)) { - document.documentElement.className += ' xcode' - window.jazzy.docset = true -} - -// On doc load, toggle the URL hash discussion if present -$(document).ready(function() { - if (!window.jazzy.docset) { - var linkToHash = $('a[href="' + window.location.hash +'"]'); - linkToHash.trigger("click"); - } -}); - -// On token click, toggle its discussion and animate token.marginLeft -$(".token").click(function(event) { - if (window.jazzy.docset) { - return; - } - var link = $(this); - var animationDuration = 300; - var tokenOffset = "15px"; - var original = link.css('marginLeft') == tokenOffset; - link.animate({'margin-left':original ? "0px" : tokenOffset}, animationDuration); - $content = link.parent().parent().next(); - $content.slideToggle(animationDuration); - - // Keeps the document from jumping to the hash. - var href = $(this).attr('href'); - if (history.pushState) { - history.pushState({}, '', href); - } else { - location.hash = href; - } - event.preventDefault(); -}); diff --git a/docs/docsets/.docset/Contents/Resources/Documents/js/jquery.min.js b/docs/docsets/.docset/Contents/Resources/Documents/js/jquery.min.js deleted file mode 100755 index ab28a24..0000000 --- a/docs/docsets/.docset/Contents/Resources/Documents/js/jquery.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery v1.11.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.1",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b=a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="
",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h; -if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML="
a",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/\s*$/g,rb={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:k.htmlSerialize?[0,"",""]:[1,"X
","
"]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?""!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m("