Update for doc lib v4-rc4

This commit is contained in:
2024-09-17 08:05:30 -04:00
parent 95be82cc84
commit 0032d15c0a
16 changed files with 119 additions and 188 deletions

View File

@@ -25,7 +25,7 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
/// Count all top-level categories for the given web log
let countTopLevel webLogId = backgroundTask {
log.LogTrace "Category.countTopLevel"
let! count = conn.countByFields Table.Category All [ webLogField webLogId; Field.NEX parentIdField ]
let! count = conn.countByFields Table.Category All [ webLogField webLogId; Field.NotExists parentIdField ]
return int count
}
@@ -43,20 +43,20 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
ordered
|> Seq.map (fun it -> backgroundTask {
// Parent category post counts include posts in subcategories
let catSql, catParams =
let childCats =
ordered
|> Seq.filter (fun cat -> cat.ParentNames |> Array.contains it.Name)
|> Seq.map _.Id
|> Seq.append (Seq.singleton it.Id)
|> List.ofSeq
|> inJsonArray Table.Post (nameof Post.Empty.CategoryIds) "catId"
let query = $"""
SELECT COUNT(DISTINCT data->>'{nameof Post.Empty.Id}')
FROM {Table.Post}
WHERE {Document.Query.whereByWebLog}
AND data->>'{nameof Post.Empty.Status}' = '{string Published}'
AND {catSql}"""
let! postCount = conn.customScalar query (webLogParam webLogId :: catParams) toCount
|> Seq.map box
let fields =
[ webLogField webLogId
Field.Equal (nameof Post.Empty.Status) (string Published)
Field.InArray (nameof Post.Empty.CategoryIds) Table.Post childCats ]
let query =
(Query.statementWhere (Query.count Table.Post) (Query.whereByFields All fields))
.Replace("(*)", $"(DISTINCT data->>'{nameof Post.Empty.Id}')")
let! postCount = conn.customScalar query (addFieldParams fields []) toCount
return it.Id, int postCount
})
|> Task.WhenAll
@@ -80,24 +80,22 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
match! findById catId webLogId with
| Some cat ->
// Reassign any children to the category's parent category
let! children = conn.countByFields Table.Category Any [ Field.EQ parentIdField (string catId) ]
let! children = conn.countByFields Table.Category Any [ Field.Equal parentIdField (string catId) ]
if children > 0L then
let parent = [ Field.EQ parentIdField (string catId) ]
let parent = [ Field.Equal parentIdField (string catId) ]
match cat.ParentId with
| Some _ -> do! conn.patchByFields Table.Category Any parent {| ParentId = cat.ParentId |}
| None -> do! conn.removeFieldsByFields Table.Category Any parent [ parentIdField ]
// Delete the category off all posts where it is assigned, and the category itself
let catIdField = nameof Post.Empty.CategoryIds
let fields = [ webLogField webLogId; Field.InArray catIdField Table.Post [ string catId ] ]
let query =
(Query.statementWhere (Query.find Table.Post) (Query.whereByFields All fields))
.Replace("SELECT data", $"SELECT data->>'{nameof Post.Empty.Id}', data->'{catIdField}'")
let! posts =
conn.customList
$"SELECT data->>'{nameof Post.Empty.Id}', data->'{catIdField}'
FROM {Table.Post}
WHERE {Document.Query.whereByWebLog}
AND EXISTS
(SELECT 1
FROM json_each({Table.Post}.data->'{catIdField}')
WHERE json_each.value = @id)"
[ idParam catId; webLogParam webLogId ]
query
(addFieldParams fields [])
(fun rdr -> rdr.GetString 0, Utils.deserialize<string list> ser (rdr.GetString 1))
for postId, cats in posts do
do! conn.patchById

View File

@@ -82,39 +82,6 @@ let instantParam =
let maybeInstant =
Option.map instantParam >> maybe
/// Create the SQL and parameters for an EXISTS applied to a JSON array
let inJsonArray<'T> table jsonField paramName (items: 'T list) =
if List.isEmpty items then "", []
else
let mutable idx = 0
items
|> List.skip 1
|> List.fold (fun (itemS, itemP) it ->
idx <- idx + 1
$"{itemS}, @%s{paramName}{idx}", (SqliteParameter($"@%s{paramName}{idx}", string it) :: itemP))
(Seq.ofList items
|> Seq.map (fun it -> $"(@%s{paramName}0", [ SqliteParameter($"@%s{paramName}0", string it) ])
|> Seq.head)
|> function
sql, ps ->
$"EXISTS (SELECT 1 FROM json_each(%s{table}.data, '$.%s{jsonField}') WHERE value IN {sql}))", ps
/// Create the SQL and parameters for an IN clause
let inClause<'T> colNameAndPrefix paramName (valueFunc: 'T -> string) (items: 'T list) =
if List.isEmpty items then "", []
else
let mutable idx = 0
items
|> List.skip 1
|> List.fold (fun (itemS, itemP) it ->
idx <- idx + 1
$"{itemS}, @%s{paramName}{idx}", (SqliteParameter ($"@%s{paramName}{idx}", valueFunc it) :: itemP))
(Seq.ofList items
|> Seq.map (fun it ->
$"%s{colNameAndPrefix} IN (@%s{paramName}0", [ SqliteParameter ($"@%s{paramName}0", valueFunc it) ])
|> Seq.head)
|> function sql, ps -> $"{sql})", ps
/// Functions to map domain items from a data reader
module Map =
@@ -219,8 +186,6 @@ module Map =
open BitBadger.Documents
open BitBadger.Documents.Sqlite
open BitBadger.Documents.Sqlite.WithConn
/// Create a named parameter
let sqlParam name (value: obj) =
@@ -232,26 +197,15 @@ let webLogParam (webLogId: WebLogId) =
/// Create a field for an ID value
let idField<'T> (idValue: 'T) =
{ Field.EQ "Id" (string idValue) with ParameterName = Some "@id" }
{ Field.Equal "Id" (string idValue) with ParameterName = Some "@id" }
/// Create a web log field
let webLogField (webLogId: WebLogId) =
{ Field.EQ "WebLogId" (string webLogId) with ParameterName = Some "@webLogId" }
{ Field.Equal "WebLogId" (string webLogId) with ParameterName = Some "@webLogId" }
/// Functions for manipulating documents
module Document =
/// Queries to assist with document manipulation
module Query =
/// Fragment to add a web log ID condition to a WHERE clause (parameter @webLogId)
let whereByWebLog =
Query.whereByFields Any [ { Field.EQ "WebLogId" "" with ParameterName = Some "@webLogId" } ]
/// A query to select from a table by its web log ID
let selectByWebLog table =
Query.statementWhere (Query.find table) whereByWebLog
open BitBadger.Documents.Sqlite
open BitBadger.Documents.Sqlite.WithConn
/// Functions to support revisions
module Revisions =
@@ -270,7 +224,7 @@ module Revisions =
$"SELECT pr.*
FROM %s{revTable} pr
INNER JOIN %s{entityTable} p ON p.data->>'Id' = pr.{entityTable}_id
WHERE p.{Document.Query.whereByWebLog}
WHERE p.{Query.whereByFields Any [ webLogField webLogId ]}
ORDER BY as_of DESC"
[ webLogParam webLogId ]
(fun rdr -> keyFunc (Map.getString $"{entityTable}_id" rdr), Map.toRevision rdr)

View File

@@ -68,7 +68,7 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
/// Count all pages shown in the page list for the given web log
let countListed webLogId = backgroundTask {
log.LogTrace "Page.countListed"
let! count = conn.countByFields Table.Page All [ webLogField webLogId; Field.EQ pgListName true ]
let! count = conn.countByFields Table.Page All [ webLogField webLogId; Field.Equal pgListName true ]
return int count
}
@@ -107,20 +107,20 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
/// Find a page by its permalink for the given web log
let findByPermalink (permalink: Permalink) webLogId =
log.LogTrace "Page.findByPermalink"
let fields = [ webLogField webLogId; Field.EQ linkName (string permalink) ]
let fields = [ webLogField webLogId; Field.Equal linkName (string permalink) ]
conn.customSingle
(Query.byFields (Query.find Table.Page) All fields) (addFieldParams fields []) pageWithoutLinks
/// Find the current permalink within a set of potential prior permalinks for the given web log
let findCurrentPermalink (permalinks: Permalink list) webLogId =
log.LogTrace "Page.findCurrentPermalink"
let linkSql, linkParams = inJsonArray Table.Page (nameof Page.Empty.PriorPermalinks) "link" permalinks
conn.customSingle
$"SELECT data->>'{linkName}' AS permalink
FROM {Table.Page}
WHERE {Document.Query.whereByWebLog} AND {linkSql}"
(webLogParam webLogId :: linkParams)
Map.toPermalink
let fields =
[ webLogField webLogId
Field.InArray (nameof Page.Empty.PriorPermalinks) Table.Page (List.map (string >> box) permalinks) ]
let query =
(Query.statementWhere (Query.find Table.Page) (Query.whereByFields All fields))
.Replace("SELECT data", $"SELECT data->>'{linkName}' AS permalink")
conn.customSingle query (addFieldParams fields []) Map.toPermalink
/// Get all complete pages for the given web log
let findFullByWebLog webLogId = backgroundTask {
@@ -133,7 +133,7 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
/// Get all listed pages for the given web log (without revisions or text)
let findListed webLogId =
log.LogTrace "Page.findListed"
let fields = [ webLogField webLogId; Field.EQ pgListName true ]
let fields = [ webLogField webLogId; Field.Equal pgListName true ]
conn.customList
(sortedPages fields) (addFieldParams fields []) (fun rdr -> { fromData<Page> rdr with Text = "" })

View File

@@ -31,7 +31,10 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
}
/// The SELECT statement to retrieve posts with a web log ID parameter
let postByWebLog = Document.Query.selectByWebLog Table.Post
let postByWebLog =
Query.statementWhere
(Query.find Table.Post)
(Query.whereByFields Any [ { Field.Equal "WebLogId" "" with ParameterName = Some "@webLogId" } ])
/// Return a post with no revisions or prior permalinks
let postWithoutLinks rdr =
@@ -62,7 +65,7 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
/// Count posts in a status for the given web log
let countByStatus (status: PostStatus) webLogId = backgroundTask {
log.LogTrace "Post.countByStatus"
let! count = conn.countByFields Table.Post All [ webLogField webLogId; Field.EQ statName (string status) ]
let! count = conn.countByFields Table.Post All [ webLogField webLogId; Field.Equal statName (string status) ]
return int count
}
@@ -77,7 +80,7 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
/// Find a post by its permalink for the given web log (excluding revisions)
let findByPermalink (permalink: Permalink) webLogId =
log.LogTrace "Post.findByPermalink"
let fields = [ webLogField webLogId; Field.EQ linkName (string permalink) ]
let fields = [ webLogField webLogId; Field.Equal linkName (string permalink) ]
conn.customSingle
(Query.byFields (Query.find Table.Post) All fields) (addFieldParams fields []) postWithoutLinks
@@ -111,13 +114,13 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
/// Find the current permalink from a list of potential prior permalinks for the given web log
let findCurrentPermalink (permalinks: Permalink list) webLogId =
log.LogTrace "Post.findCurrentPermalink"
let linkSql, linkParams = inJsonArray Table.Post (nameof Post.Empty.PriorPermalinks) "link" permalinks
conn.customSingle
$"SELECT data->>'{linkName}' AS permalink
FROM {Table.Post}
WHERE {Document.Query.whereByWebLog} AND {linkSql}"
(webLogParam webLogId :: linkParams)
Map.toPermalink
let fields =
[ webLogField webLogId
Field.InArray (nameof Post.Empty.PriorPermalinks) Table.Post (List.map (string >> box) permalinks) ]
let query =
(Query.statementWhere (Query.find Table.Post) (Query.whereByFields All fields))
.Replace("SELECT data", $"SELECT data->>'{linkName}' AS permalink")
conn.customSingle query (addFieldParams fields []) Map.toPermalink
/// Get all complete posts for the given web log
let findFullByWebLog webLogId = backgroundTask {
@@ -130,12 +133,12 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
/// Get a page of categorized posts for the given web log (excludes revisions)
let findPageOfCategorizedPosts webLogId (categoryIds: CategoryId list) pageNbr postsPerPage =
log.LogTrace "Post.findPageOfCategorizedPosts"
let catSql, catParams = inJsonArray Table.Post (nameof Post.Empty.CategoryIds) "catId" categoryIds
let catIdField = Field.InArray (nameof Post.Empty.CategoryIds) Table.Post (List.map (string >> box) categoryIds)
conn.customList
$"""{publishedPostByWebLog} AND {catSql}
$"""{publishedPostByWebLog} AND {Query.whereByFields Any [ catIdField ]}
{Query.orderBy [ Field.Named $"{publishName} DESC" ] SQLite}
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"""
(webLogParam webLogId :: catParams)
(addFieldParams [ webLogField webLogId; catIdField ] [])
postWithoutLinks
/// Get a page of posts for the given web log (excludes text and revisions)
@@ -162,12 +165,12 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
/// Get a page of tagged posts for the given web log (excludes revisions)
let findPageOfTaggedPosts webLogId (tag : string) pageNbr postsPerPage =
log.LogTrace "Post.findPageOfTaggedPosts"
let tagSql, tagParams = inJsonArray Table.Post (nameof Post.Empty.Tags) "tag" [ tag ]
let tagField = Field.InArray (nameof Post.Empty.Tags) Table.Post [ tag ]
conn.customList
$"""{publishedPostByWebLog} AND {tagSql}
$"""{publishedPostByWebLog} AND {Query.whereByFields Any [ tagField ]}
{Query.orderBy [ Field.Named $"{publishName} DESC" ] SQLite}
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"""
(webLogParam webLogId :: tagParams)
(addFieldParams [ webLogField webLogId; tagField ] [])
postWithoutLinks
/// Find the next newest and oldest post from a publish date for the given web log
@@ -176,8 +179,8 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
let adjacent op order =
let fields = [
webLogField webLogId
Field.EQ (nameof Post.Empty.Status) (string Published)
(if op = "<" then Field.LT else Field.GT) publishName (instantParam publishedOn)
Field.Equal (nameof Post.Empty.Status) (string Published)
(if op = "<" then Field.Less else Field.Greater) publishName (instantParam publishedOn)
]
conn.customSingle
(Query.byFields (Query.find Table.Post) All fields

View File

@@ -29,7 +29,7 @@ type SQLiteTagMapData(conn: SqliteConnection, log: ILogger) =
let findByUrlValue (urlValue: string) webLogId =
log.LogTrace "TagMap.findByUrlValue"
conn.findFirstByFields<TagMap>
Table.TagMap All [ webLogField webLogId; Field.EQ (nameof TagMap.Empty.UrlValue) urlValue ]
Table.TagMap All [ webLogField webLogId; Field.Equal (nameof TagMap.Empty.UrlValue) urlValue ]
/// Get all tag mappings for the given web log
let findByWebLog webLogId =
@@ -39,11 +39,8 @@ type SQLiteTagMapData(conn: SqliteConnection, log: ILogger) =
/// Find any tag mappings in a list of tags for the given web log
let findMappingForTags (tags: string list) webLogId =
log.LogTrace "TagMap.findMappingForTags"
let mapSql, mapParams = inClause $"AND data->>'{nameof TagMap.Empty.Tag}'" "tag" id tags
conn.customList
$"{Document.Query.selectByWebLog Table.TagMap} {mapSql}"
(webLogParam webLogId :: mapParams)
fromData<TagMap>
conn.findByFields<TagMap>
Table.TagMap All [ webLogField webLogId; Field.In (nameof TagMap.Empty.Tag) (List.map box tags) ]
/// Save a tag mapping
let save (tagMap: TagMap) =

View File

@@ -23,7 +23,8 @@ type SQLiteWebLogData(conn: SqliteConnection, log: ILogger) =
/// Delete a web log by its ID
let delete webLogId =
log.LogTrace "WebLog.delete"
let webLogMatches = Query.whereByFields Any [ { Field.EQ "WebLogId" "" with ParameterName = Some "@webLogId" } ]
let webLogMatches =
Query.whereByFields Any [ { Field.Equal "WebLogId" "" with ParameterName = Some "@webLogId" } ]
let subQuery table = $"(SELECT data->>'Id' FROM {table} WHERE {webLogMatches})"
Custom.nonQuery
$"""{Query.delete Table.PostComment} WHERE data ->> 'PostId' IN {subQuery Table.Post};
@@ -41,7 +42,7 @@ type SQLiteWebLogData(conn: SqliteConnection, log: ILogger) =
/// Find a web log by its host (URL base)
let findByHost (url: string) =
log.LogTrace "WebLog.findByHost"
conn.findFirstByFields<WebLog> Table.WebLog Any [ Field.EQ (nameof WebLog.Empty.UrlBase) url ]
conn.findFirstByFields<WebLog> Table.WebLog Any [ Field.Equal (nameof WebLog.Empty.UrlBase) url ]
/// Find a web log by its ID
let findById webLogId =

View File

@@ -25,7 +25,7 @@ type SQLiteWebLogUserData(conn: SqliteConnection, log: ILogger) =
log.LogTrace "WebLogUser.delete"
match! findById userId webLogId with
| Some _ ->
let author = [ Field.EQ (nameof Page.Empty.AuthorId) (string userId) ]
let author = [ Field.Equal (nameof Page.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
@@ -40,7 +40,7 @@ type SQLiteWebLogUserData(conn: SqliteConnection, log: ILogger) =
let findByEmail (email: string) webLogId =
log.LogTrace "WebLogUser.findByEmail"
conn.findFirstByFields
Table.WebLogUser All [ webLogField webLogId; Field.EQ (nameof WebLogUser.Empty.Email) email ]
Table.WebLogUser All [ webLogField webLogId; Field.Equal (nameof WebLogUser.Empty.Email) email ]
/// Get all users for the given web log
let findByWebLog webLogId = backgroundTask {
@@ -52,10 +52,11 @@ type SQLiteWebLogUserData(conn: SqliteConnection, log: ILogger) =
/// Find the names of users by their IDs for the given web log
let findNames webLogId (userIds: WebLogUserId list) =
log.LogTrace "WebLogUser.findNames"
let nameSql, nameParams = inClause $"AND data->>'{nameof WebLogUser.Empty.Id}'" "id" string userIds
let fields = [ webLogField webLogId; Field.In (nameof WebLogUser.Empty.Id) (List.map (string >> box) userIds) ]
let query = Query.statementWhere (Query.find Table.WebLogUser) (Query.whereByFields All fields)
conn.customList
$"{Document.Query.selectByWebLog Table.WebLogUser} {nameSql}"
(webLogParam webLogId :: nameParams)
query
(addFieldParams fields [])
(fun rdr ->
let user = fromData<WebLogUser> rdr
{ Name = string user.Id; Value = user.DisplayName })