# Basic Usage
_Documentation pages for `BitBadger.Npgsql.Documents` redirect here. This library replaced it as of v3; see project home if this applies to you._
## Overview
There are several categories of operations that can be accomplished against documents.
- **Count** returns the number of documents matching some criteria
- **Exists** returns true if any documents match the given criteria
- **Insert** adds a new document, failing if the ID field is not unique
- **Save** adds a new document, updating an existing one if the ID is already present ("upsert")
- **Update** updates an existing document, doing nothing if no documents satisfy the criteria
- **Patch** updates a portion of an existing document, doing nothing if no documents satisfy the criteria
- **Find** returns the documents matching some criteria as domain objects
- **Json** returns or writes documents matching some criteria as JSON text
- **RemoveFields** removes fields from documents matching some criteria
- **Delete** removes documents matching some criteria
`Insert` and `Save` were the only two that don't mention criteria. For the others, "some criteria" can be defined a few different ways:
- **All** references all documents in the table; applies to Count and Find
- **ById** looks for a single document on which to operate; applies to all but Count
- **ByFields** uses JSON field comparisons to select documents for further processing (PostgreSQL will use a numeric comparison if the field value is numeric, or a string comparison otherwise; SQLite will do its usual [best-guess on types][]{target=_blank rel=noopener}); applies to all but Update
- **ByContains** (PostgreSQL only) uses a JSON containment query (the `@>` operator) to find documents where the given sub-document occurs (think of this as an `=` comparison based on one or more properties in the document; looking for hotels with `{ "Country": "USA", "Rating": 4 }` would find all hotels with a rating of 4 in the United States); applies to all but Update
- **ByJsonPath** (PostgreSQL only) uses a JSON patch match query (the `@?` operator) to make specific queries against a document's structure (it also supports more operators than a containment query; to find all hotels rated 4 _or higher_ in the United States, we could query for `"$ ? (@.Country == \"USA\" && @.Rating > 4)"`); applies to all but Update
Finally, `Find` and `Json` also have `FirstBy*` implementations for all supported criteria types, and `Find*Ordered` implementations to sort the results in the database.
## Saving Documents
The library provides three different ways to save data. The first equates to a SQL `INSERT` statement, and adds a single document to the repository.
```csharp
// C#, All
var room = new Room(/* ... */);
// Parameters are table name and document
await Document.Insert("room", room);
```
```fsharp
// F#, All
let room = { Room.empty with (* ... *) }
do! insert "room" room
```
The second is `Save`; and inserts the data it if does not exist and replaces the document if it does exist (what some call an "upsert"). It utilizes the `ON CONFLICT` syntax to ensure an atomic statement. Its parameters are the same as those for `Insert`.
The third equates to a SQL `UPDATE` statement. `Update` applies to a full document and is usually used by ID, while `Patch` is used for partial updates and may be done by field comparison, JSON containment, or JSON Path match. For a few examples, let's begin with a query that may back the "edit hotel" page. This page lets the user update nearly all the details for the hotel, so updating the entire document would be appropriate.
```csharp
// C#, All
var hotel = await Document.Find.ById("hotel", hotelId);
if (!(hotel is null))
{
// update hotel properties from the posted form
await Update.ById("hotel", hotel.Id, hotel);
}
```
```fsharp
// F#, All
match! Find.byId "hotel" hotelId with
| Some hotel ->
do! Update.byId "hotel" hotel.Id updated
{ hotel with (* properties from posted form *) }
| None -> ()
```
For the next example, suppose we are upgrading our hotel, and need to take rooms 221-240 out of service*. We can utilize a patch via JSON Path** to accomplish this.
```csharp
// C#, PostgreSQL
await Patch.ByJsonPath("room",
"$ ? (@.HotelId == \"abc\" && (@.RoomNumber >= 221 && @.RoomNumber <= 240)",
new { InService = false });
```
```fsharp
// F#, PostgreSQL
do! Patch.byJsonPath "room"
"$ ? (@.HotelId == \"abc\" && (@.RoomNumber >= 221 && @.RoomNumber <= 240)"
{| InService = false |};
```
_* - we are ignoring the current reservations, end date, etc. This is very naïve example!_
\** - Both PostgreSQL and SQLite can also accomplish this using the `Between` comparison and a `ByFields` query:
```csharp
// C#, Both
await Patch.ByFields("room", FieldMatch.Any, [Field.Between("RoomNumber", 221, 240)],
new { InService = false });
```
```fsharp
// F#, Both
do! Patch.byFields "room" Any [ Field.Between "RoomNumber" 221 240 ] {| InService = false |}
```
This could also be done with `All`/`FieldMatch.All` and `GreaterOrEqual` and `LessOrEqual` field comparisons, or even a custom query; these are fully explained in the [Advanced Usage][] section.
> There is an `Update.ByFunc` variant that takes an ID extraction function run against the document instead of its ID. This is detailed in the [Advanced Usage][] section.
## Finding Documents as Domain Items
Functions to find documents start with `Find.`. There are variants to find all documents in a table, find by ID, find by JSON field comparisons, find by JSON containment, or find by JSON Path. The hotel update example above utilizes an ID lookup; the descriptions of JSON containment and JSON Path show examples of the criteria used to retrieve using those techniques.
`Find` methods and functions are generic; specifying the return type is crucial. Additionally, `ById` will need the type of the key being passed. In C#, `ById` and the `FirstBy*` methods will return `TDoc?`, with the value if it was found or `null` if it was not; `All` and other `By*` methods return `List` (from `System.Collections.Generic`). In F#, `byId` and the `firstBy*` functions will return `'TDoc option`; `all` and other `by*` functions return `'TDoc list`.
`Find*Ordered` methods and function append an `ORDER BY` clause to the query that will sort the results in the database. These take, as their last parameter, a sequence of `Field` items; a `.Named` method allows for field creation for these names. Within these names, prefixing the name with `n:` will tell PostgreSQL to sort this field numerically rather than alphabetically; it has no effect in SQLite (it does its own [type coercion][best-guess on types]). Adding " DESC" at the end will sort high-to-low instead of low-to-high.
## Finding Documents as JSON
All `Find` methods and functions have two corresponding `Json` functions.
* The first set return the expected document(s) as a `string`, and will always return valid JSON. Single-document queries with nothing found will return `{}`, while zero-to-many queries will return `[]` if no documents match the given criteria.
* The second set are prefixed with `Write`, and take a `StreamWriter` immediately after the table name parameter. These functions write results to the given stream instead of returning them, which can be useful for JSON API scenarios.
## Deleting Documents
Functions to delete documents start with `Delete.`. Document deletion is supported by ID, JSON field comparison, JSON containment, or JSON Path match. The pattern is the same as for finding or partially updating. _(There is no library method provided to delete all documents, though deleting by JSON field comparison where a non-existent field is null would accomplish this.)_
## Counting Documents
Functions to count documents start with `Count.`. Documents may be counted by a table in its entirety, by JSON field comparison, by JSON containment, or by JSON Path match. _(Counting by ID is an existence check!)_
## Document Existence
Functions to check for existence start with `Exists.`. Documents may be checked for existence by ID, JSON field comparison, JSON containment, or JSON Path match.
## What / How Cross-Reference
The table below shows which commands are available for each access method. (X = supported for both, P = PostgreSQL only)
Operation | `All` | `ById` | `ByFields` | `ByContains` | `ByJsonPath` | `FirstByFields` | `FirstByContains` | `FirstByJsonPath`
----------|:-----:|:------:|:---------:|:------------:|:------------:|:--------------:|:-----------------:|:----------------:|
`Count` | X | | X | P | P |
`Exists` | | X | X | P | P |
`Find` | X | X | X | P | P | X | P | P |
`Patch` | | X | X | P | P |
`RemoveFields` | | X | X | P | P |
`Delete` | | X | X | P | P |
`Insert`, `Save`, and `Update.*` operate on single documents.
[best-guess on types]: https://sqlite.org/datatype3.html "Datatypes in SQLite • SQLite"
[JSON Path]: https://www.postgresql.org/docs/15/functions-json.html#FUNCTIONS-SQLJSON-PATH "JSON Functions and Operators • PostgreSQL Documentation"
[Advanced Usage]: /open-source/relational-documents/dotnet/advanced-usage.html "Advanced Usage • BitBadger.Documents • Bit Badger Solutions"