Skip to content

Commit 0375f1d

Browse files
committed
agent: add build_relations_vector function to sqlite
1 parent 3016b92 commit 0375f1d

File tree

3 files changed

+110
-6
lines changed

3 files changed

+110
-6
lines changed

simplexmq.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ library
9090
Simplex.FileTransfer.Transport
9191
Simplex.FileTransfer.Types
9292
Simplex.FileTransfer.Util
93+
Simplex.MemberRelations.MemberRelations
9394
Simplex.Messaging.Agent
9495
Simplex.Messaging.Agent.Client
9596
Simplex.Messaging.Agent.Env.SQLite
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
{-# LANGUAGE LambdaCase #-}
2+
3+
module Simplex.MemberRelations.MemberRelations
4+
( MemberRelation (..),
5+
toRelationInt,
6+
fromRelationInt,
7+
getRelation,
8+
getRelationsIndexes,
9+
setRelation,
10+
setRelations,
11+
)
12+
where
13+
14+
import Control.Monad
15+
import Data.Bits ((.&.), (.|.), complement)
16+
import Data.ByteString (ByteString)
17+
import qualified Data.ByteString as B
18+
import Data.ByteString.Internal (toForeignPtr, unsafeCreate)
19+
import Data.Int (Int64)
20+
import Data.Word (Word8)
21+
import Foreign.ForeignPtr (withForeignPtr)
22+
import Foreign.Marshal.Utils (copyBytes, fillBytes)
23+
import Foreign.Ptr (plusPtr)
24+
import Foreign.Storable (peekByteOff, pokeByteOff)
25+
26+
data MemberRelation
27+
= MRNew
28+
| MRIntroduced
29+
| MRIntroducedTo
30+
| MRConnected
31+
deriving (Eq, Show)
32+
33+
toRelationInt :: MemberRelation -> Word8
34+
toRelationInt = \case
35+
MRNew -> 0
36+
MRIntroduced -> 1
37+
MRIntroducedTo -> 2
38+
MRConnected -> 3
39+
40+
fromRelationInt :: Word8 -> MemberRelation
41+
fromRelationInt = \case
42+
0 -> MRNew
43+
1 -> MRIntroduced
44+
2 -> MRIntroducedTo
45+
3 -> MRConnected
46+
_ -> MRNew
47+
48+
-- | Get the relation status of a member at a given index from the relations vector.
49+
-- Returns 'MRNew' if the vector is not long enough (lazy initialization).
50+
getRelation :: Int64 -> ByteString -> MemberRelation
51+
getRelation i v
52+
| i < 0 || fromIntegral i >= B.length v = MRNew
53+
| otherwise = fromRelationInt $ (v `B.index` fromIntegral i) .&. relationMask
54+
55+
-- | Get the indexes of members that satisfy the given relation predicate.
56+
getRelationsIndexes :: (MemberRelation -> Bool) -> ByteString -> [Int64]
57+
getRelationsIndexes p v = [i | i <- [0 .. fromIntegral (B.length v) - 1], p (getRelation i v)]
58+
59+
-- | Set the relation status of a member at a given index in the relations vector.
60+
-- Expands the vector lazily if needed (padding with zeros for 'MRNew' relation).
61+
setRelation :: Int64 -> MemberRelation -> ByteString -> ByteString
62+
setRelation i r v
63+
| i >= 0 = setRelations [(i, r)] v
64+
| otherwise = v
65+
66+
-- | Set multiple relations at once.
67+
-- Expands the vector lazily if needed (padding with zeros for 'MRNew' relation).
68+
setRelations :: [(Int64, MemberRelation)] -> ByteString -> ByteString
69+
setRelations [] v = v
70+
setRelations relations v =
71+
let (fp, off, len) = toForeignPtr v
72+
newLen = max len $ fromIntegral $ maximum (map fst relations) + 1
73+
in unsafeCreate newLen $ \ptr -> do
74+
withForeignPtr fp $ \vPtr -> copyBytes ptr (vPtr `plusPtr` off) len
75+
when (newLen > len) $ fillBytes (ptr `plusPtr` len) 0 (newLen - len)
76+
forM_ relations $ \(ix, r) -> when (ix >= 0) $ do
77+
let i = fromIntegral ix
78+
b <- peekByteOff ptr i
79+
let b' = (b .&. complement relationMask) .|. toRelationInt r
80+
pokeByteOff ptr i b'
81+
82+
relationMask :: Word8
83+
relationMask = 0x07 -- reserving 3 bits

src/Simplex/Messaging/Agent/Store/SQLite.hs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,26 @@ module Simplex.Messaging.Agent.Store.SQLite
4242
)
4343
where
4444

45+
import Control.Concurrent.MVar
46+
import Control.Concurrent.STM
47+
import Control.Exception (bracketOnError, onException, throwIO)
4548
import Control.Monad
4649
import Data.ByteArray (ScrubbedBytes)
4750
import qualified Data.ByteArray as BA
51+
import qualified Data.ByteString as B
4852
import Data.Functor (($>))
4953
import Data.IORef
5054
import Data.Maybe (fromMaybe)
55+
import Data.Int (Int64)
5156
import Data.Text (Text)
5257
import qualified Data.Text as T
5358
import Database.SQLite.Simple (Query (..))
5459
import qualified Database.SQLite.Simple as SQL
5560
import Database.SQLite.Simple.QQ (sql)
61+
import Database.SQLite3 (FuncArgs, FuncContext, funcArgInt64, funcResultBlob)
5662
import qualified Database.SQLite3 as SQLite3
63+
import Database.SQLite3.Direct (createAggregate)
64+
import Simplex.MemberRelations.MemberRelations (MemberRelation (..), fromRelationInt, setRelations)
5765
import Simplex.Messaging.Agent.Store.Migrations (DBMigrate (..), sharedMigrateSchema)
5866
import qualified Simplex.Messaging.Agent.Store.SQLite.Migrations as Migrations
5967
import Simplex.Messaging.Agent.Store.SQLite.Common
@@ -62,9 +70,6 @@ import Simplex.Messaging.Agent.Store.Shared (Migration (..), MigrationConfig (..
6270
import Simplex.Messaging.Util (ifM, safeDecodeUtf8)
6371
import System.Directory (copyFile, createDirectoryIfMissing, doesFileExist)
6472
import System.FilePath (takeDirectory, takeFileName, (</>))
65-
import UnliftIO.Exception (bracketOnError, onException)
66-
import UnliftIO.MVar
67-
import UnliftIO.STM
6873

6974
-- * SQLite Store implementation
7075

@@ -109,16 +114,31 @@ connectDB path key track = do
109114
pure db
110115
where
111116
prepare db = do
112-
let exec = SQLite3.exec $ SQL.connectionHandle $ DB.conn db
113-
unless (BA.null key) . exec $ "PRAGMA key = " <> keyString key <> ";"
114-
exec . fromQuery $
117+
let db' = SQL.connectionHandle $ DB.conn db
118+
unless (BA.null key) . SQLite3.exec db' $ "PRAGMA key = " <> keyString key <> ";"
119+
SQLite3.exec db' . fromQuery $
115120
[sql|
116121
PRAGMA busy_timeout = 100;
117122
PRAGMA foreign_keys = ON;
118123
-- PRAGMA trusted_schema = OFF;
119124
PRAGMA secure_delete = ON;
120125
PRAGMA auto_vacuum = FULL;
121126
|]
127+
createAggregate db' "build_relations_vector" (Just 2) [] buildRelationsVectorStep buildRelationsVectorFinal
128+
>>= either (throwIO . userError . show) pure
129+
130+
-- | Step function for build_relations_vector aggregate.
131+
-- Accumulates (idx, relation) pairs.
132+
buildRelationsVectorStep :: FuncContext -> FuncArgs -> [(Int64, MemberRelation)] -> IO [(Int64, MemberRelation)]
133+
buildRelationsVectorStep _ args acc = do
134+
idx <- funcArgInt64 args 0
135+
relation <- fromRelationInt . fromIntegral <$> funcArgInt64 args 1
136+
pure $ (idx, relation) : acc
137+
138+
-- | Final function for build_relations_vector aggregate.
139+
-- Builds the vector from accumulated pairs using setRelations.
140+
buildRelationsVectorFinal :: FuncContext -> [(Int64, MemberRelation)] -> IO ()
141+
buildRelationsVectorFinal ctx acc = funcResultBlob ctx $ setRelations acc B.empty
122142

123143
closeDBStore :: DBStore -> IO ()
124144
closeDBStore st@DBStore {dbClosed} =

0 commit comments

Comments
 (0)