V2 #36
|
@ -11,9 +11,8 @@
|
|||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.FSharpLu.Json" Version="0.11.7" />
|
||||
<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="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.FSharp" Version="0.9.0-beta-07" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -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<Page> 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<PageId, Page> 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<Page> 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<Page> 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
|
||||
|
|
|
@ -60,10 +60,10 @@ type PostgresData (source : NpgsqlDataSource, log : ILogger<PostgresData>, 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<PostgresData>, 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<PostgresData>, 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<PostgresData>, 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<PostgresData>, 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
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Markdig" Version="0.30.3" />
|
||||
<PackageReference Include="Markdown.ColorCode" Version="1.0.1" />
|
||||
<PackageReference Include="NodaTime" Version="3.1.2" />
|
||||
<PackageReference Include="NodaTime" Version="3.1.6" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -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<IConfiguration> ()
|
||||
|
@ -62,12 +68,10 @@ module DataImplementation =
|
|||
let conn = await (rethinkCfg.CreateConnectionAsync log)
|
||||
RethinkDbData (conn, rethinkCfg, log)
|
||||
elif hasConnStr "PostgreSQL" then
|
||||
let log = sp.GetRequiredService<ILogger<PostgresData>> ()
|
||||
// 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<ILogger<PostgresData>> ()
|
||||
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<IConfiguration> ()
|
||||
| :? PostgresData as postgres ->
|
||||
// ADO.NET Data Sources are designed to work as singletons
|
||||
let _ =
|
||||
builder.Services.AddScoped<NpgsqlConnection> (fun sp ->
|
||||
new NpgsqlConnection (cfg.GetConnectionString "PostgreSQL"))
|
||||
let _ = builder.Services.AddScoped<IData, PostgresData> ()
|
||||
builder.Services.AddSingleton<NpgsqlDataSource> (fun sp ->
|
||||
DataImplementation.createNpgsqlDataSource (sp.GetRequiredService<IConfiguration> ()))
|
||||
let _ = builder.Services.AddSingleton<IData> postgres
|
||||
let _ =
|
||||
builder.Services.AddSingleton<IDistributedCache> (fun sp ->
|
||||
Postgres.DistributedCache (cfg.GetConnectionString "PostgreSQL") :> IDistributedCache)
|
||||
Postgres.DistributedCache ((sp.GetRequiredService<IConfiguration> ()).GetConnectionString "PostgreSQL")
|
||||
:> IDistributedCache)
|
||||
()
|
||||
| _ -> ()
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
"Generator": "myWebLog 2.0-rc2",
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"MyWebLog.Handlers": "Information"
|
||||
"MyWebLog.Handlers": "Information",
|
||||
"MyWebLog.Data": "Trace"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user