@@ -529,7 +529,7 @@ static void firebird_handle_closer(pdo_dbh_t *dbh) /* {{{ */
529
529
530
530
if (H -> tr ) {
531
531
if (dbh -> auto_commit ) {
532
- php_firebird_commit_transaction (dbh , /* release */ false);
532
+ php_firebird_commit_transaction (dbh , /* retain */ false);
533
533
} else {
534
534
php_firebird_rollback_transaction (dbh );
535
535
}
@@ -756,51 +756,54 @@ static zend_string* firebird_handle_quoter(pdo_dbh_t *dbh, const zend_string *un
756
756
/* }}} */
757
757
758
758
/* 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 ) /* {{{ */
760
760
{
761
761
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
- }
782
762
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 ;
792
792
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 ;
800
803
}
801
804
}
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 )) {
804
807
php_firebird_error (dbh );
805
808
return false;
806
809
}
@@ -817,12 +820,12 @@ static bool firebird_handle_manually_begin(pdo_dbh_t *dbh) /* {{{ */
817
820
* If in autocommit mode and in transaction, we will need to close the transaction once.
818
821
*/
819
822
if (dbh -> auto_commit && H -> tr ) {
820
- if (!php_firebird_commit_transaction (dbh , /* release */ false)) {
823
+ if (!php_firebird_commit_transaction (dbh , /* retain */ false)) {
821
824
return false;
822
825
}
823
826
}
824
827
825
- if (!php_firebird_begin_transaction (dbh )) {
828
+ if (!php_firebird_begin_transaction (dbh , /* auto commit mode */ false )) {
826
829
return false;
827
830
}
828
831
H -> in_manually_txn = 1 ;
@@ -871,7 +874,7 @@ static bool firebird_handle_manually_commit(pdo_dbh_t *dbh) /* {{{ */
871
874
* Reopen instead of retain because isolation level may change
872
875
*/
873
876
if (dbh -> auto_commit ) {
874
- if (!php_firebird_begin_transaction (dbh )) {
877
+ if (!php_firebird_begin_transaction (dbh , /* auto commit mode */ true )) {
875
878
return false;
876
879
}
877
880
}
@@ -907,7 +910,7 @@ static bool firebird_handle_manually_rollback(pdo_dbh_t *dbh) /* {{{ */
907
910
* Reopen instead of retain because isolation level may change
908
911
*/
909
912
if (dbh -> auto_commit ) {
910
- if (!php_firebird_begin_transaction (dbh )) {
913
+ if (!php_firebird_begin_transaction (dbh , /* auto commit mode */ true )) {
911
914
return false;
912
915
}
913
916
}
@@ -961,6 +964,7 @@ static bool pdo_firebird_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val
961
964
{
962
965
pdo_firebird_db_handle * H = (pdo_firebird_db_handle * )dbh -> driver_data ;
963
966
bool bval ;
967
+ zend_long lval ;
964
968
965
969
switch (attr ) {
966
970
case PDO_ATTR_AUTOCOMMIT :
@@ -979,22 +983,22 @@ static bool pdo_firebird_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val
979
983
/* ignore if the new value equals the old one */
980
984
if (dbh -> auto_commit ^ bval ) {
981
985
if (bval ) {
982
- /* change to auto commit mode.
986
+ /*
987
+ * change to auto commit mode.
983
988
* If the transaction is not started, start it.
984
- * However, this is a fallback since such a situation usually does not occur.
985
989
*/
986
990
if (!H -> tr ) {
987
- if (!php_firebird_begin_transaction (dbh )) {
991
+ if (!php_firebird_begin_transaction (dbh , /* auto commit mode */ true )) {
988
992
return false;
989
993
}
990
994
}
991
995
} else {
992
- /* change to not auto commit mode.
996
+ /*
997
+ * change to not auto commit mode.
993
998
* close the transaction if exists.
994
- * However, this is a fallback since such a situation usually does not occur.
995
999
*/
996
1000
if (H -> tr ) {
997
- if (!php_firebird_commit_transaction (dbh , /* release */ false)) {
1001
+ if (!php_firebird_commit_transaction (dbh , /* retain */ false)) {
998
1002
return false;
999
1003
}
1000
1004
}
@@ -1052,6 +1056,69 @@ static bool pdo_firebird_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val
1052
1056
zend_string_release_ex (str , 0 );
1053
1057
}
1054
1058
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;
1055
1122
}
1056
1123
return false;
1057
1124
}
@@ -1136,6 +1203,14 @@ static int pdo_firebird_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val)
1136
1203
case PDO_FB_ATTR_TIMESTAMP_FORMAT :
1137
1204
ZVAL_STRING (val , H -> timestamp_format );
1138
1205
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 ;
1139
1214
}
1140
1215
return 0 ;
1141
1216
}
@@ -1213,6 +1288,20 @@ static int pdo_firebird_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /*
1213
1288
dbh -> password = pestrdup (vars [5 ].optval , dbh -> is_persistent );
1214
1289
}
1215
1290
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
+
1216
1305
do {
1217
1306
static char const dpb_flags [] = {
1218
1307
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) /*
1263
1352
"HY000" , H -> isc_status [1 ], errmsg );
1264
1353
}
1265
1354
1266
- H -> in_manually_txn = 0 ;
1267
1355
if (dbh -> auto_commit && !H -> tr ) {
1268
- ret = php_firebird_begin_transaction (dbh );
1356
+ ret = php_firebird_begin_transaction (dbh , /* auto commit mode */ true );
1269
1357
}
1270
1358
1271
1359
if (!ret ) {
0 commit comments