prefetch.inc

Database interface code for engines that need complete control over their result sets. For example, 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.

Classes

Namesort descending Description
DatabaseStatementPrefetch An implementation of DatabaseStatementInterface that prefetches all data.

File

drupal/includes/database/prefetch.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * Database interface code for engines that need complete control over their
  5. * result sets. For example, SQLite will prefix some column names by the name
  6. * of the table. We post-process the data, by renaming the column names
  7. * using the same convention as MySQL and PostgreSQL.
  8. */
  9. /**
  10. * @addtogroup database
  11. * @{
  12. */
  13. /**
  14. * An implementation of DatabaseStatementInterface that prefetches all data.
  15. *
  16. * This class behaves very similar to a PDOStatement but as it always fetches
  17. * every row it is possible to manipulate those results.
  18. */
  19. class DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface {
  20. /**
  21. * The query string.
  22. *
  23. * @var string
  24. */
  25. protected $queryString;
  26. /**
  27. * Driver-specific options. Can be used by child classes.
  28. *
  29. * @var Array
  30. */
  31. protected $driverOptions;
  32. /**
  33. * Reference to the database connection object for this statement.
  34. *
  35. * The name $dbh is inherited from PDOStatement.
  36. *
  37. * @var DatabaseConnection
  38. */
  39. public $dbh;
  40. /**
  41. * Main data store.
  42. *
  43. * @var Array
  44. */
  45. protected $data = array();
  46. /**
  47. * The current row, retrieved in PDO::FETCH_ASSOC format.
  48. *
  49. * @var Array
  50. */
  51. protected $currentRow = NULL;
  52. /**
  53. * The key of the current row.
  54. *
  55. * @var int
  56. */
  57. protected $currentKey = NULL;
  58. /**
  59. * The list of column names in this result set.
  60. *
  61. * @var Array
  62. */
  63. protected $columnNames = NULL;
  64. /**
  65. * The number of rows affected by the last query.
  66. *
  67. * @var int
  68. */
  69. protected $rowCount = NULL;
  70. /**
  71. * The number of rows in this result set.
  72. *
  73. * @var int
  74. */
  75. protected $resultRowCount = 0;
  76. /**
  77. * Holds the current fetch style (which will be used by the next fetch).
  78. * @see PDOStatement::fetch()
  79. *
  80. * @var int
  81. */
  82. protected $fetchStyle = PDO::FETCH_OBJ;
  83. /**
  84. * Holds supplementary current fetch options (which will be used by the next fetch).
  85. *
  86. * @var Array
  87. */
  88. protected $fetchOptions = array(
  89. 'class' => 'stdClass',
  90. 'constructor_args' => array(),
  91. 'object' => NULL,
  92. 'column' => 0,
  93. );
  94. /**
  95. * Holds the default fetch style.
  96. *
  97. * @var int
  98. */
  99. protected $defaultFetchStyle = PDO::FETCH_OBJ;
  100. /**
  101. * Holds supplementary default fetch options.
  102. *
  103. * @var Array
  104. */
  105. protected $defaultFetchOptions = array(
  106. 'class' => 'stdClass',
  107. 'constructor_args' => array(),
  108. 'object' => NULL,
  109. 'column' => 0,
  110. );
  111. public function __construct(DatabaseConnection $connection, $query, array $driver_options = array()) {
  112. $this->dbh = $connection;
  113. $this->queryString = $query;
  114. $this->driverOptions = $driver_options;
  115. }
  116. /**
  117. * Executes a prepared statement.
  118. *
  119. * @param $args
  120. * An array of values with as many elements as there are bound parameters in the SQL statement being executed.
  121. * @param $options
  122. * An array of options for this query.
  123. * @return
  124. * TRUE on success, or FALSE on failure.
  125. */
  126. public function execute($args = array(), $options = array()) {
  127. if (isset($options['fetch'])) {
  128. if (is_string($options['fetch'])) {
  129. // Default to an object. Note: db fields will be added to the object
  130. // before the constructor is run. If you need to assign fields after
  131. // the constructor is run, see http://drupal.org/node/315092.
  132. $this->setFetchMode(PDO::FETCH_CLASS, $options['fetch']);
  133. }
  134. else {
  135. $this->setFetchMode($options['fetch']);
  136. }
  137. }
  138. $logger = $this->dbh->getLogger();
  139. if (!empty($logger)) {
  140. $query_start = microtime(TRUE);
  141. }
  142. // Prepare the query.
  143. $statement = $this->getStatement($this->queryString, $args);
  144. if (!$statement) {
  145. $this->throwPDOException();
  146. }
  147. $return = $statement->execute($args);
  148. if (!$return) {
  149. $this->throwPDOException();
  150. }
  151. // Fetch all the data from the reply, in order to release any lock
  152. // as soon as possible.
  153. $this->rowCount = $statement->rowCount();
  154. $this->data = $statement->fetchAll(PDO::FETCH_ASSOC);
  155. // Destroy the statement as soon as possible. See
  156. // DatabaseConnection_sqlite::PDOPrepare() for explanation.
  157. unset($statement);
  158. $this->resultRowCount = count($this->data);
  159. if ($this->resultRowCount) {
  160. $this->columnNames = array_keys($this->data[0]);
  161. }
  162. else {
  163. $this->columnNames = array();
  164. }
  165. if (!empty($logger)) {
  166. $query_end = microtime(TRUE);
  167. $logger->log($this, $args, $query_end - $query_start);
  168. }
  169. // Initialize the first row in $this->currentRow.
  170. $this->next();
  171. return $return;
  172. }
  173. /**
  174. * Throw a PDO Exception based on the last PDO error.
  175. */
  176. protected function throwPDOException() {
  177. $error_info = $this->dbh->errorInfo();
  178. // We rebuild a message formatted in the same way as PDO.
  179. $exception = new PDOException("SQLSTATE[" . $error_info[0] . "]: General error " . $error_info[1] . ": " . $error_info[2]);
  180. $exception->errorInfo = $error_info;
  181. throw $exception;
  182. }
  183. /**
  184. * Grab a PDOStatement object from a given query and its arguments.
  185. *
  186. * Some drivers (including SQLite) will need to perform some preparation
  187. * themselves to get the statement right.
  188. *
  189. * @param $query
  190. * The query.
  191. * @param array $args
  192. * An array of arguments.
  193. * @return PDOStatement
  194. * A PDOStatement object.
  195. */
  196. protected function getStatement($query, &$args = array()) {
  197. return $this->dbh->prepare($query);
  198. }
  199. /**
  200. * Return the object's SQL query string.
  201. */
  202. public function getQueryString() {
  203. return $this->queryString;
  204. }
  205. /**
  206. * @see PDOStatement::setFetchMode()
  207. */
  208. public function setFetchMode($fetchStyle, $a2 = NULL, $a3 = NULL) {
  209. $this->defaultFetchStyle = $fetchStyle;
  210. switch ($fetchStyle) {
  211. case PDO::FETCH_CLASS:
  212. $this->defaultFetchOptions['class'] = $a2;
  213. if ($a3) {
  214. $this->defaultFetchOptions['constructor_args'] = $a3;
  215. }
  216. break;
  217. case PDO::FETCH_COLUMN:
  218. $this->defaultFetchOptions['column'] = $a2;
  219. break;
  220. case PDO::FETCH_INTO:
  221. $this->defaultFetchOptions['object'] = $a2;
  222. break;
  223. }
  224. // Set the values for the next fetch.
  225. $this->fetchStyle = $this->defaultFetchStyle;
  226. $this->fetchOptions = $this->defaultFetchOptions;
  227. }
  228. /**
  229. * Return the current row formatted according to the current fetch style.
  230. *
  231. * This is the core method of this class. It grabs the value at the current
  232. * array position in $this->data and format it according to $this->fetchStyle
  233. * and $this->fetchMode.
  234. *
  235. * @return
  236. * The current row formatted as requested.
  237. */
  238. public function current() {
  239. if (isset($this->currentRow)) {
  240. switch ($this->fetchStyle) {
  241. case PDO::FETCH_ASSOC:
  242. return $this->currentRow;
  243. case PDO::FETCH_BOTH:
  244. // PDO::FETCH_BOTH returns an array indexed by both the column name
  245. // and the column number.
  246. return $this->currentRow + array_values($this->currentRow);
  247. case PDO::FETCH_NUM:
  248. return array_values($this->currentRow);
  249. case PDO::FETCH_LAZY:
  250. // We do not do lazy as everything is fetched already. Fallback to
  251. // PDO::FETCH_OBJ.
  252. case PDO::FETCH_OBJ:
  253. return (object) $this->currentRow;
  254. case PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE:
  255. $class_name = array_unshift($this->currentRow);
  256. // Deliberate no break.
  257. case PDO::FETCH_CLASS:
  258. if (!isset($class_name)) {
  259. $class_name = $this->fetchOptions['class'];
  260. }
  261. if (count($this->fetchOptions['constructor_args'])) {
  262. $reflector = new ReflectionClass($class_name);
  263. $result = $reflector->newInstanceArgs($this->fetchOptions['constructor_args']);
  264. }
  265. else {
  266. $result = new $class_name();
  267. }
  268. foreach ($this->currentRow as $k => $v) {
  269. $result->$k = $v;
  270. }
  271. return $result;
  272. case PDO::FETCH_INTO:
  273. foreach ($this->currentRow as $k => $v) {
  274. $this->fetchOptions['object']->$k = $v;
  275. }
  276. return $this->fetchOptions['object'];
  277. case PDO::FETCH_COLUMN:
  278. if (isset($this->columnNames[$this->fetchOptions['column']])) {
  279. return $this->currentRow[$k][$this->columnNames[$this->fetchOptions['column']]];
  280. }
  281. else {
  282. return;
  283. }
  284. }
  285. }
  286. }
  287. /* Implementations of Iterator. */
  288. public function key() {
  289. return $this->currentKey;
  290. }
  291. public function rewind() {
  292. // Nothing to do: our DatabaseStatement can't be rewound.
  293. }
  294. public function next() {
  295. if (!empty($this->data)) {
  296. $this->currentRow = reset($this->data);
  297. $this->currentKey = key($this->data);
  298. unset($this->data[$this->currentKey]);
  299. }
  300. else {
  301. $this->currentRow = NULL;
  302. }
  303. }
  304. public function valid() {
  305. return isset($this->currentRow);
  306. }
  307. /* Implementations of DatabaseStatementInterface. */
  308. public function rowCount() {
  309. return $this->rowCount;
  310. }
  311. public function fetch($fetch_style = NULL, $cursor_orientation = PDO::FETCH_ORI_NEXT, $cursor_offset = NULL) {
  312. if (isset($this->currentRow)) {
  313. // Set the fetch parameter.
  314. $this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
  315. $this->fetchOptions = $this->defaultFetchOptions;
  316. // Grab the row in the format specified above.
  317. $return = $this->current();
  318. // Advance the cursor.
  319. $this->next();
  320. // Reset the fetch parameters to the value stored using setFetchMode().
  321. $this->fetchStyle = $this->defaultFetchStyle;
  322. $this->fetchOptions = $this->defaultFetchOptions;
  323. return $return;
  324. }
  325. else {
  326. return FALSE;
  327. }
  328. }
  329. public function fetchColumn($index = 0) {
  330. if (isset($this->currentRow) && isset($this->columnNames[$index])) {
  331. // We grab the value directly from $this->data, and format it.
  332. $return = $this->currentRow[$this->columnNames[$index]];
  333. $this->next();
  334. return $return;
  335. }
  336. else {
  337. return FALSE;
  338. }
  339. }
  340. public function fetchField($index = 0) {
  341. return $this->fetchColumn($index);
  342. }
  343. public function fetchObject($class_name = NULL, $constructor_args = array()) {
  344. if (isset($this->currentRow)) {
  345. if (!isset($class_name)) {
  346. // Directly cast to an object to avoid a function call.
  347. $result = (object) $this->currentRow;
  348. }
  349. else {
  350. $this->fetchStyle = PDO::FETCH_CLASS;
  351. $this->fetchOptions = array('constructor_args' => $constructor_args);
  352. // Grab the row in the format specified above.
  353. $result = $this->current();
  354. // Reset the fetch parameters to the value stored using setFetchMode().
  355. $this->fetchStyle = $this->defaultFetchStyle;
  356. $this->fetchOptions = $this->defaultFetchOptions;
  357. }
  358. $this->next();
  359. return $result;
  360. }
  361. else {
  362. return FALSE;
  363. }
  364. }
  365. public function fetchAssoc() {
  366. if (isset($this->currentRow)) {
  367. $result = $this->currentRow;
  368. $this->next();
  369. return $result;
  370. }
  371. else {
  372. return FALSE;
  373. }
  374. }
  375. public function fetchAll($fetch_style = NULL, $fetch_column = NULL, $constructor_args = NULL) {
  376. $this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
  377. $this->fetchOptions = $this->defaultFetchOptions;
  378. if (isset($fetch_column)) {
  379. $this->fetchOptions['column'] = $fetch_column;
  380. }
  381. if (isset($constructor_args)) {
  382. $this->fetchOptions['constructor_args'] = $constructor_args;
  383. }
  384. $result = array();
  385. // Traverse the array as PHP would have done.
  386. while (isset($this->currentRow)) {
  387. // Grab the row in the format specified above.
  388. $result[] = $this->current();
  389. $this->next();
  390. }
  391. // Reset the fetch parameters to the value stored using setFetchMode().
  392. $this->fetchStyle = $this->defaultFetchStyle;
  393. $this->fetchOptions = $this->defaultFetchOptions;
  394. return $result;
  395. }
  396. public function fetchCol($index = 0) {
  397. if (isset($this->columnNames[$index])) {
  398. $column = $this->columnNames[$index];
  399. $result = array();
  400. // Traverse the array as PHP would have done.
  401. while (isset($this->currentRow)) {
  402. $result[] = $this->currentRow[$this->columnNames[$index]];
  403. $this->next();
  404. }
  405. return $result;
  406. }
  407. else {
  408. return array();
  409. }
  410. }
  411. public function fetchAllKeyed($key_index = 0, $value_index = 1) {
  412. if (!isset($this->columnNames[$key_index]) || !isset($this->columnNames[$value_index]))
  413. return array();
  414. $key = $this->columnNames[$key_index];
  415. $value = $this->columnNames[$value_index];
  416. $result = array();
  417. // Traverse the array as PHP would have done.
  418. while (isset($this->currentRow)) {
  419. $result[$this->currentRow[$key]] = $this->currentRow[$value];
  420. $this->next();
  421. }
  422. return $result;
  423. }
  424. public function fetchAllAssoc($key, $fetch_style = NULL) {
  425. $this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
  426. $this->fetchOptions = $this->defaultFetchOptions;
  427. $result = array();
  428. // Traverse the array as PHP would have done.
  429. while (isset($this->currentRow)) {
  430. // Grab the row in its raw PDO::FETCH_ASSOC format.
  431. $row = $this->currentRow;
  432. // Grab the row in the format specified above.
  433. $result_row = $this->current();
  434. $result[$this->currentRow[$key]] = $result_row;
  435. $this->next();
  436. }
  437. // Reset the fetch parameters to the value stored using setFetchMode().
  438. $this->fetchStyle = $this->defaultFetchStyle;
  439. $this->fetchOptions = $this->defaultFetchOptions;
  440. return $result;
  441. }
  442. }
  443. /**
  444. * @} End of "addtogroup database".
  445. */