/**
* @param string $sql A SQL query
* @param bool $pseudoPermanent Treat any table from CREATE TEMPORARY as pseudo-permanent
- * @return int|null A self::TEMP_* constant for temp table operations or null otherwise
+ * @return array A n-tuple of:
+ * - int|null: A self::TEMP_* constant for temp table operations or null otherwise
+ * - string|null: The name of the new temporary table $sql creates, or null
+ * - string|null: The name of the temporary table that $sql drops, or null
*/
- protected function registerTempTableWrite( $sql, $pseudoPermanent ) {
+ protected function getTempWrites( $sql, $pseudoPermanent ) {
static $qt = '[`"\']?(\w+)[`"\']?'; // quoted table
if ( preg_match(
$matches
) ) {
$type = $pseudoPermanent ? self::$TEMP_PSEUDO_PERMANENT : self::$TEMP_NORMAL;
- $this->sessionTempTables[$matches[1]] = $type;
- return $type;
+ return [ $type, $matches[1], null ];
} elseif ( preg_match(
'/^DROP\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?' . $qt . '/i',
$sql,
$matches
) ) {
- $type = $this->sessionTempTables[$matches[1]] ?? null;
- unset( $this->sessionTempTables[$matches[1]] );
-
- return $type;
+ return [ $this->sessionTempTables[$matches[1]] ?? null, null, $matches[1] ];
} elseif ( preg_match(
'/^TRUNCATE\s+(?:TEMPORARY\s+)?TABLE\s+(?:IF\s+EXISTS\s+)?' . $qt . '/i',
$sql,
$matches
) ) {
- return $this->sessionTempTables[$matches[1]] ?? null;
+ return [ $this->sessionTempTables[$matches[1]] ?? null, null, null ];
} elseif ( preg_match(
'/^(?:(?:INSERT|REPLACE)\s+(?:\w+\s+)?INTO|UPDATE|DELETE\s+FROM)\s+' . $qt . '/i',
$sql,
$matches
) ) {
- return $this->sessionTempTables[$matches[1]] ?? null;
+ return [ $this->sessionTempTables[$matches[1]] ?? null, null, null ];
}
- return null;
+ return [ null, null, null ];
+ }
+
+ /**
+ * @param IResultWrapper|bool $ret
+ * @param int|null $tmpType TEMP_NORMAL or TEMP_PSEUDO_PERMANENT
+ * @param string|null $tmpNew Name of created temp table
+ * @param string|null $tmpDel Name of dropped temp table
+ */
+ protected function registerTempWrites( $ret, $tmpType, $tmpNew, $tmpDel ) {
+ if ( $ret !== false ) {
+ if ( $tmpNew !== null ) {
+ $this->sessionTempTables[$tmpNew] = $tmpType;
+ }
+ if ( $tmpDel !== null ) {
+ unset( $this->sessionTempTables[$tmpDel] );
+ }
+ }
}
public function query( $sql, $fname = __METHOD__, $flags = 0 ) {
$priorTransaction = $this->trxLevel();
if ( $this->isWriteQuery( $sql ) ) {
- # In theory, non-persistent writes are allowed in read-only mode, but due to things
- # like https://bugs.mysql.com/bug.php?id=33669 that might not work anyway...
+ // In theory, non-persistent writes are allowed in read-only mode, but due to things
+ // like https://bugs.mysql.com/bug.php?id=33669 that might not work anyway...
$this->assertIsWritableMaster();
- # Do not treat temporary table writes as "meaningful writes" since they are only
- # visible to one session and are not permanent. Profile them as reads. Integration
- # tests can override this behavior via $flags.
+ // Do not treat temporary table writes as "meaningful writes" since they are only
+ // visible to one session and are not permanent. Profile them as reads. Integration
+ // tests can override this behavior via $flags.
$pseudoPermanent = $this->hasFlags( $flags, self::QUERY_PSEUDO_PERMANENT );
- $tableType = $this->registerTempTableWrite( $sql, $pseudoPermanent );
- $isPermWrite = ( $tableType !== self::$TEMP_NORMAL );
- # DBConnRef uses QUERY_REPLICA_ROLE to enforce the replica role for raw SQL queries
+ list( $tmpType, $tmpNew, $tmpDel ) = $this->getTempWrites( $sql, $pseudoPermanent );
+ $isPermWrite = ( $tmpType !== self::$TEMP_NORMAL );
+ // DBConnRef uses QUERY_REPLICA_ROLE to enforce the replica role for raw SQL queries
if ( $isPermWrite && $this->hasFlags( $flags, self::QUERY_REPLICA_ROLE ) ) {
throw new DBReadOnlyRoleError( $this, "Cannot write; target role is DB_REPLICA" );
}
} else {
+ // No permanent writes in this query
$isPermWrite = false;
+ // No temporary tables written to either
+ list( $tmpType, $tmpNew, $tmpDel ) = [ null, null, null ];
}
// Add trace comment to the begin of the sql string, right after the operator.
// Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (T44598)
$commentedSql = preg_replace( '/\s|$/', " /* $fname {$this->agent} */ ", $sql, 1 );
- // Send the query to the server and fetch any corresponding errors
+ // Send the query to the server and fetch any corresponding errors.
+ // This also doubles as a "ping" to see if the connection was dropped.
list( $ret, $err, $errno, $recoverableSR, $recoverableCL, $reconnected ) =
$this->executeQueryAttempt( $sql, $commentedSql, $isPermWrite, $fname, $flags );
$this->executeQueryAttempt( $sql, $commentedSql, $isPermWrite, $fname, $flags );
}
+ // Register creation and dropping of temporary tables
+ $this->registerTempWrites( $ret, $tmpType, $tmpNew, $tmpDel );
+
$corruptedTrx = false;
if ( $ret === false ) {