Skip to content

Feature: Add transaction isolation level and mode settings to pdo_firebird #12815

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 140 additions & 52 deletions ext/pdo_firebird/firebird_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ static void firebird_handle_closer(pdo_dbh_t *dbh) /* {{{ */

if (H->tr) {
if (dbh->auto_commit) {
php_firebird_commit_transaction(dbh, /* release */ false);
php_firebird_commit_transaction(dbh, /* retain */ false);
} else {
php_firebird_rollback_transaction(dbh);
}
Expand Down Expand Up @@ -756,51 +756,54 @@ static zend_string* firebird_handle_quoter(pdo_dbh_t *dbh, const zend_string *un
/* }}} */

/* php_firebird_begin_transaction */
static bool php_firebird_begin_transaction(pdo_dbh_t *dbh) /* {{{ */
static bool php_firebird_begin_transaction(pdo_dbh_t *dbh, bool is_auto_commit_txn) /* {{{ */
{
pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
char tpb[8] = { isc_tpb_version3 }, *ptpb = tpb+1;
#ifdef abies_0
if (dbh->transaction_flags & PDO_TRANS_ISOLATION_LEVEL) {
if (dbh->transaction_flags & PDO_TRANS_READ_UNCOMMITTED) {
/* this is a poor fit, but it's all we have */
*ptpb++ = isc_tpb_read_committed;
*ptpb++ = isc_tpb_rec_version;
dbh->transaction_flags &= ~(PDO_TRANS_ISOLATION_LEVEL^PDO_TRANS_READ_UNCOMMITTED);
} else if (dbh->transaction_flags & PDO_TRANS_READ_COMMITTED) {
*ptpb++ = isc_tpb_read_committed;
*ptpb++ = isc_tpb_no_rec_version;
dbh->transaction_flags &= ~(PDO_TRANS_ISOLATION_LEVEL^PDO_TRANS_READ_COMMITTED);
} else if (dbh->transaction_flags & PDO_TRANS_REPEATABLE_READ) {
*ptpb++ = isc_tpb_concurrency;
dbh->transaction_flags &= ~(PDO_TRANS_ISOLATION_LEVEL^PDO_TRANS_REPEATABLE_READ);
} else {
*ptpb++ = isc_tpb_consistency;
dbh->transaction_flags &= ~(PDO_TRANS_ISOLATION_LEVEL^PDO_TRANS_SERIALIZABLE);
}
}

if (dbh->transaction_flags & PDO_TRANS_ACCESS_MODE) {
if (dbh->transaction_flags & PDO_TRANS_READONLY) {
*ptpb++ = isc_tpb_read;
dbh->transaction_flags &= ~(PDO_TRANS_ACCESS_MODE^PDO_TRANS_READONLY);
} else {
*ptpb++ = isc_tpb_write;
dbh->transaction_flags &= ~(PDO_TRANS_ACCESS_MODE^PDO_TRANS_READWRITE);
}
}
/* isc_xxx are all 1 byte. */
char tpb[4] = { isc_tpb_version3 };
size_t tpb_size;

/* access mode. writable or readonly */
tpb[1] = H->is_writable_txn ? isc_tpb_write : isc_tpb_read;

if (is_auto_commit_txn) {
/*
* In autocommit mode, we need to always read the latest information, so we set `read committed`.
*/
tpb[2] = isc_tpb_read_committed;
/* Ignore indeterminate data from other transactions. This option only required with `read committed`. */
tpb[3] = isc_tpb_rec_version;
tpb_size = 4;
} else {
switch (H->txn_isolation_level) {
/*
* firebird's `read committed` has the option to wait until other transactions
* commit or rollback if there is indeterminate data.
* Introducing too many configuration values ​​at once can cause confusion, so
* we don't support in PDO that feature yet.
*/
case PDO_FB_READ_COMMITTED:
tpb[2] = isc_tpb_read_committed;
/* Ignore indeterminate data from other transactions. This option only required with `read committed`. */
tpb[3] = isc_tpb_rec_version;
tpb_size = 4;
break;

if (dbh->transaction_flags & PDO_TRANS_CONFLICT_RESOLUTION) {
if (dbh->transaction_flags & PDO_TRANS_RETRY) {
*ptpb++ = isc_tpb_wait;
dbh->transaction_flags &= ~(PDO_TRANS_CONFLICT_RESOLUTION^PDO_TRANS_RETRY);
} else {
*ptpb++ = isc_tpb_nowait;
dbh->transaction_flags &= ~(PDO_TRANS_CONFLICT_RESOLUTION^PDO_TRANS_ABORT);
case PDO_FB_SERIALIZABLE:
tpb[2] = isc_tpb_consistency;
tpb_size = 3;
break;

case PDO_FB_REPEATABLE_READ:
default:
tpb[2] = isc_tpb_concurrency;
tpb_size = 3;
break;
}
}
#endif
if (isc_start_transaction(H->isc_status, &H->tr, 1, &H->db, (unsigned short)(ptpb-tpb), tpb)) {

if (isc_start_transaction(H->isc_status, &H->tr, 1, &H->db, tpb_size, tpb)) {
php_firebird_error(dbh);
return false;
}
Expand All @@ -817,12 +820,12 @@ static bool firebird_handle_manually_begin(pdo_dbh_t *dbh) /* {{{ */
* If in autocommit mode and in transaction, we will need to close the transaction once.
*/
if (dbh->auto_commit && H->tr) {
if (!php_firebird_commit_transaction(dbh, /* release */ false)) {
if (!php_firebird_commit_transaction(dbh, /* retain */ false)) {
return false;
}
}

if (!php_firebird_begin_transaction(dbh)) {
if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ false)) {
return false;
}
H->in_manually_txn = 1;
Expand Down Expand Up @@ -871,7 +874,7 @@ static bool firebird_handle_manually_commit(pdo_dbh_t *dbh) /* {{{ */
* Reopen instead of retain because isolation level may change
*/
if (dbh->auto_commit) {
if (!php_firebird_begin_transaction(dbh)) {
if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) {
return false;
}
}
Expand Down Expand Up @@ -907,7 +910,7 @@ static bool firebird_handle_manually_rollback(pdo_dbh_t *dbh) /* {{{ */
* Reopen instead of retain because isolation level may change
*/
if (dbh->auto_commit) {
if (!php_firebird_begin_transaction(dbh)) {
if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) {
return false;
}
}
Expand Down Expand Up @@ -961,6 +964,7 @@ static bool pdo_firebird_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val
{
pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
bool bval;
zend_long lval;

switch (attr) {
case PDO_ATTR_AUTOCOMMIT:
Expand All @@ -979,22 +983,22 @@ static bool pdo_firebird_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val
/* ignore if the new value equals the old one */
if (dbh->auto_commit ^ bval) {
if (bval) {
/* change to auto commit mode.
/*
* change to auto commit mode.
* If the transaction is not started, start it.
* However, this is a fallback since such a situation usually does not occur.
*/
if (!H->tr) {
if (!php_firebird_begin_transaction(dbh)) {
if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) {
return false;
}
}
} else {
/* change to not auto commit mode.
/*
* change to not auto commit mode.
* close the transaction if exists.
* However, this is a fallback since such a situation usually does not occur.
*/
if (H->tr) {
if (!php_firebird_commit_transaction(dbh, /* release */ false)) {
if (!php_firebird_commit_transaction(dbh, /* retain */ false)) {
return false;
}
}
Expand Down Expand Up @@ -1052,6 +1056,69 @@ static bool pdo_firebird_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val
zend_string_release_ex(str, 0);
}
return true;

case PDO_FB_TRANSACTION_ISOLATION_LEVEL:
{
if (!pdo_get_long_param(&lval, val)) {
return false;
}

if (H->in_manually_txn) {
pdo_raise_impl_error(dbh, NULL, "HY000", "Cannot change transaction isolation level while a transaction is already open");
return false;
}

/* ignore if the new value equals the old one */
if (H->txn_isolation_level != lval) {
if (lval == PDO_FB_READ_COMMITTED ||
lval == PDO_FB_REPEATABLE_READ ||
lval == PDO_FB_SERIALIZABLE
) {
/*
* Autocommit mode is always read-committed, so this setting is used the next time
* a manual transaction starts. Therefore, there is no need to immediately reopen the transaction.
*/
H->txn_isolation_level = lval;
} else {
zend_value_error("PDO::FB_TRANSACTION_ISOLATION_LEVEL must be a valid transaction isolation level "
"(PDO::FB_READ_COMMITTED, PDO::FB_REPEATABLE_READ, or PDO::FB_SERIALIZABLE)");
Comment on lines +1083 to +1084
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a possible follow-up suggestion to extract the common error message into a function so that they don't go out of sync.

return false;
}
}
}
return true;

case PDO_FB_WRITABLE_TRANSACTION:
{
if (!pdo_get_bool_param(&bval, val)) {
return false;
}

if (H->in_manually_txn) {
pdo_raise_impl_error(dbh, NULL, "HY000", "Cannot change access mode while a transaction is already open");
return false;
}

/* ignore if the new value equals the old one */
if (H->is_writable_txn != bval) {
H->is_writable_txn = bval;
if (dbh->auto_commit) {
if (H->tr) {
if (!php_firebird_commit_transaction(dbh, /* retain */ false)) {
/* In case of error, revert the setting */
H->is_writable_txn = !bval;
return false;
}
}
if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) {
/* In case of error, revert the setting */
H->is_writable_txn = !bval;
return false;
}
}
}
}
return true;
}
return false;
}
Expand Down Expand Up @@ -1136,6 +1203,14 @@ static int pdo_firebird_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val)
case PDO_FB_ATTR_TIMESTAMP_FORMAT:
ZVAL_STRING(val, H->timestamp_format);
return 1;

case PDO_FB_TRANSACTION_ISOLATION_LEVEL:
ZVAL_LONG(val, H->txn_isolation_level);
return 1;

case PDO_FB_WRITABLE_TRANSACTION:
ZVAL_BOOL(val, H->is_writable_txn);
return 1;
}
return 0;
}
Expand Down Expand Up @@ -1213,6 +1288,20 @@ static int pdo_firebird_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /*
dbh->password = pestrdup(vars[5].optval, dbh->is_persistent);
}

H->in_manually_txn = 0;
H->is_writable_txn = pdo_attr_lval(driver_options, PDO_FB_WRITABLE_TRANSACTION, 1);
zend_long txn_isolation_level = pdo_attr_lval(driver_options, PDO_FB_TRANSACTION_ISOLATION_LEVEL, PDO_FB_REPEATABLE_READ);
if (txn_isolation_level == PDO_FB_READ_COMMITTED ||
txn_isolation_level == PDO_FB_REPEATABLE_READ ||
txn_isolation_level == PDO_FB_SERIALIZABLE
) {
H->txn_isolation_level = txn_isolation_level;
} else {
zend_value_error("PDO::FB_TRANSACTION_ISOLATION_LEVEL must be a valid transaction isolation level "
"(PDO::FB_READ_COMMITTED, PDO::FB_REPEATABLE_READ, or PDO::FB_SERIALIZABLE)");
ret = 0;
}

do {
static char const dpb_flags[] = {
isc_dpb_user_name, isc_dpb_password, isc_dpb_lc_ctype, isc_dpb_sql_role_name };
Expand Down Expand Up @@ -1263,9 +1352,8 @@ static int pdo_firebird_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /*
"HY000", H->isc_status[1], errmsg);
}

H->in_manually_txn = 0;
if (dbh->auto_commit && !H->tr) {
ret = php_firebird_begin_transaction(dbh);
ret = php_firebird_begin_transaction(dbh, /* auto commit mode */ true);
}

if (!ret) {
Expand Down
5 changes: 5 additions & 0 deletions ext/pdo_firebird/pdo_firebird.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ PHP_MINIT_FUNCTION(pdo_firebird) /* {{{ */
REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_DATE_FORMAT", (zend_long) PDO_FB_ATTR_DATE_FORMAT);
REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_TIME_FORMAT", (zend_long) PDO_FB_ATTR_TIME_FORMAT);
REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_TIMESTAMP_FORMAT", (zend_long) PDO_FB_ATTR_TIMESTAMP_FORMAT);
REGISTER_PDO_CLASS_CONST_LONG("FB_TRANSACTION_ISOLATION_LEVEL", (zend_long) PDO_FB_TRANSACTION_ISOLATION_LEVEL);
REGISTER_PDO_CLASS_CONST_LONG("FB_READ_COMMITTED", (zend_long) PDO_FB_READ_COMMITTED);
REGISTER_PDO_CLASS_CONST_LONG("FB_REPEATABLE_READ", (zend_long) PDO_FB_REPEATABLE_READ);
REGISTER_PDO_CLASS_CONST_LONG("FB_SERIALIZABLE", (zend_long) PDO_FB_SERIALIZABLE);
REGISTER_PDO_CLASS_CONST_LONG("FB_WRITABLE_TRANSACTION", (zend_long) PDO_FB_WRITABLE_TRANSACTION);

if (FAILURE == php_pdo_register_driver(&pdo_firebird_driver)) {
return FAILURE;
Expand Down
14 changes: 14 additions & 0 deletions ext/pdo_firebird/php_pdo_firebird_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ typedef struct {
/* the transaction handle */
isc_tr_handle tr;
bool in_manually_txn;
bool is_writable_txn;
zend_ulong txn_isolation_level;

/* date and time format strings, can be set by the set_attribute method */
char *date_format;
Expand Down Expand Up @@ -140,6 +142,18 @@ enum {
PDO_FB_ATTR_DATE_FORMAT = PDO_ATTR_DRIVER_SPECIFIC,
PDO_FB_ATTR_TIME_FORMAT,
PDO_FB_ATTR_TIMESTAMP_FORMAT,

/*
* transaction isolation level
* firebird does not have a level equivalent to read uncommited.
*/
PDO_FB_TRANSACTION_ISOLATION_LEVEL,
PDO_FB_READ_COMMITTED,
PDO_FB_REPEATABLE_READ,
PDO_FB_SERIALIZABLE,

/* transaction access mode */
PDO_FB_WRITABLE_TRANSACTION,
};

#endif /* PHP_PDO_FIREBIRD_INT_H */
Loading