Skip to content

Commit 834cb64

Browse files
Feature: ext/pdo_firebird: Add transaction isolation level and mode settings (#12815)
* Added transaction isolation level and access mode * Raise a ValueError if an invalid value is passed to PDO::FB_TRANSACTION_ISOLATION_LEVEL.
1 parent d6d838a commit 834cb64

8 files changed

+620
-52
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ PDO_DBLIB:
4545

4646
PDO_FIREBIRD:
4747
. Fixed setAttribute and getAttribute (SakiTakamachi)
48+
. Feature: Add transaction isolation level and mode settings to pdo_firebird
49+
(SakiTakamachi)
4850

4951
PDO_MYSQL:
5052
. Fixed setAttribute and getAttribute (SakiTakamachi)

UPGRADING

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,10 @@ PHP 8.4 UPGRADE NOTES
209209
- PDO_FIREBIRD:
210210
. getAttribute, enabled to get values ​​of FB_ATTR_DATE_FORMAT, FB_ATTR_TIME_FORMAT,
211211
FB_ATTR_TIMESTAMP_FORMAT.
212+
. Added new attributes to specify transaction isolation level and access mode.
213+
Along with these, five constants (PDO::FB_TRANSACTION_ISOLATION_LEVEL,
214+
PDO::FB_READ_COMMITTED, PDO::FB_REPEATABLE_READ, PDO::FB_SERIALIZABLE,
215+
PDO::FB_WRITABLE_TRANSACTION) have been added.
212216

213217
- PDO_MYSQL:
214218
. getAttribute, enabled to get the value of ATTR_FETCH_TABLE_NAMES.

ext/pdo_firebird/firebird_driver.c

Lines changed: 140 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ static void firebird_handle_closer(pdo_dbh_t *dbh) /* {{{ */
529529

530530
if (H->tr) {
531531
if (dbh->auto_commit) {
532-
php_firebird_commit_transaction(dbh, /* release */ false);
532+
php_firebird_commit_transaction(dbh, /* retain */ false);
533533
} else {
534534
php_firebird_rollback_transaction(dbh);
535535
}
@@ -756,51 +756,54 @@ static zend_string* firebird_handle_quoter(pdo_dbh_t *dbh, const zend_string *un
756756
/* }}} */
757757

758758
/* php_firebird_begin_transaction */
759-
static bool php_firebird_begin_transaction(pdo_dbh_t *dbh) /* {{{ */
759+
static bool php_firebird_begin_transaction(pdo_dbh_t *dbh, bool is_auto_commit_txn) /* {{{ */
760760
{
761761
pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
762-
char tpb[8] = { isc_tpb_version3 }, *ptpb = tpb+1;
763-
#ifdef abies_0
764-
if (dbh->transaction_flags & PDO_TRANS_ISOLATION_LEVEL) {
765-
if (dbh->transaction_flags & PDO_TRANS_READ_UNCOMMITTED) {
766-
/* this is a poor fit, but it's all we have */
767-
*ptpb++ = isc_tpb_read_committed;
768-
*ptpb++ = isc_tpb_rec_version;
769-
dbh->transaction_flags &= ~(PDO_TRANS_ISOLATION_LEVEL^PDO_TRANS_READ_UNCOMMITTED);
770-
} else if (dbh->transaction_flags & PDO_TRANS_READ_COMMITTED) {
771-
*ptpb++ = isc_tpb_read_committed;
772-
*ptpb++ = isc_tpb_no_rec_version;
773-
dbh->transaction_flags &= ~(PDO_TRANS_ISOLATION_LEVEL^PDO_TRANS_READ_COMMITTED);
774-
} else if (dbh->transaction_flags & PDO_TRANS_REPEATABLE_READ) {
775-
*ptpb++ = isc_tpb_concurrency;
776-
dbh->transaction_flags &= ~(PDO_TRANS_ISOLATION_LEVEL^PDO_TRANS_REPEATABLE_READ);
777-
} else {
778-
*ptpb++ = isc_tpb_consistency;
779-
dbh->transaction_flags &= ~(PDO_TRANS_ISOLATION_LEVEL^PDO_TRANS_SERIALIZABLE);
780-
}
781-
}
782762

783-
if (dbh->transaction_flags & PDO_TRANS_ACCESS_MODE) {
784-
if (dbh->transaction_flags & PDO_TRANS_READONLY) {
785-
*ptpb++ = isc_tpb_read;
786-
dbh->transaction_flags &= ~(PDO_TRANS_ACCESS_MODE^PDO_TRANS_READONLY);
787-
} else {
788-
*ptpb++ = isc_tpb_write;
789-
dbh->transaction_flags &= ~(PDO_TRANS_ACCESS_MODE^PDO_TRANS_READWRITE);
790-
}
791-
}
763+
/* isc_xxx are all 1 byte. */
764+
char tpb[4] = { isc_tpb_version3 };
765+
size_t tpb_size;
766+
767+
/* access mode. writable or readonly */
768+
tpb[1] = H->is_writable_txn ? isc_tpb_write : isc_tpb_read;
769+
770+
if (is_auto_commit_txn) {
771+
/*
772+
* In autocommit mode, we need to always read the latest information, so we set `read committed`.
773+
*/
774+
tpb[2] = isc_tpb_read_committed;
775+
/* Ignore indeterminate data from other transactions. This option only required with `read committed`. */
776+
tpb[3] = isc_tpb_rec_version;
777+
tpb_size = 4;
778+
} else {
779+
switch (H->txn_isolation_level) {
780+
/*
781+
* firebird's `read committed` has the option to wait until other transactions
782+
* commit or rollback if there is indeterminate data.
783+
* Introducing too many configuration values ​​at once can cause confusion, so
784+
* we don't support in PDO that feature yet.
785+
*/
786+
case PDO_FB_READ_COMMITTED:
787+
tpb[2] = isc_tpb_read_committed;
788+
/* Ignore indeterminate data from other transactions. This option only required with `read committed`. */
789+
tpb[3] = isc_tpb_rec_version;
790+
tpb_size = 4;
791+
break;
792792

793-
if (dbh->transaction_flags & PDO_TRANS_CONFLICT_RESOLUTION) {
794-
if (dbh->transaction_flags & PDO_TRANS_RETRY) {
795-
*ptpb++ = isc_tpb_wait;
796-
dbh->transaction_flags &= ~(PDO_TRANS_CONFLICT_RESOLUTION^PDO_TRANS_RETRY);
797-
} else {
798-
*ptpb++ = isc_tpb_nowait;
799-
dbh->transaction_flags &= ~(PDO_TRANS_CONFLICT_RESOLUTION^PDO_TRANS_ABORT);
793+
case PDO_FB_SERIALIZABLE:
794+
tpb[2] = isc_tpb_consistency;
795+
tpb_size = 3;
796+
break;
797+
798+
case PDO_FB_REPEATABLE_READ:
799+
default:
800+
tpb[2] = isc_tpb_concurrency;
801+
tpb_size = 3;
802+
break;
800803
}
801804
}
802-
#endif
803-
if (isc_start_transaction(H->isc_status, &H->tr, 1, &H->db, (unsigned short)(ptpb-tpb), tpb)) {
805+
806+
if (isc_start_transaction(H->isc_status, &H->tr, 1, &H->db, tpb_size, tpb)) {
804807
php_firebird_error(dbh);
805808
return false;
806809
}
@@ -817,12 +820,12 @@ static bool firebird_handle_manually_begin(pdo_dbh_t *dbh) /* {{{ */
817820
* If in autocommit mode and in transaction, we will need to close the transaction once.
818821
*/
819822
if (dbh->auto_commit && H->tr) {
820-
if (!php_firebird_commit_transaction(dbh, /* release */ false)) {
823+
if (!php_firebird_commit_transaction(dbh, /* retain */ false)) {
821824
return false;
822825
}
823826
}
824827

825-
if (!php_firebird_begin_transaction(dbh)) {
828+
if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ false)) {
826829
return false;
827830
}
828831
H->in_manually_txn = 1;
@@ -871,7 +874,7 @@ static bool firebird_handle_manually_commit(pdo_dbh_t *dbh) /* {{{ */
871874
* Reopen instead of retain because isolation level may change
872875
*/
873876
if (dbh->auto_commit) {
874-
if (!php_firebird_begin_transaction(dbh)) {
877+
if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) {
875878
return false;
876879
}
877880
}
@@ -907,7 +910,7 @@ static bool firebird_handle_manually_rollback(pdo_dbh_t *dbh) /* {{{ */
907910
* Reopen instead of retain because isolation level may change
908911
*/
909912
if (dbh->auto_commit) {
910-
if (!php_firebird_begin_transaction(dbh)) {
913+
if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) {
911914
return false;
912915
}
913916
}
@@ -961,6 +964,7 @@ static bool pdo_firebird_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val
961964
{
962965
pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
963966
bool bval;
967+
zend_long lval;
964968

965969
switch (attr) {
966970
case PDO_ATTR_AUTOCOMMIT:
@@ -979,22 +983,22 @@ static bool pdo_firebird_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val
979983
/* ignore if the new value equals the old one */
980984
if (dbh->auto_commit ^ bval) {
981985
if (bval) {
982-
/* change to auto commit mode.
986+
/*
987+
* change to auto commit mode.
983988
* If the transaction is not started, start it.
984-
* However, this is a fallback since such a situation usually does not occur.
985989
*/
986990
if (!H->tr) {
987-
if (!php_firebird_begin_transaction(dbh)) {
991+
if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) {
988992
return false;
989993
}
990994
}
991995
} else {
992-
/* change to not auto commit mode.
996+
/*
997+
* change to not auto commit mode.
993998
* close the transaction if exists.
994-
* However, this is a fallback since such a situation usually does not occur.
995999
*/
9961000
if (H->tr) {
997-
if (!php_firebird_commit_transaction(dbh, /* release */ false)) {
1001+
if (!php_firebird_commit_transaction(dbh, /* retain */ false)) {
9981002
return false;
9991003
}
10001004
}
@@ -1052,6 +1056,69 @@ static bool pdo_firebird_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val
10521056
zend_string_release_ex(str, 0);
10531057
}
10541058
return true;
1059+
1060+
case PDO_FB_TRANSACTION_ISOLATION_LEVEL:
1061+
{
1062+
if (!pdo_get_long_param(&lval, val)) {
1063+
return false;
1064+
}
1065+
1066+
if (H->in_manually_txn) {
1067+
pdo_raise_impl_error(dbh, NULL, "HY000", "Cannot change transaction isolation level while a transaction is already open");
1068+
return false;
1069+
}
1070+
1071+
/* ignore if the new value equals the old one */
1072+
if (H->txn_isolation_level != lval) {
1073+
if (lval == PDO_FB_READ_COMMITTED ||
1074+
lval == PDO_FB_REPEATABLE_READ ||
1075+
lval == PDO_FB_SERIALIZABLE
1076+
) {
1077+
/*
1078+
* Autocommit mode is always read-committed, so this setting is used the next time
1079+
* a manual transaction starts. Therefore, there is no need to immediately reopen the transaction.
1080+
*/
1081+
H->txn_isolation_level = lval;
1082+
} else {
1083+
zend_value_error("PDO::FB_TRANSACTION_ISOLATION_LEVEL must be a valid transaction isolation level "
1084+
"(PDO::FB_READ_COMMITTED, PDO::FB_REPEATABLE_READ, or PDO::FB_SERIALIZABLE)");
1085+
return false;
1086+
}
1087+
}
1088+
}
1089+
return true;
1090+
1091+
case PDO_FB_WRITABLE_TRANSACTION:
1092+
{
1093+
if (!pdo_get_bool_param(&bval, val)) {
1094+
return false;
1095+
}
1096+
1097+
if (H->in_manually_txn) {
1098+
pdo_raise_impl_error(dbh, NULL, "HY000", "Cannot change access mode while a transaction is already open");
1099+
return false;
1100+
}
1101+
1102+
/* ignore if the new value equals the old one */
1103+
if (H->is_writable_txn != bval) {
1104+
H->is_writable_txn = bval;
1105+
if (dbh->auto_commit) {
1106+
if (H->tr) {
1107+
if (!php_firebird_commit_transaction(dbh, /* retain */ false)) {
1108+
/* In case of error, revert the setting */
1109+
H->is_writable_txn = !bval;
1110+
return false;
1111+
}
1112+
}
1113+
if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) {
1114+
/* In case of error, revert the setting */
1115+
H->is_writable_txn = !bval;
1116+
return false;
1117+
}
1118+
}
1119+
}
1120+
}
1121+
return true;
10551122
}
10561123
return false;
10571124
}
@@ -1136,6 +1203,14 @@ static int pdo_firebird_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val)
11361203
case PDO_FB_ATTR_TIMESTAMP_FORMAT:
11371204
ZVAL_STRING(val, H->timestamp_format);
11381205
return 1;
1206+
1207+
case PDO_FB_TRANSACTION_ISOLATION_LEVEL:
1208+
ZVAL_LONG(val, H->txn_isolation_level);
1209+
return 1;
1210+
1211+
case PDO_FB_WRITABLE_TRANSACTION:
1212+
ZVAL_BOOL(val, H->is_writable_txn);
1213+
return 1;
11391214
}
11401215
return 0;
11411216
}
@@ -1213,6 +1288,20 @@ static int pdo_firebird_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /*
12131288
dbh->password = pestrdup(vars[5].optval, dbh->is_persistent);
12141289
}
12151290

1291+
H->in_manually_txn = 0;
1292+
H->is_writable_txn = pdo_attr_lval(driver_options, PDO_FB_WRITABLE_TRANSACTION, 1);
1293+
zend_long txn_isolation_level = pdo_attr_lval(driver_options, PDO_FB_TRANSACTION_ISOLATION_LEVEL, PDO_FB_REPEATABLE_READ);
1294+
if (txn_isolation_level == PDO_FB_READ_COMMITTED ||
1295+
txn_isolation_level == PDO_FB_REPEATABLE_READ ||
1296+
txn_isolation_level == PDO_FB_SERIALIZABLE
1297+
) {
1298+
H->txn_isolation_level = txn_isolation_level;
1299+
} else {
1300+
zend_value_error("PDO::FB_TRANSACTION_ISOLATION_LEVEL must be a valid transaction isolation level "
1301+
"(PDO::FB_READ_COMMITTED, PDO::FB_REPEATABLE_READ, or PDO::FB_SERIALIZABLE)");
1302+
ret = 0;
1303+
}
1304+
12161305
do {
12171306
static char const dpb_flags[] = {
12181307
isc_dpb_user_name, isc_dpb_password, isc_dpb_lc_ctype, isc_dpb_sql_role_name };
@@ -1263,9 +1352,8 @@ static int pdo_firebird_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /*
12631352
"HY000", H->isc_status[1], errmsg);
12641353
}
12651354

1266-
H->in_manually_txn = 0;
12671355
if (dbh->auto_commit && !H->tr) {
1268-
ret = php_firebird_begin_transaction(dbh);
1356+
ret = php_firebird_begin_transaction(dbh, /* auto commit mode */ true);
12691357
}
12701358

12711359
if (!ret) {

ext/pdo_firebird/pdo_firebird.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ PHP_MINIT_FUNCTION(pdo_firebird) /* {{{ */
5757
REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_DATE_FORMAT", (zend_long) PDO_FB_ATTR_DATE_FORMAT);
5858
REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_TIME_FORMAT", (zend_long) PDO_FB_ATTR_TIME_FORMAT);
5959
REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_TIMESTAMP_FORMAT", (zend_long) PDO_FB_ATTR_TIMESTAMP_FORMAT);
60+
REGISTER_PDO_CLASS_CONST_LONG("FB_TRANSACTION_ISOLATION_LEVEL", (zend_long) PDO_FB_TRANSACTION_ISOLATION_LEVEL);
61+
REGISTER_PDO_CLASS_CONST_LONG("FB_READ_COMMITTED", (zend_long) PDO_FB_READ_COMMITTED);
62+
REGISTER_PDO_CLASS_CONST_LONG("FB_REPEATABLE_READ", (zend_long) PDO_FB_REPEATABLE_READ);
63+
REGISTER_PDO_CLASS_CONST_LONG("FB_SERIALIZABLE", (zend_long) PDO_FB_SERIALIZABLE);
64+
REGISTER_PDO_CLASS_CONST_LONG("FB_WRITABLE_TRANSACTION", (zend_long) PDO_FB_WRITABLE_TRANSACTION);
6065

6166
if (FAILURE == php_pdo_register_driver(&pdo_firebird_driver)) {
6267
return FAILURE;

ext/pdo_firebird/php_pdo_firebird_int.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ typedef struct {
7575
/* the transaction handle */
7676
isc_tr_handle tr;
7777
bool in_manually_txn;
78+
bool is_writable_txn;
79+
zend_ulong txn_isolation_level;
7880

7981
/* date and time format strings, can be set by the set_attribute method */
8082
char *date_format;
@@ -140,6 +142,18 @@ enum {
140142
PDO_FB_ATTR_DATE_FORMAT = PDO_ATTR_DRIVER_SPECIFIC,
141143
PDO_FB_ATTR_TIME_FORMAT,
142144
PDO_FB_ATTR_TIMESTAMP_FORMAT,
145+
146+
/*
147+
* transaction isolation level
148+
* firebird does not have a level equivalent to read uncommited.
149+
*/
150+
PDO_FB_TRANSACTION_ISOLATION_LEVEL,
151+
PDO_FB_READ_COMMITTED,
152+
PDO_FB_REPEATABLE_READ,
153+
PDO_FB_SERIALIZABLE,
154+
155+
/* transaction access mode */
156+
PDO_FB_WRITABLE_TRANSACTION,
143157
};
144158

145159
#endif /* PHP_PDO_FIREBIRD_INT_H */

0 commit comments

Comments
 (0)