Skip to content

Commit d988898

Browse files
committed
Release 1.4.0
1 parent b15ce22 commit d988898

File tree

9 files changed

+315
-233
lines changed

9 files changed

+315
-233
lines changed

docs/changelog.txt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,43 @@
22
Changelog
33
#########
44

5+
Version 1.4.0
6+
=============
7+
8+
Improvements:
9+
-------------
10+
11+
* Added `role` parameter to `.connect_server` and `.SPB_ATTACH`.
12+
* Added `encoding` parameter to `.SPB_ATTACH` with default value `ascii` - used to encode
13+
`config`, `user`, `password` and `expected_db` values.
14+
* Added `encoding` parameter to `.TPB` with default value `ascii` - used to encode table names.
15+
* `.DPB` parameter `charset` is now used to determine encoding for `config`, `user`,
16+
`password` and `role` values.
17+
* `.connect_server` has new `encoding` parameter with default value `ascii` that is passed
18+
to new `.Server.encoding` attribute. `.Server.encoding` is used to encode/decode various
19+
string values passed between client and server in parameter buffers (see below), and text
20+
output from services.
21+
* `.ServerInfoProvider` now uses `.Server.encoding` for returned SERVER_VERSION, IMPLEMENTATION,
22+
GET_ENV, GET_ENV_MSG, GET_ENV_LOCK, USER_DBPATH and DBNAME values.
23+
* `.ServerDbServices` now uses `.Server.encoding` for DBNAME, SQL_ROLE_NAME, FILE, SKIP_DATA,
24+
INCLUDE_DATA, INCLUDE_TABLE, EXCLUDE_TABLE, INCLUDE_INDEX, EXCLUDE_INDEX, LINE and
25+
isc_spb_sts_table SPB values.
26+
* `.ServerUserServices` now uses `.Server.encoding` for DBNAME, SQL_ROLE_NAME, USER_NAME,
27+
GROUP_NAME, FIRST_NAME, MIDDLE_NAME, LAST_NAME and PASSWORD (on storage only) SPB values.
28+
* `.ServerTraceServices` now uses `.Server.encoding` for CONFIG SPB value.
29+
* Failed `.Connection.close` should not cause problems on object destruction anymore.
30+
* Failed `.Server.close` should not cause problems on object destruction anymore.
31+
32+
Backward incompatible changes:
33+
------------------------------
34+
35+
* `.tpb` parameter `access` renamed to `access_mode`.
36+
* `.FirebirdWarning` now descends from `UserWarning` instead `Warning`, and is reported
37+
to application via `.warnings.warn` instead raised as exception.
38+
* `.iAttachment_v3` attribute `charset` was renamed to `encoding`.
39+
* `.iXpbBuilder.insert_string` optional parameter `encoding` is now keyword-only.
40+
Parameter also added to `.iXpbBuilder.get_string` method.
41+
542
Version 1.3.4
643
=============
744

docs/conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323
author = 'Pavel Císař'
2424

2525
# The short X.Y version
26-
version = '1.3.4'
26+
version = '1.4.0'
2727

2828
# The full version, including alpha/beta/rc tags
29-
release = '1.3.4'
29+
release = '1.4.0'
3030

3131

3232
# -- General configuration ---------------------------------------------------

docs/ref-types.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ FirebirdWarning
5555
This is the exception inheritance layout::
5656

5757
StandardError
58-
|__Warning
58+
|__UserWarning
5959
|__FirebirdWarning
6060
|__Error
6161
|__InterfaceError

firebird/driver/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,4 @@
5959
Server, Statement
6060

6161
#: Current driver version, SEMVER string.
62-
__VERSION__ = '1.3.4'
62+
__VERSION__ = '1.4.0'

firebird/driver/core.py

Lines changed: 183 additions & 156 deletions
Large diffs are not rendered by default.

firebird/driver/interfaces.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,7 @@ class iAttachment_v3(iReferenceCounted):
868868
VERSION = 3
869869
def __init__(self, intf):
870870
super().__init__(intf)
871+
#: Encoding used for string values
871872
self.encoding: str = 'ascii'
872873
def get_info(self, items: bytes, buffer: bytes) -> None:
873874
"Replaces `isc_database_info()`"

firebird/driver/types.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,12 @@
3535
"""
3636

3737
from __future__ import annotations
38-
from typing import Tuple, List, Callable, Protocol
38+
from typing import Tuple, List, Callable, Protocol, Union
3939
import time
4040
import datetime
4141
import decimal
4242
from dateutil import tz
43+
from pathlib import Path
4344
from enum import Enum, IntEnum, IntFlag
4445
from dataclasses import dataclass, field
4546
from firebird.base.types import Error
@@ -1358,6 +1359,8 @@ def __cmp__(self, other): # pragma: no cover
13581359
DESCRIPTION = Tuple[str, type, int, int, int, int, bool]
13591360
#: Callback that accepts line of text output
13601361
CB_OUTPUT_LINE = Callable[[str], None]
1362+
#: File name (incl. path) specification
1363+
FILESPEC = Union[str, Path]
13611364

13621365
class Transactional(Protocol): # pragma: no cover
13631366
"""Protocol type for object that supports transactional processing."""

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ all-files=True
55

66
[metadata]
77
name = firebird-driver
8-
version = 1.3.4
8+
version = 1.4.0
99
description = Firebird driver
1010
long_description = file: README.rst
1111
long_description_content_type = text/x-rst; charset=UTF-8

test/test_driver.py

Lines changed: 85 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@
5151
# Default user password
5252
FBTEST_PASSWORD = 'masterkey'
5353

54+
cfg = driver_config.register_server('FBTEST_HOST')
55+
cfg.host.value = FBTEST_HOST
56+
cfg.user.value = FBTEST_USER
57+
cfg.password.value = FBTEST_PASSWORD
58+
5459
trace = False
5560

5661
if not sys.warnoptions:
@@ -653,29 +658,46 @@ def setUp(self):
653658
super().setUp()
654659
self.dbfile = os.path.join(self.dbpath, self.FBTEST_DB)
655660
self.db1 = os.path.join(self.dbpath, 'fbtest-1.fdb')
656-
self.db2 = os.path.join(self.dbpath, 'fbtest-2.fdb')
657-
self.con1 = create_database(self.db1, user=FBTEST_USER, password=FBTEST_PASSWORD, overwrite=True)
661+
cfg = driver_config.register_database('dts-1')
662+
cfg.server.value = 'FBTEST_HOST'
663+
cfg.database.value = self.db1
664+
cfg.no_linger.value = True
665+
self.con1 = create_database('dts-1', user=FBTEST_USER, password=FBTEST_PASSWORD, overwrite=True)
658666
self.con1._logging_id_ = self.__class__.__name__
659667
self.con1.execute_immediate("recreate table T (PK integer, C1 integer)")
660668
self.con1.commit()
661-
self.con2 = create_database(self.db2, user=FBTEST_USER, password=FBTEST_PASSWORD, overwrite=True)
669+
670+
self.db2 = os.path.join(self.dbpath, 'fbtest-2.fdb')
671+
cfg = driver_config.register_database('dts-2')
672+
cfg.server.value = 'FBTEST_HOST'
673+
cfg.database.value = self.db2
674+
cfg.no_linger.value = True
675+
self.con2 = create_database('dts-2', user=FBTEST_USER, password=FBTEST_PASSWORD, overwrite=True)
662676
self.con2._logging_id_ = self.__class__.__name__
663677
self.con2.execute_immediate("recreate table T (PK integer, C1 integer)")
664678
self.con2.commit()
665679
def tearDown(self):
666680
#if self.con1 and self.con1.group:
667681
## We can't drop database via connection in group
668682
#self.con1.group.disband()
669-
if not self.con1:
670-
self.con1 = connect(host=FBTEST_HOST, database=self.db1,
671-
user=FBTEST_USER, password=FBTEST_PASSWORD, no_linger=True)
672-
self.con1.drop_database()
673-
self.con1.close()
674-
if not self.con2:
675-
self.con2 = connect(host=FBTEST_HOST, database=self.db2,
676-
user=FBTEST_USER, password=FBTEST_PASSWORD, no_linger=True)
677-
self.con2.drop_database()
678-
self.con2.close()
683+
if self.con1 is not None:
684+
self.con1.close()
685+
if self.con2 is not None:
686+
self.con2.close()
687+
#
688+
with connect_server('FBTEST_HOST') as srv:
689+
srv.database.shutdown(database=self.db1, mode=ShutdownMode.FULL,
690+
method=ShutdownMethod.FORCED, timeout=0)
691+
srv.database.bring_online(database=self.db1)
692+
with connect('dts-1') as con:
693+
con.drop_database()
694+
#
695+
with connect_server('FBTEST_HOST') as srv:
696+
srv.database.shutdown(database=self.db2, mode=ShutdownMode.FULL,
697+
method=ShutdownMethod.FORCED, timeout=0)
698+
srv.database.bring_online(database=self.db2)
699+
with connect('dts-2') as con:
700+
con.drop_database()
679701
def test_context_manager(self):
680702
with DistributedTransactionManager((self.con1, self.con2)) as dt:
681703
q = 'select * from T order by pk'
@@ -785,62 +807,54 @@ def test_simple_dt(self):
785807
def test_limbo_transactions(self):
786808
self.skipTest('Not implemented yet')
787809
#return
788-
with connect_server(host=FBTEST_HOST, user=FBTEST_USER, password=FBTEST_PASSWORD) as svc:
789-
dt = DistributedTransactionManager((self.con1, self.con2))
790-
ids1 = svc.get_limbo_transaction_ids(database=self.db1)
791-
self.assertEqual(ids1, [])
792-
ids2 = svc.get_limbo_transaction_ids(database=self.db2)
793-
self.assertEqual(ids2, [])
794-
dt.execute_immediate('insert into t (pk) values (3)')
795-
dt.prepare()
796-
# Force out both connections
797-
dt._tra.release()
798-
dt._tra = None
799-
dt.close()
800-
self.con1.close()
801-
self.con1 = None
802-
self.con2.close()
803-
self.con2 = None
804-
#
805-
ids1 = svc.get_limbo_transaction_ids(database=self.db1)
806-
id1 = ids1[0]
807-
ids2 = svc.get_limbo_transaction_ids(database=self.db2)
808-
id2 = ids2[0]
809-
# Data chould be blocked by limbo transaction
810-
if not self.con1:
811-
self.con1 = connect(host=FBTEST_HOST, database=self.db1, user=FBTEST_USER,
812-
password=FBTEST_PASSWORD, no_linger=True)
813-
self.con1._logging_id_ = self.__class__.__name__
814-
if not self.con2:
815-
self.con2 = connect(host=FBTEST_HOST, database=self.db2, user=FBTEST_USER,
816-
password=FBTEST_PASSWORD, no_linger=True)
817-
self.con2._logging_id_ = self.__class__.__name__
818-
c1 = self.con1.cursor()
819-
c1.execute('select * from t')
820-
with self.assertRaises(DatabaseError) as cm:
821-
row = c1.fetchall()
822-
self.assertTupleEqual(cm.exception.args,
823-
('Cursor.fetchone:\n- SQLCODE: -911\n- record from transaction %i is stuck in limbo' % id1, -911, 335544459))
824-
c2 = self.con2.cursor()
825-
c2.execute('select * from t')
826-
with self.assertRaises(DatabaseError) as cm:
827-
row = c2.fetchall()
828-
self.assertTupleEqual(cm.exception.args,
829-
('Cursor.fetchone:\n- SQLCODE: -911\n- record from transaction %i is stuck in limbo' % id2, -911, 335544459))
830-
# resolve via service
831-
svc = connect_server(host=FBTEST_HOST, password=FBTEST_PASSWORD)
832-
svc.commit_limbo_transaction(database=self.db1, transaction_id=id1)
833-
svc.rollback_limbo_transaction(database=self.db2, transaction_id=id2)
834-
835-
# check the resolution
836-
c1 = self.con1.cursor()
837-
c1.execute('select * from t')
838-
row = c1.fetchall()
839-
self.assertListEqual(row, [(3, None)])
840-
c2 = self.con2.cursor()
841-
c2.execute('select * from t')
842-
row = c2.fetchall()
843-
self.assertListEqual(row, [])
810+
#with connect_server('FBTEST_HOST') as svc:
811+
#dt = DistributedTransactionManager([self.con1, self.con2])
812+
#ids1 = svc.database.get_limbo_transaction_ids(database=self.db1)
813+
#self.assertEqual(ids1, [])
814+
#ids2 = svc.database.get_limbo_transaction_ids(database=self.db2)
815+
#self.assertEqual(ids2, [])
816+
#dt.execute_immediate('insert into t (pk) values (3)')
817+
#dt.prepare()
818+
## Force out both connections
819+
#dt._tra.release()
820+
#dt._tra = None
821+
#dt.close()
822+
#self.con1.close()
823+
#self.con2.close()
824+
##
825+
#self.con1 = connect('dts-1')
826+
#self.con2 = connect('dts-2')
827+
#ids1 = svc.database.get_limbo_transaction_ids(database=self.db1)
828+
#id1 = ids1[0]
829+
#ids2 = svc.database.get_limbo_transaction_ids(database=self.db2)
830+
#id2 = ids2[0]
831+
## Data should be blocked by limbo transaction
832+
#c1 = self.con1.cursor()
833+
#c1.execute('select * from t')
834+
#with self.assertRaises(DatabaseError) as cm:
835+
#row = c1.fetchall()
836+
#self.assertTupleEqual(cm.exception.args,
837+
#('Cursor.fetchone:\n- SQLCODE: -911\n- record from transaction %i is stuck in limbo' % id1, -911, 335544459))
838+
#c2 = self.con2.cursor()
839+
#c2.execute('select * from t')
840+
#with self.assertRaises(DatabaseError) as cm:
841+
#row = c2.fetchall()
842+
#self.assertTupleEqual(cm.exception.args,
843+
#('Cursor.fetchone:\n- SQLCODE: -911\n- record from transaction %i is stuck in limbo' % id2, -911, 335544459))
844+
## resolve via service
845+
#svc = connect_server(host=FBTEST_HOST, password=FBTEST_PASSWORD)
846+
#svc.database.commit_limbo_transaction(database=self.db1, transaction_id=id1)
847+
#svc.database.rollback_limbo_transaction(database=self.db2, transaction_id=id2)
848+
849+
## check the resolution
850+
#c1 = self.con1.cursor()
851+
#c1.execute('select * from t')
852+
#row = c1.fetchall()
853+
#self.assertListEqual(row, [(3, None)])
854+
#c2 = self.con2.cursor()
855+
#c2.execute('select * from t')
856+
#row = c2.fetchall()
857+
#self.assertListEqual(row, [])
844858

845859
class TestCursor(DriverTestBase):
846860
def setUp(self):
@@ -1614,8 +1628,8 @@ def fetchline(line):
16141628
self.assertGreater(len(output), 0)
16151629
self.assertEqual(output, log)
16161630
def test_04_get_limbo_transaction_ids(self):
1617-
self.skipTest('Not implemented yet')
1618-
ids = self.svc.get_limbo_transaction_ids(database='employee')
1631+
#self.skipTest('Not implemented yet')
1632+
ids = self.svc.database.get_limbo_transaction_ids(database='employee')
16191633
self.assertIsInstance(ids, type(list()))
16201634
def test_05_trace(self):
16211635
#self.skipTest('Not implemented yet')

0 commit comments

Comments
 (0)