Connection.php 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308
  1. <?php
  2. /*
  3. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  4. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  9. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  10. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  11. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  12. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  13. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14. *
  15. * This software consists of voluntary contributions made by many individuals
  16. * and is licensed under the MIT license. For more information, see
  17. * <http://www.doctrine-project.org>.
  18. */
  19. namespace Doctrine\DBAL;
  20. use PDO, Closure, Exception,
  21. Doctrine\DBAL\Types\Type,
  22. Doctrine\DBAL\Driver\Connection as DriverConnection,
  23. Doctrine\Common\EventManager,
  24. Doctrine\DBAL\DBALException,
  25. Doctrine\DBAL\Cache\ResultCacheStatement,
  26. Doctrine\DBAL\Cache\QueryCacheProfile,
  27. Doctrine\DBAL\Cache\ArrayStatement,
  28. Doctrine\DBAL\Cache\CacheException;
  29. /**
  30. * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like
  31. * events, transaction isolation levels, configuration, emulated transaction nesting,
  32. * lazy connecting and more.
  33. *
  34. *
  35. * @link www.doctrine-project.org
  36. * @since 2.0
  37. * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
  38. * @author Jonathan Wage <jonwage@gmail.com>
  39. * @author Roman Borschel <roman@code-factory.org>
  40. * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
  41. * @author Lukas Smith <smith@pooteeweet.org> (MDB2 library)
  42. * @author Benjamin Eberlei <kontakt@beberlei.de>
  43. */
  44. class Connection implements DriverConnection
  45. {
  46. /**
  47. * Constant for transaction isolation level READ UNCOMMITTED.
  48. */
  49. const TRANSACTION_READ_UNCOMMITTED = 1;
  50. /**
  51. * Constant for transaction isolation level READ COMMITTED.
  52. */
  53. const TRANSACTION_READ_COMMITTED = 2;
  54. /**
  55. * Constant for transaction isolation level REPEATABLE READ.
  56. */
  57. const TRANSACTION_REPEATABLE_READ = 3;
  58. /**
  59. * Constant for transaction isolation level SERIALIZABLE.
  60. */
  61. const TRANSACTION_SERIALIZABLE = 4;
  62. /**
  63. * Represents an array of ints to be expanded by Doctrine SQL parsing.
  64. *
  65. * @var int
  66. */
  67. const PARAM_INT_ARRAY = 101;
  68. /**
  69. * Represents an array of strings to be expanded by Doctrine SQL parsing.
  70. *
  71. * @var int
  72. */
  73. const PARAM_STR_ARRAY = 102;
  74. /**
  75. * Offset by which PARAM_* constants are detected as arrays of the param type.
  76. *
  77. * @var int
  78. */
  79. const ARRAY_PARAM_OFFSET = 100;
  80. /**
  81. * The wrapped driver connection.
  82. *
  83. * @var \Doctrine\DBAL\Driver\Connection
  84. */
  85. protected $_conn;
  86. /**
  87. * @var \Doctrine\DBAL\Configuration
  88. */
  89. protected $_config;
  90. /**
  91. * @var \Doctrine\Common\EventManager
  92. */
  93. protected $_eventManager;
  94. /**
  95. * @var \Doctrine\DBAL\Query\Expression\ExpressionBuilder
  96. */
  97. protected $_expr;
  98. /**
  99. * Whether or not a connection has been established.
  100. *
  101. * @var boolean
  102. */
  103. private $_isConnected = false;
  104. /**
  105. * The transaction nesting level.
  106. *
  107. * @var integer
  108. */
  109. private $_transactionNestingLevel = 0;
  110. /**
  111. * The currently active transaction isolation level.
  112. *
  113. * @var integer
  114. */
  115. private $_transactionIsolationLevel;
  116. /**
  117. * If nested transations should use savepoints
  118. *
  119. * @var integer
  120. */
  121. private $_nestTransactionsWithSavepoints;
  122. /**
  123. * The parameters used during creation of the Connection instance.
  124. *
  125. * @var array
  126. */
  127. private $_params = array();
  128. /**
  129. * The DatabasePlatform object that provides information about the
  130. * database platform used by the connection.
  131. *
  132. * @var \Doctrine\DBAL\Platforms\AbstractPlatform
  133. */
  134. protected $_platform;
  135. /**
  136. * The schema manager.
  137. *
  138. * @var \Doctrine\DBAL\Schema\AbstractSchemaManager
  139. */
  140. protected $_schemaManager;
  141. /**
  142. * The used DBAL driver.
  143. *
  144. * @var \Doctrine\DBAL\Driver
  145. */
  146. protected $_driver;
  147. /**
  148. * Flag that indicates whether the current transaction is marked for rollback only.
  149. *
  150. * @var boolean
  151. */
  152. private $_isRollbackOnly = false;
  153. private $_defaultFetchMode = PDO::FETCH_ASSOC;
  154. /**
  155. * Initializes a new instance of the Connection class.
  156. *
  157. * @param array $params The connection parameters.
  158. * @param Driver $driver
  159. * @param Configuration $config
  160. * @param EventManager $eventManager
  161. */
  162. public function __construct(array $params, Driver $driver, Configuration $config = null,
  163. EventManager $eventManager = null)
  164. {
  165. $this->_driver = $driver;
  166. $this->_params = $params;
  167. if (isset($params['pdo'])) {
  168. $this->_conn = $params['pdo'];
  169. $this->_isConnected = true;
  170. }
  171. // Create default config and event manager if none given
  172. if ( ! $config) {
  173. $config = new Configuration();
  174. }
  175. if ( ! $eventManager) {
  176. $eventManager = new EventManager();
  177. }
  178. $this->_config = $config;
  179. $this->_eventManager = $eventManager;
  180. $this->_expr = new Query\Expression\ExpressionBuilder($this);
  181. if ( ! isset($params['platform'])) {
  182. $this->_platform = $driver->getDatabasePlatform();
  183. } else if ($params['platform'] instanceof Platforms\AbstractPlatform) {
  184. $this->_platform = $params['platform'];
  185. } else {
  186. throw DBALException::invalidPlatformSpecified();
  187. }
  188. $this->_platform->setEventManager($eventManager);
  189. $this->_transactionIsolationLevel = $this->_platform->getDefaultTransactionIsolationLevel();
  190. }
  191. /**
  192. * Gets the parameters used during instantiation.
  193. *
  194. * @return array $params
  195. */
  196. public function getParams()
  197. {
  198. return $this->_params;
  199. }
  200. /**
  201. * Gets the name of the database this Connection is connected to.
  202. *
  203. * @return string $database
  204. */
  205. public function getDatabase()
  206. {
  207. return $this->_driver->getDatabase($this);
  208. }
  209. /**
  210. * Gets the hostname of the currently connected database.
  211. *
  212. * @return string
  213. */
  214. public function getHost()
  215. {
  216. return isset($this->_params['host']) ? $this->_params['host'] : null;
  217. }
  218. /**
  219. * Gets the port of the currently connected database.
  220. *
  221. * @return mixed
  222. */
  223. public function getPort()
  224. {
  225. return isset($this->_params['port']) ? $this->_params['port'] : null;
  226. }
  227. /**
  228. * Gets the username used by this connection.
  229. *
  230. * @return string
  231. */
  232. public function getUsername()
  233. {
  234. return isset($this->_params['user']) ? $this->_params['user'] : null;
  235. }
  236. /**
  237. * Gets the password used by this connection.
  238. *
  239. * @return string
  240. */
  241. public function getPassword()
  242. {
  243. return isset($this->_params['password']) ? $this->_params['password'] : null;
  244. }
  245. /**
  246. * Gets the DBAL driver instance.
  247. *
  248. * @return \Doctrine\DBAL\Driver
  249. */
  250. public function getDriver()
  251. {
  252. return $this->_driver;
  253. }
  254. /**
  255. * Gets the Configuration used by the Connection.
  256. *
  257. * @return \Doctrine\DBAL\Configuration
  258. */
  259. public function getConfiguration()
  260. {
  261. return $this->_config;
  262. }
  263. /**
  264. * Gets the EventManager used by the Connection.
  265. *
  266. * @return \Doctrine\Common\EventManager
  267. */
  268. public function getEventManager()
  269. {
  270. return $this->_eventManager;
  271. }
  272. /**
  273. * Gets the DatabasePlatform for the connection.
  274. *
  275. * @return \Doctrine\DBAL\Platforms\AbstractPlatform
  276. */
  277. public function getDatabasePlatform()
  278. {
  279. return $this->_platform;
  280. }
  281. /**
  282. * Gets the ExpressionBuilder for the connection.
  283. *
  284. * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder
  285. */
  286. public function getExpressionBuilder()
  287. {
  288. return $this->_expr;
  289. }
  290. /**
  291. * Establishes the connection with the database.
  292. *
  293. * @return boolean TRUE if the connection was successfully established, FALSE if
  294. * the connection is already open.
  295. */
  296. public function connect()
  297. {
  298. if ($this->_isConnected) return false;
  299. $driverOptions = isset($this->_params['driverOptions']) ?
  300. $this->_params['driverOptions'] : array();
  301. $user = isset($this->_params['user']) ? $this->_params['user'] : null;
  302. $password = isset($this->_params['password']) ?
  303. $this->_params['password'] : null;
  304. $this->_conn = $this->_driver->connect($this->_params, $user, $password, $driverOptions);
  305. $this->_isConnected = true;
  306. if ($this->_eventManager->hasListeners(Events::postConnect)) {
  307. $eventArgs = new Event\ConnectionEventArgs($this);
  308. $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs);
  309. }
  310. return true;
  311. }
  312. /**
  313. * setFetchMode
  314. *
  315. * @param integer $fetchMode
  316. */
  317. public function setFetchMode($fetchMode)
  318. {
  319. $this->_defaultFetchMode = $fetchMode;
  320. }
  321. /**
  322. * Prepares and executes an SQL query and returns the first row of the result
  323. * as an associative array.
  324. *
  325. * @param string $statement The SQL query.
  326. * @param array $params The query parameters.
  327. * @return array
  328. */
  329. public function fetchAssoc($statement, array $params = array())
  330. {
  331. return $this->executeQuery($statement, $params)->fetch(PDO::FETCH_ASSOC);
  332. }
  333. /**
  334. * Prepares and executes an SQL query and returns the first row of the result
  335. * as a numerically indexed array.
  336. *
  337. * @param string $statement sql query to be executed
  338. * @param array $params prepared statement params
  339. * @return array
  340. */
  341. public function fetchArray($statement, array $params = array())
  342. {
  343. return $this->executeQuery($statement, $params)->fetch(PDO::FETCH_NUM);
  344. }
  345. /**
  346. * Prepares and executes an SQL query and returns the value of a single column
  347. * of the first row of the result.
  348. *
  349. * @param string $statement sql query to be executed
  350. * @param array $params prepared statement params
  351. * @param int $colnum 0-indexed column number to retrieve
  352. * @return mixed
  353. */
  354. public function fetchColumn($statement, array $params = array(), $colnum = 0)
  355. {
  356. return $this->executeQuery($statement, $params)->fetchColumn($colnum);
  357. }
  358. /**
  359. * Whether an actual connection to the database is established.
  360. *
  361. * @return boolean
  362. */
  363. public function isConnected()
  364. {
  365. return $this->_isConnected;
  366. }
  367. /**
  368. * Checks whether a transaction is currently active.
  369. *
  370. * @return boolean TRUE if a transaction is currently active, FALSE otherwise.
  371. */
  372. public function isTransactionActive()
  373. {
  374. return $this->_transactionNestingLevel > 0;
  375. }
  376. /**
  377. * Executes an SQL DELETE statement on a table.
  378. *
  379. * @param string $tableName The name of the table on which to delete.
  380. * @param array $identifier The deletion criteria. An associative array containing column-value pairs.
  381. * @return integer The number of affected rows.
  382. */
  383. public function delete($tableName, array $identifier)
  384. {
  385. $this->connect();
  386. $criteria = array();
  387. foreach (array_keys($identifier) as $columnName) {
  388. $criteria[] = $columnName . ' = ?';
  389. }
  390. $query = 'DELETE FROM ' . $tableName . ' WHERE ' . implode(' AND ', $criteria);
  391. return $this->executeUpdate($query, array_values($identifier));
  392. }
  393. /**
  394. * Closes the connection.
  395. *
  396. * @return void
  397. */
  398. public function close()
  399. {
  400. unset($this->_conn);
  401. $this->_isConnected = false;
  402. }
  403. /**
  404. * Sets the transaction isolation level.
  405. *
  406. * @param integer $level The level to set.
  407. * @return integer
  408. */
  409. public function setTransactionIsolation($level)
  410. {
  411. $this->_transactionIsolationLevel = $level;
  412. return $this->executeUpdate($this->_platform->getSetTransactionIsolationSQL($level));
  413. }
  414. /**
  415. * Gets the currently active transaction isolation level.
  416. *
  417. * @return integer The current transaction isolation level.
  418. */
  419. public function getTransactionIsolation()
  420. {
  421. return $this->_transactionIsolationLevel;
  422. }
  423. /**
  424. * Executes an SQL UPDATE statement on a table.
  425. *
  426. * @param string $tableName The name of the table to update.
  427. * @param array $data
  428. * @param array $identifier The update criteria. An associative array containing column-value pairs.
  429. * @param array $types Types of the merged $data and $identifier arrays in that order.
  430. * @return integer The number of affected rows.
  431. */
  432. public function update($tableName, array $data, array $identifier, array $types = array())
  433. {
  434. $this->connect();
  435. $set = array();
  436. foreach ($data as $columnName => $value) {
  437. $set[] = $columnName . ' = ?';
  438. }
  439. $params = array_merge(array_values($data), array_values($identifier));
  440. $sql = 'UPDATE ' . $tableName . ' SET ' . implode(', ', $set)
  441. . ' WHERE ' . implode(' = ? AND ', array_keys($identifier))
  442. . ' = ?';
  443. return $this->executeUpdate($sql, $params, $types);
  444. }
  445. /**
  446. * Inserts a table row with specified data.
  447. *
  448. * @param string $tableName The name of the table to insert data into.
  449. * @param array $data An associative array containing column-value pairs.
  450. * @param array $types Types of the inserted data.
  451. * @return integer The number of affected rows.
  452. */
  453. public function insert($tableName, array $data, array $types = array())
  454. {
  455. $this->connect();
  456. // column names are specified as array keys
  457. $cols = array();
  458. $placeholders = array();
  459. foreach ($data as $columnName => $value) {
  460. $cols[] = $columnName;
  461. $placeholders[] = '?';
  462. }
  463. $query = 'INSERT INTO ' . $tableName
  464. . ' (' . implode(', ', $cols) . ')'
  465. . ' VALUES (' . implode(', ', $placeholders) . ')';
  466. return $this->executeUpdate($query, array_values($data), $types);
  467. }
  468. /**
  469. * Quote a string so it can be safely used as a table or column name, even if
  470. * it is a reserved name.
  471. *
  472. * Delimiting style depends on the underlying database platform that is being used.
  473. *
  474. * NOTE: Just because you CAN use quoted identifiers does not mean
  475. * you SHOULD use them. In general, they end up causing way more
  476. * problems than they solve.
  477. *
  478. * @param string $str The name to be quoted.
  479. * @return string The quoted name.
  480. */
  481. public function quoteIdentifier($str)
  482. {
  483. return $this->_platform->quoteIdentifier($str);
  484. }
  485. /**
  486. * Quotes a given input parameter.
  487. *
  488. * @param mixed $input Parameter to be quoted.
  489. * @param string $type Type of the parameter.
  490. * @return string The quoted parameter.
  491. */
  492. public function quote($input, $type = null)
  493. {
  494. $this->connect();
  495. list($value, $bindingType) = $this->getBindingInfo($input, $type);
  496. return $this->_conn->quote($value, $bindingType);
  497. }
  498. /**
  499. * Prepares and executes an SQL query and returns the result as an associative array.
  500. *
  501. * @param string $sql The SQL query.
  502. * @param array $params The query parameters.
  503. * @return array
  504. */
  505. public function fetchAll($sql, array $params = array())
  506. {
  507. return $this->executeQuery($sql, $params)->fetchAll();
  508. }
  509. /**
  510. * Prepares an SQL statement.
  511. *
  512. * @param string $statement The SQL statement to prepare.
  513. * @return \Doctrine\DBAL\Driver\Statement The prepared statement.
  514. */
  515. public function prepare($statement)
  516. {
  517. $this->connect();
  518. try {
  519. $stmt = new Statement($statement, $this);
  520. } catch (\Exception $ex) {
  521. throw DBALException::driverExceptionDuringQuery($ex, $statement);
  522. }
  523. $stmt->setFetchMode($this->_defaultFetchMode);
  524. return $stmt;
  525. }
  526. /**
  527. * Executes an, optionally parameterized, SQL query.
  528. *
  529. * If the query is parameterized, a prepared statement is used.
  530. * If an SQLLogger is configured, the execution is logged.
  531. *
  532. * @param string $query The SQL query to execute.
  533. * @param array $params The parameters to bind to the query, if any.
  534. * @param array $types The types the previous parameters are in.
  535. * @param QueryCacheProfile $qcp
  536. * @return \Doctrine\DBAL\Driver\Statement The executed statement.
  537. * @internal PERF: Directly prepares a driver statement, not a wrapper.
  538. */
  539. public function executeQuery($query, array $params = array(), $types = array(), QueryCacheProfile $qcp = null)
  540. {
  541. if ($qcp !== null) {
  542. return $this->executeCacheQuery($query, $params, $types, $qcp);
  543. }
  544. $this->connect();
  545. $logger = $this->_config->getSQLLogger();
  546. if ($logger) {
  547. $logger->startQuery($query, $params, $types);
  548. }
  549. try {
  550. if ($params) {
  551. list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
  552. $stmt = $this->_conn->prepare($query);
  553. if ($types) {
  554. $this->_bindTypedValues($stmt, $params, $types);
  555. $stmt->execute();
  556. } else {
  557. $stmt->execute($params);
  558. }
  559. } else {
  560. $stmt = $this->_conn->query($query);
  561. }
  562. } catch (\Exception $ex) {
  563. throw DBALException::driverExceptionDuringQuery($ex, $query, $this->resolveParams($params, $types));
  564. }
  565. $stmt->setFetchMode($this->_defaultFetchMode);
  566. if ($logger) {
  567. $logger->stopQuery();
  568. }
  569. return $stmt;
  570. }
  571. /**
  572. * Execute a caching query and
  573. *
  574. * @param string $query
  575. * @param array $params
  576. * @param array $types
  577. * @param QueryCacheProfile $qcp
  578. * @return \Doctrine\DBAL\Driver\ResultStatement
  579. */
  580. public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp)
  581. {
  582. $resultCache = $qcp->getResultCacheDriver() ?: $this->_config->getResultCacheImpl();
  583. if ( ! $resultCache) {
  584. throw CacheException::noResultDriverConfigured();
  585. }
  586. list($cacheKey, $realKey) = $qcp->generateCacheKeys($query, $params, $types);
  587. // fetch the row pointers entry
  588. if ($data = $resultCache->fetch($cacheKey)) {
  589. // is the real key part of this row pointers map or is the cache only pointing to other cache keys?
  590. if (isset($data[$realKey])) {
  591. $stmt = new ArrayStatement($data[$realKey]);
  592. } else if (array_key_exists($realKey, $data)) {
  593. $stmt = new ArrayStatement(array());
  594. }
  595. }
  596. if (!isset($stmt)) {
  597. $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime());
  598. }
  599. $stmt->setFetchMode($this->_defaultFetchMode);
  600. return $stmt;
  601. }
  602. /**
  603. * Executes an, optionally parameterized, SQL query and returns the result,
  604. * applying a given projection/transformation function on each row of the result.
  605. *
  606. * @param string $query The SQL query to execute.
  607. * @param array $params The parameters, if any.
  608. * @param Closure $mapper The transformation function that is applied on each row.
  609. * The function receives a single paramater, an array, that
  610. * represents a row of the result set.
  611. * @return mixed The projected result of the query.
  612. */
  613. public function project($query, array $params, Closure $function)
  614. {
  615. $result = array();
  616. $stmt = $this->executeQuery($query, $params ?: array());
  617. while ($row = $stmt->fetch()) {
  618. $result[] = $function($row);
  619. }
  620. $stmt->closeCursor();
  621. return $result;
  622. }
  623. /**
  624. * Executes an SQL statement, returning a result set as a Statement object.
  625. *
  626. * @param string $statement
  627. * @param integer $fetchType
  628. * @return \Doctrine\DBAL\Driver\Statement
  629. */
  630. public function query()
  631. {
  632. $this->connect();
  633. $args = func_get_args();
  634. $logger = $this->_config->getSQLLogger();
  635. if ($logger) {
  636. $logger->startQuery($args[0]);
  637. }
  638. try {
  639. $statement = call_user_func_array(array($this->_conn, 'query'), $args);
  640. } catch (\Exception $ex) {
  641. throw DBALException::driverExceptionDuringQuery($ex, func_get_arg(0));
  642. }
  643. $statement->setFetchMode($this->_defaultFetchMode);
  644. if ($logger) {
  645. $logger->stopQuery();
  646. }
  647. return $statement;
  648. }
  649. /**
  650. * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters
  651. * and returns the number of affected rows.
  652. *
  653. * This method supports PDO binding types as well as DBAL mapping types.
  654. *
  655. * @param string $query The SQL query.
  656. * @param array $params The query parameters.
  657. * @param array $types The parameter types.
  658. * @return integer The number of affected rows.
  659. * @internal PERF: Directly prepares a driver statement, not a wrapper.
  660. */
  661. public function executeUpdate($query, array $params = array(), array $types = array())
  662. {
  663. $this->connect();
  664. $logger = $this->_config->getSQLLogger();
  665. if ($logger) {
  666. $logger->startQuery($query, $params, $types);
  667. }
  668. try {
  669. if ($params) {
  670. list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types);
  671. $stmt = $this->_conn->prepare($query);
  672. if ($types) {
  673. $this->_bindTypedValues($stmt, $params, $types);
  674. $stmt->execute();
  675. } else {
  676. $stmt->execute($params);
  677. }
  678. $result = $stmt->rowCount();
  679. } else {
  680. $result = $this->_conn->exec($query);
  681. }
  682. } catch (\Exception $ex) {
  683. throw DBALException::driverExceptionDuringQuery($ex, $query, $this->resolveParams($params, $types));
  684. }
  685. if ($logger) {
  686. $logger->stopQuery();
  687. }
  688. return $result;
  689. }
  690. /**
  691. * Execute an SQL statement and return the number of affected rows.
  692. *
  693. * @param string $statement
  694. * @return integer The number of affected rows.
  695. */
  696. public function exec($statement)
  697. {
  698. $this->connect();
  699. $logger = $this->_config->getSQLLogger();
  700. if ($logger) {
  701. $logger->startQuery($statement);
  702. }
  703. try {
  704. $result = $this->_conn->exec($statement);
  705. } catch (\Exception $ex) {
  706. throw DBALException::driverExceptionDuringQuery($ex, $statement);
  707. }
  708. if ($logger) {
  709. $logger->stopQuery();
  710. }
  711. return $result;
  712. }
  713. /**
  714. * Returns the current transaction nesting level.
  715. *
  716. * @return integer The nesting level. A value of 0 means there's no active transaction.
  717. */
  718. public function getTransactionNestingLevel()
  719. {
  720. return $this->_transactionNestingLevel;
  721. }
  722. /**
  723. * Fetch the SQLSTATE associated with the last database operation.
  724. *
  725. * @return integer The last error code.
  726. */
  727. public function errorCode()
  728. {
  729. $this->connect();
  730. return $this->_conn->errorCode();
  731. }
  732. /**
  733. * Fetch extended error information associated with the last database operation.
  734. *
  735. * @return array The last error information.
  736. */
  737. public function errorInfo()
  738. {
  739. $this->connect();
  740. return $this->_conn->errorInfo();
  741. }
  742. /**
  743. * Returns the ID of the last inserted row, or the last value from a sequence object,
  744. * depending on the underlying driver.
  745. *
  746. * Note: This method may not return a meaningful or consistent result across different drivers,
  747. * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY
  748. * columns or sequences.
  749. *
  750. * @param string $seqName Name of the sequence object from which the ID should be returned.
  751. * @return string A string representation of the last inserted ID.
  752. */
  753. public function lastInsertId($seqName = null)
  754. {
  755. $this->connect();
  756. return $this->_conn->lastInsertId($seqName);
  757. }
  758. /**
  759. * Executes a function in a transaction.
  760. *
  761. * The function gets passed this Connection instance as an (optional) parameter.
  762. *
  763. * If an exception occurs during execution of the function or transaction commit,
  764. * the transaction is rolled back and the exception re-thrown.
  765. *
  766. * @param Closure $func The function to execute transactionally.
  767. */
  768. public function transactional(Closure $func)
  769. {
  770. $this->beginTransaction();
  771. try {
  772. $func($this);
  773. $this->commit();
  774. } catch (Exception $e) {
  775. $this->rollback();
  776. throw $e;
  777. }
  778. }
  779. /**
  780. * Set if nested transactions should use savepoints
  781. *
  782. * @param boolean $nestTransactionsWithSavepoints
  783. * @return void
  784. */
  785. public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints)
  786. {
  787. if ($this->_transactionNestingLevel > 0) {
  788. throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction();
  789. }
  790. if ( ! $this->_platform->supportsSavepoints()) {
  791. throw ConnectionException::savepointsNotSupported();
  792. }
  793. $this->_nestTransactionsWithSavepoints = $nestTransactionsWithSavepoints;
  794. }
  795. /**
  796. * Get if nested transactions should use savepoints
  797. *
  798. * @return boolean
  799. */
  800. public function getNestTransactionsWithSavepoints()
  801. {
  802. return $this->_nestTransactionsWithSavepoints;
  803. }
  804. /**
  805. * Returns the savepoint name to use for nested transactions are false if they are not supported
  806. * "savepointFormat" parameter is not set
  807. *
  808. * @return mixed a string with the savepoint name or false
  809. */
  810. protected function _getNestedTransactionSavePointName()
  811. {
  812. return 'DOCTRINE2_SAVEPOINT_'.$this->_transactionNestingLevel;
  813. }
  814. /**
  815. * Starts a transaction by suspending auto-commit mode.
  816. *
  817. * @return void
  818. */
  819. public function beginTransaction()
  820. {
  821. $this->connect();
  822. ++$this->_transactionNestingLevel;
  823. $logger = $this->_config->getSQLLogger();
  824. if ($this->_transactionNestingLevel == 1) {
  825. if ($logger) {
  826. $logger->startQuery('"START TRANSACTION"');
  827. }
  828. $this->_conn->beginTransaction();
  829. if ($logger) {
  830. $logger->stopQuery();
  831. }
  832. } else if ($this->_nestTransactionsWithSavepoints) {
  833. if ($logger) {
  834. $logger->startQuery('"SAVEPOINT"');
  835. }
  836. $this->createSavepoint($this->_getNestedTransactionSavePointName());
  837. if ($logger) {
  838. $logger->stopQuery();
  839. }
  840. }
  841. }
  842. /**
  843. * Commits the current transaction.
  844. *
  845. * @return void
  846. * @throws ConnectionException If the commit failed due to no active transaction or
  847. * because the transaction was marked for rollback only.
  848. */
  849. public function commit()
  850. {
  851. if ($this->_transactionNestingLevel == 0) {
  852. throw ConnectionException::noActiveTransaction();
  853. }
  854. if ($this->_isRollbackOnly) {
  855. throw ConnectionException::commitFailedRollbackOnly();
  856. }
  857. $this->connect();
  858. $logger = $this->_config->getSQLLogger();
  859. if ($this->_transactionNestingLevel == 1) {
  860. if ($logger) {
  861. $logger->startQuery('"COMMIT"');
  862. }
  863. $this->_conn->commit();
  864. if ($logger) {
  865. $logger->stopQuery();
  866. }
  867. } else if ($this->_nestTransactionsWithSavepoints) {
  868. if ($logger) {
  869. $logger->startQuery('"RELEASE SAVEPOINT"');
  870. }
  871. $this->releaseSavepoint($this->_getNestedTransactionSavePointName());
  872. if ($logger) {
  873. $logger->stopQuery();
  874. }
  875. }
  876. --$this->_transactionNestingLevel;
  877. }
  878. /**
  879. * Cancel any database changes done during the current transaction.
  880. *
  881. * this method can be listened with onPreTransactionRollback and onTransactionRollback
  882. * eventlistener methods
  883. *
  884. * @throws ConnectionException If the rollback operation failed.
  885. */
  886. public function rollBack()
  887. {
  888. if ($this->_transactionNestingLevel == 0) {
  889. throw ConnectionException::noActiveTransaction();
  890. }
  891. $this->connect();
  892. $logger = $this->_config->getSQLLogger();
  893. if ($this->_transactionNestingLevel == 1) {
  894. if ($logger) {
  895. $logger->startQuery('"ROLLBACK"');
  896. }
  897. $this->_transactionNestingLevel = 0;
  898. $this->_conn->rollback();
  899. $this->_isRollbackOnly = false;
  900. if ($logger) {
  901. $logger->stopQuery();
  902. }
  903. } else if ($this->_nestTransactionsWithSavepoints) {
  904. if ($logger) {
  905. $logger->startQuery('"ROLLBACK TO SAVEPOINT"');
  906. }
  907. $this->rollbackSavepoint($this->_getNestedTransactionSavePointName());
  908. --$this->_transactionNestingLevel;
  909. if ($logger) {
  910. $logger->stopQuery();
  911. }
  912. } else {
  913. $this->_isRollbackOnly = true;
  914. --$this->_transactionNestingLevel;
  915. }
  916. }
  917. /**
  918. * createSavepoint
  919. * creates a new savepoint
  920. *
  921. * @param string $savepoint name of a savepoint to set
  922. * @return void
  923. */
  924. public function createSavepoint($savepoint)
  925. {
  926. if ( ! $this->_platform->supportsSavepoints()) {
  927. throw ConnectionException::savepointsNotSupported();
  928. }
  929. $this->_conn->exec($this->_platform->createSavePoint($savepoint));
  930. }
  931. /**
  932. * releaseSavePoint
  933. * releases given savepoint
  934. *
  935. * @param string $savepoint name of a savepoint to release
  936. * @return void
  937. */
  938. public function releaseSavepoint($savepoint)
  939. {
  940. if ( ! $this->_platform->supportsSavepoints()) {
  941. throw ConnectionException::savepointsNotSupported();
  942. }
  943. if ($this->_platform->supportsReleaseSavepoints()) {
  944. $this->_conn->exec($this->_platform->releaseSavePoint($savepoint));
  945. }
  946. }
  947. /**
  948. * rollbackSavePoint
  949. * releases given savepoint
  950. *
  951. * @param string $savepoint name of a savepoint to rollback to
  952. * @return void
  953. */
  954. public function rollbackSavepoint($savepoint)
  955. {
  956. if ( ! $this->_platform->supportsSavepoints()) {
  957. throw ConnectionException::savepointsNotSupported();
  958. }
  959. $this->_conn->exec($this->_platform->rollbackSavePoint($savepoint));
  960. }
  961. /**
  962. * Gets the wrapped driver connection.
  963. *
  964. * @return \Doctrine\DBAL\Driver\Connection
  965. */
  966. public function getWrappedConnection()
  967. {
  968. $this->connect();
  969. return $this->_conn;
  970. }
  971. /**
  972. * Gets the SchemaManager that can be used to inspect or change the
  973. * database schema through the connection.
  974. *
  975. * @return \Doctrine\DBAL\Schema\AbstractSchemaManager
  976. */
  977. public function getSchemaManager()
  978. {
  979. if ( ! $this->_schemaManager) {
  980. $this->_schemaManager = $this->_driver->getSchemaManager($this);
  981. }
  982. return $this->_schemaManager;
  983. }
  984. /**
  985. * Marks the current transaction so that the only possible
  986. * outcome for the transaction to be rolled back.
  987. *
  988. * @throws ConnectionException If no transaction is active.
  989. */
  990. public function setRollbackOnly()
  991. {
  992. if ($this->_transactionNestingLevel == 0) {
  993. throw ConnectionException::noActiveTransaction();
  994. }
  995. $this->_isRollbackOnly = true;
  996. }
  997. /**
  998. * Check whether the current transaction is marked for rollback only.
  999. *
  1000. * @return boolean
  1001. * @throws ConnectionException If no transaction is active.
  1002. */
  1003. public function isRollbackOnly()
  1004. {
  1005. if ($this->_transactionNestingLevel == 0) {
  1006. throw ConnectionException::noActiveTransaction();
  1007. }
  1008. return $this->_isRollbackOnly;
  1009. }
  1010. /**
  1011. * Converts a given value to its database representation according to the conversion
  1012. * rules of a specific DBAL mapping type.
  1013. *
  1014. * @param mixed $value The value to convert.
  1015. * @param string $type The name of the DBAL mapping type.
  1016. * @return mixed The converted value.
  1017. */
  1018. public function convertToDatabaseValue($value, $type)
  1019. {
  1020. return Type::getType($type)->convertToDatabaseValue($value, $this->_platform);
  1021. }
  1022. /**
  1023. * Converts a given value to its PHP representation according to the conversion
  1024. * rules of a specific DBAL mapping type.
  1025. *
  1026. * @param mixed $value The value to convert.
  1027. * @param string $type The name of the DBAL mapping type.
  1028. * @return mixed The converted type.
  1029. */
  1030. public function convertToPHPValue($value, $type)
  1031. {
  1032. return Type::getType($type)->convertToPHPValue($value, $this->_platform);
  1033. }
  1034. /**
  1035. * Binds a set of parameters, some or all of which are typed with a PDO binding type
  1036. * or DBAL mapping type, to a given statement.
  1037. *
  1038. * @param string $stmt The statement to bind the values to.
  1039. * @param array $params The map/list of named/positional parameters.
  1040. * @param array $types The parameter types (PDO binding types or DBAL mapping types).
  1041. * @internal Duck-typing used on the $stmt parameter to support driver statements as well as
  1042. * raw PDOStatement instances.
  1043. */
  1044. private function _bindTypedValues($stmt, array $params, array $types)
  1045. {
  1046. // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
  1047. if (is_int(key($params))) {
  1048. // Positional parameters
  1049. $typeOffset = array_key_exists(0, $types) ? -1 : 0;
  1050. $bindIndex = 1;
  1051. foreach ($params as $value) {
  1052. $typeIndex = $bindIndex + $typeOffset;
  1053. if (isset($types[$typeIndex])) {
  1054. $type = $types[$typeIndex];
  1055. list($value, $bindingType) = $this->getBindingInfo($value, $type);
  1056. $stmt->bindValue($bindIndex, $value, $bindingType);
  1057. } else {
  1058. $stmt->bindValue($bindIndex, $value);
  1059. }
  1060. ++$bindIndex;
  1061. }
  1062. } else {
  1063. // Named parameters
  1064. foreach ($params as $name => $value) {
  1065. if (isset($types[$name])) {
  1066. $type = $types[$name];
  1067. list($value, $bindingType) = $this->getBindingInfo($value, $type);
  1068. $stmt->bindValue($name, $value, $bindingType);
  1069. } else {
  1070. $stmt->bindValue($name, $value);
  1071. }
  1072. }
  1073. }
  1074. }
  1075. /**
  1076. * Gets the binding type of a given type. The given type can be a PDO or DBAL mapping type.
  1077. *
  1078. * @param mixed $value The value to bind
  1079. * @param mixed $type The type to bind (PDO or DBAL)
  1080. * @return array [0] => the (escaped) value, [1] => the binding type
  1081. */
  1082. private function getBindingInfo($value, $type)
  1083. {
  1084. if (is_string($type)) {
  1085. $type = Type::getType($type);
  1086. }
  1087. if ($type instanceof Type) {
  1088. $value = $type->convertToDatabaseValue($value, $this->_platform);
  1089. $bindingType = $type->getBindingType();
  1090. } else {
  1091. $bindingType = $type; // PDO::PARAM_* constants
  1092. }
  1093. return array($value, $bindingType);
  1094. }
  1095. /**
  1096. * Resolves the parameters to a format which can be displayed.
  1097. *
  1098. * @internal This is a purely internal method. If you rely on this method, you are advised to
  1099. * copy/paste the code as this method may change, or be removed without prior notice.
  1100. *
  1101. * @param array $params
  1102. * @param array $types
  1103. *
  1104. * @return array
  1105. */
  1106. public function resolveParams(array $params, array $types)
  1107. {
  1108. $resolvedParams = array();
  1109. // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO.
  1110. if (is_int(key($params))) {
  1111. // Positional parameters
  1112. $typeOffset = array_key_exists(0, $types) ? -1 : 0;
  1113. $bindIndex = 1;
  1114. foreach ($params as $value) {
  1115. $typeIndex = $bindIndex + $typeOffset;
  1116. if (isset($types[$typeIndex])) {
  1117. $type = $types[$typeIndex];
  1118. list($value,) = $this->getBindingInfo($value, $type);
  1119. $resolvedParams[$bindIndex] = $value;
  1120. } else {
  1121. $resolvedParams[$bindIndex] = $value;
  1122. }
  1123. ++$bindIndex;
  1124. }
  1125. } else {
  1126. // Named parameters
  1127. foreach ($params as $name => $value) {
  1128. if (isset($types[$name])) {
  1129. $type = $types[$name];
  1130. list($value,) = $this->getBindingInfo($value, $type);
  1131. $resolvedParams[$name] = $value;
  1132. } else {
  1133. $resolvedParams[$name] = $value;
  1134. }
  1135. }
  1136. }
  1137. return $resolvedParams;
  1138. }
  1139. /**
  1140. * Create a new instance of a SQL query builder.
  1141. *
  1142. * @return \Doctrine\DBAL\Query\QueryBuilder
  1143. */
  1144. public function createQueryBuilder()
  1145. {
  1146. return new Query\QueryBuilder($this);
  1147. }
  1148. }