From 1a50c68668d6bd0411bffe72f5619ce304c8363d Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sun, 31 Dec 2023 13:31:46 -0500 Subject: [PATCH] Migrate SQLite data to new library --- src/MyWebLog.Data/MyWebLog.Data.fsproj | 5 +- .../SQLite/SQLiteCategoryData.fs | 28 +++++---- src/MyWebLog.Data/SQLite/SQLiteHelpers.fs | 17 +++--- src/MyWebLog.Data/SQLite/SQLitePageData.fs | 37 +++++------- src/MyWebLog.Data/SQLite/SQLitePostData.fs | 48 +++++++--------- src/MyWebLog.Data/SQLite/SQLiteTagMapData.fs | 16 +++--- src/MyWebLog.Data/SQLite/SQLiteThemeData.fs | 39 ++++++------- src/MyWebLog.Data/SQLite/SQLiteUploadData.fs | 21 +++---- src/MyWebLog.Data/SQLite/SQLiteWebLogData.fs | 33 ++++++----- .../SQLite/SQLiteWebLogUserData.fs | 22 ++++--- src/MyWebLog.Data/SQLiteData.fs | 57 ++++++++++--------- src/MyWebLog/Program.fs | 9 +-- 12 files changed, 144 insertions(+), 188 deletions(-) diff --git a/src/MyWebLog.Data/MyWebLog.Data.fsproj b/src/MyWebLog.Data/MyWebLog.Data.fsproj index d2e5d31..5c8082d 100644 --- a/src/MyWebLog.Data/MyWebLog.Data.fsproj +++ b/src/MyWebLog.Data/MyWebLog.Data.fsproj @@ -4,12 +4,9 @@ - - - - + diff --git a/src/MyWebLog.Data/SQLite/SQLiteCategoryData.fs b/src/MyWebLog.Data/SQLite/SQLiteCategoryData.fs index 7ab5dbc..425bd8c 100644 --- a/src/MyWebLog.Data/SQLite/SQLiteCategoryData.fs +++ b/src/MyWebLog.Data/SQLite/SQLiteCategoryData.fs @@ -1,8 +1,8 @@ namespace MyWebLog.Data.SQLite open System.Threading.Tasks -open BitBadger.Sqlite.FSharp.Documents -open BitBadger.Sqlite.FSharp.Documents.WithConn +open BitBadger.Documents +open BitBadger.Documents.Sqlite open Microsoft.Data.Sqlite open Microsoft.Extensions.Logging open MyWebLog @@ -23,11 +23,10 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge /// Count all top-level categories for the given web log let countTopLevel webLogId = log.LogTrace "Category.countTopLevel" - Custom.scalar + conn.customScalar $"{Document.Query.countByWebLog} AND data ->> '{parentIdField}' IS NULL" [ webLogParam webLogId ] - (fun rdr -> int (rdr.GetInt64(0))) - conn + (toCount >> int) /// Find all categories for the given web log let findByWebLog webLogId = @@ -54,9 +53,9 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge SELECT COUNT(DISTINCT data ->> '{nameof Post.Empty.Id}') FROM {Table.Post} WHERE {Document.Query.whereByWebLog} - AND {Query.whereFieldEquals (nameof Post.Empty.Status) $"'{string Published}'"} + AND {Query.whereByField (nameof Post.Empty.Status) EQ $"'{string Published}'"} AND {catSql}""" - let! postCount = Custom.scalar query (webLogParam webLogId :: catParams) (_.GetInt64(0)) conn + let! postCount = conn.customScalar query (webLogParam webLogId :: catParams) toCount return it.Id, int postCount }) |> Task.WhenAll @@ -80,13 +79,13 @@ 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 = Count.byFieldEquals Table.Category parentIdField catId conn + let! children = conn.countByField Table.Category parentIdField EQ catId if children > 0 then - do! Update.partialByFieldEquals Table.Category parentIdField catId {| ParentId = cat.ParentId |} conn + do! conn.patchByField Table.Category parentIdField EQ catId {| ParentId = cat.ParentId |} // Delete the category off all posts where it is assigned, and the category itself let catIdField = Post.Empty.CategoryIds let! posts = - Custom.list + conn.customList $"SELECT data ->> '{Post.Empty.Id}', data -> '{catIdField}' FROM {Table.Post} WHERE {Document.Query.whereByWebLog} @@ -96,11 +95,10 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge WHERE json_each.value = @id)" [ idParam catId; webLogParam webLogId ] (fun rdr -> rdr.GetString(0), Utils.deserialize 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! Delete.byId Table.Category catId conn + do! conn.patchById + Table.Post postId {| CategoryIds = cats |> List.filter (fun it -> it <> string catId) |} + do! conn.deleteById Table.Category catId return if children = 0L then CategoryDeleted else ReassignedChildCategories | None -> return CategoryNotFound } @@ -108,7 +106,7 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge /// Save a category let save cat = log.LogTrace "Category.save" - save Table.Category cat conn + conn.save Table.Category cat /// Restore categories from a backup let restore cats = backgroundTask { diff --git a/src/MyWebLog.Data/SQLite/SQLiteHelpers.fs b/src/MyWebLog.Data/SQLite/SQLiteHelpers.fs index d11cd7a..b1008d3 100644 --- a/src/MyWebLog.Data/SQLite/SQLiteHelpers.fs +++ b/src/MyWebLog.Data/SQLite/SQLiteHelpers.fs @@ -222,17 +222,14 @@ module Map = let sqlParam name (value: obj) = SqliteParameter(name, value) -/// Create a document ID parameter -let idParam (key: 'TKey) = - sqlParam "@id" (string key) - /// Create a web log ID parameter let webLogParam (webLogId: WebLogId) = sqlParam "@webLogId" (string webLogId) -open BitBadger.Sqlite.FSharp.Documents -open BitBadger.Sqlite.FSharp.Documents.WithConn +open BitBadger.Documents +open BitBadger.Documents.Sqlite +open BitBadger.Documents.Sqlite.WithConn /// Functions for manipulating documents module Document = @@ -242,7 +239,7 @@ module Document = /// Fragment to add a web log ID condition to a WHERE clause (parameter @webLogId) let whereByWebLog = - Query.whereFieldEquals "WebLogId" "@webLogId" + Query.whereByField "WebLogId" EQ "@webLogId" /// A SELECT query to count documents for a given web log ID let countByWebLog table = @@ -250,7 +247,7 @@ module Document = /// A query to select from a table by the document's ID and its web log ID let selectByIdAndWebLog table = - $"{Query.Find.byFieldEquals table} AND {whereByWebLog}" + $"{Query.Find.byId table} AND {whereByWebLog}" /// A query to select from a table by its web log ID let selectByWebLog table = @@ -258,7 +255,7 @@ module Document = /// Count documents for the given web log ID let countByWebLog table (webLogId: WebLogId) conn = backgroundTask { - let! count = Count.byFieldEquals table "WebLogId" webLogId conn + let! count = Count.byField table "WebLogId" EQ webLogId conn return int count } @@ -268,7 +265,7 @@ module Document = /// Find documents for the given web log let findByWebLog<'TDoc> table (webLogId: WebLogId) conn = - Find.byFieldEquals<'TDoc> table "WebLogId" webLogId conn + Find.byField<'TDoc> table "WebLogId" EQ webLogId conn /// Functions to support revisions diff --git a/src/MyWebLog.Data/SQLite/SQLitePageData.fs b/src/MyWebLog.Data/SQLite/SQLitePageData.fs index ba62ea0..4427fa8 100644 --- a/src/MyWebLog.Data/SQLite/SQLitePageData.fs +++ b/src/MyWebLog.Data/SQLite/SQLitePageData.fs @@ -1,8 +1,8 @@ namespace MyWebLog.Data.SQLite open System.Threading.Tasks -open BitBadger.Sqlite.FSharp.Documents -open BitBadger.Sqlite.FSharp.Documents.WithConn +open BitBadger.Documents +open BitBadger.Documents.Sqlite open Microsoft.Data.Sqlite open Microsoft.Extensions.Logging open MyWebLog @@ -39,11 +39,10 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) = /// Get all pages for a web log (without text or revisions) let all webLogId = log.LogTrace "Page.all" - Custom.list + conn.customList $"{Query.selectFromTable Table.Page} WHERE {Document.Query.whereByWebLog} ORDER BY LOWER({titleField})" [ webLogParam webLogId ] (fun rdr -> { fromData rdr with Text = "" }) - conn /// Count all pages for the given web log let countAll webLogId = @@ -53,11 +52,10 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) = /// Count all pages shown in the page list for the given web log let countListed webLogId = log.LogTrace "Page.countListed" - Custom.scalar - $"""{Document.Query.countByWebLog} AND {Query.whereFieldEquals pgListName "'true'"}""" + conn.customScalar + $"""{Document.Query.countByWebLog} AND {Query.whereByField pgListName EQ "'true'"}""" [ webLogParam webLogId ] - (fun rdr -> int (rdr.GetInt64(0))) - conn + (toCount >> int) /// Find a page by its ID (without revisions) let findById pageId webLogId = @@ -80,10 +78,9 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) = log.LogTrace "Page.delete" match! findById pageId webLogId with | Some _ -> - do! Custom.nonQuery + do! conn.customNonQuery $"DELETE FROM {Table.PageRevision} WHERE page_id = @id; {Query.Delete.byId Table.Page}" [ idParam pageId ] - conn return true | None -> return false } @@ -91,23 +88,21 @@ 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" - Custom.single - $"""{Document.Query.selectByWebLog} AND {Query.whereFieldEquals linkName "@link"}""" + conn.customSingle + $"""{Document.Query.selectByWebLog} AND {Query.whereByField linkName EQ "@link"}""" [ webLogParam webLogId; SqliteParameter("@link", string permalink) ] fromData - conn /// 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 - Custom.single + conn.customSingle $"SELECT data ->> '{linkName}' AS permalink FROM {Table.Page} WHERE {Document.Query.whereByWebLog} AND {linkSql}" (webLogParam webLogId :: linkParams) Map.toPermalink - conn /// Get all complete pages for the given web log let findFullByWebLog webLogId = backgroundTask { @@ -120,27 +115,25 @@ 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" - Custom.list - $"""{Document.Query.selectByWebLog Table.Page} AND {Query.whereFieldEquals pgListName "'true'"} + conn.customList + $"""{Document.Query.selectByWebLog Table.Page} AND {Query.whereByField pgListName EQ "'true'"} ORDER BY LOWER({titleField})""" [ webLogParam webLogId ] (fun rdr -> { fromData rdr with Text = "" }) - conn /// Get a page of pages for the given web log (without revisions) let findPageOfPages webLogId pageNbr = log.LogTrace "Page.findPageOfPages" - Custom.list + conn.customList $"{Document.Query.selectByWebLog Table.Page} ORDER BY LOWER({titleField}) LIMIT @pageSize OFFSET @toSkip" [ webLogParam webLogId; SqliteParameter("@pageSize", 26); SqliteParameter("@toSkip", (pageNbr - 1) * 25) ] fromData - conn /// Save a page let save (page: Page) = backgroundTask { log.LogTrace "Page.update" let! oldPage = findFullById page.Id page.WebLogId - do! save Table.Page { page with Revisions = [] } conn + do! conn.save Table.Page { page with Revisions = [] } do! updatePageRevisions page.Id (match oldPage with Some p -> p.Revisions | None -> []) page.Revisions } @@ -155,7 +148,7 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) = log.LogTrace "Page.updatePriorPermalinks" match! findById pageId webLogId with | Some _ -> - do! Update.partialById Table.Page pageId {| PriorPermalinks = permalinks |} conn + do! conn.patchById Table.Page pageId {| PriorPermalinks = permalinks |} return true | None -> return false } diff --git a/src/MyWebLog.Data/SQLite/SQLitePostData.fs b/src/MyWebLog.Data/SQLite/SQLitePostData.fs index 5ada20c..7990818 100644 --- a/src/MyWebLog.Data/SQLite/SQLitePostData.fs +++ b/src/MyWebLog.Data/SQLite/SQLitePostData.fs @@ -1,8 +1,8 @@ namespace MyWebLog.Data.SQLite open System.Threading.Tasks -open BitBadger.Sqlite.FSharp.Documents -open BitBadger.Sqlite.FSharp.Documents.WithConn +open BitBadger.Documents +open BitBadger.Documents.Sqlite open Microsoft.Data.Sqlite open Microsoft.Extensions.Logging open MyWebLog @@ -34,7 +34,7 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) = let postByWebLog = Document.Query.selectByWebLog Table.Post /// The SELECT statement to retrieve published posts with a web log ID parameter - let publishedPostByWebLog = $"""{postByWebLog} AND {Query.whereFieldEquals statName $"'{string Published}'"}""" + let publishedPostByWebLog = $"""{postByWebLog} AND {Query.whereByField statName EQ $"'{string Published}'"}""" /// Update a post's revisions let updatePostRevisions (postId: PostId) oldRevs newRevs = @@ -46,11 +46,10 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) = /// Count posts in a status for the given web log let countByStatus (status: PostStatus) webLogId = log.LogTrace "Post.countByStatus" - Custom.scalar - $"""{Document.Query.countByWebLog} AND {Query.whereFieldEquals statName "@status"}""" + conn.customScalar + $"""{Document.Query.countByWebLog} AND {Query.whereByField statName EQ "@status"}""" [ webLogParam webLogId; SqliteParameter("@status", string status) ] - (fun rdr -> int (rdr.GetInt64(0))) - conn + (toCount >> int) /// Find a post by its ID for the given web log (excluding revisions) let findById postId webLogId = @@ -60,11 +59,10 @@ 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" - Custom.single - $"""{Document.Query.selectByWebLog Table.Post} AND {Query.whereFieldEquals linkName "@link"}""" + conn.customSingle + $"""{Document.Query.selectByWebLog Table.Post} AND {Query.whereByField linkName EQ "@link"}""" [ webLogParam webLogId; SqliteParameter("@link", string permalink) ] fromData - conn /// Find a complete post by its ID for the given web log let findFullById postId webLogId = backgroundTask { @@ -81,13 +79,12 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) = log.LogTrace "Post.delete" match! findById postId webLogId with | Some _ -> - do! Custom.nonQuery + do! conn.customNonQuery $"""DELETE FROM {Table.PostRevision} WHERE post_id = @id; DELETE FROM {Table.PostComment} - WHERE {Query.whereFieldEquals (nameof Comment.Empty.PostId) "@id"}; + WHERE {Query.whereByField (nameof Comment.Empty.PostId) EQ "@id"}; {Query.Delete.byId Table.Post}""" [ idParam postId ] - conn return true | None -> return false } @@ -96,13 +93,12 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) = let findCurrentPermalink (permalinks: Permalink list) webLogId = log.LogTrace "Post.findCurrentPermalink" let linkSql, linkParams = inJsonArray Table.Post (nameof Post.Empty.PriorPermalinks) "link" permalinks - Custom.single + conn.customSingle $"SELECT data ->> '{linkName}' AS permalink FROM {Table.Post} WHERE {Document.Query.whereByWebLog} AND {linkSql}" (webLogParam webLogId :: linkParams) Map.toPermalink - conn /// Get all complete posts for the given web log let findFullByWebLog webLogId = backgroundTask { @@ -116,63 +112,57 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) = let findPageOfCategorizedPosts webLogId (categoryIds: CategoryId list) pageNbr postsPerPage = log.LogTrace "Post.findPageOfCategorizedPosts" let catSql, catParams = inJsonArray Table.Post (nameof Post.Empty.CategoryIds) "catId" categoryIds - Custom.list + conn.customList $"{publishedPostByWebLog} AND {catSql} ORDER BY {publishField} DESC LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}" (webLogParam webLogId :: catParams) fromData - conn /// Get a page of posts for the given web log (excludes text and revisions) let findPageOfPosts webLogId pageNbr postsPerPage = log.LogTrace "Post.findPageOfPosts" - Custom.list + conn.customList $"{postByWebLog} ORDER BY {publishField} DESC NULLS FIRST, data ->> '{nameof Post.Empty.UpdatedOn}' LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}" [ webLogParam webLogId ] (fun rdr -> { fromData rdr with Text = "" }) - conn /// Get a page of published posts for the given web log (excludes revisions) let findPageOfPublishedPosts webLogId pageNbr postsPerPage = log.LogTrace "Post.findPageOfPublishedPosts" - Custom.list + conn.customList $"{publishedPostByWebLog} ORDER BY {publishField} DESC LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}" [ webLogParam webLogId ] fromData - conn /// 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 ] - Custom.list + conn.customList $"{publishedPostByWebLog} AND {tagSql} ORDER BY p.published_on DESC LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}" (webLogParam webLogId :: tagParams) fromData - conn /// Find the next newest and oldest post from a publish date for the given web log let findSurroundingPosts webLogId (publishedOn : Instant) = backgroundTask { log.LogTrace "Post.findSurroundingPosts" let! older = - Custom.single + conn.customSingle $"{publishedPostByWebLog} AND {publishField} < @publishedOn ORDER BY {publishField} DESC LIMIT 1" [ webLogParam webLogId; SqliteParameter("@publishedOn", instantParam publishedOn) ] fromData - conn let! newer = - Custom.single + conn.customSingle $"{publishedPostByWebLog} AND {publishField} > @publishedOn ORDER BY {publishField} LIMIT 1" [ webLogParam webLogId; SqliteParameter("@publishedOn", instantParam publishedOn) ] fromData - conn return older, newer } @@ -180,7 +170,7 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) = let save (post: Post) = backgroundTask { log.LogTrace "Post.save" let! oldPost = findFullById post.Id post.WebLogId - do! save Table.Post { post with Revisions = [] } conn + do! conn.save Table.Post { post with Revisions = [] } do! updatePostRevisions post.Id (match oldPost with Some p -> p.Revisions | None -> []) post.Revisions } @@ -194,7 +184,7 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) = let updatePriorPermalinks postId webLogId (permalinks: Permalink list) = backgroundTask { match! findById postId webLogId with | Some _ -> - do! Update.partialById Table.Post postId {| PriorPermalinks = permalinks |} conn + do! conn.patchById Table.Post postId {| PriorPermalinks = permalinks |} return true | None -> return false } diff --git a/src/MyWebLog.Data/SQLite/SQLiteTagMapData.fs b/src/MyWebLog.Data/SQLite/SQLiteTagMapData.fs index 0303034..50a4b04 100644 --- a/src/MyWebLog.Data/SQLite/SQLiteTagMapData.fs +++ b/src/MyWebLog.Data/SQLite/SQLiteTagMapData.fs @@ -1,7 +1,7 @@ namespace MyWebLog.Data.SQLite -open BitBadger.Sqlite.FSharp.Documents -open BitBadger.Sqlite.FSharp.Documents.WithConn +open BitBadger.Documents +open BitBadger.Documents.Sqlite open Microsoft.Data.Sqlite open Microsoft.Extensions.Logging open MyWebLog @@ -20,7 +20,7 @@ type SQLiteTagMapData(conn: SqliteConnection, log: ILogger) = log.LogTrace "TagMap.delete" match! findById tagMapId webLogId with | Some _ -> - do! Delete.byId Table.TagMap tagMapId conn + do! conn.deleteById Table.TagMap tagMapId return true | None -> return false } @@ -28,12 +28,11 @@ type SQLiteTagMapData(conn: SqliteConnection, log: ILogger) = /// Find a tag mapping by its URL value for the given web log let findByUrlValue (urlValue: string) webLogId = log.LogTrace "TagMap.findByUrlValue" - Custom.single + conn.customSingle $"""{Document.Query.selectByWebLog Table.TagMap} - AND {Query.whereFieldEquals (nameof TagMap.Empty.UrlValue) "@urlValue"}""" + AND {Query.whereByField (nameof TagMap.Empty.UrlValue) EQ "@urlValue"}""" [ webLogParam webLogId; SqliteParameter("@urlValue", urlValue) ] fromData - conn /// Get all tag mappings for the given web log let findByWebLog webLogId = @@ -44,16 +43,15 @@ type SQLiteTagMapData(conn: SqliteConnection, log: ILogger) = let findMappingForTags (tags: string list) webLogId = log.LogTrace "TagMap.findMappingForTags" let mapSql, mapParams = inClause $"AND data ->> '{nameof TagMap.Empty.Tag}'" "tag" id tags - Custom.list + conn.customList $"{Document.Query.selectByWebLog Table.TagMap} {mapSql}" (webLogParam webLogId :: mapParams) fromData - conn /// Save a tag mapping let save (tagMap: TagMap) = log.LogTrace "TagMap.save" - save Table.TagMap tagMap conn + conn.save Table.TagMap tagMap /// Restore tag mappings from a backup let restore tagMaps = backgroundTask { diff --git a/src/MyWebLog.Data/SQLite/SQLiteThemeData.fs b/src/MyWebLog.Data/SQLite/SQLiteThemeData.fs index f5d50a1..cb41ddc 100644 --- a/src/MyWebLog.Data/SQLite/SQLiteThemeData.fs +++ b/src/MyWebLog.Data/SQLite/SQLiteThemeData.fs @@ -1,7 +1,7 @@ namespace MyWebLog.Data.SQLite -open BitBadger.Sqlite.FSharp.Documents -open BitBadger.Sqlite.FSharp.Documents.WithConn +open BitBadger.Documents +open BitBadger.Documents.Sqlite open Microsoft.Data.Sqlite open Microsoft.Extensions.Logging open MyWebLog @@ -25,36 +25,34 @@ type SQLiteThemeData(conn : SqliteConnection, log: ILogger) = /// Retrieve all themes (except 'admin'; excludes template text) let all () = log.LogTrace "Theme.all" - Custom.list + conn.customList $"{Query.selectFromTable Table.Theme} WHERE {idField} <> 'admin' ORDER BY {idField}" [] withoutTemplateText - conn /// Does a given theme exist? let exists (themeId: ThemeId) = log.LogTrace "Theme.exists" - Exists.byId Table.Theme themeId conn + conn.existsById Table.Theme themeId /// Find a theme by its ID let findById themeId = log.LogTrace "Theme.findById" - Find.byId Table.Theme themeId conn + conn.findById Table.Theme themeId /// Find a theme by its ID (excludes the text of templates) let findByIdWithoutText (themeId: ThemeId) = log.LogTrace "Theme.findByIdWithoutText" - Custom.single (Query.Find.byId Table.Theme) [ idParam themeId ] withoutTemplateText conn + conn.customSingle (Query.Find.byId Table.Theme) [ idParam themeId ] withoutTemplateText /// Delete a theme by its ID let delete themeId = backgroundTask { log.LogTrace "Theme.delete" match! findByIdWithoutText themeId with | Some _ -> - do! Custom.nonQuery + do! conn.customNonQuery $"DELETE FROM {Table.ThemeAsset} WHERE theme_id = @id; {Query.Delete.byId Table.Theme}" [ idParam themeId ] - conn return true | None -> return false } @@ -62,7 +60,7 @@ type SQLiteThemeData(conn : SqliteConnection, log: ILogger) = /// Save a theme let save (theme: Theme) = log.LogTrace "Theme.save" - save Table.Theme theme conn + conn.save Table.Theme theme interface IThemeData with member _.All() = all () @@ -86,44 +84,41 @@ type SQLiteThemeAssetData(conn : SqliteConnection, log: ILogger) = /// Get all theme assets (excludes data) let all () = log.LogTrace "ThemeAsset.all" - Custom.list $"SELECT theme_id, path, updated_on FROM {Table.ThemeAsset}" [] (Map.toThemeAsset false) conn + conn.customList $"SELECT theme_id, path, updated_on FROM {Table.ThemeAsset}" [] (Map.toThemeAsset false) /// Delete all assets for the given theme let deleteByTheme (themeId: ThemeId) = log.LogTrace "ThemeAsset.deleteByTheme" - Custom.nonQuery $"DELETE FROM {Table.ThemeAsset} WHERE theme_id = @id" [ idParam themeId ] conn + conn.customNonQuery $"DELETE FROM {Table.ThemeAsset} WHERE theme_id = @id" [ idParam themeId ] /// Find a theme asset by its ID let findById assetId = log.LogTrace "ThemeAsset.findById" - Custom.single + conn.customSingle $"SELECT *, ROWID FROM {Table.ThemeAsset} WHERE theme_id = @id AND path = @path" (assetIdParams assetId) (Map.toThemeAsset true) - conn /// Get theme assets for the given theme (excludes data) let findByTheme (themeId: ThemeId) = log.LogTrace "ThemeAsset.findByTheme" - Custom.list + conn.customList $"SELECT theme_id, path, updated_on FROM {Table.ThemeAsset} WHERE theme_id = @id" [ idParam themeId ] (Map.toThemeAsset false) - conn /// Get theme assets for the given theme let findByThemeWithData (themeId: ThemeId) = log.LogTrace "ThemeAsset.findByThemeWithData" - Custom.list + conn.customList $"SELECT *, ROWID FROM {Table.ThemeAsset} WHERE theme_id = @id" [ idParam themeId ] (Map.toThemeAsset true) - conn /// Save a theme asset let save (asset: ThemeAsset) = backgroundTask { log.LogTrace "ThemeAsset.save" - do! Custom.nonQuery + do! conn.customNonQuery $"INSERT INTO {Table.ThemeAsset} ( theme_id, path, updated_on, data ) VALUES ( @@ -134,14 +129,12 @@ type SQLiteThemeAssetData(conn : SqliteConnection, log: ILogger) = [ sqlParam "@updatedOn" (instantParam asset.UpdatedOn) sqlParam "@dataLength" asset.Data.Length yield! (assetIdParams asset.Id) ] - conn let! rowId = - Custom.scalar + conn.customScalar $"SELECT ROWID FROM {Table.ThemeAsset} WHERE theme_id = @id AND path = @path" (assetIdParams asset.Id) - (_.GetInt64(0)) - conn + _.GetInt64(0) use dataStream = new MemoryStream(asset.Data) use blobStream = new SqliteBlob(conn, Table.ThemeAsset, "data", rowId) do! dataStream.CopyToAsync blobStream diff --git a/src/MyWebLog.Data/SQLite/SQLiteUploadData.fs b/src/MyWebLog.Data/SQLite/SQLiteUploadData.fs index e6bc5e0..fbc2ce9 100644 --- a/src/MyWebLog.Data/SQLite/SQLiteUploadData.fs +++ b/src/MyWebLog.Data/SQLite/SQLiteUploadData.fs @@ -1,7 +1,7 @@ namespace MyWebLog.Data.SQLite open System.IO -open BitBadger.Sqlite.FSharp.Documents.WithConn +open BitBadger.Documents.Sqlite open Microsoft.Data.Sqlite open Microsoft.Extensions.Logging open MyWebLog @@ -13,7 +13,7 @@ type SQLiteUploadData(conn: SqliteConnection, log: ILogger) = /// Save an uploaded file let add (upload: Upload) = backgroundTask { log.LogTrace "Upload.add" - do! Custom.nonQuery + do! conn.customNonQuery $"INSERT INTO {Table.Upload} ( id, web_log_id, path, updated_on, data ) VALUES ( @@ -24,9 +24,8 @@ type SQLiteUploadData(conn: SqliteConnection, log: ILogger) = sqlParam "@path" (string upload.Path) sqlParam "@updatedOn" (instantParam upload.UpdatedOn) sqlParam "@dataLength" upload.Data.Length ] - conn let! rowId = - Custom.scalar $"SELECT ROWID FROM {Table.Upload} WHERE id = @id" [ idParam upload.Id ] (_.GetInt64(0)) conn + conn.customScalar $"SELECT ROWID FROM {Table.Upload} WHERE id = @id" [ idParam upload.Id ] _.GetInt64(0) use dataStream = new MemoryStream(upload.Data) use blobStream = new SqliteBlob(conn, Table.Upload, "data", rowId) do! dataStream.CopyToAsync blobStream @@ -36,14 +35,13 @@ type SQLiteUploadData(conn: SqliteConnection, log: ILogger) = let delete (uploadId: UploadId) webLogId = backgroundTask { log.LogTrace "Upload.delete" let! upload = - Custom.single + conn.customSingle $"SELECT id, web_log_id, path, updated_on FROM {Table.Upload} WHERE id = @id AND web_log_id = @webLogId" [ idParam uploadId; webLogParam webLogId ] (Map.toUpload false) - conn match upload with | Some up -> - do! Custom.nonQuery $"DELETE FROM {Table.Upload} WHERE id = @id" [ idParam up.Id ] conn + do! conn.customNonQuery $"DELETE FROM {Table.Upload} WHERE id = @id" [ idParam up.Id ] return Ok (string up.Path) | None -> return Error $"Upload ID {string uploadId} not found" } @@ -51,29 +49,26 @@ type SQLiteUploadData(conn: SqliteConnection, log: ILogger) = /// Find an uploaded file by its path for the given web log let findByPath (path: string) webLogId = log.LogTrace "Upload.findByPath" - Custom.single + conn.customSingle $"SELECT *, ROWID FROM {Table.Upload} WHERE web_log_id = @webLogId AND path = @path" [ webLogParam webLogId; sqlParam "@path" path ] (Map.toUpload true) - conn /// Find all uploaded files for the given web log (excludes data) let findByWebLog webLogId = log.LogTrace "Upload.findByWebLog" - Custom.list + conn.customList $"SELECT id, web_log_id, path, updated_on FROM {Table.Upload} WHERE web_log_id = @webLogId" [ webLogParam webLogId ] (Map.toUpload false) - conn /// Find all uploaded files for the given web log let findByWebLogWithData webLogId = log.LogTrace "Upload.findByWebLogWithData" - Custom.list + conn.customList $"SELECT *, ROWID FROM {Table.Upload} WHERE web_log_id = @webLogId" [ webLogParam webLogId ] (Map.toUpload true) - conn /// Restore uploads from a backup let restore uploads = backgroundTask { diff --git a/src/MyWebLog.Data/SQLite/SQLiteWebLogData.fs b/src/MyWebLog.Data/SQLite/SQLiteWebLogData.fs index e308e94..a9d1d17 100644 --- a/src/MyWebLog.Data/SQLite/SQLiteWebLogData.fs +++ b/src/MyWebLog.Data/SQLite/SQLiteWebLogData.fs @@ -1,7 +1,7 @@ namespace MyWebLog.Data.SQLite -open BitBadger.Sqlite.FSharp.Documents -open BitBadger.Sqlite.FSharp.Documents.WithConn +open BitBadger.Documents +open BitBadger.Documents.Sqlite open Microsoft.Data.Sqlite open Microsoft.Extensions.Logging open MyWebLog @@ -13,56 +13,55 @@ type SQLiteWebLogData(conn: SqliteConnection, log: ILogger) = /// Add a web log let add webLog = log.LogTrace "WebLog.add" - insert Table.WebLog webLog conn + conn.insert Table.WebLog webLog /// Retrieve all web logs let all () = log.LogTrace "WebLog.all" - Find.all Table.WebLog conn + conn.findAll Table.WebLog /// Delete a web log by its ID let delete webLogId = log.LogTrace "WebLog.delete" let subQuery table = - $"""(SELECT data ->> 'Id' FROM {table} WHERE {Query.whereFieldEquals "WebLogId" "@webLogId"}""" + $"""(SELECT data ->> 'Id' FROM {table} WHERE {Query.whereByField "WebLogId" EQ "@webLogId"}""" Custom.nonQuery $"""DELETE FROM {Table.PostComment} WHERE data ->> 'PostId' IN {subQuery Table.Post}; DELETE FROM {Table.PostRevision} WHERE post_id IN {subQuery Table.Post}; DELETE FROM {Table.PageRevision} WHERE page_id IN {subQuery Table.Page}; - DELETE FROM {Table.Post} WHERE {Query.whereFieldEquals "WebLogId" "@webLogId"}; - DELETE FROM {Table.Page} WHERE {Query.whereFieldEquals "WebLogId" "@webLogId"}; - DELETE FROM {Table.Category} WHERE {Query.whereFieldEquals "WebLogId" "@webLogId"}; - DELETE FROM {Table.TagMap} WHERE {Query.whereFieldEquals "WebLogId" "@webLogId"}; - DELETE FROM {Table.Upload} WHERE web_log_id = @id; - DELETE FROM {Table.WebLogUser} WHERE {Query.whereFieldEquals "WebLogId" "@webLogId"}; + DELETE FROM {Table.Post} WHERE {Query.whereByField "WebLogId" EQ "@webLogId"}; + DELETE FROM {Table.Page} WHERE {Query.whereByField "WebLogId" EQ "@webLogId"}; + DELETE FROM {Table.Category} WHERE {Query.whereByField "WebLogId" EQ "@webLogId"}; + DELETE FROM {Table.TagMap} WHERE {Query.whereByField "WebLogId" EQ "@webLogId"}; + DELETE FROM {Table.Upload} WHERE web_log_id = @webLogId; + DELETE FROM {Table.WebLogUser} WHERE {Query.whereByField "WebLogId" EQ "@webLogId"}; DELETE FROM {Table.WebLog} WHERE {Query.whereById "@webLogId"}""" [ webLogParam webLogId ] - conn /// Find a web log by its host (URL base) let findByHost (url: string) = log.LogTrace "WebLog.findByHost" - Find.firstByFieldEquals Table.WebLog (nameof WebLog.Empty.UrlBase) url conn + conn.findFirstByField Table.WebLog (nameof WebLog.Empty.UrlBase) EQ url /// Find a web log by its ID let findById webLogId = log.LogTrace "WebLog.findById" - Find.byId Table.WebLog webLogId conn + conn.findById Table.WebLog webLogId /// Update redirect rules for a web log let updateRedirectRules (webLog: WebLog) = log.LogTrace "WebLog.updateRedirectRules" - Update.partialById Table.WebLog webLog.Id {| RedirectRules = webLog.RedirectRules |} conn + conn.patchById Table.WebLog webLog.Id {| RedirectRules = webLog.RedirectRules |} /// Update RSS options for a web log let updateRssOptions (webLog: WebLog) = log.LogTrace "WebLog.updateRssOptions" - Update.partialById Table.WebLog webLog.Id {| Rss = webLog.Rss |} conn + conn.patchById Table.WebLog webLog.Id {| Rss = webLog.Rss |} /// Update settings for a web log let updateSettings (webLog: WebLog) = log.LogTrace "WebLog.updateSettings" - Update.full Table.WebLog webLog.Id webLog conn + conn.updateById Table.WebLog webLog.Id webLog interface IWebLogData with member _.Add webLog = add webLog diff --git a/src/MyWebLog.Data/SQLite/SQLiteWebLogUserData.fs b/src/MyWebLog.Data/SQLite/SQLiteWebLogUserData.fs index 35771e7..61cae3b 100644 --- a/src/MyWebLog.Data/SQLite/SQLiteWebLogUserData.fs +++ b/src/MyWebLog.Data/SQLite/SQLiteWebLogUserData.fs @@ -1,7 +1,7 @@ namespace MyWebLog.Data.SQLite -open BitBadger.Sqlite.FSharp.Documents -open BitBadger.Sqlite.FSharp.Documents.WithConn +open BitBadger.Documents +open BitBadger.Documents.Sqlite open Microsoft.Data.Sqlite open Microsoft.Extensions.Logging open MyWebLog @@ -20,12 +20,12 @@ type SQLiteWebLogUserData(conn: SqliteConnection, log: ILogger) = log.LogTrace "WebLogUser.delete" match! findById userId webLogId with | Some _ -> - let! pageCount = Count.byFieldEquals Table.Page (nameof Page.Empty.AuthorId) (string userId) conn - let! postCount = Count.byFieldEquals Table.Post (nameof Post.Empty.AuthorId) (string userId) conn + let! pageCount = conn.countByField Table.Page (nameof Page.Empty.AuthorId) EQ (string userId) + let! postCount = conn.countByField Table.Post (nameof Post.Empty.AuthorId) EQ (string userId) if pageCount + postCount > 0 then return Error "User has pages or posts; cannot delete" else - do! Delete.byId Table.WebLogUser userId conn + do! conn.deleteById Table.WebLogUser userId return Ok true | None -> return Error "User does not exist" } @@ -33,12 +33,11 @@ type SQLiteWebLogUserData(conn: SqliteConnection, log: ILogger) = /// Find a user by their e-mail address for the given web log let findByEmail (email: string) webLogId = log.LogTrace "WebLogUser.findByEmail" - Custom.single + conn.customSingle $"""{Document.Query.selectByWebLog Table.WebLogUser} - AND {Query.whereFieldEquals (nameof WebLogUser.Empty.Email) "@email"}""" + AND {Query.whereByField (nameof WebLogUser.Empty.Email) EQ "@email"}""" [ webLogParam webLogId; sqlParam "@email" email ] fromData - conn /// Get all users for the given web log let findByWebLog webLogId = backgroundTask { @@ -51,18 +50,17 @@ type SQLiteWebLogUserData(conn: SqliteConnection, log: ILogger) = let findNames webLogId (userIds: WebLogUserId list) = log.LogTrace "WebLogUser.findNames" let nameSql, nameParams = inClause "AND data ->> 'Id'" "id" string userIds - Custom.list + conn.customList $"{Document.Query.selectByWebLog Table.WebLogUser} {nameSql}" (webLogParam webLogId :: nameParams) (fun rdr -> let user = fromData rdr { Name = string user.Id; Value = user.DisplayName }) - conn /// Save a user let save user = log.LogTrace "WebLogUser.update" - save Table.WebLogUser user conn + conn.save Table.WebLogUser user /// Restore users from a backup let restore users = backgroundTask { @@ -74,7 +72,7 @@ type SQLiteWebLogUserData(conn: SqliteConnection, log: ILogger) = let setLastSeen userId webLogId = backgroundTask { log.LogTrace "WebLogUser.setLastSeen" match! findById userId webLogId with - | Some _ -> do! Update.partialById Table.WebLogUser userId {| LastSeenOn = Noda.now () |} conn + | Some _ -> do! conn.patchById Table.WebLogUser userId {| LastSeenOn = Noda.now () |} | None -> () } diff --git a/src/MyWebLog.Data/SQLiteData.fs b/src/MyWebLog.Data/SQLiteData.fs index 4386d09..e312395 100644 --- a/src/MyWebLog.Data/SQLiteData.fs +++ b/src/MyWebLog.Data/SQLiteData.fs @@ -1,8 +1,8 @@ namespace MyWebLog.Data open System.Threading.Tasks -open BitBadger.Sqlite.FSharp.Documents -open BitBadger.Sqlite.FSharp.Documents.WithConn +open BitBadger.Documents +open BitBadger.Documents.Sqlite open Microsoft.Data.Sqlite open Microsoft.Extensions.Logging open MyWebLog @@ -16,13 +16,13 @@ type SQLiteData(conn: SqliteConnection, log: ILogger, ser: JsonSeria /// Create tables (and their associated indexes) if they do not exist let ensureTables () = backgroundTask { - let! tables = Custom.list "SELECT name FROM sqlite_master WHERE type = 'table'" [] (_.GetString(0)) conn + let! tables = conn.customList "SELECT name FROM sqlite_master WHERE type = 'table'" [] _.GetString(0) let needsTable table = not (List.contains table tables) let jsonTable table = - $"{Definition.createTable table}; {Definition.createKey table}" + $"{Query.Definition.ensureTable table}; {Query.Definition.ensureKey table}" let tasks = seq { @@ -41,21 +41,23 @@ type SQLiteData(conn: SqliteConnection, log: ILogger, ser: JsonSeria // Category table if needsTable Table.Category then - $"{jsonTable Table.Category}; - CREATE INDEX idx_{Table.Category}_web_log ON {Table.Category} ((data ->> 'WebLogId'))" + $"""{jsonTable Table.Category}; + {Query.Definition.ensureIndexOn Table.Category "web_log" [ nameof Category.Empty.WebLogId ]}""" // Web log user table if needsTable Table.WebLogUser then - $"{jsonTable Table.WebLogUser}; - CREATE INDEX idx_{Table.WebLogUser}_email - ON {Table.WebLogUser} ((data ->> 'WebLogId'), (data ->> 'Email'))" + $"""{jsonTable Table.WebLogUser}; + {Query.Definition.ensureIndexOn + Table.WebLogUser + "email" + [ nameof WebLogUser.Empty.WebLogId; nameof WebLogUser.Empty.Email ]}""" // Page tables if needsTable Table.Page then - $"{jsonTable Table.Page}; - CREATE INDEX idx_{Table.Page}_author ON {Table.Page} ((data ->> 'AuthorId')); - CREATE INDEX idx_{Table.Page}_permalink - ON {Table.Page} ((data ->> 'WebLogId'), (data ->> 'Permalink'))" + $"""{jsonTable Table.Page}; + {Query.Definition.ensureIndexOn Table.Page "author" [ nameof Page.Empty.AuthorId ]}; + {Query.Definition.ensureIndexOn + Table.Page "permalink" [ nameof Page.Empty.WebLogId; nameof Page.Empty.Permalink ]}""" if needsTable Table.PageRevision then $"CREATE TABLE {Table.PageRevision} ( page_id TEXT NOT NULL, @@ -65,12 +67,14 @@ type SQLiteData(conn: SqliteConnection, log: ILogger, ser: JsonSeria // Post tables if needsTable Table.Post then - $"{jsonTable Table.Post}; - CREATE INDEX idx_{Table.Post}_author ON {Table.Post} ((data ->> 'AuthorId')); - CREATE INDEX idx_{Table.Post}_status - ON {Table.Post} ((data ->> 'WebLogId'), (data ->> 'Status'), (data ->> 'UpdatedOn')); - CREATE INDEX idx_{Table.Post}_permalink - ON {Table.Post} ((data ->> 'WebLogId'), (data ->> 'Permalink'))" + $"""{jsonTable Table.Post}; + {Query.Definition.ensureIndexOn Table.Post "author" [ nameof Post.Empty.AuthorId ]}; + {Query.Definition.ensureIndexOn + Table.Post "permalink" [ nameof Post.Empty.WebLogId; nameof Post.Empty.Permalink ]}; + {Query.Definition.ensureIndexOn + Table.Post + "status" + [ nameof Post.Empty.WebLogId; nameof Post.Empty.Status; nameof Post.Empty.UpdatedOn ]}""" // TODO: index categories by post? if needsTable Table.PostRevision then $"CREATE TABLE {Table.PostRevision} ( @@ -79,13 +83,14 @@ type SQLiteData(conn: SqliteConnection, log: ILogger, ser: JsonSeria revision_text TEXT NOT NULL, PRIMARY KEY (post_id, as_of))" if needsTable Table.PostComment then - $"{jsonTable Table.PostComment}; - CREATE INDEX idx_{Table.PostComment}_post ON {Table.PostComment} ((data ->> 'PostId'))" + $"""{jsonTable Table.PostComment}; + {Query.Definition.ensureIndexOn Table.PostComment "post" [ nameof Comment.Empty.PostId ]}""" // Tag map table if needsTable Table.TagMap then - $"{jsonTable Table.TagMap}; - CREATE INDEX idx_{Table.TagMap}_tag ON {Table.TagMap} ((data ->> 'WebLogId'), (data ->> 'UrlValue'))" + $"""{jsonTable Table.TagMap}; + {Query.Definition.ensureIndexOn + Table.TagMap "url" [ nameof TagMap.Empty.WebLogId; nameof TagMap.Empty.UrlValue ]}""" // Uploaded file table if needsTable Table.Upload then @@ -104,7 +109,7 @@ type SQLiteData(conn: SqliteConnection, log: ILogger, ser: JsonSeria } |> Seq.map (fun sql -> log.LogInformation $"""Creating {(sql.Replace("IF NOT EXISTS ", "").Split ' ')[2]} table...""" - Custom.nonQuery sql [] conn) + conn.customNonQuery sql []) let! _ = Task.WhenAll tasks () @@ -112,7 +117,7 @@ type SQLiteData(conn: SqliteConnection, log: ILogger, ser: JsonSeria /// Set the database version to the specified version let setDbVersion version = - Custom.nonQuery $"DELETE FROM {Table.DbVersion}; INSERT INTO {Table.DbVersion} VALUES ('%s{version}')" [] conn + conn.customNonQuery $"DELETE FROM {Table.DbVersion}; INSERT INTO {Table.DbVersion} VALUES ('%s{version}')" [] /// Implement the changes between v2-rc1 and v2-rc2 let migrateV2Rc1ToV2Rc2 () = backgroundTask { @@ -467,6 +472,6 @@ type SQLiteData(conn: SqliteConnection, log: ILogger, ser: JsonSeria member _.StartUp () = backgroundTask { do! ensureTables () - let! version = Custom.single $"SELECT id FROM {Table.DbVersion}" [] (_.GetString(0)) conn + let! version = conn.customSingle $"SELECT id FROM {Table.DbVersion}" [] _.GetString(0) do! migrate version } diff --git a/src/MyWebLog/Program.fs b/src/MyWebLog/Program.fs index e8c7380..884fe55 100644 --- a/src/MyWebLog/Program.fs +++ b/src/MyWebLog/Program.fs @@ -56,9 +56,6 @@ open MyWebLog.Data open Newtonsoft.Json open Npgsql -// The SQLite document library -module Sqlite = BitBadger.Sqlite.FSharp.Documents - /// Logic to obtain a data connection and implementation based on configured values module DataImplementation = @@ -176,11 +173,7 @@ let main args = | :? SQLiteData -> // ADO.NET connections are designed to work as per-request instantiation let cfg = sp.GetRequiredService() - let _ = - builder.Services.AddScoped(fun sp -> - let conn = Sqlite.Configuration.dbConn () - conn.OpenAsync() |> Async.AwaitTask |> Async.RunSynchronously - conn) + let _ = builder.Services.AddScoped(fun sp -> Sqlite.Configuration.dbConn ()) let _ = builder.Services.AddScoped() // Use SQLite for caching as well let cachePath = defaultArg (Option.ofObj (cfg.GetConnectionString "SQLiteCachePath")) "./session.db"