mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
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.
This commit is contained in:
2
NEWS
2
NEWS
@@ -45,6 +45,8 @@ PDO_DBLIB:
|
||||
|
||||
PDO_FIREBIRD:
|
||||
. Fixed setAttribute and getAttribute (SakiTakamachi)
|
||||
. Feature: Add transaction isolation level and mode settings to pdo_firebird
|
||||
(SakiTakamachi)
|
||||
|
||||
PDO_MYSQL:
|
||||
. Fixed setAttribute and getAttribute (SakiTakamachi)
|
||||
|
||||
@@ -209,6 +209,10 @@ PHP 8.4 UPGRADE NOTES
|
||||
- PDO_FIREBIRD:
|
||||
. getAttribute, enabled to get values of FB_ATTR_DATE_FORMAT, FB_ATTR_TIME_FORMAT,
|
||||
FB_ATTR_TIMESTAMP_FORMAT.
|
||||
. Added new attributes to specify transaction isolation level and access mode.
|
||||
Along with these, five constants (PDO::FB_TRANSACTION_ISOLATION_LEVEL,
|
||||
PDO::FB_READ_COMMITTED, PDO::FB_REPEATABLE_READ, PDO::FB_SERIALIZABLE,
|
||||
PDO::FB_WRITABLE_TRANSACTION) have been added.
|
||||
|
||||
- PDO_MYSQL:
|
||||
. getAttribute, enabled to get the value of ATTR_FETCH_TABLE_NAMES.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
/* 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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
#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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)");
|
||||
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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 };
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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 */
|
||||
|
||||
184
ext/pdo_firebird/tests/transaction_access_mode.phpt
Normal file
184
ext/pdo_firebird/tests/transaction_access_mode.phpt
Normal file
@@ -0,0 +1,184 @@
|
||||
--TEST--
|
||||
PDO_Firebird: transaction access mode
|
||||
--EXTENSIONS--
|
||||
pdo_firebird
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--XLEAK--
|
||||
A bug in firebird causes a memory leak when calling `isc_attach_database()`.
|
||||
See https://github.com/FirebirdSQL/firebird/issues/7849
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
require("testdb.inc");
|
||||
unset($dbh);
|
||||
|
||||
$table = 'transaction_access_mode';
|
||||
|
||||
$values = [
|
||||
['val' => true, 'label' => 'writable'],
|
||||
['val' => false, 'label' => 'readonly'],
|
||||
];
|
||||
|
||||
echo "========== Set attr in construct ==========\n";
|
||||
|
||||
foreach ($values as $value) {
|
||||
$dbh = new PDO(
|
||||
PDO_FIREBIRD_TEST_DSN,
|
||||
PDO_FIREBIRD_TEST_USER,
|
||||
PDO_FIREBIRD_TEST_PASS,
|
||||
[
|
||||
PDO::FB_WRITABLE_TRANSACTION => $value['val'],
|
||||
],
|
||||
);
|
||||
|
||||
if ($dbh->getAttribute(PDO::FB_WRITABLE_TRANSACTION) === $value['val']) {
|
||||
echo "OK: {$value['label']}\n";
|
||||
} else {
|
||||
echo "NG: {$value['label']}\n";
|
||||
}
|
||||
|
||||
unset($dbh);
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
echo "========== Set attr in setAttribute and behavior check ==========\n";
|
||||
|
||||
$dbh = new PDO(
|
||||
PDO_FIREBIRD_TEST_DSN,
|
||||
PDO_FIREBIRD_TEST_USER,
|
||||
PDO_FIREBIRD_TEST_PASS,
|
||||
);
|
||||
|
||||
$dbh->query("CREATE TABLE {$table} (val INT)");
|
||||
|
||||
echo "writable\n";
|
||||
var_dump($dbh->setAttribute(PDO::FB_WRITABLE_TRANSACTION, true));
|
||||
if ($dbh->getAttribute(PDO::FB_WRITABLE_TRANSACTION) === true) {
|
||||
echo "OK: writable\n";
|
||||
} else {
|
||||
echo "NG: writable\n";
|
||||
}
|
||||
$dbh->query("INSERT INTO {$table} VALUES (12)");
|
||||
$r = $dbh->query("SELECT * FROM {$table}");
|
||||
var_dump($r->fetchAll());
|
||||
|
||||
echo "\n";
|
||||
|
||||
echo "readonly\n";
|
||||
var_dump($dbh->setAttribute(PDO::FB_WRITABLE_TRANSACTION, false));
|
||||
if ($dbh->getAttribute(PDO::FB_WRITABLE_TRANSACTION) === false) {
|
||||
echo "OK: readonly\n";
|
||||
} else {
|
||||
echo "NG: readonly\n";
|
||||
}
|
||||
try {
|
||||
$dbh->query("INSERT INTO {$table} VALUES (19)");
|
||||
} catch (PDOException $e) {
|
||||
echo $e->getMessage()."\n";
|
||||
}
|
||||
$r = $dbh->query("SELECT * FROM {$table}");
|
||||
var_dump($r->fetchAll());
|
||||
|
||||
echo "\n";
|
||||
echo "========== Set attr in setAttribute while transaction ==========\n";
|
||||
|
||||
$dbh->setAttribute(PDO::FB_WRITABLE_TRANSACTION, true);
|
||||
$dbh->beginTransaction();
|
||||
|
||||
echo "writable to writable\n";
|
||||
try {
|
||||
$dbh->setAttribute(PDO::FB_WRITABLE_TRANSACTION, true);
|
||||
} catch (PDOException $e) {
|
||||
echo $e->getMessage()."\n";
|
||||
}
|
||||
var_dump($dbh->getAttribute(PDO::FB_WRITABLE_TRANSACTION));
|
||||
echo "\n";
|
||||
|
||||
echo "writable to readonly\n";
|
||||
try {
|
||||
$dbh->setAttribute(PDO::FB_WRITABLE_TRANSACTION, false);
|
||||
} catch (PDOException $e) {
|
||||
echo $e->getMessage()."\n";
|
||||
}
|
||||
var_dump($dbh->getAttribute(PDO::FB_WRITABLE_TRANSACTION));
|
||||
echo "\n";
|
||||
|
||||
$dbh->commit();
|
||||
$dbh->setAttribute(PDO::FB_WRITABLE_TRANSACTION, false);
|
||||
$dbh->beginTransaction();
|
||||
|
||||
echo "readonly to writable\n";
|
||||
try {
|
||||
$dbh->setAttribute(PDO::FB_WRITABLE_TRANSACTION, true);
|
||||
} catch (PDOException $e) {
|
||||
echo $e->getMessage()."\n";
|
||||
}
|
||||
var_dump($dbh->getAttribute(PDO::FB_WRITABLE_TRANSACTION));
|
||||
echo "\n";
|
||||
|
||||
echo "readonly to readonly\n";
|
||||
try {
|
||||
$dbh->setAttribute(PDO::FB_WRITABLE_TRANSACTION, false);
|
||||
} catch (PDOException $e) {
|
||||
echo $e->getMessage()."\n";
|
||||
}
|
||||
var_dump($dbh->getAttribute(PDO::FB_WRITABLE_TRANSACTION));
|
||||
|
||||
unset($dbh);
|
||||
?>
|
||||
--CLEAN--
|
||||
<?php
|
||||
require 'testdb.inc';
|
||||
@$dbh->exec('DROP TABLE transaction_access_mode');
|
||||
unset($dbh);
|
||||
?>
|
||||
--EXPECT--
|
||||
========== Set attr in construct ==========
|
||||
OK: writable
|
||||
OK: readonly
|
||||
|
||||
========== Set attr in setAttribute and behavior check ==========
|
||||
writable
|
||||
bool(true)
|
||||
OK: writable
|
||||
array(1) {
|
||||
[0]=>
|
||||
array(2) {
|
||||
["VAL"]=>
|
||||
int(12)
|
||||
[0]=>
|
||||
int(12)
|
||||
}
|
||||
}
|
||||
|
||||
readonly
|
||||
bool(true)
|
||||
OK: readonly
|
||||
SQLSTATE[42000]: Syntax error or access violation: -817 attempted update during read-only transaction
|
||||
array(1) {
|
||||
[0]=>
|
||||
array(2) {
|
||||
["VAL"]=>
|
||||
int(12)
|
||||
[0]=>
|
||||
int(12)
|
||||
}
|
||||
}
|
||||
|
||||
========== Set attr in setAttribute while transaction ==========
|
||||
writable to writable
|
||||
SQLSTATE[HY000]: General error: Cannot change access mode while a transaction is already open
|
||||
bool(true)
|
||||
|
||||
writable to readonly
|
||||
SQLSTATE[HY000]: General error: Cannot change access mode while a transaction is already open
|
||||
bool(true)
|
||||
|
||||
readonly to writable
|
||||
SQLSTATE[HY000]: General error: Cannot change access mode while a transaction is already open
|
||||
bool(false)
|
||||
|
||||
readonly to readonly
|
||||
SQLSTATE[HY000]: General error: Cannot change access mode while a transaction is already open
|
||||
bool(false)
|
||||
106
ext/pdo_firebird/tests/transaction_isolation_level_attr.phpt
Normal file
106
ext/pdo_firebird/tests/transaction_isolation_level_attr.phpt
Normal file
@@ -0,0 +1,106 @@
|
||||
--TEST--
|
||||
PDO_Firebird: transaction isolation level (Testing for setting attribute values)
|
||||
--EXTENSIONS--
|
||||
pdo_firebird
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--XLEAK--
|
||||
A bug in firebird causes a memory leak when calling `isc_attach_database()`.
|
||||
See https://github.com/FirebirdSQL/firebird/issues/7849
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
require("testdb.inc");
|
||||
unset($dbh);
|
||||
|
||||
$levelStrs = [
|
||||
'PDO::FB_READ_COMMITTED',
|
||||
'PDO::FB_REPEATABLE_READ',
|
||||
'PDO::FB_SERIALIZABLE',
|
||||
];
|
||||
|
||||
echo "========== Set attr in construct ==========\n";
|
||||
|
||||
foreach ($levelStrs as $levelStr) {
|
||||
$level = constant($levelStr);
|
||||
$dbh = new PDO(
|
||||
PDO_FIREBIRD_TEST_DSN,
|
||||
PDO_FIREBIRD_TEST_USER,
|
||||
PDO_FIREBIRD_TEST_PASS,
|
||||
[
|
||||
PDO::FB_TRANSACTION_ISOLATION_LEVEL => $level,
|
||||
],
|
||||
);
|
||||
|
||||
if ($dbh->getAttribute(PDO::FB_TRANSACTION_ISOLATION_LEVEL) === $level) {
|
||||
echo "OK: {$levelStr}\n";
|
||||
} else {
|
||||
echo "NG: {$levelStr}\n";
|
||||
}
|
||||
|
||||
unset($dbh);
|
||||
}
|
||||
|
||||
echo "Invalid value\n";
|
||||
try {
|
||||
$dbh = new PDO(
|
||||
PDO_FIREBIRD_TEST_DSN,
|
||||
PDO_FIREBIRD_TEST_USER,
|
||||
PDO_FIREBIRD_TEST_PASS,
|
||||
[
|
||||
PDO::FB_TRANSACTION_ISOLATION_LEVEL => PDO::ATTR_AUTOCOMMIT, // Invalid value
|
||||
],
|
||||
);
|
||||
} catch (Throwable $e) {
|
||||
echo $e->getMessage()."\n";
|
||||
}
|
||||
|
||||
unset($dbh);
|
||||
|
||||
echo "\n";
|
||||
echo "========== Set attr in setAttribute ==========\n";
|
||||
|
||||
$dbh = new PDO(
|
||||
PDO_FIREBIRD_TEST_DSN,
|
||||
PDO_FIREBIRD_TEST_USER,
|
||||
PDO_FIREBIRD_TEST_PASS,
|
||||
);
|
||||
|
||||
foreach ($levelStrs as $levelStr) {
|
||||
$level = constant($levelStr);
|
||||
|
||||
var_dump($dbh->setAttribute(PDO::FB_TRANSACTION_ISOLATION_LEVEL, $level));
|
||||
|
||||
if ($dbh->getAttribute(PDO::FB_TRANSACTION_ISOLATION_LEVEL) === $level) {
|
||||
echo "OK: {$levelStr}\n";
|
||||
} else {
|
||||
echo "NG: {$levelStr}\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "Invalid value\n";
|
||||
try {
|
||||
$dbh->setAttribute(PDO::FB_TRANSACTION_ISOLATION_LEVEL, PDO::ATTR_AUTOCOMMIT); // Invalid value
|
||||
} catch (Throwable $e) {
|
||||
echo $e->getMessage()."\n";
|
||||
}
|
||||
|
||||
unset($dbh);
|
||||
?>
|
||||
--EXPECT--
|
||||
========== Set attr in construct ==========
|
||||
OK: PDO::FB_READ_COMMITTED
|
||||
OK: PDO::FB_REPEATABLE_READ
|
||||
OK: PDO::FB_SERIALIZABLE
|
||||
Invalid value
|
||||
PDO::FB_TRANSACTION_ISOLATION_LEVEL must be a valid transaction isolation level (PDO::FB_READ_COMMITTED, PDO::FB_REPEATABLE_READ, or PDO::FB_SERIALIZABLE)
|
||||
|
||||
========== Set attr in setAttribute ==========
|
||||
bool(true)
|
||||
OK: PDO::FB_READ_COMMITTED
|
||||
bool(true)
|
||||
OK: PDO::FB_REPEATABLE_READ
|
||||
bool(true)
|
||||
OK: PDO::FB_SERIALIZABLE
|
||||
Invalid value
|
||||
PDO::FB_TRANSACTION_ISOLATION_LEVEL must be a valid transaction isolation level (PDO::FB_READ_COMMITTED, PDO::FB_REPEATABLE_READ, or PDO::FB_SERIALIZABLE)
|
||||
165
ext/pdo_firebird/tests/transaction_isolation_level_behavior.phpt
Normal file
165
ext/pdo_firebird/tests/transaction_isolation_level_behavior.phpt
Normal file
@@ -0,0 +1,165 @@
|
||||
--TEST--
|
||||
PDO_Firebird: transaction isolation level (Testing for behavior)
|
||||
--EXTENSIONS--
|
||||
pdo_firebird
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--XLEAK--
|
||||
A bug in firebird causes a memory leak when calling `isc_attach_database()`.
|
||||
See https://github.com/FirebirdSQL/firebird/issues/7849
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
require("testdb.inc");
|
||||
unset($dbh);
|
||||
|
||||
$table = 'txn_isolation_level_behavior';
|
||||
|
||||
$dbh_other = new PDO(
|
||||
PDO_FIREBIRD_TEST_DSN,
|
||||
PDO_FIREBIRD_TEST_USER,
|
||||
PDO_FIREBIRD_TEST_PASS,
|
||||
);
|
||||
$dbh_other->query("CREATE TABLE {$table} (val INT)");
|
||||
|
||||
|
||||
echo "========== default(REPEATABLE READ) ==========\n";
|
||||
$dbh = new PDO(
|
||||
PDO_FIREBIRD_TEST_DSN,
|
||||
PDO_FIREBIRD_TEST_USER,
|
||||
PDO_FIREBIRD_TEST_PASS,
|
||||
);
|
||||
echo "begin transaction\n";
|
||||
$dbh->beginTransaction();
|
||||
|
||||
echo "insert by other transaction\n";
|
||||
$dbh_other->exec("INSERT INTO {$table} VALUES (20)");
|
||||
echo "Read\n";
|
||||
$r = $dbh->query("SELECT * FROM {$table}");
|
||||
var_dump($r->fetchAll());
|
||||
|
||||
echo "Close transaction and reset table\n";
|
||||
$dbh->commit();
|
||||
$dbh_other->exec("DELETE FROM {$table}");
|
||||
unset($dbh);
|
||||
echo "\n";
|
||||
|
||||
|
||||
echo "========== READ COMMITTED ==========\n";
|
||||
$dbh = new PDO(
|
||||
PDO_FIREBIRD_TEST_DSN,
|
||||
PDO_FIREBIRD_TEST_USER,
|
||||
PDO_FIREBIRD_TEST_PASS,
|
||||
[PDO::FB_TRANSACTION_ISOLATION_LEVEL => PDO::FB_READ_COMMITTED]
|
||||
);
|
||||
echo "begin transaction\n";
|
||||
$dbh->beginTransaction();
|
||||
|
||||
echo "insert by other transaction\n";
|
||||
$dbh_other->exec("INSERT INTO {$table} VALUES (20)");
|
||||
echo "Read\n";
|
||||
$r = $dbh->query("SELECT * FROM {$table}");
|
||||
var_dump($r->fetchAll());
|
||||
|
||||
echo "Close transaction and reset table\n";
|
||||
$dbh->commit();
|
||||
$dbh_other->exec("DELETE FROM {$table}");
|
||||
unset($dbh);
|
||||
echo "\n";
|
||||
|
||||
|
||||
echo "========== REPEATABLE READ ==========\n";
|
||||
$dbh = new PDO(
|
||||
PDO_FIREBIRD_TEST_DSN,
|
||||
PDO_FIREBIRD_TEST_USER,
|
||||
PDO_FIREBIRD_TEST_PASS,
|
||||
[PDO::FB_TRANSACTION_ISOLATION_LEVEL => PDO::FB_REPEATABLE_READ]
|
||||
);
|
||||
echo "begin transaction\n";
|
||||
$dbh->beginTransaction();
|
||||
|
||||
echo "insert by other transaction\n";
|
||||
$dbh_other->exec("INSERT INTO {$table} VALUES (20)");
|
||||
echo "Read\n";
|
||||
$r = $dbh->query("SELECT * FROM {$table}");
|
||||
var_dump($r->fetchAll());
|
||||
|
||||
echo "Close transaction and reset table\n";
|
||||
$dbh->commit();
|
||||
$dbh_other->exec("DELETE FROM {$table}");
|
||||
unset($dbh);
|
||||
echo "\n";
|
||||
|
||||
|
||||
/*
|
||||
* SERIALIZABLE imposes a strong lock, so the lock will not be released and
|
||||
* the test will never end. There is currently no way to confirm.
|
||||
* If we can set the lock timeout, it might be possible to test it, so I'll leave it as is.
|
||||
*/
|
||||
|
||||
/*
|
||||
echo "========== SERIALIZABLE ==========\n";
|
||||
$dbh = new PDO(
|
||||
PDO_FIREBIRD_TEST_DSN,
|
||||
PDO_FIREBIRD_TEST_USER,
|
||||
PDO_FIREBIRD_TEST_PASS,
|
||||
[PDO::FB_TRANSACTION_ISOLATION_LEVEL => PDO::FB_SERIALIZABLE]
|
||||
);
|
||||
echo "begin transaction\n";
|
||||
$dbh->beginTransaction();
|
||||
|
||||
echo "insert by other transaction\n";
|
||||
$dbh_other->exec("INSERT INTO {$table} VALUES (20)");
|
||||
echo "Read\n";
|
||||
$r = $dbh->query("SELECT * FROM {$table}");
|
||||
var_dump($r->fetchAll());
|
||||
|
||||
echo "Close transaction and reset table\n";
|
||||
$dbh->commit();
|
||||
$dbh_other->exec("DELETE FROM {$table}");
|
||||
echo "\n";
|
||||
*/
|
||||
|
||||
unset($dbh);
|
||||
|
||||
echo "done!";
|
||||
?>
|
||||
--CLEAN--
|
||||
<?php
|
||||
require 'testdb.inc';
|
||||
@$dbh->exec('DROP TABLE txn_isolation_level_behavior');
|
||||
unset($dbh);
|
||||
?>
|
||||
--EXPECT--
|
||||
========== default(REPEATABLE READ) ==========
|
||||
begin transaction
|
||||
insert by other transaction
|
||||
Read
|
||||
array(0) {
|
||||
}
|
||||
Close transaction and reset table
|
||||
|
||||
========== READ COMMITTED ==========
|
||||
begin transaction
|
||||
insert by other transaction
|
||||
Read
|
||||
array(1) {
|
||||
[0]=>
|
||||
array(2) {
|
||||
["VAL"]=>
|
||||
int(20)
|
||||
[0]=>
|
||||
int(20)
|
||||
}
|
||||
}
|
||||
Close transaction and reset table
|
||||
|
||||
========== REPEATABLE READ ==========
|
||||
begin transaction
|
||||
insert by other transaction
|
||||
Read
|
||||
array(0) {
|
||||
}
|
||||
Close transaction and reset table
|
||||
|
||||
done!
|
||||
Reference in New Issue
Block a user