8000 Firebird 4 and 5 support · FirebirdSQL/python3-lib@8238593 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8238593

Browse files
committed
Firebird 4 and 5 support
1 parent 829521e commit 8238593

File tree

7 files changed

+468
-79
lines changed

7 files changed

+468
-79
lines changed

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "firebird-lib"
7-
version = "1.3.0"
7+
version = "1.4.0"
88
description = "Firebird driver extension library"
99
readme = "README.rst"
1010
requires-python = ">=3.8"
@@ -27,8 +27,8 @@ classifiers = [
2727
"Topic :: Database",
2828
]
2929
dependencies = [
30-
"firebird-base>=1.5.0",
31-
"firebird-driver>=1.7.0",
30+
"firebird-base>=1.6.1",
31+
"firebird-driver>=1.9.0",
3232
]
3333

3434
[project.urls]

src/firebird/lib/monitor.py

Lines changed: 229 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -41,23 +41,17 @@
4141
from typing import Dict, List, Any, Union
4242
import datetime
4343
import weakref
44+
from uuid import UUID
4445
from enum import Enum, IntEnum
4546
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
4850

4951
FLAG_NOT_SET = 0
5052
FLAG_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-
6155
class BackupState(IntEnum):
6256
"""Physical backup state.
6357
"""
@@ -73,11 +67,14 @@ class State(IntEnum):
7367

7468
class 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

8279
class 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
99107
class 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,
228238
r.MON$RECORD_SEQ_READS, r.MON$RECORD_IDX_READS, r.MON$RECORD_INSERTS,
229239
r.MON$RECORD_UPDATES, r.MON$RECORD_DELETES, r.MON$RECORD_BACKOUTS,
230240
r.MON$RECORD_PURGES, r.MON$RECORD_EXPUNGES, r.MON$RECORD_LOCKS, r.MON$RECORD_WAITS,
231241
r.MON$RECORD_CONFLICTS, r.MON$BACKVERSION_READS, r.MON$FRAGMENT_READS, r.MON$RECORD_RPT_READS,
232242
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}
234244
FROM 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,
257268
ts.MON$RECORD_STAT_ID, r.MON$RECORD_SEQ_READS, r.MON$RECORD_IDX_READS, r.MON$RECORD_INSERTS,
258269
r.MON$RECORD_UPDATES, r.MON$RECORD_DELETES, r.MON$RECORD_BACKOUTS,
259270
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}
261272
FROM 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

268290
class 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

407475
class 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

570689
class 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

751893
class 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

823973
class 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

9621121
class 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+
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

Comments
 (0)
0