143 lines
10 KiB
Markdown
143 lines
10 KiB
Markdown
# Basic Usage
|
|
|
|
## Overview
|
|
|
|
What is a document? For the purposes of this library, documents can be objects or associative arrays. Most of the functions are geared toward classes, but arrays can be handy for patching documents.
|
|
|
|
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
|
|
- **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 a JSON field comparison to select documents (note that PostgreSQL will always use a text comparison, while SQLite will do its usual [best-guess on types][]); 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` also has `firstBy*` implementations for all supported criteria types.
|
|
|
|
## 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. If automatic IDs are enabled, the document will receive one.
|
|
|
|
```php
|
|
$room = new Room(/* ... */);
|
|
// Parameters are table name and document
|
|
Document::insert('room', $room);
|
|
```
|
|
|
|
The second is `Document::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`. Note that this does _not_ consider automatic IDs; using this will allow you to bypass that generation for documents you know are new.
|
|
|
|
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.
|
|
|
|
```php
|
|
$hotel = Find::byId('hotel', $hotelId, Hotel::class);
|
|
if ($hotel->isDefined()) {
|
|
// update hotel properties from the posted form
|
|
Document::update('hotel', $hotel->id, $hotel);
|
|
}
|
|
```
|
|
|
|
For the next example, suppose we are upgrading our hotel, and need to take rooms 221-240 out of service*. For PostgreSQL, we can utilize a patch via JSON Path.
|
|
|
|
```php
|
|
// PostgreSQL only
|
|
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!_
|
|
|
|
For SQLite, we can utilize a `Field` query with a between operator. (This will also work in PostgreSQL.)
|
|
|
|
```php
|
|
// SQLite
|
|
Patch::byFields('room', [Field::between('roomNumber', 221, 240)], ['inService' => false]);
|
|
```
|
|
|
|
## Finding Documents
|
|
|
|
Functions to find documents start with `Find::`. There are variants to find all documents in a table, find by ID, find by JSON field comparison, 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` functions take a class name, and will attempt to map the document returned to the class specified. Queries which can return zero-or-one results, like `byId` and the `firstBy*` functions, will return `BitBadger\InspiredByFSharp\Option<class>` representing the possibly-single result.
|
|
|
|
All functions other than `byId` also take an optional list of fields by which the results should be ordered. There is a `Field` function `::named` to support creating a field with no comparison criteria. When specifying this name, one can include direction information like `DESC` and `NULLS FIRST`; additionally, there are two prefixes that will affect the sort order:
|
|
|
|
- `n:` will treat the field as a number. In PostgreSQL, this casts the value to a number; if a value cannot be cast, the database will return an error. For SQLite, this has no effect; it automatically guesses about types.
|
|
- `i:` will use case-insensitive ordering. In most PostgreSQL implementations, this is the default, but only due to the operating system implementation; Mac hosts are case-sensitive by default. SQLite defaults to a case-sensitive ordering. This flag will normalize this to case-insensitive sorting regardless of the host operating system or libraries.
|
|
|
|
A couple of quick examples:
|
|
|
|
```php
|
|
// Sorts "field1" ascending, using default case-sensitivity
|
|
Field::named('field1')
|
|
|
|
// Sorts "field2" descending, treating it as a number
|
|
Field::named('n:field2 DESC');
|
|
|
|
// Sorts "field3" ascending case-insensitively with NULLs last
|
|
Field::named('i:field3 NULLS LAST');
|
|
```
|
|
|
|
### Results and the `DocumentList`
|
|
|
|
`Find::all` and `Find::by*` will return a `DocumentList<class>` instance. This is a lazy iterator over these results, and has several ways to interact with the results, none of which involve loading the entire result set into memory. It is a consumable iteration; once it is read, the results are no longer available.
|
|
|
|
* **`hasItems()`** (v1) / **`hasItems`** (v2) will return `true` if there are items in the list, and will return `false` if there are no items, whether from the initial query returning 0 results or from the generator being consumed.
|
|
|
|
* **`items()`** (v1) / **`items`** (v2) returns a generator that will return each result in turn. Using it in a `foreach` loop will iterate each result; passing it into `iterator_to_array` will load the result into an in-memory array. With the `foreach` loop, only the current result is loaded into memory; it is the default way to process results.
|
|
|
|
* **`map()`** returns a generator that will map the results from the query; as it reads each result, it will transform it, returning that as the value rather than the original document. It takes a `callable` which expects a parameter of the document type in the list and returns something other than `void`.
|
|
|
|
* **`iter()`** takes a `callable` which expects a parameter of the document type in the list, and executes that function against each item as it goes. It can be used for `void` iterations, as well as iterations that may need to capture some outer state and manipulate it as the generator is iterated.
|
|
|
|
All that said, the `foreach` on `items()`/`items` is quite straightforward.
|
|
|
|
```php
|
|
// use ->items() for PHP 8.2/8.3
|
|
foreach ($result->items as $item) {
|
|
// Do something amazing with $item
|
|
}
|
|
```
|
|
|
|
## 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 | | | |
|
|
|
|
`Document::insert`, `Document::save`, and `Document::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"
|