Commit 8eb2ddc8 authored by Tiago Brito's avatar Tiago Brito

Merge pull request #4 from doctrine/master

Update from main repository
parents 8065c83f 60a19586
......@@ -16,6 +16,12 @@ env:
- DB=sqlite
- DB=mysqli
before_install:
- composer self-update
install:
- composer update --prefer-source
before_script:
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS doctrine_tests;' -U postgres; fi"
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS doctrine_tests_tmp;' -U postgres; fi"
......@@ -24,8 +30,6 @@ before_script:
- sh -c "if [ '$DB' = 'pgsql' ]; then sudo service postgresql stop; sudo service postgresql start $POSTGRESQL_VERSION; fi"
- sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS doctrine_tests_tmp;create database IF NOT EXISTS doctrine_tests;'; fi"
- sh -c "if [ '$DB' = 'mysqli' ]; then mysql -e 'create database IF NOT EXISTS doctrine_tests_tmp;create database IF NOT EXISTS doctrine_tests;'; fi"
- composer self-update
- composer update --prefer-source
script: phpunit --configuration tests/travis/$DB.travis.xml
......
# Upgrade to 2.5
## BC BREAK: Doctrine\DBAL\Schema\Table
The methods ``addIndex()`` and ``addUniqueIndex()`` in ``Doctrine\DBAL\Schema\Table``
hav an additional, optional parameter. If you override these methods, you should
add this new parameter to the declaration of your overridden methods.
## BC BREAK: Doctrine\DBAL\Connection
The visibility of the property ``$_platform`` in ``Doctrine\DBAL\Connection``
......
......@@ -10,24 +10,24 @@ all the different vendors work the same.
There are many different layers that you need to take care of, here is a quick list:
1. Returning of data is handled differently across vendors.
* Returning of data is handled differently across vendors.
Oracle converts empty strings to NULL, which means a portable application
needs to convert all empty strings to null.
2. Additionally some vendors pad CHAR columns to their length, whereas others don't.
* Additionally some vendors pad CHAR columns to their length, whereas others don't.
This means all strings returned from a database have to be passed through ``rtrim()``.
3. Case-sensitivity of column keys is handled differently in all databases, even depending
* Case-sensitivity of column keys is handled differently in all databases, even depending
on identifier quoting or not. You either need to know all the rules or fix the cases
to lower/upper-case only.
4. ANSI-SQL is not implemented fully by the different vendors. You have to make
* ANSI-SQL is not implemented fully by the different vendors. You have to make
sure that the SQL you write is supported by all the vendors you are targeting.
5. Some vendors use sequences for identity generation, some auto-increment approaches.
* Some vendors use sequences for identity generation, some auto-increment approaches.
Both are completely different (pre- and post-insert access) and therefore need
special handling.
6. Every vendor has a list of keywords that are not allowed inside SQL. Some even
* Every vendor has a list of keywords that are not allowed inside SQL. Some even
allow a subset of their keywords, but not at every position.
7. Database types like dates, long text fields, booleans and many others are handled
* Database types like dates, long text fields, booleans and many others are handled
very differently between the vendors.
8. There are differences with the regard to support of positional, named or both styles of parameters
* There are differences with the regard to support of positional, named or both styles of parameters
in prepared statements between all vendors.
For each point in this list there are different abstraction layers in Doctrine DBAL that you
......
......@@ -36,9 +36,9 @@ input to any of the methods of the QueryBuilder and use the placeholder
<?php
$queryBuilder
->select('u.id', 'u.name')
->from('users', 'u')
->where('u.email = ?')
->select('id', 'name')
->from('users')
->where('email = ?')
->setParameter(0, $userInputEmail)
;
......@@ -62,8 +62,8 @@ For ``SELECT`` queries you start with invoking the ``select()`` method
<?php
$queryBuilder
->select('u.id', 'u.name')
->from('users', 'u');
->select('id', 'name')
->from('users');
For ``INSERT``, ``UPDATE`` and ``DELETE`` queries you can pass the
table name into the ``insert($tableName)``, ``update($tableName)``
......@@ -74,15 +74,15 @@ and ``delete($tableName)``:
<?php
$queryBuilder
->insert('users', 'u')
->insert('users')
;
$queryBuilder
->update('users', 'u')
->update('users')
;
$queryBuilder
->delete('users', 'u')
->delete('users')
;
You can convert a query builder to its SQL string representation
......@@ -99,15 +99,31 @@ clauses with the following API:
<?php
$queryBuilder
->select('u.id', 'u.name')
->from('users', 'u')
->where('u.email = ?')
->select('id', 'name')
->from('users')
->where('email = ?')
;
Calling ``where()`` overwrites the previous clause and you can prevent
this by combining expressions with ``andWhere()`` and ``orWhere()`` methods.
You can alternatively use expressions to generate the where clause.
Table alias
~~~~~~~~~~~
The ``from()`` method takes an optional second parameter with which a table
alias can be specified.
.. code-block:: php
<?php
$queryBuilder
->select('u.id', 'u.name')
->from('users', 'u')
->where('u.email = ?')
;
GROUP BY and HAVING Clause
~~~~~~~~~~~~~~~~~~~~~~~~~~
......@@ -121,9 +137,9 @@ previous expressions or ``addGroupBy()`` which adds to them:
<?php
$queryBuilder
->select('DATE(u.last_login) as date', 'COUNT(u.id) AS users')
->from('users', 'u')
->groupBy('DATE(u.last_login)')
->select('DATE(last_login) as date', 'COUNT(id) AS users')
->from('users')
->groupBy('DATE(last_login)')
->having('users > 10')
;
......@@ -164,10 +180,10 @@ user input and accepts SQL expressions.
<?php
$queryBuilder
->select('u.id', 'u.name', 'p.number')
->from('users', 'u')
->orderBy('u.username', 'ASC')
->addOrderBy('u.last_login', 'ASC NULLS FIRST')
->select('id', 'name')
->from('users')
->orderBy('username', 'ASC')
->addOrderBy('last_login', 'ASC NULLS FIRST')
;
Use the ``addOrderBy`` method to add instead of replace the ``orderBy`` clause.
......@@ -185,8 +201,8 @@ returned.
<?php
$queryBuilder
->select('u.id', 'u.name')
->from('users', 'u')
->select('id', 'name')
->from('users')
->setFirstResult(10)
->setMaxResults(20);
......@@ -299,12 +315,12 @@ Most notably you can use expressions to build nested And-/Or statements:
<?php
$queryBuilder
->select('u.id', 'u.name')
->from('users', 'u')
->select('id', 'name')
->from('users')
->where(
$queryBuilder->expr()->andX(
$queryBuilder->expr()->eq('u.username', '?'),
$queryBuilder->expr()->eq('u.email', '?')
$queryBuilder->expr()->eq('username', '?'),
$queryBuilder->expr()->eq('email', '?')
)
);
......@@ -327,15 +343,15 @@ in your query as a return value:
<?php
$queryBuilder
->select('u.id', 'u.name')
->from('users', 'u')
->where('u.email = ' . $queryBuilder->createNamedParameter($userInputEmail))
->select('id', 'name')
->from('users')
->where('email = ' . $queryBuilder->createNamedParameter($userInputEmail))
;
// SELECT u.id, u.name FROM users u WHERE u.email = :dcValue1
// SELECT id, name FROM users WHERE email = :dcValue1
$queryBuilder
->select('u.id', 'u.name')
->from('users', 'u')
->where('u.email = ' . $queryBuilder->createPositionalParameter($userInputEmail))
->select('id', 'name')
->from('users')
->where('email = ' . $queryBuilder->createPositionalParameter($userInputEmail))
;
// SELECT u.id, u.name FROM users u WHERE u.email = ?
// SELECT id, name FROM users WHERE email = ?
......@@ -403,6 +403,13 @@ using deserialization or ``null`` if no data is present.
properly on vendors not supporting column comments and will fall back to
``text`` type instead.
.. warning::
Because the build-in ``text`` type of PostgreSQL does not support NULL bytes,
the object type will cause deserialization errors on PostgreSQL. A workaround is
to ``serialize()``/``unserialize()`` and ``base64_encode()``/``base64_decode()`` PHP objects and store
them into a ``text`` field manually.
Mapping Matrix
--------------
......
......@@ -1589,7 +1589,9 @@ abstract class AbstractPlatform
*/
public function getCommentOnColumnSQL($tableName, $columnName, $comment)
{
return "COMMENT ON COLUMN " . $tableName . "." . $columnName . " IS '" . $comment . "'";
$comment = $this->quoteStringLiteral($comment);
return "COMMENT ON COLUMN " . $tableName . "." . $columnName . " IS " . $comment;
}
/**
......@@ -1745,11 +1747,27 @@ abstract class AbstractPlatform
}
$query = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table;
$query .= ' (' . $this->getIndexFieldDeclarationListSQL($columns) . ')';
$query .= ' (' . $this->getIndexFieldDeclarationListSQL($columns) . ')' . $this->getPartialIndexSQL($index);
return $query;
}
/**
* Adds condition for partial index.
*
* @param \Doctrine\DBAL\Schema\Index $index
*
* @return string
*/
protected function getPartialIndexSQL(Index $index)
{
if ($this->supportsPartialIndexes() && $index->hasOption('where')) {
return ' WHERE ' . $index->getOption('where');
}
return '';
}
/**
* Adds additional flags for index generation.
*
......@@ -1788,19 +1806,6 @@ abstract class AbstractPlatform
throw DBALException::notSupported(__METHOD__);
}
/**
* Checks whether the schema $schemaName needs creating.
*
* @param string $schemaName
*
* @return boolean
* @throws \Doctrine\DBAL\DBALException If not supported on this platform.
*/
public function schemaNeedsCreation($schemaName)
{
throw DBALException::notSupported(__METHOD__);
}
/**
* Quotes a string so that it can be safely used as a table or column name,
* even if it is a reserved word of the platform. This also detects identifier
......@@ -2198,7 +2203,7 @@ abstract class AbstractPlatform
}
if ($this->supportsInlineColumnComments() && isset($field['comment']) && $field['comment']) {
$columnDef .= " COMMENT '" . $field['comment'] . "'";
$columnDef .= " COMMENT " . $this->quoteStringLiteral($field['comment']);
}
return $name . ' ' . $columnDef;
......@@ -2236,9 +2241,9 @@ abstract class AbstractPlatform
if (isset($field['default'])) {
$default = " DEFAULT '".$field['default']."'";
if (isset($field['type'])) {
if (in_array((string)$field['type'], array("Integer", "BigInteger", "SmallInteger"))) {
if (in_array((string) $field['type'], array("Integer", "BigInt", "SmallInt"))) {
$default = " DEFAULT ".$field['default'];
} elseif ((string)$field['type'] == 'DateTime' && $field['default'] == $this->getCurrentTimestampSQL()) {
} elseif (in_array((string) $field['type'], array('DateTime', 'DateTimeTz')) && $field['default'] == $this->getCurrentTimestampSQL()) {
$default = " DEFAULT ".$this->getCurrentTimestampSQL();
} elseif ((string)$field['type'] == 'Time' && $field['default'] == $this->getCurrentTimeSQL()) {
$default = " DEFAULT ".$this->getCurrentTimeSQL();
......@@ -2302,7 +2307,7 @@ abstract class AbstractPlatform
return 'CONSTRAINT ' . $name . ' UNIQUE ('
. $this->getIndexFieldDeclarationListSQL($columns)
. ')';
. ')' . $this->getPartialIndexSQL($index);
}
/**
......@@ -2325,8 +2330,8 @@ abstract class AbstractPlatform
}
return $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ('
. $this->getIndexFieldDeclarationListSQL($columns)
. ')';
. $this->getIndexFieldDeclarationListSQL($columns)
. ')' . $this->getPartialIndexSQL($index);
}
/**
......@@ -2584,7 +2589,7 @@ abstract class AbstractPlatform
return $item;
}
/**
* Some platforms have boolean literals that needs to be correctly converted
*
......@@ -2678,6 +2683,18 @@ abstract class AbstractPlatform
throw DBALException::notSupported(__METHOD__);
}
/**
* Returns the SQL statement for retrieving the namespaces defined in the database.
*
* @return string
*
* @throws \Doctrine\DBAL\DBALException If not supported on this platform.
*/
public function getListNamespacesSQL()
{
throw DBALException::notSupported(__METHOD__);
}
/**
* @param string $database
*
......@@ -3008,6 +3025,16 @@ abstract class AbstractPlatform
return true;
}
/**
* Whether the platform supports partial indexes.
*
* @return boolean
*/
public function supportsPartialIndexes()
{
return false;
}
/**
* Whether the platform supports altering tables.
*
......@@ -3459,4 +3486,31 @@ abstract class AbstractPlatform
{
throw DBALException::notSupported(__METHOD__);
}
/**
* Quotes a literal string.
* This method is NOT meant to fix SQL injections!
* It is only meant to escape this platform's string literal
* quote character inside the given literal string.
*
* @param string $str The literal string to be quoted.
*
* @return string The quoted literal string.
*/
public function quoteStringLiteral($str)
{
$c = $this->getStringLiteralQuoteCharacter();
return $c . str_replace($c, $c . $c, $str) . $c;
}
/**
* Gets the character used for string literal quoting.
*
* @return string
*/
public function getStringLiteralQuoteCharacter()
{
return "'";
}
}
......@@ -275,7 +275,7 @@ class DrizzlePlatform extends AbstractPlatform
if (isset($options['comment'])) {
$comment = trim($options['comment'], " '");
$tableOptions[] = sprintf("COMMENT = '%s' ", str_replace("'", "''", $comment));
$tableOptions[] = sprintf("COMMENT = %s ", $this->quoteStringLiteral($comment));
}
// Row format
......
......@@ -494,7 +494,7 @@ class MySqlPlatform extends AbstractPlatform
if (isset($options['comment'])) {
$comment = trim($options['comment'], " '");
$tableOptions[] = sprintf("COMMENT = '%s' ", str_replace("'", "''", $comment));
$tableOptions[] = sprintf("COMMENT = %s ", $this->quoteStringLiteral($comment));
}
// Row format
......
......@@ -174,6 +174,14 @@ class PostgreSqlPlatform extends AbstractPlatform
return true;
}
/**
* {@inheritdoc}
*/
public function supportsPartialIndexes()
{
return true;
}
/**
* {@inheritdoc}
*/
......@@ -222,6 +230,14 @@ class PostgreSqlPlatform extends AbstractPlatform
return 'SELECT datname FROM pg_database';
}
/**
* {@inheritDoc}
*/
public function getListNamespacesSQL()
{
return "SELECT nspname FROM pg_namespace WHERE nspname NOT LIKE 'pg_%' AND nspname != 'information_schema'";
}
/**
* {@inheritDoc}
*/
......@@ -314,7 +330,8 @@ class PostgreSqlPlatform extends AbstractPlatform
public function getListTableIndexesSQL($table, $currentDatabase = null)
{
return "SELECT quote_ident(relname) as relname, pg_index.indisunique, pg_index.indisprimary,
pg_index.indkey, pg_index.indrelid
pg_index.indkey, pg_index.indrelid,
TRIM(BOTH '()' FROM pg_get_expr(indpred, indrelid)) AS where
FROM pg_class, pg_index
WHERE oid IN (
SELECT indexrelid
......@@ -598,7 +615,7 @@ class PostgreSqlPlatform extends AbstractPlatform
*/
public function getCommentOnColumnSQL($tableName, $columnName, $comment)
{
$comment = $comment === null ? 'NULL' : "'$comment'";
$comment = $comment === null ? 'NULL' : $this->quoteStringLiteral($comment);
return "COMMENT ON COLUMN $tableName.$columnName IS $comment";
}
......@@ -660,14 +677,6 @@ class PostgreSqlPlatform extends AbstractPlatform
return 'CREATE SCHEMA ' . $schemaName;
}
/**
* {@inheritDoc}
*/
public function schemaNeedsCreation($schemaName)
{
return !in_array($schemaName, array('default', 'public'));
}
/**
* {@inheritDoc}
*/
......
......@@ -357,7 +357,7 @@ class SQLAnywherePlatform extends AbstractPlatform
*/
public function getCommentOnColumnSQL($tableName, $columnName, $comment)
{
$comment = $comment === null ? 'NULL' : "'$comment'";
$comment = $comment === null ? 'NULL' : $this->quoteStringLiteral($comment);
return "COMMENT ON COLUMN $tableName.$columnName IS $comment";
}
......@@ -417,14 +417,6 @@ class SQLAnywherePlatform extends AbstractPlatform
return 'ALTER TABLE ' . $table . ' ADD ' . $this->getPrimaryKeyDeclarationSQL($index);
}
/**
* {@inheritdoc}
*/
public function getCreateSchemaSQL($schemaName)
{
return 'CREATE SCHEMA AUTHORIZATION ' . $schemaName;
}
/**
* {@inheritdoc}
*/
......@@ -519,14 +511,6 @@ class SQLAnywherePlatform extends AbstractPlatform
return 'DATE';
}
/**
* {@inheritdoc}
*/
public function getDefaultSchemaName()
{
return 'DBA';
}
/**
* {@inheritdoc}
*/
......@@ -1179,14 +1163,6 @@ class SQLAnywherePlatform extends AbstractPlatform
return true;
}
/**
* {@inheritdoc}
*/
public function schemaNeedsCreation($schemaName)
{
return $schemaName !== 'DBA';
}
/**
* {@inheritdoc}
*/
......@@ -1203,14 +1179,6 @@ class SQLAnywherePlatform extends AbstractPlatform
return true;
}
/**
* {@inheritdoc}
*/
public function supportsSchemas()
{
return true;
}
/**
* {@inheritdoc}
*/
......
......@@ -155,14 +155,6 @@ class SQLServerPlatform extends AbstractPlatform
return 'CREATE SCHEMA ' . $schemaName;
}
/**
* {@inheritDoc}
*/
public function schemaNeedsCreation($schemaName)
{
return $schemaName !== 'dbo';
}
/**
* {@inheritDoc}
*/
......@@ -1025,6 +1017,14 @@ class SQLServerPlatform extends AbstractPlatform
return 'SELECT * FROM SYS.DATABASES';
}
/**
* {@inheritDoc}
*/
public function getListNamespacesSQL()
{
return "SELECT name FROM SYS.SCHEMAS WHERE name NOT IN('guest', 'INFORMATION_SCHEMA', 'sys')";
}
/**
* {@inheritDoc}
*/
......@@ -1454,11 +1454,11 @@ class SQLServerPlatform extends AbstractPlatform
return " DEFAULT '" . $field['default'] . "'";
}
if (in_array((string) $field['type'], array('Integer', 'BigInteger', 'SmallInteger'))) {
if (in_array((string) $field['type'], array('Integer', 'BigInt', 'SmallInt'))) {
return " DEFAULT " . $field['default'];
}
if ((string) $field['type'] == 'DateTime' && $field['default'] == $this->getCurrentTimestampSQL()) {
if (in_array((string) $field['type'], array('DateTime', 'DateTimeTz')) && $field['default'] == $this->getCurrentTimestampSQL()) {
return " DEFAULT " . $this->getCurrentTimestampSQL();
}
......
......@@ -20,6 +20,7 @@
namespace Doctrine\DBAL\Platforms;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
......@@ -213,6 +214,11 @@ class SqlitePlatform extends AbstractPlatform
*/
public function getBigIntTypeDeclarationSQL(array $field)
{
// SQLite autoincrement is implicit for INTEGER PKs, but not for BIGINT fields.
if ( ! empty($field['autoincrement'])) {
return $this->getIntegerTypeDeclarationSQL($field);
}
return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
}
......@@ -221,6 +227,11 @@ class SqlitePlatform extends AbstractPlatform
*/
public function getTinyIntTypeDeclarationSql(array $field)
{
// SQLite autoincrement is implicit for INTEGER PKs, but not for TINYINT fields.
if ( ! empty($field['autoincrement'])) {
return $this->getIntegerTypeDeclarationSQL($field);
}
return 'TINYINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
}
......@@ -229,6 +240,11 @@ class SqlitePlatform extends AbstractPlatform
*/
public function getSmallIntTypeDeclarationSQL(array $field)
{
// SQLite autoincrement is implicit for INTEGER PKs, but not for SMALLINT fields.
if ( ! empty($field['autoincrement'])) {
return $this->getIntegerTypeDeclarationSQL($field);
}
return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
}
......@@ -237,6 +253,11 @@ class SqlitePlatform extends AbstractPlatform
*/
public function getMediumIntTypeDeclarationSql(array $field)
{
// SQLite autoincrement is implicit for INTEGER PKs, but not for MEDIUMINT fields.
if ( ! empty($field['autoincrement'])) {
return $this->getIntegerTypeDeclarationSQL($field);
}
return 'MEDIUMINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
}
......@@ -845,6 +866,29 @@ class SqlitePlatform extends AbstractPlatform
*/
private function getSimpleAlterTableSQL(TableDiff $diff)
{
// Suppress changes on integer type autoincrement columns.
foreach ($diff->changedColumns as $oldColumnName => $columnDiff) {
if ( ! $columnDiff->fromColumn instanceof Column ||
! $columnDiff->column instanceof Column ||
! $columnDiff->column->getAutoincrement() ||
! (string) $columnDiff->column->getType() === 'Integer'
) {
continue;
}
if ( ! $columnDiff->hasChanged('type') && $columnDiff->hasChanged('unsigned')) {
unset($diff->changedColumns[$oldColumnName]);
continue;
}
$fromColumnType = (string) $columnDiff->fromColumn->getType();
if ($fromColumnType === 'SmallInt' || $fromColumnType === 'BigInt') {
unset($diff->changedColumns[$oldColumnName]);
}
}
if ( ! empty($diff->renamedColumns) || ! empty($diff->addedForeignKeys) || ! empty($diff->addedIndexes)
|| ! empty($diff->changedColumns) || ! empty($diff->changedForeignKeys) || ! empty($diff->changedIndexes)
|| ! empty($diff->removedColumns) || ! empty($diff->removedForeignKeys) || ! empty($diff->removedIndexes)
......
......@@ -583,12 +583,12 @@ class QueryBuilder
* ->from('users', 'u')
* </code>
*
* @param string $from The table.
* @param string $alias The alias of the table.
* @param string $from The table.
* @param string|null $alias The alias of the table.
*
* @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance.
* @return QueryBuilder This QueryBuilder instance.
*/
public function from($from, $alias)
public function from($from, $alias = null)
{
return $this->add('from', array(
'table' => $from,
......@@ -1087,33 +1087,71 @@ class QueryBuilder
{
$query = 'SELECT ' . implode(', ', $this->sqlParts['select']) . ' FROM ';
$query .= implode(', ', $this->getFromClauses())
. ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '')
. ($this->sqlParts['groupBy'] ? ' GROUP BY ' . implode(', ', $this->sqlParts['groupBy']) : '')
. ($this->sqlParts['having'] !== null ? ' HAVING ' . ((string) $this->sqlParts['having']) : '')
. ($this->sqlParts['orderBy'] ? ' ORDER BY ' . implode(', ', $this->sqlParts['orderBy']) : '');
if ($this->isLimitQuery()) {
return $this->connection->getDatabasePlatform()->modifyLimitQuery(
$query,
$this->maxResults,
$this->firstResult
);
}
return $query;
}
/**
* @return string[]
*/
private function getFromClauses()
{
$fromClauses = array();
$knownAliases = array();
// Loop through all FROM clauses
foreach ($this->sqlParts['from'] as $from) {
$knownAliases[$from['alias']] = true;
$fromClause = $from['table'] . ' ' . $from['alias']
. $this->getSQLForJoins($from['alias'], $knownAliases);
if ($from['alias'] === null) {
$tableSql = $from['table'];
$tableReference = $from['table'];
} else {
$tableSql = $from['table'] . ' ' . $from['alias'];
$tableReference = $from['alias'];
}
$fromClauses[$from['alias']] = $fromClause;
$knownAliases[$tableReference] = true;
$fromClauses[$tableReference] = $tableSql . $this->getSQLForJoins($tableReference, $knownAliases);
}
$this->verifyAllAliasesAreKnown($knownAliases);
return $fromClauses;
}
/**
* @param array $knownAliases
*
* @throws QueryException
*/
private function verifyAllAliasesAreKnown(array $knownAliases)
{
foreach ($this->sqlParts['join'] as $fromAlias => $joins) {
if ( ! isset($knownAliases[$fromAlias])) {
throw QueryException::unknownAlias($fromAlias, array_keys($knownAliases));
}
}
}
$query .= implode(', ', $fromClauses)
. ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '')
. ($this->sqlParts['groupBy'] ? ' GROUP BY ' . implode(', ', $this->sqlParts['groupBy']) : '')
. ($this->sqlParts['having'] !== null ? ' HAVING ' . ((string) $this->sqlParts['having']) : '')
. ($this->sqlParts['orderBy'] ? ' ORDER BY ' . implode(', ', $this->sqlParts['orderBy']) : '');
return ($this->maxResults === null && $this->firstResult == null)
? $query
: $this->connection->getDatabasePlatform()->modifyLimitQuery($query, $this->maxResults, $this->firstResult);
/**
* @return bool
*/
private function isLimitQuery()
{
return $this->maxResults !== null || $this->firstResult !== null;
}
/**
......
......@@ -115,6 +115,20 @@ abstract class AbstractSchemaManager
return $this->_getPortableDatabasesList($databases);
}
/**
* Returns a list of all namespaces in the current database.
*
* @return array
*/
public function listNamespaceNames()
{
$sql = $this->_platform->getListNamespacesSQL();
$namespaces = $this->_conn->fetchAll($sql);
return $this->getPortableNamespacesList($namespaces);
}
/**
* Lists the available sequences for this connection.
*
......@@ -651,6 +665,24 @@ abstract class AbstractSchemaManager
return $list;
}
/**
* Converts a list of namespace names from the native DBMS data definition to a portable Doctrine definition.
*
* @param array $namespaces The list of namespace names in the native DBMS data definition.
*
* @return array
*/
protected function getPortableNamespacesList(array $namespaces)
{
$namespacesList = array();
foreach ($namespaces as $namespace) {
$namespacesList[] = $this->getPortableNamespaceDefinition($namespace);
}
return $namespacesList;
}
/**
* @param array $database
*
......@@ -661,6 +693,18 @@ abstract class AbstractSchemaManager
return $database;
}
/**
* Converts a namespace definition from the native DBMS data definition to a portable Doctrine definition.
*
* @param array $namespace The native DBMS namespace definition.
*
* @return mixed
*/
protected function getPortableNamespaceDefinition(array $namespace)
{
return $namespace;
}
/**
* @param array $functions
*
......@@ -819,6 +863,7 @@ abstract class AbstractSchemaManager
'unique' => $tableIndex['non_unique'] ? false : true,
'primary' => $tableIndex['primary'],
'flags' => isset($tableIndex['flags']) ? $tableIndex['flags'] : array(),
'options' => isset($tableIndex['where']) ? array('where' => $tableIndex['where']) : array(),
);
} else {
$result[$keyName]['columns'][] = $tableIndex['column_name'];
......@@ -841,7 +886,7 @@ abstract class AbstractSchemaManager
}
if ( ! $defaultPrevented) {
$index = new Index($data['name'], $data['columns'], $data['unique'], $data['primary'], $data['flags']);
$index = new Index($data['name'], $data['columns'], $data['unique'], $data['primary'], $data['flags'], $data['options']);
}
if ($index) {
......@@ -979,13 +1024,21 @@ abstract class AbstractSchemaManager
*/
public function createSchema()
{
$namespaces = array();
if ($this->_platform->supportsSchemas()) {
$namespaces = $this->listNamespaceNames();
}
$sequences = array();
if ($this->_platform->supportsSequences()) {
$sequences = $this->listSequences();
}
$tables = $this->listTables();
return new Schema($tables, $sequences, $this->createSchemaConfig());
return new Schema($tables, $sequences, $this->createSchemaConfig(), $namespaces);
}
/**
......
......@@ -62,6 +62,18 @@ class Comparator
$foreignKeysToTable = array();
foreach ($toSchema->getNamespaces() as $namespace) {
if ( ! $fromSchema->hasNamespace($namespace)) {
$diff->newNamespaces[$namespace] = $namespace;
}
}
foreach ($fromSchema->getNamespaces() as $namespace) {
if ( ! $toSchema->hasNamespace($namespace)) {
$diff->removedNamespaces[$namespace] = $namespace;
}
}
foreach ($toSchema->getTables() as $table) {
$tableName = $table->getShortestName($toSchema->getName());
if ( ! $fromSchema->hasTable($tableName)) {
......@@ -377,7 +389,7 @@ class Comparator
$changedProperties[] = $property;
}
}
if ($properties1['default'] != $properties2['default'] ||
// Null values need to be checked additionally as they tell whether to create or drop a default value.
// null != 0, null != false, null != '' etc. This affects platform's table alteration SQL generation.
......
......@@ -49,20 +49,31 @@ class Index extends AbstractAsset implements Constraint
*/
protected $_flags = array();
/**
* Platform specific options
*
* @todo $_flags should eventually be refactored into options
*
* @var array
*/
private $options = array();
/**
* @param string $indexName
* @param string[] $columns
* @param boolean $isUnique
* @param boolean $isPrimary
* @param string[] $flags
* @param array $options
*/
public function __construct($indexName, array $columns, $isUnique = false, $isPrimary = false, array $flags = array())
public function __construct($indexName, array $columns, $isUnique = false, $isPrimary = false, array $flags = array(), array $options = array())
{
$isUnique = $isUnique || $isPrimary;
$this->_setName($indexName);
$this->_isUnique = $isUnique;
$this->_isPrimary = $isPrimary;
$this->options = $options;
foreach ($columns as $column) {
$this->_addColumn($column);
......@@ -199,15 +210,23 @@ class Index extends AbstractAsset implements Constraint
$sameColumns = $this->spansColumns($other->getColumns());
if ($sameColumns) {
if ( ! $this->isUnique() && !$this->isPrimary()) {
if ( ! $this->samePartialIndex($other)) {
return false;
}
if ( ! $this->isUnique() && ! $this->isPrimary()) {
// this is a special case: If the current key is neither primary or unique, any uniqe 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.
return true;
} elseif ($other->isPrimary() != $this->isPrimary()) {
}
if ($other->isPrimary() != $this->isPrimary()) {
return false;
} elseif ($other->isUnique() != $this->isUnique()) {
}
if ($other->isUnique() != $this->isUnique()) {
return false;
}
......@@ -232,7 +251,7 @@ class Index extends AbstractAsset implements Constraint
return false;
}
if ($this->spansColumns($other->getColumns()) && ($this->isPrimary() || $this->isUnique())) {
if ($this->spansColumns($other->getColumns()) && ($this->isPrimary() || $this->isUnique()) && $this->samePartialIndex($other)) {
return true;
}
......@@ -288,4 +307,51 @@ class Index extends AbstractAsset implements Constraint
{
unset($this->_flags[strtolower($flag)]);
}
/**
* @param string $name
*
* @return boolean
*/
public function hasOption($name)
{
return isset($this->options[strtolower($name)]);
}
/**
* @param string $name
*
* @return mixed
*/
public function getOption($name)
{
return $this->options[strtolower($name)];
}
/**
* @return array
*/
public function getOptions()
{
return $this->options;
}
/**
* Return whether the two indexes have the same partial index
* @param \Doctrine\DBAL\Schema\Index $other
* @return boolean
*/
private function samePartialIndex(Index $other)
{
if ($this->hasOption('where') && $other->hasOption('where') && $this->getOption('where') == $other->getOption('where')) {
return true;
}
if ( ! $this->hasOption('where') && ! $other->hasOption('where')) {
return true;
}
return false;
}
}
......@@ -239,7 +239,8 @@ class PostgreSqlSchemaManager extends AbstractSchemaManager
'key_name' => $row['relname'],
'column_name' => trim($colRow['attname']),
'non_unique' => !$row['indisunique'],
'primary' => $row['indisprimary']
'primary' => $row['indisprimary'],
'where' => $row['where'],
);
}
}
......@@ -283,6 +284,14 @@ class PostgreSqlSchemaManager extends AbstractSchemaManager
return $list;
}
/**
* {@inheritdoc}
*/
protected function getPortableNamespaceDefinition(array $namespace)
{
return $namespace['nspname'];
}
/**
* {@inheritdoc}
*/
......
......@@ -180,6 +180,14 @@ class SQLServerSchemaManager extends AbstractSchemaManager
return $database['name'];
}
/**
* {@inheritdoc}
*/
protected function getPortableNamespaceDefinition(array $namespace)
{
return $namespace['name'];
}
/**
* {@inheritdoc}
*/
......
......@@ -21,6 +21,7 @@ namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Schema\Visitor\CreateSchemaSqlCollector;
use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector;
use Doctrine\DBAL\Schema\Visitor\NamespaceVisitor;
use Doctrine\DBAL\Schema\Visitor\Visitor;
use Doctrine\DBAL\Platforms\AbstractPlatform;
......@@ -54,6 +55,13 @@ use Doctrine\DBAL\Platforms\AbstractPlatform;
*/
class Schema extends AbstractAsset
{
/**
* The namespaces in this schema.
*
* @var array
*/
private $namespaces = array();
/**
* @var \Doctrine\DBAL\Schema\Table[]
*/
......@@ -73,15 +81,24 @@ class Schema extends AbstractAsset
* @param \Doctrine\DBAL\Schema\Table[] $tables
* @param \Doctrine\DBAL\Schema\Sequence[] $sequences
* @param \Doctrine\DBAL\Schema\SchemaConfig $schemaConfig
* @param array $namespaces
*/
public function __construct(array $tables=array(), array $sequences=array(), SchemaConfig $schemaConfig=null)
{
public function __construct(
array $tables = array(),
array $sequences = array(),
SchemaConfig $schemaConfig = null,
array $namespaces = array()
) {
if ($schemaConfig == null) {
$schemaConfig = new SchemaConfig();
}
$this->_schemaConfig = $schemaConfig;
$this->_setName($schemaConfig->getName() ?: 'public');
foreach ($namespaces as $namespace) {
$this->createNamespace($namespace);
}
foreach ($tables as $table) {
$this->_addTable($table);
}
......@@ -108,11 +125,17 @@ class Schema extends AbstractAsset
*/
protected function _addTable(Table $table)
{
$namespaceName = $table->getNamespaceName();
$tableName = $table->getFullQualifiedName($this->getName());
if (isset($this->_tables[$tableName])) {
throw SchemaException::tableAlreadyExists($tableName);
}
if ( ! $table->isInDefaultNamespace($this->getName()) && ! $this->hasNamespace($namespaceName)) {
$this->createNamespace($namespaceName);
}
$this->_tables[$tableName] = $table;
$table->setSchemaConfig($this->_schemaConfig);
}
......@@ -126,13 +149,30 @@ class Schema extends AbstractAsset
*/
protected function _addSequence(Sequence $sequence)
{
$namespaceName = $sequence->getNamespaceName();
$seqName = $sequence->getFullQualifiedName($this->getName());
if (isset($this->_sequences[$seqName])) {
throw SchemaException::sequenceAlreadyExists($seqName);
}
if ( ! $sequence->isInDefaultNamespace($this->getName()) && ! $this->hasNamespace($namespaceName)) {
$this->createNamespace($namespaceName);
}
$this->_sequences[$seqName] = $sequence;
}
/**
* Returns the namespaces of this schema.
*
* @return array A list of namespace names.
*/
public function getNamespaces()
{
return $this->namespaces;
}
/**
* Gets all tables of this schema.
*
......@@ -167,9 +207,8 @@ class Schema extends AbstractAsset
*/
private function getFullQualifiedAssetName($name)
{
if ($this->isIdentifierQuoted($name)) {
$name = $this->trimQuotes($name);
}
$name = $this->getUnquotedAssetName($name);
if (strpos($name, ".") === false) {
$name = $this->getName() . "." . $name;
}
......@@ -177,6 +216,36 @@ class Schema extends AbstractAsset
return strtolower($name);
}
/**
* Returns the unquoted representation of a given asset name.
*
* @param string $assetName Quoted or unquoted representation of an asset name.
*
* @return string
*/
private function getUnquotedAssetName($assetName)
{
if ($this->isIdentifierQuoted($assetName)) {
return $this->trimQuotes($assetName);
}
return $assetName;
}
/**
* Does this schema have a namespace with the given name?
*
* @param string $namespaceName
*
* @return boolean
*/
public function hasNamespace($namespaceName)
{
$namespaceName = strtolower($this->getUnquotedAssetName($namespaceName));
return isset($this->namespaces[$namespaceName]);
}
/**
* Does this schema have a table with the given name?
*
......@@ -238,6 +307,26 @@ class Schema extends AbstractAsset
return $this->_sequences;
}
/**
* Creates a new namespace.
*
* @param string $namespaceName The name of the namespace to create.
*
* @return \Doctrine\DBAL\Schema\Schema This schema instance.
*/
public function createNamespace($namespaceName)
{
$unquotedNamespaceName = strtolower($this->getUnquotedAssetName($namespaceName));
if (isset($this->namespaces[$unquotedNamespaceName])) {
throw SchemaException::namespaceAlreadyExists($unquotedNamespaceName);
}
$this->namespaces[$unquotedNamespaceName] = $namespaceName;
return $this;
}
/**
* Creates a new table.
*
......@@ -389,9 +478,16 @@ class Schema extends AbstractAsset
{
$visitor->acceptSchema($this);
if ($visitor instanceof NamespaceVisitor) {
foreach ($this->namespaces as $namespace) {
$visitor->acceptNamespace($namespace);
}
}
foreach ($this->_tables as $table) {
$table->visit($visitor);
}
foreach ($this->_sequences as $sequence) {
$sequence->visit($visitor);
}
......
......@@ -37,6 +37,20 @@ class SchemaDiff
*/
public $fromSchema;
/**
* All added namespaces.
*
* @var string[]
*/
public $newNamespaces = array();
/**
* All removed namespaces.
*
* @var string[]
*/
public $removedNamespaces = array();
/**
* All added tables.
*
......@@ -132,6 +146,12 @@ class SchemaDiff
{
$sql = array();
if ($platform->supportsSchemas()) {
foreach ($this->newNamespaces as $newNamespace) {
$sql[] = $platform->getCreateSchemaSQL($newNamespace);
}
}
if ($platform->supportsForeignKeyConstraints() && $saveMode == false) {
foreach ($this->orphanedForeignKeys as $orphanedForeignKey) {
$sql[] = $platform->getDropForeignKeySQL($orphanedForeignKey, $orphanedForeignKey->getLocalTableName());
......
......@@ -31,6 +31,7 @@ class SchemaException extends \Doctrine\DBAL\DBALException
const SEQUENCE_ALREADY_EXISTS = 80;
const INDEX_INVALID_NAME = 90;
const FOREIGNKEY_DOESNT_EXIST = 100;
const NAMESPACE_ALREADY_EXISTS = 110;
/**
* @param string $tableName
......@@ -85,6 +86,19 @@ class SchemaException extends \Doctrine\DBAL\DBALException
return new self("There is no column with name '$columnName' on table '$table'.", self::COLUMN_DOESNT_EXIST);
}
/**
* @param string $namespaceName
*
* @return \Doctrine\DBAL\Schema\SchemaException
*/
static public function namespaceAlreadyExists($namespaceName)
{
return new self(
sprintf("The namespace with name '%s' already exists.", $namespaceName),
self::NAMESPACE_ALREADY_EXISTS
);
}
/**
* @param string $tableName
*
......
......@@ -146,10 +146,11 @@ class Table extends AbstractAsset
* @param array $columnNames
* @param string|null $indexName
* @param array $flags
* @param array $options
*
* @return self
*/
public function addIndex(array $columnNames, $indexName = null, array $flags = array())
public function addIndex(array $columnNames, $indexName = null, array $flags = array(), array $options = array())
{
if ($indexName == null) {
$indexName = $this->_generateIdentifierName(
......@@ -157,7 +158,7 @@ class Table extends AbstractAsset
);
}
return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags));
return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options));
}
/**
......@@ -192,10 +193,11 @@ class Table extends AbstractAsset
/**
* @param array $columnNames
* @param string|null $indexName
* @param array $options
*
* @return self
*/
public function addUniqueIndex(array $columnNames, $indexName = null)
public function addUniqueIndex(array $columnNames, $indexName = null, array $options = array())
{
if ($indexName === null) {
$indexName = $this->_generateIdentifierName(
......@@ -203,7 +205,7 @@ class Table extends AbstractAsset
);
}
return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false));
return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, array(), $options));
}
/**
......@@ -277,12 +279,13 @@ class Table extends AbstractAsset
* @param boolean $isUnique
* @param boolean $isPrimary
* @param array $flags
* @param array $options
*
* @return Index
*
* @throws SchemaException
*/
private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = array())
private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = array(), array $options = array())
{
if (preg_match('(([^a-zA-Z0-9_]+))', $indexName)) {
throw SchemaException::indexNameInvalid($indexName);
......@@ -298,7 +301,7 @@ class Table extends AbstractAsset
}
}
return new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags);
return new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags, $options);
}
/**
......
......@@ -29,7 +29,7 @@ use Doctrine\DBAL\Schema\Index;
/**
* Abstract Visitor with empty methods for easy extension.
*/
class AbstractVisitor implements Visitor
class AbstractVisitor implements Visitor, NamespaceVisitor
{
/**
* @param \Doctrine\DBAL\Schema\Schema $schema
......@@ -38,6 +38,13 @@ class AbstractVisitor implements Visitor
{
}
/**
* {@inheritdoc}
*/
public function acceptNamespace($namespaceName)
{
}
/**
* @param \Doctrine\DBAL\Schema\Table $table
*/
......
......@@ -26,6 +26,11 @@ use Doctrine\DBAL\Schema\Sequence;
class CreateSchemaSqlCollector extends AbstractVisitor
{
/**
* @var array
*/
private $createNamespaceQueries = array();
/**
* @var array
*/
......@@ -58,14 +63,22 @@ class CreateSchemaSqlCollector extends AbstractVisitor
/**
* {@inheritdoc}
*/
public function acceptTable(Table $table)
public function acceptNamespace($namespaceName)
{
$namespace = $this->getNamespace($table);
if ($this->platform->supportsSchemas()) {
$this->createNamespaceQueries = array_merge(
$this->createNamespaceQueries,
(array) $this->platform->getCreateSchemaSQL($namespaceName)
);
}
}
$this->createTableQueries[$namespace] = array_merge(
$this->createTableQueries[$namespace],
$this->platform->getCreateTableSQL($table)
);
/**
* {@inheritdoc}
*/
public function acceptTable(Table $table)
{
$this->createTableQueries = array_merge($this->createTableQueries, (array) $this->platform->getCreateTableSQL($table));
}
/**
......@@ -73,11 +86,9 @@ class CreateSchemaSqlCollector extends AbstractVisitor
*/
public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint)
{
$namespace = $this->getNamespace($localTable);
if ($this->platform->supportsForeignKeyConstraints()) {
$this->createFkConstraintQueries[$namespace] = array_merge(
$this->createFkConstraintQueries[$namespace],
$this->createFkConstraintQueries = array_merge(
$this->createFkConstraintQueries,
(array) $this->platform->getCreateForeignKeySQL(
$fkConstraint, $localTable
)
......@@ -90,41 +101,18 @@ class CreateSchemaSqlCollector extends AbstractVisitor
*/
public function acceptSequence(Sequence $sequence)
{
$namespace = $this->getNamespace($sequence);
$this->createSequenceQueries[$namespace] = array_merge(
$this->createSequenceQueries[$namespace],
$this->createSequenceQueries = array_merge(
$this->createSequenceQueries,
(array)$this->platform->getCreateSequenceSQL($sequence)
);
}
/**
* @param \Doctrine\DBAL\Schema\AbstractAsset $asset
*
* @return string
*/
private function getNamespace($asset)
{
$namespace = $asset->getNamespaceName();
if ( !isset($namespace)) {
$namespace = $this->platform->supportsSchemas() ? $this->platform->getDefaultSchemaName() : 'default';
}
if ( !isset($this->createTableQueries[$namespace])) {
$this->createTableQueries[$namespace] = array();
$this->createSequenceQueries[$namespace] = array();
$this->createFkConstraintQueries[$namespace] = array();
}
return $namespace;
}
/**
* @return void
*/
public function resetQueries()
{
$this->createNamespaceQueries = array();
$this->createTableQueries = array();
$this->createSequenceQueries = array();
$this->createFkConstraintQueries = array();
......@@ -139,23 +127,20 @@ class CreateSchemaSqlCollector extends AbstractVisitor
{
$sql = array();
foreach (array_keys($this->createTableQueries) as $namespace) {
if ($this->platform->supportsSchemas() && $this->platform->schemaNeedsCreation($namespace)) {
$query = $this->platform->getCreateSchemaSQL($namespace);
$sql[] = $query;
}
foreach ($this->createNamespaceQueries as $schemaSql) {
$sql = array_merge($sql, (array) $schemaSql);
}
foreach ($this->createTableQueries as $schemaSql) {
$sql = array_merge($sql, $schemaSql);
$sql = array_merge($sql, (array) $schemaSql);
}
foreach ($this->createSequenceQueries as $schemaSql) {
$sql = array_merge($sql, $schemaSql);
$sql = array_merge($sql, (array) $schemaSql);
}
foreach ($this->createFkConstraintQueries as $schemaSql) {
$sql = array_merge($sql, $schemaSql);
$sql = array_merge($sql, (array) $schemaSql);
}
return $sql;
......
<?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\Schema\Visitor;
/**
* Visitor that can visit schema namespaces.
*
* @author Steve Müller <st.mueller@dzh-online.de>
* @link www.doctrine-project.org
* @since 2.5
*/
interface NamespaceVisitor
{
/**
* Accepts a schema namespace name.
*
* @param string $namespaceName The schema namespace name to accept.
*/
public function acceptNamespace($namespaceName);
}
......@@ -869,4 +869,22 @@ class SchemaManagerFunctionalTestCase extends \Doctrine\Tests\DbalFunctionalTest
$this->_sm->listTableForeignKeys($defaultSchemaName . '.' . $primaryTableName)
);
}
public function testCommentStringsAreQuoted()
{
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('my_table');
$table->addColumn('id', 'integer', array('comment' => "It's a comment with a quote"));
$table->setPrimaryKey(array('id'));
$this->_sm->createTable($table);
$columns = $this->_sm->listTableColumns("my_table");
$this->assertEquals("It's a comment with a quote", $columns['id']->getComment());
}
}
......@@ -131,4 +131,44 @@ EOS
$this->assertArrayHasKey('primary', $tableIndexes, 'listTableIndexes() has to return a "primary" array key.');
$this->assertEquals(array('other_id', 'id'), array_map('strtolower', $tableIndexes['primary']->getColumns()));
}
/**
* @dataProvider getDiffListIntegerAutoincrementTableColumnsData
* @group DBAL-924
*/
public function testDiffListIntegerAutoincrementTableColumns($integerType, $unsigned, $expectedComparatorDiff)
{
$tableName = 'test_int_autoincrement_table';
$offlineTable = new \Doctrine\DBAL\Schema\Table($tableName);
$offlineTable->addColumn('id', $integerType, array('autoincrement' => true, 'unsigned' => $unsigned));
$offlineTable->setPrimaryKey(array('id'));
$this->_sm->dropAndCreateTable($offlineTable);
$onlineTable = $this->_sm->listTableDetails($tableName);
$comparator = new Schema\Comparator();
$diff = $comparator->diffTable($offlineTable, $onlineTable);
if ($expectedComparatorDiff) {
$this->assertEmpty($this->_sm->getDatabasePlatform()->getAlterTableSQL($diff));
} else {
$this->assertFalse($diff);
}
}
/**
* @return array
*/
public function getDiffListIntegerAutoincrementTableColumnsData()
{
return array(
array('smallint', false, true),
array('smallint', true, true),
array('integer', false, false),
array('integer', true, true),
array('bigint', false, true),
array('bigint', true, true),
);
}
}
......@@ -136,6 +136,27 @@ abstract class AbstractPlatformTestCase extends \Doctrine\Tests\DbalTestCase
abstract public function getGenerateUniqueIndexSql();
public function testGeneratesPartialIndexesSqlOnlyWhenSupportingPartialIndexes()
{
$where = 'test IS NULL AND test2 IS NOT NULL';
$indexDef = new \Doctrine\DBAL\Schema\Index('name', array('test', 'test2'), false, false, array(), array('where' => $where));
$expected = ' WHERE ' . $where;
$actuals = array();
$actuals []= $this->_platform->getIndexDeclarationSQL('name', $indexDef);
$actuals []= $this->_platform->getUniqueConstraintDeclarationSQL('name', $indexDef);
$actuals []= $this->_platform->getCreateIndexSQL($indexDef, 'table');
foreach ($actuals as $actual) {
if ($this->_platform->supportsPartialIndexes()) {
$this->assertStringEndsWith($expected, $actual, 'WHERE clause should be present');
} else {
$this->assertStringEndsNotWith($expected, $actual, 'WHERE clause should NOT be present');
}
}
}
public function testGeneratesForeignKeyCreationSql()
{
$fk = new \Doctrine\DBAL\Schema\ForeignKeyConstraint(array('fk_name_id'), 'other_table', array('id'), '');
......@@ -419,6 +440,47 @@ abstract class AbstractPlatformTestCase extends \Doctrine\Tests\DbalTestCase
$this->markTestSkipped('Platform does not support Column comments.');
}
public function testGetDefaultValueDeclarationSQL()
{
// non-timestamp value will get single quotes
$field = array(
'type' => 'string',
'default' => 'non_timestamp'
);
$this->assertEquals(" DEFAULT 'non_timestamp'", $this->_platform->getDefaultValueDeclarationSQL($field));
}
public function testGetDefaultValueDeclarationSQLDateTime()
{
// timestamps on datetime types should not be quoted
foreach (array('datetime', 'datetimetz') as $type) {
$field = array(
'type' => Type::getType($type),
'default' => $this->_platform->getCurrentTimestampSQL()
);
$this->assertEquals(' DEFAULT ' . $this->_platform->getCurrentTimestampSQL(), $this->_platform->getDefaultValueDeclarationSQL($field));
}
}
public function testGetDefaultValueDeclarationSQLForIntegerTypes()
{
foreach(array('bigint', 'integer', 'smallint') as $type) {
$field = array(
'type' => Type::getType($type),
'default' => 1
);
$this->assertEquals(
' DEFAULT 1',
$this->_platform->getDefaultValueDeclarationSQL($field)
);
}
}
/**
* @group DBAL-45
*/
......@@ -506,14 +568,6 @@ abstract class AbstractPlatformTestCase extends \Doctrine\Tests\DbalTestCase
$this->_platform->getCreateSchemaSQL('schema');
}
/**
* @expectedException \Doctrine\DBAL\DBALException
*/
public function testSchemaNeedsCreation()
{
$this->_platform->schemaNeedsCreation('schema');
}
/**
* @group DBAL-585
*/
......@@ -821,4 +875,75 @@ abstract class AbstractPlatformTestCase extends \Doctrine\Tests\DbalTestCase
'CREATE INDEX "bar" ON "schema"."table" (id)',
);
}
protected function getStringLiteralQuoteCharacter()
{
return "'";
}
public function testGetStringLiteralQuoteCharacter()
{
$this->assertSame($this->getStringLiteralQuoteCharacter(), $this->_platform->getStringLiteralQuoteCharacter());
}
protected function getQuotedCommentOnColumnSQLWithoutQuoteCharacter()
{
return "COMMENT ON COLUMN mytable.id IS 'This is a comment'";
}
public function testGetCommentOnColumnSQLWithoutQuoteCharacter()
{
$this->assertEquals(
$this->getQuotedCommentOnColumnSQLWithoutQuoteCharacter(),
$this->_platform->getCommentOnColumnSQL('mytable', 'id', 'This is a comment')
);
}
protected function getQuotedCommentOnColumnSQLWithQuoteCharacter()
{
return "COMMENT ON COLUMN mytable.id IS 'It''s a quote !'";
}
public function testGetCommentOnColumnSQLWithQuoteCharacter()
{
$c = $this->getStringLiteralQuoteCharacter();
$this->assertEquals(
$this->getQuotedCommentOnColumnSQLWithQuoteCharacter(),
$this->_platform->getCommentOnColumnSQL('mytable', 'id', "It" . $c . "s a quote !")
);
}
protected function getQuotedStringLiteralWithoutQuoteCharacter()
{
return "'No quote'";
}
protected function getQuotedStringLiteralWithQuoteCharacter()
{
return "'It''s a quote'";
}
protected function getQuotedStringLiteralQuoteCharacter()
{
return "''''";
}
public function testQuoteStringLiteral()
{
$c = $this->getStringLiteralQuoteCharacter();
$this->assertEquals(
$this->getQuotedStringLiteralWithoutQuoteCharacter(),
$this->_platform->quoteStringLiteral('No quote')
);
$this->assertEquals(
$this->getQuotedStringLiteralWithQuoteCharacter(),
$this->_platform->quoteStringLiteral('It' . $c . 's a quote')
);
$this->assertEquals(
$this->getQuotedStringLiteralQuoteCharacter(),
$this->_platform->quoteStringLiteral($c)
);
}
}
......@@ -387,19 +387,6 @@ abstract class AbstractPostgreSqlPlatformTestCase extends AbstractPlatformTestCa
$this->assertEquals('CREATE SCHEMA ' . $schemaName, $sql);
}
public function testSchemaNeedsCreation()
{
$schemaNames = array(
'default' => false,
'public' => false,
'schema' => true,
);
foreach ($schemaNames as $name => $expected) {
$actual = $this->_platform->schemaNeedsCreation($name);
$this->assertEquals($expected, $actual);
}
}
public function testAlterDecimalPrecisionScale()
{
......@@ -671,4 +658,12 @@ abstract class AbstractPostgreSqlPlatformTestCase extends AbstractPlatformTestCa
'ALTER INDEX "schema"."foo" RENAME TO "bar"',
);
}
public function testGetNullCommentOnColumnSQL()
{
$this->assertEquals(
"COMMENT ON COLUMN mytable.id IS NULL",
$this->_platform->getCommentOnColumnSQL('mytable', 'id', null)
);
}
}
......@@ -472,18 +472,6 @@ abstract class AbstractSQLServerPlatformTestCase extends AbstractPlatformTestCas
$this->assertEquals('CREATE SCHEMA ' . $schemaName, $sql);
}
public function testSchemaNeedsCreation()
{
$schemaNames = array(
'dbo' => false,
'schema' => true,
);
foreach ($schemaNames as $name => $expected) {
$actual = $this->_platform->schemaNeedsCreation($name);
$this->assertEquals($expected, $actual);
}
}
/**
* @group DBAL-543
*/
......
......@@ -10,4 +10,9 @@ class PostgreSqlPlatformTest extends AbstractPostgreSqlPlatformTestCase
{
return new PostgreSqlPlatform;
}
public function testSupportsPartialIndexes()
{
$this->assertTrue($this->_platform->supportsPartialIndexes());
}
}
......@@ -110,30 +110,6 @@ class SQLAnywherePlatformTest extends AbstractPlatformTestCase
);
}
public function testGetCreateSchemaSQL()
{
$schemaName = 'schema';
$sql = $this->_platform->getCreateSchemaSQL($schemaName);
$this->assertEquals('CREATE SCHEMA AUTHORIZATION ' . $schemaName, $sql);
}
public function testReturnsDefaultSchemaName()
{
$this->assertSame('DBA', $this->_platform->getDefaultSchemaName());
}
public function testSchemaNeedsCreation()
{
$schemaNames = array(
'DBA' => false,
'schema' => true,
);
foreach ($schemaNames as $name => $expected) {
$actual = $this->_platform->schemaNeedsCreation($name);
$this->assertEquals($expected, $actual);
}
}
public function testHasCorrectPlatformName()
{
$this->assertEquals('sqlanywhere', $this->_platform->getName());
......@@ -724,7 +700,7 @@ class SQLAnywherePlatformTest extends AbstractPlatformTestCase
public function testSupportsSchemas()
{
$this->assertTrue($this->_platform->supportsSchemas());
$this->assertFalse($this->_platform->supportsSchemas());
}
public function testSupportsIndexes()
......@@ -846,6 +822,11 @@ class SQLAnywherePlatformTest extends AbstractPlatformTestCase
);
}
public function testGeneratesPartialIndexesSqlOnlyWhenSupportingPartialIndexes()
{
$this->markTestSkipped('Index declaration in statements like CREATE TABLE is not supported.');
}
/**
* {@inheritdoc}
*/
......
......@@ -73,6 +73,7 @@ class SqlitePlatformTest extends AbstractPlatformTestCase
/**
* @group DBAL-752
* @group DBAL-924
*/
public function testGeneratesTypeDeclarationForTinyIntegers()
{
......@@ -81,11 +82,11 @@ class SqlitePlatformTest extends AbstractPlatformTestCase
$this->_platform->getTinyIntTypeDeclarationSQL(array())
);
$this->assertEquals(
'TINYINT',
'INTEGER',
$this->_platform->getTinyIntTypeDeclarationSQL(array('autoincrement' => true))
);
$this->assertEquals(
'TINYINT',
'INTEGER',
$this->_platform->getTinyIntTypeDeclarationSQL(
array('autoincrement' => true, 'primary' => true))
);
......@@ -101,6 +102,7 @@ class SqlitePlatformTest extends AbstractPlatformTestCase
/**
* @group DBAL-752
* @group DBAL-924
*/
public function testGeneratesTypeDeclarationForSmallIntegers()
{
......@@ -109,11 +111,15 @@ class SqlitePlatformTest extends AbstractPlatformTestCase
$this->_platform->getSmallIntTypeDeclarationSQL(array())
);
$this->assertEquals(
'SMALLINT',
'INTEGER',
$this->_platform->getSmallIntTypeDeclarationSQL(array('autoincrement' => true))
);
$this->assertEquals(
'SMALLINT',
'INTEGER',
$this->_platform->getTinyIntTypeDeclarationSQL(array('autoincrement' => true, 'unsigned' => true))
);
$this->assertEquals(
'INTEGER',
$this->_platform->getSmallIntTypeDeclarationSQL(
array('autoincrement' => true, 'primary' => true))
);
......@@ -129,6 +135,7 @@ class SqlitePlatformTest extends AbstractPlatformTestCase
/**
* @group DBAL-752
* @group DBAL-924
*/
public function testGeneratesTypeDeclarationForMediumIntegers()
{
......@@ -137,11 +144,15 @@ class SqlitePlatformTest extends AbstractPlatformTestCase
$this->_platform->getMediumIntTypeDeclarationSQL(array())
);
$this->assertEquals(
'MEDIUMINT',
'INTEGER',
$this->_platform->getMediumIntTypeDeclarationSQL(array('autoincrement' => true))
);
$this->assertEquals(
'MEDIUMINT',
'INTEGER',
$this->_platform->getMediumIntTypeDeclarationSQL(array('autoincrement' => true, 'unsigned' => true))
);
$this->assertEquals(
'INTEGER',
$this->_platform->getMediumIntTypeDeclarationSQL(
array('autoincrement' => true, 'primary' => true))
);
......@@ -165,6 +176,10 @@ class SqlitePlatformTest extends AbstractPlatformTestCase
'INTEGER',
$this->_platform->getIntegerTypeDeclarationSQL(array('autoincrement' => true))
);
$this->assertEquals(
'INTEGER',
$this->_platform->getIntegerTypeDeclarationSQL(array('autoincrement' => true, 'unsigned' => true))
);
$this->assertEquals(
'INTEGER',
$this->_platform->getIntegerTypeDeclarationSQL(
......@@ -182,6 +197,7 @@ class SqlitePlatformTest extends AbstractPlatformTestCase
/**
* @group DBAL-752
* @group DBAL-924
*/
public function testGeneratesTypeDeclarationForBigIntegers()
{
......@@ -190,11 +206,15 @@ class SqlitePlatformTest extends AbstractPlatformTestCase
$this->_platform->getBigIntTypeDeclarationSQL(array())
);
$this->assertEquals(
'BIGINT',
'INTEGER',
$this->_platform->getBigIntTypeDeclarationSQL(array('autoincrement' => true))
);
$this->assertEquals(
'BIGINT',
'INTEGER',
$this->_platform->getBigIntTypeDeclarationSQL(array('autoincrement' => true, 'unsigned' => true))
);
$this->assertEquals(
'INTEGER',
$this->_platform->getBigIntTypeDeclarationSQL(
array('autoincrement' => true, 'primary' => true))
);
......
......@@ -694,4 +694,73 @@ class QueryBuilderTest extends \Doctrine\Tests\DbalTestCase
$this->assertFalse($qb->getQueryParts() === $qb_clone->getQueryParts());
$this->assertFalse($qb->getParameters() === $qb_clone->getParameters());
}
public function testSimpleSelectWithoutTableAlias()
{
$qb = new QueryBuilder($this->conn);
$qb->select('id')
->from('users');
$this->assertEquals('SELECT id FROM users', (string) $qb);
}
public function testSelectWithSimpleWhereWithoutTableAlias()
{
$qb = new QueryBuilder($this->conn);
$qb->select('id', 'name')
->from('users')
->where('awesome=9001');
$this->assertEquals("SELECT id, name FROM users WHERE awesome=9001", (string) $qb);
}
public function testComplexSelectWithoutTableAliases()
{
$qb = new QueryBuilder($this->conn);
$qb->select('DISTINCT users.id')
->from('users')
->from('articles')
->innerJoin('users', 'permissions', 'p', 'p.user_id = users.id')
->innerJoin('articles', 'comments', 'c', 'c.article_id = articles.id')
->where('users.id = articles.user_id')
->andWhere('p.read = 1');
$this->assertEquals('SELECT DISTINCT users.id FROM users INNER JOIN permissions p ON p.user_id = users.id, articles INNER JOIN comments c ON c.article_id = articles.id WHERE (users.id = articles.user_id) AND (p.read = 1)', $qb->getSQL());
}
public function testComplexSelectWithSomeTableAliases()
{
$qb = new QueryBuilder($this->conn);
$qb->select('u.id')
->from('users', 'u')
->from('articles')
->innerJoin('u', 'permissions', 'p', 'p.user_id = u.id')
->innerJoin('articles', 'comments', 'c', 'c.article_id = articles.id');
$this->assertEquals('SELECT u.id FROM users u INNER JOIN permissions p ON p.user_id = u.id, articles INNER JOIN comments c ON c.article_id = articles.id', $qb->getSQL());
}
public function testSelectAllFromTableWithoutTableAlias()
{
$qb = new QueryBuilder($this->conn);
$qb->select('users.*')
->from('users');
$this->assertEquals("SELECT users.* FROM users", (string) $qb);
}
public function testSelectAllWithoutTableAlias()
{
$qb = new QueryBuilder($this->conn);
$qb->select('*')
->from('users');
$this->assertEquals("SELECT * FROM users", (string) $qb);
}
}
......@@ -806,6 +806,33 @@ class ComparatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expected, Comparator::compareSchemas($oldSchema, $newSchema));
}
/**
* @group DBAL-669
*/
public function testNamespacesComparison()
{
$config = new SchemaConfig();
$config->setName("schemaName");
$oldSchema = new Schema(array(), array(), $config);
$oldSchema->createTable('taz');
$oldSchema->createTable('war.tab');
$newSchema= new Schema(array(), array(), $config);
$newSchema->createTable('bar.tab');
$newSchema->createTable('baz.tab');
$newSchema->createTable('war.tab');
$expected = new SchemaDiff();
$expected->fromSchema = $oldSchema;
$expected->newNamespaces = array('bar' => 'bar', 'baz' => 'baz');
$diff = Comparator::compareSchemas($oldSchema, $newSchema);
$this->assertEquals(array('bar' => 'bar', 'baz' => 'baz'), $diff->newNamespaces);
$this->assertCount(2, $diff->newTables);
}
/**
* @group DBAL-204
*/
......@@ -1035,4 +1062,49 @@ class ComparatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array(), $comparator->diffColumn($column1, $column2));
$this->assertEquals(array(), $comparator->diffColumn($column2, $column1));
}
/**
* @group DBAL-669
*/
public function testComparesNamespaces()
{
$comparator = new Comparator();
$fromSchema = $this->getMock('Doctrine\DBAL\Schema\Schema', array('getNamespaces', 'hasNamespace'));
$toSchema = $this->getMock('Doctrine\DBAL\Schema\Schema', array('getNamespaces', 'hasNamespace'));
$fromSchema->expects($this->once())
->method('getNamespaces')
->will($this->returnValue(array('foo', 'bar')));
$fromSchema->expects($this->at(0))
->method('hasNamespace')
->with('bar')
->will($this->returnValue(true));
$fromSchema->expects($this->at(1))
->method('hasNamespace')
->with('baz')
->will($this->returnValue(false));
$toSchema->expects($this->once())
->method('getNamespaces')
->will($this->returnValue(array('bar', 'baz')));
$toSchema->expects($this->at(1))
->method('hasNamespace')
->with('foo')
->will($this->returnValue(false));
$toSchema->expects($this->at(2))
->method('hasNamespace')
->with('bar')
->will($this->returnValue(true));
$expected = new SchemaDiff();
$expected->fromSchema = $fromSchema;
$expected->newNamespaces = array('baz' => 'baz');
$expected->removedNamespaces = array('foo' => 'foo');
$this->assertEquals($expected, $comparator->compare($fromSchema, $toSchema));
}
}
......@@ -8,9 +8,9 @@ use Doctrine\DBAL\Schema\Index;
class IndexTest extends \PHPUnit_Framework_TestCase
{
public function createIndex($unique=false, $primary=false)
public function createIndex($unique = false, $primary = false, $options = array())
{
return new Index("foo", array("bar", "baz"), $unique, $primary);
return new Index("foo", array("bar", "baz"), $unique, $primary, array(), $options);
}
public function testCreateIndex()
......@@ -79,6 +79,36 @@ class IndexTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($idx1->isFullfilledBy($uniq));
}
public function testFullfilledWithPartial()
{
$without = new Index('without', array('col1', 'col2'), true, false, array(), array());
$partial = new Index('partial', array('col1', 'col2'), true, false, array(), array('where' => 'col1 IS NULL'));
$another = new Index('another', array('col1', 'col2'), true, false, array(), array('where' => 'col1 IS NULL'));
$this->assertFalse($partial->isFullfilledBy($without));
$this->assertFalse($without->isFullfilledBy($partial));
$this->assertTrue($partial->isFullfilledBy($partial));
$this->assertTrue($partial->isFullfilledBy($another));
$this->assertTrue($another->isFullfilledBy($partial));
}
public function testOverrulesWithPartial()
{
$without = new Index('without', array('col1', 'col2'), true, false, array(), array());
$partial = new Index('partial', array('col1', 'col2'), true, false, array(), array('where' => 'col1 IS NULL'));
$another = new Index('another', array('col1', 'col2'), true, false, array(), array('where' => 'col1 IS NULL'));
$this->assertFalse($partial->overrules($without));
$this->assertFalse($without->overrules($partial));
$this->assertTrue($partial->overrules($partial));
$this->assertTrue($partial->overrules($another));
$this->assertTrue($another->overrules($partial));
}
/**
* @group DBAL-220
*/
......@@ -112,4 +142,18 @@ class IndexTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($index->hasColumnAtPosition("bar", 1));
$this->assertFalse($index->hasColumnAtPosition("baz", 0));
}
public function testOptions()
{
$idx1 = $this->createIndex();
$this->assertFalse($idx1->hasOption('where'));
$this->assertEmpty($idx1->getOptions());
$idx2 = $this->createIndex(false, false, array('where' => 'name IS NULL'));
$this->assertTrue($idx2->hasOption('where'));
$this->assertTrue($idx2->hasOption('WHERE'));
$this->assertSame('name IS NULL', $idx2->getOption('where'));
$this->assertSame('name IS NULL', $idx2->getOption('WHERE'));
$this->assertSame(array('where' => 'name IS NULL'), $idx2->getOptions());
}
}
......@@ -18,7 +18,7 @@ class SchemaDiffTest extends \PHPUnit_Framework_TestCase
$sql = $diff->toSql($platform);
$expected = array('drop_orphan_fk', 'alter_seq', 'drop_seq', 'create_seq', 'create_table', 'create_foreign_key', 'drop_table', 'alter_table');
$expected = array('create_schema', 'drop_orphan_fk', 'alter_seq', 'drop_seq', 'create_seq', 'create_table', 'create_foreign_key', 'drop_table', 'alter_table');
$this->assertEquals($expected, $sql);
}
......@@ -30,7 +30,7 @@ class SchemaDiffTest extends \PHPUnit_Framework_TestCase
$sql = $diff->toSaveSql($platform);
$expected = array('alter_seq', 'create_seq', 'create_table', 'create_foreign_key', 'alter_table');
$expected = array('create_schema', 'alter_seq', 'create_seq', 'create_table', 'create_foreign_key', 'alter_table');
$this->assertEquals($expected, $sql);
}
......@@ -38,6 +38,10 @@ class SchemaDiffTest extends \PHPUnit_Framework_TestCase
public function createPlatform($unsafe = false)
{
$platform = $this->getMock('Doctrine\Tests\DBAL\Mocks\MockPlatform');
$platform->expects($this->exactly(1))
->method('getCreateSchemaSQL')
->with('foo_ns')
->will($this->returnValue('create_schema'));
if ($unsafe) {
$platform->expects($this->exactly(1))
->method('getDropSequenceSql')
......@@ -76,6 +80,9 @@ class SchemaDiffTest extends \PHPUnit_Framework_TestCase
->with($this->isInstanceof('Doctrine\DBAL\Schema\ForeignKeyConstraint'), $this->equalTo('local_table'))
->will($this->returnValue('drop_orphan_fk'));
}
$platform->expects($this->exactly(1))
->method('supportsSchemas')
->will($this->returnValue(true));
$platform->expects($this->exactly(1))
->method('supportsSequences')
->will($this->returnValue(true));
......@@ -88,6 +95,8 @@ class SchemaDiffTest extends \PHPUnit_Framework_TestCase
public function createSchemaDiff()
{
$diff = new SchemaDiff();
$diff->newNamespaces['foo_ns'] = 'foo_ns';
$diff->removedNamespaces['bar_ns'] = 'bar_ns';
$diff->changedSequences['foo_seq'] = new Sequence('foo_seq');
$diff->newSequences['bar_seq'] = new Sequence('bar_seq');
$diff->removedSequences['baz_seq'] = new Sequence('baz_seq');
......
......@@ -221,4 +221,237 @@ class SchemaTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($schema->hasTable('`foo`'));
}
/**
* @group DBAL-669
*/
public function testHasNamespace()
{
$schema = new Schema();
$this->assertFalse($schema->hasNamespace('foo'));
$schema->createTable('foo');
$this->assertFalse($schema->hasNamespace('foo'));
$schema->createTable('bar.baz');
$this->assertFalse($schema->hasNamespace('baz'));
$this->assertTrue($schema->hasNamespace('bar'));
$this->assertFalse($schema->hasNamespace('tab'));
$schema->createTable('tab.taz');
$this->assertTrue($schema->hasNamespace('tab'));
}
/**
* @group DBAL-669
*/
public function testCreatesNamespace()
{
$schema = new Schema();
$this->assertFalse($schema->hasNamespace('foo'));
$schema->createNamespace('foo');
$this->assertTrue($schema->hasNamespace('foo'));
$this->assertTrue($schema->hasNamespace('FOO'));
$this->assertTrue($schema->hasNamespace('`foo`'));
$this->assertTrue($schema->hasNamespace('`FOO`'));
$schema->createNamespace('`bar`');
$this->assertTrue($schema->hasNamespace('bar'));
$this->assertTrue($schema->hasNamespace('BAR'));
$this->assertTrue($schema->hasNamespace('`bar`'));
$this->assertTrue($schema->hasNamespace('`BAR`'));
$this->assertSame(array('foo' => 'foo', 'bar' => '`bar`'), $schema->getNamespaces());
}
/**
* @group DBAL-669
*
* @expectedException \Doctrine\DBAL\Schema\SchemaException
*/
public function testThrowsExceptionOnCreatingNamespaceTwice()
{
$schema = new Schema();
$schema->createNamespace('foo');
$schema->createNamespace('foo');
}
/**
* @group DBAL-669
*/
public function testCreatesNamespaceThroughAddingTableImplicitly()
{
$schema = new Schema();
$this->assertFalse($schema->hasNamespace('foo'));
$schema->createTable('baz');
$this->assertFalse($schema->hasNamespace('foo'));
$this->assertFalse($schema->hasNamespace('baz'));
$schema->createTable('foo.bar');
$this->assertTrue($schema->hasNamespace('foo'));
$this->assertFalse($schema->hasNamespace('bar'));
$schema->createTable('`baz`.bloo');
$this->assertTrue($schema->hasNamespace('baz'));
$this->assertFalse($schema->hasNamespace('bloo'));
$schema->createTable('`baz`.moo');
$this->assertTrue($schema->hasNamespace('baz'));
$this->assertFalse($schema->hasNamespace('moo'));
}
/**
* @group DBAL-669
*/
public function testCreatesNamespaceThroughAddingSequenceImplicitly()
{
$schema = new Schema();
$this->assertFalse($schema->hasNamespace('foo'));
$schema->createSequence('baz');
$this->assertFalse($schema->hasNamespace('foo'));
$this->assertFalse($schema->hasNamespace('baz'));
$schema->createSequence('foo.bar');
$this->assertTrue($schema->hasNamespace('foo'));
$this->assertFalse($schema->hasNamespace('bar'));
$schema->createSequence('`baz`.bloo');
$this->assertTrue($schema->hasNamespace('baz'));
$this->assertFalse($schema->hasNamespace('bloo'));
$schema->createSequence('`baz`.moo');
$this->assertTrue($schema->hasNamespace('baz'));
$this->assertFalse($schema->hasNamespace('moo'));
}
/**
* @group DBAL-669
*/
public function testVisitsVisitor()
{
$schema = new Schema();
$visitor = $this->getMock('Doctrine\DBAL\Schema\Visitor\Visitor');
$schema->createNamespace('foo');
$schema->createNamespace('bar');
$schema->createTable('baz');
$schema->createTable('bla.bloo');
$schema->createSequence('moo');
$schema->createSequence('war');
$visitor->expects($this->once())
->method('acceptSchema')
->with($schema);
$visitor->expects($this->never())
->method('acceptNamespace');
$visitor->expects($this->at(1))
->method('acceptTable')
->with($schema->getTable('baz'));
$visitor->expects($this->at(2))
->method('acceptTable')
->with($schema->getTable('bla.bloo'));
$visitor->expects($this->exactly(2))
->method('acceptTable');
$visitor->expects($this->at(3))
->method('acceptSequence')
->with($schema->getSequence('moo'));
$visitor->expects($this->at(4))
->method('acceptSequence')
->with($schema->getSequence('war'));
$visitor->expects($this->exactly(2))
->method('acceptSequence');
$this->assertNull($schema->visit($visitor));
}
/**
* @group DBAL-669
*/
public function testVisitsNamespaceVisitor()
{
$schema = new Schema();
$visitor = $this->getMock('Doctrine\DBAL\Schema\Visitor\AbstractVisitor');
$schema->createNamespace('foo');
$schema->createNamespace('bar');
$schema->createTable('baz');
$schema->createTable('bla.bloo');
$schema->createSequence('moo');
$schema->createSequence('war');
$visitor->expects($this->once())
->method('acceptSchema')
->with($schema);
$visitor->expects($this->at(1))
->method('acceptNamespace')
->with('foo');
$visitor->expects($this->at(2))
->method('acceptNamespace')
->with('bar');
$visitor->expects($this->at(3))
->method('acceptNamespace')
->with('bla');
$visitor->expects($this->exactly(3))
->method('acceptNamespace');
$visitor->expects($this->at(4))
->method('acceptTable')
->with($schema->getTable('baz'));
$visitor->expects($this->at(5))
->method('acceptTable')
->with($schema->getTable('bla.bloo'));
$visitor->expects($this->exactly(2))
->method('acceptTable');
$visitor->expects($this->at(6))
->method('acceptSequence')
->with($schema->getSequence('moo'));
$visitor->expects($this->at(7))
->method('acceptSequence')
->with($schema->getSequence('war'));
$visitor->expects($this->exactly(2))
->method('acceptSequence');
$this->assertNull($schema->visit($visitor));
}
}
......@@ -2,36 +2,158 @@
namespace Doctrine\Tests\DBAL\Schema\Visitor;
use Doctrine\DBAL\Schema\Visitor\CreateSchemaSqlCollector;
use \Doctrine\DBAL\Schema\Visitor\CreateSchemaSqlCollector;
class CreateSchemaSqlCollectorTest extends \PHPUnit_Framework_TestCase
{
public function testGetQueriesForPsql()
/**
* @var \Doctrine\DBAL\Platforms\AbstractPlatform|\PHPUnit_Framework_MockObject_MockObject
*/
private $platformMock;
/**
* @var \Doctrine\DBAL\Schema\Visitor\CreateSchemaSqlCollector
*/
private $visitor;
/**
* {@inheritdoc}
*/
protected function setUp()
{
parent::setUp();
$this->platformMock = $this->getMockBuilder('Doctrine\DBAL\Platforms\AbstractPlatform')
->setMethods(
array(
'getCreateForeignKeySQL',
'getCreateSchemaSQL',
'getCreateSequenceSQL',
'getCreateTableSQL',
'supportsForeignKeyConstraints',
'supportsSchemas'
)
)
->getMockForAbstractClass();
$this->visitor = new CreateSchemaSqlCollector($this->platformMock);
foreach (array('getCreateSchemaSQL', 'getCreateTableSQL', 'getCreateForeignKeySQL', 'getCreateSequenceSQL') as $method) {
$this->platformMock->expects($this->any())
->method($method)
->will($this->returnValue('foo'));
}
}
public function testAcceptsNamespace()
{
$this->platformMock->expects($this->at(0))
->method('supportsSchemas')
->will($this->returnValue(false));
$this->platformMock->expects($this->at(1))
->method('supportsSchemas')
->will($this->returnValue(true));
$this->visitor->acceptNamespace('foo');
$this->assertEmpty($this->visitor->getQueries());
$this->visitor->acceptNamespace('foo');
$this->assertSame(array('foo'), $this->visitor->getQueries());
}
public function testAcceptsTable()
{
$platformMock = $this->getMock(
'Doctrine\DBAL\Platforms\PostgreSqlPlatform',
array('supportsSchemas', 'schemaNeedsCreation', 'getCreateTableSQL')
);
$table = $this->createTableMock();
$platformMock->expects($this->any())
->method('supportsSchemas')
->will($this->returnValue(true));
$this->visitor->acceptTable($table);
$platformMock->expects($this->any())
->method('schemaNeedsCreation')
->will($this->returnValue(true));
$this->assertSame(array('foo'), $this->visitor->getQueries());
}
$platformMock->expects($this->any())
->method('getCreateTableSQL')
->will($this->returnValue(array('foo')));
public function testAcceptsForeignKey()
{
$this->platformMock->expects($this->at(0))
->method('supportsForeignKeyConstraints')
->will($this->returnValue(false));
$tableMock = $this->getMockBuilder('\Doctrine\DBAL\Schema\Table')
->disableOriginalConstructor()
->getMock();
$this->platformMock->expects($this->at(1))
->method('supportsForeignKeyConstraints')
->will($this->returnValue(true));
$sqlCollector = new CreateSchemaSqlCollector($platformMock);
$sqlCollector->acceptTable($tableMock);
$sql = $sqlCollector->getQueries();
$this->assertEquals('CREATE SCHEMA public', $sql[0]);
$table = $this->createTableMock();
$foreignKey = $this->createForeignKeyConstraintMock();
$this->visitor->acceptForeignKey($table, $foreignKey);
$this->assertEmpty($this->visitor->getQueries());
$this->visitor->acceptForeignKey($table, $foreignKey);
$this->assertSame(array('foo'), $this->visitor->getQueries());
}
public function testAcceptsSequences()
{
$sequence = $this->createSequenceMock();
$this->visitor->acceptSequence($sequence);
$this->assertSame(array('foo'), $this->visitor->getQueries());
}
public function testResetsQueries()
{
foreach (array('supportsSchemas', 'supportsForeignKeys') as $method) {
$this->platformMock->expects($this->any())
->method($method)
->will($this->returnValue(true));
}
$table = $this->createTableMock();
$foreignKey = $this->createForeignKeyConstraintMock();
$sequence = $this->createSequenceMock();
$this->visitor->acceptNamespace('foo');
$this->visitor->acceptTable($table);
$this->visitor->acceptForeignKey($table, $foreignKey);
$this->visitor->acceptSequence($sequence);
$this->assertNotEmpty($this->visitor->getQueries());
$this->visitor->resetQueries();
$this->assertEmpty($this->visitor->getQueries());
}
/**
* @return \Doctrine\DBAL\Schema\ForeignKeyConstraint|\PHPUnit_Framework_MockObject_MockObject
*/
private function createForeignKeyConstraintMock()
{
return $this->getMockBuilder('Doctrine\DBAL\Schema\ForeignKeyConstraint')
->disableOriginalConstructor()
->getMock();
}
/**
* @return \Doctrine\DBAL\Schema\Sequence|\PHPUnit_Framework_MockObject_MockObject
*/
private function createSequenceMock()
{
return $this->getMockBuilder('Doctrine\DBAL\Schema\Sequence')
->disableOriginalConstructor()
->getMock();
}
/**
* @return \Doctrine\DBAL\Schema\Table|\PHPUnit_Framework_MockObject_MockObject
*/
private function createTableMock()
{
return $this->getMockBuilder('Doctrine\DBAL\Schema\Table')
->disableOriginalConstructor()
->getMock();
}
}
......@@ -2,7 +2,6 @@
/*
* This file bootstraps the test environment.
*/
namespace Doctrine\Tests;
error_reporting(E_ALL | E_STRICT);
......@@ -13,7 +12,7 @@ if (file_exists(__DIR__ . '/../../../vendor/autoload.php')) {
// installed as a dependency in `vendor`
$classLoader = require __DIR__ . '/../../../../../autoload.php';
} else {
throw new \Exception('Can\'t find autoload.php. Did you install dependencies via composer?');
throw new Exception('Can\'t find autoload.php. Did you install dependencies via Composer?');
}
/* @var $classLoader \Composer\Autoload\ClassLoader */
......
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