Integrate v4 document library for SQLite

- Eliminate warnings for PostgreSQL
This commit is contained in:
Daniel J. Summers 2024-08-19 22:23:22 -04:00
parent cd450a05e5
commit fbc4e891bd
9 changed files with 102 additions and 127 deletions

View File

@ -2,7 +2,6 @@
open BitBadger.Documents open BitBadger.Documents
open BitBadger.Documents.Postgres open BitBadger.Documents.Postgres
open BitBadger.Documents.Postgres.Compat
open Microsoft.Extensions.Logging open Microsoft.Extensions.Logging
open MyWebLog open MyWebLog
open MyWebLog.Data open MyWebLog.Data
@ -21,7 +20,7 @@ type PostgresCategoryData(log: ILogger) =
log.LogTrace "Category.countTopLevel" log.LogTrace "Category.countTopLevel"
Custom.scalar Custom.scalar
$"""{Query.byContains (Query.count Table.Category)} $"""{Query.byContains (Query.count Table.Category)}
AND {Query.whereByField (Field.NEX (nameof Category.Empty.ParentId)) ""}""" AND {Query.whereByFields Any [ Field.NEX (nameof Category.Empty.ParentId) ]}"""
[ webLogContains webLogId ] [ webLogContains webLogId ]
toCount toCount
@ -95,7 +94,7 @@ type PostgresCategoryData(log: ILogger) =
Query.byId (Query.removeFields Table.Category) "", Query.byId (Query.removeFields Table.Category) "",
children children
|> List.map (fun child -> |> List.map (fun child ->
[ idParam child.Id; fieldNameParam [ nameof Category.Empty.ParentId ] ]) [ idParam child.Id; fieldNameParams [ nameof Category.Empty.ParentId ] ])
let! _ = let! _ =
Configuration.dataSource () Configuration.dataSource ()
|> Sql.fromDataSource |> Sql.fromDataSource

View File

@ -3,7 +3,6 @@ namespace MyWebLog.Data.SQLite
open System.Threading.Tasks open System.Threading.Tasks
open BitBadger.Documents open BitBadger.Documents
open BitBadger.Documents.Sqlite open BitBadger.Documents.Sqlite
open BitBadger.Documents.Sqlite.Compat
open Microsoft.Data.Sqlite open Microsoft.Data.Sqlite
open Microsoft.Extensions.Logging open Microsoft.Extensions.Logging
open MyWebLog open MyWebLog
@ -24,15 +23,14 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
/// Count all top-level categories for the given web log /// Count all top-level categories for the given web log
let countTopLevel webLogId = let countTopLevel webLogId =
log.LogTrace "Category.countTopLevel" log.LogTrace "Category.countTopLevel"
let fields = [ webLogField webLogId; Field.NEX parentIdField ]
conn.customScalar conn.customScalar
$"{Document.Query.countByWebLog Table.Category} AND data ->> '{parentIdField}' IS NULL" (Query.byFields (Query.count Table.Category) All fields) (addFieldParams fields []) (toCount >> int)
[ webLogParam webLogId ]
(toCount >> int)
/// Find all categories for the given web log /// Find all categories for the given web log
let findByWebLog webLogId = let findByWebLog webLogId =
log.LogTrace "Category.findByWebLog" log.LogTrace "Category.findByWebLog"
Document.findByWebLog<Category> Table.Category webLogId conn conn.findByFields<Category> Table.Category Any [ webLogField webLogId ]
/// Retrieve all categories for the given web log in a DotLiquid-friendly format /// Retrieve all categories for the given web log in a DotLiquid-friendly format
let findAllForView webLogId = backgroundTask { let findAllForView webLogId = backgroundTask {
@ -51,10 +49,10 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
|> List.ofSeq |> List.ofSeq
|> inJsonArray Table.Post (nameof Post.Empty.CategoryIds) "catId" |> inJsonArray Table.Post (nameof Post.Empty.CategoryIds) "catId"
let query = $""" let query = $"""
SELECT COUNT(DISTINCT data ->> '{nameof Post.Empty.Id}') SELECT COUNT(DISTINCT data->>'{nameof Post.Empty.Id}')
FROM {Table.Post} FROM {Table.Post}
WHERE {Document.Query.whereByWebLog} WHERE {Document.Query.whereByWebLog}
AND {Query.whereByField (Field.EQ (nameof Post.Empty.Status) "") $"'{string Published}'"} AND data->>'{nameof Post.Empty.Status}' = '{string Published}'
AND {catSql}""" AND {catSql}"""
let! postCount = conn.customScalar query (webLogParam webLogId :: catParams) toCount let! postCount = conn.customScalar query (webLogParam webLogId :: catParams) toCount
return it.Id, int postCount return it.Id, int postCount
@ -70,9 +68,9 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
} }
/// Find a category by its ID for the given web log /// Find a category by its ID for the given web log
let findById catId webLogId = let findById (catId: CategoryId) webLogId =
log.LogTrace "Category.findById" log.LogTrace "Category.findById"
Document.findByIdAndWebLog<CategoryId, Category> Table.Category catId webLogId conn conn.findFirstByFields<Category> Table.Category All [ idField catId; webLogField webLogId ]
/// Delete a category /// Delete a category
let delete catId webLogId = backgroundTask { let delete catId webLogId = backgroundTask {
@ -80,22 +78,22 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
match! findById catId webLogId with match! findById catId webLogId with
| Some cat -> | Some cat ->
// Reassign any children to the category's parent category // Reassign any children to the category's parent category
let! children = conn.countByField Table.Category (Field.EQ parentIdField (string catId)) let! children = conn.countByFields Table.Category Any [ Field.EQ parentIdField (string catId) ]
if children > 0L then if children > 0L then
let parent = Field.EQ parentIdField (string catId) let parent = [ Field.EQ parentIdField (string catId) ]
match cat.ParentId with match cat.ParentId with
| Some _ -> do! conn.patchByField Table.Category parent {| ParentId = cat.ParentId |} | Some _ -> do! conn.patchByFields Table.Category Any parent {| ParentId = cat.ParentId |}
| None -> do! conn.removeFieldsByField Table.Category parent [ parentIdField ] | None -> do! conn.removeFieldsByFields Table.Category Any parent [ parentIdField ]
// Delete the category off all posts where it is assigned, and the category itself // Delete the category off all posts where it is assigned, and the category itself
let catIdField = nameof Post.Empty.CategoryIds let catIdField = nameof Post.Empty.CategoryIds
let! posts = let! posts =
conn.customList conn.customList
$"SELECT data ->> '{nameof Post.Empty.Id}', data -> '{catIdField}' $"SELECT data->>'{nameof Post.Empty.Id}', data->'{catIdField}'
FROM {Table.Post} FROM {Table.Post}
WHERE {Document.Query.whereByWebLog} WHERE {Document.Query.whereByWebLog}
AND EXISTS AND EXISTS
(SELECT 1 (SELECT 1
FROM json_each({Table.Post}.data -> '{catIdField}') FROM json_each({Table.Post}.data->'{catIdField}')
WHERE json_each.value = @id)" WHERE json_each.value = @id)"
[ idParam catId; webLogParam webLogId ] [ idParam catId; webLogParam webLogId ]
(fun rdr -> rdr.GetString 0, Utils.deserialize<string list> ser (rdr.GetString 1)) (fun rdr -> rdr.GetString 0, Utils.deserialize<string list> ser (rdr.GetString 1))

View File

@ -218,6 +218,10 @@ module Map =
Data = data } Data = data }
open BitBadger.Documents
open BitBadger.Documents.Sqlite
open BitBadger.Documents.Sqlite.WithConn
/// Create a named parameter /// Create a named parameter
let sqlParam name (value: obj) = let sqlParam name (value: obj) =
SqliteParameter(name, value) SqliteParameter(name, value)
@ -226,11 +230,13 @@ let sqlParam name (value: obj) =
let webLogParam (webLogId: WebLogId) = let webLogParam (webLogId: WebLogId) =
sqlParam "@webLogId" (string webLogId) sqlParam "@webLogId" (string webLogId)
/// Create a field for an ID value
let idField<'T> (idValue: 'T) =
{ Field.EQ "Id" (string idValue) with ParameterName = Some "@id" }
open BitBadger.Documents /// Create a web log field
open BitBadger.Documents.Sqlite let webLogField (webLogId: WebLogId) =
open BitBadger.Documents.Sqlite.Compat { Field.EQ "WebLogId" (string webLogId) with ParameterName = Some "@webLogId" }
open BitBadger.Documents.Sqlite.WithConn
/// Functions for manipulating documents /// Functions for manipulating documents
module Document = module Document =
@ -240,34 +246,18 @@ module Document =
/// Fragment to add a web log ID condition to a WHERE clause (parameter @webLogId) /// Fragment to add a web log ID condition to a WHERE clause (parameter @webLogId)
let whereByWebLog = let whereByWebLog =
Query.whereByField (Field.EQ "WebLogId" "") "@webLogId" Query.whereByFields Any [ { Field.EQ "WebLogId" "" with ParameterName = Some "@webLogId" } ]
/// A SELECT query to count documents for a given web log ID
let countByWebLog table =
Query.statementWhere (Query.count table) whereByWebLog
/// A query to select from a table by the document's ID and its web log ID
let selectByIdAndWebLog table =
Query.statementWhere (Query.find table) $"""{Query.whereById "@id"} AND {whereByWebLog}"""
/// A query to select from a table by its web log ID /// A query to select from a table by its web log ID
let selectByWebLog table = let selectByWebLog table =
$"{Query.selectFromTable table} WHERE {whereByWebLog}" Query.statementWhere (Query.find table) whereByWebLog
/// Count documents for the given web log ID /// Count documents for the given web log ID
let countByWebLog table (webLogId: WebLogId) conn = backgroundTask { let countByWebLog table (webLogId: WebLogId) conn = backgroundTask {
let! count = Count.byFields table Any [ Field.EQ "WebLogId" (string webLogId) ] conn let! count = Count.byFields table Any [ webLogField webLogId ] conn
return int count return int count
} }
/// Find a document by its ID and web log ID
let findByIdAndWebLog<'TKey, 'TDoc> table (key: 'TKey) webLogId conn =
Custom.single (Query.selectByIdAndWebLog table) [ idParam key; webLogParam webLogId ] fromData<'TDoc> conn
/// Find documents for the given web log
let findByWebLog<'TDoc> table (webLogId: WebLogId) conn =
Find.byFields<'TDoc> table Any [ Field.EQ "WebLogId" (string webLogId) ] conn
/// Functions to support revisions /// Functions to support revisions
module Revisions = module Revisions =
@ -285,7 +275,7 @@ module Revisions =
Custom.list Custom.list
$"SELECT pr.* $"SELECT pr.*
FROM %s{revTable} pr FROM %s{revTable} pr
INNER JOIN %s{entityTable} p ON p.data ->> 'Id' = pr.{entityTable}_id INNER JOIN %s{entityTable} p ON p.data->>'Id' = pr.{entityTable}_id
WHERE p.{Document.Query.whereByWebLog} WHERE p.{Document.Query.whereByWebLog}
ORDER BY as_of DESC" ORDER BY as_of DESC"
[ webLogParam webLogId ] [ webLogParam webLogId ]

View File

@ -3,7 +3,6 @@ namespace MyWebLog.Data.SQLite
open System.Threading.Tasks open System.Threading.Tasks
open BitBadger.Documents open BitBadger.Documents
open BitBadger.Documents.Sqlite open BitBadger.Documents.Sqlite
open BitBadger.Documents.Sqlite.Compat
open Microsoft.Data.Sqlite open Microsoft.Data.Sqlite
open Microsoft.Extensions.Logging open Microsoft.Extensions.Logging
open MyWebLog open MyWebLog
@ -19,7 +18,7 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
let pgListName = nameof Page.Empty.IsInPageList let pgListName = nameof Page.Empty.IsInPageList
/// The JSON field for the title of the page /// The JSON field for the title of the page
let titleField = $"data ->> '{nameof Page.Empty.Title}'" let titleField = $"data->>'{nameof Page.Empty.Title}'"
// SUPPORT FUNCTIONS // SUPPORT FUNCTIONS
@ -51,9 +50,10 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
/// Get all pages for a web log (without text, metadata, revisions, or prior permalinks) /// Get all pages for a web log (without text, metadata, revisions, or prior permalinks)
let all webLogId = let all webLogId =
log.LogTrace "Page.all" log.LogTrace "Page.all"
let field = [ webLogField webLogId ]
conn.customList conn.customList
$"{Query.selectFromTable Table.Page} WHERE {Document.Query.whereByWebLog} ORDER BY LOWER({titleField})" $"{Query.byFields (Query.find Table.Page) Any field} ORDER BY LOWER({titleField})"
[ webLogParam webLogId ] (addFieldParams field [])
(fun rdr -> { fromData<Page> rdr with Text = ""; Metadata = []; PriorPermalinks = [] }) (fun rdr -> { fromData<Page> rdr with Text = ""; Metadata = []; PriorPermalinks = [] })
/// Count all pages for the given web log /// Count all pages for the given web log
@ -64,23 +64,22 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
/// Count all pages shown in the page list for the given web log /// Count all pages shown in the page list for the given web log
let countListed webLogId = let countListed webLogId =
log.LogTrace "Page.countListed" log.LogTrace "Page.countListed"
let fields = [ webLogField webLogId; Field.EQ pgListName true ]
conn.customScalar conn.customScalar
$"""{Document.Query.countByWebLog Table.Page} AND {Query.whereByField (Field.EQ pgListName "") "true"}""" (Query.byFields (Query.count Table.Page) All fields) (addFieldParams fields []) (toCount >> int)
[ webLogParam webLogId ]
(toCount >> int)
/// Find a page by its ID (without revisions and prior permalinks) /// Find a page by its ID (without revisions and prior permalinks)
let findById pageId webLogId = backgroundTask { let findById (pageId: PageId) webLogId = backgroundTask {
log.LogTrace "Page.findById" log.LogTrace "Page.findById"
match! Document.findByIdAndWebLog<PageId, Page> Table.Page pageId webLogId conn with match! conn.findFirstByFields<Page> Table.Page All [ idField pageId; webLogField webLogId ] with
| Some page -> return Some { page with PriorPermalinks = [] } | Some page -> return Some { page with PriorPermalinks = [] }
| None -> return None | None -> return None
} }
/// Find a complete page by its ID /// Find a complete page by its ID
let findFullById pageId webLogId = backgroundTask { let findFullById (pageId: PageId) webLogId = backgroundTask {
log.LogTrace "Page.findFullById" log.LogTrace "Page.findFullById"
match! Document.findByIdAndWebLog<PageId, Page> Table.Page pageId webLogId conn with match! conn.findFirstByFields<Page> Table.Page All [ idField pageId; webLogField webLogId ] with
| Some page -> | Some page ->
let! page = appendPageRevisions page let! page = appendPageRevisions page
return Some page return Some page
@ -94,7 +93,7 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
match! findById pageId webLogId with match! findById pageId webLogId with
| Some _ -> | Some _ ->
do! conn.customNonQuery do! conn.customNonQuery
$"DELETE FROM {Table.PageRevision} WHERE page_id = @id; $"{Query.delete Table.PageRevision} WHERE page_id = @id;
{Query.byId (Query.delete Table.Page) (string pageId)}" {Query.byId (Query.delete Table.Page) (string pageId)}"
[ idParam pageId ] [ idParam pageId ]
return true return true
@ -104,18 +103,16 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
/// Find a page by its permalink for the given web log /// Find a page by its permalink for the given web log
let findByPermalink (permalink: Permalink) webLogId = let findByPermalink (permalink: Permalink) webLogId =
log.LogTrace "Page.findByPermalink" log.LogTrace "Page.findByPermalink"
let linkParam = Field.EQ linkName (string permalink) let fields = [ webLogField webLogId; Field.EQ linkName (string permalink) ]
conn.customSingle conn.customSingle
$"""{Document.Query.selectByWebLog Table.Page} AND {Query.whereByField linkParam "@link"}""" (Query.byFields (Query.find Table.Page) All fields) (addFieldParams fields []) pageWithoutLinks
(addFieldParam "@link" linkParam [ webLogParam webLogId ])
pageWithoutLinks
/// Find the current permalink within a set of potential prior permalinks for the given web log /// Find the current permalink within a set of potential prior permalinks for the given web log
let findCurrentPermalink (permalinks: Permalink list) webLogId = let findCurrentPermalink (permalinks: Permalink list) webLogId =
log.LogTrace "Page.findCurrentPermalink" log.LogTrace "Page.findCurrentPermalink"
let linkSql, linkParams = inJsonArray Table.Page (nameof Page.Empty.PriorPermalinks) "link" permalinks let linkSql, linkParams = inJsonArray Table.Page (nameof Page.Empty.PriorPermalinks) "link" permalinks
conn.customSingle conn.customSingle
$"SELECT data ->> '{linkName}' AS permalink $"SELECT data->>'{linkName}' AS permalink
FROM {Table.Page} FROM {Table.Page}
WHERE {Document.Query.whereByWebLog} AND {linkSql}" WHERE {Document.Query.whereByWebLog} AND {linkSql}"
(webLogParam webLogId :: linkParams) (webLogParam webLogId :: linkParams)
@ -124,7 +121,7 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
/// Get all complete pages for the given web log /// Get all complete pages for the given web log
let findFullByWebLog webLogId = backgroundTask { let findFullByWebLog webLogId = backgroundTask {
log.LogTrace "Page.findFullByWebLog" log.LogTrace "Page.findFullByWebLog"
let! pages = Document.findByWebLog<Page> Table.Page webLogId conn let! pages = conn.findByFields<Page> Table.Page Any [ webLogField webLogId ]
let! withRevs = pages |> List.map appendPageRevisions |> Task.WhenAll let! withRevs = pages |> List.map appendPageRevisions |> Task.WhenAll
return List.ofArray withRevs return List.ofArray withRevs
} }
@ -132,18 +129,20 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
/// Get all listed pages for the given web log (without revisions or text) /// Get all listed pages for the given web log (without revisions or text)
let findListed webLogId = let findListed webLogId =
log.LogTrace "Page.findListed" log.LogTrace "Page.findListed"
let fields = [ webLogField webLogId; Field.EQ pgListName true ]
conn.customList conn.customList
$"""{Document.Query.selectByWebLog Table.Page} AND {Query.whereByField (Field.EQ pgListName "") "true"} $"{Query.byFields (Query.find Table.Page) All fields} ORDER BY LOWER({titleField})"
ORDER BY LOWER({titleField})""" (addFieldParams fields [])
[ webLogParam webLogId ]
(fun rdr -> { fromData<Page> rdr with Text = "" }) (fun rdr -> { fromData<Page> rdr with Text = "" })
/// Get a page of pages for the given web log (without revisions) /// Get a page of pages for the given web log (without revisions)
let findPageOfPages webLogId pageNbr = let findPageOfPages webLogId pageNbr =
log.LogTrace "Page.findPageOfPages" log.LogTrace "Page.findPageOfPages"
let field = [ webLogField webLogId ]
conn.customList conn.customList
$"{Document.Query.selectByWebLog Table.Page} ORDER BY LOWER({titleField}) LIMIT @pageSize OFFSET @toSkip" $"{Query.byFields (Query.find Table.Page) Any field} ORDER BY LOWER({titleField})
[ webLogParam webLogId; SqliteParameter("@pageSize", 26); SqliteParameter("@toSkip", (pageNbr - 1) * 25) ] LIMIT @pageSize OFFSET @toSkip"
(addFieldParams field [ sqlParam "@pageSize" 26; sqlParam "@toSkip" ((pageNbr - 1) * 25) ])
(fun rdr -> { pageWithoutLinks rdr with Metadata = [] }) (fun rdr -> { pageWithoutLinks rdr with Metadata = [] })
/// Update a page /// Update a page

View File

@ -3,7 +3,6 @@ namespace MyWebLog.Data.SQLite
open System.Threading.Tasks open System.Threading.Tasks
open BitBadger.Documents open BitBadger.Documents
open BitBadger.Documents.Sqlite open BitBadger.Documents.Sqlite
open BitBadger.Documents.Sqlite.Compat
open Microsoft.Data.Sqlite open Microsoft.Data.Sqlite
open Microsoft.Extensions.Logging open Microsoft.Extensions.Logging
open MyWebLog open MyWebLog
@ -17,7 +16,7 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
let linkName = nameof Post.Empty.Permalink let linkName = nameof Post.Empty.Permalink
/// The JSON field for when the post was published /// The JSON field for when the post was published
let publishField = $"data ->> '{nameof Post.Empty.PublishedOn}'" let publishField = $"data->>'{nameof Post.Empty.PublishedOn}'"
/// The name of the JSON field for the post's status /// The name of the JSON field for the post's status
let statName = nameof Post.Empty.Status let statName = nameof Post.Empty.Status
@ -44,7 +43,7 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
/// The SELECT statement to retrieve published posts with a web log ID parameter /// The SELECT statement to retrieve published posts with a web log ID parameter
let publishedPostByWebLog = let publishedPostByWebLog =
$"""{postByWebLog} AND {Query.whereByField (Field.EQ statName "") $"'{string Published}'"}""" $"{postByWebLog} AND data->>'{statName}' = '{string Published}'"
/// Update a post's revisions /// Update a post's revisions
let updatePostRevisions (postId: PostId) oldRevs newRevs = let updatePostRevisions (postId: PostId) oldRevs newRevs =
@ -63,16 +62,14 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
/// Count posts in a status for the given web log /// Count posts in a status for the given web log
let countByStatus (status: PostStatus) webLogId = let countByStatus (status: PostStatus) webLogId =
log.LogTrace "Post.countByStatus" log.LogTrace "Post.countByStatus"
let statParam = Field.EQ statName (string status) let fields = [ webLogField webLogId; Field.EQ statName (string status) ]
conn.customScalar conn.customScalar
$"""{Document.Query.countByWebLog Table.Post} AND {Query.whereByField statParam "@status"}""" (Query.byFields (Query.count Table.Post) All fields) (addFieldParams fields []) (toCount >> int)
(addFieldParam "@status" statParam [ webLogParam webLogId ])
(toCount >> int)
/// Find a post by its ID for the given web log (excluding revisions) /// Find a post by its ID for the given web log (excluding revisions)
let findById postId webLogId = backgroundTask { let findById (postId: PostId) webLogId = backgroundTask {
log.LogTrace "Post.findById" log.LogTrace "Post.findById"
match! Document.findByIdAndWebLog<PostId, Post> Table.Post postId webLogId conn with match! conn.findFirstByFields<Post> Table.Post All [ idField postId; webLogField webLogId ] with
| Some post -> return Some { post with PriorPermalinks = [] } | Some post -> return Some { post with PriorPermalinks = [] }
| None -> return None | None -> return None
} }
@ -80,16 +77,14 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
/// Find a post by its permalink for the given web log (excluding revisions) /// Find a post by its permalink for the given web log (excluding revisions)
let findByPermalink (permalink: Permalink) webLogId = let findByPermalink (permalink: Permalink) webLogId =
log.LogTrace "Post.findByPermalink" log.LogTrace "Post.findByPermalink"
let linkParam = Field.EQ linkName (string permalink) let fields = [ webLogField webLogId; Field.EQ linkName (string permalink) ]
conn.customSingle conn.customSingle
$"""{Document.Query.selectByWebLog Table.Post} AND {Query.whereByField linkParam "@link"}""" (Query.byFields (Query.find Table.Post) All fields) (addFieldParams fields []) postWithoutLinks
(addFieldParam "@link" linkParam [ webLogParam webLogId ])
postWithoutLinks
/// Find a complete post by its ID for the given web log /// Find a complete post by its ID for the given web log
let findFullById postId webLogId = backgroundTask { let findFullById postId webLogId = backgroundTask {
log.LogTrace "Post.findFullById" log.LogTrace "Post.findFullById"
match! Document.findByIdAndWebLog<PostId, Post> Table.Post postId webLogId conn with match! conn.findFirstByFields<Post> Table.Post All [ idField postId; webLogField webLogId ] with
| Some post -> | Some post ->
let! post = appendPostRevisions post let! post = appendPostRevisions post
return Some post return Some post
@ -102,9 +97,11 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
match! findById postId webLogId with match! findById postId webLogId with
| Some _ -> | Some _ ->
do! conn.customNonQuery do! conn.customNonQuery
$"""DELETE FROM {Table.PostRevision} WHERE post_id = @id; $"""{Query.delete Table.PostRevision} WHERE post_id = @id;
DELETE FROM {Table.PostComment} {Query.byFields
WHERE {Query.whereByField (Field.EQ (nameof Comment.Empty.PostId) "") "@id"}; (Query.delete Table.PostComment)
Any
[ { Field.EQ (nameof Comment.Empty.PostId) postId with ParameterName = Some "@id" }]};
{Query.byId (Query.delete Table.Post) (string postId)}""" {Query.byId (Query.delete Table.Post) (string postId)}"""
[ idParam postId ] [ idParam postId ]
return true return true
@ -116,7 +113,7 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
log.LogTrace "Post.findCurrentPermalink" log.LogTrace "Post.findCurrentPermalink"
let linkSql, linkParams = inJsonArray Table.Post (nameof Post.Empty.PriorPermalinks) "link" permalinks let linkSql, linkParams = inJsonArray Table.Post (nameof Post.Empty.PriorPermalinks) "link" permalinks
conn.customSingle conn.customSingle
$"SELECT data ->> '{linkName}' AS permalink $"SELECT data->>'{linkName}' AS permalink
FROM {Table.Post} FROM {Table.Post}
WHERE {Document.Query.whereByWebLog} AND {linkSql}" WHERE {Document.Query.whereByWebLog} AND {linkSql}"
(webLogParam webLogId :: linkParams) (webLogParam webLogId :: linkParams)
@ -125,7 +122,7 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
/// Get all complete posts for the given web log /// Get all complete posts for the given web log
let findFullByWebLog webLogId = backgroundTask { let findFullByWebLog webLogId = backgroundTask {
log.LogTrace "Post.findFullByWebLog" log.LogTrace "Post.findFullByWebLog"
let! posts = Document.findByWebLog<Post> Table.Post webLogId conn let! posts = conn.findByFields<Post> Table.Post Any [ webLogField webLogId ]
let! withRevs = posts |> List.map appendPostRevisions |> Task.WhenAll let! withRevs = posts |> List.map appendPostRevisions |> Task.WhenAll
return List.ofArray withRevs return List.ofArray withRevs
} }
@ -146,7 +143,7 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
log.LogTrace "Post.findPageOfPosts" log.LogTrace "Post.findPageOfPosts"
conn.customList conn.customList
$"{postByWebLog} $"{postByWebLog}
ORDER BY {publishField} DESC NULLS FIRST, data ->> '{nameof Post.Empty.UpdatedOn}' ORDER BY {publishField} DESC NULLS FIRST, data->>'{nameof Post.Empty.UpdatedOn}'
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}" LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
[ webLogParam webLogId ] [ webLogParam webLogId ]
postWithoutText postWithoutText
@ -175,15 +172,16 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
/// Find the next newest and oldest post from a publish date for the given web log /// Find the next newest and oldest post from a publish date for the given web log
let findSurroundingPosts webLogId (publishedOn : Instant) = backgroundTask { let findSurroundingPosts webLogId (publishedOn : Instant) = backgroundTask {
log.LogTrace "Post.findSurroundingPosts" log.LogTrace "Post.findSurroundingPosts"
let parameters = [ webLogParam webLogId; sqlParam "@publishedOn" (instantParam publishedOn) ]
let! older = let! older =
conn.customSingle conn.customSingle
$"{publishedPostByWebLog} AND {publishField} < @publishedOn ORDER BY {publishField} DESC LIMIT 1" $"{publishedPostByWebLog} AND {publishField} < @publishedOn ORDER BY {publishField} DESC LIMIT 1"
[ webLogParam webLogId; SqliteParameter("@publishedOn", instantParam publishedOn) ] parameters
postWithoutLinks postWithoutLinks
let! newer = let! newer =
conn.customSingle conn.customSingle
$"{publishedPostByWebLog} AND {publishField} > @publishedOn ORDER BY {publishField} LIMIT 1" $"{publishedPostByWebLog} AND {publishField} > @publishedOn ORDER BY {publishField} LIMIT 1"
[ webLogParam webLogId; SqliteParameter("@publishedOn", instantParam publishedOn) ] parameters
postWithoutLinks postWithoutLinks
return older, newer return older, newer
} }

View File

@ -2,7 +2,6 @@ namespace MyWebLog.Data.SQLite
open BitBadger.Documents open BitBadger.Documents
open BitBadger.Documents.Sqlite open BitBadger.Documents.Sqlite
open BitBadger.Documents.Sqlite.Compat
open Microsoft.Data.Sqlite open Microsoft.Data.Sqlite
open Microsoft.Extensions.Logging open Microsoft.Extensions.Logging
open MyWebLog open MyWebLog
@ -12,9 +11,9 @@ open MyWebLog.Data
type SQLiteTagMapData(conn: SqliteConnection, log: ILogger) = type SQLiteTagMapData(conn: SqliteConnection, log: ILogger) =
/// Find a tag mapping by its ID for the given web log /// Find a tag mapping by its ID for the given web log
let findById tagMapId webLogId = let findById (tagMapId: TagMapId) webLogId =
log.LogTrace "TagMap.findById" log.LogTrace "TagMap.findById"
Document.findByIdAndWebLog<TagMapId, TagMap> Table.TagMap tagMapId webLogId conn conn.findFirstByFields<TagMap> Table.TagMap All [ idField tagMapId; webLogField webLogId ]
/// Delete a tag mapping for the given web log /// Delete a tag mapping for the given web log
let delete tagMapId webLogId = backgroundTask { let delete tagMapId webLogId = backgroundTask {
@ -29,21 +28,18 @@ type SQLiteTagMapData(conn: SqliteConnection, log: ILogger) =
/// Find a tag mapping by its URL value for the given web log /// Find a tag mapping by its URL value for the given web log
let findByUrlValue (urlValue: string) webLogId = let findByUrlValue (urlValue: string) webLogId =
log.LogTrace "TagMap.findByUrlValue" log.LogTrace "TagMap.findByUrlValue"
let urlParam = Field.EQ (nameof TagMap.Empty.UrlValue) urlValue conn.findFirstByFields<TagMap>
conn.customSingle Table.TagMap All [ webLogField webLogId; Field.EQ (nameof TagMap.Empty.UrlValue) urlValue ]
$"""{Document.Query.selectByWebLog Table.TagMap} AND {Query.whereByField urlParam "@urlValue"}"""
(addFieldParam "@urlValue" urlParam [ webLogParam webLogId ])
fromData<TagMap>
/// Get all tag mappings for the given web log /// Get all tag mappings for the given web log
let findByWebLog webLogId = let findByWebLog webLogId =
log.LogTrace "TagMap.findByWebLog" log.LogTrace "TagMap.findByWebLog"
Document.findByWebLog<TagMap> Table.TagMap webLogId conn conn.findByFields<TagMap> Table.TagMap Any [ webLogField webLogId ]
/// Find any tag mappings in a list of tags for the given web log /// Find any tag mappings in a list of tags for the given web log
let findMappingForTags (tags: string list) webLogId = let findMappingForTags (tags: string list) webLogId =
log.LogTrace "TagMap.findMappingForTags" log.LogTrace "TagMap.findMappingForTags"
let mapSql, mapParams = inClause $"AND data ->> '{nameof TagMap.Empty.Tag}'" "tag" id tags let mapSql, mapParams = inClause $"AND data->>'{nameof TagMap.Empty.Tag}'" "tag" id tags
conn.customList conn.customList
$"{Document.Query.selectByWebLog Table.TagMap} {mapSql}" $"{Document.Query.selectByWebLog Table.TagMap} {mapSql}"
(webLogParam webLogId :: mapParams) (webLogParam webLogId :: mapParams)

View File

@ -11,7 +11,7 @@ open MyWebLog.Data
type SQLiteThemeData(conn : SqliteConnection, log: ILogger) = type SQLiteThemeData(conn : SqliteConnection, log: ILogger) =
/// The JSON field for the theme ID /// The JSON field for the theme ID
let idField = $"data ->> '{nameof Theme.Empty.Id}'" let idField = $"data->>'{nameof Theme.Empty.Id}'"
/// Convert a document to a theme with no template text /// Convert a document to a theme with no template text
let withoutTemplateText (rdr: SqliteDataReader) = let withoutTemplateText (rdr: SqliteDataReader) =
@ -48,7 +48,7 @@ type SQLiteThemeData(conn : SqliteConnection, log: ILogger) =
match! findByIdWithoutText themeId with match! findByIdWithoutText themeId with
| Some _ -> | Some _ ->
do! conn.customNonQuery do! conn.customNonQuery
$"DELETE FROM {Table.ThemeAsset} WHERE theme_id = @id; $"{Query.delete Table.ThemeAsset} WHERE theme_id = @id;
{Query.byId (Query.delete Table.Theme) (string themeId)}" {Query.byId (Query.delete Table.Theme) (string themeId)}"
[ idParam themeId ] [ idParam themeId ]
return true return true
@ -87,7 +87,7 @@ type SQLiteThemeAssetData(conn : SqliteConnection, log: ILogger) =
/// Delete all assets for the given theme /// Delete all assets for the given theme
let deleteByTheme (themeId: ThemeId) = let deleteByTheme (themeId: ThemeId) =
log.LogTrace "ThemeAsset.deleteByTheme" log.LogTrace "ThemeAsset.deleteByTheme"
conn.customNonQuery $"DELETE FROM {Table.ThemeAsset} WHERE theme_id = @id" [ idParam themeId ] conn.customNonQuery $"{Query.delete Table.ThemeAsset} WHERE theme_id = @id" [ idParam themeId ]
/// Find a theme asset by its ID /// Find a theme asset by its ID
let findById assetId = let findById assetId =

View File

@ -2,7 +2,6 @@ namespace MyWebLog.Data.SQLite
open BitBadger.Documents open BitBadger.Documents
open BitBadger.Documents.Sqlite open BitBadger.Documents.Sqlite
open BitBadger.Documents.Sqlite.Compat
open Microsoft.Data.Sqlite open Microsoft.Data.Sqlite
open Microsoft.Extensions.Logging open Microsoft.Extensions.Logging
open MyWebLog open MyWebLog
@ -24,25 +23,25 @@ type SQLiteWebLogData(conn: SqliteConnection, log: ILogger) =
/// Delete a web log by its ID /// Delete a web log by its ID
let delete webLogId = let delete webLogId =
log.LogTrace "WebLog.delete" log.LogTrace "WebLog.delete"
let webLogMatches = Query.whereByField (Field.EQ "WebLogId" "") "@webLogId" let webLogMatches = Query.whereByFields Any [ { Field.EQ "WebLogId" "" with ParameterName = Some "@webLogId" } ]
let subQuery table = $"(SELECT data ->> 'Id' FROM {table} WHERE {webLogMatches})" let subQuery table = $"(SELECT data->>'Id' FROM {table} WHERE {webLogMatches})"
Custom.nonQuery Custom.nonQuery
$"""DELETE FROM {Table.PostComment} WHERE data ->> 'PostId' IN {subQuery Table.Post}; $"""{Query.delete Table.PostComment} WHERE data ->> 'PostId' IN {subQuery Table.Post};
DELETE FROM {Table.PostRevision} WHERE post_id IN {subQuery Table.Post}; {Query.delete Table.PostRevision} WHERE post_id IN {subQuery Table.Post};
DELETE FROM {Table.PageRevision} WHERE page_id IN {subQuery Table.Page}; {Query.delete Table.PageRevision} WHERE page_id IN {subQuery Table.Page};
DELETE FROM {Table.Post} WHERE {webLogMatches}; {Query.delete Table.Post} WHERE {webLogMatches};
DELETE FROM {Table.Page} WHERE {webLogMatches}; {Query.delete Table.Page} WHERE {webLogMatches};
DELETE FROM {Table.Category} WHERE {webLogMatches}; {Query.delete Table.Category} WHERE {webLogMatches};
DELETE FROM {Table.TagMap} WHERE {webLogMatches}; {Query.delete Table.TagMap} WHERE {webLogMatches};
DELETE FROM {Table.Upload} WHERE web_log_id = @webLogId; {Query.delete Table.Upload} WHERE web_log_id = @webLogId;
DELETE FROM {Table.WebLogUser} WHERE {webLogMatches}; {Query.delete Table.WebLogUser} WHERE {webLogMatches};
DELETE FROM {Table.WebLog} WHERE {Query.whereById "@webLogId"}""" {Query.delete Table.WebLog} WHERE {Query.whereById "@webLogId"}"""
[ webLogParam webLogId ] [ webLogParam webLogId ]
/// Find a web log by its host (URL base) /// Find a web log by its host (URL base)
let findByHost (url: string) = let findByHost (url: string) =
log.LogTrace "WebLog.findByHost" log.LogTrace "WebLog.findByHost"
conn.findFirstByField<WebLog> Table.WebLog (Field.EQ (nameof WebLog.Empty.UrlBase) url) conn.findFirstByFields<WebLog> Table.WebLog Any [ Field.EQ (nameof WebLog.Empty.UrlBase) url ]
/// Find a web log by its ID /// Find a web log by its ID
let findById webLogId = let findById webLogId =

View File

@ -2,7 +2,6 @@ namespace MyWebLog.Data.SQLite
open BitBadger.Documents open BitBadger.Documents
open BitBadger.Documents.Sqlite open BitBadger.Documents.Sqlite
open BitBadger.Documents.Sqlite.Compat
open Microsoft.Data.Sqlite open Microsoft.Data.Sqlite
open Microsoft.Extensions.Logging open Microsoft.Extensions.Logging
open MyWebLog open MyWebLog
@ -17,17 +16,18 @@ type SQLiteWebLogUserData(conn: SqliteConnection, log: ILogger) =
conn.insert<WebLogUser> Table.WebLogUser user conn.insert<WebLogUser> Table.WebLogUser user
/// Find a user by their ID for the given web log /// Find a user by their ID for the given web log
let findById userId webLogId = let findById (userId: WebLogUserId) webLogId =
log.LogTrace "WebLogUser.findById" log.LogTrace "WebLogUser.findById"
Document.findByIdAndWebLog<WebLogUserId, WebLogUser> Table.WebLogUser userId webLogId conn conn.findFirstByFields<WebLogUser> Table.WebLogUser All [ idField userId; webLogField webLogId ]
/// Delete a user if they have no posts or pages /// Delete a user if they have no posts or pages
let delete userId webLogId = backgroundTask { let delete userId webLogId = backgroundTask {
log.LogTrace "WebLogUser.delete" log.LogTrace "WebLogUser.delete"
match! findById userId webLogId with match! findById userId webLogId with
| Some _ -> | Some _ ->
let! pageCount = conn.countByField Table.Page (Field.EQ (nameof Page.Empty.AuthorId) (string userId)) let author = [ Field.EQ (nameof Page.Empty.AuthorId) (string userId) ]
let! postCount = conn.countByField Table.Post (Field.EQ (nameof Post.Empty.AuthorId) (string userId)) let! pageCount = conn.countByFields Table.Page Any author
let! postCount = conn.countByFields Table.Post Any author
if pageCount + postCount > 0 then if pageCount + postCount > 0 then
return Error "User has pages or posts; cannot delete" return Error "User has pages or posts; cannot delete"
else else
@ -39,24 +39,20 @@ type SQLiteWebLogUserData(conn: SqliteConnection, log: ILogger) =
/// Find a user by their e-mail address for the given web log /// Find a user by their e-mail address for the given web log
let findByEmail (email: string) webLogId = let findByEmail (email: string) webLogId =
log.LogTrace "WebLogUser.findByEmail" log.LogTrace "WebLogUser.findByEmail"
let emailParam = Field.EQ (nameof WebLogUser.Empty.Email) email conn.findFirstByFields
conn.customSingle Table.WebLogUser All [ webLogField webLogId; Field.EQ (nameof WebLogUser.Empty.Email) email ]
$"""{Document.Query.selectByWebLog Table.WebLogUser}
AND {Query.whereByField emailParam "@email"}"""
(addFieldParam "@email" emailParam [ webLogParam webLogId ])
fromData<WebLogUser>
/// Get all users for the given web log /// Get all users for the given web log
let findByWebLog webLogId = backgroundTask { let findByWebLog webLogId = backgroundTask {
log.LogTrace "WebLogUser.findByWebLog" log.LogTrace "WebLogUser.findByWebLog"
let! users = Document.findByWebLog<WebLogUser> Table.WebLogUser webLogId conn let! users = conn.findByFields<WebLogUser> Table.WebLogUser Any [ webLogField webLogId ]
return users |> List.sortBy _.PreferredName.ToLowerInvariant() return users |> List.sortBy _.PreferredName.ToLowerInvariant()
} }
/// Find the names of users by their IDs for the given web log /// Find the names of users by their IDs for the given web log
let findNames webLogId (userIds: WebLogUserId list) = let findNames webLogId (userIds: WebLogUserId list) =
log.LogTrace "WebLogUser.findNames" log.LogTrace "WebLogUser.findNames"
let nameSql, nameParams = inClause $"AND data ->> '{nameof WebLogUser.Empty.Id}'" "id" string userIds let nameSql, nameParams = inClause $"AND data->>'{nameof WebLogUser.Empty.Id}'" "id" string userIds
conn.customList conn.customList
$"{Document.Query.selectByWebLog Table.WebLogUser} {nameSql}" $"{Document.Query.selectByWebLog Table.WebLogUser} {nameSql}"
(webLogParam webLogId :: nameParams) (webLogParam webLogId :: nameParams)