WIP on SQLite doc library
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
namespace MyWebLog.Data.SQLite
|
||||
|
||||
open System.Threading.Tasks
|
||||
open BitBadger.Sqlite.FSharp.Documents
|
||||
open BitBadger.Sqlite.FSharp.Documents.WithConn
|
||||
open Microsoft.Data.Sqlite
|
||||
open Microsoft.Extensions.Logging
|
||||
open MyWebLog
|
||||
@@ -13,29 +15,24 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
|
||||
/// The name of the parent ID field
|
||||
let parentIdField = nameof Category.Empty.ParentId
|
||||
|
||||
/// Add a category
|
||||
let add (cat: Category) =
|
||||
log.LogTrace "Category.add"
|
||||
Document.insert conn ser Table.Category cat
|
||||
|
||||
/// Count all categories for the given web log
|
||||
let countAll webLogId =
|
||||
log.LogTrace "Category.countAll"
|
||||
Document.countByWebLog conn Table.Category webLogId
|
||||
Document.countByWebLog Table.Category webLogId conn
|
||||
|
||||
/// Count all top-level categories for the given web log
|
||||
let countTopLevel webLogId = backgroundTask {
|
||||
let countTopLevel webLogId =
|
||||
log.LogTrace "Category.countTopLevel"
|
||||
use cmd = conn.CreateCommand()
|
||||
cmd.CommandText <- $"{Query.countByWebLog} AND data ->> '{parentIdField}' IS NULL"
|
||||
addWebLogId cmd webLogId
|
||||
return! count cmd
|
||||
}
|
||||
Custom.scalar
|
||||
$"{Document.Query.countByWebLog} AND data ->> '{parentIdField}' IS NULL"
|
||||
[ webLogParam webLogId ]
|
||||
(fun rdr -> int (rdr.GetInt64(0)))
|
||||
conn
|
||||
|
||||
/// Find all categories for the given web log
|
||||
let findByWebLog webLogId =
|
||||
log.LogTrace "Category.findByWebLog"
|
||||
Document.findByWebLog<Category> conn ser Table.Category webLogId
|
||||
Document.findByWebLog<Category> Table.Category webLogId conn
|
||||
|
||||
/// Retrieve all categories for the given web log in a DotLiquid-friendly format
|
||||
let findAllForView webLogId = backgroundTask {
|
||||
@@ -53,104 +50,74 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
|
||||
|> Seq.append (Seq.singleton it.Id)
|
||||
|> List.ofSeq
|
||||
|> inJsonArray Table.Post (nameof Post.Empty.CategoryIds) "catId"
|
||||
use cmd = conn.CreateCommand()
|
||||
cmd.CommandText <- $"
|
||||
let query = $"""
|
||||
SELECT COUNT(DISTINCT data ->> '{nameof Post.Empty.Id}')
|
||||
FROM {Table.Post}
|
||||
WHERE {Query.whereByWebLog}
|
||||
AND data ->> '{nameof Post.Empty.Status}' = '{string Published}'
|
||||
AND {catSql}"
|
||||
addWebLogId cmd webLogId
|
||||
cmd.Parameters.AddRange catParams
|
||||
let! postCount = count cmd
|
||||
return it.Id, postCount
|
||||
WHERE {Document.Query.whereByWebLog}
|
||||
AND {Query.whereFieldEquals (nameof Post.Empty.Status) $"'{string Published}'"}
|
||||
AND {catSql}"""
|
||||
let! postCount = Custom.scalar query (webLogParam webLogId :: catParams) (_.GetInt64(0)) conn
|
||||
return it.Id, int postCount
|
||||
})
|
||||
|> Task.WhenAll
|
||||
return
|
||||
ordered
|
||||
|> Seq.map (fun cat ->
|
||||
{ cat with
|
||||
PostCount =
|
||||
counts
|
||||
|> Array.tryFind (fun c -> fst c = cat.Id)
|
||||
|> Option.map snd
|
||||
|> Option.defaultValue 0 })
|
||||
PostCount = defaultArg (counts |> Array.tryFind (fun c -> fst c = cat.Id) |> Option.map snd) 0
|
||||
})
|
||||
|> Array.ofSeq
|
||||
}
|
||||
|
||||
/// Find a category by its ID for the given web log
|
||||
let findById catId webLogId =
|
||||
log.LogTrace "Category.findById"
|
||||
Document.findByIdAndWebLog<CategoryId, Category> conn ser Table.Category catId webLogId
|
||||
Document.findByIdAndWebLog<CategoryId, Category> Table.Category catId webLogId conn
|
||||
|
||||
/// Delete a category
|
||||
let delete catId webLogId = backgroundTask {
|
||||
log.LogTrace "Category.delete"
|
||||
match! findById catId webLogId with
|
||||
| Some cat ->
|
||||
use cmd = conn.CreateCommand()
|
||||
// Reassign any children to the category's parent category
|
||||
cmd.CommandText <- $"SELECT COUNT(*) FROM {Table.Category} WHERE data ->> '{parentIdField}' = @parentId"
|
||||
addParam cmd "@parentId" (string catId)
|
||||
let! children = count cmd
|
||||
let! children = Count.byFieldEquals Table.Category parentIdField catId conn
|
||||
if children > 0 then
|
||||
cmd.CommandText <- $"
|
||||
UPDATE {Table.Category}
|
||||
SET data = json_set(data, '$.{parentIdField}', @newParentId)
|
||||
WHERE data ->> '{parentIdField}' = @parentId"
|
||||
addParam cmd "@newParentId" (maybe (cat.ParentId |> Option.map string))
|
||||
do! write cmd
|
||||
do! Update.partialByFieldEquals Table.Category parentIdField catId {| ParentId = cat.ParentId |} conn
|
||||
// Delete the category off all posts where it is assigned, and the category itself
|
||||
let catIdField = Post.Empty.CategoryIds
|
||||
cmd.CommandText <- $"
|
||||
SELECT data ->> '{Post.Empty.Id}' AS id, data -> '{catIdField}' AS cat_ids
|
||||
FROM {Table.Post}
|
||||
WHERE {Query.whereByWebLog}
|
||||
AND EXISTS
|
||||
(SELECT 1 FROM json_each({Table.Post}.data -> '{catIdField}') WHERE json_each.value = @id)"
|
||||
cmd.Parameters.Clear()
|
||||
addDocId cmd catId
|
||||
addWebLogId cmd webLogId
|
||||
use! postRdr = cmd.ExecuteReaderAsync()
|
||||
if postRdr.HasRows then
|
||||
let postIdAndCats =
|
||||
toList
|
||||
(fun rdr ->
|
||||
Map.getString "id" rdr, Utils.deserialize<string list> ser (Map.getString "cat_ids" rdr))
|
||||
postRdr
|
||||
do! postRdr.CloseAsync()
|
||||
for postId, cats in postIdAndCats do
|
||||
cmd.CommandText <- $"
|
||||
UPDATE {Table.Post}
|
||||
SET data = json_set(data, '$.{catIdField}', json(@catIds))
|
||||
WHERE {Query.whereById}"
|
||||
cmd.Parameters.Clear()
|
||||
addDocId cmd postId
|
||||
addParam cmd "@catIds" (cats |> List.filter (fun it -> it <> string catId) |> Utils.serialize ser)
|
||||
do! write cmd
|
||||
let! posts =
|
||||
Custom.list
|
||||
$"SELECT data ->> '{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 ]
|
||||
(fun rdr -> rdr.GetString(0), Utils.deserialize<string list> ser (rdr.GetString(1)))
|
||||
conn
|
||||
for postId, cats in posts do
|
||||
do! Update.partialById
|
||||
Table.Post postId {| CategoryIds = cats |> List.filter (fun it -> it <> string catId) |} conn
|
||||
do! Document.delete conn Table.Category catId
|
||||
return if children = 0 then CategoryDeleted else ReassignedChildCategories
|
||||
return if children = 0L then CategoryDeleted else ReassignedChildCategories
|
||||
| None -> return CategoryNotFound
|
||||
}
|
||||
|
||||
/// Save a category
|
||||
let save cat =
|
||||
log.LogTrace "Category.save"
|
||||
save<Category> Table.Category cat conn
|
||||
|
||||
/// Restore categories from a backup
|
||||
let restore cats = backgroundTask {
|
||||
for cat in cats do
|
||||
do! add cat
|
||||
}
|
||||
|
||||
/// Update a category
|
||||
let update (cat: Category) = backgroundTask {
|
||||
use cmd = conn.CreateCommand()
|
||||
cmd.CommandText <- $"{Query.updateById} AND {Query.whereByWebLog}"
|
||||
addDocId cmd cat.Id
|
||||
addDocParam cmd cat ser
|
||||
addWebLogId cmd cat.WebLogId
|
||||
do! write cmd
|
||||
log.LogTrace "Category.restore"
|
||||
for cat in cats do do! save cat
|
||||
}
|
||||
|
||||
interface ICategoryData with
|
||||
member _.Add cat = add cat
|
||||
member _.Add cat = save cat
|
||||
member _.CountAll webLogId = countAll webLogId
|
||||
member _.CountTopLevel webLogId = countTopLevel webLogId
|
||||
member _.FindAllForView webLogId = findAllForView webLogId
|
||||
@@ -158,4 +125,4 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
|
||||
member _.FindByWebLog webLogId = findByWebLog webLogId
|
||||
member _.Delete catId webLogId = delete catId webLogId
|
||||
member _.Restore cats = restore cats
|
||||
member _.Update cat = update cat
|
||||
member _.Update cat = save cat
|
||||
|
||||
Reference in New Issue
Block a user