Newer
Older
Alex Pott
committed
<?php
namespace Drupal\Core\Database\Driver\sqlite;
use Drupal\Core\Database\StatementPrefetch;
use Drupal\Core\Database\StatementInterface;
/**
* SQLite implementation of \Drupal\Core\Database\Statement.
Alex Pott
committed
*
* The PDO SQLite driver only closes SELECT statements when the PDOStatement
* destructor is called and SQLite does not allow data change (INSERT,
* UPDATE etc) on a table which has open SELECT statements. This is a
* user-space mock of PDOStatement that buffers all the data and doesn't
* have those limitations.
*/
class Statement extends StatementPrefetch implements StatementInterface {
/**
Alex Pott
committed
* {@inheritdoc}
Alex Pott
committed
*
* The PDO SQLite layer doesn't replace numeric placeholders in queries
* correctly, and this makes numeric expressions (such as COUNT(*) >= :count)
* fail. We replace numeric placeholders in the query ourselves to work
* around this bug.
*
* See http://bugs.php.net/bug.php?id=45259 for more details.
*/
protected function getStatement($query, &$args = []) {
Alex Pott
committed
if (is_array($args) && !empty($args)) {
Alex Pott
committed
// Check if $args is a simple numeric array.
if (range(0, count($args) - 1) === array_keys($args)) {
// In that case, we have unnamed placeholders.
$count = 0;
$new_args = [];
Alex Pott
committed
foreach ($args as $value) {
Alex Pott
committed
if (is_float($value) || is_int($value)) {
Alex Pott
committed
if (is_float($value)) {
// Force the conversion to float so as not to loose precision
// in the automatic cast.
$value = sprintf('%F', $value);
}
$query = substr_replace($query, $value, strpos($query, '?'), 1);
}
else {
$placeholder = ':db_statement_placeholder_' . $count++;
$query = substr_replace($query, $placeholder, strpos($query, '?'), 1);
$new_args[$placeholder] = $value;
}
}
$args = $new_args;
}
else {
// Else, this is using named placeholders.
foreach ($args as $placeholder => $value) {
Alex Pott
committed
if (is_float($value) || is_int($value)) {
Alex Pott
committed
if (is_float($value)) {
// Force the conversion to float so as not to loose precision
// in the automatic cast.
$value = sprintf('%F', $value);
}
// We will remove this placeholder from the query as PDO throws an
// exception if the number of placeholders in the query and the
// arguments does not match.
unset($args[$placeholder]);
// PDO allows placeholders to not be prefixed by a colon. See
// http://marc.info/?l=php-internals&m=111234321827149&w=2 for
// more.
if ($placeholder[0] != ':') {
$placeholder = ":$placeholder";
}
// When replacing the placeholders, make sure we search for the
// exact placeholder. For example, if searching for
// ':db_placeholder_1', do not replace ':db_placeholder_11'.
$query = preg_replace('/' . preg_quote($placeholder) . '\b/', $value, $query);
}
}
}
}
return $this->pdoConnection->prepare($query);
Alex Pott
committed
}
Alex Pott
committed
/**
* {@inheritdoc}
*/
public function execute($args = [], $options = []) {
Alex Pott
committed
try {
$return = parent::execute($args, $options);
}
catch (\PDOException $e) {
if (!empty($e->errorInfo[1]) && $e->errorInfo[1] === 17) {
// The schema has changed. SQLite specifies that we must resend the query.
$return = parent::execute($args, $options);
}
else {
// Rethrow the exception.
throw $e;
}
}
// In some weird cases, SQLite will prefix some column names by the name
// of the table. We post-process the data, by renaming the column names
// using the same convention as MySQL and PostgreSQL.
$rename_columns = [];
Alex Pott
committed
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
foreach ($this->columnNames as $k => $column) {
// In some SQLite versions, SELECT DISTINCT(field) will return "(field)"
// instead of "field".
if (preg_match("/^\((.*)\)$/", $column, $matches)) {
$rename_columns[$column] = $matches[1];
$this->columnNames[$k] = $matches[1];
$column = $matches[1];
}
// Remove "table." prefixes.
if (preg_match("/^.*\.(.*)$/", $column, $matches)) {
$rename_columns[$column] = $matches[1];
$this->columnNames[$k] = $matches[1];
}
}
if ($rename_columns) {
// DatabaseStatementPrefetch already extracted the first row,
// put it back into the result set.
if (isset($this->currentRow)) {
$this->data[0] = &$this->currentRow;
}
// Then rename all the columns across the result set.
foreach ($this->data as $k => $row) {
foreach ($rename_columns as $old_column => $new_column) {
$this->data[$k][$new_column] = $this->data[$k][$old_column];
unset($this->data[$k][$old_column]);
}
}
// Finally, extract the first row again.
$this->currentRow = $this->data[0];
unset($this->data[0]);
}
return $return;
}