Compare commits
No commits in common. "main" and "v1.0.0-rc1" have entirely different histories.
main
...
v1.0.0-rc1
4
.gitignore
vendored
4
.gitignore
vendored
@ -30,5 +30,5 @@ replay_pid*
|
||||
# Temporary output directories
|
||||
**/target
|
||||
|
||||
# Doc output directory
|
||||
_site
|
||||
# Maven Central Repo settings
|
||||
settings.xml
|
||||
|
26
.idea/libraries/Maven__scala_sdk_3_5_2.xml
generated
Normal file
26
.idea/libraries/Maven__scala_sdk_3_5_2.xml
generated
Normal file
@ -0,0 +1,26 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Maven: scala-sdk-3.5.2" type="Scala">
|
||||
<properties>
|
||||
<language-level>Scala_3_5</language-level>
|
||||
<compiler-classpath>
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-compiler_3/3.5.2/scala3-compiler_3-3.5.2.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-interfaces/3.5.2/scala3-interfaces-3.5.2.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-library_3/3.5.2/scala3-library_3-3.5.2.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/scala-library/2.13.14/scala-library-2.13.14.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/tasty-core_3/3.5.2/tasty-core_3-3.5.2.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-lang/modules/scala-asm/9.7.0-scala-2/scala-asm-9.7.0-scala-2.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-sbt/compiler-interface/1.9.6/compiler-interface-1.9.6.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/scala-sbt/util-interface/1.9.8/util-interface-1.9.8.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/jline/jline-reader/3.25.1/jline-reader-3.25.1.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/jline/jline-terminal/3.25.1/jline-terminal-3.25.1.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/jline/jline-native/3.25.1/jline-native-3.25.1.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/org/jline/jline-terminal-jna/3.25.1/jline-terminal-jna-3.25.1.jar" />
|
||||
<root url="file://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.14.0/jna-5.14.0.jar" />
|
||||
</compiler-classpath>
|
||||
<compiler-bridge-binary-jar>file://$MAVEN_REPOSITORY$/org/scala-lang/scala3-sbt-bridge/3.5.2/scala3-sbt-bridge-3.5.2.jar</compiler-bridge-binary-jar>
|
||||
</properties>
|
||||
<CLASSES />
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
Binary file not shown.
Before Width: | Height: | Size: 12 KiB |
@ -1,4 +0,0 @@
|
||||
article h2 {
|
||||
border-bottom: solid 1px gray;
|
||||
margin-bottom: 1rem;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
export default {
|
||||
defaultTheme: "auto",
|
||||
iconLinks: [
|
||||
{
|
||||
icon: "git",
|
||||
href: "https://git.bitbadger.solutions/bit-badger/solutions.bitbadger.documents",
|
||||
title: "Source Repository"
|
||||
}
|
||||
]
|
||||
}
|
41
docfx.json
41
docfx.json
@ -1,41 +0,0 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/dotnet/docfx/main/schemas/docfx.schema.json",
|
||||
"build": {
|
||||
"content": [
|
||||
{
|
||||
"files": [
|
||||
"index.md",
|
||||
"toc.yml",
|
||||
"docs/**/*.{md,yml}"
|
||||
],
|
||||
"exclude": [
|
||||
"_site/**"
|
||||
]
|
||||
}
|
||||
],
|
||||
"resource": [
|
||||
{
|
||||
"files": [
|
||||
"images/**",
|
||||
"bitbadger-doc.png",
|
||||
"favicon.ico"
|
||||
]
|
||||
}
|
||||
],
|
||||
"output": "_site",
|
||||
"template": [
|
||||
"default",
|
||||
"modern",
|
||||
"doc-template"
|
||||
],
|
||||
"globalMetadata": {
|
||||
"_appName": "solutions.bitbadger.documents",
|
||||
"_appTitle": "solutions.bitbadger.documents",
|
||||
"_appLogoPath": "bitbadger-doc.png",
|
||||
"_appFaviconPath": "favicon.ico",
|
||||
"_appFooter": "Hand-crafted documentation created with <a href=https://dotnet.github.io/docfx target=_blank class=external>docfx</a> by <a href=https://bitbadger.solutions target=_blank class=external>Bit Badger Solutions</a>",
|
||||
"_enableSearch": true,
|
||||
"pdf": false
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
# Advanced Usage
|
||||
|
||||
This documentation is under active development. As of April 19th, 2025, this page is next.
|
||||
|
||||
## Examples
|
||||
|
||||
Each library has an exhaustive suite of integration tests; reviewing those may also provide insight into the patterns used for effective document storage and retrieval.
|
@ -1,176 +0,0 @@
|
||||
# Basic Usage
|
||||
|
||||
## 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** (`Document.insert`) adds a new document, failing if the ID field is not unique
|
||||
- **Save** (`Document.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 documents matching some criteria as a JSON string, or writes that text directly to a `PrintWriter`
|
||||
- **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][]); 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, and `Find.*Ordered` implementations to sort the results in the database.
|
||||
|
||||
## Saving Documents
|
||||
|
||||
> [!NOTE]
|
||||
> All code samples are in Java unless otherwise noted. Also, they use the connection-creating functions for clarity; see below for notes on how these names translate to `Connection` extensions.
|
||||
|
||||
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.
|
||||
|
||||
```java
|
||||
Room room = new Room(/* ... */);
|
||||
// Parameters are table name and document
|
||||
Document.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.
|
||||
|
||||
```java
|
||||
Hotel hotel = Find.byId("hotel", hotelId, Hotel.class);
|
||||
if (hotel != null) {
|
||||
// update hotel properties from the posted form
|
||||
Update.byId("hotel", hotel.getId(), hotel);
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```java
|
||||
Patch.byJsonPath("room",
|
||||
"$ ? (@.hotelId == \"abc\" && (@.roomNumber >= 221 && @.roomNumber <= 240)",
|
||||
Map.of("inService", false));
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> We are ignoring the current reservations, end date, etc. This is very naïve example!
|
||||
|
||||
JSON Path queries are only available for PostgreSQL. Both PostgreSQL and SQLite could accomplish this using the `Between` comparison and a `byFields` query:
|
||||
|
||||
```java
|
||||
// Parameters are table name, fields to match, and patch to apply;
|
||||
// there are others, but they are optional
|
||||
Patch.byFields("room", List.of(Field.between("roomNumber", 221, 240)),
|
||||
Map.of("inService", false));
|
||||
```
|
||||
|
||||
Scala programs can use its `::` operator to provide an immutable list:
|
||||
|
||||
```scala
|
||||
// Scala
|
||||
Patch.byFields("room", Field.between("roomNumber", 221, 240) :: Nil,
|
||||
Map.Map1("inService", false))
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> These examples use `Map`s as the patch documents, as that is a convenient way to update one or more fields in a document. As patches are serialized to JSON, this can be used to update a sub-document (a domain object which is a property in a larger document/object).
|
||||
|
||||
This could also be done using multiple fields, such as `Field.greaterOrEqual` and `Field.lessOrEqual`, providing `FieldMatch.All` after the patch object; there are many different ways to do things!
|
||||
|
||||
There are more complex ways to update data, including custom queries; that will be detailed in the [Advanced Usage][] section.
|
||||
|
||||
## Retrieving 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.*Ordered` methods append an `ORDER BY` clause to the query that will sort the results in the database. These take, as their last parameter, a collection of `Field` items; a `.named` static 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]). Prefixing the name with `i:` will do a case-insensitive sort. Adding " DESC" at the end will sort high-to-low instead of low-to-high.
|
||||
|
||||
#### Choose Your Adventure
|
||||
|
||||
Document retrieval is the reason there are three distinct implementations of this library (`groovy` shares `core`'s implementation). Java's type-erased generics require a `Class` for documents to be deserialized. Scala's implicit `ClassTag` and Kotlin's reified generics do not require a `Class` instance as a parameter, but will typically require a generic type when they are called. Scala's collection types will convert back and forth to Java's and Kotlin's, but littering `.asJava` at the end of collections is a terrible developer experience.
|
||||
|
||||
Rather than try to take one pass through the `Find` methods, calling out all the different ways they are implemented, we'll do a pass for each implementation.
|
||||
|
||||
#### Core _(Java, Groovy, reflection-based Kotlin)_
|
||||
|
||||
Methods that attempt to return a single document will return an instance of Java's `Optional<T>` class. Methods that return a list of documents return a Kotlin `List<T>` (immutable, but otherwise behaves as one would expect a Java list to behave). Parameters which can have multiple values expect a Kotlin `Sequence<T>`, which most Java collection types satisfy (`ArrayList<T>`, `HashSet<T>`, etc.).
|
||||
|
||||
Each method will have, as one of its later parameters, a `Class` instance. This is used by the deserializer to select the class which gets created. For example, `Find.byId`'s parameters are table name, document ID, and `Class` to return.
|
||||
|
||||
#### Scala
|
||||
|
||||
Methods that attempt to return a single document will return an instance of Scala's `Option[A]` type. Methods that return a list of documents return Scala's immutable `List[Doc]`. Parameters which can have multiple values expect a Scala `Seq[A]`.
|
||||
|
||||
Each `Find.` method requires a generic type parameter to indicate what type of object should be returned. To select a hotel by its ID, for example:
|
||||
|
||||
```scala
|
||||
// Scala
|
||||
val hotel = Find.byId[Hotel](tableName, hotelId)
|
||||
```
|
||||
|
||||
#### KotlinX
|
||||
|
||||
Methods that attempt to return a single document will return a nullable type. Others are the same as the `core` module.
|
||||
|
||||
Each `Find.` method requires a generic type parameter, and requires `inline` on the method definition to support the reified type parameter (and compiler-implemented serialization). This choice can cascade throughout the calling application, but the compiler will be happy to tell you if the enclosing context needs to be marked as `inline`.
|
||||
|
||||
### As JSON
|
||||
|
||||
All `Find` methods and functions have two corresponding `Json` methods. While the input types for these methods also vary based on the implementation, these all return either `String`s or `void`.
|
||||
|
||||
* 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 `PrintWriter` immediately after the table name parameter. These functions write results to the given writer as they are retrieved from the database, instead of accumulating them all and returning a `String`. This can be useful for JSON API scenarios, as it can be written directly to a servlet output stream.
|
||||
|
||||
> [!NOTE]
|
||||
> This library does no flushing of the underlying stream. Applications should handle that after calling `Json.write*` or configure the `PrintWriter` to auto-flush.
|
||||
|
||||
## 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` / `Json` | 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 `Update.*` operate on single documents.
|
||||
|
||||
### Extension Methods
|
||||
|
||||
Extension methods are defined in the `.extensions` package for each library. They can be selectively imported, or all can be imported at once. (Groovy extension methods should be visible just from the module being installed.)
|
||||
|
||||
Here is how the names are translated to those extensions methods:
|
||||
|
||||
- Most have the name of the class plus the name of the method - e.g., `Count.all` becomes `countAll`, `Find.byFieldsOrdered` becomes `findByFieldsOrdered`, etc.
|
||||
- `Document` and `Definition` methods are defined using their existing name - e.g., `Document.insert` is simply `insert`, `Definition.ensureTable` is `ensureTable`, etc.
|
||||
- `Json.write*` functions begin with `writeJson` - e.g., `Json.writeByFields` becomes `writeJsonByFields`, etc.
|
||||
|
||||
As extensions, these will be visible on the instance of the `Connection` object in the application.
|
||||
|
||||
|
||||
[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]: ./advanced/index.md "Advanced Usage • solutions.bitBadger.documents"
|
@ -1,125 +0,0 @@
|
||||
# Getting Started
|
||||
|
||||
## Determining the Lay of the Land
|
||||
|
||||
While the aim is to provide a library that makes it easy to get up-and-running, thinking through some decisions up front will pay off as we go.
|
||||
|
||||
- Each module has document access functions which take a JDBC `Connection` as its last parameter, and a version of those functions which do not. For the latter, the library will create a connection for each execution. For all languages except Java, there are also extension methods on the `Connection` object itself. We'll discuss connection management more below.
|
||||
- Document IDs default to the `id` property for the given domain object/document, but can be named whatever you like. The library can also generate three forms of automatic IDs for documents. As with connection management, more discussion and considerations will be presented below.
|
||||
- No assumption is made as to how documents are serialized and deserialized. While this allows the library to also have no dependencies on any (possibly conflicting) JSON library, it does require a two-method interface implementation. (The exception here is `kotlinx`, which does have a built-in serializer based on `kotlinx`.)
|
||||
|
||||
Projects will need a dependency on their chosen library. The `core` module is a dependency of the other modules, so Groovy, Scala, and KotlinX projects only need the `groovy`, `scala`, or `kotlinx` dependencies, respectively. Java or reflection-based-serialization Kotlin projects can depend on `core` directly.
|
||||
|
||||
## Connections
|
||||
|
||||
Any database library will need a connection string, and this one is no different. If you are using a library or framework which provides a way to configure connection strings, use that. If not, a great way to configure connections (or sensitive parts of it) is via environment variables. Java's `System.getenv(key)` static method can [read the value of an environment variable][env].
|
||||
|
||||
However the connection string is configured, the library needs to know about it. The `Configuration` class (found in the `solutions.bitbadger.documents` package, part of the `core` module) has a static property `connectionString`; set it to the connection string you have configured.
|
||||
|
||||
### Connection Management
|
||||
|
||||
Once the connection string has been configured, `Configuration.dbConn()` will return new connections to that database. Combining this with extension methods provides several options:
|
||||
|
||||
- **Do nothing.** This will result in a new `Connection` being obtained for each request, which may seem crazy! However, SQLite connections are local actions, and pooling PostgreSQL connections can mitigate the overhead of multiple PostgreSQL connections required to satisfy a particular application action.
|
||||
- **Use a connection from your <abbr title="Dependency Injection">DI</abbr> container.** Combined with the extension methods (or functions with a `Connection` parameter), this can be a great way to introduce documents into an existing application. All queries will be executed on the given connection, and the DI container can manage the lifetime (in the context of web requests, likely per-request).
|
||||
- **Configure this library to provide the DI container's connection.** If you can set up your container to run custom code to return its objects (i.e., factories), `Configuration.dbConn()` can be treated as a connection factory.
|
||||
|
||||
> [!NOTE]
|
||||
> Those are a lot of options (and are missing ad-hoc / hybrid options). On the other hand, this is a low-stress decision for those getting started. For some, one of those options will trigger the "Yeah, that's it!" response; in that case, go with that. In others, pick one and get started. For web applications, the DI-provided connection is a good choice. The library still needs to be configured so it knows what type of database it is targeting, but the connection does not have to be provided by the library; any JDBC connection will do.
|
||||
|
||||
## Document IDs
|
||||
|
||||
### Naming IDs
|
||||
|
||||
As mentioned above, the default configuration is to use the document field `id` as the identifier for each document. For projects who want to use a different name (e.g., `key`), set the `Configuration.idField` property to whatever value will be used.
|
||||
|
||||
Unlike the connection strategy, this is a decision to make up front; once documents exist, this cannot be easily changed.
|
||||
|
||||
### Automatic IDs
|
||||
|
||||
Relational databases provide several ways to create automatic IDs, the most common being ever-increasing numbers or <abbr title="Universally Unique Identifiers">UUIDs</abbr>/<abbr title="Globally Unique Identifiers">GUIDs</abbr>. This library provides a replacement (or approximation) of these options, all defined in the `AutoId` enum.
|
||||
|
||||
- `DISABLED` - no automatic IDs are applied; your IDs are your ~~problem~~ responsibility.
|
||||
- `NUMBER` - a `MAX + 1`-style algorithm is applied if the document has a numeric ID with the value `0`. _(This is applied as a subquery on the `INSERT` statement; it should not be considered nearly as robust as a sequence.)_
|
||||
- `UUID` - a `String` UUID is generated for documents with blank string ID fields.
|
||||
- `RANDOM_STRING` - a string of random hex characters is generated for documents with blank string ID fields; the length of this string is controlled by `Configuration.idStringLength`.
|
||||
|
||||
In all automatic generation cases, if the document being inserted has an ID value already, it is passed through unmodified.
|
||||
|
||||
> [!WARNING]
|
||||
> For `NUMBER` auto IDs, both PostgreSQL and SQLite will have trouble if any document with a string ID is written. Numbers can be treated as strings, but strings cannot be treated as numbers. (SQLite will do its best - if a string has a numeric value, it will work - but PostgreSQL will fail spectacularly in this case.)
|
||||
|
||||
> [!TIP]
|
||||
> `AutoId.generateRandomString(length)` can be used to generate random hex strings of a specified length, not just the one specified in the configuration. Also, `AutoId.generateUUID()` can be used to generate a lowercase UUID with no dashes, regardless of the configured `AutoId` values.
|
||||
>
|
||||
> (Non-Kotlin projects may need to specify `AutoId.Companion` to see these functions.)
|
||||
|
||||
## Document Serialization
|
||||
|
||||
### Traditional (AKA "reflection-based")
|
||||
|
||||
With many applications already defining a JSON <abbr title="Application Programming Interface">API</abbr>, a document data store can utilize whatever JSON serialization strategies these applications already employ. In this case, implementing a `DocumentSerializer` (found in the `solutions.bitbadger.documents` namespace) is trivial; its methods can delegate to the existing serialization and deserialization process.
|
||||
|
||||
For new applications, or applications that do not already have JSON serialization as part of their normal process, the integration tests for the `core`, `groovy`, and `scala` modules have examples of a `DocumentSerializer` implementation using Jackson's default options. The project will need a dependency on `jackson.databind`, but that implementation is trivial (thus why it's duplicated in each module's integration tests).
|
||||
|
||||
Once the serializer is created, set `DocumentConfig.serializer` property to an instance of that serializer. (`DocumentConfig` is in the `solutions.bitbadger.documents.java` package.)
|
||||
### Using `kotlinx.serialization`
|
||||
|
||||
The `kotlinx` module configures the serializer with the following default options:
|
||||
|
||||
- Coerce Input Values = true; this means that `null` values in JSON will be represented by the class's default property value rather than being `null`.
|
||||
- Encode Defaults = true; this means properties with default values will have those values encoded as part of the output JSON.
|
||||
- Explicit Nulls = false; this means that `null` values will not be written to the output JSON. For documents with many optional values, this can make a decent size difference once many documents are stored.
|
||||
|
||||
Any of the [KotlinX Json][kx-json] properties can be set on the `options` property of `DocumentConfig` in the `solutions.bitbadger.documents.kotlinx` package. As with reflection-based serialization, if the project already has a set of `Json` properties, the existing configuration can be replaced with that set.
|
||||
|
||||
## Document Tables
|
||||
|
||||
> [!NOTE]
|
||||
> If you want to customize the document's `id` field, this needs be done before tables are created.
|
||||
|
||||
The final step to being able to store and retrieve documents is to define one or more tables for them. The `Definition` class (in the `.java`, `.scala`, or `.kotlinx` packages) provides an `ensureTable` method that creates both a table and its ID index if that table does not already exist. (`ensureTable` is also a `Connection` extension method.)
|
||||
|
||||
To create a document table named `hotel` in Java...
|
||||
|
||||
```java
|
||||
// Function that creates its own connection
|
||||
Definition.ensureTable("hotel");
|
||||
// ...or, on a connection variable named "conn"
|
||||
conn.ensureTable("hotel");
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Most operations could throw `DocumentException`, which is a checked exception. Java consumers must catch or declare it; for other languages, "must" becomes "should consider".
|
||||
|
||||
The repeatable nature of this call means that your application can run through a set of `ensureTable` calls at startup.
|
||||
|
||||
### Indexing Documents
|
||||
|
||||
Both PostgreSQL and SQLite support indexing individual fields in the document, just as they can index columns in a relational table. `Definition` provides the `ensureFieldIndex` method to establish these indexes. These functions take a table name, an index name, and a collection of field names from the document which should be indexed.
|
||||
|
||||
For example, imagine we had a `user` document, but we allow users to sign in via their e-mail address. In Java, this may look something like...
|
||||
|
||||
```java
|
||||
// Create an index named idx_user_email on the user table
|
||||
Definition.ensureFieldIndex("user", "email", List.of("email"));
|
||||
```
|
||||
|
||||
Multiple-field indexes are also possible.
|
||||
|
||||
For PostgreSQL, they provide a <abbr title="Generalized Inverted Index">GIN</abbr> index which can index the entire document. This index can be a full document index, which can be used for both containment queries or JSON Path matching queries, or one that is optimized for JSON Path operations. This library has a `DocumentIndex` enum with values `Full` and `Optimized` to specify which type of index is required. `ensureDocumentIndex` creates the index.
|
||||
|
||||
```java
|
||||
// This index will be named idx_user_doc and is suitable for any operation
|
||||
Definition.ensureDocumentIndex("user", DocumentIndex.Full);
|
||||
```
|
||||
|
||||
Indexes are not as important an up-front decision as some other aspects; nothing prevents a developer from adding an index when they realize they may need one.
|
||||
|
||||
---
|
||||
|
||||
We have access to a data store, we know how to create documents, and we have a place to store them. Now, it's time to use all that!
|
||||
|
||||
|
||||
[env]: https://docs.oracle.com/javase/tutorial/essential/environment/env.html "Environment Variables • The Java Tutorials"
|
||||
[kx-json]: https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/json.md "JSON Features kotlinx.serialization • GitHub"
|
@ -1,7 +0,0 @@
|
||||
- name: Getting Started
|
||||
href: getting-started.md
|
||||
- name: Basic Usage
|
||||
href: basic-usage.md
|
||||
- name: Advanced Usage
|
||||
href: advanced/index.md
|
||||
items:
|
BIN
favicon.ico
BIN
favicon.ico
Binary file not shown.
Before Width: | Height: | Size: 9.3 KiB |
56
index.md
56
index.md
@ -1,56 +0,0 @@
|
||||
---
|
||||
_layout: landing
|
||||
title: Welcome!
|
||||
---
|
||||
|
||||
Welcome! This project implements the [relational document](/) concepts for Java, Kotlin, Scala, and Groovy applications. It uses four different modules to provide an idiomatic developer experience across four major JVM languages.
|
||||
|
||||
## Depend on Them
|
||||
|
||||
The coordinate structure for these packages is
|
||||
```xml
|
||||
<groupId>solutions.bitbadger.documents</groupId>
|
||||
<artifactId>[module]</artifactId>
|
||||
<version>1.0.0-RC1</version>
|
||||
```
|
||||
|
||||
### core [![Maven Central Version][core-badge]][core-mvn]
|
||||
|
||||
This module does most of the heavy lifting for all languages. It provides an optimal Java experience and an optimal Kotlin experience when using a reflection-based JSON serializer. Queries that can return one-or-none return an `Optional<T>` instance. This module also has Kotlin extensions on the <abbr title="Java Database Connectivity">JDBC</abbr> `Connection` type
|
||||
|
||||
### groovy [![Maven Central Version][groovy-badge]][groovy-mvn]
|
||||
|
||||
This far-out module provides Groovy-style extension methods on the `Connection` type. (Right on!)
|
||||
|
||||
### scala [![Maven Central Version][scala-badge]][scala-mvn]
|
||||
|
||||
This module provides a native Scala API for this library, using its collection types instead of the default Java collections, and returns the Scala `Option[A]` type for one-or-none queries. It also provides Scala extension methods for the `Connection` type.
|
||||
|
||||
### kotlinx [![Maven Central Version][kotlinx-badge]][kotlinx-mvn]
|
||||
|
||||
This module utilizes [`kotlix.serialization`][kotlinx] for its serialization and deserialization, which implements these actions without reflection. As its primary consumer is Kotlin, it returns `null` for one-or-none queries.
|
||||
|
||||
## Read All about Them
|
||||
|
||||
- The "Docs" heading above will take you to the "Getting Started" page for these libraries, and is the entry point for all documentation (currently under active development).
|
||||
|
||||
- The "Core API" heading links to the JavaDoc for the `core` module. Groovy consumers can also review the `Connection` extension functions there.
|
||||
|
||||
- The "Scala API" heading links to the ScalaDoc for the `scala` module.
|
||||
|
||||
- The "KotlinX API" heading links to the JavaDoc for the `kotlinx` module.
|
||||
|
||||
## Talk about Them
|
||||
|
||||
Bit Badger Solutions Git is not set up for account registration _(yet! -- Homer Simpson)_. However, the author can be reached as `@Bit_Badger` on Twitter, `daniel@fedi.summershome.org` on the Fediverse, or via e-mail as `daniel` at the parent domain to this site's domain. This project is open to the public, so you will be able to track the progress of any issues you may report.
|
||||
|
||||
|
||||
[core-badge]: https://img.shields.io/maven-central/v/solutions.bitbadger.documents/core
|
||||
[core-mvn]: https://central.sonatype.com/artifact/solutions.bitbadger.documents/core
|
||||
[groovy-badge]: https://img.shields.io/maven-central/v/solutions.bitbadger.documents/groovy
|
||||
[groovy-mvn]: https://central.sonatype.com/artifact/solutions.bitbadger.documents/groovy
|
||||
[scala-badge]: https://img.shields.io/maven-central/v/solutions.bitbadger.documents/scala
|
||||
[scala-mvn]: https://central.sonatype.com/artifact/solutions.bitbadger.documents/scala
|
||||
[kotlinx-badge]: https://img.shields.io/maven-central/v/solutions.bitbadger.documents/kotlinx
|
||||
[kotlinx-mvn]: https://central.sonatype.com/artifact/solutions.bitbadger.documents/kotlinx
|
||||
[kotlinx]: https://github.com/Kotlin/kotlinx.serialization "KotlinX Serialization • GitHub"
|
Loading…
x
Reference in New Issue
Block a user