v4.1 (#11)
- Add `Json` module to return JSON strings and write JSON as it's read to a `PipeWriter` - Add `docfx`-based documentation to allow how-to docs and API docs to be generated on the same site Reviewed-on: #11
This commit was merged in pull request #11.
This commit is contained in:
@@ -1,44 +1,45 @@
|
||||
namespace BitBadger.Documents
|
||||
|
||||
open System.Security.Cryptography
|
||||
open System.Text
|
||||
|
||||
/// <summary>The types of comparisons available for JSON fields</summary>
|
||||
/// <exclude />
|
||||
type Comparison =
|
||||
|
||||
/// <summary>Equals (<tt>=</tt>)</summary>
|
||||
|
||||
/// <summary>Equals (<c>=</c>)</summary>
|
||||
| Equal of Value: obj
|
||||
|
||||
/// <summary>Greater Than (<tt>></tt>)</summary>
|
||||
|
||||
/// <summary>Greater Than (<c>></c>)</summary>
|
||||
| Greater of Value: obj
|
||||
|
||||
/// <summary>Greater Than or Equal To (<tt>>=</tt>)</summary>
|
||||
|
||||
/// <summary>Greater Than or Equal To (<c>>=</c>)</summary>
|
||||
| GreaterOrEqual of Value: obj
|
||||
|
||||
/// <summary>Less Than (<tt><</tt>)</summary>
|
||||
|
||||
/// <summary>Less Than (<c><</c>)</summary>
|
||||
| Less of Value: obj
|
||||
|
||||
/// <summary>Less Than or Equal To (<tt><=</tt>)</summary>
|
||||
| LessOrEqual of Value: obj
|
||||
|
||||
/// <summary>Not Equal to (<tt><></tt>)</summary>
|
||||
|
||||
/// <summary>Less Than or Equal To (<c><=</c>)</summary>
|
||||
| LessOrEqual of Value: obj
|
||||
|
||||
/// <summary>Not Equal to (<c><></c>)</summary>
|
||||
| NotEqual of Value: obj
|
||||
|
||||
/// <summary>Between (<tt>BETWEEN</tt>)</summary>
|
||||
|
||||
/// <summary>Between (<c>BETWEEN</c>)</summary>
|
||||
| Between of Min: obj * Max: obj
|
||||
|
||||
/// <summary>In (<tt>IN</tt>)</summary>
|
||||
|
||||
/// <summary>In (<c>IN</c>)</summary>
|
||||
| In of Values: obj seq
|
||||
|
||||
/// <summary>In Array (PostgreSQL: <tt>|?</tt>, SQLite: <tt>EXISTS / json_each / IN</tt>)</summary>
|
||||
|
||||
/// <summary>In Array (PostgreSQL: <c>|?</c>, SQLite: <c>EXISTS / json_each / IN</c>)</summary>
|
||||
| InArray of Table: string * Values: obj seq
|
||||
|
||||
/// <summary>Exists (<tt>IS NOT NULL</tt>)</summary>
|
||||
|
||||
/// <summary>Exists (<c>IS NOT NULL</c>)</summary>
|
||||
| Exists
|
||||
|
||||
/// <summary>Does Not Exist (<tt>IS NULL</tt>)</summary>
|
||||
|
||||
/// <summary>Does Not Exist (<c>IS NULL</c>)</summary>
|
||||
| NotExists
|
||||
|
||||
|
||||
/// <summary>The operator SQL for this comparison</summary>
|
||||
member this.OpSql =
|
||||
match this with
|
||||
@@ -50,7 +51,7 @@ type Comparison =
|
||||
| NotEqual _ -> "<>"
|
||||
| Between _ -> "BETWEEN"
|
||||
| In _ -> "IN"
|
||||
| InArray _ -> "?|" // PostgreSQL only; SQL needs a subquery for this
|
||||
| InArray _ -> "?|" // PostgreSQL only; SQL needs a subquery for this
|
||||
| Exists -> "IS NOT NULL"
|
||||
| NotExists -> "IS NULL"
|
||||
|
||||
@@ -62,120 +63,120 @@ type Dialect =
|
||||
| SQLite
|
||||
|
||||
|
||||
/// <summary>The format in which an element of a JSON field should be extracted</summary>
|
||||
/// <summary>The format in which an element of a JSON field should be extracted</summary>
|
||||
[<Struct>]
|
||||
type FieldFormat =
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Use <tt>->></tt> or <tt>#>></tt>; extracts a text (PostgreSQL) or SQL (SQLite) value
|
||||
/// Use <c>->></c> or <c>#>></c>; extracts a text (PostgreSQL) or SQL (SQLite) value
|
||||
/// </summary>
|
||||
| AsSql
|
||||
|
||||
/// <summary>Use <tt>-></tt> or <tt>#></tt>; extracts a JSONB (PostgreSQL) or JSON (SQLite) value</summary>
|
||||
|
||||
/// <summary>Use <c>-></c> or <c>#></c>; extracts a JSONB (PostgreSQL) or JSON (SQLite) value</summary>
|
||||
| AsJson
|
||||
|
||||
|
||||
/// <summary>Criteria for a field <tt>WHERE</tt> clause</summary>
|
||||
/// <summary>Criteria for a field <c>WHERE</c> clause</summary>
|
||||
type Field = {
|
||||
|
||||
|
||||
/// <summary>The name of the field</summary>
|
||||
Name: string
|
||||
|
||||
|
||||
/// <summary>The comparison for the field</summary>
|
||||
Comparison: Comparison
|
||||
|
||||
|
||||
/// <summary>The name of the parameter for this field</summary>
|
||||
ParameterName: string option
|
||||
|
||||
|
||||
/// <summary>The table qualifier for this field</summary>
|
||||
Qualifier: string option
|
||||
} with
|
||||
|
||||
|
||||
/// <summary>Create a comparison against a field</summary>
|
||||
/// <param name="name">The name of the field against which the comparison should be applied</param>
|
||||
/// <param name="comparison">The comparison for the given field</param>
|
||||
/// <returns>A new <tt>Field</tt> instance implementing the given comparison</returns>
|
||||
/// <returns>A new <c>Field</c> instance implementing the given comparison</returns>
|
||||
static member Where name (comparison: Comparison) =
|
||||
{ Name = name; Comparison = comparison; ParameterName = None; Qualifier = None }
|
||||
|
||||
/// <summary>Create an equals (<tt>=</tt>) field criterion</summary>
|
||||
|
||||
/// <summary>Create an equals (<c>=</c>) field criterion</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <param name="value">The value for the comparison</param>
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member Equal<'T> name (value: 'T) =
|
||||
Field.Where name (Equal value)
|
||||
|
||||
/// <summary>Create an equals (<tt>=</tt>) field criterion (alias)</summary>
|
||||
|
||||
/// <summary>Create an equals (<c>=</c>) field criterion (alias)</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <param name="value">The value for the comparison</param>
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member EQ<'T> name (value: 'T) = Field.Equal name value
|
||||
|
||||
/// <summary>Create a greater than (<tt>></tt>) field criterion</summary>
|
||||
|
||||
/// <summary>Create a greater than (<c>></c>) field criterion</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <param name="value">The value for the comparison</param>
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member Greater<'T> name (value: 'T) =
|
||||
Field.Where name (Greater value)
|
||||
|
||||
/// <summary>Create a greater than (<tt>></tt>) field criterion (alias)</summary>
|
||||
|
||||
/// <summary>Create a greater than (<c>></c>) field criterion (alias)</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <param name="value">The value for the comparison</param>
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member GT<'T> name (value: 'T) = Field.Greater name value
|
||||
|
||||
/// <summary>Create a greater than or equal to (<tt>>=</tt>) field criterion</summary>
|
||||
|
||||
/// <summary>Create a greater than or equal to (<c>>=</c>) field criterion</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <param name="value">The value for the comparison</param>
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member GreaterOrEqual<'T> name (value: 'T) =
|
||||
Field.Where name (GreaterOrEqual value)
|
||||
|
||||
/// <summary>Create a greater than or equal to (<tt>>=</tt>) field criterion (alias)</summary>
|
||||
|
||||
/// <summary>Create a greater than or equal to (<c>>=</c>) field criterion (alias)</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <param name="value">The value for the comparison</param>
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member GE<'T> name (value: 'T) = Field.GreaterOrEqual name value
|
||||
|
||||
/// <summary>Create a less than (<tt><</tt>) field criterion</summary>
|
||||
|
||||
/// <summary>Create a less than (<c><</c>) field criterion</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <param name="value">The value for the comparison</param>
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member Less<'T> name (value: 'T) =
|
||||
Field.Where name (Less value)
|
||||
|
||||
/// <summary>Create a less than (<tt><</tt>) field criterion (alias)</summary>
|
||||
|
||||
/// <summary>Create a less than (<c><</c>) field criterion (alias)</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <param name="value">The value for the comparison</param>
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member LT<'T> name (value: 'T) = Field.Less name value
|
||||
|
||||
/// <summary>Create a less than or equal to (<tt><=</tt>) field criterion</summary>
|
||||
|
||||
/// <summary>Create a less than or equal to (<c><=</c>) field criterion</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <param name="value">The value for the comparison</param>
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member LessOrEqual<'T> name (value: 'T) =
|
||||
Field.Where name (LessOrEqual value)
|
||||
|
||||
/// <summary>Create a less than or equal to (<tt><=</tt>) field criterion (alias)</summary>
|
||||
|
||||
/// <summary>Create a less than or equal to (<c><=</c>) field criterion (alias)</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <param name="value">The value for the comparison</param>
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member LE<'T> name (value: 'T) = Field.LessOrEqual name value
|
||||
|
||||
/// <summary>Create a not equals (<tt><></tt>) field criterion</summary>
|
||||
|
||||
/// <summary>Create a not equals (<c><></c>) field criterion</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <param name="value">The value for the comparison</param>
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member NotEqual<'T> name (value: 'T) =
|
||||
Field.Where name (NotEqual value)
|
||||
|
||||
/// <summary>Create a not equals (<tt><></tt>) field criterion (alias)</summary>
|
||||
|
||||
/// <summary>Create a not equals (<c><></c>) field criterion (alias)</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <param name="value">The value for the comparison</param>
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member NE<'T> name (value: 'T) = Field.NotEqual name value
|
||||
|
||||
|
||||
/// <summary>Create a Between field criterion</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <param name="min">The minimum value for the comparison range</param>
|
||||
@@ -183,27 +184,27 @@ type Field = {
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member Between<'T> name (min: 'T) (max: 'T) =
|
||||
Field.Where name (Between(min, max))
|
||||
|
||||
|
||||
/// <summary>Create a Between field criterion (alias)</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <param name="min">The minimum value for the comparison range</param>
|
||||
/// <param name="max">The maximum value for the comparison range</param>
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member BT<'T> name (min: 'T) (max: 'T) = Field.Between name min max
|
||||
|
||||
|
||||
/// <summary>Create an In field criterion</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <param name="values">The values for the comparison</param>
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member In<'T> name (values: 'T seq) =
|
||||
Field.Where name (In (Seq.map box values))
|
||||
|
||||
|
||||
/// <summary>Create an In field criterion (alias)</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <param name="values">The values for the comparison</param>
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member IN<'T> name (values: 'T seq) = Field.In name values
|
||||
|
||||
|
||||
/// <summary>Create an InArray field criterion</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <param name="tableName">The name of the table in which the field's documents are stored</param>
|
||||
@@ -211,34 +212,34 @@ type Field = {
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member InArray<'T> name tableName (values: 'T seq) =
|
||||
Field.Where name (InArray(tableName, Seq.map box values))
|
||||
|
||||
/// <summary>Create an exists (<tt>IS NOT NULL</tt>) field criterion</summary>
|
||||
|
||||
/// <summary>Create an exists (<c>IS NOT NULL</c>) field criterion</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member Exists name =
|
||||
Field.Where name Exists
|
||||
|
||||
/// <summary>Create an exists (<tt>IS NOT NULL</tt>) field criterion (alias)</summary>
|
||||
|
||||
/// <summary>Create an exists (<c>IS NOT NULL</c>) field criterion (alias)</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member EX name = Field.Exists name
|
||||
|
||||
/// <summary>Create a not exists (<tt>IS NULL</tt>) field criterion</summary>
|
||||
|
||||
/// <summary>Create a not exists (<c>IS NULL</c>) field criterion</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member NotExists name =
|
||||
Field.Where name NotExists
|
||||
|
||||
/// <summary>Create a not exists (<tt>IS NULL</tt>) field criterion (alias)</summary>
|
||||
|
||||
/// <summary>Create a not exists (<c>IS NULL</c>) field criterion (alias)</summary>
|
||||
/// <param name="name">The name of the field to be compared</param>
|
||||
/// <returns>A field with the given comparison</returns>
|
||||
static member NEX name = Field.NotExists name
|
||||
|
||||
/// <summary>Transform a field name (<tt>a.b.c</tt>) to a path for the given SQL dialect</summary>
|
||||
|
||||
/// <summary>Transform a field name (<c>a.b.c</c>) to a path for the given SQL dialect</summary>
|
||||
/// <param name="name">The name of the field in dotted format</param>
|
||||
/// <param name="dialect">The SQL dialect to use when converting the name to nested path format</param>
|
||||
/// <param name="format">Whether to reference this path as a JSON value or a SQL value</param>
|
||||
/// <returns>A <tt>string</tt> with the path required to address the nested document value</returns>
|
||||
/// <returns>A <c>string</c> with the path required to address the nested document value</returns>
|
||||
static member NameToPath (name: string) dialect format =
|
||||
let path =
|
||||
if name.Contains '.' then
|
||||
@@ -254,19 +255,19 @@ type Field = {
|
||||
else
|
||||
match format with AsJson -> $"->'{name}'" | AsSql -> $"->>'{name}'"
|
||||
$"data{path}"
|
||||
|
||||
|
||||
/// <summary>Create a field with a given name, but no other properties filled</summary>
|
||||
/// <param name="name">The field name, along with any other qualifications if used in a sorting context</param>
|
||||
/// <remarks><tt>Comparison</tt> will be <tt>Equal</tt>, value will be an empty string</remarks>
|
||||
/// <remarks><c>Comparison</c> will be <c>Equal</c>, value will be an empty string</remarks>
|
||||
static member Named name =
|
||||
Field.Where name (Equal "")
|
||||
|
||||
|
||||
/// <summary>Specify the name of the parameter for this field</summary>
|
||||
/// <param name="name">The parameter name (including <tt>:</tt> or <tt>@</tt>)</param>
|
||||
/// <param name="name">The parameter name (including <c>:</c> or <c>@</c>)</param>
|
||||
/// <returns>A field with the given parameter name specified</returns>
|
||||
member this.WithParameterName name =
|
||||
{ this with ParameterName = Some name }
|
||||
|
||||
|
||||
/// <summary>Specify a qualifier (alias) for the table from which this field will be referenced</summary>
|
||||
/// <param name="alias">The table alias for this field comparison</param>
|
||||
/// <returns>A field with the given qualifier specified</returns>
|
||||
@@ -276,7 +277,7 @@ type Field = {
|
||||
/// <summary>Get the qualified path to the field</summary>
|
||||
/// <param name="dialect">The SQL dialect to use when converting the name to nested path format</param>
|
||||
/// <param name="format">Whether to reference this path as a JSON value or a SQL value</param>
|
||||
/// <returns>A <tt>string</tt> with the qualified path required to address the nested document value</returns>
|
||||
/// <returns>A <c>string</c> with the qualified path required to address the nested document value</returns>
|
||||
member this.Path dialect format =
|
||||
(this.Qualifier |> Option.map (fun q -> $"{q}.") |> Option.defaultValue "")
|
||||
+ Field.NameToPath this.Name dialect format
|
||||
@@ -285,13 +286,13 @@ type Field = {
|
||||
/// <summary>How fields should be matched</summary>
|
||||
[<Struct>]
|
||||
type FieldMatch =
|
||||
|
||||
/// <summary>Any field matches (<tt>OR</tt>)</summary>
|
||||
|
||||
/// <summary>Any field matches (<c>OR</c>)</summary>
|
||||
| Any
|
||||
|
||||
/// <summary>All fields match (<tt>AND</tt>)</summary>
|
||||
|
||||
/// <summary>All fields match (<c>AND</c>)</summary>
|
||||
| All
|
||||
|
||||
|
||||
/// <summary>The SQL value implementing each matching strategy</summary>
|
||||
override this.ToString() =
|
||||
match this with Any -> "OR" | All -> "AND"
|
||||
@@ -299,10 +300,10 @@ type FieldMatch =
|
||||
|
||||
/// <summary>Derive parameter names (each instance wraps a counter to uniquely name anonymous fields)</summary>
|
||||
type ParameterName() =
|
||||
|
||||
|
||||
/// The counter for the next field value
|
||||
let mutable currentIdx = -1
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Return the specified name for the parameter, or an anonymous parameter name if none is specified
|
||||
/// </summary>
|
||||
@@ -319,30 +320,30 @@ type ParameterName() =
|
||||
/// <summary>Automatically-generated document ID strategies</summary>
|
||||
[<Struct>]
|
||||
type AutoId =
|
||||
|
||||
|
||||
/// <summary>No automatic IDs will be generated</summary>
|
||||
| Disabled
|
||||
|
||||
|
||||
/// <summary>Generate a MAX-plus-1 numeric value for documents</summary>
|
||||
| Number
|
||||
|
||||
/// <summary>Generate a <tt>GUID</tt> for each document (as a lowercase, no-dashes, 32-character string)</summary>
|
||||
|
||||
/// <summary>Generate a <c>GUID</c> for each document (as a lowercase, no-dashes, 32-character string)</summary>
|
||||
| Guid
|
||||
|
||||
|
||||
/// <summary>Generate a random string of hexadecimal characters for each document</summary>
|
||||
| RandomString
|
||||
with
|
||||
/// <summary>Generate a <tt>GUID</tt> string</summary>
|
||||
/// <returns>A <tt>GUID</tt> string</returns>
|
||||
/// <summary>Generate a <c>GUID</c> string</summary>
|
||||
/// <returns>A <c>GUID</c> string</returns>
|
||||
static member GenerateGuid() =
|
||||
System.Guid.NewGuid().ToString "N"
|
||||
|
||||
|
||||
/// <summary>Generate a string of random hexadecimal characters</summary>
|
||||
/// <param name="length">The number of characters to generate</param>
|
||||
/// <returns>A string of the given length with random hexadecimal characters</returns>
|
||||
static member GenerateRandomString(length: int) =
|
||||
RandomNumberGenerator.GetHexString(length, lowercase = true)
|
||||
|
||||
|
||||
/// <summary>Does the given document need an automatic ID generated?</summary>
|
||||
/// <param name="strategy">The auto-ID strategy currently in use</param>
|
||||
/// <param name="document">The document being inserted</param>
|
||||
@@ -387,26 +388,26 @@ with
|
||||
|
||||
/// <summary>The required document serialization implementation</summary>
|
||||
type IDocumentSerializer =
|
||||
|
||||
|
||||
/// <summary>Serialize an object to a JSON string</summary>
|
||||
abstract Serialize<'T> : 'T -> string
|
||||
|
||||
|
||||
/// <summary>Deserialize a JSON string into an object</summary>
|
||||
abstract Deserialize<'T> : string -> 'T
|
||||
|
||||
|
||||
/// <summary>Document serializer defaults</summary>
|
||||
module DocumentSerializer =
|
||||
|
||||
|
||||
open System.Text.Json
|
||||
open System.Text.Json.Serialization
|
||||
|
||||
|
||||
/// The default JSON serializer options to use with the stock serializer
|
||||
let private jsonDefaultOpts =
|
||||
let o = JsonSerializerOptions()
|
||||
o.Converters.Add(JsonFSharpConverter())
|
||||
o
|
||||
|
||||
|
||||
/// <summary>The default JSON serializer</summary>
|
||||
[<CompiledName "Default">]
|
||||
let ``default`` =
|
||||
@@ -424,7 +425,7 @@ module Configuration =
|
||||
|
||||
/// The serializer to use for document manipulation
|
||||
let mutable private serializerValue = DocumentSerializer.``default``
|
||||
|
||||
|
||||
/// <summary>Register a serializer to use for translating documents to domain types</summary>
|
||||
/// <param name="ser">The serializer to use when manipulating documents</param>
|
||||
[<CompiledName "UseSerializer">]
|
||||
@@ -436,46 +437,46 @@ module Configuration =
|
||||
[<CompiledName "Serializer">]
|
||||
let serializer () =
|
||||
serializerValue
|
||||
|
||||
|
||||
/// The serialized name of the ID field for documents
|
||||
let mutable private idFieldValue = "Id"
|
||||
|
||||
|
||||
/// <summary>Specify the name of the ID field for documents</summary>
|
||||
/// <param name="it">The name of the ID field for documents</param>
|
||||
[<CompiledName "UseIdField">]
|
||||
let useIdField it =
|
||||
idFieldValue <- it
|
||||
|
||||
|
||||
/// <summary>Retrieve the currently configured ID field for documents</summary>
|
||||
/// <returns>The currently configured ID field</returns>
|
||||
[<CompiledName "IdField">]
|
||||
let idField () =
|
||||
idFieldValue
|
||||
|
||||
|
||||
/// The automatic ID strategy used by the library
|
||||
let mutable private autoIdValue = Disabled
|
||||
|
||||
|
||||
/// <summary>Specify the automatic ID generation strategy used by the library</summary>
|
||||
/// <param name="it">The automatic ID generation strategy to use</param>
|
||||
[<CompiledName "UseAutoIdStrategy">]
|
||||
let useAutoIdStrategy it =
|
||||
autoIdValue <- it
|
||||
|
||||
|
||||
/// <summary>Retrieve the currently configured automatic ID generation strategy</summary>
|
||||
/// <returns>The current automatic ID generation strategy</returns>
|
||||
[<CompiledName "AutoIdStrategy">]
|
||||
let autoIdStrategy () =
|
||||
autoIdValue
|
||||
|
||||
|
||||
/// The length of automatically generated random strings
|
||||
let mutable private idStringLengthValue = 16
|
||||
|
||||
|
||||
/// <summary>Specify the length of automatically generated random strings</summary>
|
||||
/// <param name="length">The length of automatically generated random strings</param>
|
||||
[<CompiledName "UseIdStringLength">]
|
||||
let useIdStringLength length =
|
||||
idStringLengthValue <- length
|
||||
|
||||
|
||||
/// <summary>Retrieve the currently configured length of automatically generated random strings</summary>
|
||||
/// <returns>The current length of automatically generated random strings</returns>
|
||||
[<CompiledName "IdStringLength">]
|
||||
@@ -486,31 +487,31 @@ module Configuration =
|
||||
/// <summary>Query construction functions</summary>
|
||||
[<RequireQualifiedAccess>]
|
||||
module Query =
|
||||
|
||||
/// <summary>Combine a query (<tt>SELECT</tt>, <tt>UPDATE</tt>, etc.) and a <tt>WHERE</tt> clause</summary>
|
||||
|
||||
/// <summary>Combine a query (<c>SELECT</c>, <c>UPDATE</c>, etc.) and a <c>WHERE</c> clause</summary>
|
||||
/// <param name="statement">The first part of the statement</param>
|
||||
/// <param name="where">The <tt>WHERE</tt> clause for the statement</param>
|
||||
/// <returns>The two parts of the query combined with <tt>WHERE</tt></returns>
|
||||
/// <param name="where">The <c>WHERE</c> clause for the statement</param>
|
||||
/// <returns>The two parts of the query combined with <c>WHERE</c></returns>
|
||||
[<CompiledName "StatementWhere">]
|
||||
let statementWhere statement where =
|
||||
$"%s{statement} WHERE %s{where}"
|
||||
|
||||
|
||||
/// <summary>Queries to define tables and indexes</summary>
|
||||
module Definition =
|
||||
|
||||
|
||||
/// <summary>SQL statement to create a document table</summary>
|
||||
/// <param name="name">The name of the table to create (may include schema)</param>
|
||||
/// <param name="dataType">The type of data for the column (<tt>JSON</tt>, <tt>JSONB</tt>, etc.)</param>
|
||||
/// <param name="dataType">The type of data for the column (<c>JSON</c>, <c>JSONB</c>, etc.)</param>
|
||||
/// <returns>A query to create a document table</returns>
|
||||
[<CompiledName "EnsureTableFor">]
|
||||
let ensureTableFor name dataType =
|
||||
$"CREATE TABLE IF NOT EXISTS %s{name} (data %s{dataType} NOT NULL)"
|
||||
|
||||
|
||||
/// Split a schema and table name
|
||||
let private splitSchemaAndTable (tableName: string) =
|
||||
let parts = tableName.Split '.'
|
||||
if Array.length parts = 1 then "", tableName else parts[0], parts[1]
|
||||
|
||||
|
||||
/// <summary>SQL statement to create an index on one or more fields in a JSON document</summary>
|
||||
/// <param name="tableName">The table on which an index should be created (may include schema)</param>
|
||||
/// <param name="indexName">The name of the index to be created</param>
|
||||
@@ -537,7 +538,7 @@ module Query =
|
||||
[<CompiledName "EnsureKey">]
|
||||
let ensureKey tableName dialect =
|
||||
(ensureIndexOn tableName "key" [ Configuration.idField () ] dialect).Replace("INDEX", "UNIQUE INDEX")
|
||||
|
||||
|
||||
/// <summary>Query to insert a document</summary>
|
||||
/// <param name="tableName">The table into which to insert (may include schema)</param>
|
||||
/// <returns>A query to insert a document</returns>
|
||||
@@ -554,48 +555,48 @@ module Query =
|
||||
let save tableName =
|
||||
sprintf
|
||||
"INSERT INTO %s VALUES (@data) ON CONFLICT ((data->>'%s')) DO UPDATE SET data = EXCLUDED.data"
|
||||
tableName (Configuration.idField ())
|
||||
|
||||
tableName (Configuration.idField ())
|
||||
|
||||
/// <summary>Query to count documents in a table</summary>
|
||||
/// <param name="tableName">The table in which to count documents (may include schema)</param>
|
||||
/// <returns>A query to count documents</returns>
|
||||
/// <remarks>This query has no <tt>WHERE</tt> clause</remarks>
|
||||
/// <remarks>This query has no <c>WHERE</c> clause</remarks>
|
||||
[<CompiledName "Count">]
|
||||
let count tableName =
|
||||
$"SELECT COUNT(*) AS it FROM %s{tableName}"
|
||||
|
||||
|
||||
/// <summary>Query to check for document existence in a table</summary>
|
||||
/// <param name="tableName">The table in which existence should be checked (may include schema)</param>
|
||||
/// <param name="where">The <tt>WHERE</tt> clause with the existence criteria</param>
|
||||
/// <param name="where">The <c>WHERE</c> clause with the existence criteria</param>
|
||||
/// <returns>A query to check document existence</returns>
|
||||
[<CompiledName "Exists">]
|
||||
let exists tableName where =
|
||||
$"SELECT EXISTS (SELECT 1 FROM %s{tableName} WHERE %s{where}) AS it"
|
||||
|
||||
|
||||
/// <summary>Query to select documents from a table</summary>
|
||||
/// <param name="tableName">The table from which documents should be found (may include schema)</param>
|
||||
/// <returns>A query to retrieve documents</returns>
|
||||
/// <remarks>This query has no <tt>WHERE</tt> clause</remarks>
|
||||
/// <remarks>This query has no <c>WHERE</c> clause</remarks>
|
||||
[<CompiledName "Find">]
|
||||
let find tableName =
|
||||
$"SELECT data FROM %s{tableName}"
|
||||
|
||||
|
||||
/// <summary>Query to update (replace) a document</summary>
|
||||
/// <param name="tableName">The table in which documents should be replaced (may include schema)</param>
|
||||
/// <returns>A query to update documents</returns>
|
||||
/// <remarks>This query has no <tt>WHERE</tt> clause</remarks>
|
||||
/// <remarks>This query has no <c>WHERE</c> clause</remarks>
|
||||
[<CompiledName "Update">]
|
||||
let update tableName =
|
||||
$"UPDATE %s{tableName} SET data = @data"
|
||||
|
||||
|
||||
/// <summary>Query to delete documents from a table</summary>
|
||||
/// <param name="tableName">The table in which documents should be deleted (may include schema)</param>
|
||||
/// <returns>A query to delete documents</returns>
|
||||
/// <remarks>This query has no <tt>WHERE</tt> clause</remarks>
|
||||
/// <remarks>This query has no <c>WHERE</c> clause</remarks>
|
||||
[<CompiledName "Delete">]
|
||||
let delete tableName =
|
||||
$"DELETE FROM %s{tableName}"
|
||||
|
||||
|
||||
/// <summary>Create a SELECT clause to retrieve the document data from the given table</summary>
|
||||
/// <param name="tableName">The table from which documents should be found (may include schema)</param>
|
||||
/// <returns>A query to retrieve documents</returns>
|
||||
@@ -603,11 +604,11 @@ module Query =
|
||||
[<System.Obsolete "Use Find instead">]
|
||||
let selectFromTable tableName =
|
||||
find tableName
|
||||
|
||||
/// <summary>Create an <tt>ORDER BY</tt> clause for the given fields</summary>
|
||||
|
||||
/// <summary>Create an <c>ORDER BY</c> clause for the given fields</summary>
|
||||
/// <param name="fields">One or more fields by which to order</param>
|
||||
/// <param name="dialect">The SQL dialect for the generated clause</param>
|
||||
/// <returns>An <tt>ORDER BY</tt> clause for the given fields</returns>
|
||||
/// <returns>An <c>ORDER BY</c> clause for the given fields</returns>
|
||||
[<CompiledName "OrderBy">]
|
||||
let orderBy fields dialect =
|
||||
if Seq.isEmpty fields then ""
|
||||
@@ -631,3 +632,49 @@ module Query =
|
||||
|> function path -> path + defaultArg direction "")
|
||||
|> String.concat ", "
|
||||
|> function it -> $" ORDER BY {it}"
|
||||
|
||||
|
||||
#nowarn "FS3511" // "let rec" is not statically compilable
|
||||
|
||||
open System.IO.Pipelines
|
||||
|
||||
/// <summary>Functions that manipulate <c>PipeWriter</c>s</summary>
|
||||
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
|
||||
module PipeWriter =
|
||||
|
||||
/// <summary>Write a UTF-8 string to this pipe</summary>
|
||||
/// <param name="writer">The PipeWriter to which the string should be written</param>
|
||||
/// <param name="text">The string to be written to the pipe</param>
|
||||
/// <returns><c>true</c> if the pipe is still open, <c>false</c> if not</returns>
|
||||
[<CompiledName "WriteString">]
|
||||
let writeString (writer: PipeWriter) (text: string) = backgroundTask {
|
||||
try
|
||||
let! writeResult = writer.WriteAsync(Encoding.UTF8.GetBytes text)
|
||||
return not writeResult.IsCompleted
|
||||
with :? System.ObjectDisposedException -> return false
|
||||
}
|
||||
|
||||
/// <summary>Write an array of strings, abandoning the sequence if the pipe is closed</summary>
|
||||
/// <param name="writer">The PipeWriter to which the strings should be written</param>
|
||||
/// <param name="items">The strings to be written</param>
|
||||
/// <returns><c>true</c> if the pipe is still open, <c>false</c> if not</returns>
|
||||
[<CompiledName "WriteStrings">]
|
||||
let writeStrings writer items = backgroundTask {
|
||||
let theItems = Seq.cache items
|
||||
let rec writeNext idx = backgroundTask {
|
||||
match theItems |> Seq.tryItem idx with
|
||||
| Some item ->
|
||||
if idx > 0 then
|
||||
let! _ = writeString writer ","
|
||||
()
|
||||
match! writeString writer item with
|
||||
| true -> return! writeNext (idx + 1)
|
||||
| false -> return false
|
||||
| None -> return true
|
||||
}
|
||||
let! _ = writeString writer "["
|
||||
let! isCleanFinish = writeNext 0
|
||||
if isCleanFinish then
|
||||
let! _ = writeString writer "]"
|
||||
()
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ This package provides common definitions and functionality for `BitBadger.Docume
|
||||
|
||||
- Select, insert, update, save (upsert), delete, count, and check existence of documents, and create tables and indexes for these documents
|
||||
- Automatically generate IDs for documents (numeric IDs, GUIDs, or random strings)
|
||||
- Addresses documents via ID and via comparison on any field (for PostgreSQL, also via equality on any property by using JSON containment, or via condition on any property using JSON Path queries)
|
||||
- Accesses documents as your domain models (<abbr title="Plain Old CLR Objects">POCO</abbr>s)
|
||||
- Uses `Task`-based async for all data access functions
|
||||
- Uses building blocks for more complex queries
|
||||
- Address documents via ID and via comparison on any field (for PostgreSQL, also via equality on any property by using JSON containment, or via condition on any property using JSON Path queries)
|
||||
- Access documents as your domain models (<abbr title="Plain Old CLR Objects">POCO</abbr>s), as JSON strings, or as JSON written directly to a `PipeWriter`
|
||||
- Use `Task`-based async for all data access functions
|
||||
- Use building blocks for more complex queries
|
||||
|
||||
## Getting Started
|
||||
|
||||
Install the library of your choice and follow its README; also, the [project site](https://bitbadger.solutions/open-source/relational-documents/) has complete documentation.
|
||||
Install the library of your choice and follow its README; also, the [project site](https://relationaldocs.bitbadger.solutions/dotnet/) has complete documentation.
|
||||
|
||||
Reference in New Issue
Block a user