44# Made by Humans from OpenPeeps
55# https://github.com/openpeeps/ozark
66
7- import std/ [macros, net, strutils, tables]
7+ import std/ [macros, net, strutils, tables, locks, os ]
88
99import pkg/ threading/ once
1010import pkg/ db_connector/ db_postgres
@@ -14,28 +14,27 @@ export Port, strVal, `%`
1414
1515type
1616 DBConnectionPool * = ref object
17- connections* : seq [DBConn ]
18- busyConnections* : seq [DBConn ]
17+ connections* : seq [DBConn ] # available
18+ busyConnections* : seq [DBConn ] # checked-out
19+ maxSize* : int
20+ lock: Lock
1921
2022 DBDriver * = enum
2123 PostgreSQLDriver
2224 MYSQLDriver
2325 SQLiteDriver
2426
25- DBConnection = ref object
26- driver: DBDriver
27+ DBConnection * = ref object
28+ driver* : DBDriver
2729 address* , name* , user* , password* : string
28- # dbConn: DBConn
29- # pool: DBConnectionPool
30- port: Port
30+ port* : Port
3131
32- DBConnections = OrderedTableRef [string , DBConnection ]
32+ DBConnections * = OrderedTableRef [string , DBConnection ]
3333
3434 Ozark = object
3535 dbs: DBConnections
36- # holds credentials for multiple Database Connections
3736 maindb: DBConnection
38- # credentials for the main database connection
37+ mainPool: DBConnectionPool
3938
4039var
4140 DB : ptr Ozark
@@ -45,13 +44,12 @@ proc getInstance*(): ptr Ozark =
4544 # # Get the singleton instance of the database manager
4645 once (o):
4746 DB = createShared (Ozark )
47+ DB [].dbs = newOrderedTable [string , DBConnection ]() # init map
4848 result = DB
4949
5050proc initOzarkDatabase * (address, name, user, password: string ,
5151 port: Port = Port (5432 ),
5252 driver: DBDriver = DBDriver .PostgreSQLDriver ) =
53- # # Initializes the singleton instance of the database manager
54- # # using provided credentials as main database
5553 let db = getInstance ()
5654 db[].maindb = DBConnection (
5755 address: address,
@@ -102,5 +100,93 @@ macro withDatabase*(id: static string, body: untyped) =
102100 db[id].password, db[$ id].name)
103101 defer :
104102 dbcon.close ()
103+ block :
104+ `body`
105+
106+
107+ proc openConn (cfg: DBConnection ): DBConn =
108+ case cfg.driver
109+ of PostgreSQLDriver :
110+ open (cfg.address, cfg.user, cfg.password, cfg.name)
111+ else :
112+ raise newException (ValueError , " Only PostgreSQL driver pool is currently implemented." )
113+
114+ proc initOzarkPool * (size: Positive = 10 ) =
115+ # # Initialize main DB connection pool.
116+ let db = getInstance ()
117+ assert db[].maindb != nil , " Main DB credentials not initialized. Call initOzarkDatabase first."
118+
119+ var pool = DBConnectionPool (
120+ maxSize: size.int ,
121+ connections: @ [],
122+ busyConnections: @ []
123+ )
124+ initLock (pool.lock)
125+
126+ for _ in 0 ..< size.int :
127+ pool.connections.add (openConn (db[].maindb))
128+
129+ db[].mainPool = pool
130+
131+ proc closeOzarkPool * () =
132+ # # Close all pooled connections.
133+ let db = getInstance ()
134+ if db[].mainPool.isNil: return
135+
136+ acquire (db[].mainPool.lock)
137+ defer : release (db[].mainPool.lock)
138+
139+ for c in db[].mainPool.connections:
140+ c.close ()
141+ for c in db[].mainPool.busyConnections:
142+ c.close ()
143+
144+ db[].mainPool.connections.setLen (0 )
145+ db[].mainPool.busyConnections.setLen (0 )
146+
147+ proc acquireConn * (pool: DBConnectionPool , timeoutMs: int = 5000 ): DBConn =
148+ # # Borrow one connection from pool, waiting up to timeoutMs.
149+ let stepMs = 10
150+ var waited = 0
151+ while waited <= timeoutMs:
152+ acquire (pool.lock)
153+ if pool.connections.len > 0 :
154+ result = pool.connections.pop ()
155+ pool.busyConnections.add (result )
156+ release (pool.lock)
157+ return
158+ release (pool.lock)
159+ sleep (stepMs)
160+ inc (waited, stepMs)
161+
162+ raise newException (ValueError , " Timed out waiting for a DB connection from pool." )
163+
164+ proc releaseConn * (pool: DBConnectionPool , conn: DBConn ) =
165+ # # Return a connection to pool.
166+ acquire (pool.lock)
167+ defer : release (pool.lock)
168+
169+ var idx = - 1
170+ for i, c in pool.busyConnections:
171+ if c == conn:
172+ idx = i
173+ break
174+
175+ if idx >= 0 :
176+ pool.busyConnections.del (idx)
177+ pool.connections.add (conn)
178+
179+ macro withDBPool * (body: untyped ) =
180+ # # Run queries using a pooled connection.
181+ result = newStmtList ()
182+ add result , quote do :
183+ let db = getInstance ()
184+ assert db != nil , " Database manager not initialized. Call initOzarkDatabase first."
185+ assert db[].mainPool != nil , " DB pool not initialized. Call initOzarkPool first."
186+
187+ let dbcon {.inject .} = acquireConn (db[].mainPool)
188+ defer :
189+ releaseConn (db[].mainPool, dbcon)
190+
105191 block :
106192 `body`
0 commit comments