Incorporate doc lib v4 ordering

This commit is contained in:
Daniel J. Summers 2024-08-22 23:00:25 -04:00
parent d4c0e4e26c
commit cc3e41ddc5
15 changed files with 184 additions and 181 deletions

View File

@ -5,9 +5,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BitBadger.Documents.Postgres" Version="4.0.0-rc1" /> <PackageReference Include="BitBadger.Documents.Postgres" Version="4.0.0-rc3" />
<PackageReference Include="BitBadger.Documents.Sqlite" Version="4.0.0-rc1" /> <PackageReference Include="BitBadger.Documents.Sqlite" Version="4.0.0-rc3" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.6" /> <PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.8" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.FSharpLu.Json" Version="0.11.7" /> <PackageReference Include="Microsoft.FSharpLu.Json" Version="0.11.7" />
@ -15,7 +15,7 @@
<PackageReference Include="Npgsql.NodaTime" Version="8.0.3" /> <PackageReference Include="Npgsql.NodaTime" Version="8.0.3" />
<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" />
<PackageReference Update="FSharp.Core" Version="8.0.300" /> <PackageReference Update="FSharp.Core" Version="8.0.400" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -83,7 +83,7 @@ let webLogContains webLogId =
/// A SQL string to select data from a table with the given JSON document contains criteria /// A SQL string to select data from a table with the given JSON document contains criteria
let selectWithCriteria tableName = let selectWithCriteria tableName =
$"""{Query.find tableName} WHERE {Query.whereDataContains "@criteria"}""" Query.byContains (Query.find tableName)
/// Create the SQL and parameters for an IN clause /// Create the SQL and parameters for an IN clause
let inClause<'T> colNameAndPrefix paramName (items: 'T list) = let inClause<'T> colNameAndPrefix paramName (items: 'T list) =

View File

@ -33,6 +33,10 @@ type PostgresPageData(log: ILogger) =
log.LogTrace "Page.pageExists" log.LogTrace "Page.pageExists"
Document.existsByWebLog Table.Page pageId webLogId Document.existsByWebLog Table.Page pageId webLogId
/// The query to get all pages ordered by title
let sortedPages =
selectWithCriteria Table.Page + Query.orderBy [ Field.Named $"i:{nameof Page.Empty.Title}" ] PostgreSQL
// IMPLEMENTATION FUNCTIONS // IMPLEMENTATION FUNCTIONS
/// Add a page /// Add a page
@ -47,7 +51,7 @@ type PostgresPageData(log: ILogger) =
let all webLogId = let all webLogId =
log.LogTrace "Page.all" log.LogTrace "Page.all"
Custom.list Custom.list
$"{selectWithCriteria Table.Page} ORDER BY LOWER(data->>'{nameof Page.Empty.Title}')" sortedPages
[ webLogContains webLogId ] [ webLogContains webLogId ]
(fun row -> { fromData<Page> row with Text = ""; Metadata = []; PriorPermalinks = [] }) (fun row -> { fromData<Page> row with Text = ""; Metadata = []; PriorPermalinks = [] })
@ -133,17 +137,13 @@ type PostgresPageData(log: ILogger) =
let findListed webLogId = let findListed webLogId =
log.LogTrace "Page.findListed" log.LogTrace "Page.findListed"
Custom.list Custom.list
$"{selectWithCriteria Table.Page} ORDER BY LOWER(data->>'{nameof Page.Empty.Title}')" sortedPages [ jsonParam "@criteria" {| webLogDoc webLogId with IsInPageList = true |} ] pageWithoutText
[ jsonParam "@criteria" {| webLogDoc webLogId with IsInPageList = true |} ]
pageWithoutText
/// 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 "Page.findPageOfPages" log.LogTrace "Page.findPageOfPages"
Custom.list Custom.list
$"{selectWithCriteria Table.Page} $"{sortedPages} LIMIT @pageSize OFFSET @toSkip"
ORDER BY LOWER(data->>'{nameof Page.Empty.Title}')
LIMIT @pageSize OFFSET @toSkip"
[ webLogContains webLogId; "@pageSize", Sql.int 26; "@toSkip", Sql.int ((pageNbr - 1) * 25) ] [ webLogContains webLogId; "@pageSize", Sql.int 26; "@toSkip", Sql.int ((pageNbr - 1) * 25) ]
(fun row -> { fromData<Page> row with Metadata = []; PriorPermalinks = [] }) (fun row -> { fromData<Page> row with Metadata = []; PriorPermalinks = [] })

View File

@ -124,20 +124,23 @@ type PostgresPostData(log: ILogger) =
log.LogTrace "Post.findPageOfCategorizedPosts" log.LogTrace "Post.findPageOfCategorizedPosts"
let catSql, catParam = arrayContains (nameof Post.Empty.CategoryIds) string categoryIds let catSql, catParam = arrayContains (nameof Post.Empty.CategoryIds) string categoryIds
Custom.list Custom.list
$"{selectWithCriteria Table.Post} $"""{selectWithCriteria Table.Post}
AND {catSql} AND {catSql}
ORDER BY data->>'{nameof Post.Empty.PublishedOn}' DESC {Query.orderBy [ Field.Named $"{nameof Post.Empty.PublishedOn} DESC" ] PostgreSQL}
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}" LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"""
[ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |}; catParam ] [ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |}; catParam ]
postWithoutLinks postWithoutLinks
/// Get a page of posts for the given web log (excludes text and revisions) /// Get a page of posts for the given web log (excludes text and revisions)
let findPageOfPosts webLogId pageNbr postsPerPage = let findPageOfPosts webLogId pageNbr postsPerPage =
log.LogTrace "Post.findPageOfPosts" log.LogTrace "Post.findPageOfPosts"
let order =
Query.orderBy
[ Field.Named $"{nameof Post.Empty.PublishedOn} DESC NULLS FIRST"
Field.Named (nameof Post.Empty.UpdatedOn) ]
PostgreSQL
Custom.list Custom.list
$"{selectWithCriteria Table.Post} $"{selectWithCriteria Table.Post}{order}
ORDER BY data->>'{nameof Post.Empty.PublishedOn}' DESC NULLS FIRST,
data->>'{nameof Post.Empty.UpdatedOn}'
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}" LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
[ webLogContains webLogId ] [ webLogContains webLogId ]
postWithoutText postWithoutText
@ -146,9 +149,9 @@ type PostgresPostData(log: ILogger) =
let findPageOfPublishedPosts webLogId pageNbr postsPerPage = let findPageOfPublishedPosts webLogId pageNbr postsPerPage =
log.LogTrace "Post.findPageOfPublishedPosts" log.LogTrace "Post.findPageOfPublishedPosts"
Custom.list Custom.list
$"{selectWithCriteria Table.Post} $"""{selectWithCriteria Table.Post}
ORDER BY data->>'{nameof Post.Empty.PublishedOn}' DESC {Query.orderBy [ Field.Named $"{nameof Post.Empty.PublishedOn} DESC" ] PostgreSQL}
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}" LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"""
[ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |} ] [ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |} ]
postWithoutLinks postWithoutLinks
@ -156,10 +159,10 @@ type PostgresPostData(log: ILogger) =
let findPageOfTaggedPosts webLogId (tag: string) pageNbr postsPerPage = let findPageOfTaggedPosts webLogId (tag: string) pageNbr postsPerPage =
log.LogTrace "Post.findPageOfTaggedPosts" log.LogTrace "Post.findPageOfTaggedPosts"
Custom.list Custom.list
$"{selectWithCriteria Table.Post} $"""{selectWithCriteria Table.Post}
AND data['{nameof Post.Empty.Tags}'] @> @tag AND data['{nameof Post.Empty.Tags}'] @> @tag
ORDER BY data->>'{nameof Post.Empty.PublishedOn}' DESC {Query.orderBy [ Field.Named $"{nameof Post.Empty.PublishedOn} DESC" ] PostgreSQL}
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}" LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"""
[ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |}; jsonParam "@tag" [| tag |] ] [ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |}; jsonParam "@tag" [| tag |] ]
postWithoutLinks postWithoutLinks
@ -170,10 +173,10 @@ type PostgresPostData(log: ILogger) =
[ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |} [ jsonParam "@criteria" {| webLogDoc webLogId with Status = Published |}
"@publishedOn", Sql.timestamptz (publishedOn.ToDateTimeOffset()) ] "@publishedOn", Sql.timestamptz (publishedOn.ToDateTimeOffset()) ]
let query op direction = let query op direction =
$"{selectWithCriteria Table.Post} $"""{selectWithCriteria Table.Post}
AND (data->>'{nameof Post.Empty.PublishedOn}')::timestamp with time zone %s{op} @publishedOn AND (data->>'{nameof Post.Empty.PublishedOn}')::timestamp with time zone %s{op} @publishedOn
ORDER BY data->>'{nameof Post.Empty.PublishedOn}' %s{direction} {Query.orderBy [ Field.Named $"{nameof Post.Empty.PublishedOn} %s{direction}" ] PostgreSQL}
LIMIT 1" LIMIT 1"""
let! older = Custom.list (query "<" "DESC") (queryParams ()) postWithoutLinks let! older = Custom.list (query "<" "DESC") (queryParams ()) postWithoutLinks
let! newer = Custom.list (query ">" "") (queryParams ()) postWithoutLinks let! newer = Custom.list (query ">" "") (queryParams ()) postWithoutLinks
return List.tryHead older, List.tryHead newer return List.tryHead older, List.tryHead newer

View File

@ -17,11 +17,11 @@ type PostgresThemeData(log: ILogger) =
/// Retrieve all themes (except 'admin'; excludes template text) /// Retrieve all themes (except 'admin'; excludes template text)
let all () = let all () =
log.LogTrace "Theme.all" log.LogTrace "Theme.all"
let fields = [ Field.NE (nameof Theme.Empty.Id) "admin" ]
Custom.list Custom.list
$"{Query.find Table.Theme} (Query.byFields (Query.find Table.Theme) Any fields
WHERE data->>'{nameof Theme.Empty.Id}' <> 'admin' + Query.orderBy [ Field.Named (nameof Theme.Empty.Id) ] PostgreSQL)
ORDER BY data->>'{nameof Theme.Empty.Id}'" (addFieldParams fields [])
[]
withoutTemplateText withoutTemplateText
/// Does a given theme exist? /// Does a given theme exist?

View File

@ -49,10 +49,8 @@ type PostgresWebLogUserData(log: ILogger) =
/// Get all users for the given web log /// Get all users for the given web log
let findByWebLog webLogId = let findByWebLog webLogId =
log.LogTrace "WebLogUser.findByWebLog" log.LogTrace "WebLogUser.findByWebLog"
Custom.list Find.byContainsOrdered<WebLogUser>
$"{selectWithCriteria Table.WebLogUser} ORDER BY LOWER(data->>'{nameof WebLogUser.Empty.PreferredName}')" Table.WebLogUser (webLogDoc webLogId) [ Field.Named $"i:{nameof WebLogUser.Empty.PreferredName}" ]
[ webLogContains webLogId ]
fromData<WebLogUser>
/// Find the names of users by their IDs for the given web log /// Find the names of users by their IDs for the given web log
let findNames webLogId (userIds: WebLogUserId list) = backgroundTask { let findNames webLogId (userIds: WebLogUserId list) = backgroundTask {

View File

@ -16,16 +16,18 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
let parentIdField = nameof Category.Empty.ParentId let parentIdField = nameof Category.Empty.ParentId
/// Count all categories for the given web log /// Count all categories for the given web log
let countAll webLogId = let countAll webLogId = backgroundTask {
log.LogTrace "Category.countAll" log.LogTrace "Category.countAll"
Document.countByWebLog Table.Category webLogId conn let! count = conn.countByFields Table.Category Any [ webLogField webLogId ]
return int count
}
/// Count all top-level categories for the given web log /// Count all top-level categories for the given web log
let countTopLevel webLogId = let countTopLevel webLogId = backgroundTask {
log.LogTrace "Category.countTopLevel" log.LogTrace "Category.countTopLevel"
let fields = [ webLogField webLogId; Field.NEX parentIdField ] let! count = conn.countByFields Table.Category All [ webLogField webLogId; Field.NEX parentIdField ]
conn.customScalar return int count
(Query.byFields (Query.count Table.Category) All fields) (addFieldParams fields []) (toCount >> int) }
/// Find all categories for the given web log /// Find all categories for the given web log
let findByWebLog webLogId = let findByWebLog webLogId =

View File

@ -252,12 +252,6 @@ module Document =
let selectByWebLog table = let selectByWebLog table =
Query.statementWhere (Query.find table) whereByWebLog Query.statementWhere (Query.find table) whereByWebLog
/// Count documents for the given web log ID
let countByWebLog table (webLogId: WebLogId) conn = backgroundTask {
let! count = Count.byFields table Any [ webLogField webLogId ] conn
return int count
}
/// Functions to support revisions /// Functions to support revisions
module Revisions = module Revisions =

View File

@ -17,8 +17,10 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
/// The JSON field name for the "is in page list" flag /// The JSON field name for the "is in page list" flag
let pgListName = nameof Page.Empty.IsInPageList let pgListName = nameof Page.Empty.IsInPageList
/// The JSON field for the title of the page /// Query to return pages sorted by title
let titleField = $"data->>'{nameof Page.Empty.Title}'" let sortedPages fields =
Query.byFields (Query.find Table.Page) All fields
+ Query.orderBy [ Field.Named $"i:{nameof Page.Empty.Title}" ] SQLite
// SUPPORT FUNCTIONS // SUPPORT FUNCTIONS
@ -52,21 +54,23 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
log.LogTrace "Page.all" log.LogTrace "Page.all"
let field = [ webLogField webLogId ] let field = [ webLogField webLogId ]
conn.customList conn.customList
$"{Query.byFields (Query.find Table.Page) Any field} ORDER BY LOWER({titleField})" (sortedPages field)
(addFieldParams field []) (addFieldParams field [])
(fun rdr -> { fromData<Page> rdr with Text = ""; Metadata = []; PriorPermalinks = [] }) (fun rdr -> { fromData<Page> rdr with Text = ""; Metadata = []; PriorPermalinks = [] })
/// Count all pages for the given web log /// Count all pages for the given web log
let countAll webLogId = let countAll webLogId = backgroundTask {
log.LogTrace "Page.countAll" log.LogTrace "Page.countAll"
Document.countByWebLog Table.Page webLogId conn let! count = conn.countByFields Table.Page Any [ webLogField webLogId ]
return int count
}
/// 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 = backgroundTask {
log.LogTrace "Page.countListed" log.LogTrace "Page.countListed"
let fields = [ webLogField webLogId; Field.EQ pgListName true ] let! count = conn.countByFields Table.Page All [ webLogField webLogId; Field.EQ pgListName true ]
conn.customScalar return int count
(Query.byFields (Query.count Table.Page) All fields) (addFieldParams fields []) (toCount >> int) }
/// Find a page by its ID (without revisions and prior permalinks) /// Find a page by its ID (without revisions and prior permalinks)
let findById (pageId: PageId) webLogId = backgroundTask { let findById (pageId: PageId) webLogId = backgroundTask {
@ -131,17 +135,14 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
log.LogTrace "Page.findListed" log.LogTrace "Page.findListed"
let fields = [ webLogField webLogId; Field.EQ pgListName true ] let fields = [ webLogField webLogId; Field.EQ pgListName true ]
conn.customList conn.customList
$"{Query.byFields (Query.find Table.Page) All fields} ORDER BY LOWER({titleField})" (sortedPages fields) (addFieldParams fields []) (fun rdr -> { fromData<Page> rdr with Text = "" })
(addFieldParams fields [])
(fun rdr -> { fromData<Page> rdr with Text = "" })
/// 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 "Page.findPageOfPages" log.LogTrace "Page.findPageOfPages"
let field = [ webLogField webLogId ] let field = [ webLogField webLogId ]
conn.customList conn.customList
$"{Query.byFields (Query.find Table.Page) Any field} ORDER BY LOWER({titleField}) $"{sortedPages field} LIMIT @pageSize OFFSET @toSkip"
LIMIT @pageSize OFFSET @toSkip"
(addFieldParams field [ sqlParam "@pageSize" 26; sqlParam "@toSkip" ((pageNbr - 1) * 25) ]) (addFieldParams field [ sqlParam "@pageSize" 26; sqlParam "@toSkip" ((pageNbr - 1) * 25) ])
(fun rdr -> { pageWithoutLinks rdr with Metadata = [] }) (fun rdr -> { pageWithoutLinks rdr with Metadata = [] })

View File

@ -16,7 +16,7 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
let linkName = nameof Post.Empty.Permalink let linkName = nameof Post.Empty.Permalink
/// The JSON field for when the post was published /// The JSON field for when the post was published
let publishField = $"data->>'{nameof Post.Empty.PublishedOn}'" let publishName = nameof Post.Empty.PublishedOn
/// The name of the JSON field for the post's status /// The name of the JSON field for the post's status
let statName = nameof Post.Empty.Status let statName = nameof Post.Empty.Status
@ -60,11 +60,11 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
} }
/// Count posts in a status for the given web log /// Count posts in a status for the given web log
let countByStatus (status: PostStatus) webLogId = let countByStatus (status: PostStatus) webLogId = backgroundTask {
log.LogTrace "Post.countByStatus" log.LogTrace "Post.countByStatus"
let fields = [ webLogField webLogId; Field.EQ statName (string status) ] let! count = conn.countByFields Table.Post All [ webLogField webLogId; Field.EQ statName (string status) ]
conn.customScalar return int count
(Query.byFields (Query.count Table.Post) All fields) (addFieldParams fields []) (toCount >> int) }
/// Find a post by its ID for the given web log (excluding revisions) /// Find a post by its ID for the given web log (excluding revisions)
let findById (postId: PostId) webLogId = backgroundTask { let findById (postId: PostId) webLogId = backgroundTask {
@ -132,19 +132,20 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
log.LogTrace "Post.findPageOfCategorizedPosts" log.LogTrace "Post.findPageOfCategorizedPosts"
let catSql, catParams = inJsonArray Table.Post (nameof Post.Empty.CategoryIds) "catId" categoryIds let catSql, catParams = inJsonArray Table.Post (nameof Post.Empty.CategoryIds) "catId" categoryIds
conn.customList conn.customList
$"{publishedPostByWebLog} AND {catSql} $"""{publishedPostByWebLog} AND {catSql}
ORDER BY {publishField} DESC {Query.orderBy [ Field.Named $"{publishName} DESC" ] SQLite}
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}" LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"""
(webLogParam webLogId :: catParams) (webLogParam webLogId :: catParams)
postWithoutLinks postWithoutLinks
/// Get a page of posts for the given web log (excludes text and revisions) /// Get a page of posts for the given web log (excludes text and revisions)
let findPageOfPosts webLogId pageNbr postsPerPage = let findPageOfPosts webLogId pageNbr postsPerPage =
log.LogTrace "Post.findPageOfPosts" log.LogTrace "Post.findPageOfPosts"
let order =
Query.orderBy
[ Field.Named $"{publishName} DESC NULLS FIRST"; Field.Named (nameof Post.Empty.UpdatedOn) ] SQLite
conn.customList conn.customList
$"{postByWebLog} $"{postByWebLog}{order} LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
ORDER BY {publishField} DESC NULLS FIRST, data->>'{nameof Post.Empty.UpdatedOn}'
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
[ webLogParam webLogId ] [ webLogParam webLogId ]
postWithoutText postWithoutText
@ -152,9 +153,9 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
let findPageOfPublishedPosts webLogId pageNbr postsPerPage = let findPageOfPublishedPosts webLogId pageNbr postsPerPage =
log.LogTrace "Post.findPageOfPublishedPosts" log.LogTrace "Post.findPageOfPublishedPosts"
conn.customList conn.customList
$"{publishedPostByWebLog} $"""{publishedPostByWebLog}
ORDER BY {publishField} DESC {Query.orderBy [ Field.Named $"{publishName} DESC" ] SQLite}
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}" LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"""
[ webLogParam webLogId ] [ webLogParam webLogId ]
postWithoutLinks postWithoutLinks
@ -163,26 +164,28 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
log.LogTrace "Post.findPageOfTaggedPosts" log.LogTrace "Post.findPageOfTaggedPosts"
let tagSql, tagParams = inJsonArray Table.Post (nameof Post.Empty.Tags) "tag" [ tag ] let tagSql, tagParams = inJsonArray Table.Post (nameof Post.Empty.Tags) "tag" [ tag ]
conn.customList conn.customList
$"{publishedPostByWebLog} AND {tagSql} $"""{publishedPostByWebLog} AND {tagSql}
ORDER BY {publishField} DESC {Query.orderBy [ Field.Named $"{publishName} DESC" ] SQLite}
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}" LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"""
(webLogParam webLogId :: tagParams) (webLogParam webLogId :: tagParams)
postWithoutLinks postWithoutLinks
/// Find the next newest and oldest post from a publish date for the given web log /// 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 : Instant) = backgroundTask {
log.LogTrace "Post.findSurroundingPosts" log.LogTrace "Post.findSurroundingPosts"
let parameters = [ webLogParam webLogId; sqlParam "@publishedOn" (instantParam publishedOn) ] let adjacent op order =
let! older = let fields = [
webLogField webLogId
Field.EQ (nameof Post.Empty.Status) (string Published)
(if op = "<" then Field.LT else Field.GT) publishName (instantParam publishedOn)
]
conn.customSingle conn.customSingle
$"{publishedPostByWebLog} AND {publishField} < @publishedOn ORDER BY {publishField} DESC LIMIT 1" (Query.byFields (Query.find Table.Post) All fields
parameters + Query.orderBy [ Field.Named (publishName + order) ] SQLite + " LIMIT 1")
postWithoutLinks (addFieldParams fields [])
let! newer =
conn.customSingle
$"{publishedPostByWebLog} AND {publishField} > @publishedOn ORDER BY {publishField} LIMIT 1"
parameters
postWithoutLinks postWithoutLinks
let! older = adjacent "<" " DESC"
let! newer = adjacent ">" ""
return older, newer return older, newer
} }

View File

@ -10,8 +10,8 @@ open MyWebLog.Data
/// SQLite myWebLog theme data implementation /// SQLite myWebLog theme data implementation
type SQLiteThemeData(conn : SqliteConnection, log: ILogger) = type SQLiteThemeData(conn : SqliteConnection, log: ILogger) =
/// The JSON field for the theme ID /// The name of the theme ID field
let idField = $"data->>'{nameof Theme.Empty.Id}'" let idName = nameof Theme.Empty.Id
/// Convert a document to a theme with no template text /// Convert a document to a theme with no template text
let withoutTemplateText (rdr: SqliteDataReader) = let withoutTemplateText (rdr: SqliteDataReader) =
@ -25,7 +25,11 @@ type SQLiteThemeData(conn : SqliteConnection, log: ILogger) =
/// Retrieve all themes (except 'admin'; excludes template text) /// Retrieve all themes (except 'admin'; excludes template text)
let all () = let all () =
log.LogTrace "Theme.all" log.LogTrace "Theme.all"
conn.customList $"{Query.find Table.Theme} WHERE {idField} <> 'admin' ORDER BY {idField}" [] withoutTemplateText let fields = [ Field.NE idName "admin" ]
conn.customList
(Query.byFields (Query.find Table.Theme) Any fields + Query.orderBy [ Field.Named idName ] SQLite)
(addFieldParams fields [])
withoutTemplateText
/// Does a given theme exist? /// Does a given theme exist?
let exists (themeId: ThemeId) = let exists (themeId: ThemeId) =

View File

@ -1,7 +1,6 @@
namespace MyWebLog.Data namespace MyWebLog.Data
open System open System
open System.Threading.Tasks
open BitBadger.Documents open BitBadger.Documents
open BitBadger.Documents.Sqlite open BitBadger.Documents.Sqlite
open Microsoft.Data.Sqlite open Microsoft.Data.Sqlite
@ -24,108 +23,107 @@ type SQLiteData(conn: SqliteConnection, log: ILogger<SQLiteData>, ser: JsonSeria
let needsTable table = let needsTable table =
not (List.contains table tables) not (List.contains table tables)
let jsonTable table = let creatingTable = "Creating {Table} table..."
$"{Query.Definition.ensureTable table}; {Query.Definition.ensureKey table SQLite}"
let tasks =
seq {
// Theme tables // Theme tables
if needsTable Table.Theme then jsonTable Table.Theme if needsTable Table.Theme then
log.LogInformation(creatingTable, Table.Theme)
do! conn.ensureTable Table.Theme
if needsTable Table.ThemeAsset then if needsTable Table.ThemeAsset then
log.LogInformation(creatingTable, Table.ThemeAsset)
do! conn.customNonQuery
$"CREATE TABLE {Table.ThemeAsset} ( $"CREATE TABLE {Table.ThemeAsset} (
theme_id TEXT NOT NULL, theme_id TEXT NOT NULL,
path TEXT NOT NULL, path TEXT NOT NULL,
updated_on TEXT NOT NULL, updated_on TEXT NOT NULL,
data BLOB NOT NULL, data BLOB NOT NULL,
PRIMARY KEY (theme_id, path))" PRIMARY KEY (theme_id, path))" []
// Web log table // Web log table
if needsTable Table.WebLog then jsonTable Table.WebLog if needsTable Table.WebLog then
log.LogInformation(creatingTable, Table.WebLog)
do! conn.ensureTable Table.WebLog
// Category table // Category table
if needsTable Table.Category then if needsTable Table.Category then
$"""{jsonTable Table.Category}; log.LogInformation(creatingTable, Table.Category)
{Query.Definition.ensureIndexOn do! conn.ensureTable Table.Category
Table.Category "web_log" [ nameof Category.Empty.WebLogId ] SQLite}""" do! conn.ensureFieldIndex Table.Category "web_log" [ nameof Category.Empty.WebLogId ]
// Web log user table // Web log user table
if needsTable Table.WebLogUser then if needsTable Table.WebLogUser then
$"""{jsonTable Table.WebLogUser}; log.LogInformation(creatingTable, Table.WebLogUser)
{Query.Definition.ensureIndexOn do! conn.ensureTable Table.WebLogUser
Table.WebLogUser do! conn.ensureFieldIndex
"email" Table.WebLogUser "email" [ nameof WebLogUser.Empty.WebLogId; nameof WebLogUser.Empty.Email ]
[ nameof WebLogUser.Empty.WebLogId; nameof WebLogUser.Empty.Email ]
SQLite}"""
// Page tables // Page tables
if needsTable Table.Page then if needsTable Table.Page then
$"""{jsonTable Table.Page}; log.LogInformation(creatingTable, Table.Page)
{Query.Definition.ensureIndexOn Table.Page "author" [ nameof Page.Empty.AuthorId ] SQLite}; do! conn.ensureTable Table.Page
{Query.Definition.ensureIndexOn do! conn.ensureFieldIndex Table.Page "author" [ nameof Page.Empty.AuthorId ]
Table.Page do! conn.ensureFieldIndex Table.Page "permalink" [ nameof Page.Empty.WebLogId; nameof Page.Empty.Permalink ]
"permalink"
[ nameof Page.Empty.WebLogId; nameof Page.Empty.Permalink ]
SQLite}"""
if needsTable Table.PageRevision then if needsTable Table.PageRevision then
log.LogInformation(creatingTable, Table.PageRevision)
do! conn.customNonQuery
$"CREATE TABLE {Table.PageRevision} ( $"CREATE TABLE {Table.PageRevision} (
page_id TEXT NOT NULL, page_id TEXT NOT NULL,
as_of TEXT NOT NULL, as_of TEXT NOT NULL,
revision_text TEXT NOT NULL, revision_text TEXT NOT NULL,
PRIMARY KEY (page_id, as_of))" PRIMARY KEY (page_id, as_of))" []
// Post tables // Post tables
if needsTable Table.Post then if needsTable Table.Post then
$"""{jsonTable Table.Post}; log.LogInformation(creatingTable, Table.Post)
{Query.Definition.ensureIndexOn Table.Post "author" [ nameof Post.Empty.AuthorId ] SQLite}; do! conn.ensureTable Table.Post
{Query.Definition.ensureIndexOn do! conn.ensureFieldIndex Table.Post "author" [ nameof Post.Empty.AuthorId ]
Table.Post "permalink" [ nameof Post.Empty.WebLogId; nameof Post.Empty.Permalink ] SQLite}; do! conn.ensureFieldIndex Table.Post "permalink" [ nameof Post.Empty.WebLogId; nameof Post.Empty.Permalink ]
{Query.Definition.ensureIndexOn do! conn.ensureFieldIndex
Table.Post Table.Post
"status" "status"
[ nameof Post.Empty.WebLogId; nameof Post.Empty.Status; nameof Post.Empty.UpdatedOn ] [ nameof Post.Empty.WebLogId; nameof Post.Empty.Status; nameof Post.Empty.UpdatedOn ]
SQLite}"""
// TODO: index categories by post? // TODO: index categories by post?
if needsTable Table.PostRevision then if needsTable Table.PostRevision then
log.LogInformation(creatingTable, Table.PostRevision)
do! conn.customNonQuery
$"CREATE TABLE {Table.PostRevision} ( $"CREATE TABLE {Table.PostRevision} (
post_id TEXT NOT NULL, post_id TEXT NOT NULL,
as_of TEXT NOT NULL, as_of TEXT NOT NULL,
revision_text TEXT NOT NULL, revision_text TEXT NOT NULL,
PRIMARY KEY (post_id, as_of))" PRIMARY KEY (post_id, as_of))" []
if needsTable Table.PostComment then if needsTable Table.PostComment then
$"""{jsonTable Table.PostComment}; log.LogInformation(creatingTable, Table.PostComment)
{Query.Definition.ensureIndexOn do! conn.ensureTable Table.PostComment
Table.PostComment "post" [ nameof Comment.Empty.PostId ] SQLite}""" do! conn.ensureFieldIndex Table.PostComment "post" [ nameof Comment.Empty.PostId ]
// Tag map table // Tag map table
if needsTable Table.TagMap then if needsTable Table.TagMap then
$"""{jsonTable Table.TagMap}; log.LogInformation(creatingTable, Table.TagMap)
{Query.Definition.ensureIndexOn do! conn.ensureTable Table.TagMap
Table.TagMap do! conn.ensureFieldIndex Table.TagMap "url" [ nameof TagMap.Empty.WebLogId; nameof TagMap.Empty.UrlValue ]
"url"
[ nameof TagMap.Empty.WebLogId; nameof TagMap.Empty.UrlValue ]
SQLite}"""
// Uploaded file table // Uploaded file table
if needsTable Table.Upload then if needsTable Table.Upload then
log.LogInformation(creatingTable, Table.Upload)
do! conn.customNonQuery
$"CREATE TABLE {Table.Upload} ( $"CREATE TABLE {Table.Upload} (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
web_log_id TEXT NOT NULL, web_log_id TEXT NOT NULL,
path TEXT NOT NULL, path TEXT NOT NULL,
updated_on TEXT NOT NULL, updated_on TEXT NOT NULL,
data BLOB NOT NULL); data BLOB NOT NULL);
CREATE INDEX idx_{Table.Upload}_path ON {Table.Upload} (web_log_id, path)" CREATE INDEX idx_{Table.Upload}_path ON {Table.Upload} (web_log_id, path)" []
// Database version table // Database version table
if needsTable Table.DbVersion then if needsTable Table.DbVersion then
log.LogInformation(creatingTable, Table.DbVersion)
do! conn.customNonQuery
$"CREATE TABLE {Table.DbVersion} (id TEXT PRIMARY KEY); $"CREATE TABLE {Table.DbVersion} (id TEXT PRIMARY KEY);
INSERT INTO {Table.DbVersion} VALUES ('{Utils.Migration.currentDbVersion}')" INSERT INTO {Table.DbVersion} VALUES ('{Utils.Migration.currentDbVersion}')" []
}
|> Seq.map (fun sql ->
log.LogInformation $"""Creating {(sql.Replace("IF NOT EXISTS ", "").Split ' ')[2]} table..."""
conn.customNonQuery sql [])
let! _ = Task.WhenAll tasks
()
} }
/// Set the database version to the specified version /// Set the database version to the specified version

View File

@ -11,7 +11,7 @@
<PackageReference Include="Markdown.ColorCode" Version="2.2.2" /> <PackageReference Include="Markdown.ColorCode" Version="2.2.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NodaTime" Version="3.1.11" /> <PackageReference Include="NodaTime" Version="3.1.11" />
<PackageReference Update="FSharp.Core" Version="8.0.300" /> <PackageReference Update="FSharp.Core" Version="8.0.400" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -28,7 +28,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Expecto" Version="10.2.1" /> <PackageReference Include="Expecto" Version="10.2.1" />
<PackageReference Include="ThrowawayDb.Postgres" Version="1.4.0" /> <PackageReference Include="ThrowawayDb.Postgres" Version="1.4.0" />
<PackageReference Update="FSharp.Core" Version="8.0.300" /> <PackageReference Update="FSharp.Core" Version="8.0.400" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -32,12 +32,12 @@
<PackageReference Include="BitBadger.AspNetCore.CanonicalDomains" Version="1.0.0" /> <PackageReference Include="BitBadger.AspNetCore.CanonicalDomains" Version="1.0.0" />
<PackageReference Include="DotLiquid" Version="2.2.692" /> <PackageReference Include="DotLiquid" Version="2.2.692" />
<PackageReference Include="Giraffe" Version="6.4.0" /> <PackageReference Include="Giraffe" Version="6.4.0" />
<PackageReference Include="Giraffe.Htmx" Version="2.0.0" /> <PackageReference Include="Giraffe.Htmx" Version="2.0.2" />
<PackageReference Include="Giraffe.ViewEngine.Htmx" Version="2.0.0" /> <PackageReference Include="Giraffe.ViewEngine.Htmx" Version="2.0.2" />
<PackageReference Include="NeoSmart.Caching.Sqlite.AspNetCore" Version="8.0.0" /> <PackageReference Include="NeoSmart.Caching.Sqlite.AspNetCore" Version="8.0.0" />
<PackageReference Include="RethinkDB.DistributedCache" Version="1.0.0-rc1" /> <PackageReference Include="RethinkDB.DistributedCache" Version="1.0.0-rc1" />
<PackageReference Include="System.ServiceModel.Syndication" Version="8.0.0" /> <PackageReference Include="System.ServiceModel.Syndication" Version="8.0.0" />
<PackageReference Update="FSharp.Core" Version="8.0.300" /> <PackageReference Update="FSharp.Core" Version="8.0.400" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>