b0wter.CouchDb
8.4.1
dotnet add package b0wter.CouchDb --version 8.4.1
NuGet\Install-Package b0wter.CouchDb -Version 8.4.1
<PackageReference Include="b0wter.CouchDb" Version="8.4.1" />
paket add b0wter.CouchDb --version 8.4.1
#r "nuget: b0wter.CouchDb, 8.4.1"
// Install b0wter.CouchDb as a Cake Addin #addin nuget:?package=b0wter.CouchDb&version=8.4.1 // Install b0wter.CouchDb as a Cake Tool #tool nuget:?package=b0wter.CouchDb&version=8.4.1
Current Status
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.
Contributing
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!
Features
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.
Databases endpoint
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.
Server endpoint
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 | ❌ |
Documents endpoint
Endpoint | HEAD | GET | POST | PUT | DELETE | COPY |
---|---|---|---|---|---|---|
/db/doc | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | |
/db/doc/attachment | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
Design Documents endpoint
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.
Partition endpoint
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 | ❌ |
How-to
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`
Connection Details
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.
Query Examples
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.
More examples
Please take a look at the integration tests for more examples. Each query is run at least once.
Id and revision properties
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!
Custom json settings
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.
Tests
Integration tests
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
Running a local CouchDb server
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.
Using docker-compose
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".
Using docker run
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
Running the tests
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
.
Use dotnet cli
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
.
Use docker
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.
Why not use a new container instance for each test?
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.
Can't the tests start the container?
I am currently looking into using Docker.Net to automatically create and run the docker container. However, this work is not finished yet.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- FifteenBelow.Json.Core (>= 1.0.0)
- Newtonsoft.Json (>= 12.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
8.4.1 | 514 | 9/23/2022 |
8.1.4 | 700 | 1/23/2021 |
8.1.2 | 356 | 1/19/2021 |
8.0.1 | 354 | 1/18/2021 |
8.0.0 | 497 | 12/28/2020 |
7.2.0 | 512 | 11/24/2020 |
7.1.0 | 425 | 10/16/2020 |
7.0.1 | 431 | 9/29/2020 |
6.0.1 | 479 | 9/23/2020 |
5.0.0 | 563 | 5/22/2020 |
2.3.2 | 474 | 5/11/2020 |
2.2.3 | 618 | 12/29/2019 |
2.2.2 | 517 | 12/29/2019 |
2.2.1 | 609 | 12/29/2019 |
2.1.0 | 531 | 11/26/2019 |
2.0.1 | 503 | 11/25/2019 |
2.0.0 | 550 | 11/23/2019 |
1.1.1 | 528 | 11/23/2019 |
1.1.0 | 559 | 11/3/2019 |
1.0.0 | 534 | 11/3/2019 |
0.6.1 | 544 | 10/18/2019 |
0.6.0 | 531 | 10/14/2019 |
0.3.2 | 510 | 10/11/2019 |
0.3.0 | 538 | 10/11/2019 |
0.2.1 | 493 | 10/8/2019 |
0.1.5 | 508 | 10/5/2019 |
0.1.4 | 520 | 10/4/2019 |
0.1.0 | 519 | 9/26/2019 |
Fixed package contents