php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login

Patch PDO_Transaction_During_AutoCommit_and_Commit_Continue_Support for PDO Firebird Bug #63690

Patch version 2012-12-05 07:16 UTC

Return to Bug #63690 | Download this patch
Patch Revisions:

Developer: james@kenjim.com

diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c
index acdc0dd..28d9e45 100644
--- a/ext/pdo/pdo_dbh.c
+++ b/ext/pdo/pdo_dbh.c
@@ -366,7 +366,8 @@ static PHP_METHOD(PDO, dbh_constructor)
 		dbh->default_fetch_type = PDO_FETCH_BOTH;
 	}	
 
-	dbh->auto_commit = pdo_attr_lval(options, PDO_ATTR_AUTOCOMMIT, 1 TSRMLS_CC);
+	dbh->auto_commit_setting = dbh->auto_commit = pdo_attr_lval(options, PDO_ATTR_AUTOCOMMIT, 1 TSRMLS_CC);
+	dbh->commit_continue = 0;
 
 	if (!dbh->data_source || (username && !dbh->username) || (password && !dbh->password)) {
 		php_error_docref(NULL TSRMLS_CC, E_ERROR, "out of memory");
@@ -611,11 +612,6 @@ static PHP_METHOD(PDO, beginTransaction)
 	}
 	PDO_CONSTRUCT_CHECK;
 
-	if (dbh->in_txn) {
-		zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "There is already an active transaction");
-		RETURN_FALSE;
-	}
-	
 	if (!dbh->methods->begin) {
 		/* TODO: this should be an exception; see the auto-commit mode
 		 * comments below */
@@ -623,6 +619,27 @@ static PHP_METHOD(PDO, beginTransaction)
 		RETURN_FALSE;
 	}
 
+	if (dbh->auto_commit_setting && dbh->auto_commit) {
+		if (dbh->methods->set_attribute) {
+			PDO_DBH_CLEAR_ERR();
+
+			zval *auto_commit_value;
+			MAKE_STD_ZVAL(auto_commit_value);
+			ZVAL_BOOL(auto_commit_value, FALSE);
+			dbh->methods->set_attribute(dbh, PDO_ATTR_AUTOCOMMIT, auto_commit_value TSRMLS_CC);
+			zval_dtor(auto_commit_value);
+		}
+	}
+
+	if (dbh->in_txn) {
+		if (dbh->commit_continue) { //Silently ignore error if we're using commits that keep transactions.
+			RETURN_TRUE;
+		} else {
+			zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "There is already an active transaction");
+			RETURN_FALSE;
+		}
+	}
+
 	if (dbh->methods->begin(dbh TSRMLS_CC)) {
 		dbh->in_txn = 1;
 		RETURN_TRUE;
@@ -650,7 +667,20 @@ static PHP_METHOD(PDO, commit)
 	}
 
 	if (dbh->methods->commit(dbh TSRMLS_CC)) {
-		dbh->in_txn = 0;
+		if (!dbh->commit_continue) {
+			dbh->in_txn = 0;
+		}
+		if (dbh->auto_commit_setting && !dbh->auto_commit) {
+			if (dbh->methods->set_attribute) {
+				PDO_DBH_CLEAR_ERR();
+	
+				zval *auto_commit_value;
+				MAKE_STD_ZVAL(auto_commit_value);
+				ZVAL_BOOL(auto_commit_value, TRUE);
+				dbh->methods->set_attribute(dbh, PDO_ATTR_AUTOCOMMIT, auto_commit_value TSRMLS_CC);
+				zval_dtor(auto_commit_value);
+			}
+		}
 		RETURN_TRUE;
 	}
 	
@@ -676,7 +706,21 @@ static PHP_METHOD(PDO, rollBack)
 	}
 
 	if (dbh->methods->rollback(dbh TSRMLS_CC)) {
-		dbh->in_txn = 0;
+		if (!dbh->commit_continue) {
+			dbh->in_txn = 0;
+		}
+
+		if (dbh->auto_commit_setting && !dbh->auto_commit) {
+			if (dbh->methods->set_attribute) {
+				PDO_DBH_CLEAR_ERR();
+	
+				zval *auto_commit_value;
+				MAKE_STD_ZVAL(auto_commit_value);
+				ZVAL_BOOL(auto_commit_value, TRUE);
+				dbh->methods->set_attribute(dbh, PDO_ATTR_AUTOCOMMIT, auto_commit_value TSRMLS_CC);
+				zval_dtor(auto_commit_value);
+			}
+		}
 		RETURN_TRUE;
 	}
 		
@@ -835,6 +879,33 @@ static int pdo_dbh_attribute_set(pdo_dbh_t *dbh, long attr, zval *value TSRMLS_D
 			return SUCCESS;
 		}
 			
+		case PDO_ATTR_AUTOCOMMIT:
+			/* handle autocommit so we can update auto_commit_setting before passing it on to the driver */
+			convert_to_boolean(value);
+
+			/* ignore if the new value equals the old one */
+			if (dbh->auto_commit_setting ^ Z_BVAL_P(value)) {
+				if (dbh->auto_commit_setting != dbh->auto_commit) {
+					zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "Can not set auto commit while in a manual transaction.");
+					return FAILURE;
+				}
+				if (!dbh->methods->begin) {
+					zend_throw_exception_ex(php_pdo_get_exception(), 0 TSRMLS_CC, "This driver doesn't support transactions");
+					return FAILURE;
+				}
+
+				dbh->auto_commit_setting = Z_BVAL_P(value);
+				if (!dbh->methods->set_attribute) {
+					goto fail;
+				}
+			
+				PDO_DBH_CLEAR_ERR();
+				if (dbh->methods->set_attribute(dbh, attr, value TSRMLS_CC)) {
+					return SUCCESS;
+				}
+			}
+
+			
 		default:
 			;
 	}
@@ -924,6 +995,9 @@ static PHP_METHOD(PDO, getAttribute)
 		case PDO_ATTR_DEFAULT_FETCH_MODE:
 			RETURN_LONG(dbh->default_fetch_type);
 
+		case PDO_ATTR_AUTOCOMMIT:
+			RETURN_BOOL(dbh->auto_commit_setting);
+
 	}
 	
 	if (!dbh->methods->get_attribute) {
@@ -1568,6 +1642,7 @@ static void pdo_dbh_free_storage(pdo_dbh_t *dbh TSRMLS_DC)
 	if (dbh->in_txn && dbh->methods && dbh->methods->rollback) {
 		dbh->methods->rollback(dbh TSRMLS_CC);
 		dbh->in_txn = 0;
+		dbh->auto_commit = dbh->auto_commit_setting;
 	}
 	
 	if (dbh->is_persistent && dbh->methods && dbh->methods->persistent_shutdown) {
diff --git a/ext/pdo/php_pdo_driver.h b/ext/pdo/php_pdo_driver.h
index b9c46ba..4c4a28c 100644
--- a/ext/pdo/php_pdo_driver.h
+++ b/ext/pdo/php_pdo_driver.h
@@ -469,9 +469,16 @@ struct _pdo_dbh_t {
 	/* when set, convert int/floats to strings */
 	unsigned stringify:1;
 
+	/* keeps track of actual auto commit setting.
+	 * differes from auto_commit when in a manually started transaction */
+	unsigned auto_commit_setting:1;
+
+	/* if true, after a commit we will still be in a transaction */
+	unsigned commit_continue:1;
+
 	/* the sum of the number of bits here and the bit fields preceeding should
 	 * equal 32 */
-	unsigned _reserved_flags:21;
+	unsigned _reserved_flags:19;
 
 	/* data source string used to open this handle */
 	const char *data_source;
diff --git a/ext/pdo_firebird/firebird_driver.c b/ext/pdo_firebird/firebird_driver.c
index 9b0f596..f2ead40 100644
--- a/ext/pdo_firebird/firebird_driver.c
+++ b/ext/pdo_firebird/firebird_driver.c
@@ -371,9 +371,19 @@ static int firebird_handle_commit(pdo_dbh_t *dbh TSRMLS_DC) /* {{{ */
 {
 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
 
-	if (isc_commit_transaction(H->isc_status, &H->tr)) {
-		RECORD_ERROR(dbh);
-		return 0;
+	if (H->commit_retaining) {
+		dbh->commit_continue = 1;
+		if (isc_commit_retaining(H->isc_status, &H->tr)) {
+			RECORD_ERROR(dbh);
+			return 0;
+		}
+	} else {
+		dbh->commit_continue = 0;
+		if (isc_commit_transaction(H->isc_status, &H->tr)) {
+			RECORD_ERROR(dbh);
+			return 0;
+		}
+		dbh->in_txn = 0;
 	}
 	return 1;
 }
@@ -384,9 +394,18 @@ static int firebird_handle_rollback(pdo_dbh_t *dbh TSRMLS_DC) /* {{{ */
 {
 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
 
-	if (isc_rollback_transaction(H->isc_status, &H->tr)) {
-		RECORD_ERROR(dbh);
-		return 0;
+	if (H->commit_retaining) {
+		dbh->commit_continue = 1;
+		if (isc_rollback_retaining(H->isc_status, &H->tr)) {
+			RECORD_ERROR(dbh);
+			return 0;
+		}
+	} else {
+		dbh->commit_continue = 0;
+		if (isc_rollback_transaction(H->isc_status, &H->tr)) {
+			RECORD_ERROR(dbh);
+			return 0;
+		}
 	}
 	return 1;
 }
@@ -479,24 +498,25 @@ static int firebird_handle_set_attribute(pdo_dbh_t *dbh, long attr, zval *val TS
 		case PDO_ATTR_AUTOCOMMIT:
 
 			convert_to_boolean(val);
-	
+
 			/* ignore if the new value equals the old one */			
-			if (dbh->auto_commit ^ Z_BVAL_P(val)) {
-				if (dbh->in_txn) {
-					if (Z_BVAL_P(val)) {
-						/* turning on auto_commit with an open transaction is illegal, because
-						   we won't know what to do with it */
-						H->last_app_error = "Cannot enable auto-commit while a transaction is already open";
-						return 0;
-					} else {
-						/* close the transaction */
-						if (!firebird_handle_commit(dbh TSRMLS_CC)) {
-							break;
+			if (dbh->auto_commit ^ Z_BVAL_P(val)) {
+				if (dbh->in_txn) {
+					if (Z_BVAL_P(val)) { //Turn on auto commit
+						if(!H->commit_retaining) {
+							/* turning on auto_commit with an open transaction is illegal, because
+							   we won't know what to do with it */
+							H->last_app_error = "Cannot enable auto-commit while a transaction is already open";
+							return 0;
 						}
-						dbh->in_txn = 0;
-					}
-				}
-				dbh->auto_commit = Z_BVAL_P(val);
+					} else {
+						/* close the transaction */
+						if (!firebird_handle_commit(dbh TSRMLS_CC)) {
+							break;
+						}
+					}
+				}
+				dbh->auto_commit = Z_BVAL_P(val);
 			}
 			return 1;
 
@@ -505,6 +525,11 @@ static int firebird_handle_set_attribute(pdo_dbh_t *dbh, long attr, zval *val TS
 			H->fetch_table_names = Z_BVAL_P(val);
 			return 1;
 
+		case PDO_FB_ATTR_COMMIT_RETAINING:
+			convert_to_boolean(val);
+			H->commit_retaining = Z_BVAL_P(val);
+			return 1;
+
 		case PDO_FB_ATTR_DATE_FORMAT:
 			convert_to_string(val);
 			if (H->date_format) {
@@ -656,6 +681,8 @@ static int pdo_firebird_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRM
 
 	pdo_firebird_db_handle *H = dbh->driver_data = pecalloc(1,sizeof(*H),dbh->is_persistent);
 
+	H->commit_retaining = 0;
+
 	php_pdo_parse_data_source(dbh->data_source, dbh->data_source_len, vars, 3);
 	
 	do {
diff --git a/ext/pdo_firebird/pdo_firebird.c b/ext/pdo_firebird/pdo_firebird.c
index c2bbe37..7d7705d 100644
--- a/ext/pdo_firebird/pdo_firebird.c
+++ b/ext/pdo_firebird/pdo_firebird.c
@@ -70,6 +70,7 @@ ZEND_GET_MODULE(pdo_firebird)
 
 PHP_MINIT_FUNCTION(pdo_firebird) /* {{{ */
 {
+	REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_COMMIT_RETAINING", (long) PDO_FB_ATTR_COMMIT_RETAINING);
 	REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_DATE_FORMAT", (long) PDO_FB_ATTR_DATE_FORMAT);
 	REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_TIME_FORMAT", (long) PDO_FB_ATTR_TIME_FORMAT);
 	REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_TIMESTAMP_FORMAT", (long) PDO_FB_ATTR_TIMESTAMP_FORMAT);
diff --git a/ext/pdo_firebird/php_pdo_firebird_int.h b/ext/pdo_firebird/php_pdo_firebird_int.h
index bb15d54..0316f3f 100644
--- a/ext/pdo_firebird/php_pdo_firebird_int.h
+++ b/ext/pdo_firebird/php_pdo_firebird_int.h
@@ -84,8 +84,11 @@ typedef struct {
 	
 	/* prepend table names on column names in fetch */
 	unsigned fetch_table_names:1;
+
+	/* commit retain used to store commit/rollback mode */
+	unsigned commit_retaining:1;
 	
-	unsigned _reserved:31;
+	unsigned _reserved:30;
 	
 } pdo_firebird_db_handle;
 
@@ -136,6 +139,7 @@ enum {
 	PDO_FB_ATTR_DATE_FORMAT = PDO_ATTR_DRIVER_SPECIFIC,
 	PDO_FB_ATTR_TIME_FORMAT,
 	PDO_FB_ATTR_TIMESTAMP_FORMAT,
+	PDO_FB_ATTR_COMMIT_RETAINING,
 };
 
 #endif	/* PHP_PDO_FIREBIRD_INT_H */
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Fri Apr 26 02:01:29 2024 UTC