V2 #36

Merged
danieljsummers merged 17 commits from v2-out-the-door into main 2023-02-26 18:01:21 +00:00
6 changed files with 58 additions and 34 deletions
Showing only changes of commit 0fd1760fa4 - Show all commits

View File

@ -11,9 +11,8 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.FSharpLu.Json" Version="0.11.7" /> <PackageReference Include="Microsoft.FSharpLu.Json" Version="0.11.7" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NodaTime" Version="3.1.2" />
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.0.0" /> <PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.0.0" />
<PackageReference Include="Npgsql.NodaTime" Version="6.0.6" /> <PackageReference Include="Npgsql.NodaTime" Version="7.0.1" />
<PackageReference Include="RethinkDb.Driver" Version="2.3.150" /> <PackageReference Include="RethinkDb.Driver" Version="2.3.150" />
<PackageReference Include="RethinkDb.Driver.FSharp" Version="0.9.0-beta-07" /> <PackageReference Include="RethinkDb.Driver.FSharp" Version="0.9.0-beta-07" />
</ItemGroup> </ItemGroup>

View File

@ -1,5 +1,6 @@
namespace MyWebLog.Data.Postgres namespace MyWebLog.Data.Postgres
open Microsoft.Extensions.Logging
open MyWebLog open MyWebLog
open MyWebLog.Data open MyWebLog.Data
open Npgsql open Npgsql
@ -7,22 +8,25 @@ open Npgsql.FSharp
open Npgsql.FSharp.Documents open Npgsql.FSharp.Documents
/// PostgreSQL myWebLog page data implementation /// PostgreSQL myWebLog page data implementation
type PostgresPageData (source : NpgsqlDataSource) = type PostgresPageData (source : NpgsqlDataSource, log : ILogger) =
// SUPPORT FUNCTIONS // SUPPORT FUNCTIONS
/// Append revisions to a page /// Append revisions to a page
let appendPageRevisions (page : Page) = backgroundTask { let appendPageRevisions (page : Page) = backgroundTask {
log.LogTrace "PostgresPageData.appendPageRevisions"
let! revisions = Revisions.findByEntityId source Table.PageRevision Table.Page page.Id PageId.toString let! revisions = Revisions.findByEntityId source Table.PageRevision Table.Page page.Id PageId.toString
return { page with Revisions = revisions } return { page with Revisions = revisions }
} }
/// Return a page with no text or revisions /// Return a page with no text or revisions
let pageWithoutText row = let pageWithoutText (row : RowReader) =
log.LogDebug ("data: {0}", row.string "data")
{ fromData<Page> row with Text = "" } { fromData<Page> row with Text = "" }
/// Update a page's revisions /// Update a page's revisions
let updatePageRevisions pageId oldRevs newRevs = let updatePageRevisions pageId oldRevs newRevs =
log.LogTrace "PostgresPageData.updatePageRevisions"
Revisions.update source Table.PageRevision Table.Page pageId PageId.toString oldRevs newRevs Revisions.update source Table.PageRevision Table.Page pageId PageId.toString oldRevs newRevs
/// Does the given page exist? /// Does the given page exist?
@ -37,6 +41,7 @@ type PostgresPageData (source : NpgsqlDataSource) =
/// Get all pages for a web log (without text or revisions) /// Get all pages for a web log (without text or revisions)
let all webLogId = let all webLogId =
log.LogTrace "PostgresPageData.all"
Sql.fromDataSource source Sql.fromDataSource source
|> Sql.query $"{pageByCriteria} ORDER BY LOWER(data->>'{nameof Page.empty.Title}')" |> Sql.query $"{pageByCriteria} ORDER BY LOWER(data->>'{nameof Page.empty.Title}')"
|> Sql.parameters [ webLogContains webLogId ] |> Sql.parameters [ webLogContains webLogId ]
@ -44,20 +49,24 @@ type PostgresPageData (source : NpgsqlDataSource) =
/// Count all pages for the given web log /// Count all pages for the given web log
let countAll webLogId = let countAll webLogId =
log.LogTrace "PostgresPageData.countAll"
Sql.fromDataSource source Sql.fromDataSource source
|> Query.countByContains Table.Page (webLogDoc webLogId) |> Query.countByContains Table.Page (webLogDoc webLogId)
/// Count all pages shown in the page list for the given web log /// Count all pages shown in the page list for the given web log
let countListed webLogId = let countListed webLogId =
log.LogTrace "PostgresPageData.countListed"
Sql.fromDataSource source Sql.fromDataSource source
|> Query.countByContains Table.Page {| webLogDoc webLogId with IsInPageList = true |} |> Query.countByContains Table.Page {| webLogDoc webLogId with IsInPageList = true |}
/// Find a page by its ID (without revisions) /// Find a page by its ID (without revisions)
let findById pageId webLogId = let findById pageId webLogId =
log.LogTrace "PostgresPageData.findById"
Document.findByIdAndWebLog<PageId, Page> source Table.Page pageId PageId.toString webLogId Document.findByIdAndWebLog<PageId, Page> source Table.Page pageId PageId.toString webLogId
/// Find a complete page by its ID /// Find a complete page by its ID
let findFullById pageId webLogId = backgroundTask { let findFullById pageId webLogId = backgroundTask {
log.LogTrace "PostgresPageData.findFullById"
match! findById pageId webLogId with match! findById pageId webLogId with
| Some page -> | Some page ->
let! withMore = appendPageRevisions page let! withMore = appendPageRevisions page
@ -67,6 +76,7 @@ type PostgresPageData (source : NpgsqlDataSource) =
/// Delete a page by its ID /// Delete a page by its ID
let delete pageId webLogId = backgroundTask { let delete pageId webLogId = backgroundTask {
log.LogTrace "PostgresPageData.delete"
match! pageExists pageId webLogId with match! pageExists pageId webLogId with
| true -> | true ->
do! Sql.fromDataSource source |> Query.deleteById Table.Page (PageId.toString pageId) 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 /// Find a page by its permalink for the given web log
let findByPermalink permalink webLogId = let findByPermalink permalink webLogId =
log.LogTrace "PostgresPageData.findByPermalink"
Sql.fromDataSource source Sql.fromDataSource source
|> Query.findByContains<Page> Table.Page {| webLogDoc webLogId with Permalink = Permalink.toString permalink |} |> Query.findByContains<Page> Table.Page {| webLogDoc webLogId with Permalink = Permalink.toString permalink |}
|> tryHead |> tryHead
/// Find the current permalink within a set of potential prior permalinks for the given web log /// Find the current permalink within a set of potential prior permalinks for the given web log
let findCurrentPermalink permalinks webLogId = backgroundTask { let findCurrentPermalink permalinks webLogId = backgroundTask {
log.LogTrace "PostgresPageData.findCurrentPermalink"
if List.isEmpty permalinks then return None if List.isEmpty permalinks then return None
else else
let linkSql, linkParams = let linkSql, linkParams =
@ -101,6 +113,7 @@ type PostgresPageData (source : NpgsqlDataSource) =
/// Get all complete pages for the given web log /// Get all complete pages for the given web log
let findFullByWebLog webLogId = backgroundTask { let findFullByWebLog webLogId = backgroundTask {
log.LogTrace "PostgresPageData.findFullByWebLog"
let! pages = Document.findByWebLog<Page> source Table.Page webLogId let! pages = Document.findByWebLog<Page> source Table.Page webLogId
let! revisions = Revisions.findByWebLog source Table.PageRevision Table.Page PageId webLogId let! revisions = Revisions.findByWebLog source Table.PageRevision Table.Page PageId webLogId
return return
@ -111,6 +124,7 @@ type PostgresPageData (source : NpgsqlDataSource) =
/// Get all listed pages for the given web log (without revisions or text) /// Get all listed pages for the given web log (without revisions or text)
let findListed webLogId = let findListed webLogId =
log.LogTrace "PostgresPageData.findListed"
Sql.fromDataSource source Sql.fromDataSource source
|> Sql.query $"{pageByCriteria} ORDER BY LOWER(data->>'{nameof Page.empty.Title}')" |> Sql.query $"{pageByCriteria} ORDER BY LOWER(data->>'{nameof Page.empty.Title}')"
|> Sql.parameters [ "@criteria", Query.jsonbDocParam {| webLogDoc webLogId with IsInPageList = true |} ] |> 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) /// Get a page of pages for the given web log (without revisions)
let findPageOfPages webLogId pageNbr = let findPageOfPages webLogId pageNbr =
log.LogTrace "PostgresPageData.findPageOfPages"
Sql.fromDataSource source Sql.fromDataSource source
|> Sql.query $" |> Sql.query $"
{pageByCriteria} {pageByCriteria}
@ -132,11 +147,14 @@ type PostgresPageData (source : NpgsqlDataSource) =
/// Restore pages from a backup /// Restore pages from a backup
let restore (pages : Page list) = backgroundTask { 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 revisions = pages |> List.collect (fun p -> p.Revisions |> List.map (fun r -> p.Id, r))
let! _ = let! _ =
Sql.fromDataSource source Sql.fromDataSource source
|> Sql.executeTransactionAsync [ |> 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.insertSql Table.PageRevision,
revisions |> List.map (fun (pageId, rev) -> Revisions.revParams pageId PageId.toString rev) revisions |> List.map (fun (pageId, rev) -> Revisions.revParams pageId PageId.toString rev)
] ]
@ -145,6 +163,7 @@ type PostgresPageData (source : NpgsqlDataSource) =
/// Save a page /// Save a page
let save (page : Page) = backgroundTask { let save (page : Page) = backgroundTask {
log.LogTrace "PostgresPageData.save"
let! oldPage = findFullById page.Id page.WebLogId 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
do! updatePageRevisions page.Id (match oldPage with Some p -> p.Revisions | None -> []) page.Revisions 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 /// Update a page's prior permalinks
let updatePriorPermalinks pageId webLogId permalinks = backgroundTask { let updatePriorPermalinks pageId webLogId permalinks = backgroundTask {
log.LogTrace "PostgresPageData.updatePriorPermalinks"
match! findById pageId webLogId with match! findById pageId webLogId with
| Some page -> | Some page ->
do! Sql.fromDataSource source do! Sql.fromDataSource source

View File

@ -60,10 +60,10 @@ type PostgresData (source : NpgsqlDataSource, log : ILogger<PostgresData>, ser :
// Page tables // Page tables
if needsTable Table.Page then if needsTable Table.Page then
Definition.createTable Table.Page Definition.createTable Table.Page
$"CREATE INDEX page_web_log_idx ON {Table.Page} (data ->> '{nameof Page.empty.WebLogId}')" $"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_author_idx ON {Table.Page} ((data ->> '{nameof Page.empty.AuthorId}'))"
$"CREATE INDEX page_permalink_idx ON {Table.Page} $"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 if needsTable Table.PageRevision then
$"CREATE TABLE {Table.PageRevision} ( $"CREATE TABLE {Table.PageRevision} (
page_id TEXT NOT NULL REFERENCES {Table.Page} (id) ON DELETE CASCADE, page_id TEXT NOT NULL REFERENCES {Table.Page} (id) ON DELETE CASCADE,
@ -74,16 +74,15 @@ type PostgresData (source : NpgsqlDataSource, log : ILogger<PostgresData>, ser :
// Post tables // Post tables
if needsTable Table.Post then if needsTable Table.Post then
Definition.createTable Table.Post Definition.createTable Table.Post
$"CREATE INDEX post_web_log_idx ON {Table.Post} (data ->> '{nameof Post.empty.WebLogId}')" $"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_author_idx ON {Table.Post} ((data ->> '{nameof Post.empty.AuthorId}'))"
$"CREATE INDEX post_status_idx ON {Table.Post} $"CREATE INDEX post_status_idx ON {Table.Post}
(data ->> '{nameof Post.empty.WebLogId}', data ->> '{nameof Post.empty.Status}', ((data ->> '{nameof Post.empty.WebLogId}'), (data ->> '{nameof Post.empty.Status}'),
data ->> '{nameof Post.empty.UpdatedOn}')" (data ->> '{nameof Post.empty.UpdatedOn}'))"
$"CREATE INDEX post_permalink_idx ON {Table.Post} $"CREATE INDEX post_permalink_idx ON {Table.Post}
(data ->> '{nameof Post.empty.WebLogId}', data ->> '{nameof Post.empty.Permalink}')" ((data ->> '{nameof Post.empty.WebLogId}'), (data ->> '{nameof Post.empty.Permalink}'))"
$"CREATE INDEX post_category_idx ON {Table.Post} USING GIN $"CREATE INDEX post_category_idx ON {Table.Post} USING GIN ((data['{nameof Post.empty.CategoryIds}']))"
(data ->> '{nameof Post.empty.CategoryIds}')" $"CREATE INDEX post_tag_idx ON {Table.Post} USING GIN ((data['{nameof Post.empty.Tags}']))"
$"CREATE INDEX post_tag_idx ON {Table.Post} USING GIN (data ->> '{nameof Post.empty.Tags}')"
if needsTable Table.PostRevision then if needsTable Table.PostRevision then
$"CREATE TABLE {Table.PostRevision} ( $"CREATE TABLE {Table.PostRevision} (
post_id TEXT NOT NULL REFERENCES {Table.Post} (id) ON DELETE CASCADE, post_id TEXT NOT NULL REFERENCES {Table.Post} (id) ON DELETE CASCADE,
@ -92,7 +91,8 @@ type PostgresData (source : NpgsqlDataSource, log : ILogger<PostgresData>, ser :
PRIMARY KEY (post_id, as_of))" PRIMARY KEY (post_id, as_of))"
if needsTable Table.PostComment then if needsTable Table.PostComment then
Definition.createTable Table.PostComment 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 // Tag map table
if needsTable Table.TagMap then if needsTable Table.TagMap then
@ -120,7 +120,7 @@ type PostgresData (source : NpgsqlDataSource, log : ILogger<PostgresData>, ser :
|> Sql.executeTransactionAsync |> Sql.executeTransactionAsync
(sql (sql
|> Seq.map (fun s -> |> 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 if parts[1].ToLowerInvariant () = "table" then
log.LogInformation $"Creating {parts[2]} table..." log.LogInformation $"Creating {parts[2]} table..."
s, [ [] ]) s, [ [] ])
@ -153,7 +153,7 @@ type PostgresData (source : NpgsqlDataSource, log : ILogger<PostgresData>, ser :
interface IData with interface IData with
member _.Category = PostgresCategoryData source member _.Category = PostgresCategoryData source
member _.Page = PostgresPageData source member _.Page = PostgresPageData (source, log)
member _.Post = PostgresPostData source member _.Post = PostgresPostData source
member _.TagMap = PostgresTagMapData source member _.TagMap = PostgresTagMapData source
member _.Theme = PostgresThemeData source member _.Theme = PostgresThemeData source

View File

@ -9,7 +9,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Markdig" Version="0.30.3" /> <PackageReference Include="Markdig" Version="0.30.3" />
<PackageReference Include="Markdown.ColorCode" Version="1.0.1" /> <PackageReference Include="Markdown.ColorCode" Version="1.0.1" />
<PackageReference Include="NodaTime" Version="3.1.2" /> <PackageReference Include="NodaTime" Version="3.1.6" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -36,10 +36,16 @@ open Npgsql
module DataImplementation = module DataImplementation =
open MyWebLog.Converters open MyWebLog.Converters
// open Npgsql.Logging
open RethinkDb.Driver.FSharp open RethinkDb.Driver.FSharp
open RethinkDb.Driver.Net 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 /// Get the configured data implementation
let get (sp : IServiceProvider) : IData = let get (sp : IServiceProvider) : IData =
let config = sp.GetRequiredService<IConfiguration> () let config = sp.GetRequiredService<IConfiguration> ()
@ -62,12 +68,10 @@ module DataImplementation =
let conn = await (rethinkCfg.CreateConnectionAsync log) let conn = await (rethinkCfg.CreateConnectionAsync log)
RethinkDbData (conn, rethinkCfg, log) RethinkDbData (conn, rethinkCfg, log)
elif hasConnStr "PostgreSQL" then elif hasConnStr "PostgreSQL" then
let log = sp.GetRequiredService<ILogger<PostgresData>> () let source = createNpgsqlDataSource config
// NpgsqlLogManager.Provider <- ConsoleLoggingProvider NpgsqlLogLevel.Debug
let builder = NpgsqlDataSourceBuilder (connStr "PostgreSQL")
let _ = builder.UseNodaTime ()
let source = builder.Build ()
use conn = source.CreateConnection () use conn = source.CreateConnection ()
let log = sp.GetRequiredService<ILogger<PostgresData>> ()
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.Host}:{conn.Port}/{conn.Database}"
PostgresData (source, log, Json.configure (JsonSerializer.CreateDefault ())) PostgresData (source, log, Json.configure (JsonSerializer.CreateDefault ()))
else else
@ -155,16 +159,16 @@ let rec main args =
let cachePath = defaultArg (Option.ofObj (cfg.GetConnectionString "SQLiteCachePath")) "./session.db" let cachePath = defaultArg (Option.ofObj (cfg.GetConnectionString "SQLiteCachePath")) "./session.db"
let _ = builder.Services.AddSqliteCache (fun o -> o.CachePath <- cachePath) let _ = builder.Services.AddSqliteCache (fun o -> o.CachePath <- cachePath)
() ()
| :? PostgresData -> | :? PostgresData as postgres ->
// ADO.NET connections are designed to work as per-request instantiation // ADO.NET Data Sources are designed to work as singletons
let cfg = sp.GetRequiredService<IConfiguration> ()
let _ = let _ =
builder.Services.AddScoped<NpgsqlConnection> (fun sp -> builder.Services.AddSingleton<NpgsqlDataSource> (fun sp ->
new NpgsqlConnection (cfg.GetConnectionString "PostgreSQL")) DataImplementation.createNpgsqlDataSource (sp.GetRequiredService<IConfiguration> ()))
let _ = builder.Services.AddScoped<IData, PostgresData> () let _ = builder.Services.AddSingleton<IData> postgres
let _ = let _ =
builder.Services.AddSingleton<IDistributedCache> (fun sp -> builder.Services.AddSingleton<IDistributedCache> (fun sp ->
Postgres.DistributedCache (cfg.GetConnectionString "PostgreSQL") :> IDistributedCache) Postgres.DistributedCache ((sp.GetRequiredService<IConfiguration> ()).GetConnectionString "PostgreSQL")
:> IDistributedCache)
() ()
| _ -> () | _ -> ()

View File

@ -2,7 +2,8 @@
"Generator": "myWebLog 2.0-rc2", "Generator": "myWebLog 2.0-rc2",
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"MyWebLog.Handlers": "Information" "MyWebLog.Handlers": "Information",
"MyWebLog.Data": "Trace"
} }
} }
} }