167
167
--- @field array_result boolean option to receive PGResult row fields as array instead of table (default : false )
168
168
--- @field private conn PGconn native connection object (do not use it directly , otherwise be careful not to store it anywhere else , otherwise closing connection will be impossible )
169
169
--- @field private queries { push : fun ( self , q : PGQuery ), prepend : fun ( self , q : PGQuery ), pop : ( fun ( self ): PGQuery ), size : fun ( self ): number } list of queries
170
+ --- @field package errorHandler function function that just calls self : onError (... )
170
171
--- @field package acquired boolean
171
172
--- @field package pool PGPool ?
172
173
local Client = {}
@@ -203,7 +204,7 @@ function Client:reset(callback)
203
204
self .retryAttempted = 0
204
205
end
205
206
206
- xpcall (callback , ErrorNoHaltWithStack , ok , err )
207
+ xpcall (callback , self . errorHandler , ok , err )
207
208
self :processQueue ()
208
209
end )
209
210
@@ -237,10 +238,10 @@ function Client:connect(callback)
237
238
--- @cast conn PGconn
238
239
self .conn = conn
239
240
self .conn :setNotifyCallback (function (channel , payload , backendPID )
240
- self : onNotify ( channel , payload , backendPID )
241
+ xpcall ( self . onNotify , self . errorHandler , self , channel , payload , backendPID )
241
242
end )
242
243
243
- xpcall (callback , ErrorNoHaltWithStack , ok )
244
+ xpcall (callback , self . errorHandler , ok )
244
245
self :processQueue ()
245
246
else
246
247
--- @cast conn string
@@ -250,7 +251,7 @@ function Client:connect(callback)
250
251
251
252
-- async_postgres.connect() can throw error if for example url is invalid
252
253
if not ok then
253
- xpcall (callback , ErrorNoHaltWithStack , false , err )
254
+ xpcall (callback , self . errorHandler , false , err )
254
255
return
255
256
end
256
257
@@ -290,7 +291,7 @@ function Client:runQuery(query)
290
291
self .conn :setArrayResult (false )
291
292
end
292
293
293
- xpcall (query .callback , ErrorNoHaltWithStack , ok , result , errdata )
294
+ xpcall (query .callback , self . errorHandler , ok , result , errdata )
294
295
self :processQueue ()
295
296
end
296
297
@@ -319,7 +320,7 @@ function Client:processQueue()
319
320
local query = self .queries :pop ()
320
321
local ok , err = pcall (self .runQuery , self , query )
321
322
if not ok then
322
- xpcall (query .callback , ErrorNoHaltWithStack , false , err )
323
+ xpcall (query .callback , self . errorHandler , false , err )
323
324
end
324
325
end
325
326
@@ -434,8 +435,7 @@ function Client:close(wait)
434
435
while self :wait () do
435
436
iterations = iterations + 1
436
437
if iterations > 1000 then
437
- ErrorNoHaltWithStack (
438
- " PGClient:close() - waiting for queries too long, closing forcefully" )
438
+ self :onError (" PGClient:close() - waiting for queries too long, closing forcefully" )
439
439
break
440
440
end
441
441
end
@@ -444,11 +444,11 @@ function Client:close(wait)
444
444
local iterations = 0
445
445
while self .queries :size () ~= 0 do
446
446
local q = self .queries :pop ()
447
- xpcall (q .callback , ErrorNoHaltWithStack , false , " connection to the database was closed" )
447
+ xpcall (q .callback , self . errorHandler , false , " connection to the database was closed" )
448
448
449
449
iterations = iterations + 1
450
450
if iterations > 1000 then
451
- ErrorNoHaltWithStack (" PGClient:close() - queries are infinite, ignoring them" )
451
+ self : onError (" PGClient:close() - queries are infinite, ignoring them" )
452
452
break
453
453
end
454
454
end
617
617
618
618
--- Releases client back to the pool, only use after getting it from the pool
619
619
--- @see PGPool.connect to acquire a client from the pool
620
- function Client :release ()
620
+ --- @param suppress boolean ? if true , then Pool : onRelease won ' t be called
621
+ function Client :release (suppress )
621
622
if self .acquired == false then
622
623
error (" client was not acquired, :release() was called multiple times or misused" )
623
624
end
@@ -630,10 +631,15 @@ function Client:release()
630
631
self .pool = nil
631
632
632
633
--- @cast pool PGPool
634
+
635
+ if not suppress then
636
+ xpcall (function () return pool :onRelease (self ) end , self .errorHandler )
637
+ end
638
+
633
639
pool :processQueue () -- after client was release, we need to process pool queue
634
640
end
635
641
636
- --- This function is called when NOTIFY message is received
642
+ --- This **event** function is called when NOTIFY message is received
637
643
---
638
644
--- You can set it to your own function to handle NOTIFY messages
639
645
--- @param channel string
642
648
function Client :onNotify (channel , payload , backendPID )
643
649
end
644
650
651
+ --- This **event** function is called whenever an error occurs inside connect/query callback.
652
+ ---
653
+ --- You can set it to your own function to handle errors.
654
+ --- By default this function calls `ErrorNoHaltWithStack`
655
+ --- @param message string error message
656
+ function Client :onError (message )
657
+ -- return used here to hide additional stack trace
658
+ return ErrorNoHaltWithStack (message )
659
+ end
660
+
645
661
--- Creates a new client with given connection url
646
662
--- ```lua
647
663
--- local client = async_postgres.Client("postgresql://user:password@localhost:5432/database")
@@ -659,11 +675,16 @@ end
659
675
--- @param url string connection url , see libpq documentation for more information
660
676
--- @return PGClient
661
677
function async_postgres .Client (url )
662
- return setmetatable ({
678
+ --- @class PGClient
679
+ local client = setmetatable ({
663
680
url = url ,
664
681
connecting = false ,
665
682
queries = Queue .new (),
666
683
}, Client )
684
+
685
+ client .errorHandler = function (...) return client :onError (... ) end
686
+
687
+ return client
667
688
end
668
689
669
690
--- @class PGTransactionContext
770
791
--- @class PGPool
771
792
--- @field url string **readonly** connection url
772
793
--- @field max number maximum number of clients in the pool (default : 10)
794
+ --- @field threshold number threshold of waiting : connect (... ) acquire functions to create a new client (default : 5)
773
795
--- @field private clients PGClient[]
774
796
--- @field private queue { push : fun ( self , f : function ), prepend : fun ( self , f : function ), pop : ( fun ( self ): function ), size : fun ( self ): number }
797
+ --- @field private errorHandler function function that just calls self : onError (... )
775
798
local Pool = {}
776
799
777
800
--- @private
@@ -799,23 +822,29 @@ function Pool:acquireClient()
799
822
800
823
-- then if client is ready to use, just call callback immideatly
801
824
if client :connected () then
825
+ -- notify about acquired client
826
+ xpcall (function () return self :onAcquire (client ) end , client .errorHandler )
827
+
828
+ -- call callback
802
829
local callback = self .queue :pop ()
803
- xpcall (callback , ErrorNoHaltWithStack , client )
830
+ xpcall (callback , client .errorHandler , client )
831
+
804
832
-- if client was not connected, begin connection process
805
833
-- unless it's already connecting, then just wait until it will be connected
806
834
elseif not client .connecting then
807
835
client :connect (function (ok , err )
808
836
if ok then
809
- client :release ()
837
+ client :release (true )
838
+ xpcall (function () return self :onConnect (client ) end , client .errorHandler )
810
839
self :acquireClient ()
811
840
return
812
841
end
813
842
814
- ErrorNoHaltWithStack (" PGPool - failed to connect to the database: " .. err )
843
+ self : onError (" PGPool - failed to connect to the database: " .. err )
815
844
816
845
-- try to restart acquiring, maybe we can connect with another try
817
846
timer .Simple (5 , function ()
818
- client :release ()
847
+ client :release (true )
819
848
self :acquireClient ()
820
849
end )
821
850
end )
@@ -838,9 +867,14 @@ function Pool:processQueue()
838
867
-- if we haven't found available clients, and queue is too big, we need to create new client
839
868
local clients = # self .clients
840
869
local waiters = self .queue :size ()
841
- local threshold = clients * 2
870
+ local threshold = clients * self . threshold
842
871
if clients < self .max and waiters > threshold then
843
- self .clients [clients + 1 ] = async_postgres .Client (self .url )
872
+ local client = async_postgres .Client (self .url )
873
+ client .onError = function (client , message )
874
+ return self :onError (message )
875
+ end
876
+
877
+ self .clients [clients + 1 ] = client
844
878
self :acquireClient ()
845
879
end
846
880
end
@@ -949,19 +983,21 @@ function Pool:describePortal(name, callback)
949
983
end
950
984
951
985
--- @async
986
+ --- @param client PGClient
987
+ --- @param callback fun ( ctx : PGTransactionContext )
952
988
local function transactionThread (client , callback )
953
989
xpcall (function ()
954
990
local ctx = TransactionContext .new (client )
955
991
local ok = xpcall (function ()
956
992
ctx :query (" BEGIN" )
957
993
callback (ctx )
958
994
ctx :query (" COMMIT" )
959
- end , ErrorNoHaltWithStack )
995
+ end , client . errorHandler )
960
996
961
997
if not ok then
962
998
ctx :query (" ROLLBACK" )
963
999
end
964
- end , ErrorNoHaltWithStack )
1000
+ end , client . errorHandler )
965
1001
client :release ()
966
1002
end
967
1003
@@ -987,14 +1023,55 @@ function Pool:transaction(callback)
987
1023
end )
988
1024
end
989
1025
1026
+ --- This **event** function is called whenever new client connection
1027
+ --- was estabileshed.
1028
+ --- You can run setup commands on a client.
1029
+ --- ```lua
1030
+ --- function pool:onConnect(client)
1031
+ --- client:query("SET DATESTYLE = iso, mdy")
1032
+ --- end
1033
+ --- ```
1034
+ --- @param client PGClient client that was connected
1035
+ function Pool :onConnect (client )
1036
+ end
1037
+
1038
+ --- This **event** function is called whenever a client was acquired.
1039
+ --- @param client PGClient client that was acquired
1040
+ function Pool :onAcquire (client )
1041
+ end
1042
+
1043
+ --- This **event** function is called whenever an error occurs inside connect/query callback for client or pool.
1044
+ --- @param message string error message
1045
+ --- @param client PGClient ? client that caused the error , or nil if error happened in pool
1046
+ function Pool :onError (message , client )
1047
+ return ErrorNoHaltWithStack (message )
1048
+ end
1049
+
1050
+ --- This **event** function is called whenever a client was released back to the pool.
1051
+ ---
1052
+ --- Warning! This funct
1053
+ --- @param client PGClient client that was released
1054
+ function Pool :onRelease (client )
1055
+ end
1056
+
990
1057
--- Creates a new connection pool with given connection url,
991
1058
--- then use :connect() to get available connection,
992
1059
--- and then :release() to release it back to the pool
993
1060
function async_postgres .Pool (url )
994
- return setmetatable ({
1061
+ --- @class PGPool
1062
+ local pool = setmetatable ({
995
1063
url = url ,
996
1064
clients = { async_postgres .Client (url ) },
997
1065
queue = Queue .new (),
998
1066
max = 10 ,
1067
+ threshold = 5 ,
999
1068
}, Pool )
1069
+
1070
+ pool .clients [1 ].onError = function (client , message )
1071
+ return pool :onError (message , client )
1072
+ end
1073
+
1074
+ pool .errorHandler = function (...) return pool :onError (... ) end
1075
+
1076
+ return pool
1000
1077
end
0 commit comments