This project is currently in development. Please consult the tables below to see which features are supported. The endpoints refer to the endpoints listed in the official CouchDb documentation. There are integration tests for all implemented endpoints.
You can get the library from nuget.
I am currently developing this library with CouchDb version 3.2.2 as its target.
Contributions (bug fixes, features, ...) are very welcome! Please submit a pull request. I will merge the PR if it satisfies the following requirements:
- The integration tests succeed.
- You have added tests (if it's a new feature) or
- You have fixed tests (if it's a bugfix)
- The new methods/members have XML documentation tags
- I am convinced it will not break anything :)
If you are in doubt please submit an issue!
Note that (optional) query parameter are currently not supported! Even for endpoints that are marked as working. Query parameters that are required (like the rev
parameter for /db/docid/
[PUT]
) are supported.
Authentication | Status |
---|---|
Cookie | ✔️ |
Basic | ❌ |
Proxy | ❌ |
There are currently no plans to support the other two authentication methods. If you feel like you need them please open an issue or (better) open a pull request.
Endpoint | HEAD | GET | POST | PUT | DELETE |
---|---|---|---|---|---|
/db | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
/db/_all_docs | ✔️ | ✔️ | |||
/db/_design_docs | ✔️ | ✔️ | |||
/db/_bulk_get (*) | 🚫 | ||||
/db/_bulk_docs | ✔️ | ||||
/db/_find | ✔️ | ||||
/db/_index (**) | ✔️ | ✔️ | |||
/db/_explain | ❌ | ||||
/db/_shards | ❌ | ||||
/db/_shards/doc | ❌ | ||||
/db/_sync_shards | ❌ | ||||
/db/_changes | ❌ | ||||
/db/_compact | ❌ | ||||
/db/_compact/design-doc | ❌ | ||||
/db/_ensure_full_commit | ❌ | ||||
/db/_view_cleanup | ❌ | ||||
/db/_security | ❌ | ❌ | |||
/db/_purge | ❌ | ||||
/db/_purged_infos_limit | ❌ | ❌ | |||
/db/_missing_revs | ❌ | ||||
/db/_revs_diff | ❌ | ||||
/db/_revs_limit | ❌ | ❌ |
(*) /db/_bulk_get
is not implemented because POST /{db}/_all_docs
is implemented and serves the same purpose.
(**) GET /db/_index
currently only returns a Newtonsoft.Json.Linq.JObject
for the partial_filter_selector
property in the response.
Endpoint | HEAD | GET | POST | PUT | DELETE |
---|---|---|---|---|---|
/ | ✔️ | ||||
/_active_tasks | ✔️ | ||||
/_all_dbs | ✔️ | ||||
/_dbs_info | ✔️ | ||||
/_cluster_setup | ❌ | ❌ | |||
/_db_updates | ❌ | ||||
/_membership | ❌ | ||||
/_replicate | ❌ | ||||
/_scheduler/jobs | ❌ | ||||
/_scheduler/docs | ❌ | ||||
/_node/{node-name}/_stats | ❌ | ||||
/_node/{node-name}/_system | ❌ | ||||
/_node/{node-name}/_restart | ❌ | ||||
/_utils | ❌ | ||||
/_up | ❌ | ||||
/_uuids | ❌ | ||||
/favicon.ico | ❌ |
Endpoint | HEAD | GET | POST | PUT | DELETE | COPY |
---|---|---|---|---|---|---|
/db/doc | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | |
/db/doc/attachment | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
Endpoint | HEAD | GET | POST | PUT | DELETE | COPY |
---|---|---|---|---|---|---|
/db/_design/design-doc | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | |
/db/_design/design-doc/attachment | ❌ | ❌ | ❌ | ❌ | ||
/db/_design/design-doc/_info | ❌ | |||||
/db/_design/design-doc/_view/view-name | ✔️ | ✔️ | ||||
/db/_design/design-doc/_search/index-name | ❌ | |||||
/db/_design/design-doc/_search_info/index-name | ❌ | |||||
/db/_design/design-doc/_update/update-name | ❌ | |||||
/db/_design/design-doc/_update/update-name/doc-id | ❌ |
The show
, list
and rewrite
functions for design documents will not be implemented since the feature will be removed with CouchDb 4.0.
Endpoint | HEAD | GET | POST | PUT | DELETE | COPY |
---|---|---|---|---|---|---|
/db/_partition/partition | ✔️ | |||||
/db/_partition/partition/_all_docs | ✔️ | |||||
/db/_partition/partition/_design/design-doc/_view/view-name | ✔️ | |||||
/db/_partition/partition_id/_find | ✔️ | |||||
/db/_partition/partition_id/_explain | ❌ |
If you're stuck using this library open a new issue and add the label 'howto'!
Usage of this library is simple. Start by opening the library.
open b0wter.CouchDb.Lib
There is a submodule for the database, server and documents endpoints. Each type of query has its own submodule containing:
* a `query` method (sometimes there are multiple methods)
* a `Response` that contains the results of a successful response from the CouchDb Server
* a `Result` type that contains all error cases (matching the ones listed in the official documentation) and a `Success` type that contains the `Response`
* a 'asResult' wraps the query-specific `Result` as a `FSharp.Core.Result<Response, ErrorRequestResult.T>`. This allows you to easily bind it
* a 'queryAsResult' runs `query` followed by `asResult`
Each query takes a DbProperties.T
argument that contains the information necessary to connect to the server. Use b0wter.CouchDb.Lib.DbProperties.create
to create an instance.
Note that adding credentials does not automatically authenticate you. You will have to run a b0wter.CouchDb.Lib.Server.Authenticate.query
request to do so. Since all HTTP requests share the same cookie container you only need to authenticate once for all subsequent requests.
If your CouchDb server requires authentication please take a look at Connection Details.
Every query uses the async
computational expression. Here is a quick example of how a check for the existance of a database works:
open b0wter.CouchDb.Lib
let doesTestDbExist () =
async {
let! exists = Database.Exists.query p "test-db"
match exists with
| Database.Exists.Result.Exists -> printf "The database exists."
| Database.Exists.Result.DoesNotExist -> printf "The database does not exist."
| Database.Exists.Result.RequestError e -> printf "Encountered a request error: %s" e.reason
}
Note: If you only care about success or failure you can use queryAsResult
instead of query
. That will return a Result<true, ErrorRequestResult.T>>
. An ErrorRequestResult.T
contains a status code, an error message and the response headers.
All other requests work in the same way. Here is another example using the _find
-endpoint:
open b0wter.CouchDb.Lib.Mango
let findWithMultipleSelectors () =
async {
let nameFindSelector = condition "name" <| Equal (Text "myName")
let typefindSelector = condition "type" <| Equal (Text "myType")
let intSelector = condition "age" <| LessOrEqual (Integer 100)
let multiSelector = And [ nameFindSelector; typefindSelector; intSelector ]
let expression = createExpression multiSelector
let! result = Database.Find.query<MyDocumentType> p "test-db" expression
do printfn "%A" result
}
If you need more control over the search expression you can create the b0wter.CouchDb.Lib.Find.Expression
yourself instead of using the premade <Mango.createExpression
. There are a couple of helper functions that allow you to handle special cases like two conditions:
open b0wter.CouchDb.Lib.Mango
let findWithMultipleSelectors () =
async {
let nameFindSelector = condition "name" <| Equal (Text "myName")
let typefindSelector = condition "type" <| Equal (Text "myType")
let multiSelector = nameFindSelector |> ``and`` typefindSelector
let expression = createExpression multiSelector
let! result = Database.Find.query<MyDocumentType> p "test-db" expression
do printfn "%A" result
}
Other examples are: ''or''
, nor
and all
.
In case you only require a single selector the previous example boils down to this:
open b0wter.CouchDb.Lib.Mango
let findWithSingleSelectors () =
async {
let nameFindSelector = condition "name" <| Equal (Text "myName")
let findParams = createExpression nameFindSelector
let! result = Database.Find.query<MyDocumentType> p "test-db" findParams
do printfn "%A" result
}
The last parameter of a TypedSelector
is a translator function that translates the argument (second parameter) to a string. In the above examples it is easy since it's already a string. So we can use the identity function.
Please take a look at the integration tests for more examples. Each query is run at least once.
In order to do any meaningful PUT/POST operations your records need to define an _id
and a _rev
property. You can name these fields whatever you like. However, you have to add the [<JsonProperty("_id")]
and [<JsonProperty("_rev")]
attribute.
type MyRecord = {
[<JsonProperty("_id")>]
myId: System.Guid
[<JsonProperty("_rev")>]
myRevision: string
myA: int
myB: float }
}
In case you miss these attributes CouchDb will assign these values on its own. But since their names are unknown to the deserializer these values will never be used. Thus, updating a document will result in the creation of a new document!
Why use this approach? I could define interfaces or make the requirement that all objects need to be inherited from an abstract base class but I want to keep things as simple as possible.
Hint: In most cases it's pretty handy to add a "constant property" to all records that specifies the type. This helps you to restrict queries to certain types of entities. Since records are compiled to classes the following adds a readonly property to MyRecord
that does not need to be specified when creating a new instance:
type MyRecord =
{
[<JsonProperty("_id")>]
myId: System.Guid
[<JsonProperty("_rev")>]
myRevision: string
myA: int
myB: float }
}
member this.``type`` = "MyRecord"
Correct indentation is absolutely necessary to make this compile!
This library makes use of NewtonSoft.Json. In order to better serialize some F# specific objects, like union cases and options we use a port of Fifteenbelow to dotnet core. The default settings are:
ContractResolver = Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(),
Converters = [ FifteenBelow.Json.OptionConverter () ]
Formatting = Newtonsoft.Json.Formatting.Indented,
NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)
This may be outdated in the future, check this file. Since the settings are stored in a NewtonSoft.Json.JsonSettings
instance its properties are mutable. Use this to change the settings as you like. E.g.:
Json.settings.Formatting <- Newtonsoft.Json.Formatting.None
You have the option to set a string -> string
as postprocessing for the serialized object before it is sent to the db server. Simple replace Json.postProcessing
with a function of your choice.
The repository comes with a project for integration tests. These tests require you to have a couchdb server running. You need to set the connection details either in tests/integration/appsettings.json
or pass them as environment variables, e.g.:
COUCHDB_HOST=localhost
COUCHDB_PORT=5984
COUCHDB_USER=admin
COUCHDB_PASSWORD=password
The two recommended ways to start a local CouchDb instance use docker. If you want to install CouchDb on your local system please consult the os specific instructions on the CouchDb page.
There is a docker-compose.yml
in tests/integration
. Simply change into that directory and run docker-compose up
to start a server on the default port (5984), username "admin" and password "password".
The following command is the same as running the docker-compose.yml
. It will automatically destroy the container on exit.
$ docker run --rm -it -p 5984:5984 -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password couchdb:latest
There are two ways to run the tests. If you have the dotnet sdk installed you can use the dotnet test
command, otherwise you'll have to rely on a Dockerfile
.
If you just want to see the results of the test just run the following command in the repository root:
$ dotnet test
If you want to generate an XML result file use:
$ dotnet test --test-adapter-path:. --logger:xunit
instead. The results will be stored in tests/integration/TestResults/TestResults.xml
.
The repository root contains a Dockerfile
that compiles its contents. You will need to first build the image and then run it:
$ docker build -t couchdb-lib .
$ HOSTIP=$(ip -4 addr show docker0 | grep -Po 'inet \K[\d.]+')
$ docker run --rm --name couchdb-tests -it -e COUCHDB_HOST=$HOSTIP couchdb-lib:latest
This will show you the test results in the terminal and delete the container after running the tests. You must supply the ip address of the CouchDb server even if it's running on localhost since localhost inside the test container is not the same as localhost on your machine! The given command expects the CouchDb to run as a docker container with exposed port (5984).
Test results are automatically generated. To retrieve them you can either mount a local folder to /output
in the container or remove the --rm
flag and run
$ docker cp couchdb_tests:/output/integration.xml <YOUR_LOCAL_FOLDER>
$ docker rm couchdb_tests
after the tests have finished.
The latest version of the CouchDb Docker image takes several minutes to start. This prohibits any useful use of fresh containers for every tests. Once this issue has been fixed I will look into changing the behaviour.
I am currently looking into using Docker.Net to automatically create and run the docker container. However, this work is not finished yet.