Chris@0: 'stdClass', Chris@0: 'constructor_args' => [], Chris@0: 'object' => NULL, Chris@0: 'column' => 0, Chris@0: ]; Chris@0: Chris@0: /** Chris@0: * Holds the default fetch style. Chris@0: * Chris@0: * @var int Chris@0: */ Chris@0: protected $defaultFetchStyle = \PDO::FETCH_OBJ; Chris@0: Chris@0: /** Chris@0: * Holds supplementary default fetch options. Chris@0: * Chris@17: * @var array Chris@0: */ Chris@0: protected $defaultFetchOptions = [ Chris@0: 'class' => 'stdClass', Chris@0: 'constructor_args' => [], Chris@0: 'object' => NULL, Chris@0: 'column' => 0, Chris@0: ]; Chris@0: Chris@0: /** Chris@0: * Is rowCount() execution allowed. Chris@0: * Chris@0: * @var bool Chris@0: */ Chris@0: public $allowRowCount = FALSE; Chris@0: Chris@0: public function __construct(\PDO $pdo_connection, Connection $connection, $query, array $driver_options = []) { Chris@0: $this->pdoConnection = $pdo_connection; Chris@0: $this->dbh = $connection; Chris@0: $this->queryString = $query; Chris@0: $this->driverOptions = $driver_options; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function execute($args = [], $options = []) { Chris@0: if (isset($options['fetch'])) { Chris@0: if (is_string($options['fetch'])) { Chris@0: // Default to an object. Note: db fields will be added to the object Chris@0: // before the constructor is run. If you need to assign fields after Chris@0: // the constructor is run. See https://www.drupal.org/node/315092. Chris@0: $this->setFetchMode(\PDO::FETCH_CLASS, $options['fetch']); Chris@0: } Chris@0: else { Chris@0: $this->setFetchMode($options['fetch']); Chris@0: } Chris@0: } Chris@0: Chris@0: $logger = $this->dbh->getLogger(); Chris@0: if (!empty($logger)) { Chris@0: $query_start = microtime(TRUE); Chris@0: } Chris@0: Chris@0: // Prepare the query. Chris@0: $statement = $this->getStatement($this->queryString, $args); Chris@0: if (!$statement) { Chris@0: $this->throwPDOException(); Chris@0: } Chris@0: Chris@0: $return = $statement->execute($args); Chris@0: if (!$return) { Chris@0: $this->throwPDOException(); Chris@0: } Chris@0: Chris@0: if ($options['return'] == Database::RETURN_AFFECTED) { Chris@0: $this->rowCount = $statement->rowCount(); Chris@0: } Chris@0: // Fetch all the data from the reply, in order to release any lock Chris@0: // as soon as possible. Chris@0: $this->data = $statement->fetchAll(\PDO::FETCH_ASSOC); Chris@0: // Destroy the statement as soon as possible. See the documentation of Chris@0: // \Drupal\Core\Database\Driver\sqlite\Statement for an explanation. Chris@0: unset($statement); Chris@0: Chris@0: $this->resultRowCount = count($this->data); Chris@0: Chris@0: if ($this->resultRowCount) { Chris@0: $this->columnNames = array_keys($this->data[0]); Chris@0: } Chris@0: else { Chris@0: $this->columnNames = []; Chris@0: } Chris@0: Chris@0: if (!empty($logger)) { Chris@0: $query_end = microtime(TRUE); Chris@0: $logger->log($this, $args, $query_end - $query_start); Chris@0: } Chris@0: Chris@0: // Initialize the first row in $this->currentRow. Chris@0: $this->next(); Chris@0: Chris@0: return $return; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Throw a PDO Exception based on the last PDO error. Chris@0: */ Chris@0: protected function throwPDOException() { Chris@0: $error_info = $this->dbh->errorInfo(); Chris@0: // We rebuild a message formatted in the same way as PDO. Chris@0: $exception = new \PDOException("SQLSTATE[" . $error_info[0] . "]: General error " . $error_info[1] . ": " . $error_info[2]); Chris@0: $exception->errorInfo = $error_info; Chris@0: throw $exception; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Grab a PDOStatement object from a given query and its arguments. Chris@0: * Chris@0: * Some drivers (including SQLite) will need to perform some preparation Chris@0: * themselves to get the statement right. Chris@0: * Chris@0: * @param $query Chris@0: * The query. Chris@16: * @param array|null $args Chris@16: * An array of arguments. This can be NULL. Chris@0: * @return \PDOStatement Chris@0: * A PDOStatement object. Chris@0: */ Chris@0: protected function getStatement($query, &$args = []) { Chris@0: return $this->dbh->prepare($query); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getQueryString() { Chris@0: return $this->queryString; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setFetchMode($mode, $a1 = NULL, $a2 = []) { Chris@0: $this->defaultFetchStyle = $mode; Chris@0: switch ($mode) { Chris@0: case \PDO::FETCH_CLASS: Chris@0: $this->defaultFetchOptions['class'] = $a1; Chris@0: if ($a2) { Chris@0: $this->defaultFetchOptions['constructor_args'] = $a2; Chris@0: } Chris@0: break; Chris@0: case \PDO::FETCH_COLUMN: Chris@0: $this->defaultFetchOptions['column'] = $a1; Chris@0: break; Chris@0: case \PDO::FETCH_INTO: Chris@0: $this->defaultFetchOptions['object'] = $a1; Chris@0: break; Chris@0: } Chris@0: Chris@0: // Set the values for the next fetch. Chris@0: $this->fetchStyle = $this->defaultFetchStyle; Chris@0: $this->fetchOptions = $this->defaultFetchOptions; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Return the current row formatted according to the current fetch style. Chris@0: * Chris@0: * This is the core method of this class. It grabs the value at the current Chris@0: * array position in $this->data and format it according to $this->fetchStyle Chris@0: * and $this->fetchMode. Chris@0: * Chris@0: * @return mixed Chris@0: * The current row formatted as requested. Chris@0: */ Chris@0: public function current() { Chris@0: if (isset($this->currentRow)) { Chris@0: switch ($this->fetchStyle) { Chris@0: case \PDO::FETCH_ASSOC: Chris@0: return $this->currentRow; Chris@0: case \PDO::FETCH_BOTH: Chris@0: // \PDO::FETCH_BOTH returns an array indexed by both the column name Chris@0: // and the column number. Chris@0: return $this->currentRow + array_values($this->currentRow); Chris@0: case \PDO::FETCH_NUM: Chris@0: return array_values($this->currentRow); Chris@0: case \PDO::FETCH_LAZY: Chris@0: // We do not do lazy as everything is fetched already. Fallback to Chris@0: // \PDO::FETCH_OBJ. Chris@0: case \PDO::FETCH_OBJ: Chris@0: return (object) $this->currentRow; Chris@0: case \PDO::FETCH_CLASS | \PDO::FETCH_CLASSTYPE: Chris@0: $class_name = array_unshift($this->currentRow); Chris@0: // Deliberate no break. Chris@0: case \PDO::FETCH_CLASS: Chris@0: if (!isset($class_name)) { Chris@0: $class_name = $this->fetchOptions['class']; Chris@0: } Chris@0: if (count($this->fetchOptions['constructor_args'])) { Chris@0: $reflector = new \ReflectionClass($class_name); Chris@0: $result = $reflector->newInstanceArgs($this->fetchOptions['constructor_args']); Chris@0: } Chris@0: else { Chris@0: $result = new $class_name(); Chris@0: } Chris@0: foreach ($this->currentRow as $k => $v) { Chris@0: $result->$k = $v; Chris@0: } Chris@0: return $result; Chris@0: case \PDO::FETCH_INTO: Chris@0: foreach ($this->currentRow as $k => $v) { Chris@0: $this->fetchOptions['object']->$k = $v; Chris@0: } Chris@0: return $this->fetchOptions['object']; Chris@0: case \PDO::FETCH_COLUMN: Chris@0: if (isset($this->columnNames[$this->fetchOptions['column']])) { Chris@0: return $this->currentRow[$this->columnNames[$this->fetchOptions['column']]]; Chris@0: } Chris@0: else { Chris@0: return; Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function key() { Chris@0: return $this->currentKey; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function rewind() { Chris@0: // Nothing to do: our DatabaseStatement can't be rewound. Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function next() { Chris@0: if (!empty($this->data)) { Chris@0: $this->currentRow = reset($this->data); Chris@0: $this->currentKey = key($this->data); Chris@0: unset($this->data[$this->currentKey]); Chris@0: } Chris@0: else { Chris@0: $this->currentRow = NULL; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function valid() { Chris@0: return isset($this->currentRow); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function rowCount() { Chris@0: // SELECT query should not use the method. Chris@0: if ($this->allowRowCount) { Chris@0: return $this->rowCount; Chris@0: } Chris@0: else { Chris@0: throw new RowCountException(); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function fetch($fetch_style = NULL, $cursor_orientation = \PDO::FETCH_ORI_NEXT, $cursor_offset = NULL) { Chris@0: if (isset($this->currentRow)) { Chris@0: // Set the fetch parameter. Chris@0: $this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle; Chris@0: $this->fetchOptions = $this->defaultFetchOptions; Chris@0: Chris@0: // Grab the row in the format specified above. Chris@0: $return = $this->current(); Chris@0: // Advance the cursor. Chris@0: $this->next(); Chris@0: Chris@0: // Reset the fetch parameters to the value stored using setFetchMode(). Chris@0: $this->fetchStyle = $this->defaultFetchStyle; Chris@0: $this->fetchOptions = $this->defaultFetchOptions; Chris@0: return $return; Chris@0: } Chris@0: else { Chris@0: return FALSE; Chris@0: } Chris@0: } Chris@0: Chris@0: public function fetchColumn($index = 0) { Chris@0: if (isset($this->currentRow) && isset($this->columnNames[$index])) { Chris@0: // We grab the value directly from $this->data, and format it. Chris@0: $return = $this->currentRow[$this->columnNames[$index]]; Chris@0: $this->next(); Chris@0: return $return; Chris@0: } Chris@0: else { Chris@0: return FALSE; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function fetchField($index = 0) { Chris@0: return $this->fetchColumn($index); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function fetchObject($class_name = NULL, $constructor_args = []) { Chris@0: if (isset($this->currentRow)) { Chris@0: if (!isset($class_name)) { Chris@0: // Directly cast to an object to avoid a function call. Chris@0: $result = (object) $this->currentRow; Chris@0: } Chris@0: else { Chris@0: $this->fetchStyle = \PDO::FETCH_CLASS; Chris@0: $this->fetchOptions = ['constructor_args' => $constructor_args]; Chris@0: // Grab the row in the format specified above. Chris@0: $result = $this->current(); Chris@0: // Reset the fetch parameters to the value stored using setFetchMode(). Chris@0: $this->fetchStyle = $this->defaultFetchStyle; Chris@0: $this->fetchOptions = $this->defaultFetchOptions; Chris@0: } Chris@0: Chris@0: $this->next(); Chris@0: Chris@0: return $result; Chris@0: } Chris@0: else { Chris@0: return FALSE; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function fetchAssoc() { Chris@0: if (isset($this->currentRow)) { Chris@0: $result = $this->currentRow; Chris@0: $this->next(); Chris@0: return $result; Chris@0: } Chris@0: else { Chris@0: return FALSE; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function fetchAll($mode = NULL, $column_index = NULL, $constructor_arguments = NULL) { Chris@0: $this->fetchStyle = isset($mode) ? $mode : $this->defaultFetchStyle; Chris@0: $this->fetchOptions = $this->defaultFetchOptions; Chris@0: if (isset($column_index)) { Chris@0: $this->fetchOptions['column'] = $column_index; Chris@0: } Chris@0: if (isset($constructor_arguments)) { Chris@0: $this->fetchOptions['constructor_args'] = $constructor_arguments; Chris@0: } Chris@0: Chris@0: $result = []; Chris@0: // Traverse the array as PHP would have done. Chris@0: while (isset($this->currentRow)) { Chris@0: // Grab the row in the format specified above. Chris@0: $result[] = $this->current(); Chris@0: $this->next(); Chris@0: } Chris@0: Chris@0: // Reset the fetch parameters to the value stored using setFetchMode(). Chris@0: $this->fetchStyle = $this->defaultFetchStyle; Chris@0: $this->fetchOptions = $this->defaultFetchOptions; Chris@0: return $result; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function fetchCol($index = 0) { Chris@0: if (isset($this->columnNames[$index])) { Chris@0: $result = []; Chris@0: // Traverse the array as PHP would have done. Chris@0: while (isset($this->currentRow)) { Chris@0: $result[] = $this->currentRow[$this->columnNames[$index]]; Chris@0: $this->next(); Chris@0: } Chris@0: return $result; Chris@0: } Chris@0: else { Chris@0: return []; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function fetchAllKeyed($key_index = 0, $value_index = 1) { Chris@0: if (!isset($this->columnNames[$key_index]) || !isset($this->columnNames[$value_index])) { Chris@0: return []; Chris@0: } Chris@0: Chris@0: $key = $this->columnNames[$key_index]; Chris@0: $value = $this->columnNames[$value_index]; Chris@0: Chris@0: $result = []; Chris@0: // Traverse the array as PHP would have done. Chris@0: while (isset($this->currentRow)) { Chris@0: $result[$this->currentRow[$key]] = $this->currentRow[$value]; Chris@0: $this->next(); Chris@0: } Chris@0: return $result; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function fetchAllAssoc($key, $fetch_style = NULL) { Chris@0: $this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle; Chris@0: $this->fetchOptions = $this->defaultFetchOptions; Chris@0: Chris@0: $result = []; Chris@0: // Traverse the array as PHP would have done. Chris@0: while (isset($this->currentRow)) { Chris@0: // Grab the row in its raw \PDO::FETCH_ASSOC format. Chris@0: $result_row = $this->current(); Chris@0: $result[$this->currentRow[$key]] = $result_row; Chris@0: $this->next(); Chris@0: } Chris@0: Chris@0: // Reset the fetch parameters to the value stored using setFetchMode(). Chris@0: $this->fetchStyle = $this->defaultFetchStyle; Chris@0: $this->fetchOptions = $this->defaultFetchOptions; Chris@0: return $result; Chris@0: } Chris@0: Chris@0: }