1
0
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:
Saki Takamachi
2023-12-08 02:34:55 +09:00
committed by GitHub
parent d6d838a21a
commit 834cb64403
8 changed files with 621 additions and 53 deletions

2
NEWS
View File

@@ -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)

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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 */

View 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)

View 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)

View 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!