Commit a550e7d7 authored by Bill Schaller's avatar Bill Schaller

Merge branch 'master' into sqlserverlimitfix-2

parents be977c13 069280e5
......@@ -8,4 +8,3 @@ build.properties export-ignore
build.xml export-ignore
phpunit.xml.dist export-ignore
run-all.sh export-ignore
composer.lock export-ignore
......@@ -3,7 +3,6 @@ logs/
reports/
dist/
download/
lib/Doctrine/Common/
vendor/
*.phpunit.xml
composer.lock
\ No newline at end of file
[submodule "lib/vendor/doctrine-common"]
path = lib/vendor/doctrine-common
url = git://github.com/doctrine/common.git
[submodule "lib/vendor/Symfony/Component/Console"]
path = lib/vendor/Symfony/Component/Console
url = git://github.com/symfony/Console.git
[submodule "lib/vendor/doctrine-build-common"]
path = lib/vendor/doctrine-build-common
url = git://github.com/doctrine/doctrine-build-common.git
[submodule "docs/en/_theme"]
path = docs/en/_theme
url = git://github.com/doctrine/doctrine-sphinx-theme.git
language: php
sudo: false
cache:
directories:
- vendor
- $HOME/.composer/cache
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
- hhvm-nightly
env:
- DB=mysql
- DB=pgsql POSTGRESQL_VERSION=9.1
- DB=pgsql POSTGRESQL_VERSION=9.2
- DB=pgsql POSTGRESQL_VERSION=9.3
- DB=pgsql POSTGRESQL_VERSION=9.4
- DB=sqlite
- DB=mysqli
before_install:
- composer self-update
install:
- composer update --prefer-source
- travis_retry composer install
before_script:
- if [ '$DB' = 'pgsql' ]; then sudo service postgresql stop; sudo service postgresql start $POSTGRESQL_VERSION; fi
......@@ -29,19 +32,73 @@ before_script:
script: ./vendor/bin/phpunit --configuration tests/travis/$DB.travis.xml
matrix:
allow_failures:
fast_finish: true
include:
- php: 5.4
env: DB=mariadb
addons:
mariadb: 5.5
- php: 5.5
env: DB=mariadb
addons:
mariadb: 5.5
- php: 5.6
env: DB=mariadb
addons:
mariadb: 5.5
- php: 7.0
env: DB=mariadb
addons:
mariadb: 5.5
- php: hhvm
env: DB=pgsql POSTGRESQL_VERSION=9.1
- php: hhvm
env: DB=pgsql POSTGRESQL_VERSION=9.2
env: DB=mariadb
addons:
mariadb: 5.5
- php: 5.4
env: DB=mariadb
addons:
mariadb: 10.0
- php: 5.5
env: DB=mariadb
addons:
mariadb: 10.0
- php: 5.6
env: DB=mariadb
addons:
mariadb: 10.0
- php: 7.0
env: DB=mariadb
addons:
mariadb: 10.0
- php: hhvm
env: DB=pgsql POSTGRESQL_VERSION=9.3
env: DB=mariadb
addons:
mariadb: 10.0
- php: 5.4
env: DB=mariadb
addons:
mariadb: 10.1
- php: 5.5
env: DB=mariadb
addons:
mariadb: 10.1
- php: 5.6
env: DB=mariadb
addons:
mariadb: 10.1
- php: 7.0
env: DB=mariadb
addons:
mariadb: 10.1
- php: hhvm
env: DB=sqlite
env: DB=mariadb
addons:
mariadb: 10.1
allow_failures:
- php: 7.0
- php: hhvm
env: DB=mysql
- php: hhvm-nightly
exclude:
- php: hhvm
env: DB=pgsql POSTGRESQL_VERSION=9.1 # driver currently unsupported by HHVM
......@@ -49,9 +106,8 @@ matrix:
env: DB=pgsql POSTGRESQL_VERSION=9.2 # driver currently unsupported by HHVM
- php: hhvm
env: DB=pgsql POSTGRESQL_VERSION=9.3 # driver currently unsupported by HHVM
- php: hhvm-nightly
env: DB=pgsql POSTGRESQL_VERSION=9.1 # driver currently unsupported by HHVM
- php: hhvm-nightly
env: DB=pgsql POSTGRESQL_VERSION=9.2 # driver currently unsupported by HHVM
- php: hhvm-nightly
env: DB=pgsql POSTGRESQL_VERSION=9.3 # driver currently unsupported by HHVM
- php: hhvm
env: DB=pgsql POSTGRESQL_VERSION=9.4 # driver currently unsupported by HHVM
addons:
postgresql: '9.4'
# Doctrine DBAL
| [Master][Master] | [2.5][2.5] | [2.4][2.4] |
|:----------------:|:----------:|:----------:|
| [![Build status][Master image]][Master] | [![Build status][2.5 image]][2.5] | [![Build status][2.4 image]][2.4] |
Powerful database abstraction layer with many features for database schema introspection, schema management and PDO abstraction.
* Master: [![Build Status](https://secure.travis-ci.org/doctrine/dbal.png?branch=master)](http://travis-ci.org/doctrine/dbal) [![Dependency Status](https://www.versioneye.com/php/doctrine:dbal/dev-master/badge.png)](https://www.versioneye.com/php/doctrine:dbal/dev-master)
* 2.5: [![Build Status](https://secure.travis-ci.org/doctrine/dbal.png?branch=2.5)](http://travis-ci.org/doctrine/dbal) [![Dependency Status](https://www.versioneye.com/php/doctrine:dbal/2.5.0/badge.png)](https://www.versioneye.com/php/doctrine:dbal/2.5.0)
* 2.4: [![Build Status](https://secure.travis-ci.org/doctrine/dbal.png?branch=2.4)](http://travis-ci.org/doctrine/dbal) [![Dependency Status](https://www.versioneye.com/php/doctrine:dbal/2.4.2/badge.png)](https://www.versioneye.com/php/doctrine:dbal/2.4.2)
* 2.3: [![Build Status](https://secure.travis-ci.org/doctrine/dbal.png?branch=2.3)](http://travis-ci.org/doctrine/dbal) [![Dependency Status](https://www.versioneye.com/php/doctrine:dbal/2.3.4/badge.png)](https://www.versioneye.com/php/doctrine:dbal/2.3.4)
## More resources:
* [Website](http://www.doctrine-project.org/projects/dbal.html)
* [Documentation](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/)
* [Issue Tracker](http://www.doctrine-project.org/jira/browse/DBAL)
* [Downloads](http://github.com/doctrine/dbal/downloads)
[Master image]: https://img.shields.io/travis/doctrine/dbal/master.svg?style=flat-square
[Master]: https://travis-ci.org/doctrine/dbal
[2.5 image]: https://img.shields.io/travis/doctrine/dbal/2.5.svg?style=flat-square
[2.5]: https://github.com/doctrine/dbal/tree/2.5
[2.4 image]: https://img.shields.io/travis/doctrine/dbal/2.4.svg?style=flat-square
[2.4]: https://github.com/doctrine/dbal/tree/2.4
......@@ -12,8 +12,8 @@
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"}
],
"require": {
"php": ">=5.3.2",
"doctrine/common": ">=2.4,<2.6-dev"
"php": ">=5.4",
"doctrine/common": "~2.4"
},
"require-dev": {
"phpunit/phpunit": "4.*",
......@@ -22,7 +22,7 @@
"suggest": {
"symfony/console": "For helpful console commands such as SQL execution and import of files."
},
"bin": ["bin/doctrine-dbal", "bin/doctrine-dbal.php"],
"bin": ["bin/doctrine-dbal"],
"autoload": {
"psr-0": { "Doctrine\\DBAL\\": "lib/" }
},
......
# Doctrine DBAL Documentation
## How to Generate
## How to Generate:
Using Ubuntu 14.04 LTS:
1. Run ./bin/install-dependencies.sh
2. Run ./bin/generate-docs.sh
It will generate the documentation into the build directory of the checkout.
\ No newline at end of file
It will generate the documentation into the build directory of the checkout.
## Theme issues
If you get a "Theme error", check if the `en/_theme` subdirectory is empty,
in which case you will need to run:
1. git submodule init
2. git submodule update
\ No newline at end of file
#!/bin/bash
sudo apt-get install python25 python25-dev texlive-full rubber
sudo easy_install pygments
sudo easy_install sphinx
\ No newline at end of file
sudo apt-get update && sudo apt-get install -y python2.7 python-sphinx python-pygments
\ No newline at end of file
......@@ -38,16 +38,16 @@ master_doc = 'index'
# General information about the project.
project = u'Doctrine DBAL'
copyright = u'2010, Roman Borschel, Guilherme Blanco, Benjamin Eberlei, Jonathan Wage'
copyright = u'2010-2015, Roman Borschel, Guilherme Blanco, Benjamin Eberlei, Jonathan Wage'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '2.1'
version = '2'
# The full version, including alpha/beta/rc tags.
release = '2.1.0'
release = '2'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
......
......@@ -254,6 +254,11 @@ pdo\_pgsql
a SSL TCP/IP connection will be negotiated with the server.
See the list of available modes:
`http://www.postgresql.org/docs/9.1/static/libpq-connect.html#LIBPQ-CONNECT-SSLMODE`
- ``sslrootcert`` (string): specifies the name of a file containing
SSL certificate authority (CA) certificate(s). If the file exists,
the server's certificate will be verified to be signed by one of these
authorities.
See http://www.postgresql.org/docs/9.0/static/libpq-connect.html#LIBPQ-CONNECT-SSLROOTCERT
PostgreSQL behaves differently with regard to booleans when you use
``PDO::ATTR_EMULATE_PREPARES`` or not. To switch from using ``'true'``
......
......@@ -327,7 +327,7 @@ returns the affected rows count:
The ``$types`` variable contains the PDO or Doctrine Type constants
to perform necessary type conversions between actual input
parameters and expected database values. See the
`Types <./types#type-conversion>`_ section for more information.
`Types <./types#mapping-matrix>`_ section for more information.
executeQuery()
~~~~~~~~~~~~~~
......@@ -351,7 +351,7 @@ parameters to the execute method, then returning the statement:
The ``$types`` variable contains the PDO or Doctrine Type constants
to perform necessary type conversions between actual input
parameters and expected database values. See the
`Types <./types#type-conversion>`_ section for more information.
`Types <./types#mapping-matrix>`_ section for more information.
fetchAll()
~~~~~~~~~~
......
......@@ -73,6 +73,15 @@ type therefore behave like the DateTime type.
Sqlite
------
Buffered Queries and Isolation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Careful if you execute a ``SELECT`` query and do not iterate over the
statements results immediately. ``UPDATE`` statements executed before iteration
affect only the rows that have not been buffered into PHP memory yet. This
breaks the SERIALIZABLE transaction isolation property that SQLite supposedly
has.
DateTime
~~~~~~~~~~
......
......@@ -55,6 +55,7 @@ PostgreSQL
- ``PostgreSqlPlatform`` for all versions.
- ``PostgreSQL91Platform`` for version 9.1 and above.
- ``PostgreSQL92Platform`` for version 9.2 and above.
- ``PostgreSQL94Platform`` for version 9.4 and above.
SAP Sybase SQL Anywhere
^^^^^^^^^^^^^^^^^^^^^^^
......
......@@ -134,7 +134,7 @@ The following options are completely vendor specific and absolutely not portable
- **charset** (string): The character set to use for the column. Currently only supported
on MySQL and Drizzle.
- **collate** (string): The collation to use for the column. Currently only supported on
SQL Server.
- **collation** (string): The collation to use for the column. Supported by MySQL, PostgreSQL,
Sqlite, SQL Server and Drizzle.
- **check** (string): The check constraint clause to add to the column.
Defaults to ``null``.
......@@ -154,21 +154,4 @@ the ``Connection#quote`` method:
$sql = "SELECT * FROM users WHERE name = " . $connection->quote($_GET['username'], \PDO::PARAM_STR);
This method is only available for SQL, not for DQL. For DQL it is always encouraged to use prepared
statements not only for security, but also for caching reasons.
Non-ASCII compatible Charsets in MySQL
--------------------------------------
Up until PHP 5.3.6 PDO has a security problem when using non ascii compatible charsets. Even if specifying
the charset using "SET NAMES", emulated prepared statements and ``PDO#quote`` could not reliably escape
values, opening up to potential SQL injections. If you are running PHP 5.3.6 you can solve this issue
by passing the driver option "charset" to Doctrine PDO MySQL driver. Using SET NAMES does not suffice!
.. code-block::
<?php
$conn = DriverManager::getConnection(array(
'driver' => 'pdo_mysql',
'charset' => 'UTF8',
));
statements not only for security, but also for caching reasons.
\ No newline at end of file
......@@ -4,7 +4,7 @@ Transactions
A ``Doctrine\DBAL\Connection`` provides a PDO-like API for
transaction management, with the methods
``Connection#beginTransaction()``, ``Connection#commit()`` and
``Connection#rollback()``.
``Connection#rollBack()``.
Transaction demarcation with the Doctrine DBAL looks as follows:
......@@ -16,7 +16,7 @@ Transaction demarcation with the Doctrine DBAL looks as follows:
// do stuff
$conn->commit();
} catch(Exception $e) {
$conn->rollback();
$conn->rollBack();
throw $e;
}
......@@ -60,9 +60,9 @@ transactions, or rather propagating transaction control up the call
stack. For that purpose, the ``Connection`` class keeps an internal
counter that represents the nesting level and is
increased/decreased as ``beginTransaction()``, ``commit()`` and
``rollback()`` are invoked. ``beginTransaction()`` increases the
``rollBack()`` are invoked. ``beginTransaction()`` increases the
nesting level whilst
``commit()`` and ``rollback()`` decrease the nesting level. The nesting level starts at 0. Whenever the nesting level transitions from 0 to 1, ``beginTransaction()`` is invoked on the underlying driver connection and whenever the nesting level transitions from 1 to 0, ``commit()`` or ``rollback()`` is invoked on the underlying driver, depending on whether the transition was caused by ``Connection#commit()`` or ``Connection#rollback()``.
``commit()`` and ``rollBack()`` decrease the nesting level. The nesting level starts at 0. Whenever the nesting level transitions from 0 to 1, ``beginTransaction()`` is invoked on the underlying driver connection and whenever the nesting level transitions from 1 to 0, ``commit()`` or ``rollBack()`` is invoked on the underlying driver, depending on whether the transition was caused by ``Connection#commit()`` or ``Connection#rollBack()``.
What this means is that transaction control is basically passed to
code higher up in the call stack and the inner transaction block is
......@@ -91,7 +91,7 @@ example:
$conn->commit(); // 2 => 1
} catch (Exception $e) {
$conn->rollback(); // 2 => 1, transaction marked for rollback only
$conn->rollBack(); // 2 => 1, transaction marked for rollback only
throw $e;
}
......@@ -99,7 +99,7 @@ example:
$conn->commit(); // 1 => 0, "real" transaction committed
} catch (Exception $e) {
$conn->rollback(); // 1 => 0, "real" transaction rollback
$conn->rollBack(); // 1 => 0, "real" transaction rollback
throw $e;
}
......@@ -120,7 +120,7 @@ to avoid nesting transaction blocks. If this is not possible
because the nested transaction blocks are in a third-party API
you're out of luck.
All that is guaruanteed to the inner transaction is that it still
All that is guaranteed to the inner transaction is that it still
happens atomically, all or nothing, the transaction just gets a
wider scope and the control is handed to the outer scope.
......@@ -136,7 +136,7 @@ wider scope and the control is handed to the outer scope.
.. warning::
Directly invoking ``PDO#beginTransaction()``,
``PDO#commit()`` or ``PDO#rollback()`` or the corresponding methods
``PDO#commit()`` or ``PDO#rollBack()`` or the corresponding methods
on the particular ``Doctrine\DBAL\Driver\Connection`` instance in
use bypasses the transparent transaction nesting that is provided
by ``Doctrine\DBAL\Connection`` and can therefore corrupt the
......@@ -152,7 +152,7 @@ transaction or directly be committed to the database.
By default a connection runs in auto-commit mode which means
that it is non-transactional unless you start a transaction explicitly
via ``beginTransaction()``. To have a connection automatically open up
a new transaction on ``connect()`` and after ``commit()`` or ``rollback()``,
a new transaction on ``connect()`` and after ``commit()`` or ``rollBack()``,
you can disable auto-commit mode with ``setAutoCommit(false)``.
::
......@@ -169,7 +169,7 @@ you can disable auto-commit mode with ``setAutoCommit(false)``.
// do stuff
$conn->commit(); // commits transaction and immediately starts a new one
} catch (\Exception $e) {
$conn->rollback(); // rolls back transaction and immediately starts a new one
$conn->rollBack(); // rolls back transaction and immediately starts a new one
}
// still transactional
......@@ -220,14 +220,14 @@ by this behaviour.
// do stuff
$conn->commit(); // commits inner transaction, does not start a new one
} catch (\Exception $e) {
$conn->rollback(); // rolls back inner transaction, does not start a new one
$conn->rollBack(); // rolls back inner transaction, does not start a new one
}
// do stuff
$conn->commit(); // commits outer transaction, and immediately starts a new one
} catch (\Exception $e) {
$conn->rollback(); // rolls back outer transaction, and immediately starts a new one
$conn->rollBack(); // rolls back outer transaction, and immediately starts a new one
}
......@@ -235,4 +235,3 @@ To initialize a ``Doctrine\DBAL\Connection`` with auto-commit disabled,
you can also use the ``Doctrine\DBAL\Configuration`` container to modify the
default auto-commit mode via ``Doctrine\DBAL\Configuration::setAutoCommit(false)``
and pass it to a ``Doctrine\DBAL\Connection`` when instantiating.
......@@ -289,6 +289,15 @@ without date, time and timezone information, you should consider using this type
Values retrieved from the database are always converted to PHP's ``\DateTime`` object
or ``null`` if no data is present.
dateinterval
^^^^^^^^^^^^
Maps and converts date and time difference data without timezone information.
If you know that the data to be stored is the difference between two date and time values,
you should consider using this type.
Values retrieved from the database are always converted to PHP's ``\DateInterval`` object
or ``null`` if no data is present.
.. note::
See the `Known Vendor Issue <./known-vendor-issues>`_ section
......@@ -689,11 +698,15 @@ Please also notice the mapping specific footnotes for additional information.
| | | | +----------------------------------------------------------+
| | | | | ``LONGTEXT`` [20]_ |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | >= 9.2 | ``JSON`` |
| | | **PostgreSQL** | < 9.2 | ``TEXT`` [1]_ |
| | | +---------+----------------------------------------------------------+
| | | | < 9.2 | ``TEXT`` [1]_ |
| | +--------------------------+---------+ |
| | | **SQL Anywhere** | *all* | |
| | | | < 9.4 | ``JSON`` |
| | | +---------+----------------------------------------------------------+
| | | | >= 9.4 | ``JSON`` [21]_ |
| | | | +----------------------------------------------------------+
| | | | | ``JSONB`` [22]_ |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Anywhere** | *all* | ``TEXT`` [1]_ |
| | +--------------------------+ | |
| | | **Drizzle** | | |
| | +--------------------------+---------+----------------------------------------------------------+
......@@ -744,7 +757,7 @@ Please also notice the mapping specific footnotes for additional information.
.. [10] Used if **unsigned** attribute is set to ``true`` in the column definition (default ``false``).
.. [11] Used if **autoincrement** attribute is set to ``true`` in the column definition (default ``false``).
.. [12] Chosen if the column definition has the **autoincrement** attribute set to ``false`` (default).
.. [13] Chosen if the column definition not contains the **version** option inside the **platformOptions**
.. [13] Chosen if the column definition does not contain the **version** option inside the **platformOptions**
attribute array or is set to ``false`` which marks it as a non-locking information column.
.. [14] Chosen if the column definition contains the **version** option inside the **platformOptions**
attribute array and is set to ``true`` which marks it as a locking information column.
......@@ -759,6 +772,10 @@ Please also notice the mapping specific footnotes for additional information.
.. [18] Chosen if the column length is less or equal to **2 ^ 16 - 1 = 65535**.
.. [19] Chosen if the column length is less or equal to **2 ^ 24 - 1 = 16777215**.
.. [20] Chosen if the column length is less or equal to **2 ^ 32 - 1 = 4294967295** or empty.
.. [21] Chosen if the column definition does not contain the **jsonb** option inside the **platformOptions**
attribute array or is set to ``false``.
.. [22] Chosen if the column definition contains the **jsonb** option inside the **platformOptions**
attribute array and is set to ``true``.
Detection of Database Types
---------------------------
......
......@@ -354,10 +354,10 @@ class Connection implements DriverConnection
}
$driverOptions = isset($this->_params['driverOptions']) ?
$this->_params['driverOptions'] : array();
$this->_params['driverOptions'] : array();
$user = isset($this->_params['user']) ? $this->_params['user'] : null;
$password = isset($this->_params['password']) ?
$this->_params['password'] : null;
$this->_params['password'] : null;
$this->_conn = $this->_driver->connect($this->_params, $user, $password, $driverOptions);
$this->_isConnected = true;
......@@ -1093,7 +1093,7 @@ class Connection implements DriverConnection
*
* @param \Closure $func The function to execute transactionally.
*
* @return void
* @return mixed The value returned by $func
*
* @throws \Exception
*/
......@@ -1101,10 +1101,11 @@ class Connection implements DriverConnection
{
$this->beginTransaction();
try {
$func($this);
$res = $func($this);
$this->commit();
return $res;
} catch (Exception $e) {
$this->rollback();
$this->rollBack();
throw $e;
}
}
......@@ -1251,9 +1252,6 @@ class Connection implements DriverConnection
/**
* Cancels any database changes done during the current transaction.
*
* This method can be listened with onPreTransactionRollback and onTransactionRollback
* eventlistener methods.
*
* @throws \Doctrine\DBAL\ConnectionException If the rollback operation failed.
*/
public function rollBack()
......@@ -1271,7 +1269,7 @@ class Connection implements DriverConnection
$logger->startQuery('"ROLLBACK"');
}
$this->_transactionNestingLevel = 0;
$this->_conn->rollback();
$this->_conn->rollBack();
$this->_isRollbackOnly = false;
if ($logger) {
$logger->stopQuery();
......@@ -1593,7 +1591,7 @@ class Connection implements DriverConnection
}
try {
$this->query($this->platform->getDummySelectSQL());
$this->query($this->getDatabasePlatform()->getDummySelectSQL());
return true;
} catch (DBALException $e) {
......
......@@ -151,7 +151,7 @@ class MasterSlaveConnection extends Connection
// If we have a connection open, and this is not an explicit connection
// change request, then abort right here, because we are already done.
// This prevents writes to the slave in case of "keepSlave" option enabled.
if ($this->_conn && !$requestedConnectionChange) {
if (isset($this->_conn) && $this->_conn && !$requestedConnectionChange) {
return false;
}
......@@ -162,7 +162,7 @@ class MasterSlaveConnection extends Connection
$forceMasterAsSlave = true;
}
if ($this->connections[$connectionName]) {
if (isset($this->connections[$connectionName]) && $this->connections[$connectionName]) {
$this->_conn = $this->connections[$connectionName];
if ($forceMasterAsSlave && ! $this->keepSlave) {
......
......@@ -19,7 +19,8 @@
namespace Doctrine\DBAL;
use Doctrine\DBAL\Driver\DriverException;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\ExceptionConverterDriver;
class DBALException extends \Exception
......@@ -112,11 +113,7 @@ class DBALException extends \Exception
}
$msg .= ":\n\n".$driverEx->getMessage();
if ($driver instanceof ExceptionConverterDriver && $driverEx instanceof DriverException) {
return $driver->convertException($msg, $driverEx);
}
return new self($msg, 0, $driverEx);
return static::wrapException($driver, $driverEx, $msg);
}
/**
......@@ -127,9 +124,21 @@ class DBALException extends \Exception
*/
public static function driverException(Driver $driver, \Exception $driverEx)
{
$msg = "An exception occured in driver: " . $driverEx->getMessage();
return static::wrapException($driver, $driverEx, "An exception occurred in driver: " . $driverEx->getMessage());
}
if ($driver instanceof ExceptionConverterDriver && $driverEx instanceof DriverException) {
/**
* @param \Doctrine\DBAL\Driver $driver
* @param \Exception $driverEx
*
* @return \Doctrine\DBAL\DBALException
*/
private static function wrapException(Driver $driver, \Exception $driverEx, $msg)
{
if ($driverEx instanceof Exception\DriverException) {
return $driverEx;
}
if ($driver instanceof ExceptionConverterDriver && $driverEx instanceof Driver\DriverException) {
return $driver->convertException($msg, $driverEx);
}
......
......@@ -46,7 +46,7 @@ abstract class AbstractDriverException extends \Exception implements DriverExcep
* Constructor.
*
* @param string $message The driver error message.
* @param string|null $sqlState The SQLSTATE the driver is in at the time the error occured, if any.
* @param string|null $sqlState The SQLSTATE the driver is in at the time the error occurred, if any.
* @param integer|string|null $errorCode The driver specific error code if any.
*/
public function __construct($message, $sqlState = null, $errorCode = null)
......
......@@ -24,6 +24,7 @@ use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\PostgreSQL91Platform;
use Doctrine\DBAL\Platforms\PostgreSQL92Platform;
use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
use Doctrine\DBAL\Schema\PostgreSqlSchemaManager;
use Doctrine\DBAL\VersionAwarePlatformDriver;
......@@ -109,6 +110,8 @@ abstract class AbstractPostgreSQLDriver implements Driver, ExceptionConverterDri
$version = $majorVersion . '.' . $minorVersion . '.' . $patchVersion;
switch(true) {
case version_compare($version, '9.4', '>='):
return new PostgreSQL94Platform();
case version_compare($version, '9.2', '>='):
return new PostgreSQL92Platform();
case version_compare($version, '9.1', '>='):
......
......@@ -116,7 +116,11 @@ abstract class AbstractSQLAnywhereDriver implements Driver, ExceptionConverterDr
{
$params = $conn->getParams();
return $params['dbname'];
if (isset($params['dbname'])) {
return $params['dbname'];
}
return $conn->query('SELECT DB_NAME()')->fetchColumn();
}
/**
......
......@@ -78,7 +78,11 @@ abstract class AbstractSQLServerDriver implements Driver, VersionAwarePlatformDr
{
$params = $conn->getParams();
return $params['dbname'];
if (isset($params['dbname'])) {
return $params['dbname'];
}
return $conn->query('SELECT DB_NAME()')->fetchColumn();
}
/**
......
......@@ -39,9 +39,9 @@ class DB2Connection implements Connection, ServerInfoAwareConnection
*/
public function __construct(array $params, $username, $password, $driverOptions = array())
{
$isPersistant = (isset($params['persistent']) && $params['persistent'] == true);
$isPersistent = (isset($params['persistent']) && $params['persistent'] == true);
if ($isPersistant) {
if ($isPersistent) {
$this->_conn = db2_pconnect($params['dbname'], $username, $password, $driverOptions);
} else {
$this->_conn = db2_connect($params['dbname'], $username, $password, $driverOptions);
......
......@@ -53,6 +53,14 @@ class Driver extends AbstractPostgreSQLDriver
$pdo->setAttribute(PDO::PGSQL_ATTR_DISABLE_PREPARES, true);
}
/* defining client_encoding via SET NAMES to avoid inconsistent DSN support
* - the 'client_encoding' connection param only works with postgres >= 9.1
* - passing client_encoding via the 'options' param breaks pgbouncer support
*/
if (isset($params['charset'])) {
$pdo->query('SET NAMES \''.$params['charset'].'\'');
}
return $pdo;
} catch (PDOException $e) {
throw DBALException::driverException($this, $e);
......@@ -80,16 +88,21 @@ class Driver extends AbstractPostgreSQLDriver
if (isset($params['dbname'])) {
$dsn .= 'dbname=' . $params['dbname'] . ' ';
}
if (isset($params['charset'])) {
$dsn .= "options='--client_encoding=" . $params['charset'] . "'";
} else {
// Used for temporary connections to allow operations like dropping the database currently connected to.
// Connecting without an explicit database does not work, therefore "template1" database is used
// as it is certainly present in every server setup.
$dsn .= 'dbname=template1' . ' ';
}
if (isset($params['sslmode'])) {
$dsn .= 'sslmode=' . $params['sslmode'] . ' ';
}
if (isset($params['sslrootcert'])) {
$dsn .= 'sslrootcert=' . $params['sslrootcert'] . ' ';
}
return $dsn;
}
......
......@@ -29,7 +29,7 @@ use Doctrine\DBAL\Driver\PDOConnection;
class Connection extends PDOConnection implements \Doctrine\DBAL\Driver\Connection
{
/**
* @override
* {@inheritDoc}
*/
public function quote($value, $type=\PDO::PARAM_STR)
{
......
......@@ -218,9 +218,10 @@ final class DriverManager
*
* @param array $params The list of parameters.
*
* @param array A modified list of parameters with info from a database
* URL extracted into indidivual parameter parts.
* @return array A modified list of parameters with info from a database
* URL extracted into indidivual parameter parts.
*
* @throws DBALException
*/
private static function parseDatabaseUrl(array $params)
{
......
......@@ -156,7 +156,7 @@ class TableGenerator
$this->conn->commit();
} catch (\Exception $e) {
$this->conn->rollback();
$this->conn->rollBack();
throw new \Doctrine\DBAL\DBALException("Error occurred while generating ID with TableGenerator, aborted generation: " . $e->getMessage(), 0, $e);
}
......
......@@ -20,7 +20,6 @@
namespace Doctrine\DBAL\Platforms;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\ColumnDiff;
use Doctrine\DBAL\Schema\Identifier;
use Doctrine\DBAL\Schema\Index;
......
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Platforms\Keywords;
/**
* PostgreSQL 9.4 reserved keywords list.
*
* @author Matteo Beccati <matteo@beccati.com>
* @link www.doctrine-project.org
* @since 2.6
*/
class PostgreSQL94Keywords extends PostgreSQL92Keywords
{
/**
* {@inheritdoc}
*/
public function getName()
{
return 'PostgreSQL94';
}
/**
* {@inheritdoc}
*
* @link http://www.postgresql.org/docs/9.4/static/sql-keywords-appendix.html
*/
protected function getKeywords()
{
$parentKeywords = array_diff(parent::getKeywords(), array(
'OVER',
));
return array_merge($parentKeywords, array(
'LATERAL',
));
}
}
......@@ -49,6 +49,12 @@ class MySqlPlatform extends AbstractPlatform
/**
* Adds MySQL-specific LIMIT clause to the query
* 18446744073709551615 is 2^64-1 maximum of unsigned BIGINT the biggest limit possible
*
* @param string $query
* @param integer $limit
* @param integer $offset
*
* @return string
*/
protected function doModifyLimitQuery($query, $limit, $offset)
{
......@@ -183,10 +189,9 @@ class MySqlPlatform extends AbstractPlatform
" c.constraint_name = k.constraint_name AND ".
" c.table_name = '$table' */ WHERE k.table_name = '$table'";
if ($database) {
$sql .= " AND k.table_schema = '$database' /*!50116 AND c.constraint_schema = '$database' */";
}
$databaseNameSql = null === $database ? "'$database'" : 'DATABASE()';
$sql .= " AND k.table_schema = $databaseNameSql /*!50116 AND c.constraint_schema = $databaseNameSql */";
$sql .= " AND k.`REFERENCED_COLUMN_NAME` is not NULL";
return $sql;
......@@ -346,6 +351,9 @@ class MySqlPlatform extends AbstractPlatform
return true;
}
/**
* {@inheritDoc}
*/
public function getListTablesSQL()
{
return "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'";
......@@ -1035,7 +1043,7 @@ class MySqlPlatform extends AbstractPlatform
if ($table instanceof Table) {
$table = $table->getQuotedName($this);
} elseif (!is_string($table)) {
throw new \InvalidArgumentException('getDropTableSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.');
throw new \InvalidArgumentException('getDropTemporaryTableSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.');
}
return 'DROP TEMPORARY TABLE ' . $table;
......
......@@ -72,4 +72,12 @@ class PostgreSQL92Platform extends PostgreSQL91Platform
parent::initializeDoctrineTypeMappings();
$this->doctrineTypeMapping['json'] = 'json_array';
}
/**
* {@inheritdoc}
*/
public function getCloseActiveDatabaseConnectionsSQL($database)
{
return "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$database'";
}
}
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Platforms;
/**
* Provides the behavior, features and SQL dialect of the PostgreSQL 9.4 database platform.
*
* @author Matteo Beccati <matteo@beccati.com>
* @link www.doctrine-project.org
* @since 2.6
*/
class PostgreSQL94Platform extends PostgreSQL92Platform
{
/**
* {@inheritdoc}
*/
public function getJsonTypeDeclarationSQL(array $field)
{
if (!empty($field['jsonb'])) {
return 'JSONB';
}
return 'JSON';
}
/**
* {@inheritdoc}
*/
protected function getReservedKeywordsClass()
{
return 'Doctrine\DBAL\Platforms\Keywords\PostgreSQL94Keywords';
}
/**
* {@inheritdoc}
*/
protected function initializeDoctrineTypeMappings()
{
parent::initializeDoctrineTypeMappings();
$this->doctrineTypeMapping['jsonb'] = 'json_array';
}
}
......@@ -420,6 +420,34 @@ class PostgreSqlPlatform extends AbstractPlatform
return 'CREATE DATABASE ' . $name;
}
/**
* Returns the SQL statement for disallowing new connections on the given database.
*
* This is useful to force DROP DATABASE operations which could fail because of active connections.
*
* @param string $database The name of the database to disallow new connections for.
*
* @return string
*/
public function getDisallowDatabaseConnectionsSQL($database)
{
return "UPDATE pg_database SET datallowconn = 'false' WHERE datname = '$database'";
}
/**
* Returns the SQL statement for closing currently active connections on the given database.
*
* This is useful to force DROP DATABASE operations which could fail because of active connections.
*
* @param string $database The name of the database to close currently active connections for.
*
* @return string
*/
public function getCloseActiveDatabaseConnectionsSQL($database)
{
return "SELECT pg_terminate_backend(procpid) FROM pg_stat_activity WHERE datname = '$database'";
}
/**
* {@inheritDoc}
*/
......@@ -517,7 +545,7 @@ class PostgreSqlPlatform extends AbstractPlatform
}
if ($columnDiff->hasChanged('notnull')) {
$query = 'ALTER ' . $oldColumnName . ' ' . ($column->getNotNull() ? 'SET' : 'DROP') . ' NOT NULL';
$query = 'ALTER ' . $oldColumnName . ' ' . ($column->getNotnull() ? 'SET' : 'DROP') . ' NOT NULL';
$sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
}
......
......@@ -120,9 +120,18 @@ class SQLServer2012Platform extends SQLServer2008Platform
if ($orderByPos === false
|| substr_count($query, "(", $orderByPos) - substr_count($query, ")", $orderByPos)
) {
// In another DBMS, we could do ORDER BY 0, but SQL Server gets angry if you use constant expressions in
// the order by list.
$query .= " ORDER BY (SELECT 0)";
if (stripos($query, 'SELECT DISTINCT') === 0) {
// SQL Server won't let us order by a non-selected column in a DISTINCT query,
// so we have to do this madness. This says, order by the first column in the
// result. SQL Server's docs say that a nonordered query's result order is non-
// deterministic anyway, so this won't do anything that a bunch of update and
// deletes to the table wouldn't do anyway.
$query .= " ORDER BY 1";
} else {
// In another DBMS, we could do ORDER BY 0, but SQL Server gets angry if you
// use constant expressions in the order by list.
$query .= " ORDER BY (SELECT 0)";
}
}
if ($offset === null) {
......
......@@ -893,7 +893,7 @@ class SQLServerPlatform extends AbstractPlatform
JOIN sys.index_columns AS idxcol ON idx.object_id = idxcol.object_id AND idx.index_id = idxcol.index_id
JOIN sys.columns AS col ON idxcol.object_id = col.object_id AND idxcol.column_id = col.column_id
WHERE " . $this->getTableWhereClause($table, 'scm.name', 'tbl.name') . "
ORDER BY idx.index_id ASC, idxcol.index_column_id ASC";
ORDER BY idx.index_id ASC, idxcol.key_ordinal ASC";
}
/**
......
......@@ -45,7 +45,7 @@ class SqlitePlatform extends AbstractPlatform
*/
public function getRegexpExpression()
{
return 'RLIKE';
return 'REGEXP';
}
/**
......
......@@ -1302,6 +1302,8 @@ class QueryBuilder
* @param array $knownAliases
*
* @return string
*
* @throws QueryException
*/
private function getSQLForJoins($fromAlias, array &$knownAliases)
{
......
......@@ -152,7 +152,7 @@ abstract class AbstractSchemaManager
*
* In contrast to other libraries and to the old version of Doctrine,
* this column definition does try to contain the 'primary' field for
* the reason that it is not portable accross different RDBMS. Use
* the reason that it is not portable across different RDBMS. Use
* {@see listTableIndexes($tableName)} to retrieve the primary key
* of a table. We're a RDBMS specifies more details these are held
* in the platformDetails array.
......
......@@ -22,7 +22,7 @@ namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Platforms\AbstractPlatform;
/**
* Marker interface for contraints.
* Marker interface for constraints.
*
* @link www.doctrine-project.org
* @since 2.0
......
......@@ -215,7 +215,7 @@ class Index extends AbstractAsset implements Constraint
}
if ( ! $this->isUnique() && ! $this->isPrimary()) {
// this is a special case: If the current key is neither primary or unique, any uniqe or
// this is a special case: If the current key is neither primary or unique, any unique or
// primary key will always have the same effect for the index and there cannot be any constraint
// overlaps. This means a primary or unique index can always fulfill the requirements of just an
// index that has no constraints.
......
......@@ -19,6 +19,7 @@
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Types\Type;
/**
......@@ -100,6 +101,33 @@ class PostgreSqlSchemaManager extends AbstractSchemaManager
});
}
/**
* {@inheritdoc}
*/
public function dropDatabase($database)
{
try {
parent::dropDatabase($database);
} catch (DriverException $exception) {
// If we have a SQLSTATE 55006, the drop database operation failed
// because of active connections on the database.
// To force dropping the database, we first have to close all active connections
// on that database and issue the drop database operation again.
if ($exception->getSQLState() !== '55006') {
throw $exception;
}
$this->_execSql(
array(
$this->_platform->getDisallowDatabaseConnectionsSQL($database),
$this->_platform->getCloseActiveDatabaseConnectionsSQL($database),
)
);
parent::dropDatabase($database);
}
}
/**
* {@inheritdoc}
*/
......@@ -311,6 +339,7 @@ class PostgreSqlSchemaManager extends AbstractSchemaManager
$precision = null;
$scale = null;
$jsonb = null;
$dbType = strtolower($tableColumn['type']);
if (strlen($tableColumn['domain_type']) && !$this->_platform->hasDoctrineTypeMappingFor($tableColumn['type'])) {
......@@ -378,6 +407,11 @@ class PostgreSqlSchemaManager extends AbstractSchemaManager
case 'year':
$length = null;
break;
// PostgreSQL 9.4+ only
case 'jsonb':
$jsonb = true;
break;
}
if ($tableColumn['default'] && preg_match("('([^']+)'::)", $tableColumn['default'], $match)) {
......@@ -405,6 +439,10 @@ class PostgreSqlSchemaManager extends AbstractSchemaManager
$column->setPlatformOption('collation', $tableColumn['collation']);
}
if ($column->getType()->getName() === 'json_array') {
$column->setPlatformOption('jsonb', $jsonb);
}
return $column;
}
}
......@@ -313,6 +313,8 @@ class Schema extends AbstractAsset
* @param string $namespaceName The name of the namespace to create.
*
* @return \Doctrine\DBAL\Schema\Schema This schema instance.
*
* @throws SchemaException
*/
public function createNamespace($namespaceName)
{
......
......@@ -418,6 +418,12 @@ class SqliteSchemaManager extends AbstractSchemaManager
return $tableDiff;
}
/**
* @param string $column
* @param string $sql
*
* @return string|false
*/
private function parseColumnCollationFromSQL($column, $sql)
{
if (preg_match(
......
......@@ -331,7 +331,7 @@ class Table extends AbstractAsset
* @param string $oldColumnName
* @param string $newColumnName
*
* @return self
* @deprecated
*
* @throws DBALException
*/
......
......@@ -66,10 +66,7 @@ class CreateSchemaSqlCollector extends AbstractVisitor
public function acceptNamespace($namespaceName)
{
if ($this->platform->supportsSchemas()) {
$this->createNamespaceQueries = array_merge(
$this->createNamespaceQueries,
(array) $this->platform->getCreateSchemaSQL($namespaceName)
);
$this->createNamespaceQueries[] = $this->platform->getCreateSchemaSQL($namespaceName);
}
}
......@@ -87,12 +84,7 @@ class CreateSchemaSqlCollector extends AbstractVisitor
public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint)
{
if ($this->platform->supportsForeignKeyConstraints()) {
$this->createFkConstraintQueries = array_merge(
$this->createFkConstraintQueries,
(array) $this->platform->getCreateForeignKeySQL(
$fkConstraint, $localTable
)
);
$this->createFkConstraintQueries[] = $this->platform->getCreateForeignKeySQL($fkConstraint, $localTable);
}
}
......@@ -101,10 +93,7 @@ class CreateSchemaSqlCollector extends AbstractVisitor
*/
public function acceptSequence(Sequence $sequence)
{
$this->createSequenceQueries = array_merge(
$this->createSequenceQueries,
(array) $this->platform->getCreateSequenceSQL($sequence)
);
$this->createSequenceQueries[] = $this->platform->getCreateSequenceSQL($sequence);
}
/**
......@@ -125,24 +114,11 @@ class CreateSchemaSqlCollector extends AbstractVisitor
*/
public function getQueries()
{
$sql = array();
foreach ($this->createNamespaceQueries as $schemaSql) {
$sql = array_merge($sql, (array) $schemaSql);
}
foreach ($this->createTableQueries as $schemaSql) {
$sql = array_merge($sql, (array) $schemaSql);
}
foreach ($this->createSequenceQueries as $schemaSql) {
$sql = array_merge($sql, (array) $schemaSql);
}
foreach ($this->createFkConstraintQueries as $schemaSql) {
$sql = array_merge($sql, (array) $schemaSql);
}
return $sql;
return array_merge(
$this->createNamespaceQueries,
$this->createTableQueries,
$this->createSequenceQueries,
$this->createFkConstraintQueries
);
}
}
......@@ -19,14 +19,12 @@
namespace Doctrine\DBAL\Sharding;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Event\ConnectionEventArgs;
use Doctrine\DBAL\Events;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Configuration;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Sharding\ShardChoser\ShardChoser;
/**
......@@ -128,6 +126,64 @@ class PoolingShardConnection extends Connection
parent::__construct($params, $driver, $config, $eventManager);
}
/**
* Get active shard id.
*
* @return integer
*/
public function getActiveShardId()
{
return $this->activeShardId;
}
/**
* {@inheritdoc}
*/
public function getParams()
{
return $this->activeShardId ? $this->connections[$this->activeShardId] : $this->connections[0];
}
/**
* {@inheritdoc}
*/
public function getHost()
{
$params = $this->getParams();
return isset($params['host']) ? $params['host'] : parent::getHost();
}
/**
* {@inheritdoc}
*/
public function getPort()
{
$params = $this->getParams();
return isset($params['port']) ? $params['port'] : parent::getPort();
}
/**
* {@inheritdoc}
*/
public function getUsername()
{
$params = $this->getParams();
return isset($params['user']) ? $params['user'] : parent::getUsername();
}
/**
* {@inheritdoc}
*/
public function getPassword()
{
$params = $this->getParams();
return isset($params['password']) ? $params['password'] : parent::getPassword();
}
/**
* Connects to a given shard.
*
......@@ -151,7 +207,7 @@ class PoolingShardConnection extends Connection
throw new ShardingException("Cannot switch shard when transaction is active.");
}
$this->activeShardId = (int) $shardId;
$this->activeShardId = (int)$shardId;
if (isset($this->activeConnections[$this->activeShardId])) {
$this->_conn = $this->activeConnections[$this->activeShardId];
......@@ -211,5 +267,6 @@ class PoolingShardConnection extends Connection
{
$this->_conn = null;
$this->activeConnections = null;
$this->activeShardId = null;
}
}
......@@ -19,6 +19,8 @@
namespace Doctrine\DBAL\Sharding;
use Doctrine\DBAL\Sharding\ShardChoser\ShardChoser;
/**
* Shard Manager for the Connection Pooling Shard Strategy
*
......@@ -27,12 +29,12 @@ namespace Doctrine\DBAL\Sharding;
class PoolingShardManager implements ShardManager
{
/**
* @var \Doctrine\DBAL\Sharding\PoolingShardConnection
* @var PoolingShardConnection
*/
private $conn;
/**
* @var \Doctrine\DBAL\Sharding\ShardChoser\ShardChoser
* @var ShardChoser
*/
private $choser;
......@@ -42,7 +44,7 @@ class PoolingShardManager implements ShardManager
private $currentDistributionValue;
/**
* @param \Doctrine\DBAL\Sharding\PoolingShardConnection $conn
* @param PoolingShardConnection $conn
*/
public function __construct(PoolingShardConnection $conn)
{
......
......@@ -41,8 +41,8 @@ use Doctrine\DBAL\Schema\Index;
* - You always have to work with `filtering=On` when using federations with this
* multi-tenant approach.
* - Primary keys are either using globally unique ids (GUID, Table Generator)
* or you explicitly add the tenent_id in every UPDATE or DELETE statement
* (otherwise they will affect the same-id rows from other tenents as well).
* or you explicitly add the tenant_id in every UPDATE or DELETE statement
* (otherwise they will affect the same-id rows from other tenants as well).
* SQLAzure throws errors when you try to create IDENTIY columns on federated
* tables.
*
......
......@@ -164,6 +164,9 @@ class Statement implements \IteratorAggregate, DriverStatement
try {
$stmt = $this->stmt->execute($params);
} catch (\Exception $ex) {
if ($logger) {
$logger->stopQuery();
}
throw DBALException::driverExceptionDuringQuery(
$this->conn->getDriver(),
$ex,
......
......@@ -41,7 +41,7 @@ class ConversionException extends \Doctrine\DBAL\DBALException
*/
static public function conversionFailed($value, $toType)
{
$value = (strlen($value) > 32) ? substr($value, 0, 20) . "..." : $value;
$value = (strlen($value) > 32) ? substr($value, 0, 20) . '...' : $value;
return new self('Could not convert database value "' . $value . '" to Doctrine Type ' . $toType);
}
......@@ -50,19 +50,53 @@ class ConversionException extends \Doctrine\DBAL\DBALException
* Thrown when a Database to Doctrine Type Conversion fails and we can make a statement
* about the expected format.
*
* @param string $value
* @param string $toType
* @param string $expectedFormat
* @param string $value
* @param string $toType
* @param string $expectedFormat
* @param \Exception|null $previous
*
* @return \Doctrine\DBAL\Types\ConversionException
*/
static public function conversionFailedFormat($value, $toType, $expectedFormat)
static public function conversionFailedFormat($value, $toType, $expectedFormat, \Exception $previous = null)
{
$value = (strlen($value) > 32) ? substr($value, 0, 20) . "..." : $value;
$value = (strlen($value) > 32) ? substr($value, 0, 20) . '...' : $value;
return new self(
'Could not convert database value "' . $value . '" to Doctrine Type ' .
$toType . '. Expected format: ' . $expectedFormat
$toType . '. Expected format: ' . $expectedFormat,
0,
$previous
);
}
/**
* Thrown when the PHP value passed to the converter was not of the expected type.
*
* @param mixed $value
* @param string $toType
* @param string[] $possibleTypes
*
* @return \Doctrine\DBAL\Types\ConversionException
*/
static public function conversionFailedInvalidType($value, $toType, array $possibleTypes)
{
$actualType = is_object($value) ? get_class($value) : gettype($value);
if (is_scalar($value)) {
return new self(sprintf(
"Could not convert PHP value '%s' of type '%s' to type '%s'. Expected one of the following types: %s",
$value,
$actualType,
$toType,
implode(', ', $possibleTypes)
));
}
return new self(sprintf(
"Could not convert PHP value of type '%s' to type '%s'. Expected one of the following types: %s",
$actualType,
$toType,
implode(', ', $possibleTypes)
));
}
}
<?php
namespace Doctrine\DBAL\Types;
use Doctrine\DBAL\Platforms\AbstractPlatform;
/**
* Type that maps interval string to a PHP DateInterval Object.
*/
class DateIntervalType extends Type
{
/**
* {@inheritdoc}
*/
public function getName()
{
return Type::DATEINTERVAL;
}
/**
* {@inheritdoc}
*/
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
$fieldDeclaration['length'] = 20;
$fieldDeclaration['fixed'] = true;
return $platform->getVarcharTypeDeclarationSQL($fieldDeclaration);
}
/**
* {@inheritdoc}
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if (null === $value) {
return null;
}
if ($value instanceof \DateInterval) {
return 'P'
. str_pad($value->y, 4, '0', STR_PAD_LEFT) . '-'
. $value->format('%M-%DT%H:%I:%S');
}
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'DateInterval']);
}
/**
* {@inheritdoc}
*/
public function convertToPHPValue($value, AbstractPlatform $platform)
{
if ($value === null || $value instanceof \DateInterval) {
return $value;
}
try {
return new \DateInterval($value);
} catch (\Exception $exception) {
throw ConversionException::conversionFailedFormat($value, $this->getName(), 'PY-m-dTH:i:s', $exception);
}
}
/**
* {@inheritdoc}
*/
public function requiresSQLCommentHint(AbstractPlatform $platform)
{
return true;
}
}
......@@ -49,8 +49,15 @@ class DateTimeType extends Type
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
return ($value !== null)
? $value->format($platform->getDateTimeFormatString()) : null;
if (null === $value) {
return $value;
}
if ($value instanceof \DateTime) {
return $value->format($platform->getDateTimeFormatString());
}
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'DateTime']);
}
/**
......
......@@ -67,8 +67,15 @@ class DateTimeTzType extends Type
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
return ($value !== null)
? $value->format($platform->getDateTimeTzFormatString()) : null;
if (null === $value) {
return $value;
}
if ($value instanceof \DateTime) {
return $value->format($platform->getDateTimeTzFormatString());
}
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'DateTime']);
}
/**
......
......@@ -49,8 +49,15 @@ class DateType extends Type
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
return ($value !== null)
? $value->format($platform->getDateFormatString()) : null;
if (null === $value) {
return $value;
}
if ($value instanceof \DateTime) {
return $value->format($platform->getDateFormatString());
}
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'DateTime']);
}
/**
......
......@@ -49,8 +49,15 @@ class TimeType extends Type
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
return ($value !== null)
? $value->format($platform->getTimeFormatString()) : null;
if (null === $value) {
return $value;
}
if ($value instanceof \DateTime) {
return $value->format($platform->getTimeFormatString());
}
throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'DateTime']);
}
/**
......
......@@ -52,6 +52,7 @@ abstract class Type
const BLOB = 'blob';
const FLOAT = 'float';
const GUID = 'guid';
const DATEINTERVAL = 'dateinterval';
/**
* Map of already instantiated type objects. One instance per type (flyweight).
......@@ -85,6 +86,7 @@ abstract class Type
self::BINARY => 'Doctrine\DBAL\Types\BinaryType',
self::BLOB => 'Doctrine\DBAL\Types\BlobType',
self::GUID => 'Doctrine\DBAL\Types\GuidType',
self::DATEINTERVAL => 'Doctrine\DBAL\Types\DateIntervalType',
);
/**
......
Subproject commit 4200b4bc95ae3c1b03d943cd875277e35a17898a
Subproject commit efa94de25beef4aefaeb7972c122798f9876fc39
Subproject commit d1c7d4334e38cad603a5c863d4c7b91bb04ec6b2
......@@ -11,41 +11,52 @@
tests/ folder: phpunit -c <filename> ...
Example: phpunit -c mysqlconf.xml
-->
<phpunit backupGlobals="false"
backupStaticAttributes="false"
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.8/phpunit.xsd"
backupGlobals="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="./tests/Doctrine/Tests/TestInit.php"
bootstrap="vendor/autoload.php"
>
<php>
<!-- "Real" test database -->
<!-- Uncomment, otherwise SQLite runs
<var name="db_type" value="pdo_mysql"/>
<var name="db_host" value="localhost" />
<var name="db_username" value="root" />
<var name="db_password" value="" />
<var name="db_name" value="doctrine_tests" />
<var name="db_port" value="3306"/>
-->
<!--<var name="db_event_subscribers" value="Doctrine\DBAL\Event\Listeners\OracleSessionInit">-->
<php>
<ini name="error_reporting" value="-1" />
<!-- "Real" test database -->
<!-- Uncomment, otherwise SQLite runs
<var name="db_type" value="pdo_mysql"/>
<var name="db_host" value="localhost" />
<var name="db_username" value="root" />
<var name="db_password" value="" />
<var name="db_name" value="doctrine_tests" />
<var name="db_port" value="3306"/>
-->
<!--<var name="db_event_subscribers" value="Doctrine\DBAL\Event\Listeners\OracleSessionInit">-->
<!-- Database for temporary connections (i.e. to drop/create the main database) -->
<var name="tmpdb_type" value="pdo_mysql"/>
<var name="tmpdb_host" value="localhost" />
<var name="tmpdb_username" value="root" />
<var name="tmpdb_password" value="" />
<var name="tmpdb_name" value="doctrine_tests_tmp" />
<var name="tmpdb_port" value="3306"/>
</php>
<!-- Database for temporary connections (i.e. to drop/create the main database) -->
<var name="tmpdb_type" value="pdo_mysql"/>
<var name="tmpdb_host" value="localhost" />
<var name="tmpdb_username" value="root" />
<var name="tmpdb_password" value="" />
<var name="tmpdb_name" value="doctrine_tests_tmp" />
<var name="tmpdb_port" value="3306"/>
</php>
<testsuites>
<testsuite name="Doctrine DBAL Test Suite">
<directory>./tests/Doctrine/Tests/DBAL</directory>
<exclude>./tests/Doctrine/Tests/DBAL/Performance</exclude>
</testsuite>
<testsuite name="Doctrine DBAL Performance Test Suite">
<directory>./tests/Doctrine/Tests/DBAL/Performance</directory>
</testsuite>
</testsuites>
<listeners>
<listener class="Doctrine\Tests\DbalPerformanceTestListener"/>
</listeners>
<groups>
<exclude>
<group>performance</group>
</exclude>
</groups>
</phpunit>
\ No newline at end of file
......@@ -39,7 +39,7 @@ class ConfigurationTest extends DbalTestCase
/**
* {@inheritdoc}
*/
public function setUp()
protected function setUp()
{
$this->config = new Configuration();
}
......
......@@ -24,7 +24,7 @@ class ConnectionTest extends \Doctrine\Tests\DbalTestCase
'port' => '1234'
);
public function setUp()
protected function setUp()
{
$this->_conn = \Doctrine\DBAL\DriverManager::getConnection($this->params);
}
......@@ -131,9 +131,7 @@ class ConnectionTest extends \Doctrine\Tests\DbalTestCase
*/
public function testDriverExceptionIsWrapped($method)
{
$this->setExpectedException('Doctrine\DBAL\DBALException', "An exception occurred while executing 'MUUHAAAAHAAAA':
SQLSTATE[HY000]: General error: 1 near \"MUUHAAAAHAAAA\"");
$this->setExpectedException('Doctrine\DBAL\DBALException', "An exception occurred while executing 'MUUHAAAAHAAAA':\n\nSQLSTATE[HY000]: General error: 1 near \"MUUHAAAAHAAAA\"");
$con = \Doctrine\DBAL\DriverManager::getConnection(array(
'driver' => 'pdo_sqlite',
......
......@@ -3,6 +3,7 @@
namespace Doctrine\Tests\DBAL;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Exception\DriverException;
class DBALExceptionTest extends \Doctrine\Tests\DbalTestCase
{
......@@ -12,4 +13,12 @@ class DBALExceptionTest extends \Doctrine\Tests\DbalTestCase
$e = DBALException::driverExceptionDuringQuery($driver, new \Exception, '', array('ABC', chr(128)));
$this->assertContains('with params ["ABC", "\x80"]', $e->getMessage());
}
public function testAvoidOverWrappingOnDriverException()
{
$driver = $this->getMock('\Doctrine\DBAL\Driver');
$ex = new DriverException('', $this->getMock('\Doctrine\DBAL\Driver\DriverException'));
$e = DBALException::driverExceptionDuringQuery($driver, $ex, '');
$this->assertSame($ex, $e);
}
}
......@@ -63,7 +63,11 @@ class AbstractPostgreSQLDriverTest extends AbstractDriverTest
array('9.2', 'Doctrine\DBAL\Platforms\PostgreSQL92Platform'),
array('9.2.0', 'Doctrine\DBAL\Platforms\PostgreSQL92Platform'),
array('9.2.1', 'Doctrine\DBAL\Platforms\PostgreSQL92Platform'),
array('10', 'Doctrine\DBAL\Platforms\PostgreSQL92Platform'),
array('9.3.6', 'Doctrine\DBAL\Platforms\PostgreSQL92Platform'),
array('9.4', 'Doctrine\DBAL\Platforms\PostgreSQL94Platform'),
array('9.4.0', 'Doctrine\DBAL\Platforms\PostgreSQL94Platform'),
array('9.4.1', 'Doctrine\DBAL\Platforms\PostgreSQL94Platform'),
array('10', 'Doctrine\DBAL\Platforms\PostgreSQL94Platform'),
);
}
......
......@@ -4,7 +4,7 @@ namespace Doctrine\Tests\DBAL;
class OCI8StatementTest extends \Doctrine\Tests\DbalTestCase
{
public function setUp()
protected function setUp()
{
if (!extension_loaded('oci8')) {
$this->markTestSkipped('oci8 is not installed.');
......
......@@ -10,7 +10,7 @@ use PDO;
*/
class BlobTest extends \Doctrine\Tests\DbalFunctionalTestCase
{
public function setUp()
protected function setUp()
{
parent::setUp();
......
......@@ -8,13 +8,13 @@ use Doctrine\DBAL\Types\Type;
class ConnectionTest extends \Doctrine\Tests\DbalFunctionalTestCase
{
public function setUp()
protected function setUp()
{
$this->resetSharedConn();
parent::setUp();
}
public function tearDown()
protected function tearDown()
{
parent::tearDown();
$this->resetSharedConn();
......@@ -209,6 +209,14 @@ class ConnectionTest extends \Doctrine\Tests\DbalFunctionalTestCase
});
}
public function testTransactionalReturnValue()
{
$res = $this->_conn->transactional(function($conn) {
return 42;
});
$this->assertEquals(42, $res);
}
/**
* Tests that the quote function accepts DBAL and PDO types.
*/
......
......@@ -11,7 +11,7 @@ class DataAccessTest extends \Doctrine\Tests\DbalFunctionalTestCase
{
static private $generated = false;
public function setUp()
protected function setUp()
{
parent::setUp();
......@@ -533,11 +533,11 @@ class DataAccessTest extends \Doctrine\Tests\DbalFunctionalTestCase
$this->assertEquals('2010-01-08', date('Y-m-d', strtotime($row['add_weeks'])), "Adding week should end up on 2010-01-08");
$this->assertEquals('2009-12-25', date('Y-m-d', strtotime($row['sub_weeks'])), "Subtracting week should end up on 2009-12-25");
$this->assertEquals('2010-03-01', date('Y-m-d', strtotime($row['add_month'])), "Adding month should end up on 2010-03-01");
$this->assertEquals('2009-11-01', date('Y-m-d', strtotime($row['sub_month'])), "Substracting month should end up on 2009-11-01");
$this->assertEquals('2009-11-01', date('Y-m-d', strtotime($row['sub_month'])), "Subtracting month should end up on 2009-11-01");
$this->assertEquals('2010-10-01', date('Y-m-d', strtotime($row['add_quarters'])), "Adding quarters should end up on 2010-04-01");
$this->assertEquals('2009-04-01', date('Y-m-d', strtotime($row['sub_quarters'])), "Substracting quarters should end up on 2009-10-01");
$this->assertEquals('2009-04-01', date('Y-m-d', strtotime($row['sub_quarters'])), "Subtracting quarters should end up on 2009-10-01");
$this->assertEquals('2016-01-01', date('Y-m-d', strtotime($row['add_years'])), "Adding years should end up on 2016-01-01");
$this->assertEquals('2004-01-01', date('Y-m-d', strtotime($row['sub_years'])), "Substracting years should end up on 2004-01-01");
$this->assertEquals('2004-01-01', date('Y-m-d', strtotime($row['sub_years'])), "Subtracting years should end up on 2004-01-01");
}
public function testLocateExpression()
......
<?php
namespace Doctrine\Tests\DBAL\Functional\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\Tests\DbalFunctionalTestCase;
abstract class AbstractDriverTest extends DbalFunctionalTestCase
{
/**
* The driver instance under test.
*
* @var \Doctrine\DBAL\Driver
*/
protected $driver;
protected function setUp()
{
parent::setUp();
$this->driver = $this->createDriver();
}
/**
* @group DBAL-1215
*/
public function testConnectsWithoutDatabaseNameParameter()
{
$params = $this->_conn->getParams();
unset($params['dbname']);
$user = isset($params['user']) ? $params['user'] : null;
$password = isset($params['password']) ? $params['password'] : null;
$connection = $this->driver->connect($params, $user, $password);
$this->assertInstanceOf('Doctrine\DBAL\Driver\Connection', $connection);
}
/**
* @group DBAL-1215
*/
public function testReturnsDatabaseNameWithoutDatabaseNameParameter()
{
$params = $this->_conn->getParams();
unset($params['dbname']);
$connection = new Connection(
$params,
$this->_conn->getDriver(),
$this->_conn->getConfiguration(),
$this->_conn->getEventManager()
);
$this->assertSame(
$this->getDatabaseNameForConnectionWithoutDatabaseNameParameter(),
$this->driver->getDatabase($connection)
);
}
/**
* @return \Doctrine\DBAL\Driver
*/
abstract protected function createDriver();
/**
* @return string|null
*/
protected function getDatabaseNameForConnectionWithoutDatabaseNameParameter()
{
return null;
}
}
<?php
namespace Doctrine\Tests\DBAL\Functional\Driver\IBMDB2;
use Doctrine\DBAL\Driver\IBMDB2\DB2Driver;
use Doctrine\Tests\DBAL\Functional\Driver\AbstractDriverTest;
class DB2DriverTest extends AbstractDriverTest
{
protected function setUp()
{
if (! extension_loaded('ibm_db2')) {
$this->markTestSkipped('ibm_db2 is not installed.');
}
parent::setUp();
if (! $this->_conn->getDriver() instanceof DB2Driver) {
$this->markTestSkipped('ibm_db2 only test.');
}
}
/**
* {@inheritdoc}
*/
public function testConnectsWithoutDatabaseNameParameter()
{
$this->markTestSkipped('IBM DB2 does not support connecting without database name.');
}
/**
* {@inheritdoc}
*/
public function testReturnsDatabaseNameWithoutDatabaseNameParameter()
{
$this->markTestSkipped('IBM DB2 does not support connecting without database name.');
}
/**
* {@inheritdoc}
*/
protected function createDriver()
{
return new DB2Driver();
}
}
......@@ -3,7 +3,7 @@ namespace Doctrine\Tests\DBAL\Functional\Driver\Mysqli;
class ConnectionTest extends \Doctrine\Tests\DbalFunctionalTestCase
{
public function setUp()
protected function setUp()
{
if (!extension_loaded('mysqli')) {
$this->markTestSkipped('mysqli is not installed.');
......@@ -16,7 +16,7 @@ class ConnectionTest extends \Doctrine\Tests\DbalFunctionalTestCase
}
}
public function tearDown()
protected function tearDown()
{
parent::tearDown();
}
......
<?php
namespace Doctrine\Tests\DBAL\Functional\Driver\Mysqli;
use Doctrine\DBAL\Driver\Mysqli\Driver;
use Doctrine\Tests\DBAL\Functional\Driver\AbstractDriverTest;
class DriverTest extends AbstractDriverTest
{
protected function setUp()
{
if (! extension_loaded('mysqli')) {
$this->markTestSkipped('mysqli is not installed.');
}
parent::setUp();
if (! $this->_conn->getDriver() instanceof Driver) {
$this->markTestSkipped('MySQLi only test.');
}
}
/**
* {@inheritdoc}
*/
protected function createDriver()
{
return new Driver();
}
}
<?php
namespace Doctrine\Tests\DBAL\Functional\Driver\OCI8;
use Doctrine\DBAL\Driver\OCI8\Driver;
use Doctrine\Tests\DBAL\Functional\Driver\AbstractDriverTest;
class DriverTest extends AbstractDriverTest
{
protected function setUp()
{
if (! extension_loaded('oci8')) {
$this->markTestSkipped('oci8 is not installed.');
}
parent::setUp();
if (! $this->_conn->getDriver() instanceof Driver) {
$this->markTestSkipped('oci8 only test.');
}
}
/**
* {@inheritdoc}
*/
public function testConnectsWithoutDatabaseNameParameter()
{
$this->markTestSkipped('Oracle does not support connecting without database name.');
}
/**
* {@inheritdoc}
*/
public function testReturnsDatabaseNameWithoutDatabaseNameParameter()
{
$this->markTestSkipped('Oracle does not support connecting without database name.');
}
/**
* {@inheritdoc}
*/
protected function createDriver()
{
return new Driver();
}
}
<?php
namespace Doctrine\Tests\DBAL\Functional\Driver\PDOMySql;
use Doctrine\DBAL\Driver\PDOMySql\Driver;
use Doctrine\Tests\DBAL\Functional\Driver\AbstractDriverTest;
class DriverTest extends AbstractDriverTest
{
protected function setUp()
{
if (! extension_loaded('pdo_mysql')) {
$this->markTestSkipped('pdo_mysql is not installed.');
}
parent::setUp();
if (! $this->_conn->getDriver() instanceof Driver) {
$this->markTestSkipped('pdo_mysql only test.');
}
}
/**
* {@inheritdoc}
*/
protected function createDriver()
{
return new Driver();
}
}
<?php
namespace Doctrine\Tests\DBAL\Functional\Driver\PDOOracle;
use Doctrine\DBAL\Driver\PDOOracle\Driver;
use Doctrine\Tests\DBAL\Functional\Driver\AbstractDriverTest;
class DriverTest extends AbstractDriverTest
{
protected function setUp()
{
if (! extension_loaded('PDO_OCI')) {
$this->markTestSkipped('PDO_OCI is not installed.');
}
parent::setUp();
if (! $this->_conn->getDriver() instanceof Driver) {
$this->markTestSkipped('PDO_OCI only test.');
}
}
/**
* {@inheritdoc}
*/
public function testConnectsWithoutDatabaseNameParameter()
{
$this->markTestSkipped('Oracle does not support connecting without database name.');
}
/**
* {@inheritdoc}
*/
public function testReturnsDatabaseNameWithoutDatabaseNameParameter()
{
$this->markTestSkipped('Oracle does not support connecting without database name.');
}
/**
* {@inheritdoc}
*/
protected function createDriver()
{
return new Driver();
}
}
<?php
namespace Doctrine\Tests\DBAL\Functional\Driver\PDOPgSql;
use Doctrine\DBAL\Driver\PDOPgSql\Driver;
use Doctrine\Tests\DBAL\Functional\Driver\AbstractDriverTest;
class DriverTest extends AbstractDriverTest
{
protected function setUp()
{
if (! extension_loaded('pdo_pgsql')) {
$this->markTestSkipped('pdo_pgsql is not installed.');
}
parent::setUp();
if (! $this->_conn->getDriver() instanceof Driver) {
$this->markTestSkipped('pdo_pgsql only test.');
}
}
/**
* {@inheritdoc}
*/
protected function createDriver()
{
return new Driver();
}
/**
* {@inheritdoc}
*/
protected function getDatabaseNameForConnectionWithoutDatabaseNameParameter()
{
return 'template1';
}
}
<?php
namespace Doctrine\Tests\DBAL\Functional\Driver;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
use Doctrine\Tests\DbalFunctionalTestCase;
class PDOPgsqlConnectionTest extends DbalFunctionalTestCase
{
protected function setUp()
{
if ( ! extension_loaded('pdo_pgsql')) {
$this->markTestSkipped('pdo_pgsql is not loaded.');
}
parent::setUp();
if ( ! $this->_conn->getDatabasePlatform() instanceof PostgreSqlPlatform) {
$this->markTestSkipped('PDOPgsql only test.');
}
}
/**
* @group DBAL-1183
* @group DBAL-1189
*
* @dataProvider getValidCharsets
*
* @param string $charset
*/
public function testConnectsWithValidCharsetOption($charset)
{
$params = $this->_conn->getParams();
$params['charset'] = $charset;
$connection = DriverManager::getConnection(
$params,
$this->_conn->getConfiguration(),
$this->_conn->getEventManager()
);
$this->assertEquals($charset, $connection->query("SHOW client_encoding")->fetch(\PDO::FETCH_COLUMN));
}
/**
* @return array
*/
public function getValidCharsets()
{
return array(
array("UTF8"),
array("LATIN1")
);
}
}
<?php
namespace Doctrine\Tests\DBAL\Functional\Driver\PDOSqlite;
use Doctrine\DBAL\Driver\PDOSqlite\Driver;
use Doctrine\Tests\DBAL\Functional\Driver\AbstractDriverTest;
class DriverTest extends AbstractDriverTest
{
protected function setUp()
{
if (! extension_loaded('pdo_sqlite')) {
$this->markTestSkipped('pdo_sqlite is not installed.');
}
parent::setUp();
if (! $this->_conn->getDriver() instanceof Driver) {
$this->markTestSkipped('pdo_sqlite only test.');
}
}
/**
* {@inheritdoc}
*/
protected function createDriver()
{
return new Driver();
}
}
<?php
namespace Doctrine\Tests\DBAL\Functional\Driver\PDOSqlsrv;
use Doctrine\DBAL\Driver\PDOSqlsrv\Driver;
use Doctrine\Tests\DBAL\Functional\Driver\AbstractDriverTest;
class DriverTest extends AbstractDriverTest
{
protected function setUp()
{
if (! extension_loaded('pdo_sqlsrv')) {
$this->markTestSkipped('pdo_sqlsrv is not installed.');
}
parent::setUp();
if (! $this->_conn->getDriver() instanceof Driver) {
$this->markTestSkipped('pdo_sqlsrv only test.');
}
}
/**
* {@inheritdoc}
*/
protected function createDriver()
{
return new Driver();
}
/**
* {@inheritdoc}
*/
protected function getDatabaseNameForConnectionWithoutDatabaseNameParameter()
{
return 'master';
}
}
<?php
namespace Doctrine\Tests\DBAL\Functional\Driver\SQLAnywhere;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver\SQLAnywhere\Driver;
use Doctrine\Tests\DBAL\Functional\Driver\AbstractDriverTest;
class DriverTest extends AbstractDriverTest
{
protected function setUp()
{
if (! extension_loaded('sqlanywhere')) {
$this->markTestSkipped('sqlanywhere is not installed.');
}
parent::setUp();
if (! $this->_conn->getDriver() instanceof Driver) {
$this->markTestSkipped('sqlanywhere only test.');
}
}
public function testReturnsDatabaseNameWithoutDatabaseNameParameter()
{
$params = $this->_conn->getParams();
unset($params['dbname']);
$connection = new Connection(
$params,
$this->_conn->getDriver(),
$this->_conn->getConfiguration(),
$this->_conn->getEventManager()
);
// SQL Anywhere has no "default" database. The name of the default database
// is defined on server startup and therefore can be arbitrary.
$this->assertInternalType('string', $this->driver->getDatabase($connection));
}
/**
* {@inheritdoc}
*/
protected function createDriver()
{
return new Driver();
}
}
<?php
namespace Doctrine\Tests\DBAL\Functional\Driver\SQLSrv;
use Doctrine\DBAL\Driver\SQLSrv\Driver;
use Doctrine\Tests\DBAL\Functional\Driver\AbstractDriverTest;
class DriverTest extends AbstractDriverTest
{
protected function setUp()
{
if (! extension_loaded('sqlsrv')) {
$this->markTestSkipped('sqlsrv is not installed.');
}
parent::setUp();
if (! $this->_conn->getDriver() instanceof Driver) {
$this->markTestSkipped('sqlsrv only test.');
}
}
/**
* {@inheritdoc}
*/
protected function createDriver()
{
return new Driver();
}
/**
* {@inheritdoc}
*/
protected function getDatabaseNameForConnectionWithoutDatabaseNameParameter()
{
return 'master';
}
}
......@@ -56,7 +56,7 @@ class ExceptionTest extends \Doctrine\Tests\DbalFunctionalTestCase
}
}
public function testForeignKeyContraintViolationExceptionOnInsert()
public function testForeignKeyConstraintViolationExceptionOnInsert()
{
if ( ! $this->_conn->getDatabasePlatform()->supportsForeignKeyConstraints()) {
$this->markTestSkipped("Only fails on platforms with foreign key constraints.");
......@@ -64,8 +64,14 @@ class ExceptionTest extends \Doctrine\Tests\DbalFunctionalTestCase
$this->setUpForeignKeyConstraintViolationExceptionTest();
$this->_conn->insert("constraint_error_table", array('id' => 1));
$this->_conn->insert("owning_table", array('id' => 1, 'constraint_id' => 1));
try {
$this->_conn->insert("constraint_error_table", array('id' => 1));
$this->_conn->insert("owning_table", array('id' => 1, 'constraint_id' => 1));
} catch (\Exception $exception) {
$this->tearDownForeignKeyConstraintViolationExceptionTest();
throw $exception;
}
$this->setExpectedException('\Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException');
......@@ -74,13 +80,17 @@ class ExceptionTest extends \Doctrine\Tests\DbalFunctionalTestCase
} catch (ForeignKeyConstraintViolationException $exception) {
$this->tearDownForeignKeyConstraintViolationExceptionTest();
throw $exception;
} catch (\Exception $exception) {
$this->tearDownForeignKeyConstraintViolationExceptionTest();
throw $exception;
}
$this->tearDownForeignKeyConstraintViolationExceptionTest();
}
public function testForeignKeyContraintViolationExceptionOnUpdate()
public function testForeignKeyConstraintViolationExceptionOnUpdate()
{
if ( ! $this->_conn->getDatabasePlatform()->supportsForeignKeyConstraints()) {
$this->markTestSkipped("Only fails on platforms with foreign key constraints.");
......@@ -88,8 +98,14 @@ class ExceptionTest extends \Doctrine\Tests\DbalFunctionalTestCase
$this->setUpForeignKeyConstraintViolationExceptionTest();
$this->_conn->insert("constraint_error_table", array('id' => 1));
$this->_conn->insert("owning_table", array('id' => 1, 'constraint_id' => 1));
try {
$this->_conn->insert("constraint_error_table", array('id' => 1));
$this->_conn->insert("owning_table", array('id' => 1, 'constraint_id' => 1));
} catch (\Exception $exception) {
$this->tearDownForeignKeyConstraintViolationExceptionTest();
throw $exception;
}
$this->setExpectedException('\Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException');
......@@ -98,13 +114,17 @@ class ExceptionTest extends \Doctrine\Tests\DbalFunctionalTestCase
} catch (ForeignKeyConstraintViolationException $exception) {
$this->tearDownForeignKeyConstraintViolationExceptionTest();
throw $exception;
} catch (\Exception $exception) {
$this->tearDownForeignKeyConstraintViolationExceptionTest();
throw $exception;
}
$this->tearDownForeignKeyConstraintViolationExceptionTest();
}
public function testForeignKeyContraintViolationExceptionOnDelete()
public function testForeignKeyConstraintViolationExceptionOnDelete()
{
if ( ! $this->_conn->getDatabasePlatform()->supportsForeignKeyConstraints()) {
$this->markTestSkipped("Only fails on platforms with foreign key constraints.");
......@@ -112,8 +132,14 @@ class ExceptionTest extends \Doctrine\Tests\DbalFunctionalTestCase
$this->setUpForeignKeyConstraintViolationExceptionTest();
$this->_conn->insert("constraint_error_table", array('id' => 1));
$this->_conn->insert("owning_table", array('id' => 1, 'constraint_id' => 1));
try {
$this->_conn->insert("constraint_error_table", array('id' => 1));
$this->_conn->insert("owning_table", array('id' => 1, 'constraint_id' => 1));
} catch (\Exception $exception) {
$this->tearDownForeignKeyConstraintViolationExceptionTest();
throw $exception;
}
$this->setExpectedException('\Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException');
......@@ -122,24 +148,34 @@ class ExceptionTest extends \Doctrine\Tests\DbalFunctionalTestCase
} catch (ForeignKeyConstraintViolationException $exception) {
$this->tearDownForeignKeyConstraintViolationExceptionTest();
throw $exception;
} catch (\Exception $exception) {
$this->tearDownForeignKeyConstraintViolationExceptionTest();
throw $exception;
}
$this->tearDownForeignKeyConstraintViolationExceptionTest();
}
public function testForeignKeyContraintViolationExceptionOnTruncate()
public function testForeignKeyConstraintViolationExceptionOnTruncate()
{
$platform = $this->_conn->getDatabasePlatform();
if ( ! $platform->supportsForeignKeyConstraints()) {
if (!$platform->supportsForeignKeyConstraints()) {
$this->markTestSkipped("Only fails on platforms with foreign key constraints.");
}
$this->setUpForeignKeyConstraintViolationExceptionTest();
$this->_conn->insert("constraint_error_table", array('id' => 1));
$this->_conn->insert("owning_table", array('id' => 1, 'constraint_id' => 1));
try {
$this->_conn->insert("constraint_error_table", array('id' => 1));
$this->_conn->insert("owning_table", array('id' => 1, 'constraint_id' => 1));
} catch (\Exception $exception) {
$this->tearDownForeignKeyConstraintViolationExceptionTest();
throw $exception;
}
$this->setExpectedException('\Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException');
......@@ -148,6 +184,10 @@ class ExceptionTest extends \Doctrine\Tests\DbalFunctionalTestCase
} catch (ForeignKeyConstraintViolationException $exception) {
$this->tearDownForeignKeyConstraintViolationExceptionTest();
throw $exception;
} catch (\Exception $exception) {
$this->tearDownForeignKeyConstraintViolationExceptionTest();
throw $exception;
}
......
......@@ -10,7 +10,7 @@ use Doctrine\Tests\DbalFunctionalTestCase;
*/
class MasterSlaveConnectionTest extends DbalFunctionalTestCase
{
public function setUp()
protected function setUp()
{
parent::setUp();
......
......@@ -6,7 +6,7 @@ class ModifyLimitQueryTest extends \Doctrine\Tests\DbalFunctionalTestCase
{
private static $tableCreated = false;
public function setUp()
protected function setUp()
{
parent::setUp();
......
......@@ -101,7 +101,7 @@ class NamedParametersTest extends \Doctrine\Tests\DbalFunctionalTestCase
);
}
public function setUp()
protected function setUp()
{
parent::setUp();
......
......@@ -14,7 +14,7 @@ class PortabilityTest extends \Doctrine\Tests\DbalFunctionalTestCase
{
private $portableConnection;
public function tearDown()
protected function tearDown()
{
if ($this->portableConnection) {
$this->portableConnection->close();
......
......@@ -12,7 +12,7 @@ class ResultCacheTest extends \Doctrine\Tests\DbalFunctionalTestCase
private $expectedResult = array(array('test_int' => 100, 'test_string' => 'foo'), array('test_int' => 200, 'test_string' => 'bar'), array('test_int' => 300, 'test_string' => 'baz'));
private $sqlLogger;
public function setUp()
protected function setUp()
{
parent::setUp();
......
......@@ -7,7 +7,7 @@ use Doctrine\Tests\TestUtil;
class OracleSchemaManagerTest extends SchemaManagerFunctionalTestCase
{
public function setUp()
protected function setUp()
{
parent::setUp();
......
......@@ -3,12 +3,13 @@
namespace Doctrine\Tests\DBAL\Functional\Schema;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
use Doctrine\DBAL\Schema;
use Doctrine\DBAL\Types\Type;
class PostgreSqlSchemaManagerTest extends SchemaManagerFunctionalTestCase
{
public function tearDown()
protected function tearDown()
{
parent::tearDown();
......@@ -372,6 +373,23 @@ class PostgreSqlSchemaManagerTest extends SchemaManagerFunctionalTestCase
);
}
public function testJsonbColumn()
{
if (!$this->_sm->getDatabasePlatform() instanceof PostgreSQL94Platform) {
$this->markTestSkipped("Requires PostgresSQL 9.4+");
return;
}
$table = new Schema\Table('test_jsonb');
$table->addColumn('foo', 'json_array')->setPlatformOption('jsonb', true);
$this->_sm->dropAndCreateTable($table);
/** @var Schema\Column[] $columns */
$columns = $this->_sm->listTableColumns('test_jsonb');
$this->assertEquals('json_array', $columns['foo']->getType()->getName());
$this->assertEquals(true, $columns['foo']->getPlatformOption('jsonb'));
}
}
class MoneyType extends Type
......
......@@ -53,7 +53,7 @@ class SQLServerSchemaManagerTest extends SchemaManagerFunctionalTestCase
$this->assertEquals($collation, $columns[$columnName]->getPlatformOption('collation'));
}
public function testDefaultContraints()
public function testDefaultConstraints()
{
$table = new Table('sqlsrv_default_constraints');
$table->addColumn('no_default', 'string');
......@@ -179,7 +179,7 @@ class SQLServerSchemaManagerTest extends SchemaManagerFunctionalTestCase
$table->addColumn('comment_float_0', 'integer', array('comment' => 0.0));
$table->addColumn('comment_string_0', 'integer', array('comment' => '0'));
$table->addColumn('comment', 'integer', array('comment' => 'Doctrine 0wnz you!'));
$table->addColumn('`comment_quoted`', 'integer', array('comment' => 'Doctrine 0wnz comments for explicitely quoted columns!'));
$table->addColumn('`comment_quoted`', 'integer', array('comment' => 'Doctrine 0wnz comments for explicitly quoted columns!'));
$table->addColumn('create', 'integer', array('comment' => 'Doctrine 0wnz comments for reserved keyword columns!'));
$table->addColumn('commented_type', 'object');
$table->addColumn('commented_type_with_comment', 'array', array('comment' => 'Doctrine array type.'));
......@@ -197,7 +197,7 @@ class SQLServerSchemaManagerTest extends SchemaManagerFunctionalTestCase
$this->assertEquals('0', $columns['comment_float_0']->getComment());
$this->assertEquals('0', $columns['comment_string_0']->getComment());
$this->assertEquals('Doctrine 0wnz you!', $columns['comment']->getComment());
$this->assertEquals('Doctrine 0wnz comments for explicitely quoted columns!', $columns['comment_quoted']->getComment());
$this->assertEquals('Doctrine 0wnz comments for explicitly quoted columns!', $columns['comment_quoted']->getComment());
$this->assertEquals('Doctrine 0wnz comments for reserved keyword columns!', $columns['[create]']->getComment());
$this->assertNull($columns['commented_type']->getComment());
$this->assertEquals('Doctrine array type.', $columns['commented_type_with_comment']->getComment());
......@@ -329,4 +329,30 @@ class SQLServerSchemaManagerTest extends SchemaManagerFunctionalTestCase
$this->assertNull($columns['added_commented_type']->getComment());
$this->assertEquals('666', $columns['added_commented_type_with_comment']->getComment());
}
public function testPkOrdering()
{
// SQL Server stores index column information in a system table with two
// columns that almost always have the same value: index_column_id and key_ordinal.
// The only situation when the two values doesn't match up is when a clustered index
// is declared that references columns in a different order from which they are
// declared in the table. In that case, key_ordinal != index_column_id.
// key_ordinal holds the index ordering. index_column_id is just a unique identifier
// for index columns within the given index.
$table = new Table('sqlsrv_pk_ordering');
$table->addColumn('colA', 'integer', array('notnull' => true));
$table->addColumn('colB', 'integer', array('notnull' => true));
$table->setPrimaryKey(array('colB', 'colA'));
$this->_sm->createTable($table);
$indexes = $this->_sm->listTableIndexes('sqlsrv_pk_ordering');
$this->assertCount(1, $indexes);
$firstIndex = current($indexes);
$columns = $firstIndex->getColumns();
$this->assertCount(2, $columns);
$this->assertEquals('colB', $columns[0]);
$this->assertEquals('colA', $columns[1]);
}
}
......@@ -40,6 +40,36 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest
$this->_sm = $this->_conn->getSchemaManager();
}
/**
* @group DBAL-1220
*/
public function testDropsDatabaseWithActiveConnections()
{
if (! $this->_sm->getDatabasePlatform()->supportsCreateDropDatabase()) {
$this->markTestSkipped('Cannot drop Database client side with this Driver.');
}
$this->_sm->dropAndCreateDatabase('test_drop_database');
$this->assertContains('test_drop_database', $this->_sm->listDatabases());
$params = $this->_conn->getParams();
$params['dbname'] = 'test_drop_database';
$user = isset($params['user']) ? $params['user'] : null;
$password = isset($params['password']) ? $params['password'] : null;
$connection = $this->_conn->getDriver()->connect($params, $user, $password);
$this->assertInstanceOf('Doctrine\DBAL\Driver\Connection', $connection);
$this->_sm->dropDatabase('test_drop_database');
$this->assertNotContains('test_drop_database', $this->_sm->listDatabases());
unset($connection);
}
/**
* @group DBAL-195
*/
......@@ -706,6 +736,31 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest
$this->assertInstanceOf('Doctrine\DBAL\Types\ArrayType', $columns['arr']->getType(), "The Doctrine2 should be detected from comment hint.");
}
/**
* @group DBAL-1228
*/
public function testCommentHintOnDateIntervalTypeColumn()
{
if ( ! $this->_conn->getDatabasePlatform()->supportsInlineColumnComments() &&
! $this->_conn->getDatabasePlatform()->supportsCommentOnStatement() &&
$this->_conn->getDatabasePlatform()->getName() != 'mssql') {
$this->markTestSkipped('Database does not support column comments.');
}
$table = new Table('column_dateinterval_comment');
$table->addColumn('id', 'integer', array('comment' => 'This is a comment'));
$table->addColumn('date_interval', 'dateinterval', array('comment' => 'This is a comment'));
$table->setPrimaryKey(array('id'));
$this->_sm->createTable($table);
$columns = $this->_sm->listTableColumns("column_dateinterval_comment");
$this->assertEquals(2, count($columns));
$this->assertEquals('This is a comment', $columns['id']->getComment());
$this->assertEquals('This is a comment', $columns['date_interval']->getComment(), "The Doctrine2 Typehint should be stripped from comment.");
$this->assertInstanceOf('Doctrine\DBAL\Types\DateIntervalType', $columns['date_interval']->getType(), "The Doctrine2 should be detected from comment hint.");
}
/**
* @group DBAL-825
*/
......@@ -1046,24 +1101,24 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest
$this->markTestSkipped('This test is only supported on platforms that have foreign keys.');
}
$primaryTable = new Table('test_list_index_implicit_primary');
$primaryTable = new Table('test_list_index_impl_primary');
$primaryTable->addColumn('id', 'integer');
$primaryTable->setPrimaryKey(array('id'));
$foreignTable = new Table('test_list_index_implicit_foreign');
$foreignTable = new Table('test_list_index_impl_foreign');
$foreignTable->addColumn('fk1', 'integer');
$foreignTable->addColumn('fk2', 'integer');
$foreignTable->addIndex(array('fk1'), 'explicit_fk1_idx');
$foreignTable->addForeignKeyConstraint('test_list_index_implicit_primary', array('fk1'), array('id'));
$foreignTable->addForeignKeyConstraint('test_list_index_implicit_primary', array('fk2'), array('id'));
$foreignTable->addForeignKeyConstraint('test_list_index_impl_primary', array('fk1'), array('id'));
$foreignTable->addForeignKeyConstraint('test_list_index_impl_primary', array('fk2'), array('id'));
$this->_sm->dropAndCreateTable($primaryTable);
$this->_sm->dropAndCreateTable($foreignTable);
$indexes = $this->_sm->listTableIndexes('test_list_index_implicit_foreign');
$indexes = $this->_sm->listTableIndexes('test_list_index_impl_foreign');
$this->assertCount(2, $indexes);
$this->assertArrayHasKey('explicit_fk1_idx', $indexes);
$this->assertArrayHasKey('idx_6d88c7b4fdc58d6c', $indexes);
$this->assertArrayHasKey('idx_3d6c147fdc58d6c', $indexes);
}
}
......@@ -26,6 +26,32 @@ class SqliteSchemaManagerTest extends SchemaManagerFunctionalTestCase
$this->assertEquals(false, file_exists($path));
}
/**
* @group DBAL-1220
*/
public function testDropsDatabaseWithActiveConnections()
{
$this->_sm->dropAndCreateDatabase('test_drop_database');
$this->assertFileExists('test_drop_database');
$params = $this->_conn->getParams();
$params['dbname'] = 'test_drop_database';
$user = isset($params['user']) ? $params['user'] : null;
$password = isset($params['password']) ? $params['password'] : null;
$connection = $this->_conn->getDriver()->connect($params, $user, $password);
$this->assertInstanceOf('Doctrine\DBAL\Driver\Connection', $connection);
$this->_sm->dropDatabase('test_drop_database');
$this->assertFileNotExists('test_drop_database');
unset($connection);
}
public function testRenameTable()
{
$this->createTestTable('oldname');
......
......@@ -11,7 +11,7 @@ class TableGeneratorTest extends \Doctrine\Tests\DbalFunctionalTestCase
{
private $generator;
public function setUp()
protected function setUp()
{
parent::setUp();
......
......@@ -7,7 +7,7 @@ use Doctrine\DBAL\Types\Type;
class TemporaryTableTest extends \Doctrine\Tests\DbalFunctionalTestCase
{
public function setUp()
protected function setUp()
{
parent::setUp();
try {
......@@ -17,7 +17,7 @@ class TemporaryTableTest extends \Doctrine\Tests\DbalFunctionalTestCase
}
}
public function tearDown()
protected function tearDown()
{
if ($this->_conn) {
try {
......
......@@ -10,7 +10,7 @@ use Doctrine\DBAL\Schema\Table;
*/
class DBAL510Test extends \Doctrine\Tests\DbalFunctionalTestCase
{
public function setUp()
protected function setUp()
{
parent::setUp();
......
......@@ -8,7 +8,7 @@ class TypeConversionTest extends \Doctrine\Tests\DbalFunctionalTestCase
{
static private $typeCounter = 0;
public function setUp()
protected function setUp()
{
parent::setUp();
......
......@@ -6,7 +6,7 @@ use PDO;
class WriteTest extends \Doctrine\Tests\DbalFunctionalTestCase
{
public function setUp()
protected function setUp()
{
parent::setUp();
......
......@@ -4,12 +4,12 @@ namespace Doctrine\Tests\DBAL\Logging;
class DebugStackTest extends \Doctrine\Tests\DbalTestCase
{
public function setUp()
protected function setUp()
{
$this->logger = new \Doctrine\DBAL\Logging\DebugStack();
}
public function tearDown()
protected function tearDown()
{
unset($this->logger);
}
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment