41
41
from typing import Dict , List , Any , Union
42
42
import datetime
43
43
import weakref
44
+ from uuid import UUID
44
45
from enum import Enum , IntEnum
45
46
from firebird .base .collections import DataList
46
- from firebird .driver import tpb , Connection , Cursor , Statement , Isolation , Error , TraAccessMode
47
- from .schema import ObjectType , CharacterSet , Procedure , Trigger , Function
47
+ from firebird .driver import (tpb , Connection , Cursor , Statement , Isolation , Error ,
48
+ TraAccessMode , ReplicaMode , ShutdownMode )
49
+ from .schema import ObjectType , CharacterSet , Procedure , Trigger , Function , ObjectType
48
50
49
51
FLAG_NOT_SET = 0
50
52
FLAG_SET = 1
51
53
52
54
# Enums
53
- class ShutdownMode (IntEnum ):
54
- """Shutdown mode.
55
- """
56
- ONLINE = 0
57
- MULTI = 1
58
- SINGLE = 2
59
- FULL = 3
60
-
61
55
class BackupState (IntEnum ):
62
56
"""Physical backup state.
63
57
"""
@@ -73,11 +67,14 @@ class State(IntEnum):
73
67
74
68
class IsolationMode (IntEnum ):
75
69
"""Transaction solation mode.
70
+
71
+ .. versionchanged:: 1.4.0 - `READ_COMMITTED_READ_CONSISTENCY` value added
76
72
"""
77
73
CONSISTENCY = 0
78
74
CONCURRENCY = 1
79
75
READ_COMMITTED_RV = 2
80
76
READ_COMMITTED_NO_RV = 3
77
+ READ_COMMITTED_READ_CONSISTENCY = 4
81
78
82
79
class Group (IntEnum ):
83
80
"""Statistics group.
@@ -95,6 +92,17 @@ class Security(Enum):
95
92
SELF = 'Self'
96
93
OTHER = 'Other'
97
94
95
+ class CryptState (IntEnum ):
96
+ """Database encryption state.
97
+
98
+ .. versionadded:: 1.4.0
99
+ """
100
+ NOT_ENCRYPTED = 0
101
+ ENCRYPTED = 1
102
+ DECRYPTION_IN_PROGRESS = 2
103
+ ENCRYPTION_IN_PROGRESS = 3
104
+
105
+
98
106
# Classes
99
107
class Monitor :
100
108
"""Class for access to Firebird monitoring tables.
@@ -119,6 +127,7 @@ def __init__(self, connection: Connection):
119
127
self .__iostats = None
120
128
self .__variables = None
121
129
self .__tablestats = None
130
+ self .__compiled_statements = None
122
131
def __del__ (self ):
123
132
if not self .closed :
124
133
self .close ()
@@ -224,13 +233,14 @@ def iostats(self) -> DataList[IOStatsInfo]:
224
233
"""List of all I/O statistics.
225
234
"""
226
235
if self .__iostats is None :
227
- cmd = """SELECT r.MON$STAT_ID, r.MON$STAT_GROUP,
236
+ ext = '' if self .db .ods < 13.0 else ', r.MON$RECORD_IMGC'
237
+ cmd = f"""SELECT r.MON$STAT_ID, r.MON$STAT_GROUP,
228
238
r.MON$RECORD_SEQ_READS, r.MON$RECORD_IDX_READS, r.MON$RECORD_INSERTS,
229
239
r.MON$RECORD_UPDATES, r.MON$RECORD_DELETES, r.MON$RECORD_BACKOUTS,
230
240
r.MON$RECORD_PURGES, r.MON$RECORD_EXPUNGES, r.MON$RECORD_LOCKS, r.MON$RECORD_WAITS,
231
241
r.MON$RECORD_CONFLICTS, r.MON$BACKVERSION_READS, r.MON$FRAGMENT_READS, r.MON$RECORD_RPT_READS,
232
242
io.MON$PAGE_FETCHES, io.MON$PAGE_MARKS, io.MON$PAGE_READS, io.MON$PAGE_WRITES,
233
- m.MON$MEMORY_ALLOCATED, m.MON$MEMORY_USED, m.MON$MAX_MEMORY_ALLOCATED, m.MON$MAX_MEMORY_USED
243
+ m.MON$MEMORY_ALLOCATED, m.MON$MEMORY_USED, m.MON$MAX_MEMORY_ALLOCATED, m.MON$MAX_MEMORY_USED{ ext }
234
244
FROM MON$RECORD_STATS r join MON$IO_STATS io
235
245
on r.MON$STAT_ID = io.MON$STAT_ID and r.MON$STAT_GROUP = io.MON$STAT_GROUP
236
246
join MON$MEMORY_USAGE m
@@ -253,17 +263,29 @@ def tablestats(self) -> DataList[TableStatsInfo]:
253
263
"""List of all table record I/O statistics.
254
264
"""
255
265
if self .__tablestats is None :
256
- cmd = """SELECT ts.MON$STAT_ID, ts.MON$STAT_GROUP, ts.MON$TABLE_NAME,
266
+ ext = '' if self .db .ods < 13.0 else ', r.MON$RECORD_IMGC'
267
+ cmd = f"""SELECT ts.MON$STAT_ID, ts.MON$STAT_GROUP, ts.MON$TABLE_NAME,
257
268
ts.MON$RECORD_STAT_ID, r.MON$RECORD_SEQ_READS, r.MON$RECORD_IDX_READS, r.MON$RECORD_INSERTS,
258
269
r.MON$RECORD_UPDATES, r.MON$RECORD_DELETES, r.MON$RECORD_BACKOUTS,
259
270
r.MON$RECORD_PURGES, r.MON$RECORD_EXPUNGES, r.MON$RECORD_LOCKS, r.MON$RECORD_WAITS,
260
- r.MON$RECORD_CONFLICTS, r.MON$BACKVERSION_READS, r.MON$FRAGMENT_READS, r.MON$RECORD_RPT_READS
271
+ r.MON$RECORD_CONFLICTS, r.MON$BACKVERSION_READS, r.MON$FRAGMENT_READS, r.MON$RECORD_RPT_READS{ ext }
261
272
FROM MON$TABLE_STATS ts join MON$RECORD_STATS r
262
273
on ts.MON$RECORD_STAT_ID = r.MON$STAT_ID"""
263
274
self .__tablestats = DataList ((TableStatsInfo (self , row ) for row
264
275
in self ._select (cmd )),
265
276
TableStatsInfo , 'item.stat_id' , frozen = True )
266
277
return self .__tablestats
278
+ @property
279
+ def compiled_statements (self ) -> DataList [CompiledStatementInfo ]:
280
+ """List of all compiled statements.
281
+
282
+ .. versionadded:: 1.4.0
283
+ """
284
+ if self .__compiled_statements is None :
285
+ self .__compiled_statements = DataList ((CompiledStatementInfo (self , row ) for row
286
+ in self ._select ('select * from mon$compiled_statements' )),
287
+ CompiledStatementInfo , 'item.id' , frozen = True )
288
+ return self .__compiled_statements
267
289
268
290
class InfoItem :
269
291
"""Base class for all database monitoring objects.
@@ -403,6 +425,52 @@ def tablestats(self) -> Dict[str, TableStatsInfo]:
403
425
"""
404
426
return {io .table_name : io for io in self .monitor .tablestats
405
427
if (io .stat_id == self .stat_id ) and (io .group is Group .DATABASE )}
428
+ # Firebird 4
429
+ @property
430
+ def crypt_state (self ) -> Optional [CryptState ]:
431
+ """Current state of database encryption.
432
+
433
+ .. versionadded:: 1.4.0
434
+ """
435
+ value = self ._attributes .get ('MON$CRYPT_STATE' )
436
+ return None if value is None else CryptState (value )
437
+ @property
438
+ def guid (self ) -> Optional [UUID ]:
439
+ """Database GUID (persistent until restore / fixup).
440
+
441
+ .. versionadded:: 1.4.0
442
+ """
443
+ value = self ._attributes .get ('MON$GUID' )
444
+ return None if value is None else UUID (value )
445
+ @property
446
+ def file_id (self ) -> Optional [str ]:
447
+ """Unique ID of the database file at the filesystem level.
448
+
449
+ .. versionadded:: 1.4.0
450
+ """
451
+ return self ._attributes .get ('MON$FILE_ID' )
452
+ @property
453
+ def next_attachment (self ) -> Optional [int ]:
454
+ """Current value of the next attachment ID counter.
455
+
456
+ .. versionadded:: 1.4.0
457
+ """
458
+ return self ._attributes .get ('MON$NEXT_ATTACHMENT' )
459
+ @property
460
+ def next_statement (self ) -> Optional [int ]:
461
+ """Current value of the next statement ID counter.
462
+
463
+ .. versionadded:: 1.4.0
464
+ """
465
+ return self ._attributes .get ('MON$NEXT_STATEMENT' )
466
+ @property
467
+ def replica_mode (self ) -> Optional [ReplicaMode ]:
468
+ """Database replica mode.
469
+
470
+ .. versionadded:: 1.4.0
471
+ """
472
+ value = self ._attributes .get ('MON$REPLICA_MODE' )
473
+ return None if value is None else ReplicaMode (value )
406
474
407
475
class AttachmentInfo (InfoItem ):
408
476
"""Information about attachment (connection) to database.
@@ -472,27 +540,27 @@ def user(self) -> str:
472
540
"""
473
541
return self ._attributes ['MON$USER' ]
474
542
@property
475
- def role (self ) -> str :
543
+ def role (self ) -> Optional [ str ] :
476
544
"""Role name.
477
545
"""
478
546
return self ._attributes ['MON$ROLE' ]
479
547
@property
480
- def remote_protocol (self ) -> str :
548
+ def remote_protocol (self ) -> Optional [ str ] :
481
549
"""Remote protocol name.
482
550
"""
483
551
return self ._attributes ['MON$REMOTE_PROTOCOL' ]
484
552
@property
485
- def remote_address (self ) -> str :
553
+ def remote_address (self ) -> Optional [ str ] :
486
554
"""Remote address.
487
555
"""
488
556
return self ._attributes ['MON$REMOTE_ADDRESS' ]
489
557
@property
490
- def remote_pid (self ) -> int :
558
+ def remote_pid (self ) -> Optional [ int ] :
491
559
"""Remote client process ID.
492
560
"""
493
561
return self ._attributes ['MON$REMOTE_PID' ]
494
562
@property
495
- def remote_process (self ) -> str :
563
+ def remote_process (self ) -> Optional [ str ] :
496
564
"""Remote client process pathname.
497
565
"""
498
566
return self ._attributes ['MON$REMOTE_PROCESS' ]
@@ -566,6 +634,57 @@ def tablestats(self) -> Dict[str, TableStatsInfo]:
566
634
"""
567
635
return {io .table_name : io for io in self .monitor .tablestats
568
636
if (io .stat_id == self .stat_id ) and (io .group is Group .ATTACHMENT )}
637
+ # Firebird 4
638
+ @property
639
+ def idle_timeout (self ) -> Optional [int ]:
640
+ """Connection level idle timeout.
641
+
642
+ .. versionadded:: 1.4.0
643
+ """
644
+ return self ._attributes .get ('MON$IDLE_TIMEOUT' )
645
+ @property
646
+ def idle_timer (self ) -> Optional [datetime ]:
647
+ """Idle timer expiration time.
648
+
649
+ .. versionadded:: 1.4.0
650
+ """
651
+ return self ._attributes .get ('MON$IDLE_TIMER' )
652
+ @property
653
+ def statement_timeout (self ) -> Optional [int ]:
654
+ """Connection level statement timeout.
655
+
656
+ .. versionadded:: 1.4.0
657
+ """
658
+ return self ._attributes .get ('MON$STATEMENT_TIMEOUT' )
659
+ @property
660
+ def wire_compressed (self ) -> Optional [bool ]:
661
+ """Wire compression.
662
+
663
+ .. versionadded:: 1.4.0
664
+ """
665
+ return bool (self ._attributes .get ('MON$WIRE_COMPRESSED' ))
666
+ @property
667
+ def wire_encrypted (self ) -> Optional [bool ]:
668
+ """Wire encryption.
669
+
670
+ .. versionadded:: 1.4.0
671
+ """
672
+ return bool (self ._attributes .get ('MON$WIRE_ENCRYPTED' ))
673
+ @property
674
+ def wire_crypt_plugin (self ) -> Optional [str ]:
675
+ """Name of the wire encryption plugin used by client.
676
+
677
+ .. versionadded:: 1.4.0
678
+ """
679
+ return self ._attributes .get ('MON$WIRE_CRYPT_PLUGIN' )
680
+ # Firebird 5
681
+ @property
682
+ def session_timezone (self ) -> Optional [str ]:
683
+ """Actual timezone of the session.
684
+
685
+ .. versionadded:: 1.4.0
686
+ """
687
+ return self ._attributes .get ('MON$SESSION_TIMEZONE' )
569
688
570
689
class TransactionInfo (InfoItem ):
571
690
"""Information about transaction.
@@ -747,6 +866,29 @@ def tablestats(self) -> Dict[str, TableStatsInfo]:
747
866
"""
748
867
return {io .table_name : io for io in self .monitor .tablestats
749
868
if (io .stat_id == self .stat_id ) and (io .group is Group .STATEMENT )}
869
+ # Firebird 4
870
+ @property
871
+ def timeout (self ) -> Optional [int ]:
872
+ """Connection level statement timeout.
873
+
874
+ .. versionadded:: 1.4.0
875
+ """
876
+ return self ._attributes .get ('MON$STATEMENT_TIMEOUT' )
877
+ @property
878
+ def timer (self ) -> Optional [datetime ]:
879
+ """Statement timer expiration time.
880
+
881
+ .. versionadded:: 1.4.0
882
+ """
883
+ return self ._attributes .get ('MON$STATEMENT_TIMER' )
884
+ # Firebird 5
885
+ @property
886
+ def compiled_statement (self ) -> Optional [CompiledStatementInfo ]:
887
+ """`.CompiledStatementInfo` instance to which this statement relates.
888
+
889
+ .. versionadded:: 1.4.0
890
+ """
891
+ return self .monitor .compiled_statements .get (self ._attributes ['MON$COMPILED_STATEMENT_ID' ])
750
892
751
893
class CallStackInfo (InfoItem ):
752
894
"""Information about PSQL call (stack frame).
@@ -819,6 +961,14 @@ def iostats(self) -> IOStatsInfo:
819
961
"""
820
962
return self .monitor .iostats .find (lambda io : (io .stat_id == self .stat_id )
821
963
and (io .group is Group .CALL ))
964
+ # Firebird 5
965
+ @property
966
+ def compiled_statement (self ) -> Optional [CompiledStatementInfo ]:
967
+ """`.CompiledStatementInfo` instance to which this statement relates.
968
+
969
+ .. versionadded:: 1.4.0
970
+ """
971
+ return self .monitor .compiled_statements .get (self ._attributes ['MON$COMPILED_STATEMENT_ID' ])
822
972
823
973
class IOStatsInfo (InfoItem ):
824
974
"""Information about page and row level I/O operations, and about memory consumption.
@@ -958,6 +1108,15 @@ def repeated_reads(self) -> int:
958
1108
"""Number of repeated record reads.
959
1109
"""
960
1110
return self ._attributes .get ('MON$RECORD_RPT_READS' )
1111
+ # Firebird 4
1112
+ @property
1113
+ def intermediate_gc (self ) -> Optional [int ]:
1114
+ """Number of records processed by the intermediate garbage collection.
1115
+
1116
+ .. versionadded:: 1.4.0
1117
+ """
1118
+ return self ._attributes .get ('MON$RECORD_IMGC' )
1119
+
961
1120
962
1121
class TableStatsInfo (InfoItem ):
963
1122
"""Information about row level I/O operations on single table.
@@ -1106,3 +1265,54 @@ def value(self) -> str:
1106
1265
"""Value of context variable.
1107
1266
"""
1108
1267
return self ._attributes ['MON$VARIABLE_VALUE' ]
1268
+
1269
+ # Firebird 5
1270
+
1271
+ class CompiledStatementInfo (InfoItem ):
1272
+ """Information about compiled statement.
1273
+
1274
+ .. versionadded:: 1.4.0
1275
+ """
1276
+ def __init__ (self , monitor : Monitor , attributes : Dict [str , Any ]):
1277
+ super ().__init__ (monitor , attributes )
1278
+ self ._strip_attribute ('MON$OBJECT_NAME' )
1279
+ self ._strip_attribute ('MON$PACKAGE_NAME' )
1280
+ self ._strip_attribute ('MON$SQL_TEXT' )
1281
+ self ._strip_attribute ('MON$EXPLAINED_PLAN' )
1282
+ @property
1283
+ def id (self ) -> int :
1284
+ """Compiled statement ID.
1285
+ """
1286
+ return self ._attributes ['MON$COMPILED_STATEMENT_ID' ]
1287
+ @property
1288
+ def sql (self ) -> Optional [str ]:
1289
+ """Text of the SQL query.
1290
+ """
1291
+ return self ._attributes ['MON$SQL_TEXT' ]
1292
+ @property
1293
+ def plan (self ) -> Optional [str ]:
1294
+ """Plan (in the explained form) of the SQL query.
1295
+ """
1296
+ return self ._attributes .get ('MON$EXPLAINED_PLAN' )
1297
+ @property
1298
+ def object_name (self ) -> Optional [str ]:
1299
+ """PSQL object name.
1300
+ """
1301
+ return self ._attributes .get ('MON$OBJECT_NAME' )
1302
+ @property
1303
+ def object_type (self ) -> Optional [ObjectType ]:
1304
+
864B
"""PSQL object type.
1305
+ """
1306
+ value = self ._attributes .get ('MON$OBJECT_TYPE' )
1307
+ return value if value is None else ObjectType (value )
1308
+ @property
1309
+ def package_name (self ) -> Optional [str ]:
1310
+ """Package name of the PSQL object.
1311
+ """
1312
+ return self ._attributes .get ('MON$PACKAGE_NAME' )
1313
+ @property
1314
+ def iostats (self ) -> IOStatsInfo :
1315
+ """`.IOStatsInfo` for this object.
1316
+ """
1317
+ return self .monitor .iostats .find (lambda io : (io .stat_id == self .stat_id )
1318
+ and (io .group is Group .STATEMENT ))
0 commit comments