@@ -58,6 +58,7 @@ module Unison.Sqlite.Connection
58
58
)
59
59
where
60
60
61
+ import Data.Map qualified as Map
61
62
import Database.SQLite.Simple qualified as Sqlite
62
63
import Database.SQLite.Simple.FromField qualified as Sqlite
63
64
import Database.SQLite3 qualified as Direct.Sqlite
@@ -71,7 +72,10 @@ import Unison.Sqlite.Connection.Internal (Connection (..))
71
72
import Unison.Sqlite.Exception
72
73
import Unison.Sqlite.Sql (Sql (.. ))
73
74
import Unison.Sqlite.Sql qualified as Sql
75
+ import UnliftIO (atomically )
74
76
import UnliftIO.Exception
77
+ import UnliftIO.STM (readTVar )
78
+ import UnliftIO.STM qualified as STM
75
79
76
80
-- | Perform an action with a connection to a SQLite database.
77
81
--
@@ -103,19 +107,47 @@ openConnection name file = do
103
107
Just " " -> file
104
108
_ -> " file:" <> file <> " ?mode=ro"
105
109
conn0 <- Sqlite. open sqliteURI `catch` rethrowAsSqliteConnectException name file
106
- let conn = Connection {conn = conn0, file, name}
110
+ statementCache <- STM. newTVarIO Map. empty
111
+ let conn = Connection {conn = conn0, file, name, statementCache}
107
112
execute conn [Sql. sql | PRAGMA foreign_keys = ON |]
108
113
execute conn [Sql. sql | PRAGMA busy_timeout = 60000 |]
114
+ execute conn [Sql. sql | PRAGMA synchronous = normal |]
115
+ execute conn [Sql. sql | PRAGMA journal_size_limit = 6144000 |]
116
+ execute conn [Sql. sql | PRAGMA cache_size = -64000 |]
117
+ execute conn [Sql. sql | PRAGMA temp_store = 2 |]
118
+
109
119
pure conn
110
120
111
121
-- Close a connection opened with 'openConnection'.
112
122
closeConnection :: Connection -> IO ()
113
- closeConnection (Connection _ _ conn ) =
123
+ closeConnection conn @ (Connection {conn = conn0} ) = do
114
124
-- FIXME if this throws an exception, it won't be under `SomeSqliteException`
115
125
-- Possible fixes:
116
126
-- 1. Add close exception to the hierarchy, e.g. `SqliteCloseException`
117
127
-- 2. Always ignore exceptions thrown by `close` (Mitchell prefers this one)
118
- Sqlite. close conn
128
+ closeAllStatements conn
129
+ Sqlite. close conn0
130
+
131
+ withStatement :: Connection -> Text -> (Sqlite. Statement -> IO a ) -> IO a
132
+ withStatement conn sql action = do
133
+ bracket (prepareStatement conn sql) Sqlite. reset action
134
+ where
135
+ prepareStatement :: Connection -> Text -> IO Sqlite. Statement
136
+ prepareStatement Connection {conn, statementCache} sql = do
137
+ cached <- atomically $ do
138
+ cache <- STM. readTVar statementCache
139
+ pure $ Map. lookup sql cache
140
+ case cached of
141
+ Just stmt -> pure stmt
142
+ Nothing -> do
143
+ stmt <- Sqlite. openStatement conn (coerce @ Text @ Sqlite. Query sql)
144
+ atomically $ STM. modifyTVar statementCache (Map. insert sql stmt)
145
+ pure stmt
146
+
147
+ closeAllStatements :: Connection -> IO ()
148
+ closeAllStatements Connection {statementCache} = do
149
+ cache <- atomically $ readTVar statementCache
150
+ for_ cache Sqlite. closeStatement
119
151
120
152
-- An internal type, for making prettier debug logs
121
153
@@ -152,7 +184,7 @@ logQuery (Sql sql params) result =
152
184
-- Without results
153
185
154
186
execute :: (HasCallStack ) => Connection -> Sql -> IO ()
155
- execute conn@ ( Connection _ _ conn0) sql@ (Sql s params) = do
187
+ execute conn sql@ (Sql s params) = do
156
188
logQuery sql Nothing
157
189
doExecute `catch` \ (exception :: Sqlite. SQLError ) ->
158
190
throwSqliteQueryException
@@ -163,16 +195,16 @@ execute conn@(Connection _ _ conn0) sql@(Sql s params) = do
163
195
}
164
196
where
165
197
doExecute :: IO ()
166
- doExecute =
167
- Sqlite. withStatement conn0 (coerce s) \ ( Sqlite. Statement statement) -> do
168
- bindParameters statement params
169
- void (Direct.Sqlite. step statement)
198
+ doExecute = do
199
+ withStatement conn s \ statement -> do
200
+ bindParameters (coerce statement) params
201
+ void (Direct.Sqlite. step $ coerce statement)
170
202
171
203
-- | Execute one or more semicolon-delimited statements.
172
204
--
173
205
-- This function does not support parameters, and is mostly useful for executing DDL and migrations.
174
206
executeStatements :: (HasCallStack ) => Connection -> Text -> IO ()
175
- executeStatements conn@ (Connection _ _ ( Sqlite. Connection database _tempNameCounter) ) sql = do
207
+ executeStatements conn@ (Connection {conn = Sqlite. Connection database _tempNameCounter} ) sql = do
176
208
logQuery (Sql sql [] ) Nothing
177
209
Direct.Sqlite. exec database sql `catch` \ (exception :: Sqlite. SQLError ) ->
178
210
throwSqliteQueryException
@@ -185,7 +217,7 @@ executeStatements conn@(Connection _ _ (Sqlite.Connection database _tempNameCoun
185
217
-- With results, without checks
186
218
187
219
queryStreamRow :: (HasCallStack , Sqlite. FromRow a ) => Connection -> Sql -> (IO (Maybe a ) -> IO r ) -> IO r
188
- queryStreamRow conn@ ( Connection _ _ conn0) sql@ (Sql s params) callback =
220
+ queryStreamRow conn sql@ (Sql s params) callback =
189
221
run `catch` \ (exception :: Sqlite. SQLError ) ->
190
222
throwSqliteQueryException
191
223
SqliteQueryExceptionInfo
@@ -194,8 +226,8 @@ queryStreamRow conn@(Connection _ _ conn0) sql@(Sql s params) callback =
194
226
sql
195
227
}
196
228
where
197
- run =
198
- bracket ( Sqlite. openStatement conn0 (coerce s)) Sqlite. closeStatement \ statement -> do
229
+ run = do
230
+ withStatement conn s \ statement -> do
199
231
Sqlite. bind statement params
200
232
callback (Sqlite. nextRow statement)
201
233
@@ -213,7 +245,7 @@ queryStreamCol =
213
245
queryStreamRow
214
246
215
247
queryListRow :: forall a . (Sqlite. FromRow a , HasCallStack ) => Connection -> Sql -> IO [a ]
216
- queryListRow conn@ ( Connection _ _ conn0) sql@ (Sql s params) = do
248
+ queryListRow conn sql@ (Sql s params) = do
217
249
result <-
218
250
doQuery
219
251
`catch` \ (exception :: Sqlite. SQLError ) ->
@@ -228,7 +260,7 @@ queryListRow conn@(Connection _ _ conn0) sql@(Sql s params) = do
228
260
where
229
261
doQuery :: IO [a ]
230
262
doQuery =
231
- Sqlite. withStatement conn0 (coerce s) \ statement -> do
263
+ withStatement conn (coerce s) \ statement -> do
232
264
bindParameters (coerce statement) params
233
265
let loop :: [a ] -> IO [a ]
234
266
loop rows =
@@ -347,7 +379,7 @@ queryOneColCheck conn s check =
347
379
-- Rows modified
348
380
349
381
rowsModified :: Connection -> IO Int
350
- rowsModified (Connection _ _ conn) =
382
+ rowsModified (Connection { conn} ) =
351
383
Sqlite. changes conn
352
384
353
385
-- Vacuum
0 commit comments