diff --git a/src/MyWebLog.Data/MyWebLog.Data.fsproj b/src/MyWebLog.Data/MyWebLog.Data.fsproj index a788fee..9e43e6e 100644 --- a/src/MyWebLog.Data/MyWebLog.Data.fsproj +++ b/src/MyWebLog.Data/MyWebLog.Data.fsproj @@ -11,9 +11,8 @@ - - + diff --git a/src/MyWebLog.Data/Postgres/PostgresPageData.fs b/src/MyWebLog.Data/Postgres/PostgresPageData.fs index 7b7bf29..2730976 100644 --- a/src/MyWebLog.Data/Postgres/PostgresPageData.fs +++ b/src/MyWebLog.Data/Postgres/PostgresPageData.fs @@ -1,5 +1,6 @@ namespace MyWebLog.Data.Postgres +open Microsoft.Extensions.Logging open MyWebLog open MyWebLog.Data open Npgsql @@ -7,22 +8,25 @@ open Npgsql.FSharp open Npgsql.FSharp.Documents /// PostgreSQL myWebLog page data implementation -type PostgresPageData (source : NpgsqlDataSource) = +type PostgresPageData (source : NpgsqlDataSource, log : ILogger) = // SUPPORT FUNCTIONS /// Append revisions to a page let appendPageRevisions (page : Page) = backgroundTask { + log.LogTrace "PostgresPageData.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 = + 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" Revisions.update source Table.PageRevision Table.Page pageId PageId.toString oldRevs newRevs /// Does the given page exist? @@ -37,6 +41,7 @@ type PostgresPageData (source : NpgsqlDataSource) = /// Get all pages for a web log (without text or revisions) let all webLogId = + log.LogTrace "PostgresPageData.all" Sql.fromDataSource source |> Sql.query $"{pageByCriteria} ORDER BY LOWER(data->>'{nameof Page.empty.Title}')" |> Sql.parameters [ webLogContains webLogId ] @@ -44,20 +49,24 @@ type PostgresPageData (source : NpgsqlDataSource) = /// Count all pages for the given web log let countAll webLogId = + log.LogTrace "PostgresPageData.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" 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" 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" match! findById pageId webLogId with | Some page -> let! withMore = appendPageRevisions page @@ -67,6 +76,7 @@ type PostgresPageData (source : NpgsqlDataSource) = /// Delete a page by its ID let delete pageId webLogId = backgroundTask { + log.LogTrace "PostgresPageData.delete" match! pageExists pageId webLogId with | true -> do! Sql.fromDataSource source |> Query.deleteById Table.Page (PageId.toString pageId) @@ -76,12 +86,14 @@ type PostgresPageData (source : NpgsqlDataSource) = /// Find a page by its permalink for the given web log let findByPermalink permalink webLogId = + log.LogTrace "PostgresPageData.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" if List.isEmpty permalinks then return None else let linkSql, linkParams = @@ -101,6 +113,7 @@ type PostgresPageData (source : NpgsqlDataSource) = /// Get all complete pages for the given web log let findFullByWebLog webLogId = backgroundTask { + log.LogTrace "PostgresPageData.findFullByWebLog" let! pages = Document.findByWebLog source Table.Page webLogId let! revisions = Revisions.findByWebLog source Table.PageRevision Table.Page PageId webLogId return @@ -111,6 +124,7 @@ type PostgresPageData (source : NpgsqlDataSource) = /// Get all listed pages for the given web log (without revisions or text) let findListed webLogId = + log.LogTrace "PostgresPageData.findListed" Sql.fromDataSource source |> Sql.query $"{pageByCriteria} ORDER BY LOWER(data->>'{nameof Page.empty.Title}')" |> Sql.parameters [ "@criteria", Query.jsonbDocParam {| webLogDoc webLogId with IsInPageList = true |} ] @@ -118,6 +132,7 @@ type PostgresPageData (source : NpgsqlDataSource) = /// Get a page of pages for the given web log (without revisions) let findPageOfPages webLogId pageNbr = + log.LogTrace "PostgresPageData.findPageOfPages" Sql.fromDataSource source |> Sql.query $" {pageByCriteria} @@ -132,11 +147,14 @@ type PostgresPageData (source : NpgsqlDataSource) = /// Restore pages from a backup let restore (pages : Page list) = backgroundTask { + log.LogTrace "PostgresPageData.restore" let revisions = pages |> List.collect (fun p -> p.Revisions |> List.map (fun r -> p.Id, r)) let! _ = Sql.fromDataSource source |> Sql.executeTransactionAsync [ - Query.insertQuery Table.Page, pages |> List.map pageParams + Query.insertQuery Table.Page, + pages + |> List.map (fun page -> Query.docParameters (PageId.toString page.Id) { page with Revisions = [] }) Revisions.insertSql Table.PageRevision, revisions |> List.map (fun (pageId, rev) -> Revisions.revParams pageId PageId.toString rev) ] @@ -145,6 +163,7 @@ type PostgresPageData (source : NpgsqlDataSource) = /// Save a page let save (page : Page) = backgroundTask { + log.LogTrace "PostgresPageData.save" let! oldPage = findFullById page.Id page.WebLogId do! Sql.fromDataSource source |> Query.save Table.Page (PageId.toString page.Id) page do! updatePageRevisions page.Id (match oldPage with Some p -> p.Revisions | None -> []) page.Revisions @@ -153,6 +172,7 @@ type PostgresPageData (source : NpgsqlDataSource) = /// Update a page's prior permalinks let updatePriorPermalinks pageId webLogId permalinks = backgroundTask { + log.LogTrace "PostgresPageData.updatePriorPermalinks" match! findById pageId webLogId with | Some page -> do! Sql.fromDataSource source diff --git a/src/MyWebLog.Data/PostgresData.fs b/src/MyWebLog.Data/PostgresData.fs index 932a80a..f3b63ca 100644 --- a/src/MyWebLog.Data/PostgresData.fs +++ b/src/MyWebLog.Data/PostgresData.fs @@ -60,10 +60,10 @@ type PostgresData (source : NpgsqlDataSource, log : ILogger, ser : // Page tables if needsTable Table.Page then Definition.createTable Table.Page - $"CREATE INDEX page_web_log_idx ON {Table.Page} (data ->> '{nameof Page.empty.WebLogId}')" - $"CREATE INDEX page_author_idx ON {Table.Page} (data ->> '{nameof Page.empty.AuthorId}')" + $"CREATE INDEX page_web_log_idx ON {Table.Page} ((data ->> '{nameof Page.empty.WebLogId}'))" + $"CREATE INDEX page_author_idx ON {Table.Page} ((data ->> '{nameof Page.empty.AuthorId}'))" $"CREATE INDEX page_permalink_idx ON {Table.Page} - (data ->> '{nameof Page.empty.WebLogId}', data ->> '{nameof Page.empty.Permalink}')" + ((data ->> '{nameof Page.empty.WebLogId}'), (data ->> '{nameof Page.empty.Permalink}'))" if needsTable Table.PageRevision then $"CREATE TABLE {Table.PageRevision} ( page_id TEXT NOT NULL REFERENCES {Table.Page} (id) ON DELETE CASCADE, @@ -74,16 +74,15 @@ type PostgresData (source : NpgsqlDataSource, log : ILogger, ser : // Post tables if needsTable Table.Post then Definition.createTable Table.Post - $"CREATE INDEX post_web_log_idx ON {Table.Post} (data ->> '{nameof Post.empty.WebLogId}')" - $"CREATE INDEX post_author_idx ON {Table.Post} (data ->> '{nameof Post.empty.AuthorId}')" + $"CREATE INDEX post_web_log_idx ON {Table.Post} ((data ->> '{nameof Post.empty.WebLogId}'))" + $"CREATE INDEX post_author_idx ON {Table.Post} ((data ->> '{nameof Post.empty.AuthorId}'))" $"CREATE INDEX post_status_idx ON {Table.Post} - (data ->> '{nameof Post.empty.WebLogId}', data ->> '{nameof Post.empty.Status}', - data ->> '{nameof Post.empty.UpdatedOn}')" + ((data ->> '{nameof Post.empty.WebLogId}'), (data ->> '{nameof Post.empty.Status}'), + (data ->> '{nameof Post.empty.UpdatedOn}'))" $"CREATE INDEX post_permalink_idx ON {Table.Post} - (data ->> '{nameof Post.empty.WebLogId}', data ->> '{nameof Post.empty.Permalink}')" - $"CREATE INDEX post_category_idx ON {Table.Post} USING GIN - (data ->> '{nameof Post.empty.CategoryIds}')" - $"CREATE INDEX post_tag_idx ON {Table.Post} USING GIN (data ->> '{nameof Post.empty.Tags}')" + ((data ->> '{nameof Post.empty.WebLogId}'), (data ->> '{nameof Post.empty.Permalink}'))" + $"CREATE INDEX post_category_idx ON {Table.Post} USING GIN ((data['{nameof Post.empty.CategoryIds}']))" + $"CREATE INDEX post_tag_idx ON {Table.Post} USING GIN ((data['{nameof Post.empty.Tags}']))" if needsTable Table.PostRevision then $"CREATE TABLE {Table.PostRevision} ( post_id TEXT NOT NULL REFERENCES {Table.Post} (id) ON DELETE CASCADE, @@ -92,7 +91,8 @@ type PostgresData (source : NpgsqlDataSource, log : ILogger, ser : PRIMARY KEY (post_id, as_of))" if needsTable Table.PostComment then Definition.createTable Table.PostComment - $"CREATE INDEX post_comment_post_idx ON {Table.PostComment} (data ->> '{nameof Comment.empty.PostId}')" + $"CREATE INDEX post_comment_post_idx ON {Table.PostComment} + ((data ->> '{nameof Comment.empty.PostId}'))" // Tag map table if needsTable Table.TagMap then @@ -120,7 +120,7 @@ type PostgresData (source : NpgsqlDataSource, log : ILogger, ser : |> Sql.executeTransactionAsync (sql |> Seq.map (fun s -> - let parts = s.Split ' ' + let parts = s.Replace(" IF NOT EXISTS", "", System.StringComparison.OrdinalIgnoreCase).Split ' ' if parts[1].ToLowerInvariant () = "table" then log.LogInformation $"Creating {parts[2]} table..." s, [ [] ]) @@ -153,7 +153,7 @@ type PostgresData (source : NpgsqlDataSource, log : ILogger, ser : interface IData with member _.Category = PostgresCategoryData source - member _.Page = PostgresPageData source + member _.Page = PostgresPageData (source, log) member _.Post = PostgresPostData source member _.TagMap = PostgresTagMapData source member _.Theme = PostgresThemeData source diff --git a/src/MyWebLog.Domain/MyWebLog.Domain.fsproj b/src/MyWebLog.Domain/MyWebLog.Domain.fsproj index ad1ed87..83c76c1 100644 --- a/src/MyWebLog.Domain/MyWebLog.Domain.fsproj +++ b/src/MyWebLog.Domain/MyWebLog.Domain.fsproj @@ -9,7 +9,7 @@ - + diff --git a/src/MyWebLog/Program.fs b/src/MyWebLog/Program.fs index 7ae623d..192f4a8 100644 --- a/src/MyWebLog/Program.fs +++ b/src/MyWebLog/Program.fs @@ -36,10 +36,16 @@ open Npgsql module DataImplementation = open MyWebLog.Converters - // open Npgsql.Logging open RethinkDb.Driver.FSharp open RethinkDb.Driver.Net + /// Create an NpgsqlDataSource from the connection string, configuring appropriately + let createNpgsqlDataSource (cfg : IConfiguration) = + let builder = NpgsqlDataSourceBuilder (cfg.GetConnectionString "PostgreSQL") + let _ = builder.UseNodaTime () + let _ = builder.UseLoggerFactory(LoggerFactory.Create(fun it -> it.AddConsole () |> ignore)) + builder.Build () + /// Get the configured data implementation let get (sp : IServiceProvider) : IData = let config = sp.GetRequiredService () @@ -62,12 +68,10 @@ module DataImplementation = let conn = await (rethinkCfg.CreateConnectionAsync log) RethinkDbData (conn, rethinkCfg, log) elif hasConnStr "PostgreSQL" then - let log = sp.GetRequiredService> () - // NpgsqlLogManager.Provider <- ConsoleLoggingProvider NpgsqlLogLevel.Debug - let builder = NpgsqlDataSourceBuilder (connStr "PostgreSQL") - let _ = builder.UseNodaTime () - let source = builder.Build () + 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}" PostgresData (source, log, Json.configure (JsonSerializer.CreateDefault ())) else @@ -155,16 +159,16 @@ let rec main args = let cachePath = defaultArg (Option.ofObj (cfg.GetConnectionString "SQLiteCachePath")) "./session.db" let _ = builder.Services.AddSqliteCache (fun o -> o.CachePath <- cachePath) () - | :? PostgresData -> - // ADO.NET connections are designed to work as per-request instantiation - let cfg = sp.GetRequiredService () + | :? PostgresData as postgres -> + // ADO.NET Data Sources are designed to work as singletons let _ = - builder.Services.AddScoped (fun sp -> - new NpgsqlConnection (cfg.GetConnectionString "PostgreSQL")) - let _ = builder.Services.AddScoped () + builder.Services.AddSingleton (fun sp -> + DataImplementation.createNpgsqlDataSource (sp.GetRequiredService ())) + let _ = builder.Services.AddSingleton postgres let _ = builder.Services.AddSingleton (fun sp -> - Postgres.DistributedCache (cfg.GetConnectionString "PostgreSQL") :> IDistributedCache) + Postgres.DistributedCache ((sp.GetRequiredService ()).GetConnectionString "PostgreSQL") + :> IDistributedCache) () | _ -> () diff --git a/src/MyWebLog/appsettings.json b/src/MyWebLog/appsettings.json index 62fa309..e7412a4 100644 --- a/src/MyWebLog/appsettings.json +++ b/src/MyWebLog/appsettings.json @@ -2,7 +2,8 @@ "Generator": "myWebLog 2.0-rc2", "Logging": { "LogLevel": { - "MyWebLog.Handlers": "Information" + "MyWebLog.Handlers": "Information", + "MyWebLog.Data": "Trace" } } }