Connection.php 41 KB

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