Statement.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. <?php
  2. namespace Drupal\Core\Database\Driver\sqlite;
  3. use Drupal\Core\Database\StatementPrefetch;
  4. use Drupal\Core\Database\StatementInterface;
  5. /**
  6. * SQLite implementation of \Drupal\Core\Database\Statement.
  7. *
  8. * The PDO SQLite driver only closes SELECT statements when the PDOStatement
  9. * destructor is called and SQLite does not allow data change (INSERT,
  10. * UPDATE etc) on a table which has open SELECT statements. This is a
  11. * user-space mock of PDOStatement that buffers all the data and doesn't
  12. * have those limitations.
  13. */
  14. class Statement extends StatementPrefetch implements StatementInterface {
  15. /**
  16. * {@inheritdoc}
  17. *
  18. * The PDO SQLite layer doesn't replace numeric placeholders in queries
  19. * correctly, and this makes numeric expressions (such as COUNT(*) >= :count)
  20. * fail. We replace numeric placeholders in the query ourselves to work
  21. * around this bug.
  22. *
  23. * See http://bugs.php.net/bug.php?id=45259 for more details.
  24. */
  25. protected function getStatement($query, &$args = []) {
  26. if (is_array($args) && !empty($args)) {
  27. // Check if $args is a simple numeric array.
  28. if (range(0, count($args) - 1) === array_keys($args)) {
  29. // In that case, we have unnamed placeholders.
  30. $count = 0;
  31. $new_args = [];
  32. foreach ($args as $value) {
  33. if (is_float($value) || is_int($value)) {
  34. if (is_float($value)) {
  35. // Force the conversion to float so as not to loose precision
  36. // in the automatic cast.
  37. $value = sprintf('%F', $value);
  38. }
  39. $query = substr_replace($query, $value, strpos($query, '?'), 1);
  40. }
  41. else {
  42. $placeholder = ':db_statement_placeholder_' . $count++;
  43. $query = substr_replace($query, $placeholder, strpos($query, '?'), 1);
  44. $new_args[$placeholder] = $value;
  45. }
  46. }
  47. $args = $new_args;
  48. }
  49. else {
  50. // Else, this is using named placeholders.
  51. foreach ($args as $placeholder => $value) {
  52. if (is_float($value) || is_int($value)) {
  53. if (is_float($value)) {
  54. // Force the conversion to float so as not to loose precision
  55. // in the automatic cast.
  56. $value = sprintf('%F', $value);
  57. }
  58. // We will remove this placeholder from the query as PDO throws an
  59. // exception if the number of placeholders in the query and the
  60. // arguments does not match.
  61. unset($args[$placeholder]);
  62. // PDO allows placeholders to not be prefixed by a colon. See
  63. // http://marc.info/?l=php-internals&m=111234321827149&w=2 for
  64. // more.
  65. if ($placeholder[0] != ':') {
  66. $placeholder = ":$placeholder";
  67. }
  68. // When replacing the placeholders, make sure we search for the
  69. // exact placeholder. For example, if searching for
  70. // ':db_placeholder_1', do not replace ':db_placeholder_11'.
  71. $query = preg_replace('/' . preg_quote($placeholder) . '\b/', $value, $query);
  72. }
  73. }
  74. }
  75. }
  76. return $this->pdoConnection->prepare($query);
  77. }
  78. /**
  79. * {@inheritdoc}
  80. */
  81. public function execute($args = [], $options = []) {
  82. try {
  83. $return = parent::execute($args, $options);
  84. }
  85. catch (\PDOException $e) {
  86. if (!empty($e->errorInfo[1]) && $e->errorInfo[1] === 17) {
  87. // The schema has changed. SQLite specifies that we must resend the query.
  88. $return = parent::execute($args, $options);
  89. }
  90. else {
  91. // Rethrow the exception.
  92. throw $e;
  93. }
  94. }
  95. // In some weird cases, SQLite will prefix some column names by the name
  96. // of the table. We post-process the data, by renaming the column names
  97. // using the same convention as MySQL and PostgreSQL.
  98. $rename_columns = [];
  99. foreach ($this->columnNames as $k => $column) {
  100. // In some SQLite versions, SELECT DISTINCT(field) will return "(field)"
  101. // instead of "field".
  102. if (preg_match("/^\((.*)\)$/", $column, $matches)) {
  103. $rename_columns[$column] = $matches[1];
  104. $this->columnNames[$k] = $matches[1];
  105. $column = $matches[1];
  106. }
  107. // Remove "table." prefixes.
  108. if (preg_match("/^.*\.(.*)$/", $column, $matches)) {
  109. $rename_columns[$column] = $matches[1];
  110. $this->columnNames[$k] = $matches[1];
  111. }
  112. }
  113. if ($rename_columns) {
  114. // DatabaseStatementPrefetch already extracted the first row,
  115. // put it back into the result set.
  116. if (isset($this->currentRow)) {
  117. $this->data[0] = &$this->currentRow;
  118. }
  119. // Then rename all the columns across the result set.
  120. foreach ($this->data as $k => $row) {
  121. foreach ($rename_columns as $old_column => $new_column) {
  122. $this->data[$k][$new_column] = $this->data[$k][$old_column];
  123. unset($this->data[$k][$old_column]);
  124. }
  125. }
  126. // Finally, extract the first row again.
  127. $this->currentRow = $this->data[0];
  128. unset($this->data[0]);
  129. }
  130. return $return;
  131. }
  132. }