Version 2.1 #41
|
@ -4,12 +4,9 @@
|
|||
<ProjectReference Include="..\MyWebLog.Domain\MyWebLog.Domain.fsproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\BitBadger.Sqlite.Documents\src\BitBadger.Sqlite.FSharp.Documents\BitBadger.Sqlite.FSharp.Documents.fsproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BitBadger.Documents.Postgres" Version="3.0.0-rc-1" />
|
||||
<PackageReference Include="BitBadger.Documents.Sqlite" Version="3.0.0-rc-1" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
namespace MyWebLog.Data.SQLite
|
||||
|
||||
open System.Threading.Tasks
|
||||
open BitBadger.Sqlite.FSharp.Documents
|
||||
open BitBadger.Sqlite.FSharp.Documents.WithConn
|
||||
open BitBadger.Documents
|
||||
open BitBadger.Documents.Sqlite
|
||||
open Microsoft.Data.Sqlite
|
||||
open Microsoft.Extensions.Logging
|
||||
open MyWebLog
|
||||
|
@ -23,11 +23,10 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
|
|||
/// Count all top-level categories for the given web log
|
||||
let countTopLevel webLogId =
|
||||
log.LogTrace "Category.countTopLevel"
|
||||
Custom.scalar
|
||||
conn.customScalar
|
||||
$"{Document.Query.countByWebLog} AND data ->> '{parentIdField}' IS NULL"
|
||||
[ webLogParam webLogId ]
|
||||
(fun rdr -> int (rdr.GetInt64(0)))
|
||||
conn
|
||||
(toCount >> int)
|
||||
|
||||
/// Find all categories for the given web log
|
||||
let findByWebLog webLogId =
|
||||
|
@ -54,9 +53,9 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
|
|||
SELECT COUNT(DISTINCT data ->> '{nameof Post.Empty.Id}')
|
||||
FROM {Table.Post}
|
||||
WHERE {Document.Query.whereByWebLog}
|
||||
AND {Query.whereFieldEquals (nameof Post.Empty.Status) $"'{string Published}'"}
|
||||
AND {Query.whereByField (nameof Post.Empty.Status) EQ $"'{string Published}'"}
|
||||
AND {catSql}"""
|
||||
let! postCount = Custom.scalar query (webLogParam webLogId :: catParams) (_.GetInt64(0)) conn
|
||||
let! postCount = conn.customScalar query (webLogParam webLogId :: catParams) toCount
|
||||
return it.Id, int postCount
|
||||
})
|
||||
|> Task.WhenAll
|
||||
|
@ -80,13 +79,13 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
|
|||
match! findById catId webLogId with
|
||||
| Some cat ->
|
||||
// Reassign any children to the category's parent category
|
||||
let! children = Count.byFieldEquals Table.Category parentIdField catId conn
|
||||
let! children = conn.countByField Table.Category parentIdField EQ catId
|
||||
if children > 0 then
|
||||
do! Update.partialByFieldEquals Table.Category parentIdField catId {| ParentId = cat.ParentId |} conn
|
||||
do! conn.patchByField Table.Category parentIdField EQ catId {| ParentId = cat.ParentId |}
|
||||
// Delete the category off all posts where it is assigned, and the category itself
|
||||
let catIdField = Post.Empty.CategoryIds
|
||||
let! posts =
|
||||
Custom.list
|
||||
conn.customList
|
||||
$"SELECT data ->> '{Post.Empty.Id}', data -> '{catIdField}'
|
||||
FROM {Table.Post}
|
||||
WHERE {Document.Query.whereByWebLog}
|
||||
|
@ -96,11 +95,10 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
|
|||
WHERE json_each.value = @id)"
|
||||
[ idParam catId; webLogParam webLogId ]
|
||||
(fun rdr -> rdr.GetString(0), Utils.deserialize<string list> ser (rdr.GetString(1)))
|
||||
conn
|
||||
for postId, cats in posts do
|
||||
do! Update.partialById
|
||||
Table.Post postId {| CategoryIds = cats |> List.filter (fun it -> it <> string catId) |} conn
|
||||
do! Delete.byId Table.Category catId conn
|
||||
do! conn.patchById
|
||||
Table.Post postId {| CategoryIds = cats |> List.filter (fun it -> it <> string catId) |}
|
||||
do! conn.deleteById Table.Category catId
|
||||
return if children = 0L then CategoryDeleted else ReassignedChildCategories
|
||||
| None -> return CategoryNotFound
|
||||
}
|
||||
|
@ -108,7 +106,7 @@ type SQLiteCategoryData(conn: SqliteConnection, ser: JsonSerializer, log: ILogge
|
|||
/// Save a category
|
||||
let save cat =
|
||||
log.LogTrace "Category.save"
|
||||
save<Category> Table.Category cat conn
|
||||
conn.save<Category> Table.Category cat
|
||||
|
||||
/// Restore categories from a backup
|
||||
let restore cats = backgroundTask {
|
||||
|
|
|
@ -222,17 +222,14 @@ module Map =
|
|||
let sqlParam name (value: obj) =
|
||||
SqliteParameter(name, value)
|
||||
|
||||
/// Create a document ID parameter
|
||||
let idParam (key: 'TKey) =
|
||||
sqlParam "@id" (string key)
|
||||
|
||||
/// Create a web log ID parameter
|
||||
let webLogParam (webLogId: WebLogId) =
|
||||
sqlParam "@webLogId" (string webLogId)
|
||||
|
||||
|
||||
open BitBadger.Sqlite.FSharp.Documents
|
||||
open BitBadger.Sqlite.FSharp.Documents.WithConn
|
||||
open BitBadger.Documents
|
||||
open BitBadger.Documents.Sqlite
|
||||
open BitBadger.Documents.Sqlite.WithConn
|
||||
|
||||
/// Functions for manipulating documents
|
||||
module Document =
|
||||
|
@ -242,7 +239,7 @@ module Document =
|
|||
|
||||
/// Fragment to add a web log ID condition to a WHERE clause (parameter @webLogId)
|
||||
let whereByWebLog =
|
||||
Query.whereFieldEquals "WebLogId" "@webLogId"
|
||||
Query.whereByField "WebLogId" EQ "@webLogId"
|
||||
|
||||
/// A SELECT query to count documents for a given web log ID
|
||||
let countByWebLog table =
|
||||
|
@ -250,7 +247,7 @@ module Document =
|
|||
|
||||
/// A query to select from a table by the document's ID and its web log ID
|
||||
let selectByIdAndWebLog table =
|
||||
$"{Query.Find.byFieldEquals table} AND {whereByWebLog}"
|
||||
$"{Query.Find.byId table} AND {whereByWebLog}"
|
||||
|
||||
/// A query to select from a table by its web log ID
|
||||
let selectByWebLog table =
|
||||
|
@ -258,7 +255,7 @@ module Document =
|
|||
|
||||
/// Count documents for the given web log ID
|
||||
let countByWebLog table (webLogId: WebLogId) conn = backgroundTask {
|
||||
let! count = Count.byFieldEquals table "WebLogId" webLogId conn
|
||||
let! count = Count.byField table "WebLogId" EQ webLogId conn
|
||||
return int count
|
||||
}
|
||||
|
||||
|
@ -268,7 +265,7 @@ module Document =
|
|||
|
||||
/// Find documents for the given web log
|
||||
let findByWebLog<'TDoc> table (webLogId: WebLogId) conn =
|
||||
Find.byFieldEquals<'TDoc> table "WebLogId" webLogId conn
|
||||
Find.byField<'TDoc> table "WebLogId" EQ webLogId conn
|
||||
|
||||
|
||||
/// Functions to support revisions
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
namespace MyWebLog.Data.SQLite
|
||||
|
||||
open System.Threading.Tasks
|
||||
open BitBadger.Sqlite.FSharp.Documents
|
||||
open BitBadger.Sqlite.FSharp.Documents.WithConn
|
||||
open BitBadger.Documents
|
||||
open BitBadger.Documents.Sqlite
|
||||
open Microsoft.Data.Sqlite
|
||||
open Microsoft.Extensions.Logging
|
||||
open MyWebLog
|
||||
|
@ -39,11 +39,10 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
|
|||
/// Get all pages for a web log (without text or revisions)
|
||||
let all webLogId =
|
||||
log.LogTrace "Page.all"
|
||||
Custom.list
|
||||
conn.customList
|
||||
$"{Query.selectFromTable Table.Page} WHERE {Document.Query.whereByWebLog} ORDER BY LOWER({titleField})"
|
||||
[ webLogParam webLogId ]
|
||||
(fun rdr -> { fromData<Page> rdr with Text = "" })
|
||||
conn
|
||||
|
||||
/// Count all pages for the given web log
|
||||
let countAll webLogId =
|
||||
|
@ -53,11 +52,10 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
|
|||
/// Count all pages shown in the page list for the given web log
|
||||
let countListed webLogId =
|
||||
log.LogTrace "Page.countListed"
|
||||
Custom.scalar
|
||||
$"""{Document.Query.countByWebLog} AND {Query.whereFieldEquals pgListName "'true'"}"""
|
||||
conn.customScalar
|
||||
$"""{Document.Query.countByWebLog} AND {Query.whereByField pgListName EQ "'true'"}"""
|
||||
[ webLogParam webLogId ]
|
||||
(fun rdr -> int (rdr.GetInt64(0)))
|
||||
conn
|
||||
(toCount >> int)
|
||||
|
||||
/// Find a page by its ID (without revisions)
|
||||
let findById pageId webLogId =
|
||||
|
@ -80,10 +78,9 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
|
|||
log.LogTrace "Page.delete"
|
||||
match! findById pageId webLogId with
|
||||
| Some _ ->
|
||||
do! Custom.nonQuery
|
||||
do! conn.customNonQuery
|
||||
$"DELETE FROM {Table.PageRevision} WHERE page_id = @id; {Query.Delete.byId Table.Page}"
|
||||
[ idParam pageId ]
|
||||
conn
|
||||
return true
|
||||
| None -> return false
|
||||
}
|
||||
|
@ -91,23 +88,21 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
|
|||
/// Find a page by its permalink for the given web log
|
||||
let findByPermalink (permalink: Permalink) webLogId =
|
||||
log.LogTrace "Page.findByPermalink"
|
||||
Custom.single
|
||||
$"""{Document.Query.selectByWebLog} AND {Query.whereFieldEquals linkName "@link"}"""
|
||||
conn.customSingle
|
||||
$"""{Document.Query.selectByWebLog} AND {Query.whereByField linkName EQ "@link"}"""
|
||||
[ webLogParam webLogId; SqliteParameter("@link", string permalink) ]
|
||||
fromData<Page>
|
||||
conn
|
||||
|
||||
/// Find the current permalink within a set of potential prior permalinks for the given web log
|
||||
let findCurrentPermalink (permalinks: Permalink list) webLogId =
|
||||
log.LogTrace "Page.findCurrentPermalink"
|
||||
let linkSql, linkParams = inJsonArray Table.Page (nameof Page.Empty.PriorPermalinks) "link" permalinks
|
||||
Custom.single
|
||||
conn.customSingle
|
||||
$"SELECT data ->> '{linkName}' AS permalink
|
||||
FROM {Table.Page}
|
||||
WHERE {Document.Query.whereByWebLog} AND {linkSql}"
|
||||
(webLogParam webLogId :: linkParams)
|
||||
Map.toPermalink
|
||||
conn
|
||||
|
||||
/// Get all complete pages for the given web log
|
||||
let findFullByWebLog webLogId = backgroundTask {
|
||||
|
@ -120,27 +115,25 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
|
|||
/// Get all listed pages for the given web log (without revisions or text)
|
||||
let findListed webLogId =
|
||||
log.LogTrace "Page.findListed"
|
||||
Custom.list
|
||||
$"""{Document.Query.selectByWebLog Table.Page} AND {Query.whereFieldEquals pgListName "'true'"}
|
||||
conn.customList
|
||||
$"""{Document.Query.selectByWebLog Table.Page} AND {Query.whereByField pgListName EQ "'true'"}
|
||||
ORDER BY LOWER({titleField})"""
|
||||
[ webLogParam webLogId ]
|
||||
(fun rdr -> { fromData<Page> rdr with Text = "" })
|
||||
conn
|
||||
|
||||
/// Get a page of pages for the given web log (without revisions)
|
||||
let findPageOfPages webLogId pageNbr =
|
||||
log.LogTrace "Page.findPageOfPages"
|
||||
Custom.list
|
||||
conn.customList
|
||||
$"{Document.Query.selectByWebLog Table.Page} ORDER BY LOWER({titleField}) LIMIT @pageSize OFFSET @toSkip"
|
||||
[ webLogParam webLogId; SqliteParameter("@pageSize", 26); SqliteParameter("@toSkip", (pageNbr - 1) * 25) ]
|
||||
fromData<Page>
|
||||
conn
|
||||
|
||||
/// Save a page
|
||||
let save (page: Page) = backgroundTask {
|
||||
log.LogTrace "Page.update"
|
||||
let! oldPage = findFullById page.Id page.WebLogId
|
||||
do! save Table.Page { page with Revisions = [] } conn
|
||||
do! conn.save Table.Page { page with Revisions = [] }
|
||||
do! updatePageRevisions page.Id (match oldPage with Some p -> p.Revisions | None -> []) page.Revisions
|
||||
}
|
||||
|
||||
|
@ -155,7 +148,7 @@ type SQLitePageData(conn: SqliteConnection, log: ILogger) =
|
|||
log.LogTrace "Page.updatePriorPermalinks"
|
||||
match! findById pageId webLogId with
|
||||
| Some _ ->
|
||||
do! Update.partialById Table.Page pageId {| PriorPermalinks = permalinks |} conn
|
||||
do! conn.patchById Table.Page pageId {| PriorPermalinks = permalinks |}
|
||||
return true
|
||||
| None -> return false
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
namespace MyWebLog.Data.SQLite
|
||||
|
||||
open System.Threading.Tasks
|
||||
open BitBadger.Sqlite.FSharp.Documents
|
||||
open BitBadger.Sqlite.FSharp.Documents.WithConn
|
||||
open BitBadger.Documents
|
||||
open BitBadger.Documents.Sqlite
|
||||
open Microsoft.Data.Sqlite
|
||||
open Microsoft.Extensions.Logging
|
||||
open MyWebLog
|
||||
|
@ -34,7 +34,7 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
|
|||
let postByWebLog = Document.Query.selectByWebLog Table.Post
|
||||
|
||||
/// The SELECT statement to retrieve published posts with a web log ID parameter
|
||||
let publishedPostByWebLog = $"""{postByWebLog} AND {Query.whereFieldEquals statName $"'{string Published}'"}"""
|
||||
let publishedPostByWebLog = $"""{postByWebLog} AND {Query.whereByField statName EQ $"'{string Published}'"}"""
|
||||
|
||||
/// Update a post's revisions
|
||||
let updatePostRevisions (postId: PostId) oldRevs newRevs =
|
||||
|
@ -46,11 +46,10 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
|
|||
/// Count posts in a status for the given web log
|
||||
let countByStatus (status: PostStatus) webLogId =
|
||||
log.LogTrace "Post.countByStatus"
|
||||
Custom.scalar
|
||||
$"""{Document.Query.countByWebLog} AND {Query.whereFieldEquals statName "@status"}"""
|
||||
conn.customScalar
|
||||
$"""{Document.Query.countByWebLog} AND {Query.whereByField statName EQ "@status"}"""
|
||||
[ webLogParam webLogId; SqliteParameter("@status", string status) ]
|
||||
(fun rdr -> int (rdr.GetInt64(0)))
|
||||
conn
|
||||
(toCount >> int)
|
||||
|
||||
/// Find a post by its ID for the given web log (excluding revisions)
|
||||
let findById postId webLogId =
|
||||
|
@ -60,11 +59,10 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
|
|||
/// Find a post by its permalink for the given web log (excluding revisions)
|
||||
let findByPermalink (permalink: Permalink) webLogId =
|
||||
log.LogTrace "Post.findByPermalink"
|
||||
Custom.single
|
||||
$"""{Document.Query.selectByWebLog Table.Post} AND {Query.whereFieldEquals linkName "@link"}"""
|
||||
conn.customSingle
|
||||
$"""{Document.Query.selectByWebLog Table.Post} AND {Query.whereByField linkName EQ "@link"}"""
|
||||
[ webLogParam webLogId; SqliteParameter("@link", string permalink) ]
|
||||
fromData<Post>
|
||||
conn
|
||||
|
||||
/// Find a complete post by its ID for the given web log
|
||||
let findFullById postId webLogId = backgroundTask {
|
||||
|
@ -81,13 +79,12 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
|
|||
log.LogTrace "Post.delete"
|
||||
match! findById postId webLogId with
|
||||
| Some _ ->
|
||||
do! Custom.nonQuery
|
||||
do! conn.customNonQuery
|
||||
$"""DELETE FROM {Table.PostRevision} WHERE post_id = @id;
|
||||
DELETE FROM {Table.PostComment}
|
||||
WHERE {Query.whereFieldEquals (nameof Comment.Empty.PostId) "@id"};
|
||||
WHERE {Query.whereByField (nameof Comment.Empty.PostId) EQ "@id"};
|
||||
{Query.Delete.byId Table.Post}"""
|
||||
[ idParam postId ]
|
||||
conn
|
||||
return true
|
||||
| None -> return false
|
||||
}
|
||||
|
@ -96,13 +93,12 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
|
|||
let findCurrentPermalink (permalinks: Permalink list) webLogId =
|
||||
log.LogTrace "Post.findCurrentPermalink"
|
||||
let linkSql, linkParams = inJsonArray Table.Post (nameof Post.Empty.PriorPermalinks) "link" permalinks
|
||||
Custom.single
|
||||
conn.customSingle
|
||||
$"SELECT data ->> '{linkName}' AS permalink
|
||||
FROM {Table.Post}
|
||||
WHERE {Document.Query.whereByWebLog} AND {linkSql}"
|
||||
(webLogParam webLogId :: linkParams)
|
||||
Map.toPermalink
|
||||
conn
|
||||
|
||||
/// Get all complete posts for the given web log
|
||||
let findFullByWebLog webLogId = backgroundTask {
|
||||
|
@ -116,63 +112,57 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
|
|||
let findPageOfCategorizedPosts webLogId (categoryIds: CategoryId list) pageNbr postsPerPage =
|
||||
log.LogTrace "Post.findPageOfCategorizedPosts"
|
||||
let catSql, catParams = inJsonArray Table.Post (nameof Post.Empty.CategoryIds) "catId" categoryIds
|
||||
Custom.list
|
||||
conn.customList
|
||||
$"{publishedPostByWebLog} AND {catSql}
|
||||
ORDER BY {publishField} DESC
|
||||
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
|
||||
(webLogParam webLogId :: catParams)
|
||||
fromData<Post>
|
||||
conn
|
||||
|
||||
/// Get a page of posts for the given web log (excludes text and revisions)
|
||||
let findPageOfPosts webLogId pageNbr postsPerPage =
|
||||
log.LogTrace "Post.findPageOfPosts"
|
||||
Custom.list
|
||||
conn.customList
|
||||
$"{postByWebLog}
|
||||
ORDER BY {publishField} DESC NULLS FIRST, data ->> '{nameof Post.Empty.UpdatedOn}'
|
||||
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
|
||||
[ webLogParam webLogId ]
|
||||
(fun rdr -> { fromData<Post> rdr with Text = "" })
|
||||
conn
|
||||
|
||||
/// Get a page of published posts for the given web log (excludes revisions)
|
||||
let findPageOfPublishedPosts webLogId pageNbr postsPerPage =
|
||||
log.LogTrace "Post.findPageOfPublishedPosts"
|
||||
Custom.list
|
||||
conn.customList
|
||||
$"{publishedPostByWebLog}
|
||||
ORDER BY {publishField} DESC
|
||||
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
|
||||
[ webLogParam webLogId ]
|
||||
fromData<Post>
|
||||
conn
|
||||
|
||||
/// Get a page of tagged posts for the given web log (excludes revisions)
|
||||
let findPageOfTaggedPosts webLogId (tag : string) pageNbr postsPerPage =
|
||||
log.LogTrace "Post.findPageOfTaggedPosts"
|
||||
let tagSql, tagParams = inJsonArray Table.Post (nameof Post.Empty.Tags) "tag" [ tag ]
|
||||
Custom.list
|
||||
conn.customList
|
||||
$"{publishedPostByWebLog} AND {tagSql}
|
||||
ORDER BY p.published_on DESC
|
||||
LIMIT {postsPerPage + 1} OFFSET {(pageNbr - 1) * postsPerPage}"
|
||||
(webLogParam webLogId :: tagParams)
|
||||
fromData<Post>
|
||||
conn
|
||||
|
||||
/// Find the next newest and oldest post from a publish date for the given web log
|
||||
let findSurroundingPosts webLogId (publishedOn : Instant) = backgroundTask {
|
||||
log.LogTrace "Post.findSurroundingPosts"
|
||||
let! older =
|
||||
Custom.single
|
||||
conn.customSingle
|
||||
$"{publishedPostByWebLog} AND {publishField} < @publishedOn ORDER BY {publishField} DESC LIMIT 1"
|
||||
[ webLogParam webLogId; SqliteParameter("@publishedOn", instantParam publishedOn) ]
|
||||
fromData<Post>
|
||||
conn
|
||||
let! newer =
|
||||
Custom.single
|
||||
conn.customSingle
|
||||
$"{publishedPostByWebLog} AND {publishField} > @publishedOn ORDER BY {publishField} LIMIT 1"
|
||||
[ webLogParam webLogId; SqliteParameter("@publishedOn", instantParam publishedOn) ]
|
||||
fromData<Post>
|
||||
conn
|
||||
return older, newer
|
||||
}
|
||||
|
||||
|
@ -180,7 +170,7 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
|
|||
let save (post: Post) = backgroundTask {
|
||||
log.LogTrace "Post.save"
|
||||
let! oldPost = findFullById post.Id post.WebLogId
|
||||
do! save Table.Post { post with Revisions = [] } conn
|
||||
do! conn.save Table.Post { post with Revisions = [] }
|
||||
do! updatePostRevisions post.Id (match oldPost with Some p -> p.Revisions | None -> []) post.Revisions
|
||||
}
|
||||
|
||||
|
@ -194,7 +184,7 @@ type SQLitePostData(conn: SqliteConnection, log: ILogger) =
|
|||
let updatePriorPermalinks postId webLogId (permalinks: Permalink list) = backgroundTask {
|
||||
match! findById postId webLogId with
|
||||
| Some _ ->
|
||||
do! Update.partialById Table.Post postId {| PriorPermalinks = permalinks |} conn
|
||||
do! conn.patchById Table.Post postId {| PriorPermalinks = permalinks |}
|
||||
return true
|
||||
| None -> return false
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
namespace MyWebLog.Data.SQLite
|
||||
|
||||
open BitBadger.Sqlite.FSharp.Documents
|
||||
open BitBadger.Sqlite.FSharp.Documents.WithConn
|
||||
open BitBadger.Documents
|
||||
open BitBadger.Documents.Sqlite
|
||||
open Microsoft.Data.Sqlite
|
||||
open Microsoft.Extensions.Logging
|
||||
open MyWebLog
|
||||
|
@ -20,7 +20,7 @@ type SQLiteTagMapData(conn: SqliteConnection, log: ILogger) =
|
|||
log.LogTrace "TagMap.delete"
|
||||
match! findById tagMapId webLogId with
|
||||
| Some _ ->
|
||||
do! Delete.byId Table.TagMap tagMapId conn
|
||||
do! conn.deleteById Table.TagMap tagMapId
|
||||
return true
|
||||
| None -> return false
|
||||
}
|
||||
|
@ -28,12 +28,11 @@ type SQLiteTagMapData(conn: SqliteConnection, log: ILogger) =
|
|||
/// Find a tag mapping by its URL value for the given web log
|
||||
let findByUrlValue (urlValue: string) webLogId =
|
||||
log.LogTrace "TagMap.findByUrlValue"
|
||||
Custom.single
|
||||
conn.customSingle
|
||||
$"""{Document.Query.selectByWebLog Table.TagMap}
|
||||
AND {Query.whereFieldEquals (nameof TagMap.Empty.UrlValue) "@urlValue"}"""
|
||||
AND {Query.whereByField (nameof TagMap.Empty.UrlValue) EQ "@urlValue"}"""
|
||||
[ webLogParam webLogId; SqliteParameter("@urlValue", urlValue) ]
|
||||
fromData<TagMap>
|
||||
conn
|
||||
|
||||
/// Get all tag mappings for the given web log
|
||||
let findByWebLog webLogId =
|
||||
|
@ -44,16 +43,15 @@ type SQLiteTagMapData(conn: SqliteConnection, log: ILogger) =
|
|||
let findMappingForTags (tags: string list) webLogId =
|
||||
log.LogTrace "TagMap.findMappingForTags"
|
||||
let mapSql, mapParams = inClause $"AND data ->> '{nameof TagMap.Empty.Tag}'" "tag" id tags
|
||||
Custom.list
|
||||
conn.customList
|
||||
$"{Document.Query.selectByWebLog Table.TagMap} {mapSql}"
|
||||
(webLogParam webLogId :: mapParams)
|
||||
fromData<TagMap>
|
||||
conn
|
||||
|
||||
/// Save a tag mapping
|
||||
let save (tagMap: TagMap) =
|
||||
log.LogTrace "TagMap.save"
|
||||
save Table.TagMap tagMap conn
|
||||
conn.save Table.TagMap tagMap
|
||||
|
||||
/// Restore tag mappings from a backup
|
||||
let restore tagMaps = backgroundTask {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
namespace MyWebLog.Data.SQLite
|
||||
|
||||
open BitBadger.Sqlite.FSharp.Documents
|
||||
open BitBadger.Sqlite.FSharp.Documents.WithConn
|
||||
open BitBadger.Documents
|
||||
open BitBadger.Documents.Sqlite
|
||||
open Microsoft.Data.Sqlite
|
||||
open Microsoft.Extensions.Logging
|
||||
open MyWebLog
|
||||
|
@ -25,36 +25,34 @@ type SQLiteThemeData(conn : SqliteConnection, log: ILogger) =
|
|||
/// Retrieve all themes (except 'admin'; excludes template text)
|
||||
let all () =
|
||||
log.LogTrace "Theme.all"
|
||||
Custom.list
|
||||
conn.customList
|
||||
$"{Query.selectFromTable Table.Theme} WHERE {idField} <> 'admin' ORDER BY {idField}"
|
||||
[]
|
||||
withoutTemplateText
|
||||
conn
|
||||
|
||||
/// Does a given theme exist?
|
||||
let exists (themeId: ThemeId) =
|
||||
log.LogTrace "Theme.exists"
|
||||
Exists.byId Table.Theme themeId conn
|
||||
conn.existsById Table.Theme themeId
|
||||
|
||||
/// Find a theme by its ID
|
||||
let findById themeId =
|
||||
log.LogTrace "Theme.findById"
|
||||
Find.byId<ThemeId, Theme> Table.Theme themeId conn
|
||||
conn.findById<ThemeId, Theme> Table.Theme themeId
|
||||
|
||||
/// Find a theme by its ID (excludes the text of templates)
|
||||
let findByIdWithoutText (themeId: ThemeId) =
|
||||
log.LogTrace "Theme.findByIdWithoutText"
|
||||
Custom.single (Query.Find.byId Table.Theme) [ idParam themeId ] withoutTemplateText conn
|
||||
conn.customSingle (Query.Find.byId Table.Theme) [ idParam themeId ] withoutTemplateText
|
||||
|
||||
/// Delete a theme by its ID
|
||||
let delete themeId = backgroundTask {
|
||||
log.LogTrace "Theme.delete"
|
||||
match! findByIdWithoutText themeId with
|
||||
| Some _ ->
|
||||
do! Custom.nonQuery
|
||||
do! conn.customNonQuery
|
||||
$"DELETE FROM {Table.ThemeAsset} WHERE theme_id = @id; {Query.Delete.byId Table.Theme}"
|
||||
[ idParam themeId ]
|
||||
conn
|
||||
return true
|
||||
| None -> return false
|
||||
}
|
||||
|
@ -62,7 +60,7 @@ type SQLiteThemeData(conn : SqliteConnection, log: ILogger) =
|
|||
/// Save a theme
|
||||
let save (theme: Theme) =
|
||||
log.LogTrace "Theme.save"
|
||||
save Table.Theme theme conn
|
||||
conn.save Table.Theme theme
|
||||
|
||||
interface IThemeData with
|
||||
member _.All() = all ()
|
||||
|
@ -86,44 +84,41 @@ type SQLiteThemeAssetData(conn : SqliteConnection, log: ILogger) =
|
|||
/// Get all theme assets (excludes data)
|
||||
let all () =
|
||||
log.LogTrace "ThemeAsset.all"
|
||||
Custom.list $"SELECT theme_id, path, updated_on FROM {Table.ThemeAsset}" [] (Map.toThemeAsset false) conn
|
||||
conn.customList $"SELECT theme_id, path, updated_on FROM {Table.ThemeAsset}" [] (Map.toThemeAsset false)
|
||||
|
||||
/// Delete all assets for the given theme
|
||||
let deleteByTheme (themeId: ThemeId) =
|
||||
log.LogTrace "ThemeAsset.deleteByTheme"
|
||||
Custom.nonQuery $"DELETE FROM {Table.ThemeAsset} WHERE theme_id = @id" [ idParam themeId ] conn
|
||||
conn.customNonQuery $"DELETE FROM {Table.ThemeAsset} WHERE theme_id = @id" [ idParam themeId ]
|
||||
|
||||
/// Find a theme asset by its ID
|
||||
let findById assetId =
|
||||
log.LogTrace "ThemeAsset.findById"
|
||||
Custom.single
|
||||
conn.customSingle
|
||||
$"SELECT *, ROWID FROM {Table.ThemeAsset} WHERE theme_id = @id AND path = @path"
|
||||
(assetIdParams assetId)
|
||||
(Map.toThemeAsset true)
|
||||
conn
|
||||
|
||||
/// Get theme assets for the given theme (excludes data)
|
||||
let findByTheme (themeId: ThemeId) =
|
||||
log.LogTrace "ThemeAsset.findByTheme"
|
||||
Custom.list
|
||||
conn.customList
|
||||
$"SELECT theme_id, path, updated_on FROM {Table.ThemeAsset} WHERE theme_id = @id"
|
||||
[ idParam themeId ]
|
||||
(Map.toThemeAsset false)
|
||||
conn
|
||||
|
||||
/// Get theme assets for the given theme
|
||||
let findByThemeWithData (themeId: ThemeId) =
|
||||
log.LogTrace "ThemeAsset.findByThemeWithData"
|
||||
Custom.list
|
||||
conn.customList
|
||||
$"SELECT *, ROWID FROM {Table.ThemeAsset} WHERE theme_id = @id"
|
||||
[ idParam themeId ]
|
||||
(Map.toThemeAsset true)
|
||||
conn
|
||||
|
||||
/// Save a theme asset
|
||||
let save (asset: ThemeAsset) = backgroundTask {
|
||||
log.LogTrace "ThemeAsset.save"
|
||||
do! Custom.nonQuery
|
||||
do! conn.customNonQuery
|
||||
$"INSERT INTO {Table.ThemeAsset} (
|
||||
theme_id, path, updated_on, data
|
||||
) VALUES (
|
||||
|
@ -134,14 +129,12 @@ type SQLiteThemeAssetData(conn : SqliteConnection, log: ILogger) =
|
|||
[ sqlParam "@updatedOn" (instantParam asset.UpdatedOn)
|
||||
sqlParam "@dataLength" asset.Data.Length
|
||||
yield! (assetIdParams asset.Id) ]
|
||||
conn
|
||||
|
||||
let! rowId =
|
||||
Custom.scalar
|
||||
conn.customScalar
|
||||
$"SELECT ROWID FROM {Table.ThemeAsset} WHERE theme_id = @id AND path = @path"
|
||||
(assetIdParams asset.Id)
|
||||
(_.GetInt64(0))
|
||||
conn
|
||||
_.GetInt64(0)
|
||||
use dataStream = new MemoryStream(asset.Data)
|
||||
use blobStream = new SqliteBlob(conn, Table.ThemeAsset, "data", rowId)
|
||||
do! dataStream.CopyToAsync blobStream
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
namespace MyWebLog.Data.SQLite
|
||||
|
||||
open System.IO
|
||||
open BitBadger.Sqlite.FSharp.Documents.WithConn
|
||||
open BitBadger.Documents.Sqlite
|
||||
open Microsoft.Data.Sqlite
|
||||
open Microsoft.Extensions.Logging
|
||||
open MyWebLog
|
||||
|
@ -13,7 +13,7 @@ type SQLiteUploadData(conn: SqliteConnection, log: ILogger) =
|
|||
/// Save an uploaded file
|
||||
let add (upload: Upload) = backgroundTask {
|
||||
log.LogTrace "Upload.add"
|
||||
do! Custom.nonQuery
|
||||
do! conn.customNonQuery
|
||||
$"INSERT INTO {Table.Upload} (
|
||||
id, web_log_id, path, updated_on, data
|
||||
) VALUES (
|
||||
|
@ -24,9 +24,8 @@ type SQLiteUploadData(conn: SqliteConnection, log: ILogger) =
|
|||
sqlParam "@path" (string upload.Path)
|
||||
sqlParam "@updatedOn" (instantParam upload.UpdatedOn)
|
||||
sqlParam "@dataLength" upload.Data.Length ]
|
||||
conn
|
||||
let! rowId =
|
||||
Custom.scalar $"SELECT ROWID FROM {Table.Upload} WHERE id = @id" [ idParam upload.Id ] (_.GetInt64(0)) conn
|
||||
conn.customScalar $"SELECT ROWID FROM {Table.Upload} WHERE id = @id" [ idParam upload.Id ] _.GetInt64(0)
|
||||
use dataStream = new MemoryStream(upload.Data)
|
||||
use blobStream = new SqliteBlob(conn, Table.Upload, "data", rowId)
|
||||
do! dataStream.CopyToAsync blobStream
|
||||
|
@ -36,14 +35,13 @@ type SQLiteUploadData(conn: SqliteConnection, log: ILogger) =
|
|||
let delete (uploadId: UploadId) webLogId = backgroundTask {
|
||||
log.LogTrace "Upload.delete"
|
||||
let! upload =
|
||||
Custom.single
|
||||
conn.customSingle
|
||||
$"SELECT id, web_log_id, path, updated_on FROM {Table.Upload} WHERE id = @id AND web_log_id = @webLogId"
|
||||
[ idParam uploadId; webLogParam webLogId ]
|
||||
(Map.toUpload false)
|
||||
conn
|
||||
match upload with
|
||||
| Some up ->
|
||||
do! Custom.nonQuery $"DELETE FROM {Table.Upload} WHERE id = @id" [ idParam up.Id ] conn
|
||||
do! conn.customNonQuery $"DELETE FROM {Table.Upload} WHERE id = @id" [ idParam up.Id ]
|
||||
return Ok (string up.Path)
|
||||
| None -> return Error $"Upload ID {string uploadId} not found"
|
||||
}
|
||||
|
@ -51,29 +49,26 @@ type SQLiteUploadData(conn: SqliteConnection, log: ILogger) =
|
|||
/// Find an uploaded file by its path for the given web log
|
||||
let findByPath (path: string) webLogId =
|
||||
log.LogTrace "Upload.findByPath"
|
||||
Custom.single
|
||||
conn.customSingle
|
||||
$"SELECT *, ROWID FROM {Table.Upload} WHERE web_log_id = @webLogId AND path = @path"
|
||||
[ webLogParam webLogId; sqlParam "@path" path ]
|
||||
(Map.toUpload true)
|
||||
conn
|
||||
|
||||
/// Find all uploaded files for the given web log (excludes data)
|
||||
let findByWebLog webLogId =
|
||||
log.LogTrace "Upload.findByWebLog"
|
||||
Custom.list
|
||||
conn.customList
|
||||
$"SELECT id, web_log_id, path, updated_on FROM {Table.Upload} WHERE web_log_id = @webLogId"
|
||||
[ webLogParam webLogId ]
|
||||
(Map.toUpload false)
|
||||
conn
|
||||
|
||||
/// Find all uploaded files for the given web log
|
||||
let findByWebLogWithData webLogId =
|
||||
log.LogTrace "Upload.findByWebLogWithData"
|
||||
Custom.list
|
||||
conn.customList
|
||||
$"SELECT *, ROWID FROM {Table.Upload} WHERE web_log_id = @webLogId"
|
||||
[ webLogParam webLogId ]
|
||||
(Map.toUpload true)
|
||||
conn
|
||||
|
||||
/// Restore uploads from a backup
|
||||
let restore uploads = backgroundTask {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
namespace MyWebLog.Data.SQLite
|
||||
|
||||
open BitBadger.Sqlite.FSharp.Documents
|
||||
open BitBadger.Sqlite.FSharp.Documents.WithConn
|
||||
open BitBadger.Documents
|
||||
open BitBadger.Documents.Sqlite
|
||||
open Microsoft.Data.Sqlite
|
||||
open Microsoft.Extensions.Logging
|
||||
open MyWebLog
|
||||
|
@ -13,56 +13,55 @@ type SQLiteWebLogData(conn: SqliteConnection, log: ILogger) =
|
|||
/// Add a web log
|
||||
let add webLog =
|
||||
log.LogTrace "WebLog.add"
|
||||
insert<WebLog> Table.WebLog webLog conn
|
||||
conn.insert<WebLog> Table.WebLog webLog
|
||||
|
||||
/// Retrieve all web logs
|
||||
let all () =
|
||||
log.LogTrace "WebLog.all"
|
||||
Find.all<WebLog> Table.WebLog conn
|
||||
conn.findAll<WebLog> Table.WebLog
|
||||
|
||||
/// Delete a web log by its ID
|
||||
let delete webLogId =
|
||||
log.LogTrace "WebLog.delete"
|
||||
let subQuery table =
|
||||
$"""(SELECT data ->> 'Id' FROM {table} WHERE {Query.whereFieldEquals "WebLogId" "@webLogId"}"""
|
||||
$"""(SELECT data ->> 'Id' FROM {table} WHERE {Query.whereByField "WebLogId" EQ "@webLogId"}"""
|
||||
Custom.nonQuery
|
||||
$"""DELETE FROM {Table.PostComment} WHERE data ->> 'PostId' IN {subQuery Table.Post};
|
||||
DELETE FROM {Table.PostRevision} WHERE post_id IN {subQuery Table.Post};
|
||||
DELETE FROM {Table.PageRevision} WHERE page_id IN {subQuery Table.Page};
|
||||
DELETE FROM {Table.Post} WHERE {Query.whereFieldEquals "WebLogId" "@webLogId"};
|
||||
DELETE FROM {Table.Page} WHERE {Query.whereFieldEquals "WebLogId" "@webLogId"};
|
||||
DELETE FROM {Table.Category} WHERE {Query.whereFieldEquals "WebLogId" "@webLogId"};
|
||||
DELETE FROM {Table.TagMap} WHERE {Query.whereFieldEquals "WebLogId" "@webLogId"};
|
||||
DELETE FROM {Table.Upload} WHERE web_log_id = @id;
|
||||
DELETE FROM {Table.WebLogUser} WHERE {Query.whereFieldEquals "WebLogId" "@webLogId"};
|
||||
DELETE FROM {Table.Post} WHERE {Query.whereByField "WebLogId" EQ "@webLogId"};
|
||||
DELETE FROM {Table.Page} WHERE {Query.whereByField "WebLogId" EQ "@webLogId"};
|
||||
DELETE FROM {Table.Category} WHERE {Query.whereByField "WebLogId" EQ "@webLogId"};
|
||||
DELETE FROM {Table.TagMap} WHERE {Query.whereByField "WebLogId" EQ "@webLogId"};
|
||||
DELETE FROM {Table.Upload} WHERE web_log_id = @webLogId;
|
||||
DELETE FROM {Table.WebLogUser} WHERE {Query.whereByField "WebLogId" EQ "@webLogId"};
|
||||
DELETE FROM {Table.WebLog} WHERE {Query.whereById "@webLogId"}"""
|
||||
[ webLogParam webLogId ]
|
||||
conn
|
||||
|
||||
/// Find a web log by its host (URL base)
|
||||
let findByHost (url: string) =
|
||||
log.LogTrace "WebLog.findByHost"
|
||||
Find.firstByFieldEquals<WebLog> Table.WebLog (nameof WebLog.Empty.UrlBase) url conn
|
||||
conn.findFirstByField<WebLog> Table.WebLog (nameof WebLog.Empty.UrlBase) EQ url
|
||||
|
||||
/// Find a web log by its ID
|
||||
let findById webLogId =
|
||||
log.LogTrace "WebLog.findById"
|
||||
Find.byId<WebLogId, WebLog> Table.WebLog webLogId conn
|
||||
conn.findById<WebLogId, WebLog> Table.WebLog webLogId
|
||||
|
||||
/// Update redirect rules for a web log
|
||||
let updateRedirectRules (webLog: WebLog) =
|
||||
log.LogTrace "WebLog.updateRedirectRules"
|
||||
Update.partialById Table.WebLog webLog.Id {| RedirectRules = webLog.RedirectRules |} conn
|
||||
conn.patchById Table.WebLog webLog.Id {| RedirectRules = webLog.RedirectRules |}
|
||||
|
||||
/// Update RSS options for a web log
|
||||
let updateRssOptions (webLog: WebLog) =
|
||||
log.LogTrace "WebLog.updateRssOptions"
|
||||
Update.partialById Table.WebLog webLog.Id {| Rss = webLog.Rss |} conn
|
||||
conn.patchById Table.WebLog webLog.Id {| Rss = webLog.Rss |}
|
||||
|
||||
/// Update settings for a web log
|
||||
let updateSettings (webLog: WebLog) =
|
||||
log.LogTrace "WebLog.updateSettings"
|
||||
Update.full Table.WebLog webLog.Id webLog conn
|
||||
conn.updateById Table.WebLog webLog.Id webLog
|
||||
|
||||
interface IWebLogData with
|
||||
member _.Add webLog = add webLog
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
namespace MyWebLog.Data.SQLite
|
||||
|
||||
open BitBadger.Sqlite.FSharp.Documents
|
||||
open BitBadger.Sqlite.FSharp.Documents.WithConn
|
||||
open BitBadger.Documents
|
||||
open BitBadger.Documents.Sqlite
|
||||
open Microsoft.Data.Sqlite
|
||||
open Microsoft.Extensions.Logging
|
||||
open MyWebLog
|
||||
|
@ -20,12 +20,12 @@ type SQLiteWebLogUserData(conn: SqliteConnection, log: ILogger) =
|
|||
log.LogTrace "WebLogUser.delete"
|
||||
match! findById userId webLogId with
|
||||
| Some _ ->
|
||||
let! pageCount = Count.byFieldEquals Table.Page (nameof Page.Empty.AuthorId) (string userId) conn
|
||||
let! postCount = Count.byFieldEquals Table.Post (nameof Post.Empty.AuthorId) (string userId) conn
|
||||
let! pageCount = conn.countByField Table.Page (nameof Page.Empty.AuthorId) EQ (string userId)
|
||||
let! postCount = conn.countByField Table.Post (nameof Post.Empty.AuthorId) EQ (string userId)
|
||||
if pageCount + postCount > 0 then
|
||||
return Error "User has pages or posts; cannot delete"
|
||||
else
|
||||
do! Delete.byId Table.WebLogUser userId conn
|
||||
do! conn.deleteById Table.WebLogUser userId
|
||||
return Ok true
|
||||
| None -> return Error "User does not exist"
|
||||
}
|
||||
|
@ -33,12 +33,11 @@ type SQLiteWebLogUserData(conn: SqliteConnection, log: ILogger) =
|
|||
/// Find a user by their e-mail address for the given web log
|
||||
let findByEmail (email: string) webLogId =
|
||||
log.LogTrace "WebLogUser.findByEmail"
|
||||
Custom.single
|
||||
conn.customSingle
|
||||
$"""{Document.Query.selectByWebLog Table.WebLogUser}
|
||||
AND {Query.whereFieldEquals (nameof WebLogUser.Empty.Email) "@email"}"""
|
||||
AND {Query.whereByField (nameof WebLogUser.Empty.Email) EQ "@email"}"""
|
||||
[ webLogParam webLogId; sqlParam "@email" email ]
|
||||
fromData<WebLogUser>
|
||||
conn
|
||||
|
||||
/// Get all users for the given web log
|
||||
let findByWebLog webLogId = backgroundTask {
|
||||
|
@ -51,18 +50,17 @@ type SQLiteWebLogUserData(conn: SqliteConnection, log: ILogger) =
|
|||
let findNames webLogId (userIds: WebLogUserId list) =
|
||||
log.LogTrace "WebLogUser.findNames"
|
||||
let nameSql, nameParams = inClause "AND data ->> 'Id'" "id" string userIds
|
||||
Custom.list
|
||||
conn.customList
|
||||
$"{Document.Query.selectByWebLog Table.WebLogUser} {nameSql}"
|
||||
(webLogParam webLogId :: nameParams)
|
||||
(fun rdr ->
|
||||
let user = fromData<WebLogUser> rdr
|
||||
{ Name = string user.Id; Value = user.DisplayName })
|
||||
conn
|
||||
|
||||
/// Save a user
|
||||
let save user =
|
||||
log.LogTrace "WebLogUser.update"
|
||||
save<WebLogUser> Table.WebLogUser user conn
|
||||
conn.save<WebLogUser> Table.WebLogUser user
|
||||
|
||||
/// Restore users from a backup
|
||||
let restore users = backgroundTask {
|
||||
|
@ -74,7 +72,7 @@ type SQLiteWebLogUserData(conn: SqliteConnection, log: ILogger) =
|
|||
let setLastSeen userId webLogId = backgroundTask {
|
||||
log.LogTrace "WebLogUser.setLastSeen"
|
||||
match! findById userId webLogId with
|
||||
| Some _ -> do! Update.partialById Table.WebLogUser userId {| LastSeenOn = Noda.now () |} conn
|
||||
| Some _ -> do! conn.patchById Table.WebLogUser userId {| LastSeenOn = Noda.now () |}
|
||||
| None -> ()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
namespace MyWebLog.Data
|
||||
|
||||
open System.Threading.Tasks
|
||||
open BitBadger.Sqlite.FSharp.Documents
|
||||
open BitBadger.Sqlite.FSharp.Documents.WithConn
|
||||
open BitBadger.Documents
|
||||
open BitBadger.Documents.Sqlite
|
||||
open Microsoft.Data.Sqlite
|
||||
open Microsoft.Extensions.Logging
|
||||
open MyWebLog
|
||||
|
@ -16,13 +16,13 @@ type SQLiteData(conn: SqliteConnection, log: ILogger<SQLiteData>, ser: JsonSeria
|
|||
/// Create tables (and their associated indexes) if they do not exist
|
||||
let ensureTables () = backgroundTask {
|
||||
|
||||
let! tables = Custom.list<string> "SELECT name FROM sqlite_master WHERE type = 'table'" [] (_.GetString(0)) conn
|
||||
let! tables = conn.customList<string> "SELECT name FROM sqlite_master WHERE type = 'table'" [] _.GetString(0)
|
||||
|
||||
let needsTable table =
|
||||
not (List.contains table tables)
|
||||
|
||||
let jsonTable table =
|
||||
$"{Definition.createTable table}; {Definition.createKey table}"
|
||||
$"{Query.Definition.ensureTable table}; {Query.Definition.ensureKey table}"
|
||||
|
||||
let tasks =
|
||||
seq {
|
||||
|
@ -41,21 +41,23 @@ type SQLiteData(conn: SqliteConnection, log: ILogger<SQLiteData>, ser: JsonSeria
|
|||
|
||||
// Category table
|
||||
if needsTable Table.Category then
|
||||
$"{jsonTable Table.Category};
|
||||
CREATE INDEX idx_{Table.Category}_web_log ON {Table.Category} ((data ->> 'WebLogId'))"
|
||||
$"""{jsonTable Table.Category};
|
||||
{Query.Definition.ensureIndexOn Table.Category "web_log" [ nameof Category.Empty.WebLogId ]}"""
|
||||
|
||||
// Web log user table
|
||||
if needsTable Table.WebLogUser then
|
||||
$"{jsonTable Table.WebLogUser};
|
||||
CREATE INDEX idx_{Table.WebLogUser}_email
|
||||
ON {Table.WebLogUser} ((data ->> 'WebLogId'), (data ->> 'Email'))"
|
||||
$"""{jsonTable Table.WebLogUser};
|
||||
{Query.Definition.ensureIndexOn
|
||||
Table.WebLogUser
|
||||
"email"
|
||||
[ nameof WebLogUser.Empty.WebLogId; nameof WebLogUser.Empty.Email ]}"""
|
||||
|
||||
// Page tables
|
||||
if needsTable Table.Page then
|
||||
$"{jsonTable Table.Page};
|
||||
CREATE INDEX idx_{Table.Page}_author ON {Table.Page} ((data ->> 'AuthorId'));
|
||||
CREATE INDEX idx_{Table.Page}_permalink
|
||||
ON {Table.Page} ((data ->> 'WebLogId'), (data ->> 'Permalink'))"
|
||||
$"""{jsonTable Table.Page};
|
||||
{Query.Definition.ensureIndexOn Table.Page "author" [ nameof Page.Empty.AuthorId ]};
|
||||
{Query.Definition.ensureIndexOn
|
||||
Table.Page "permalink" [ nameof Page.Empty.WebLogId; nameof Page.Empty.Permalink ]}"""
|
||||
if needsTable Table.PageRevision then
|
||||
$"CREATE TABLE {Table.PageRevision} (
|
||||
page_id TEXT NOT NULL,
|
||||
|
@ -65,12 +67,14 @@ type SQLiteData(conn: SqliteConnection, log: ILogger<SQLiteData>, ser: JsonSeria
|
|||
|
||||
// Post tables
|
||||
if needsTable Table.Post then
|
||||
$"{jsonTable Table.Post};
|
||||
CREATE INDEX idx_{Table.Post}_author ON {Table.Post} ((data ->> 'AuthorId'));
|
||||
CREATE INDEX idx_{Table.Post}_status
|
||||
ON {Table.Post} ((data ->> 'WebLogId'), (data ->> 'Status'), (data ->> 'UpdatedOn'));
|
||||
CREATE INDEX idx_{Table.Post}_permalink
|
||||
ON {Table.Post} ((data ->> 'WebLogId'), (data ->> 'Permalink'))"
|
||||
$"""{jsonTable Table.Post};
|
||||
{Query.Definition.ensureIndexOn Table.Post "author" [ nameof Post.Empty.AuthorId ]};
|
||||
{Query.Definition.ensureIndexOn
|
||||
Table.Post "permalink" [ nameof Post.Empty.WebLogId; nameof Post.Empty.Permalink ]};
|
||||
{Query.Definition.ensureIndexOn
|
||||
Table.Post
|
||||
"status"
|
||||
[ nameof Post.Empty.WebLogId; nameof Post.Empty.Status; nameof Post.Empty.UpdatedOn ]}"""
|
||||
// TODO: index categories by post?
|
||||
if needsTable Table.PostRevision then
|
||||
$"CREATE TABLE {Table.PostRevision} (
|
||||
|
@ -79,13 +83,14 @@ type SQLiteData(conn: SqliteConnection, log: ILogger<SQLiteData>, ser: JsonSeria
|
|||
revision_text TEXT NOT NULL,
|
||||
PRIMARY KEY (post_id, as_of))"
|
||||
if needsTable Table.PostComment then
|
||||
$"{jsonTable Table.PostComment};
|
||||
CREATE INDEX idx_{Table.PostComment}_post ON {Table.PostComment} ((data ->> 'PostId'))"
|
||||
$"""{jsonTable Table.PostComment};
|
||||
{Query.Definition.ensureIndexOn Table.PostComment "post" [ nameof Comment.Empty.PostId ]}"""
|
||||
|
||||
// Tag map table
|
||||
if needsTable Table.TagMap then
|
||||
$"{jsonTable Table.TagMap};
|
||||
CREATE INDEX idx_{Table.TagMap}_tag ON {Table.TagMap} ((data ->> 'WebLogId'), (data ->> 'UrlValue'))"
|
||||
$"""{jsonTable Table.TagMap};
|
||||
{Query.Definition.ensureIndexOn
|
||||
Table.TagMap "url" [ nameof TagMap.Empty.WebLogId; nameof TagMap.Empty.UrlValue ]}"""
|
||||
|
||||
// Uploaded file table
|
||||
if needsTable Table.Upload then
|
||||
|
@ -104,7 +109,7 @@ type SQLiteData(conn: SqliteConnection, log: ILogger<SQLiteData>, ser: JsonSeria
|
|||
}
|
||||
|> Seq.map (fun sql ->
|
||||
log.LogInformation $"""Creating {(sql.Replace("IF NOT EXISTS ", "").Split ' ')[2]} table..."""
|
||||
Custom.nonQuery sql [] conn)
|
||||
conn.customNonQuery sql [])
|
||||
|
||||
let! _ = Task.WhenAll tasks
|
||||
()
|
||||
|
@ -112,7 +117,7 @@ type SQLiteData(conn: SqliteConnection, log: ILogger<SQLiteData>, ser: JsonSeria
|
|||
|
||||
/// Set the database version to the specified version
|
||||
let setDbVersion version =
|
||||
Custom.nonQuery $"DELETE FROM {Table.DbVersion}; INSERT INTO {Table.DbVersion} VALUES ('%s{version}')" [] conn
|
||||
conn.customNonQuery $"DELETE FROM {Table.DbVersion}; INSERT INTO {Table.DbVersion} VALUES ('%s{version}')" []
|
||||
|
||||
/// Implement the changes between v2-rc1 and v2-rc2
|
||||
let migrateV2Rc1ToV2Rc2 () = backgroundTask {
|
||||
|
@ -467,6 +472,6 @@ type SQLiteData(conn: SqliteConnection, log: ILogger<SQLiteData>, ser: JsonSeria
|
|||
|
||||
member _.StartUp () = backgroundTask {
|
||||
do! ensureTables ()
|
||||
let! version = Custom.single<string> $"SELECT id FROM {Table.DbVersion}" [] (_.GetString(0)) conn
|
||||
let! version = conn.customSingle<string> $"SELECT id FROM {Table.DbVersion}" [] _.GetString(0)
|
||||
do! migrate version
|
||||
}
|
||||
|
|
|
@ -56,9 +56,6 @@ open MyWebLog.Data
|
|||
open Newtonsoft.Json
|
||||
open Npgsql
|
||||
|
||||
// The SQLite document library
|
||||
module Sqlite = BitBadger.Sqlite.FSharp.Documents
|
||||
|
||||
/// Logic to obtain a data connection and implementation based on configured values
|
||||
module DataImplementation =
|
||||
|
||||
|
@ -176,11 +173,7 @@ let main args =
|
|||
| :? SQLiteData ->
|
||||
// ADO.NET connections are designed to work as per-request instantiation
|
||||
let cfg = sp.GetRequiredService<IConfiguration>()
|
||||
let _ =
|
||||
builder.Services.AddScoped<SqliteConnection>(fun sp ->
|
||||
let conn = Sqlite.Configuration.dbConn ()
|
||||
conn.OpenAsync() |> Async.AwaitTask |> Async.RunSynchronously
|
||||
conn)
|
||||
let _ = builder.Services.AddScoped<SqliteConnection>(fun sp -> Sqlite.Configuration.dbConn ())
|
||||
let _ = builder.Services.AddScoped<IData, SQLiteData>()
|
||||
// Use SQLite for caching as well
|
||||
let cachePath = defaultArg (Option.ofObj (cfg.GetConnectionString "SQLiteCachePath")) "./session.db"
|
||||
|
|
Loading…
Reference in New Issue
Block a user