4141from typing import Dict , List , Any , Union
4242import datetime
4343import weakref
44+ from uuid import UUID
4445from enum import Enum , IntEnum
4546from 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
4850
4951FLAG_NOT_SET = 0
5052FLAG_SET = 1
5153
5254# Enums
53- class ShutdownMode (IntEnum ):
54- """Shutdown mode.
55- """
56- ONLINE = 0
57- MULTI = 1
58- SINGLE = 2
59- FULL = 3
60-
6155class BackupState (IntEnum ):
6256 """Physical backup state.
6357 """
@@ -73,11 +67,14 @@ class State(IntEnum):
7367
7468class IsolationMode (IntEnum ):
7569 """Transaction solation mode.
70+
71+ .. versionchanged:: 1.4.0 - `READ_COMMITTED_READ_CONSISTENCY` value added
7672 """
7773 CONSISTENCY = 0
7874 CONCURRENCY = 1
7975 READ_COMMITTED_RV = 2
8076 READ_COMMITTED_NO_RV = 3
77+ READ_COMMITTED_READ_CONSISTENCY = 4
8178
8279class Group (IntEnum ):
8380 """Statistics group.
@@ -95,6 +92,17 @@ class Security(Enum):
9592 SELF = 'Self'
9693 OTHER = 'Other'
9794
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+
98106# Classes
99107class Monitor :
100108 """Class for access to Firebird monitoring tables.
@@ -119,6 +127,7 @@ def __init__(self, connection: Connection):
119127 self .__iostats = None
120128 self .__variables = None
121129 self .__tablestats = None
130+ self .__compiled_statements = None
122131 def __del__ (self ):
123132 if not self .closed :
124133 self .close ()
@@ -224,13 +233,14 @@ def iostats(self) -> DataList[IOStatsInfo]:
224233 """List of all I/O statistics.
225234 """
226235 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,
228238r.MON$RECORD_SEQ_READS, r.MON$RECORD_IDX_READS, r.MON$RECORD_INSERTS,
229239r.MON$RECORD_UPDATES, r.MON$RECORD_DELETES, r.MON$RECORD_BACKOUTS,
230240r.MON$RECORD_PURGES, r.MON$RECORD_EXPUNGES, r.MON$RECORD_LOCKS, r.MON$RECORD_WAITS,
231241r.MON$RECORD_CONFLICTS, r.MON$BACKVERSION_READS, r.MON$FRAGMENT_READS, r.MON$RECORD_RPT_READS,
232242io.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 }
234244FROM MON$RECORD_STATS r join MON$IO_STATS io
235245 on r.MON$STAT_ID = io.MON$STAT_ID and r.MON$STAT_GROUP = io.MON$STAT_GROUP
236246 join MON$MEMORY_USAGE m
@@ -253,17 +263,29 @@ def tablestats(self) -> DataList[TableStatsInfo]:
253263 """List of all table record I/O statistics.
254264 """
255265 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,
257268ts.MON$RECORD_STAT_ID, r.MON$RECORD_SEQ_READS, r.MON$RECORD_IDX_READS, r.MON$RECORD_INSERTS,
258269r.MON$RECORD_UPDATES, r.MON$RECORD_DELETES, r.MON$RECORD_BACKOUTS,
259270r.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 }
261272FROM MON$TABLE_STATS ts join MON$RECORD_STATS r
262273 on ts.MON$RECORD_STAT_ID = r.MON$STAT_ID"""
263274 self .__tablestats = DataList ((TableStatsInfo (self , row ) for row
264275 in self ._select (cmd )),
265276 TableStatsInfo , 'item.stat_id' , frozen = True )
266277 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
267289
268290class InfoItem :
269291 """Base class for all database monitoring objects.
@@ -403,6 +425,52 @@ def tablestats(self) -> Dict[str, TableStatsInfo]:
403425 """
404426 return {io .table_name : io for io in self .monitor .tablestats
405427 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 )
406474
407475class AttachmentInfo (InfoItem ):
408476 """Information about attachment (connection) to database.
@@ -472,27 +540,27 @@ def user(self) -> str:
472540 """
473541 return self ._attributes ['MON$USER' ]
474542 @property
475- def role (self ) -> str :
543+ def role (self ) -> Optional [ str ] :
476544 """Role name.
477545 """
478546 return self ._attributes ['MON$ROLE' ]
479547 @property
480- def remote_protocol (self ) -> str :
548+ def remote_protocol (self ) -> Optional [ str ] :
481549 """Remote protocol name.
482550 """
483551 return self ._attributes ['MON$REMOTE_PROTOCOL' ]
484552 @property
485- def remote_address (self ) -> str :
553+ def remote_address (self ) -> Optional [ str ] :
486554 """Remote address.
487555 """
488556 return self ._attributes ['MON$REMOTE_ADDRESS' ]
489557 @property
490- def remote_pid (self ) -> int :
558+ def remote_pid (self ) -> Optional [ int ] :
491559 """Remote client process ID.
492560 """
493561 return self ._attributes ['MON$REMOTE_PID' ]
494562 @property
495- def remote_process (self ) -> str :
563+ def remote_process (self ) -> Optional [ str ] :
496564 """Remote client process pathname.
497565 """
498566 return self ._attributes ['MON$REMOTE_PROCESS' ]
@@ -566,6 +634,57 @@ def tablestats(self) -> Dict[str, TableStatsInfo]:
566634 """
567635 return {io .table_name : io for io in self .monitor .tablestats
568636 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' )
569688
570689class TransactionInfo (InfoItem ):
571690 """Information about transaction.
@@ -747,6 +866,29 @@ def tablestats(self) -> Dict[str, TableStatsInfo]:
747866 """
748867 return {io .table_name : io for io in self .monitor .tablestats
749868 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' ])
750892
751893class CallStackInfo (InfoItem ):
752894 """Information about PSQL call (stack frame).
@@ -819,6 +961,14 @@ def iostats(self) -> IOStatsInfo:
819961 """
820962 return self .monitor .iostats .find (lambda io : (io .stat_id == self .stat_id )
821963 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' ])
822972
823973class IOStatsInfo (InfoItem ):
824974 """Information about page and row level I/O operations, and about memory consumption.
@@ -958,6 +1108,15 @@ def repeated_reads(self) -> int:
9581108 """Number of repeated record reads.
9591109 """
9601110 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+
9611120
9621121class TableStatsInfo (InfoItem ):
9631122 """Information about row level I/O operations on single table.
@@ -1106,3 +1265,54 @@ def value(self) -> str:
11061265 """Value of context variable.
11071266 """
11081267 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+ """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