Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions codebase2/codebase-sqlite/U/Codebase/Sqlite/DbId.hs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ newtype CausalHashId = CausalHashId {unCausalHashId :: HashId}
deriving (Eq, Ord)
deriving (Num, Real, Enum, Integral, Bits, FromField, ToField) via HashId

newtype CommentHashId = CommentHashId {unCommentHashId :: HashId}
deriving (Eq, Ord)
deriving (Num, Real, Enum, Integral, Bits, FromField, ToField) via HashId

newtype CommentRevisionHashId = CommentRevisionHashId {unCommentRevisionHashId :: HashId}
deriving (Eq, Ord)
deriving (Num, Real, Enum, Integral, Bits, FromField, ToField) via HashId

newtype ProjectBranchId = ProjectBranchId {unProjectBranchId :: UUID}
deriving newtype (Eq, FromField, Ord, Show, ToField)

Expand Down Expand Up @@ -67,6 +75,20 @@ instance Show BranchHashId where
instance Show CausalHashId where
show h = "CausalHashId (" ++ show (unCausalHashId h) ++ ")"

instance Show CommentHashId where
show h = "CommentHashId (" ++ show (unCommentHashId h) ++ ")"

instance Show CommentRevisionHashId where
show h = "CommentRevisionHashId (" ++ show (unCommentRevisionHashId h) ++ ")"

newtype HistoryCommentId = HistoryCommentId Word64
deriving (Eq, Ord, Show)
deriving (Num, Real, Enum, Integral, Bits, FromField, ToField) via Word64

newtype HistoryCommentRevisionId = HistoryCommentRevisionId Word64
deriving (Eq, Ord, Show)
deriving (Num, Real, Enum, Integral, Bits, FromField, ToField) via Word64

newtype KeyThumbprintId = KeyThumbprintId Word64
deriving (Eq, Ord, Show)
deriving (Num, Real, Enum, Integral, Bits, FromField, ToField) via Word64
12 changes: 0 additions & 12 deletions codebase2/codebase-sqlite/U/Codebase/Sqlite/HistoryComment.hs

This file was deleted.

153 changes: 127 additions & 26 deletions codebase2/codebase-sqlite/U/Codebase/Sqlite/Queries.hs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE TemplateHaskell #-}

-- | Some naming conventions used in this module:
Expand Down Expand Up @@ -35,6 +36,8 @@ module U.Codebase.Sqlite.Queries
expectCausalHash,
expectBranchHashForCausalHash,
saveBranchHash,
saveHistoryCommentHash,
saveHistoryCommentRevisionHash,

-- * hash_object table
saveHashObject,
Expand Down Expand Up @@ -260,6 +263,8 @@ module U.Codebase.Sqlite.Queries
addDerivedDependentsByDependencyIndex,
addUpgradeBranchTable,
addHistoryComments,
addHistoryCommentHashing,
historyCommentHashingCleanup,

-- ** schema version
currentSchemaVersion,
Expand Down Expand Up @@ -296,6 +301,9 @@ module U.Codebase.Sqlite.Queries
getConfigValue,
setConfigValue,

-- * Personal Keys
ensurePersonalKeyThumbprintId,

-- * Types
TextPathSegments,
JsonParseFailure (..),
Expand Down Expand Up @@ -328,14 +336,21 @@ import Data.Text qualified as Text
import Data.Text.Encoding qualified as Text
import Data.Text.Lazy qualified as Text.Lazy
import Data.Time qualified as Time
import Data.Time.Clock.POSIX qualified as POSIX
import Data.Vector qualified as Vector
import Network.URI (URI)
import U.Codebase.Branch.Type (NamespaceStats (..))
import U.Codebase.Config (AuthorName, ConfigKey)
import U.Codebase.Config qualified as Config
import U.Codebase.Decl qualified as C
import U.Codebase.Decl qualified as C.Decl
import U.Codebase.HashTags (BranchHash (..), CausalHash (..), PatchHash (..))
import U.Codebase.HashTags
( BranchHash (..),
CausalHash (..),
HistoryCommentHash (..),
HistoryCommentRevisionHash (..),
PatchHash (..),
)
import U.Codebase.Reference (Reference' (..))
import U.Codebase.Reference qualified as C (Reference)
import U.Codebase.Reference qualified as C.Reference
Expand All @@ -348,9 +363,13 @@ import U.Codebase.Sqlite.DbId
( BranchHashId (..),
BranchObjectId (..),
CausalHashId (..),
CommentHashId (..),
CommentRevisionHashId (..),
HashId (..),
HashVersion,
HistoryCommentId,
HistoryCommentRevisionId,
KeyThumbprintId,
ObjectId (..),
PatchObjectId (..),
ProjectBranchId (..),
Expand All @@ -366,7 +385,6 @@ import U.Codebase.Sqlite.Decode
import U.Codebase.Sqlite.Entity (SyncEntity)
import U.Codebase.Sqlite.Entity qualified as Entity
import U.Codebase.Sqlite.HashHandle (HashHandle (..))
import U.Codebase.Sqlite.HistoryComment (HistoryComment (..))
import U.Codebase.Sqlite.LocalIds
( LocalDefnId (..),
LocalIds,
Expand Down Expand Up @@ -407,6 +425,12 @@ import Unison.Hash qualified as Hash
import Unison.Hash32 (Hash32)
import Unison.Hash32 qualified as Hash32
import Unison.Hash32.Orphans.Sqlite ()
import Unison.HistoryComment
( HistoryComment (..),
HistoryCommentRevision (..),
LatestHistoryComment,
)
import Unison.KeyThumbprint (KeyThumbprint (..))
import Unison.Name (Name)
import Unison.Name qualified as Name
import Unison.NameSegment.Internal (NameSegment (NameSegment))
Expand All @@ -429,7 +453,7 @@ type TextPathSegments = [Text]
-- * main squeeze

currentSchemaVersion :: SchemaVersion
currentSchemaVersion = 23
currentSchemaVersion = 26

runCreateSql :: Transaction ()
runCreateSql =
Expand Down Expand Up @@ -519,6 +543,14 @@ addHistoryComments :: Transaction ()
addHistoryComments =
executeStatements $(embedProjectStringFile "sql/020-add-history-comments.sql")

addHistoryCommentHashing :: Transaction ()
addHistoryCommentHashing =
executeStatements $(embedProjectStringFile "sql/021-hash-history-comments.sql")

historyCommentHashingCleanup :: Transaction ()
historyCommentHashingCleanup =
executeStatements $(embedProjectStringFile "sql/022-hash-history-comments-cleanup.sql")

schemaVersion :: Transaction SchemaVersion
schemaVersion =
queryOneCol
Expand Down Expand Up @@ -629,6 +661,12 @@ expectCausalByCausalHash ch = do
bhId <- expectCausalValueHashId hId
pure (hId, bhId)

saveHistoryCommentHash :: HistoryCommentHash -> Transaction CommentHashId
saveHistoryCommentHash = fmap CommentHashId . saveHashHash . unHistoryCommentHash

saveHistoryCommentRevisionHash :: HistoryCommentRevisionHash -> Transaction CommentRevisionHashId
saveHistoryCommentRevisionHash = fmap CommentRevisionHashId . saveHashHash . unHistoryCommentRevisionHash

expectHashIdByHash :: Hash -> Transaction HashId
expectHashIdByHash = expectHashId . Hash32.fromHash

Expand Down Expand Up @@ -4123,44 +4161,86 @@ saveSquashResult bhId chId =
ON CONFLICT DO NOTHING
|]

-- Convert milliseconds since epoch to UTCTime _exactly_.
-- UTCTime has picosecond precision so this is lossless.
millisToUTCTime :: Int64 -> Time.UTCTime
millisToUTCTime ms =
toRational ms
& (/ (1_000 :: Rational))
& fromRational
& POSIX.posixSecondsToUTCTime

utcTimeToMillis :: Time.UTCTime -> Int64
utcTimeToMillis utcTime =
POSIX.utcTimeToPOSIXSeconds utcTime
& toRational
& (* (1_000 :: Rational))
& round

getLatestCausalComment ::
CausalHashId ->
Transaction (Maybe (HistoryComment CausalHashId HistoryCommentId))
Transaction (Maybe (LatestHistoryComment KeyThumbprintId CausalHash HistoryCommentRevisionId HistoryCommentHash))
getLatestCausalComment causalHashId =
queryMaybeRow @(HistoryCommentId, CausalHashId, Text, Text, Text)
queryMaybeRow @(Hash32, Hash32, Text, KeyThumbprintId, Int64, HistoryCommentRevisionId, Text, Text, Int64)
[sql|
SELECT cc.id, cc.causal_hash_id, cc.author, ccr.subject, ccr.contents
SELECT comment_hash.base32, causal_hash.base32, cc.author, cc.author_thumbprint_id, cc.created_at_ms, ccr.id, ccr.subject, ccr.contents, ccr.created_at_ms
FROM history_comments AS cc
JOIN history_comment_revisions AS ccr ON cc.id = ccr.comment_id
JOIN hash AS comment_hash ON comment_hash.id = cc.comment_hash_id
JOIN hash AS causal_hash ON causal_hash.id = cc.causal_hash_id
WHERE cc.causal_hash_id = :causalHashId
ORDER BY ccr.created_at DESC
ORDER BY ccr.created_at_ms DESC
LIMIT 1
|]
<&> fmap \(commentId, causal, author, subject, content) ->
HistoryComment {author, subject, content, commentId, causal}

commentOnCausal :: HistoryComment CausalHashId () -> Transaction ()
commentOnCausal HistoryComment {author, content, subject, causal = causalHashId} = do
mayExistingCommentId <-
queryMaybeCol @HistoryCommentId
[sql|
<&> fmap \(commentHash, causalHash, author, authorThumbprint, commentCreatedAtMs, revisionId, subject, content, revisionCreatedAtMs) ->
HistoryCommentRevision
{ subject,
content,
createdAt = millisToUTCTime revisionCreatedAtMs,
revisionId,
comment =
HistoryComment
{ author,
authorThumbprint,
causal = CausalHash . Hash32.toHash $ causalHash,
createdAt = millisToUTCTime commentCreatedAtMs,
commentId = HistoryCommentHash . Hash32.toHash $ commentHash
}
}

commentOnCausal :: LatestHistoryComment KeyThumbprint CausalHashId HistoryCommentRevisionHash HistoryCommentHash -> Transaction ()
commentOnCausal
HistoryCommentRevision
{ content,
subject,
revisionId = commentRevisionHash,
comment = HistoryComment {author, authorThumbprint, causal = causalHashId, commentId = commentHash}
} = do
commentHashId <- saveHistoryCommentHash commentHash
commentRevisionHashId <- saveHistoryCommentRevisionHash commentRevisionHash
thumbprintId <- ensurePersonalKeyThumbprintId authorThumbprint
mayExistingCommentId <-
queryMaybeCol @HistoryCommentId
[sql|
SELECT id
FROM history_comments
WHERE causal_hash_id = :causalHashId
|]
commentId <- case mayExistingCommentId of
Nothing ->
queryOneCol @HistoryCommentId
[sql|
INSERT INTO history_comments (author, causal_hash_id, created_at)
VALUES (:author, :causalHashId, strftime('%s', 'now', 'subsec'))
now <- Sqlite.unsafeIO $ Time.getCurrentTime
createdAtMs <- pure $ utcTimeToMillis now
commentId <- case mayExistingCommentId of
Nothing ->
queryOneCol @HistoryCommentId
[sql|
INSERT INTO history_comments (comment_hash_id, author_thumbprint_id, author, causal_hash_id, created_at_ms)
VALUES (:commentHashId, :thumbprintId, :author, :causalHashId, :createdAtMs)
RETURNING id
|]
Just cid -> pure cid
execute
[sql|
INSERT INTO history_comment_revisions (comment_id, subject, contents, created_at)
VALUES (:commentId, :subject, :content, strftime('%s', 'now', 'subsec'))
Just cid -> pure cid
execute
[sql|
INSERT INTO history_comment_revisions (revision_hash_id, comment_id, subject, contents, created_at)
VALUES (:commentRevisionHashId, :commentId, :subject, :content, :createdAtMs)
|]

getAuthorName :: Transaction (Maybe AuthorName)
Expand Down Expand Up @@ -4192,3 +4272,24 @@ getConfigValue key =
FROM config
WHERE key = :key
|]

-- | Save or return the id for a given key thumbprint
ensurePersonalKeyThumbprintId :: KeyThumbprint -> Transaction KeyThumbprintId
ensurePersonalKeyThumbprintId thumbprint = do
let thumbprintText = thumbprintToText thumbprint
mayExisting <-
queryMaybeCol
[sql|
SELECT id
FROM key_thumbprints
WHERE thumbprint = :thumbprintText
|]
case mayExisting of
Just thumbprintId -> pure thumbprintId
Nothing ->
queryOneCol
[sql|
INSERT INTO key_thumbprints (thumbprint)
VALUES (:thumbprintText)
RETURNING id
|]
25 changes: 25 additions & 0 deletions codebase2/codebase-sqlite/sql/021-hash-history-comments.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-- Table for storing personal key thumbprints,
-- which we may later associate with users.
CREATE TABLE IF NOT EXISTS key_thumbprints (
id INTEGER PRIMARY KEY,
thumbprint TEXT UNIQUE NOT NULL
);

ALTER TABLE history_comments
-- The hash used for comment identity.
-- It's the hash of (causal_hash <> author <> created_at_ms)
ADD COLUMN comment_hash_id INTEGER NULL REFERENCES hash(id);

CREATE UNIQUE INDEX IF NOT EXISTS idx_history_comments_comment_hash_id
ON history_comments(comment_hash_id);

ALTER TABLE history_comments
ADD COLUMN author_thumbprint_id INTEGER NULL REFERENCES key_thumbprints(id);

ALTER TABLE history_comment_revisions
-- The hash used for this revision's identity.
-- It's the hash of (comment_hash <> subject <> contents <> hidden <> created_at_ms)
ADD COLUMN revision_hash_id INTEGER NULL REFERENCES hash(id);

CREATE UNIQUE INDEX IF NOT EXISTS idx_history_comment_revisions_revision_hash_id
ON history_comment_revisions(revision_hash_id);
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
-- Assert hash columns are non-nullable after we've filled in all the values.
-- SQLite annoying does not support doing this in place, so we need to make a new table.

CREATE TABLE history_comments_new (
id INTEGER PRIMARY KEY,
causal_hash_id INTEGER NOT NULL REFERENCES hash(id),
author TEXT NOT NULL,

-- Remember that SQLITE doesn't have any actual 'time' type,
-- This column contains the number of milliseconds since epoch
-- as an integer.
created_at_ms INTEGER NOT NULL,

comment_hash_id INTEGER UNIQUE NOT NULL REFERENCES hash(id),
author_thumbprint_id INTEGER NOT NULL REFERENCES key_thumbprints(id)
);

-- Copy data from old tables to new tables.
-- We convert the created_at to created_at_ms by multiplying by 1000 and casting to INTEGER.
INSERT INTO history_comments_new (id, causal_hash_id, author, created_at_ms, comment_hash_id, author_thumbprint_id)
SELECT id, causal_hash_id, author, CAST((created_at * 1000) AS INTEGER), comment_hash_id, author_thumbprint_id
FROM history_comments;

-- Now do the revisions
CREATE TABLE history_comment_revisions_new (
id INTEGER PRIMARY KEY,
comment_id INTEGER NOT NULL REFERENCES history_comments_new(id),
subject TEXT NOT NULL,
contents TEXT NOT NULL,

-- Remember that SQLITE doesn't have any actual 'time' type,
-- This column contains the number of milliseconds since epoch
-- as an integer.
created_at_ms INTEGER NOT NULL,

-- - In a distributed system you really can’t ever truly delete comments,
-- but you can ask to hide them.
hidden BOOL NOT NULL DEFAULT FALSE,

revision_hash_id INTEGER UNIQUE NOT NULL REFERENCES hash(id)
);

-- We convert the created_at to created_at_ms by multiplying by 1000 and casting to INTEGER.
INSERT INTO history_comment_revisions_new (id, comment_id, subject, contents, created_at_ms, hidden, revision_hash_id)
SELECT id, comment_id, subject, contents, CAST((created_at * 1000) AS INTEGER), hidden, revision_hash_id
FROM history_comment_revisions;

-- Drop old tables
DROP TABLE history_comment_revisions;
DROP TABLE history_comments;

-- Rename new tables to original table names
ALTER TABLE history_comments_new RENAME TO history_comments;
ALTER TABLE history_comment_revisions_new RENAME TO history_comment_revisions;

CREATE INDEX history_comments_by_causal_hash_id ON history_comments(causal_hash_id, created_at_ms DESC);
CREATE INDEX history_comment_revisions_by_comment_id_and_created_at_ms ON history_comment_revisions(comment_id, created_at_ms DESC);
Loading
Loading