From e09638d8fdef41ed7014273f87d3944552531221 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Wed, 8 Feb 2023 20:09:57 -0500 Subject: [PATCH] WIP on PostgreSQL doc implementation --- src/MyWebLog.Data/Converters.fs | 1 + src/MyWebLog.Data/Postgres/PostgresCache.fs | 18 ++-- .../Postgres/PostgresCategoryData.fs | 33 +++--- src/MyWebLog.Data/Postgres/PostgresHelpers.fs | 25 ++--- .../Postgres/PostgresPageData.fs | 61 +++++------ .../Postgres/PostgresPostData.fs | 100 ++++++++++-------- .../Postgres/PostgresTagMapData.fs | 31 +++--- .../Postgres/PostgresThemeData.fs | 18 +++- .../Postgres/PostgresUploadData.fs | 13 ++- .../Postgres/PostgresWebLogData.fs | 12 ++- .../Postgres/PostgresWebLogUserData.fs | 31 +++--- src/MyWebLog.Data/PostgresData.fs | 17 +-- src/MyWebLog/Program.fs | 8 +- src/MyWebLog/appsettings.json | 3 +- 14 files changed, 203 insertions(+), 168 deletions(-) diff --git a/src/MyWebLog.Data/Converters.fs b/src/MyWebLog.Data/Converters.fs index 82ff4c7..52a132c 100644 --- a/src/MyWebLog.Data/Converters.fs +++ b/src/MyWebLog.Data/Converters.fs @@ -165,6 +165,7 @@ module Json = Converters = ser.Converters, DefaultValueHandling = ser.DefaultValueHandling, DateFormatHandling = ser.DateFormatHandling, + DateParseHandling = ser.DateParseHandling, MetadataPropertyHandling = ser.MetadataPropertyHandling, MissingMemberHandling = ser.MissingMemberHandling, NullValueHandling = ser.NullValueHandling, diff --git a/src/MyWebLog.Data/Postgres/PostgresCache.fs b/src/MyWebLog.Data/Postgres/PostgresCache.fs index 14c359d..8b706a6 100644 --- a/src/MyWebLog.Data/Postgres/PostgresCache.fs +++ b/src/MyWebLog.Data/Postgres/PostgresCache.fs @@ -39,15 +39,17 @@ module private Helpers = typedParam "expireAt" +open Npgsql + /// A distributed cache implementation in PostgreSQL used to handle sessions for myWebLog -type DistributedCache (connStr : string) = +type DistributedCache (dataSource : NpgsqlDataSource) = // ~~~ INITIALIZATION ~~~ do task { let! exists = - Sql.connect connStr + Sql.fromDataSource dataSource |> Sql.query $" SELECT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = 'session') @@ -55,7 +57,7 @@ type DistributedCache (connStr : string) = |> Sql.executeRowAsync Map.toExists if not exists then let! _ = - Sql.connect connStr + Sql.fromDataSource dataSource |> Sql.query "CREATE TABLE session ( id TEXT NOT NULL PRIMARY KEY, @@ -74,7 +76,7 @@ type DistributedCache (connStr : string) = let getEntry key = backgroundTask { let idParam = "@id", Sql.string key let! tryEntry = - Sql.connect connStr + Sql.fromDataSource dataSource |> Sql.query "SELECT * FROM session WHERE id = @id" |> Sql.parameters [ idParam ] |> Sql.executeAsync (fun row -> @@ -97,7 +99,7 @@ type DistributedCache (connStr : string) = else true, { entry with ExpireAt = now.Plus slideExp } if needsRefresh then let! _ = - Sql.connect connStr + Sql.fromDataSource dataSource |> Sql.query "UPDATE session SET expire_at = @expireAt WHERE id = @id" |> Sql.parameters [ expireParam item.ExpireAt; idParam ] |> Sql.executeNonQueryAsync @@ -114,7 +116,7 @@ type DistributedCache (connStr : string) = let now = getNow () if lastPurge.Plus (Duration.FromMinutes 30L) < now then let! _ = - Sql.connect connStr + Sql.fromDataSource dataSource |> Sql.query "DELETE FROM session WHERE expire_at < @expireAt" |> Sql.parameters [ expireParam now ] |> Sql.executeNonQueryAsync @@ -124,7 +126,7 @@ type DistributedCache (connStr : string) = /// Remove a cache entry let removeEntry key = backgroundTask { let! _ = - Sql.connect connStr + Sql.fromDataSource dataSource |> Sql.query "DELETE FROM session WHERE id = @id" |> Sql.parameters [ "@id", Sql.string key ] |> Sql.executeNonQueryAsync @@ -149,7 +151,7 @@ type DistributedCache (connStr : string) = let slide = Duration.FromHours 1 now.Plus slide, Some slide, None let! _ = - Sql.connect connStr + Sql.fromDataSource dataSource |> Sql.query "INSERT INTO session ( id, payload, expire_at, sliding_expiration, absolute_expiration diff --git a/src/MyWebLog.Data/Postgres/PostgresCategoryData.fs b/src/MyWebLog.Data/Postgres/PostgresCategoryData.fs index e06f45b..ce0bda7 100644 --- a/src/MyWebLog.Data/Postgres/PostgresCategoryData.fs +++ b/src/MyWebLog.Data/Postgres/PostgresCategoryData.fs @@ -1,5 +1,6 @@ namespace MyWebLog.Data.Postgres +open Microsoft.Extensions.Logging open MyWebLog open MyWebLog.Data open Npgsql @@ -7,26 +8,26 @@ open Npgsql.FSharp open Npgsql.FSharp.Documents /// PostgreSQL myWebLog category data implementation -type PostgresCategoryData (source : NpgsqlDataSource) = +type PostgresCategoryData (source : NpgsqlDataSource, log : ILogger) = /// Count all categories for the given web log let countAll webLogId = + log.LogTrace "Category.countAll" Sql.fromDataSource source |> Query.countByContains Table.Category (webLogDoc webLogId) /// Count all top-level categories for the given web log let countTopLevel webLogId = + log.LogTrace "Category.countTopLevel" Sql.fromDataSource source |> Query.countByContains Table.Category {| webLogDoc webLogId with ParentId = None |} /// Retrieve all categories for the given web log in a DotLiquid-friendly format let findAllForView webLogId = backgroundTask { + log.LogTrace "Category.findAllForView" let! cats = Sql.fromDataSource source - |> Sql.query $""" - {Query.selectFromTable Table.Category} - WHERE {Query.whereDataContains "@criteria"} - ORDER BY LOWER(data->>'{nameof Category.empty.Name}')""" + |> Sql.query $"{selectWithCriteria Table.Category} ORDER BY LOWER(data ->> '{nameof Category.empty.Name}')" |> Sql.parameters [ webLogContains webLogId ] |> Sql.executeAsync fromData let ordered = Utils.orderByHierarchy cats None None [] @@ -40,18 +41,19 @@ type PostgresCategoryData (source : NpgsqlDataSource) = |> Seq.map (fun cat -> cat.Id) |> Seq.append (Seq.singleton it.Id) |> List.ofSeq - |> jsonArrayInClause (nameof Post.empty.CategoryIds) id + |> arrayContains (nameof Post.empty.CategoryIds) id let postCount = Sql.fromDataSource source |> Sql.query $""" SELECT COUNT(DISTINCT id) AS {countName} FROM {Table.Post} WHERE {Query.whereDataContains "@criteria"} - AND ({catIdSql})""" - |> Sql.parameters ( - ("@criteria", - Query.jsonbDocParam {| webLogDoc webLogId with Status = PostStatus.toString Published |}) - :: catIdParams) + AND {catIdSql}""" + |> Sql.parameters + [ "@criteria", + Query.jsonbDocParam {| webLogDoc webLogId with Status = PostStatus.toString Published |} + catIdParams + ] |> Sql.executeRowAsync Map.toCount |> Async.AwaitTask |> Async.RunSynchronously @@ -70,10 +72,12 @@ type PostgresCategoryData (source : NpgsqlDataSource) = } /// Find a category by its ID for the given web log let findById catId webLogId = + log.LogTrace "Category.findById" Document.findByIdAndWebLog source Table.Category catId CategoryId.toString webLogId /// Find all categories for the given web log let findByWebLog webLogId = + log.LogTrace "Category.findByWebLog" Document.findByWebLog source Table.Category webLogId /// Create parameters for a category insert / update @@ -82,6 +86,7 @@ type PostgresCategoryData (source : NpgsqlDataSource) = /// Delete a category let delete catId webLogId = backgroundTask { + log.LogTrace "Category.delete" match! findById catId webLogId with | Some cat -> // Reassign any children to the category's parent category @@ -100,8 +105,8 @@ type PostgresCategoryData (source : NpgsqlDataSource) = // Delete the category off all posts where it is assigned let! posts = Sql.fromDataSource source - |> Sql.query $"SELECT data FROM {Table.Post} WHERE data->'{nameof Post.empty.CategoryIds}' ? @id" - |> Sql.parameters [ "@id", Sql.jsonb (CategoryId.toString catId) ] + |> Sql.query $"SELECT data FROM {Table.Post} WHERE data -> '{nameof Post.empty.CategoryIds}' @> @id" + |> Sql.parameters [ "@id", Query.jsonbDocParam [| CategoryId.toString catId |] ] |> Sql.executeAsync fromData if not (List.isEmpty posts) then let! _ = @@ -125,11 +130,13 @@ type PostgresCategoryData (source : NpgsqlDataSource) = /// Save a category let save (cat : Category) = backgroundTask { + log.LogTrace "Category.save" do! Sql.fromDataSource source |> Query.save Table.Category (CategoryId.toString cat.Id) cat } /// Restore categories from a backup let restore cats = backgroundTask { + log.LogTrace "Category.restore" let! _ = Sql.fromDataSource source |> Sql.executeTransactionAsync [ diff --git a/src/MyWebLog.Data/Postgres/PostgresHelpers.fs b/src/MyWebLog.Data/Postgres/PostgresHelpers.fs index 8802d09..573236c 100644 --- a/src/MyWebLog.Data/Postgres/PostgresHelpers.fs +++ b/src/MyWebLog.Data/Postgres/PostgresHelpers.fs @@ -86,6 +86,10 @@ let countName = "the_count" /// The name of the field to select to be able to use Map.toExists let existsName = "does_exist" +/// A SQL string to select data from a table with the given JSON document contains criteria +let selectWithCriteria tableName = + $"""{Query.selectFromTable tableName} WHERE {Query.whereDataContains "@criteria"}""" + /// 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 "", [] @@ -102,22 +106,11 @@ let inClause<'T> colNameAndPrefix paramName (valueFunc: 'T -> string) (items : ' |> Seq.head) |> function sql, ps -> $"{sql})", ps -/// Create the SQL and parameters for the array-in-JSON equivalent of an IN clause -let jsonArrayInClause<'T> name (valueFunc : 'T -> string) (items : 'T list) = - if List.isEmpty items then "TRUE = FALSE", [] - else - let mutable idx = 0 - items - |> List.skip 1 - |> List.fold (fun (itemS, itemP) it -> - idx <- idx + 1 - $"{itemS} OR data->'%s{name}' ? @{name}{idx}", - ($"@{name}{idx}", Sql.jsonb (valueFunc it)) :: itemP) - (Seq.ofList items - |> Seq.map (fun it -> - $"data->'{name}' ? @{name}0", [ $"@{name}0", Sql.string (valueFunc it) ]) - |> Seq.head) - +/// Create the SQL and parameters for match-any array query +let arrayContains<'T> name (valueFunc : 'T -> string) (items : 'T list) = + $"data['{name}'] ?| @{name}Values", + ($"@{name}Values", Sql.stringArray (items |> List.map valueFunc |> Array.ofList)) + /// Get the first result of the given query let tryHead<'T> (query : Task<'T list>) = backgroundTask { let! results = query diff --git a/src/MyWebLog.Data/Postgres/PostgresPageData.fs b/src/MyWebLog.Data/Postgres/PostgresPageData.fs index 2730976..c1b1679 100644 --- a/src/MyWebLog.Data/Postgres/PostgresPageData.fs +++ b/src/MyWebLog.Data/Postgres/PostgresPageData.fs @@ -14,59 +14,55 @@ type PostgresPageData (source : NpgsqlDataSource, log : ILogger) = /// Append revisions to a page let appendPageRevisions (page : Page) = backgroundTask { - log.LogTrace "PostgresPageData.appendPageRevisions" + log.LogTrace "Page.appendPageRevisions" let! revisions = Revisions.findByEntityId source Table.PageRevision Table.Page page.Id PageId.toString return { page with Revisions = revisions } } /// Return a page with no text or revisions let pageWithoutText (row : RowReader) = - log.LogDebug ("data: {0}", row.string "data") { fromData row with Text = "" } /// Update a page's revisions let updatePageRevisions pageId oldRevs newRevs = - log.LogTrace "PostgresPageData.updatePageRevisions" + log.LogTrace "Page.updatePageRevisions" Revisions.update source Table.PageRevision Table.Page pageId PageId.toString oldRevs newRevs /// Does the given page exist? let pageExists pageId webLogId = + log.LogTrace "Page.pageExists" Document.existsByWebLog source Table.Page pageId PageId.toString webLogId - /// Select pages via a JSON document containment query - let pageByCriteria = - $"""{Query.selectFromTable Table.Page} WHERE {Query.whereDataContains "@criteria"}""" - // IMPLEMENTATION FUNCTIONS /// Get all pages for a web log (without text or revisions) let all webLogId = - log.LogTrace "PostgresPageData.all" + log.LogTrace "Page.all" Sql.fromDataSource source - |> Sql.query $"{pageByCriteria} ORDER BY LOWER(data->>'{nameof Page.empty.Title}')" + |> Sql.query $"{selectWithCriteria Table.Page} ORDER BY LOWER(data ->> '{nameof Page.empty.Title}')" |> Sql.parameters [ webLogContains webLogId ] |> Sql.executeAsync fromData /// Count all pages for the given web log let countAll webLogId = - log.LogTrace "PostgresPageData.countAll" + log.LogTrace "Page.countAll" Sql.fromDataSource source |> Query.countByContains Table.Page (webLogDoc webLogId) /// Count all pages shown in the page list for the given web log let countListed webLogId = - log.LogTrace "PostgresPageData.countListed" + log.LogTrace "Page.countListed" Sql.fromDataSource source |> Query.countByContains Table.Page {| webLogDoc webLogId with IsInPageList = true |} /// Find a page by its ID (without revisions) let findById pageId webLogId = - log.LogTrace "PostgresPageData.findById" + log.LogTrace "Page.findById" Document.findByIdAndWebLog source Table.Page pageId PageId.toString webLogId /// Find a complete page by its ID let findFullById pageId webLogId = backgroundTask { - log.LogTrace "PostgresPageData.findFullById" + log.LogTrace "Page.findFullById" match! findById pageId webLogId with | Some page -> let! withMore = appendPageRevisions page @@ -76,7 +72,7 @@ type PostgresPageData (source : NpgsqlDataSource, log : ILogger) = /// Delete a page by its ID let delete pageId webLogId = backgroundTask { - log.LogTrace "PostgresPageData.delete" + log.LogTrace "Page.delete" match! pageExists pageId webLogId with | true -> do! Sql.fromDataSource source |> Query.deleteById Table.Page (PageId.toString pageId) @@ -86,34 +82,33 @@ type PostgresPageData (source : NpgsqlDataSource, log : ILogger) = /// Find a page by its permalink for the given web log let findByPermalink permalink webLogId = - log.LogTrace "PostgresPageData.findByPermalink" + log.LogTrace "Page.findByPermalink" Sql.fromDataSource source |> Query.findByContains Table.Page {| webLogDoc webLogId with Permalink = Permalink.toString permalink |} |> tryHead /// Find the current permalink within a set of potential prior permalinks for the given web log let findCurrentPermalink permalinks webLogId = backgroundTask { - log.LogTrace "PostgresPageData.findCurrentPermalink" + log.LogTrace "Page.findCurrentPermalink" if List.isEmpty permalinks then return None else - let linkSql, linkParams = - jsonArrayInClause (nameof Page.empty.PriorPermalinks) Permalink.toString permalinks + let linkSql, linkParam = + arrayContains (nameof Page.empty.PriorPermalinks) Permalink.toString permalinks return! - // TODO: stopped here Sql.fromDataSource source |> Sql.query $""" - SELECT data->>'{nameof Page.empty.Permalink}' AS permalink + SELECT data ->> '{nameof Page.empty.Permalink}' AS permalink FROM page WHERE {Query.whereDataContains "@criteria"} - AND ({linkSql})""" - |> Sql.parameters (webLogContains webLogId :: linkParams) + AND {linkSql}""" + |> Sql.parameters [ webLogContains webLogId; linkParam ] |> Sql.executeAsync Map.toPermalink |> tryHead } /// Get all complete pages for the given web log let findFullByWebLog webLogId = backgroundTask { - log.LogTrace "PostgresPageData.findFullByWebLog" + log.LogTrace "Page.findFullByWebLog" let! pages = Document.findByWebLog source Table.Page webLogId let! revisions = Revisions.findByWebLog source Table.PageRevision Table.Page PageId webLogId return @@ -124,30 +119,26 @@ type PostgresPageData (source : NpgsqlDataSource, log : ILogger) = /// Get all listed pages for the given web log (without revisions or text) let findListed webLogId = - log.LogTrace "PostgresPageData.findListed" + log.LogTrace "Page.findListed" Sql.fromDataSource source - |> Sql.query $"{pageByCriteria} ORDER BY LOWER(data->>'{nameof Page.empty.Title}')" + |> Sql.query $"{selectWithCriteria Table.Page} ORDER BY LOWER(data ->> '{nameof Page.empty.Title}')" |> Sql.parameters [ "@criteria", Query.jsonbDocParam {| webLogDoc webLogId with IsInPageList = true |} ] |> Sql.executeAsync pageWithoutText /// Get a page of pages for the given web log (without revisions) let findPageOfPages webLogId pageNbr = - log.LogTrace "PostgresPageData.findPageOfPages" + log.LogTrace "Page.findPageOfPages" Sql.fromDataSource source |> Sql.query $" - {pageByCriteria} + {selectWithCriteria Table.Page} ORDER BY LOWER(data->>'{nameof Page.empty.Title}') LIMIT @pageSize OFFSET @toSkip" |> Sql.parameters [ webLogContains webLogId; "@pageSize", Sql.int 26; "@toSkip", Sql.int ((pageNbr - 1) * 25) ] |> Sql.executeAsync fromData - /// The parameters for saving a page - let pageParams (page : Page) = - Query.docParameters (PageId.toString page.Id) page - /// Restore pages from a backup let restore (pages : Page list) = backgroundTask { - log.LogTrace "PostgresPageData.restore" + log.LogTrace "Page.restore" let revisions = pages |> List.collect (fun p -> p.Revisions |> List.map (fun r -> p.Id, r)) let! _ = Sql.fromDataSource source @@ -163,16 +154,16 @@ type PostgresPageData (source : NpgsqlDataSource, log : ILogger) = /// Save a page let save (page : Page) = backgroundTask { - log.LogTrace "PostgresPageData.save" + log.LogTrace "Page.save" let! oldPage = findFullById page.Id page.WebLogId - do! Sql.fromDataSource source |> Query.save Table.Page (PageId.toString page.Id) page + do! Sql.fromDataSource source |> Query.save Table.Page (PageId.toString page.Id) { page with Revisions = [] } do! updatePageRevisions page.Id (match oldPage with Some p -> p.Revisions | None -> []) page.Revisions () } /// Update a page's prior permalinks let updatePriorPermalinks pageId webLogId permalinks = backgroundTask { - log.LogTrace "PostgresPageData.updatePriorPermalinks" + log.LogTrace "Page.updatePriorPermalinks" match! findById pageId webLogId with | Some page -> do! Sql.fromDataSource source diff --git a/src/MyWebLog.Data/Postgres/PostgresPostData.fs b/src/MyWebLog.Data/Postgres/PostgresPostData.fs index 3336314..01537b2 100644 --- a/src/MyWebLog.Data/Postgres/PostgresPostData.fs +++ b/src/MyWebLog.Data/Postgres/PostgresPostData.fs @@ -1,19 +1,22 @@ namespace MyWebLog.Data.Postgres +open Microsoft.Extensions.Logging open MyWebLog open MyWebLog.Data open NodaTime +open NodaTime.Text open Npgsql open Npgsql.FSharp open Npgsql.FSharp.Documents /// PostgreSQL myWebLog post data implementation -type PostgresPostData (source : NpgsqlDataSource) = +type PostgresPostData (source : NpgsqlDataSource, log : ILogger) = // SUPPORT FUNCTIONS /// Append revisions to a post let appendPostRevisions (post : Post) = backgroundTask { + log.LogTrace "Post.appendPostRevisions" let! revisions = Revisions.findByEntityId source Table.PostRevision Table.Post post.Id PostId.toString return { post with Revisions = revisions } } @@ -24,20 +27,19 @@ type PostgresPostData (source : NpgsqlDataSource) = /// Update a post's revisions let updatePostRevisions postId oldRevs newRevs = + log.LogTrace "Post.updatePostRevisions" Revisions.update source Table.PostRevision Table.Post postId PostId.toString oldRevs newRevs /// Does the given post exist? let postExists postId webLogId = + log.LogTrace "Post.postExists" Document.existsByWebLog source Table.Post postId PostId.toString webLogId - /// Query to select posts by JSON document containment criteria - let postsByCriteria = - $"""{Query.selectFromTable Table.Post} WHERE {Query.whereDataContains "@criteria"}""" - // IMPLEMENTATION FUNCTIONS /// Count posts in a status for the given web log let countByStatus status webLogId = + log.LogTrace "Post.countByStatus" Sql.fromDataSource source |> Sql.query $"""SELECT COUNT(id) AS {countName} FROM {Table.Post} WHERE {Query.whereDataContains "@criteria"}""" @@ -47,12 +49,14 @@ type PostgresPostData (source : NpgsqlDataSource) = /// Find a post by its ID for the given web log (excluding revisions) let findById postId webLogId = + log.LogTrace "Post.findById" Document.findByIdAndWebLog source Table.Post postId PostId.toString webLogId /// Find a post by its permalink for the given web log (excluding revisions and prior permalinks) let findByPermalink permalink webLogId = + log.LogTrace "Post.findByPermalink" Sql.fromDataSource source - |> Sql.query postsByCriteria + |> Sql.query (selectWithCriteria Table.Post) |> Sql.parameters [ "@criteria", Query.jsonbDocParam {| webLogDoc webLogId with Permalink = Permalink.toString permalink |} ] |> Sql.executeAsync fromData @@ -60,6 +64,7 @@ type PostgresPostData (source : NpgsqlDataSource) = /// Find a complete post by its ID for the given web log let findFullById postId webLogId = backgroundTask { + log.LogTrace "Post.findFullById" match! findById postId webLogId with | Some post -> let! withRevisions = appendPostRevisions post @@ -69,6 +74,7 @@ type PostgresPostData (source : NpgsqlDataSource) = /// Delete a post by its ID for the given web log let delete postId webLogId = backgroundTask { + log.LogTrace "Post.delete" match! postExists postId webLogId with | true -> let theId = PostId.toString postId @@ -85,24 +91,26 @@ type PostgresPostData (source : NpgsqlDataSource) = /// Find the current permalink from a list of potential prior permalinks for the given web log let findCurrentPermalink permalinks webLogId = backgroundTask { + log.LogTrace "Post.findCurrentPermalink" if List.isEmpty permalinks then return None else - let linkSql, linkParams = - jsonArrayInClause (nameof Post.empty.PriorPermalinks) Permalink.toString permalinks + let linkSql, linkParam = + arrayContains (nameof Post.empty.PriorPermalinks) Permalink.toString permalinks return! Sql.fromDataSource source |> Sql.query $""" - SELECT data->>'{nameof Post.empty.Permalink}' AS permalink + SELECT data ->> '{nameof Post.empty.Permalink}' AS permalink FROM {Table.Post} WHERE {Query.whereDataContains "@criteria"} - AND ({linkSql})""" - |> Sql.parameters (webLogContains webLogId :: linkParams) + AND {linkSql}""" + |> Sql.parameters [ webLogContains webLogId; linkParam ] |> Sql.executeAsync Map.toPermalink |> tryHead } /// Get all complete posts for the given web log let findFullByWebLog webLogId = backgroundTask { + log.LogTrace "Post.findFullByWebLog" let! posts = Document.findByWebLog source Table.Post webLogId let! revisions = Revisions.findByWebLog source Table.PostRevision Table.Post PostId webLogId return @@ -113,35 +121,39 @@ type PostgresPostData (source : NpgsqlDataSource) = /// Get a page of categorized posts for the given web log (excludes revisions) let findPageOfCategorizedPosts webLogId categoryIds pageNbr postsPerPage = - let catSql, catParams = jsonArrayInClause (nameof Post.empty.CategoryIds) CategoryId.toString categoryIds + log.LogTrace "Post.findPageOfCategorizedPosts" + let catSql, catParam = arrayContains (nameof Post.empty.CategoryIds) CategoryId.toString categoryIds Sql.fromDataSource source |> Sql.query $" - {postsByCriteria} - AND ({catSql}) - ORDER BY published_on DESC + {selectWithCriteria Table.Post} + AND {catSql} + ORDER BY data ->> '{nameof Post.empty.PublishedOn}' DESC LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}" - |> Sql.parameters ( - ("@criteria", Query.jsonbDocParam {| webLogDoc webLogId with Status = PostStatus.toString Published |}) - :: catParams) + |> Sql.parameters + [ "@criteria", Query.jsonbDocParam {| webLogDoc webLogId with Status = PostStatus.toString Published |} + catParam + ] |> Sql.executeAsync fromData /// Get a page of posts for the given web log (excludes text and revisions) let findPageOfPosts webLogId pageNbr postsPerPage = + log.LogTrace "Post.findPageOfPosts" Sql.fromDataSource source |> Sql.query $" - {postsByCriteria} - ORDER BY data->>'{nameof Post.empty.PublishedOn}' DESC NULLS FIRST, - data->>'{nameof Post.empty.UpdatedOn}' + {selectWithCriteria Table.Post} + ORDER BY data ->> '{nameof Post.empty.PublishedOn}' DESC NULLS FIRST, + data ->> '{nameof Post.empty.UpdatedOn}' LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}" |> Sql.parameters [ webLogContains webLogId ] |> Sql.executeAsync postWithoutText /// Get a page of published posts for the given web log (excludes revisions) let findPageOfPublishedPosts webLogId pageNbr postsPerPage = + log.LogTrace "Post.findPageOfPublishedPosts" Sql.fromDataSource source |> Sql.query $" - {postsByCriteria} - ORDER BY data->>'{nameof Post.empty.PublishedOn}' DESC + {selectWithCriteria Table.Post} + ORDER BY data ->> '{nameof Post.empty.PublishedOn}' DESC LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}" |> Sql.parameters [ "@criteria", Query.jsonbDocParam {| webLogDoc webLogId with Status = PostStatus.toString Published |} ] @@ -149,63 +161,66 @@ type PostgresPostData (source : NpgsqlDataSource) = /// Get a page of tagged posts for the given web log (excludes revisions and prior permalinks) let findPageOfTaggedPosts webLogId (tag : string) pageNbr postsPerPage = + log.LogTrace "Post.findPageOfTaggedPosts" Sql.fromDataSource source |> Sql.query $" - {postsByCriteria} - AND data->'{nameof Post.empty.Tags}' ? @tag - ORDER BY data->>'{nameof Post.empty.PublishedOn}' DESC + {selectWithCriteria Table.Post} + AND data['{nameof Post.empty.Tags}'] @> @tag + ORDER BY data ->> '{nameof Post.empty.PublishedOn}' DESC LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}" |> Sql.parameters [ "@criteria", Query.jsonbDocParam {| webLogDoc webLogId with Status = PostStatus.toString Published |} - "@tag", Sql.jsonb tag + "@tag", Query.jsonbDocParam [| tag |] ] |> Sql.executeAsync fromData /// 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 = backgroundTask { + log.LogTrace "Post.findSurroundingPosts" let queryParams () = Sql.parameters [ - "@criteria", Query.jsonbDocParam {| webLogDoc webLogId with Status = PostStatus.toString Published |} - typedParam "publishedOn" publishedOn + "@criteria", Query.jsonbDocParam {| webLogDoc webLogId with Status = PostStatus.toString Published |} + "@publishedOn", Sql.string ((InstantPattern.General.Format publishedOn).Substring (0, 19)) ] + let pubField = nameof Post.empty.PublishedOn let! older = Sql.fromDataSource source |> Sql.query $" - {postsByCriteria} - AND data->>'{nameof Post.empty.PublishedOn}' < @publishedOn - ORDER BY data->>'{nameof Post.empty.PublishedOn}' DESC + {selectWithCriteria Table.Post} + AND SUBSTR(data ->> '{pubField}', 1, 19) < @publishedOn + ORDER BY data ->> '{pubField}' DESC LIMIT 1" |> queryParams () |> Sql.executeAsync fromData let! newer = Sql.fromDataSource source |> Sql.query $" - {postsByCriteria} - AND data->>'{nameof Post.empty.PublishedOn}' > @publishedOn - ORDER BY data->>'{nameof Post.empty.PublishedOn}' + {selectWithCriteria Table.Post} + AND SUBSTR(data ->> '{pubField}', 1, 19) > @publishedOn + ORDER BY data ->> '{pubField}' LIMIT 1" |> queryParams () |> Sql.executeAsync fromData return List.tryHead older, List.tryHead newer } - /// The parameters for saving a post - let postParams (post : Post) = - Query.docParameters (PostId.toString post.Id) post - /// Save a post let save (post : Post) = backgroundTask { + log.LogTrace "Post.save" let! oldPost = findFullById post.Id post.WebLogId - do! Sql.fromDataSource source |> Query.save Table.Post (PostId.toString post.Id) post + do! Sql.fromDataSource source |> Query.save Table.Post (PostId.toString post.Id) { post with Revisions = [] } do! updatePostRevisions post.Id (match oldPost with Some p -> p.Revisions | None -> []) post.Revisions } /// Restore posts from a backup let restore posts = backgroundTask { + log.LogTrace "Post.restore" let revisions = posts |> List.collect (fun p -> p.Revisions |> List.map (fun r -> p.Id, r)) let! _ = Sql.fromDataSource source |> Sql.executeTransactionAsync [ - Query.insertQuery Table.Post, posts |> List.map postParams + Query.insertQuery Table.Post, + posts + |> List.map (fun post -> Query.docParameters (PostId.toString post.Id) { post with Revisions = [] }) Revisions.insertSql Table.PostRevision, revisions |> List.map (fun (postId, rev) -> Revisions.revParams postId PostId.toString rev) ] @@ -214,6 +229,7 @@ type PostgresPostData (source : NpgsqlDataSource) = /// Update prior permalinks for a post let updatePriorPermalinks postId webLogId permalinks = backgroundTask { + log.LogTrace "Post.updatePriorPermalinks" match! findById postId webLogId with | Some post -> do! Sql.fromDataSource source diff --git a/src/MyWebLog.Data/Postgres/PostgresTagMapData.fs b/src/MyWebLog.Data/Postgres/PostgresTagMapData.fs index 49ee19e..3993685 100644 --- a/src/MyWebLog.Data/Postgres/PostgresTagMapData.fs +++ b/src/MyWebLog.Data/Postgres/PostgresTagMapData.fs @@ -1,5 +1,6 @@ namespace MyWebLog.Data.Postgres +open Microsoft.Extensions.Logging open MyWebLog open MyWebLog.Data open Npgsql @@ -7,18 +8,16 @@ open Npgsql.FSharp open Npgsql.FSharp.Documents /// PostgreSQL myWebLog tag mapping data implementation -type PostgresTagMapData (source : NpgsqlDataSource) = - - /// A query to select tag map(s) by JSON document containment criteria - let tagMapByCriteria = - $"""{Query.selectFromTable Table.TagMap} WHERE {Query.whereDataContains "@criteria"}""" +type PostgresTagMapData (source : NpgsqlDataSource, log : ILogger) = /// Find a tag mapping by its ID for the given web log let findById tagMapId webLogId = + log.LogTrace "TagMap.findById" Document.findByIdAndWebLog source Table.TagMap tagMapId TagMapId.toString webLogId /// Delete a tag mapping for the given web log let delete tagMapId webLogId = backgroundTask { + log.LogTrace "TagMap.delete" let! exists = Document.existsByWebLog source Table.TagMap tagMapId TagMapId.toString webLogId if exists then do! Sql.fromDataSource source |> Query.deleteById Table.TagMap (TagMapId.toString tagMapId) @@ -28,42 +27,42 @@ type PostgresTagMapData (source : NpgsqlDataSource) = /// Find a tag mapping by its URL value for the given web log let findByUrlValue (urlValue : string) webLogId = + log.LogTrace "TagMap.findByUrlValue" Sql.fromDataSource source - |> Sql.query tagMapByCriteria + |> Sql.query (selectWithCriteria Table.TagMap) |> Sql.parameters [ "@criteria", Query.jsonbDocParam {| webLogDoc webLogId with UrlValue = urlValue |} ] |> Sql.executeAsync fromData |> tryHead /// Get all tag mappings for the given web log let findByWebLog webLogId = + log.LogTrace "TagMap.findByWebLog" Sql.fromDataSource source - |> Sql.query $"{tagMapByCriteria} ORDER BY data->>'tag'" + |> Sql.query $"{selectWithCriteria Table.TagMap} ORDER BY data ->> 'tag'" |> Sql.parameters [ webLogContains webLogId ] |> Sql.executeAsync fromData /// Find any tag mappings in a list of tags for the given web log let findMappingForTags tags webLogId = - let tagSql, tagParams = jsonArrayInClause (nameof TagMap.empty.Tag) id tags + log.LogTrace "TagMap.findMappingForTags" + let tagSql, tagParam = arrayContains (nameof TagMap.empty.Tag) id tags Sql.fromDataSource source - |> Sql.query $"{tagMapByCriteria} AND ({tagSql})" - |> Sql.parameters (webLogContains webLogId :: tagParams) + |> Sql.query $"{selectWithCriteria Table.TagMap} AND {tagSql}" + |> Sql.parameters [ webLogContains webLogId; tagParam ] |> Sql.executeAsync fromData - /// The parameters for saving a tag mapping - let tagMapParams (tagMap : TagMap) = - Query.docParameters (TagMapId.toString tagMap.Id) tagMap - /// Save a tag mapping let save (tagMap : TagMap) = backgroundTask { do! Sql.fromDataSource source |> Query.save Table.TagMap (TagMapId.toString tagMap.Id) tagMap } /// Restore tag mappings from a backup - let restore tagMaps = backgroundTask { + let restore (tagMaps : TagMap list) = backgroundTask { let! _ = Sql.fromDataSource source |> Sql.executeTransactionAsync [ - Query.insertQuery Table.TagMap, tagMaps |> List.map tagMapParams + Query.insertQuery Table.TagMap, + tagMaps |> List.map (fun tagMap -> Query.docParameters (TagMapId.toString tagMap.Id) tagMap) ] () } diff --git a/src/MyWebLog.Data/Postgres/PostgresThemeData.fs b/src/MyWebLog.Data/Postgres/PostgresThemeData.fs index 1c56fca..757b8d4 100644 --- a/src/MyWebLog.Data/Postgres/PostgresThemeData.fs +++ b/src/MyWebLog.Data/Postgres/PostgresThemeData.fs @@ -1,14 +1,14 @@ namespace MyWebLog.Data.Postgres +open Microsoft.Extensions.Logging open MyWebLog open MyWebLog.Data -open Newtonsoft.Json open Npgsql open Npgsql.FSharp open Npgsql.FSharp.Documents /// PostreSQL myWebLog theme data implementation -type PostgresThemeData (source : NpgsqlDataSource) = +type PostgresThemeData (source : NpgsqlDataSource, log : ILogger) = /// Clear out the template text from a theme let withoutTemplateText row = @@ -17,22 +17,26 @@ type PostgresThemeData (source : NpgsqlDataSource) = /// Retrieve all themes (except 'admin'; excludes template text) let all () = + log.LogTrace "Theme.all" Sql.fromDataSource source |> Sql.query $"{Query.selectFromTable Table.Theme} WHERE id <> 'admin' ORDER BY id" |> Sql.executeAsync withoutTemplateText /// Does a given theme exist? let exists themeId = + log.LogTrace "Theme.exists" Sql.fromDataSource source |> Query.existsById Table.Theme (ThemeId.toString themeId) /// Find a theme by its ID let findById themeId = + log.LogTrace "Theme.findById" Sql.fromDataSource source |> Query.tryById Table.Theme (ThemeId.toString themeId) /// Find a theme by its ID (excludes the text of templates) let findByIdWithoutText themeId = + log.LogTrace "Theme.findByIdWithoutText" Sql.fromDataSource source |> Sql.query $"{Query.selectFromTable Table.Theme} WHERE id = @id" |> Sql.parameters [ "@id", Sql.string (ThemeId.toString themeId) ] @@ -41,6 +45,7 @@ type PostgresThemeData (source : NpgsqlDataSource) = /// Delete a theme by its ID let delete themeId = backgroundTask { + log.LogTrace "Theme.delete" match! exists themeId with | true -> do! Sql.fromDataSource source |> Query.deleteById Table.Theme (ThemeId.toString themeId) @@ -50,6 +55,7 @@ type PostgresThemeData (source : NpgsqlDataSource) = /// Save a theme let save (theme : Theme) = + log.LogTrace "Theme.save" Sql.fromDataSource source |> Query.save Table.Theme (ThemeId.toString theme.Id) theme interface IThemeData with @@ -62,16 +68,18 @@ type PostgresThemeData (source : NpgsqlDataSource) = /// PostreSQL myWebLog theme data implementation -type PostgresThemeAssetData (source : NpgsqlDataSource) = +type PostgresThemeAssetData (source : NpgsqlDataSource, log : ILogger) = /// Get all theme assets (excludes data) let all () = + log.LogTrace "ThemeAsset.all" Sql.fromDataSource source |> Sql.query $"SELECT theme_id, path, updated_on FROM {Table.ThemeAsset}" |> Sql.executeAsync (Map.toThemeAsset false) /// Delete all assets for the given theme let deleteByTheme themeId = backgroundTask { + log.LogTrace "ThemeAsset.deleteByTheme" let! _ = Sql.fromDataSource source |> Sql.query $"DELETE FROM {Table.ThemeAsset} WHERE theme_id = @themeId" @@ -82,6 +90,7 @@ type PostgresThemeAssetData (source : NpgsqlDataSource) = /// Find a theme asset by its ID let findById assetId = + log.LogTrace "ThemeAsset.findById" let (ThemeAssetId (ThemeId themeId, path)) = assetId Sql.fromDataSource source |> Sql.query $"SELECT * FROM {Table.ThemeAsset} WHERE theme_id = @themeId AND path = @path" @@ -91,6 +100,7 @@ type PostgresThemeAssetData (source : NpgsqlDataSource) = /// Get theme assets for the given theme (excludes data) let findByTheme themeId = + log.LogTrace "ThemeAsset.findByTheme" Sql.fromDataSource source |> Sql.query $"SELECT theme_id, path, updated_on FROM {Table.ThemeAsset} WHERE theme_id = @themeId" |> Sql.parameters [ "@themeId", Sql.string (ThemeId.toString themeId) ] @@ -98,6 +108,7 @@ type PostgresThemeAssetData (source : NpgsqlDataSource) = /// Get theme assets for the given theme let findByThemeWithData themeId = + log.LogTrace "ThemeAsset.findByThemeWithData" Sql.fromDataSource source |> Sql.query $"SELECT * FROM {Table.ThemeAsset} WHERE theme_id = @themeId" |> Sql.parameters [ "@themeId", Sql.string (ThemeId.toString themeId) ] @@ -105,6 +116,7 @@ type PostgresThemeAssetData (source : NpgsqlDataSource) = /// Save a theme asset let save (asset : ThemeAsset) = backgroundTask { + log.LogTrace "ThemeAsset.save" let (ThemeAssetId (ThemeId themeId, path)) = asset.Id let! _ = Sql.fromDataSource source diff --git a/src/MyWebLog.Data/Postgres/PostgresUploadData.fs b/src/MyWebLog.Data/Postgres/PostgresUploadData.fs index 08f2f2f..7c032b3 100644 --- a/src/MyWebLog.Data/Postgres/PostgresUploadData.fs +++ b/src/MyWebLog.Data/Postgres/PostgresUploadData.fs @@ -1,12 +1,13 @@ namespace MyWebLog.Data.Postgres +open Microsoft.Extensions.Logging open MyWebLog open MyWebLog.Data open Npgsql open Npgsql.FSharp /// PostgreSQL myWebLog uploaded file data implementation -type PostgresUploadData (source : NpgsqlDataSource) = +type PostgresUploadData (source : NpgsqlDataSource, log : ILogger) = /// The INSERT statement for an uploaded file let upInsert = $" @@ -27,6 +28,7 @@ type PostgresUploadData (source : NpgsqlDataSource) = /// Save an uploaded file let add upload = backgroundTask { + log.LogTrace "Upload.add" let! _ = Sql.fromDataSource source |> Sql.query upInsert @@ -37,6 +39,7 @@ type PostgresUploadData (source : NpgsqlDataSource) = /// Delete an uploaded file by its ID let delete uploadId webLogId = backgroundTask { + log.LogTrace "Upload.delete" let idParam = [ "@id", Sql.string (UploadId.toString uploadId) ] let! path = Sql.fromDataSource source @@ -56,6 +59,7 @@ type PostgresUploadData (source : NpgsqlDataSource) = /// Find an uploaded file by its path for the given web log let findByPath path webLogId = + log.LogTrace "Upload.findByPath" Sql.fromDataSource source |> Sql.query $"SELECT * FROM {Table.Upload} WHERE web_log_id = @webLogId AND path = @path" |> Sql.parameters [ webLogIdParam webLogId; "@path", Sql.string path ] @@ -64,6 +68,7 @@ type PostgresUploadData (source : NpgsqlDataSource) = /// Find all uploaded files for the given web log (excludes data) let findByWebLog webLogId = + log.LogTrace "Upload.findByWebLog" Sql.fromDataSource source |> Sql.query $"SELECT id, web_log_id, path, updated_on FROM {Table.Upload} WHERE web_log_id = @webLogId" |> Sql.parameters [ webLogIdParam webLogId ] @@ -71,6 +76,7 @@ type PostgresUploadData (source : NpgsqlDataSource) = /// Find all uploaded files for the given web log let findByWebLogWithData webLogId = + log.LogTrace "Upload.findByWebLogWithData" Sql.fromDataSource source |> Sql.query $"SELECT * FROM {Table.Upload} WHERE web_log_id = @webLogId" |> Sql.parameters [ webLogIdParam webLogId ] @@ -78,12 +84,11 @@ type PostgresUploadData (source : NpgsqlDataSource) = /// Restore uploads from a backup let restore uploads = backgroundTask { + log.LogTrace "Upload.restore" for batch in uploads |> List.chunkBySize 5 do let! _ = Sql.fromDataSource source - |> Sql.executeTransactionAsync [ - upInsert, batch |> List.map upParams - ] + |> Sql.executeTransactionAsync [ upInsert, batch |> List.map upParams ] () } diff --git a/src/MyWebLog.Data/Postgres/PostgresWebLogData.fs b/src/MyWebLog.Data/Postgres/PostgresWebLogData.fs index 4e1dace..5af36e3 100644 --- a/src/MyWebLog.Data/Postgres/PostgresWebLogData.fs +++ b/src/MyWebLog.Data/Postgres/PostgresWebLogData.fs @@ -1,5 +1,6 @@ namespace MyWebLog.Data.Postgres +open Microsoft.Extensions.Logging open MyWebLog open MyWebLog.Data open Npgsql @@ -7,19 +8,22 @@ open Npgsql.FSharp open Npgsql.FSharp.Documents /// PostgreSQL myWebLog web log data implementation -type PostgresWebLogData (source : NpgsqlDataSource) = +type PostgresWebLogData (source : NpgsqlDataSource, log : ILogger) = /// Add a web log let add (webLog : WebLog) = + log.LogTrace "WebLog.add" Sql.fromDataSource source |> Query.insert Table.WebLog (WebLogId.toString webLog.Id) webLog /// Retrieve all web logs let all () = + log.LogTrace "WebLog.all" Sql.fromDataSource source |> Query.all Table.WebLog /// Delete a web log by its ID let delete webLogId = backgroundTask { + log.LogTrace "WebLog.delete" let criteria = Query.whereDataContains "@criteria" let! _ = Sql.fromDataSource source @@ -40,23 +44,27 @@ type PostgresWebLogData (source : NpgsqlDataSource) = /// Find a web log by its host (URL base) let findByHost (url : string) = + log.LogTrace "WebLog.findByHost" Sql.fromDataSource source - |> Sql.query $"""{Query.selectFromTable Table.WebLog} WHERE {Query.whereDataContains "@criteria"}""" + |> Sql.query (selectWithCriteria Table.WebLog) |> Sql.parameters [ "@criteria", Query.jsonbDocParam {| UrlBase = url |} ] |> Sql.executeAsync fromData |> tryHead /// Find a web log by its ID let findById webLogId = + log.LogTrace "WebLog.findById" Sql.fromDataSource source |> Query.tryById Table.WebLog (WebLogId.toString webLogId) /// Update settings for a web log let updateSettings (webLog : WebLog) = + log.LogTrace "WebLog.updateSettings" Sql.fromDataSource source |> Query.update Table.WebLog (WebLogId.toString webLog.Id) webLog /// Update RSS options for a web log let updateRssOptions (webLog : WebLog) = backgroundTask { + log.LogTrace "WebLog.updateRssOptions" match! findById webLog.Id with | Some blog -> do! Sql.fromDataSource source diff --git a/src/MyWebLog.Data/Postgres/PostgresWebLogUserData.fs b/src/MyWebLog.Data/Postgres/PostgresWebLogUserData.fs index 58d8f7a..ebc03c5 100644 --- a/src/MyWebLog.Data/Postgres/PostgresWebLogUserData.fs +++ b/src/MyWebLog.Data/Postgres/PostgresWebLogUserData.fs @@ -1,5 +1,6 @@ namespace MyWebLog.Data.Postgres +open Microsoft.Extensions.Logging open MyWebLog open MyWebLog.Data open Npgsql @@ -7,23 +8,17 @@ open Npgsql.FSharp open Npgsql.FSharp.Documents /// PostgreSQL myWebLog user data implementation -type PostgresWebLogUserData (source : NpgsqlDataSource) = +type PostgresWebLogUserData (source : NpgsqlDataSource, log : ILogger) = - /// Query to get users by JSON document containment criteria - let userByCriteria = - $"""{Query.selectFromTable Table.WebLogUser} WHERE {Query.whereDataContains "@criteria"}""" - - /// Parameters for saving web log users - let userParams (user : WebLogUser) = - Query.docParameters (WebLogUserId.toString user.Id) user - /// Find a user by their ID for the given web log let findById userId webLogId = + log.LogTrace "WebLogUser.findById" Document.findByIdAndWebLog source Table.WebLogUser userId WebLogUserId.toString webLogId /// Delete a user if they have no posts or pages let delete userId webLogId = backgroundTask { + log.LogTrace "WebLogUser.delete" match! findById userId webLogId with | Some _ -> let criteria = Query.whereDataContains "@criteria" @@ -46,25 +41,29 @@ type PostgresWebLogUserData (source : NpgsqlDataSource) = /// Find a user by their e-mail address for the given web log let findByEmail (email : string) webLogId = + log.LogTrace "WebLogUser.findByEmail" Sql.fromDataSource source - |> Sql.query userByCriteria + |> Sql.query (selectWithCriteria Table.WebLogUser) |> Sql.parameters [ "@criteria", Query.jsonbDocParam {| webLogDoc webLogId with Email = email |} ] |> Sql.executeAsync fromData |> tryHead /// Get all users for the given web log let findByWebLog webLogId = + log.LogTrace "WebLogUser.findByWebLog" Sql.fromDataSource source - |> Sql.query $"{userByCriteria} ORDER BY LOWER(data->>'{nameof WebLogUser.empty.PreferredName}')" + |> Sql.query + $"{selectWithCriteria Table.WebLogUser} ORDER BY LOWER(data->>'{nameof WebLogUser.empty.PreferredName}')" |> Sql.parameters [ webLogContains webLogId ] |> Sql.executeAsync fromData /// Find the names of users by their IDs for the given web log let findNames webLogId userIds = backgroundTask { + log.LogTrace "WebLogUser.findNames" let idSql, idParams = inClause "AND id" "id" WebLogUserId.toString userIds let! users = Sql.fromDataSource source - |> Sql.query $"{userByCriteria} {idSql}" + |> Sql.query $"{selectWithCriteria Table.WebLogUser} {idSql}" |> Sql.parameters (webLogContains webLogId :: idParams) |> Sql.executeAsync fromData return @@ -73,17 +72,20 @@ type PostgresWebLogUserData (source : NpgsqlDataSource) = } /// Restore users from a backup - let restore users = backgroundTask { + let restore (users : WebLogUser list) = backgroundTask { + log.LogTrace "WebLogUser.restore" let! _ = Sql.fromDataSource source |> Sql.executeTransactionAsync [ - Query.insertQuery Table.WebLogUser, users |> List.map userParams + Query.insertQuery Table.WebLogUser, + users |> List.map (fun user -> Query.docParameters (WebLogUserId.toString user.Id) user) ] () } /// Set a user's last seen date/time to now let setLastSeen userId webLogId = backgroundTask { + log.LogTrace "WebLogUser.setLastSeen" match! findById userId webLogId with | Some user -> do! Sql.fromDataSource source @@ -94,6 +96,7 @@ type PostgresWebLogUserData (source : NpgsqlDataSource) = /// Save a user let save (user : WebLogUser) = + log.LogTrace "WebLogUser.save" Sql.fromDataSource source |> Query.save Table.WebLogUser (WebLogUserId.toString user.Id) user interface IWebLogUserData with diff --git a/src/MyWebLog.Data/PostgresData.fs b/src/MyWebLog.Data/PostgresData.fs index f3b63ca..4af3459 100644 --- a/src/MyWebLog.Data/PostgresData.fs +++ b/src/MyWebLog.Data/PostgresData.fs @@ -152,19 +152,20 @@ type PostgresData (source : NpgsqlDataSource, log : ILogger, ser : interface IData with - member _.Category = PostgresCategoryData source + member _.Category = PostgresCategoryData (source, log) member _.Page = PostgresPageData (source, log) - member _.Post = PostgresPostData source - member _.TagMap = PostgresTagMapData source - member _.Theme = PostgresThemeData source - member _.ThemeAsset = PostgresThemeAssetData source - member _.Upload = PostgresUploadData source - member _.WebLog = PostgresWebLogData source - member _.WebLogUser = PostgresWebLogUserData source + member _.Post = PostgresPostData (source, log) + member _.TagMap = PostgresTagMapData (source, log) + member _.Theme = PostgresThemeData (source, log) + member _.ThemeAsset = PostgresThemeAssetData (source, log) + member _.Upload = PostgresUploadData (source, log) + member _.WebLog = PostgresWebLogData (source, log) + member _.WebLogUser = PostgresWebLogUserData (source, log) member _.Serializer = ser member _.StartUp () = backgroundTask { + log.LogTrace "PostgresData.StartUp" do! ensureTables () let! version = diff --git a/src/MyWebLog/Program.fs b/src/MyWebLog/Program.fs index 192f4a8..0ea8258 100644 --- a/src/MyWebLog/Program.fs +++ b/src/MyWebLog/Program.fs @@ -43,7 +43,7 @@ module DataImplementation = let createNpgsqlDataSource (cfg : IConfiguration) = let builder = NpgsqlDataSourceBuilder (cfg.GetConnectionString "PostgreSQL") let _ = builder.UseNodaTime () - let _ = builder.UseLoggerFactory(LoggerFactory.Create(fun it -> it.AddConsole () |> ignore)) + // let _ = builder.UseLoggerFactory(LoggerFactory.Create(fun it -> it.AddConsole () |> ignore)) builder.Build () /// Get the configured data implementation @@ -71,8 +71,7 @@ module DataImplementation = let source = createNpgsqlDataSource config use conn = source.CreateConnection () let log = sp.GetRequiredService> () - log.LogWarning (sprintf "%s %s" conn.DataSource conn.Database) - log.LogInformation $"Using PostgreSQL database {conn.Host}:{conn.Port}/{conn.Database}" + log.LogInformation $"Using PostgreSQL database {conn.Database}" PostgresData (source, log, Json.configure (JsonSerializer.CreateDefault ())) else createSQLite "Data Source=./myweblog.db;Cache=Shared" @@ -167,8 +166,7 @@ let rec main args = let _ = builder.Services.AddSingleton postgres let _ = builder.Services.AddSingleton (fun sp -> - Postgres.DistributedCache ((sp.GetRequiredService ()).GetConnectionString "PostgreSQL") - :> IDistributedCache) + Postgres.DistributedCache (sp.GetRequiredService ()) :> IDistributedCache) () | _ -> () diff --git a/src/MyWebLog/appsettings.json b/src/MyWebLog/appsettings.json index e7412a4..62fa309 100644 --- a/src/MyWebLog/appsettings.json +++ b/src/MyWebLog/appsettings.json @@ -2,8 +2,7 @@ "Generator": "myWebLog 2.0-rc2", "Logging": { "LogLevel": { - "MyWebLog.Handlers": "Information", - "MyWebLog.Data": "Trace" + "MyWebLog.Handlers": "Information" } } }