/** * Summary of pushTransaction * @param string $name * @param DatabaseTransactionSettings $settings * @throws DatabaseTransactionNameNonUniqueException * @return void */ public function pushTransaction($name, $settings = NULL) { if ($settings == NULL) { $settings = DatabaseTransactionSettings::GetBetterDefaults(); } if (!$this->supportsTransactions()) { return; } if (isset($this->transactionLayers[$name])) { throw new DatabaseTransactionNameNonUniqueException($name . " is already in use."); } $started = FALSE; // If we're already in a transaction. // TODO: Transaction scope Options is not working properly // for first level transactions. It assumes that - always - a first level // transaction must be started. if ($this->inTransaction()) { switch ($settings->Get_ScopeOption()) { case DatabaseTransactionScopeOption::RequiresNew(): $this->query_direct('SAVE TRANSACTION ' . $name); $started = TRUE; break; case DatabaseTransactionScopeOption::Required(): // We are already in a transaction, do nothing. break; case DatabaseTransactionScopeOption::Supress(): // The only way to supress the ambient transaction is to use a new connection // during the scope of this transaction, a bit messy to implement. throw new Exception('DatabaseTransactionScopeOption::Supress not implemented.'); } } else { if ($settings->Get_IsolationLevel() != DatabaseTransactionIsolationLevel::Ignore()) { $current_isolation_level = strtoupper($this->schema()->UserOptions()['isolation level']); // Se what isolation level was requested. $level = $settings->Get_IsolationLevel()->__toString(); if (strcasecmp($current_isolation_level, $level) !== 0) { $this->query_direct("SET TRANSACTION ISOLATION LEVEL {$level}"); } } // In order to start a transaction current statement cursors // must be closed. foreach($this->statement_cache as $statement) { $statement->closeCursor(); } $this->connection->beginTransaction(); } // Store the name and settings in the stack. $this->transactionLayers[$name] = array('settings' => $settings, 'active' => TRUE, 'name' => $name, 'started' => $started); }
public function execute() { if (!$this->preExecute()) { return NULL; } // Fetch the list of blobs and sequences used on that table. $columnInformation = $this->connection->schema()->queryColumnInformation($this->table); // Find out if there is an identity field set in this insert. $this->setIdentity = !empty($columnInformation['identity']) && in_array($columnInformation['identity'], $this->insertFields); $identity = !empty($columnInformation['identity']) ? $columnInformation['identity'] : NULL; #region Select Based Insert if (!empty($this->fromQuery)) { // Re-initialize the values array so that we can re-use this query. $this->insertValues = array(); $stmt = $this->connection->prepareQuery((string) $this); // Handle the case of SELECT-based INSERT queries first. $arguments = $this->fromQuery->getArguments(); DatabaseUtils::BindArguments($stmt, $arguments); $stmt->execute(); // We can only have 1 identity column per table (or none, where fetchColumn will fail) try { return $stmt->fetchColumn(0); } catch(\PDOException $e) { return NULL; } } #endregion #region Inserts with no values (full defaults) // Handle the case of full-default queries. if (empty($this->fromQuery) && (empty($this->insertFields) || empty($this->insertValues))) { // Re-initialize the values array so that we can re-use this query. $this->insertValues = array(); $stmt = $this->connection->prepareQuery((string) $this); $stmt->execute(); // We can only have 1 identity column per table (or none, where fetchColumn will fail) try { return $stmt->fetchColumn(0); } catch(\PDOException $e) { return NULL; } } #endregion #region Regular Inserts // Each insert happens in its own query. However, we wrap it in a transaction // so that it is atomic where possible. $transaction = NULL; $batch_size = 200; // At most we can process in batches of 250 elements. $batch = array_splice($this->insertValues, 0, $batch_size); // If we are going to need more than one batch for this... start a transaction. if (empty($this->queryOptions['sqlsrv_skip_transactions']) && !empty($this->insertValues)) { $transaction = $this->connection->startTransaction('', DatabaseTransactionSettings::GetBetterDefaults()); } while (!empty($batch)) { // Give me a query with the amount of batch inserts. $query = (string) $this->__toString2(count($batch)); // Prepare the query. $stmt = $this->connection->prepareQuery($query); // We use this array to store references to the blob handles. // This is necessary because the PDO will otherwise messes up with references. $blobs = array(); $max_placeholder = 0; foreach ($batch as $insert_index => $insert_values) { $values = array_combine($this->insertFields, $insert_values); DatabaseUtils::BindValues($stmt, $values, $blobs, ':db_insert', $columnInformation, $max_placeholder, $insert_index); } $stmt->execute(array(), array('fetch' => PDO::FETCH_ASSOC)); // We can only have 1 identity column per table (or none, where fetchColumn will fail) // When the column does not have an identity column, no results are thrown back. foreach($stmt as $insert) { try { $this->inserted_keys[] = $insert[$identity]; } catch(\Exception $e) { $this->inserted_keys[] = NULL; } } // Fetch the next batch. $batch = array_splice($this->insertValues, 0, $batch_size); } // If we started a transaction, commit it. if ($transaction) { $transaction->commit(); } // Re-initialize the values array so that we can re-use this query. $this->insertValues = array(); // Return the last inserted key. return empty($this->inserted_keys) ? NULL : end($this->inserted_keys); #endregion }