Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
40 changes: 40 additions & 0 deletions fbird_query_exec.c
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,46 @@ int _php_fbird_exec(INTERNAL_FUNCTION_PARAMETERS, fbird_query *fb_query, zval *a
goto _php_fbird_ex_error;
}

/* Issue #572/#578: DDL executed on the DEFAULT transaction must commit
* immediately. Before this block, default-tx DDL committed only at
* connection close (#294's free-path commit is gated on had_open_cursor,
* which DDL statements never have): metadata locks (RDB$RELATION_FIELDS
* et al.) lingered for the life of the connection and blocked other
* connections' no-wait transactions (customer bug #572; doctrine suite
* SERIALIZABLE flake #578).
*
* The default transaction is implicit-autocommit context (trans_res ==
* NULL); users wanting transactional DDL use an explicit transaction,
* handled by the #540/#566 block above.
*
* Mirrors #294 guards: only the link's default tr_list head, skip
* persistent links (their cleanup belongs to MSHUTDOWN / #295), skip
* during module shutdown.
*
* Consequence (same trade-off as #566 documents for explicit tx):
* a hard commit closes open cursors on this transaction. A default-tx
* SELECT result left unfetched across a DDL execute will be invalidated;
* its query struct frees safely afterwards (stale-handle errors absorbed
* by the status wrapper).
*
* After the commit, fbt_transaction is NULL; the restart block at the
* top of this function transparently recreates the default tx on the
* next execute. */
if (fb_query->statement_type == isc_info_sql_stmt_ddl &&
fb_query->trans && fb_query->trans->fbt_transaction &&
fb_query->trans_res == NULL &&
!FBG(in_mshutdown) &&
fb_query->link && fb_query->link->fbc_connection &&
fb_query->link->tr_list &&
fb_query->link->tr_list->trans == fb_query->trans &&
!fb_query->link->is_persistent) {
ISC_STATUS ddl_status[256];
FBDEBUG("Issue #572: autocommit default tx after DDL execute");
fbt_commit(fb_query->trans->fbt_transaction, ddl_status);
fbt_free(fb_query->trans->fbt_transaction);
fb_query->trans->fbt_transaction = NULL;
}

fb_query->trans->affected_rows = 0;

/* For SELECT statements, mark cursor state as open with rows pending.
Expand Down
56 changes: 56 additions & 0 deletions tests/ddl_default_tx_commit_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
--TEST--
Issue #572: DDL via prepare+execute on the default tx must commit immediately
--SKIPIF--
<?php require_once __DIR__ . '/skipif.inc'; ?>
--FILE--
<?php
require_once __DIR__ . '/firebird.inc';

// Connection A: DDL via prepare_ex+execute on the DEFAULT transaction
// (trans_handle = null, mirroring the doctrine driver's
// fbird_prepare_ex($conn, $sql, getActiveTransaction()=null) call)
// (no explicit transaction resource). Pre-fix (#572): the statement
// executes fine but the default tx only commits at connection close,
// so other connections cannot see the table.
$c1 = fbird_connect($test_base);
var_dump($c1 instanceof Firebird\Connection);

$q = fbird_prepare_ex($c1, 'CREATE TABLE T572_DDL (id INTEGER)', null);
$r = fbird_execute($q);
var_dump($r === true); // DDL executes return true, not a result resource
fbird_free_query($q);

// Connection B: a fresh physical connection must see the table NOW,
// while $c1 is still open. This is the customer-visible #572 symptom.
$c2 = fbird_connect($test_base, '', '', '', 0, 3, '', FBIRD_CONNECT_FORCE_NEW);
var_dump($c2 instanceof Firebird\Connection && $c2 !== $c1);

$r2 = fbird_query($c2, 'SELECT COUNT(*) FROM T572_DDL');
var_dump($r2 !== false);
if ($r2 !== false) {
$row = fbird_fetch_row($r2);
var_dump($row !== false && (int)$row[0] === 0);
fbird_free_result($r2);
}

// cleanup on A
$q3 = fbird_prepare_ex($c1, 'DROP TABLE T572_DDL', null);
$r3 = fbird_execute($q3);
var_dump($r3 === true);
fbird_free_query($q3);

// jane: explicit close dodges the pre-existing FORCE_NEW shutdown segfault
// (#580 - two live links corrupt IAttachment at request shutdown); tracked there
fbird_close($c2);
unset($c2);

echo "done\n";
?>
--EXPECT--
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
done
Loading