diff --git a/docs/en/reference/portability.rst b/docs/en/reference/portability.rst index 85246fa2207..f2d1d35a2de 100644 --- a/docs/en/reference/portability.rst +++ b/docs/en/reference/portability.rst @@ -4,9 +4,9 @@ Portability There are often cases when you need to write an application or library that is portable across multiple different database vendors. The Doctrine ORM is one example of such a library. It is an abstraction layer over all the currently supported vendors (MySQL, Oracle, -PostgreSQL, SQLite and Microsoft SQL Server). If you want to use the DBAL to write a portable application -or library you have to follow lots of rules to make all the different vendors work the -same. +PostgreSQL, SQLite, SAP SQL Anywhere and Microsoft SQL Server). If you want to use the DBAL +to write a portable application or library you have to follow lots of rules to make +all the different vendors work the same. There are many different layers that you need to take care of, here is a quick list: @@ -39,7 +39,7 @@ Connection Wrapper This functionality is only implemented with Doctrine 2.1 upwards. To handle all the points 1-3 you have to use a special wrapper around the database -connection. The handling and differences to tackle are all taken from the great +connection. The handling and differences to tackle are all taken from the great `PEAR MDB2 library `_. Using the following code block in your initialization will: diff --git a/lib/Doctrine/DBAL/Driver/SQLAnywhere/Driver.php b/lib/Doctrine/DBAL/Driver/SQLAnywhere/Driver.php new file mode 100644 index 00000000000..2a347d715ae --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/SQLAnywhere/Driver.php @@ -0,0 +1,134 @@ +. + */ + +namespace Doctrine\DBAL\Driver\SQLAnywhere; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Platforms\SQLAnywhere12Platform; +use Doctrine\DBAL\Schema\SQLAnywhereSchemaManager; + +/** + * A Doctrine DBAL driver for the SAP Sybase SQL Anywhere PHP extension. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class Driver implements \Doctrine\DBAL\Driver +{ + /** + * Build the connection string for given connection parameters and driver options. + * + * @param string $host Host address to connect to. + * @param integer $port Port to use for the connection (default to SQL Anywhere standard port 2683). + * @param string $server Database server name on the host to connect to. + * SQL Anywhere allows multiple database server instances on the same host, + * therefore specifying the server instance name to use is mandatory. + * @param string $dbname Name of the database on the server instance to connect to. + * @param string $username User name to use for connection authentication. + * @param string $password Password to use for connection authentication. + * @param array $driverOptions Additional parameters to use for the connection. + * + * @return string + */ + public function buildDsn($host, $port, $server, $dbname, $username = null, $password = null, array $driverOptions = array()) + { + $port = $port ?: 2683; + + return + 'LINKS=tcpip(HOST=' . $host . ';PORT=' . $port . ';DoBroadcast=Direct)' . + ';ServerName=' . $server . + ';DBN=' . $dbname . + ';UID=' . $username . + ';PWD=' . $password . + ';' . implode( + ';', + array_map(function ($key, $value) { + return $key . '=' . $value; + }, array_keys($driverOptions), $driverOptions) + ); + } + + /** + * {@inheritdoc} + * + * @throws SQLAnywhereException + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + if ( ! isset($params['host'])) { + throw new SQLAnywhereException("Missing 'host' in configuration for sqlanywhere driver."); + } + + if ( ! isset($params['server'])) { + throw new SQLAnywhereException("Missing 'server' in configuration for sqlanywhere driver."); + } + + if ( ! isset($params['dbname'])) { + throw new SQLAnywhereException("Missing 'dbname' in configuration for sqlanywhere driver."); + } + + return new SQLAnywhereConnection( + $this->buildDsn( + $params['host'], + isset($params['port']) ? $params['port'] : null, + $params['server'], + $params['dbname'], + $username, + $password, + $driverOptions + ), + isset($params['persistent']) ? $params['persistent'] : false + ); + } + + /** + * {@inheritdoc} + */ + public function getDatabase(Connection $conn) + { + $params = $conn->getParams(); + + return $params['dbname']; + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return new SQLAnywhere12Platform(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'sqlanywhere'; + } + + /** + * {@inheritdoc} + */ + public function getSchemaManager(Connection $conn) + { + return new SQLAnywhereSchemaManager($conn); + } +} diff --git a/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereConnection.php b/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereConnection.php new file mode 100644 index 00000000000..eaac9a54ae6 --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereConnection.php @@ -0,0 +1,206 @@ +. + */ + +namespace Doctrine\DBAL\Driver\SQLAnywhere; + +use Doctrine\DBAL\Driver\Connection; + +/** + * SAP Sybase SQL Anywhere implementation of the Connection interface. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class SQLAnywhereConnection implements Connection +{ + /** + * @var resource The SQL Anywhere connection resource. + */ + private $connection; + + /** + * Constructor. + * + * Connects to database with given connection string. + * + * @param string $dsn The connection string. + * @param boolean $persistent Whether or not to establish a persistent connection. + * + * @throws SQLAnywhereException + */ + public function __construct($dsn, $persistent = false) + { + $this->connection = $persistent ? @sasql_pconnect($dsn) : @sasql_connect($dsn); + + if ( ! is_resource($this->connection) || get_resource_type($this->connection) !== 'SQLAnywhere connection') { + throw SQLAnywhereException::fromSQLAnywhereError(); + } + + // Disable PHP warnings on error. + if ( ! sasql_set_option($this->connection, 'verbose_errors', false)) { + throw SQLAnywhereException::fromSQLAnywhereError($this->connection); + } + + // Enable auto committing by default. + if ( ! sasql_set_option($this->connection, 'auto_commit', 'on')) { + throw SQLAnywhereException::fromSQLAnywhereError($this->connection); + } + + // Enable exact, non-approximated row count retrieval. + if ( ! sasql_set_option($this->connection, 'row_counts', true)) { + throw SQLAnywhereException::fromSQLAnywhereError($this->connection); + } + } + + /** + * {@inheritdoc} + * + * @throws SQLAnywhereException + */ + public function beginTransaction() + { + if ( ! sasql_set_option($this->connection, 'auto_commit', 'off')) { + throw SQLAnywhereException::fromSQLAnywhereError($this->connection); + } + + return true; + } + + /** + * {@inheritdoc} + * + * @throws SQLAnywhereException + */ + public function commit() + { + if ( ! sasql_commit($this->connection)) { + throw SQLAnywhereException::fromSQLAnywhereError($this->connection); + } + + $this->endTransaction(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + return sasql_errorcode($this->connection); + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return sasql_error($this->connection); + } + + /** + * {@inheritdoc} + */ + public function exec($statement) + { + $stmt = $this->prepare($statement); + + $stmt->execute(); + + return $stmt->rowCount(); + } + + /** + * {@inheritdoc} + */ + public function lastInsertId($name = null) + { + if (null === $name) { + return sasql_insert_id($this->connection); + } + + return $this->query('SELECT ' . $name . '.CURRVAL')->fetchColumn(); + } + + /** + * {@inheritdoc} + */ + public function prepare($prepareString) + { + return new SQLAnywhereStatement($this->connection, $prepareString); + } + + /** + * {@inheritdoc} + */ + public function query() + { + $args = func_get_args(); + $stmt = $this->prepare($args[0]); + + $stmt->execute(); + + return $stmt; + } + + /** + * {@inheritdoc} + */ + public function quote($input, $type = \PDO::PARAM_STR) + { + if (is_int($input) || is_float($input)) { + return $input; + } + + return "'" . sasql_escape_string($this->connection, $input) . "'"; + } + + /** + * {@inheritdoc} + * + * @throws SQLAnywhereException + */ + public function rollBack() + { + if ( ! sasql_rollback($this->connection)) { + throw SQLAnywhereException::fromSQLAnywhereError($this->connection); + } + + $this->endTransaction(); + + return true; + } + + /** + * Ends transactional mode and enables auto commit again. + * + * @throws SQLAnywhereException + * + * @return boolean Whether or not ending transactional mode succeeded. + */ + private function endTransaction() + { + if ( ! sasql_set_option($this->connection, 'auto_commit', 'on')) { + throw SQLAnywhereException::fromSQLAnywhereError($this->connection); + } + + return true; + } +} diff --git a/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereException.php b/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereException.php new file mode 100644 index 00000000000..ccd70b3dd71 --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereException.php @@ -0,0 +1,95 @@ +. + */ + +namespace Doctrine\DBAL\Driver\SQLAnywhere; + +use Doctrine\DBAL\DBALException; + +/** + * SAP Sybase SQL Anywhere driver exception. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class SQLAnywhereException extends DBALException +{ + /** + * Helper method to turn SQL Anywhere error into exception. + * + * @param resource|null $conn The SQL Anywhere connection resource to retrieve the last error from. + * @param resource|null $stmt The SQL Anywhere statement resource to retrieve the last error from. + * + * @return SQLAnywhereException + * + * @throws \InvalidArgumentException + */ + public static function fromSQLAnywhereError($conn = null, $stmt = null) + { + if (null !== $conn && ! (is_resource($conn) && get_resource_type($conn) === 'SQLAnywhere connection')) { + throw new \InvalidArgumentException('Invalid SQL Anywhere connection resource given: ' . $conn); + } + + if (null !== $stmt && ! (is_resource($stmt) && get_resource_type($stmt) === 'SQLAnywhere statement')) { + throw new \InvalidArgumentException('Invalid SQL Anywhere statement resource given: ' . $stmt); + } + + $state = $conn ? sasql_sqlstate($conn) : sasql_sqlstate(); + $code = null; + $message = null; + + /** + * Try retrieving the last error from statement resource if given + */ + if ($stmt) { + $code = sasql_stmt_errno($stmt); + $message = sasql_stmt_error($stmt); + } + + /** + * Try retrieving the last error from the connection resource + * if either the statement resource is not given or the statement + * resource is given but the last error could not be retrieved from it (fallback). + * Depending on the type of error, it is sometimes necessary to retrieve + * it from the connection resource even though it occurred during + * a prepared statement. + */ + if ($conn && ! $code) { + $code = sasql_errorcode($conn); + $message = sasql_error($conn); + } + + /** + * Fallback mode if either no connection resource is given + * or the last error could not be retrieved from the given + * connection / statement resource. + */ + if ( ! $conn || ! $code) { + $code = sasql_errorcode(); + $message = sasql_error(); + } + + if ($message) { + return new self('SQLSTATE [' . $state . '] [' . $code . '] ' . $message, $code); + } + + return new self('SQL Anywhere error occurred but no error message was retrieved from driver.', $code); + } +} + diff --git a/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereStatement.php b/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereStatement.php new file mode 100644 index 00000000000..57adf89a912 --- /dev/null +++ b/lib/Doctrine/DBAL/Driver/SQLAnywhere/SQLAnywhereStatement.php @@ -0,0 +1,347 @@ +. + */ + +namespace Doctrine\DBAL\Driver\SQLAnywhere; + +use IteratorAggregate; +use PDO; +use Doctrine\DBAL\Driver\Statement; + +/** + * SAP SQL Anywhere implementation of the Statement interface. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class SQLAnywhereStatement implements IteratorAggregate, Statement +{ + /** + * @var resource The connection resource. + */ + private $conn; + + /** + * @var string Name of the default class to instantiate when fetch mode is \PDO::FETCH_CLASS. + */ + private $defaultFetchClass = '\stdClass'; + + /** + * @var string Constructor arguments for the default class to instantiate when fetch mode is \PDO::FETCH_CLASS. + */ + private $defaultFetchClassCtorArgs = array(); + + /** + * @var int Default fetch mode to use. + */ + private $defaultFetchMode = PDO::FETCH_BOTH; + + /** + * @var resource The result set resource to fetch. + */ + private $result; + + /** + * @var resource The prepared SQL statement to execute. + */ + private $stmt; + + /** + * Constructor. + * + * Prepares given statement for given connection. + * + * @param resource $conn The connection resource to use. + * @param string $sql The SQL statement to prepare. + * + * @throws SQLAnywhereException + */ + public function __construct($conn, $sql) + { + if ( ! is_resource($conn) || get_resource_type($conn) !== 'SQLAnywhere connection') { + throw new SQLAnywhereException('Invalid SQL Anywhere connection resource: ' . $conn); + } + + $this->conn = $conn; + $this->stmt = sasql_prepare($conn, $sql); + + if ( ! is_resource($this->stmt) || get_resource_type($this->stmt) !== 'SQLAnywhere statement') { + throw SQLAnywhereException::fromSQLAnywhereError($conn); + } + } + + /** + * {@inheritdoc} + * + * @throws SQLAnywhereException + */ + public function bindParam($column, &$variable, $type = null, $length = null) + { + switch ($type) { + case PDO::PARAM_INT: + case PDO::PARAM_BOOL: + $type = 'i'; + break; + case PDO::PARAM_LOB: + $type = 'b'; + break; + case PDO::PARAM_NULL: + case PDO::PARAM_STR: + $type = 's'; + break; + default: + throw new SQLAnywhereException('Unknown type: ' . $type); + } + + if ( ! sasql_stmt_bind_param_ex($this->stmt, $column - 1, $variable, $type, $variable === null)) { + throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function bindValue($param, $value, $type = null) + { + return $this->bindParam($param, $value, $type); + } + + /** + * {@inheritdoc} + * + * @throws SQLAnywhereException + */ + public function closeCursor() + { + if ( ! sasql_stmt_free_result($this->stmt)) { + throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function columnCount() + { + return sasql_stmt_field_count($this->stmt); + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + return sasql_stmt_errno($this->stmt); + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return sasql_stmt_error($this->stmt); + } + + /** + * {@inheritdoc} + * + * @throws SQLAnywhereException + */ + public function execute($params = null) + { + if (is_array($params)) { + $hasZeroIndex = array_key_exists(0, $params); + + foreach ($params as $key => $val) { + $key = ($hasZeroIndex && is_numeric($key)) ? $key + 1 : $key; + + $this->bindValue($key, $val); + } + } + + if ( ! sasql_stmt_execute($this->stmt)) { + throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt); + } + + $this->result = sasql_stmt_result_metadata($this->stmt); + + return true; + } + + /** + * {@inheritdoc} + * + * @throws SQLAnywhereException + */ + public function fetch($fetchMode = null) + { + if ( ! is_resource($this->result) || get_resource_type($this->result) !== 'SQLAnywhere result') { + return false; + } + + $fetchMode = $fetchMode ?: $this->defaultFetchMode; + + switch ($fetchMode) { + case PDO::FETCH_ASSOC: + return sasql_fetch_assoc($this->result); + case PDO::FETCH_BOTH: + return sasql_fetch_array($this->result, SASQL_BOTH); + case PDO::FETCH_CLASS: + $className = $this->defaultFetchClass; + $ctorArgs = $this->defaultFetchClassCtorArgs; + + if (func_num_args() >= 2) { + $args = func_get_args(); + $className = $args[1]; + $ctorArgs = isset($args[2]) ? $args[2] : array(); + } + + $result = sasql_fetch_object($this->result); + + if ($result instanceof \stdClass) { + $result = $this->castObject($result, $className, $ctorArgs); + } + + return $result; + case PDO::FETCH_NUM: + return sasql_fetch_row($this->result); + case PDO::FETCH_OBJ: + return sasql_fetch_object($this->result); + default: + throw new SQLAnywhereException('Fetch mode is not supported: ' . $fetchMode); + } + } + + /** + * {@inheritdoc} + */ + public function fetchAll($fetchMode = null) + { + $rows = array(); + + switch ($fetchMode) { + case PDO::FETCH_CLASS: + while ($row = call_user_func_array(array($this, 'fetch'), func_get_args())) { + $rows[] = $row; + } + break; + case PDO::FETCH_COLUMN: + while ($row = $this->fetchColumn()) { + $rows[] = $row; + } + break; + default: + while ($row = $this->fetch($fetchMode)) { + $rows[] = $row; + } + } + + return $rows; + } + + /** + * {@inheritdoc} + */ + public function fetchColumn($columnIndex = 0) + { + $row = $this->fetch(PDO::FETCH_NUM); + + if ($row && isset($row[$columnIndex])) { + return $row[$columnIndex]; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + return new \ArrayIterator($this->fetchAll()); + } + + /** + * {@inheritdoc} + */ + public function rowCount() + { + return sasql_stmt_affected_rows($this->stmt); + } + + /** + * {@inheritdoc} + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + $this->defaultFetchMode = $fetchMode; + $this->defaultFetchClass = $arg2 ? $arg2 : $this->defaultFetchClass; + $this->defaultFetchClassCtorArgs = $arg3 ? (array) $arg3 : $this->defaultFetchClassCtorArgs; + } + + /** + * Casts a stdClass object to the given class name mapping its' properties. + * + * @param \stdClass $sourceObject Object to cast from. + * @param string|object $destinationClass Name of the class or class instance to cast to. + * @param array $ctorArgs Arguments to use for constructing the destination class instance. + * + * @return object + * + * @throws SQLAnywhereException + */ + private function castObject(\stdClass $sourceObject, $destinationClass, array $ctorArgs = array()) + { + if ( ! is_string($destinationClass)) { + if ( ! is_object($destinationClass)) { + throw new SQLAnywhereException(sprintf( + 'Destination class has to be of type string or object, %s given.', gettype($destinationClass) + )); + } + } else { + $destinationClass = new \ReflectionClass($destinationClass); + $destinationClass = $destinationClass->newInstanceArgs($ctorArgs); + } + + $sourceReflection = new \ReflectionObject($sourceObject); + $destinationClassReflection = new \ReflectionObject($destinationClass); + + foreach ($sourceReflection->getProperties() as $sourceProperty) { + $sourceProperty->setAccessible(true); + + $name = $sourceProperty->getName(); + $value = $sourceProperty->getValue($sourceObject); + + if ($destinationClassReflection->hasProperty($name)) { + $destinationProperty = $destinationClassReflection->getProperty($name); + + $destinationProperty->setAccessible(true); + $destinationProperty->setValue($destinationClass, $value); + } else { + $destinationClass->$name = $value; + } + } + + return $destinationClass; + } +} diff --git a/lib/Doctrine/DBAL/DriverManager.php b/lib/Doctrine/DBAL/DriverManager.php index 371f5b46c3e..9d7556f3027 100644 --- a/lib/Doctrine/DBAL/DriverManager.php +++ b/lib/Doctrine/DBAL/DriverManager.php @@ -38,18 +38,19 @@ final class DriverManager * @var array */ private static $_driverMap = array( - 'pdo_mysql' => 'Doctrine\DBAL\Driver\PDOMySql\Driver', - 'pdo_sqlite' => 'Doctrine\DBAL\Driver\PDOSqlite\Driver', - 'pdo_pgsql' => 'Doctrine\DBAL\Driver\PDOPgSql\Driver', - 'pdo_oci' => 'Doctrine\DBAL\Driver\PDOOracle\Driver', - 'oci8' => 'Doctrine\DBAL\Driver\OCI8\Driver', - 'ibm_db2' => 'Doctrine\DBAL\Driver\IBMDB2\DB2Driver', - 'pdo_ibm' => 'Doctrine\DBAL\Driver\PDOIbm\Driver', - 'pdo_sqlsrv' => 'Doctrine\DBAL\Driver\PDOSqlsrv\Driver', - 'mysqli' => 'Doctrine\DBAL\Driver\Mysqli\Driver', - 'drizzle_pdo_mysql' => 'Doctrine\DBAL\Driver\DrizzlePDOMySql\Driver', - 'sqlsrv' => 'Doctrine\DBAL\Driver\SQLSrv\Driver', - ); + 'pdo_mysql' => 'Doctrine\DBAL\Driver\PDOMySql\Driver', + 'pdo_sqlite' => 'Doctrine\DBAL\Driver\PDOSqlite\Driver', + 'pdo_pgsql' => 'Doctrine\DBAL\Driver\PDOPgSql\Driver', + 'pdo_oci' => 'Doctrine\DBAL\Driver\PDOOracle\Driver', + 'oci8' => 'Doctrine\DBAL\Driver\OCI8\Driver', + 'ibm_db2' => 'Doctrine\DBAL\Driver\IBMDB2\DB2Driver', + 'pdo_ibm' => 'Doctrine\DBAL\Driver\PDOIbm\Driver', + 'pdo_sqlsrv' => 'Doctrine\DBAL\Driver\PDOSqlsrv\Driver', + 'mysqli' => 'Doctrine\DBAL\Driver\Mysqli\Driver', + 'drizzle_pdo_mysql' => 'Doctrine\DBAL\Driver\DrizzlePDOMySql\Driver', + 'sqlanywhere' => 'Doctrine\DBAL\Driver\SQLAnywhere\Driver', + 'sqlsrv' => 'Doctrine\DBAL\Driver\SQLSrv\Driver', + ); /** * Private constructor. This class cannot be instantiated. @@ -75,6 +76,7 @@ private function __construct() * pdo_ibm (unstable) * pdo_sqlsrv * mysqli + * sqlanywhere * sqlsrv * ibm_db2 (unstable) * drizzle_pdo_mysql diff --git a/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere11Keywords.php b/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere11Keywords.php new file mode 100644 index 00000000000..12eacbf9b9b --- /dev/null +++ b/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere11Keywords.php @@ -0,0 +1,55 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * SAP Sybase SQL Anywhere 11 reserved keywords list. + * + * @author Steve Müller + */ +class SQLAnywhere11Keywords extends SQLAnywhereKeywords +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'SQLAnywhere11'; + } + + /** + * {@inheritdoc} + * + * @link http://dcx.sybase.com/1100/en/dbreference_en11/alhakeywords.html + */ + protected function getKeywords() + { + return array_merge( + array_diff( + parent::getKeywords(), + array('IQ') + ), + array( + 'MERGE', + 'OPENSTRING' + ) + ); + } +} diff --git a/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere12Keywords.php b/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere12Keywords.php new file mode 100644 index 00000000000..22e04893e98 --- /dev/null +++ b/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere12Keywords.php @@ -0,0 +1,64 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * SAP Sybase SQL Anywhere 12 reserved keywords list. + * + * @author Steve Müller + */ +class SQLAnywhere12Keywords extends SQLAnywhere11Keywords +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'SQLAnywhere12'; + } + + /** + * {@inheritdoc} + * + * @link http://dcx.sybase.com/1200/en/dbreference/alhakeywords.html + */ + protected function getKeywords() + { + return array_merge( + array_diff( + parent::getKeywords(), + array( + 'INDEX_LPAREN', + 'SYNTAX_ERROR', + 'WITH_CUBE', + 'WITH_LPAREN', + 'WITH_ROLLUP' + ) + ), + array( + 'DATETIMEOFFSET', + 'LIMIT', + 'OPENXML', + 'SPATIAL', + 'TREAT' + ) + ); + } +} diff --git a/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere16Keywords.php b/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere16Keywords.php new file mode 100644 index 00000000000..4d78b84109f --- /dev/null +++ b/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhere16Keywords.php @@ -0,0 +1,56 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * SAP Sybase SQL Anywhere 16 reserved keywords list. + * + * @author Steve Müller + */ +class SQLAnywhere16Keywords extends SQLAnywhere12Keywords +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'SQLAnywhere16'; + } + + /** + * {@inheritdoc} + * + * @link http://dcx.sybase.com/index.html#sa160/en/dbreference/alhakeywords.html + */ + protected function getKeywords() + { + return array_merge( + parent::getKeywords(), + array( + 'ARRAY', + 'JSON', + 'ROW', + 'ROWTYPE', + 'UNNEST', + 'VARRAY' + ) + ); + } +} diff --git a/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhereKeywords.php b/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhereKeywords.php new file mode 100644 index 00000000000..cabf1e44914 --- /dev/null +++ b/lib/Doctrine/DBAL/Platforms/Keywords/SQLAnywhereKeywords.php @@ -0,0 +1,281 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * SAP Sybase SQL Anywhere 10 reserved keywords list. + * + * @author Steve Müller + */ +class SQLAnywhereKeywords extends KeywordList +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'SQLAnywhere'; + } + + /** + * {@inheritdoc} + * + * @link http://infocenter.sybase.com/help/topic/com.sybase.dbrfen10/pdf/dbrfen10.pdf?noframes=true + */ + protected function getKeywords() + { + return array( + 'ADD', + 'ALL', + 'ALTER', + 'AND', + 'ANY', + 'AS', + 'ASC', + 'ATTACH', + 'BACKUP', + 'BEGIN', + 'BETWEEN', + 'BIGINT', + 'BINARY', + 'BIT', + 'BOTTOM', + 'BREAK', + 'BY', + 'CALL', + 'CAPABILITY', + 'CASCADE', + 'CASE', + 'CAST', + 'CHAR', + 'CHAR_CONVERT', + 'CHARACTER', + 'CHECK', + 'CHECKPOINT', + 'CLOSE', + 'COMMENT', + 'COMMIT', + 'COMPRESSED', + 'CONFLICT', + 'CONNECT', + 'CONSTRAINT', + 'CONTAINS', + 'CONTINUE', + 'CONVERT', + 'CREATE', + 'CROSS', + 'CUBE', + 'CURRENT', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'CURSOR', + 'DATE', + 'DBSPACE', + 'DEALLOCATE', + 'DEC', + 'DECIMAL', + 'DECLARE', + 'DEFAULT', + 'DELETE', + 'DELETING', + 'DESC', + 'DETACH', + 'DISTINCT', + 'DO', + 'DOUBLE', + 'DROP', + 'DYNAMIC', + 'ELSE', + 'ELSEIF', + 'ENCRYPTED', + 'END', + 'ENDIF', + 'ESCAPE', + 'EXCEPT', + 'EXCEPTION', + 'EXEC', + 'EXECUTE', + 'EXISTING', + 'EXISTS', + 'EXTERNLOGIN', + 'FETCH', + 'FIRST', + 'FLOAT', + 'FOR', + 'FORCE', + 'FOREIGN', + 'FORWARD', + 'FROM', + 'FULL', + 'GOTO', + 'GRANT', + 'GROUP', + 'HAVING', + 'HOLDLOCK', + 'IDENTIFIED', + 'IF', + 'IN', + 'INDEX', + 'INDEX_LPAREN', + 'INNER', + 'INOUT', + 'INSENSITIVE', + 'INSERT', + 'INSERTING', + 'INSTALL', + 'INSTEAD', + 'INT', + 'INTEGER', + 'INTEGRATED', + 'INTERSECT', + 'INTO', + 'IQ', + 'IS', + 'ISOLATION', + 'JOIN', + 'KERBEROS', + 'KEY', + 'LATERAL', + 'LEFT', + 'LIKE', + 'LOCK', + 'LOGIN', + 'LONG', + 'MATCH', + 'MEMBERSHIP', + 'MESSAGE', + 'MODE', + 'MODIFY', + 'NATURAL', + 'NCHAR', + 'NEW', + 'NO', + 'NOHOLDLOCK', + 'NOT', + 'NOTIFY', + 'NULL', + 'NUMERIC', + 'NVARCHAR', + 'OF', + 'OFF', + 'ON', + 'OPEN', + 'OPTION', + 'OPTIONS', + 'OR', + 'ORDER', + 'OTHERS', + 'OUT', + 'OUTER', + 'OVER', + 'PASSTHROUGH', + 'PRECISION', + 'PREPARE', + 'PRIMARY', + 'PRINT', + 'PRIVILEGES', + 'PROC', + 'PROCEDURE', + 'PUBLICATION', + 'RAISERROR', + 'READTEXT', + 'REAL', + 'REFERENCE', + 'REFERENCES', + 'REFRESH', + 'RELEASE', + 'REMOTE', + 'REMOVE', + 'RENAME', + 'REORGANIZE', + 'RESOURCE', + 'RESTORE', + 'RESTRICT', + 'RETURN', + 'REVOKE', + 'RIGHT', + 'ROLLBACK', + 'ROLLUP', + 'SAVE', + 'SAVEPOINT', + 'SCROLL', + 'SELECT', + 'SENSITIVE', + 'SESSION', + 'SET', + 'SETUSER', + 'SHARE', + 'SMALLINT', + 'SOME', + 'SQLCODE', + 'SQLSTATE', + 'START', + 'STOP', + 'SUBTRANS', + 'SUBTRANSACTION', + 'SYNCHRONIZE', + 'SYNTAX_ERROR', + 'TABLE', + 'TEMPORARY', + 'THEN', + 'TIME', + 'TIMESTAMP', + 'TINYINT', + 'TO', + 'TOP', + 'TRAN', + 'TRIGGER', + 'TRUNCATE', + 'TSEQUAL', + 'UNBOUNDED', + 'UNION', + 'UNIQUE', + 'UNIQUEIDENTIFIER', + 'UNKNOWN', + 'UNSIGNED', + 'UPDATE', + 'UPDATING', + 'USER', + 'USING', + 'VALIDATE', + 'VALUES', + 'VARBINARY', + 'VARBIT', + 'VARCHAR', + 'VARIABLE', + 'VARYING', + 'VIEW', + 'WAIT', + 'WAITFOR', + 'WHEN', + 'WHERE', + 'WHILE', + 'WINDOW', + 'WITH', + 'WITH_CUBE', + 'WITH_LPAREN', + 'WITH_ROLLUP', + 'WITHIN', + 'WORK', + 'WRITETEXT', + 'XML' + ); + } +} diff --git a/lib/Doctrine/DBAL/Platforms/SQLAnywhere11Platform.php b/lib/Doctrine/DBAL/Platforms/SQLAnywhere11Platform.php new file mode 100644 index 00000000000..44ba707b617 --- /dev/null +++ b/lib/Doctrine/DBAL/Platforms/SQLAnywhere11Platform.php @@ -0,0 +1,47 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +/** + * The SQLAnywhere11Platform provides the behavior, features and SQL dialect of the + * SAP Sybase SQL Anywhere 11 database platform. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class SQLAnywhere11Platform extends SQLAnywherePlatform +{ + /** + * {@inheritdoc} + */ + public function getRegexpExpression() + { + return 'REGEXP'; + } + + /** + * {@inheritdoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\SQLAnywhere11Keywords'; + } +} diff --git a/lib/Doctrine/DBAL/Platforms/SQLAnywhere12Platform.php b/lib/Doctrine/DBAL/Platforms/SQLAnywhere12Platform.php new file mode 100644 index 00000000000..3e526b039e5 --- /dev/null +++ b/lib/Doctrine/DBAL/Platforms/SQLAnywhere12Platform.php @@ -0,0 +1,135 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\Sequence; + +/** + * The SQLAnywhere12Platform provides the behavior, features and SQL dialect of the + * SAP Sybase SQL Anywhere 12 database platform. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class SQLAnywhere12Platform extends SQLAnywhere11Platform +{ + /** + * {@inheritdoc} + */ + public function getCreateSequenceSQL(Sequence $sequence) + { + return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . + ' INCREMENT BY ' . $sequence->getAllocationSize() . + ' START WITH ' . $sequence->getInitialValue() . + ' MINVALUE ' . $sequence->getInitialValue(); + } + + /** + * {@inheritdoc} + */ + public function getAlterSequenceSQL(Sequence $sequence) + { + return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . + ' INCREMENT BY ' . $sequence->getAllocationSize(); + } + + /** + * {@inheritdoc} + */ + public function getDateTimeTzFormatString() + { + return 'Y-m-d H:i:s.uP'; + } + + /** + * {@inheritdoc} + */ + public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIMESTAMP WITH TIME ZONE'; + } + + /** + * {@inheritdoc} + */ + public function getDropSequenceSQL($sequence) + { + if ($sequence instanceof Sequence) { + $sequence = $sequence->getQuotedName($this); + } + + return 'DROP SEQUENCE ' . $sequence; + } + + /** + * {@inheritdoc} + */ + public function getListSequencesSQL($database) + { + return 'SELECT sequence_name, increment_by, start_with, min_value FROM SYS.SYSSEQUENCE'; + } + + /** + * {@inheritdoc} + */ + public function getSequenceNextValSQL($sequenceName) + { + return 'SELECT ' . $sequenceName . '.NEXTVAL'; + } + + /** + * {@inheritdoc} + */ + public function supportsSequences() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function getAdvancedIndexOptionsSQL(Index $index) + { + if (!$index->isPrimary() && $index->isUnique() && $index->hasFlag('with_nulls_not_distinct')) { + return ' WITH NULLS NOT DISTINCT' . parent::getAdvancedIndexOptionsSQL($index); + } + + return parent::getAdvancedIndexOptionsSQL($index); + } + + /** + * {@inheritdoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\SQLAnywhere12Keywords'; + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + parent::initializeDoctrineTypeMappings(); + $this->doctrineTypeMapping['timestamp with time zone'] = 'datetime'; + } +} diff --git a/lib/Doctrine/DBAL/Platforms/SQLAnywhere16Platform.php b/lib/Doctrine/DBAL/Platforms/SQLAnywhere16Platform.php new file mode 100644 index 00000000000..6f21d11c85c --- /dev/null +++ b/lib/Doctrine/DBAL/Platforms/SQLAnywhere16Platform.php @@ -0,0 +1,60 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\Common\Proxy\Exception\UnexpectedValueException; +use Doctrine\DBAL\Schema\Index; + +/** + * The SQLAnywhere16Platform provides the behavior, features and SQL dialect of the + * SAP Sybase SQL Anywhere 16 database platform. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class SQLAnywhere16Platform extends SQLAnywhere12Platform +{ + /** + * {@inheritdoc} + */ + protected function getAdvancedIndexOptionsSQL(Index $index) + { + if ($index->hasFlag('with_nulls_distinct') && $index->hasFlag('with_nulls_not_distinct')) { + throw new UnexpectedValueException( + 'An Index can have either have a "with_nulls_distinct" or "with_nulls_not_distinct" flag but not both.' + ); + } + + if (!$index->isPrimary() && $index->isUnique() && $index->hasFlag('with_nulls_distinct')) { + return ' WITH NULLS DISTINCT' . parent::getAdvancedIndexOptionsSQL($index); + } + + return parent::getAdvancedIndexOptionsSQL($index); + } + + /** + * {@inheritdoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\SQLAnywhere16Keywords'; + } +} diff --git a/lib/Doctrine/DBAL/Platforms/SQLAnywherePlatform.php b/lib/Doctrine/DBAL/Platforms/SQLAnywherePlatform.php new file mode 100644 index 00000000000..696c4785673 --- /dev/null +++ b/lib/Doctrine/DBAL/Platforms/SQLAnywherePlatform.php @@ -0,0 +1,1450 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\LockMode; +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\ColumnDiff; +use Doctrine\DBAL\Schema\Constraint; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\TableDiff; + +/** + * The SQLAnywherePlatform provides the behavior, features and SQL dialect of the + * SAP Sybase SQL Anywhere 10 database platform. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class SQLAnywherePlatform extends AbstractPlatform +{ + /** + * @var integer + */ + const FOREIGN_KEY_MATCH_SIMPLE = 1; + /** + * @var integer + */ + const FOREIGN_KEY_MATCH_FULL = 2; + /** + * @var integer + */ + const FOREIGN_KEY_MATCH_SIMPLE_UNIQUE = 129; + /** + * @var integer + */ + const FOREIGN_KEY_MATCH_FULL_UNIQUE = 130; + + /** + * {@inheritdoc} + * + * @throws \InvalidArgumentException + */ + public function appendLockHint($fromClause, $lockMode) + { + switch (true) { + case $lockMode === LockMode::NONE: + $lockClause = ' WITH (NOLOCK)'; + break; + case $lockMode === LockMode::PESSIMISTIC_READ: + $lockClause = ' WITH (UPDLOCK)'; + break; + case $lockMode === LockMode::PESSIMISTIC_WRITE: + $lockClause = ' WITH (XLOCK)'; + break; + default: + throw new \InvalidArgumentException('Invalid lock mode: ' . $lockMode); + } + + return $fromClause . $lockClause; + } + + /** + * {@inheritdoc} + * + * SQL Anywhere supports a maximum length of 128 bytes for identifiers. + */ + public function fixSchemaElementName($schemaElementName) + { + $maxIdentifierLength = $this->getMaxIdentifierLength(); + + if (strlen($schemaElementName) > $maxIdentifierLength) { + return substr($schemaElementName, 0, $maxIdentifierLength); + } + + return $schemaElementName; + } + + /** + * {@inheritdoc} + */ + public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) + { + $query = ''; + + if ($foreignKey->hasOption('match')) { + $query = ' MATCH ' . $this->getForeignKeyMatchClauseSQL($foreignKey->getOption('match')); + } + + $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey); + + if ($foreignKey->hasOption('check_on_commit') && (boolean) $foreignKey->getOption('check_on_commit')) { + $query .= ' CHECK ON COMMIT'; + } + + if ($foreignKey->hasOption('clustered') && (boolean) $foreignKey->getOption('clustered')) { + $query .= ' CLUSTERED'; + } + + if ($foreignKey->hasOption('for_olap_workload') && (boolean) $foreignKey->getOption('for_olap_workload')) { + $query .= ' FOR OLAP WORKLOAD'; + } + + return $query; + } + + /** + * {@inheritdoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $sql = array(); + $columnSql = array(); + $commentsSQL = array(); + $tableSql = array(); + $alterClauses = array(); + + /** @var \Doctrine\DBAL\Schema\Column $column */ + foreach ($diff->addedColumns as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $alterClauses[] = $this->getAlterTableAddColumnClause($column); + + $comment = $this->getColumnComment($column); + + if ($comment) { + $commentsSQL[] = $this->getCommentOnColumnSQL($diff->name, $column->getQuotedName($this), $comment); + } + } + + /** @var \Doctrine\DBAL\Schema\Column $column */ + foreach ($diff->removedColumns as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $alterClauses[] = $this->getAlterTableRemoveColumnClause($column); + } + + /** @var \Doctrine\DBAL\Schema\ColumnDiff $columnDiff */ + foreach ($diff->changedColumns as $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + $alterClause = $this->getAlterTableChangeColumnClause($columnDiff); + + if (null !== $alterClause) { + $alterClauses[] = $alterClause; + } + + if ($columnDiff->hasChanged('comment')) { + $column = $columnDiff->column; + + $commentsSQL[] = $this->getCommentOnColumnSQL( + $diff->name, + $column->getQuotedName($this), + $this->getColumnComment($column) + ); + } + } + + foreach ($diff->renamedColumns as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $sql[] = $this->getAlterTableClause($diff->name) . ' ' . + $this->getAlterTableRenameColumnClause($oldColumnName, $column); + } + + if ( ! $this->onSchemaAlterTable($diff, $tableSql)) { + if ( ! empty($alterClauses)) { + $sql[] = $this->getAlterTableClause($diff->name) . ' ' . implode(", ", $alterClauses); + } + + if ($diff->newName !== false) { + $sql[] = $this->getAlterTableClause($diff->name) . ' ' . + $this->getAlterTableRenameTableClause($diff->newName); + } + + $sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySQL($diff), $commentsSQL); + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * Returns the SQL clause for creating a column in a table alteration. + * + * @param Column $column The column to add. + * + * @return string + */ + protected function getAlterTableAddColumnClause(Column $column) + { + return 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); + } + + /** + * Returns the SQL clause for altering a table. + * + * @param string $tableName The quoted name of the table to alter. + * + * @return string + */ + protected function getAlterTableClause($tableName) + { + return 'ALTER TABLE ' . $tableName; + } + + /** + * Returns the SQL clause for dropping a column in a table alteration. + * + * @param Column $column The column to drop. + * + * @return string + */ + protected function getAlterTableRemoveColumnClause(Column $column) + { + return 'DROP ' . $column->getQuotedName($this); + } + + /** + * Returns the SQL clause for renaming a column in a table alteration. + * + * @param string $oldColumnName The quoted name of the column to rename. + * @param Column $column The column to rename to. + * + * @return string + */ + protected function getAlterTableRenameColumnClause($oldColumnName, Column $column) + { + return 'RENAME ' . $oldColumnName .' TO ' . $column->getQuotedName($this); + } + + /** + * Returns the SQL clause for renaming a table in a table alteration. + * + * @param string $newTableName The quoted name of the table to rename to. + * + * @return string + */ + protected function getAlterTableRenameTableClause($newTableName) + { + return 'RENAME ' . $newTableName; + } + + /** + * Returns the SQL clause for altering a column in a table alteration. + * + * This method returns null in case that only the column comment has changed. + * Changes in column comments have to be handled differently. + * + * @param ColumnDiff $columnDiff The diff of the column to alter. + * + * @return string|null + */ + protected function getAlterTableChangeColumnClause(ColumnDiff $columnDiff) + { + $column = $columnDiff->column; + + // Do not return alter clause if only comment has changed. + if ( ! ($columnDiff->hasChanged('comment') && count($columnDiff->changedProperties) === 1)) { + return 'ALTER ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); + } + } + + /** + * {@inheritdoc} + */ + public function getBigIntTypeDeclarationSQL(array $columnDef) + { + $columnDef['integer_type'] = 'BIGINT'; + + return $this->_getCommonIntegerTypeDeclarationSQL($columnDef); + } + + /** + * {@inheritdoc} + */ + public function getBlobTypeDeclarationSQL(array $field) + { + return 'LONG BINARY'; + } + + /** + * {@inheritdoc} + * + * BIT type columns require an explicit NULL declaration + * in SQL Anywhere if they shall be nullable. + * Otherwise by just omitting the NOT NULL clause, + * SQL Anywhere will declare them NOT NULL nonetheless. + */ + public function getBooleanTypeDeclarationSQL(array $columnDef) + { + $nullClause = isset($columnDef['notnull']) && (boolean) $columnDef['notnull'] === false ? ' NULL' : ''; + + return 'BIT' . $nullClause; + } + + /** + * {@inheritdoc} + */ + public function getClobTypeDeclarationSQL(array $field) + { + return 'TEXT'; + } + + /** + * {@inheritdoc} + */ + public function getCommentOnColumnSQL($tableName, $columnName, $comment) + { + $comment = $comment === null ? 'NULL' : "'$comment'"; + + return "COMMENT ON COLUMN $tableName.$columnName IS $comment"; + } + + /** + * {@inheritdoc} + */ + public function getConcatExpression() + { + return 'STRING(' . implode(', ', (array) func_get_args()) . ')'; + } + + /** + * {@inheritdoc} + */ + public function getCreateConstraintSQL(Constraint $constraint, $table) + { + if ($constraint instanceof ForeignKeyConstraint) { + return $this->getCreateForeignKeySQL($constraint, $table); + } + + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + + return 'ALTER TABLE ' . $table . + ' ADD ' . $this->getTableConstraintDeclarationSQL($constraint, $constraint->getQuotedName($this)); + } + + /** + * {@inheritdoc} + */ + public function getCreateDatabaseSQL($database) + { + return "CREATE DATABASE '$database'"; + } + + /** + * {@inheritdoc} + * + * Appends SQL Anywhere specific flags if given. + */ + public function getCreateIndexSQL(Index $index, $table) + { + return parent::getCreateIndexSQL($index, $table). $this->getAdvancedIndexOptionsSQL($index); + } + + /** + * {@inheritdoc} + */ + public function getCreatePrimaryKeySQL(Index $index, $table) + { + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + + return 'ALTER TABLE ' . $table . ' ADD ' . $this->getPrimaryKeyDeclarationSQL($index); + } + + /** + * {@inheritdoc} + */ + public function getCreateSchemaSQL($schemaName) + { + return 'CREATE SCHEMA AUTHORIZATION ' . $schemaName; + } + + /** + * {@inheritdoc} + */ + public function getCreateTemporaryTableSnippetSQL() + { + return 'CREATE ' . $this->getTemporaryTableSQL() . ' TABLE'; + } + + /** + * {@inheritdoc} + */ + public function getCreateViewSQL($name, $sql) + { + return 'CREATE VIEW ' . $name . ' AS ' . $sql; + } + + /** + * {@inheritdoc} + */ + public function getCurrentDateSQL() + { + return 'CURRENT DATE'; + } + + /** + * {@inheritdoc} + */ + public function getCurrentTimeSQL() + { + return 'CURRENT TIME'; + } + + /** + * {@inheritdoc} + */ + public function getCurrentTimestampSQL() + { + return 'CURRENT TIMESTAMP'; + } + + /** + * {@inheritdoc} + */ + public function getDateAddDaysExpression($date, $days) + { + return 'DATEADD(day, ' . $days . ', ' . $date . ')'; + } + + /** + * {@inheritdoc} + */ + public function getDateAddHourExpression($date, $hours) + { + return 'DATEADD(hour, ' . $hours . ', ' . $date . ')'; + } + + /** + * {@inheritdoc} + */ + public function getDateAddMonthExpression($date, $months) + { + return 'DATEADD(month, ' . $months . ', ' . $date . ')'; + } + + /** + * {@inheritdoc} + */ + public function getDateDiffExpression($date1, $date2) + { + return 'DATEDIFF(day, ' . $date2 . ', ' . $date1 . ')'; + } + + /** + * {@inheritdoc} + */ + public function getDateSubDaysExpression($date, $days) + { + return 'DATEADD(day, -1 * ' . $days . ', ' . $date . ')'; + } + + /** + * {@inheritdoc} + */ + public function getDateSubHourExpression($date, $hours) + { + return 'DATEADD(hour, -1 * ' . $hours . ', ' . $date . ')'; + } + + /** + * {@inheritdoc} + */ + public function getDateSubMonthExpression($date, $months) + { + return 'DATEADD(month, -1 * ' . $months . ', ' . $date . ')'; + } + + /** + * {@inheritdoc} + */ + public function getDateTimeFormatString() + { + return 'Y-m-d H:i:s.u'; + } + + /** + * {@inheritdoc} + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATETIME'; + } + + /** + * {@inheritdoc} + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATE'; + } + + /** + * {@inheritdoc} + */ + public function getDefaultSchemaName() + { + return 'DBA'; + } + + /** + * {@inheritdoc} + */ + public function getDefaultTransactionIsolationLevel() + { + return Connection::TRANSACTION_READ_UNCOMMITTED; + } + + /** + * {@inheritdoc} + */ + public function getDropDatabaseSQL($database) + { + return "DROP DATABASE '$database'"; + } + + /** + * {@inheritdoc} + */ + public function getDropIndexSQL($index, $table = null) + { + if ($index instanceof Index) { + $index = $index->getQuotedName($this); + } + + if ( ! is_string($index)) { + throw new \InvalidArgumentException( + 'SQLAnywherePlatform::getDropIndexSQL() expects $index parameter to be string or ' . + '\Doctrine\DBAL\Schema\Index.' + ); + } + + if ( ! isset($table)) { + return 'DROP INDEX ' . $index; + } + + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + + if ( ! is_string($table)) { + throw new \InvalidArgumentException( + 'SQLAnywherePlatform::getDropIndexSQL() expects $table parameter to be string or ' . + '\Doctrine\DBAL\Schema\Table.' + ); + } + + return 'DROP INDEX ' . $table . '.' . $index; + } + + /** + * {@inheritdoc} + */ + public function getDropViewSQL($name) + { + return 'DROP VIEW ' . $name; + } + + /** + * {@inheritdoc} + */ + public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey) + { + $sql = ''; + $foreignKeyName = $foreignKey->getName(); + $localColumns = $foreignKey->getQuotedLocalColumns($this); + $foreignColumns = $foreignKey->getQuotedForeignColumns($this); + $foreignTableName = $foreignKey->getQuotedForeignTableName($this); + + if ( ! empty($foreignKeyName)) { + $sql .= 'CONSTRAINT ' . $foreignKey->getQuotedName($this) . ' '; + } + + if (empty($localColumns)) { + throw new \InvalidArgumentException("Incomplete definition. 'local' required."); + } + + if (empty($foreignColumns)) { + throw new \InvalidArgumentException("Incomplete definition. 'foreign' required."); + } + + if (empty($foreignTableName)) { + throw new \InvalidArgumentException("Incomplete definition. 'foreignTable' required."); + } + + if ($foreignKey->hasOption('notnull') && (boolean) $foreignKey->getOption('notnull')) { + $sql .= 'NOT NULL '; + } + + return $sql . + 'FOREIGN KEY (' . $this->getIndexFieldDeclarationListSQL($localColumns) . ') ' . + 'REFERENCES ' . $foreignKey->getQuotedForeignTableName($this) . + ' (' . $this->getIndexFieldDeclarationListSQL($foreignColumns) . ')'; + } + + /** + * Returns foreign key MATCH clause for given type. + * + * @param integer $type The foreign key match type + * + * @return string + * + * @throws \InvalidArgumentException if unknown match type given + */ + public function getForeignKeyMatchClauseSQL($type) + { + switch ((int) $type) { + case self::FOREIGN_KEY_MATCH_SIMPLE: + return 'SIMPLE'; + break; + case self::FOREIGN_KEY_MATCH_FULL: + return 'FULL'; + break; + case self::FOREIGN_KEY_MATCH_SIMPLE_UNIQUE: + return 'UNIQUE SIMPLE'; + break; + case self::FOREIGN_KEY_MATCH_FULL_UNIQUE: + return 'UNIQUE FULL'; + default: + throw new \InvalidArgumentException('Invalid foreign key match type: ' . $type); + } + } + + /** + * {@inheritdoc} + */ + public function getForeignKeyReferentialActionSQL($action) + { + $action = strtoupper($action); + + switch ($action) { + case 'CASCADE': + case 'SET NULL': + case 'SET DEFAULT': + case 'RESTRICT': + return $action; + default: + throw new \InvalidArgumentException('Invalid foreign key action: ' . $action); + } + } + + /** + * {@inheritdoc} + */ + public function getForUpdateSQL() + { + return 'FOR UPDATE BY LOCK'; + } + + /** + * {@inheritdoc} + */ + public function getGuidExpression() + { + return 'NEWID()'; + } + + /** + * {@inheritdoc} + */ + public function getGuidTypeDeclarationSQL(array $field) + { + return 'UNIQUEIDENTIFIER'; + } + + /** + * {@inheritdoc} + */ + public function getIndexDeclarationSQL($name, Index $index) + { + // Index declaration in statements like CREATE TABLE is not supported. + throw DBALException::notSupported(__METHOD__); + } + + /** + * {@inheritdoc} + */ + public function getIntegerTypeDeclarationSQL(array $columnDef) + { + $columnDef['integer_type'] = 'INT'; + + return $this->_getCommonIntegerTypeDeclarationSQL($columnDef); + } + + /** + * {@inheritdoc} + */ + public function getListDatabasesSQL() + { + return 'SELECT db_name(number) AS name FROM sa_db_list()'; + } + + /** + * {@inheritdoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + return "SELECT col.column_name, + COALESCE(def.user_type_name, def.domain_name) AS 'type', + def.declared_width AS 'length', + def.scale, + CHARINDEX('unsigned', def.domain_name) AS 'unsigned', + IF col.nulls = 'Y' THEN 0 ELSE 1 ENDIF AS 'notnull', + col.\"default\", + def.is_autoincrement AS 'autoincrement', + rem.remarks AS 'comment' + FROM sa_describe_query('SELECT * FROM \"$table\"') AS def + JOIN SYS.SYSTABCOL AS col + ON col.table_id = def.base_table_id AND col.column_id = def.base_column_id + LEFT JOIN SYS.SYSREMARK AS rem + ON col.object_id = rem.object_id + ORDER BY def.base_column_id ASC"; + } + + /** + * {@inheritdoc} + * + * @todo Where is this used? Which information should be retrieved? + */ + public function getListTableConstraintsSQL($table) + { + return "SELECT con.* + FROM SYS.SYSCONSTRAINT AS con + JOIN SYS.SYSTABLE AS tab ON con.table_object_id = tab.object_id + WHERE tab.table_name = '$table'"; + } + + /** + * {@inheritdoc} + */ + public function getListTableForeignKeysSQL($table) + { + return "SELECT fcol.column_name AS local_column, + ptbl.table_name AS foreign_table, + pcol.column_name AS foreign_column, + idx.index_name, + IF fk.nulls = 'N' + THEN 1 + ELSE NULL + ENDIF AS notnull, + CASE ut.referential_action + WHEN 'C' THEN 'CASCADE' + WHEN 'D' THEN 'SET DEFAULT' + WHEN 'N' THEN 'SET NULL' + WHEN 'R' THEN 'RESTRICT' + ELSE NULL + END AS on_update, + CASE dt.referential_action + WHEN 'C' THEN 'CASCADE' + WHEN 'D' THEN 'SET DEFAULT' + WHEN 'N' THEN 'SET NULL' + WHEN 'R' THEN 'RESTRICT' + ELSE NULL + END AS on_delete, + IF fk.check_on_commit = 'Y' + THEN 1 + ELSE NULL + ENDIF AS check_on_commit, -- check_on_commit flag + IF ftbl.clustered_index_id = idx.index_id + THEN 1 + ELSE NULL + ENDIF AS 'clustered', -- clustered flag + IF fk.match_type = 0 + THEN NULL + ELSE fk.match_type + ENDIF AS 'match', -- match option + IF pidx.max_key_distance = 1 + THEN 1 + ELSE NULL + ENDIF AS for_olap_workload -- for_olap_workload flag + FROM SYS.SYSFKEY AS fk + JOIN SYS.SYSIDX AS idx + ON fk.foreign_table_id = idx.table_id + AND fk.foreign_index_id = idx.index_id + JOIN SYS.SYSPHYSIDX pidx + ON idx.table_id = pidx.table_id + AND idx.phys_index_id = pidx.phys_index_id + JOIN SYS.SYSTAB AS ptbl + ON fk.primary_table_id = ptbl.table_id + JOIN SYS.SYSTAB AS ftbl + ON fk.foreign_table_id = ftbl.table_id + JOIN SYS.SYSIDXCOL AS idxcol + ON idx.table_id = idxcol.table_id + AND idx.index_id = idxcol.index_id + JOIN SYS.SYSTABCOL AS pcol + ON ptbl.table_id = pcol.table_id + AND idxcol.primary_column_id = pcol.column_id + JOIN SYS.SYSTABCOL AS fcol + ON ftbl.table_id = fcol.table_id + AND idxcol.column_id = fcol.column_id + LEFT JOIN SYS.SYSTRIGGER ut + ON fk.foreign_table_id = ut.foreign_table_id + AND fk.foreign_index_id = ut.foreign_key_id + AND ut.event = 'C' + LEFT JOIN SYS.SYSTRIGGER dt + ON fk.foreign_table_id = dt.foreign_table_id + AND fk.foreign_index_id = dt.foreign_key_id + AND dt.event = 'D' + WHERE ftbl.table_name = '$table' + ORDER BY fk.foreign_index_id ASC, idxcol.sequence ASC"; + } + + /** + * {@inheritdoc} + */ + public function getListTableIndexesSQL($table, $currentDatabase = null) + { + return "SELECT idx.index_name AS key_name, + IF idx.index_category = 1 + THEN 1 + ELSE 0 + ENDIF AS 'primary', + col.column_name, + IF idx.\"unique\" IN(1, 2, 5) + THEN 0 + ELSE 1 + ENDIF AS non_unique, + IF tbl.clustered_index_id = idx.index_id + THEN 1 + ELSE NULL + ENDIF AS 'clustered', -- clustered flag + IF idx.\"unique\" = 5 + THEN 1 + ELSE NULL + ENDIF AS with_nulls_not_distinct, -- with_nulls_not_distinct flag + IF pidx.max_key_distance = 1 + THEN 1 + ELSE NULL + ENDIF AS for_olap_workload -- for_olap_workload flag + FROM SYS.SYSIDX AS idx + JOIN SYS.SYSPHYSIDX pidx + ON idx.table_id = pidx.table_id + AND idx.phys_index_id = pidx.phys_index_id + JOIN SYS.SYSIDXCOL AS idxcol + ON idx.table_id = idxcol.table_id AND idx.index_id = idxcol.index_id + JOIN SYS.SYSTABCOL AS col + ON idxcol.table_id = col.table_id AND idxcol.column_id = col.column_id + JOIN SYS.SYSTAB AS tbl + ON idx.table_id = tbl.table_id + WHERE tbl.table_name = '$table' + ORDER BY idx.index_id ASC, idxcol.sequence ASC"; + } + + /** + * {@inheritdoc} + */ + public function getListTablesSQL() + { + return "SELECT tbl.table_name + FROM SYS.SYSTAB AS tbl + JOIN SYS.SYSUSER AS usr ON tbl.creator = usr.user_id + JOIN dbo.SYSOBJECTS AS obj ON tbl.object_id = obj.id + WHERE tbl.table_type IN(1, 3) -- 'BASE', 'GBL TEMP' + AND usr.user_name NOT IN('SYS', 'dbo', 'rs_systabgroup') -- exclude system users + AND obj.type = 'U' -- user created tables only + ORDER BY tbl.table_name ASC"; + } + + /** + * {@inheritdoc} + * + * @todo Where is this used? Which information should be retrieved? + */ + public function getListUsersSQL() + { + return 'SELECT * FROM SYS.SYSUSER ORDER BY user_name ASC'; + } + + /** + * {@inheritdoc} + */ + public function getListViewsSQL($database) + { + return "SELECT tbl.table_name, v.view_def + FROM SYS.SYSVIEW v + JOIN SYS.SYSTAB tbl ON v.view_object_id = tbl.object_id + JOIN SYS.SYSUSER usr ON tbl.creator = usr.user_id + JOIN dbo.SYSOBJECTS obj ON tbl.object_id = obj.id + WHERE usr.user_name NOT IN('SYS', 'dbo', 'rs_systabgroup') -- exclude system users + ORDER BY tbl.table_name ASC"; + } + + /** + * {@inheritdoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos == false) { + return 'CHARINDEX(' . $substr . ', ' . $str . ')'; + } + + return 'CHARINDEX(' . $substr . ', SUBSTR(' . $str . ', ' . ($startPos + 1) . '))'; + } + + /** + * {@inheritdoc} + */ + public function getMaxIdentifierLength() + { + return 128; + } + + /** + * {@inheritdoc} + */ + public function getMd5Expression($column) + { + return "HASH(" . $column . ", 'MD5')"; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'sqlanywhere'; + } + + /** + * Obtain DBMS specific SQL code portion needed to set a primary key + * declaration to be used in statements like ALTER TABLE. + * + * @param Index $index Index definition + * @param string $name Name of the primary key + * + * @return string DBMS specific SQL code portion needed to set a primary key + * + * @throws \InvalidArgumentException if the given index is not a primary key. + */ + public function getPrimaryKeyDeclarationSQL(Index $index, $name = null) + { + if ( ! $index->isPrimary()) { + throw new \InvalidArgumentException( + 'Can only create primary key declarations with getPrimaryKeyDeclarationSQL()' + ); + } + + return $this->getTableConstraintDeclarationSQL($index, $name); + } + + /** + * {@inheritdoc} + */ + public function getSetTransactionIsolationSQL($level) + { + return 'SET TEMPORARY OPTION isolation_level = ' . $this->_getTransactionIsolationLevelSQL($level); + } + + /** + * {@inheritdoc} + */ + public function getSmallIntTypeDeclarationSQL(array $columnDef) + { + $columnDef['integer_type'] = 'SMALLINT'; + + return $this->_getCommonIntegerTypeDeclarationSQL($columnDef); + } + + /** + * Returns the SQL statement for starting an existing database. + * + * In SQL Anywhere you can start and stop databases on a + * database server instance. + * This is a required statement after having created a new database + * as it has to be explicitly started to be usable. + * SQL Anywhere does not automatically start a database after creation! + * + * @param string $database Name of the database to start. + * + * @return string + */ + public function getStartDatabaseSQL($database) + { + return "START DATABASE '$database' AUTOSTOP OFF"; + } + + /** + * Returns the SQL statement for stopping a running database. + * + * In SQL Anywhere you can start and stop databases on a + * database server instance. + * This is a required statement before dropping an existing database + * as it has to be explicitly stopped before it can be dropped. + * + * @param string $database Name of the database to stop. + * + * @return string + */ + public function getStopDatabaseSQL($database) + { + return 'STOP DATABASE "' . $database . '" UNCONDITIONALLY'; + } + + /** + * {@inheritdoc} + */ + public function getSubstringExpression($value, $from, $length = null) + { + if (null === $length) { + return 'SUBSTRING(' . $value . ', ' . $from . ')'; + } + + return 'SUBSTRING(' . $value . ', ' . $from . ', ' . $length . ')'; + } + + /** + * {@inheritdoc} + */ + public function getTemporaryTableSQL() + { + return 'GLOBAL TEMPORARY'; + } + + /** + * {@inheritdoc} + */ + public function getTimeFormatString() + { + return 'H:i:s.u'; + } + + /** + * {@inheritdoc} + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIME'; + } + + /** + * {@inheritdoc} + */ + public function getTrimExpression($str, $pos = self::TRIM_UNSPECIFIED, $char = false) + { + if ( ! $char) { + switch ($pos) { + case self::TRIM_LEADING: + return $this->getLtrimExpression($str); + case self::TRIM_TRAILING: + return $this->getRtrimExpression($str); + default: + return 'TRIM(' . $str . ')'; + } + } + + $pattern = "'%[^$char]%'"; + + switch ($pos) { + case self::TRIM_LEADING: + return 'SUBSTR(' . $str . ', PATINDEX(' . $pattern . ', ' . $str . '))'; + case self::TRIM_TRAILING: + return 'REVERSE(SUBSTR(REVERSE(' . $str . '), PATINDEX(' . $pattern . ', REVERSE(' . $str . '))))'; + default: + return + 'REVERSE(SUBSTR(REVERSE(SUBSTR(' . $str . ', PATINDEX(' . $pattern . ', ' . $str . '))), ' . + 'PATINDEX(' . $pattern . ', REVERSE(SUBSTR(' . $str . ', PATINDEX(' . $pattern . ', ' . $str . '))))))'; + } + } + + /** + * {@inheritdoc} + */ + public function getTruncateTableSQL($tableName, $cascade = false) + { + return 'TRUNCATE TABLE ' . $tableName; + } + + /** + * {@inheritdoc} + */ + public function getUniqueConstraintDeclarationSQL($name, Index $index) + { + if ($index->isPrimary()) { + throw new \InvalidArgumentException( + 'Cannot create primary key constraint declarations with getUniqueConstraintDeclarationSQL().' + ); + } + + if ( ! $index->isUnique()) { + throw new \InvalidArgumentException( + 'Can only create unique constraint declarations, no common index declarations with ' . + 'getUniqueConstraintDeclarationSQL().' + ); + } + + return $this->getTableConstraintDeclarationSQL($index, $name); + } + + /** + * {@inheritdoc} + */ + public function getVarcharDefaultLength() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function getVarcharMaxLength() + { + return 32767; + } + + /** + * {@inheritdoc} + */ + public function hasNativeGuidType() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function prefersIdentityColumns() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function schemaNeedsCreation($schemaName) + { + return $schemaName !== 'DBA'; + } + + /** + * {@inheritdoc} + */ + public function supportsCommentOnStatement() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function supportsSchemas() + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) + { + $unsigned = ! empty($columnDef['unsigned']) ? 'UNSIGNED ' : ''; + $autoincrement = ! empty($columnDef['autoincrement']) ? ' IDENTITY' : ''; + + return $unsigned . $columnDef['integer_type'] . $autoincrement; + } + + /** + * {@inheritdoc} + */ + protected function _getCreateTableSQL($tableName, array $columns, array $options = array()) + { + $columnListSql = $this->getColumnDeclarationListSQL($columns); + $indexSql = array(); + + if ( ! empty($options['uniqueConstraints'])) { + foreach ((array) $options['uniqueConstraints'] as $name => $definition) { + $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition); + } + } + + if ( ! empty($options['indexes'])) { + /** @var \Doctrine\DBAL\Schema\Index $index */ + foreach ((array) $options['indexes'] as $name => $index) { + if ($index->isUnique()) { + $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $index); + } else { + $indexSql[] = $this->getCreateIndexSQL($index, $tableName); + } + } + } + + if ( ! empty($options['primary'])) { + $flags = ''; + + if (isset($options['primary_index']) && $options['primary_index']->hasFlag('clustered')) { + $flags = ' CLUSTERED '; + } + + $columnListSql .= ', PRIMARY KEY' . $flags . ' (' . implode(', ', array_unique(array_values((array) $options['primary']))) . ')'; + } + + if ( ! empty($options['foreignKeys'])) { + foreach ((array) $options['foreignKeys'] as $definition) { + $columnListSql .= ', ' . $this->getForeignKeyDeclarationSQL($definition); + } + } + + $query = 'CREATE TABLE ' . $tableName . ' (' . $columnListSql; + $check = $this->getCheckDeclarationSQL($columns); + + if ( ! empty($check)) { + $query .= ', ' . $check; + } + + $query .= ')'; + + return array_merge(array($query), $indexSql); + } + + /** + * {@inheritdoc} + */ + protected function _getTransactionIsolationLevelSQL($level) + { + switch ($level) { + case Connection::TRANSACTION_READ_UNCOMMITTED: + return 0; + case Connection::TRANSACTION_READ_COMMITTED: + return 1; + case Connection::TRANSACTION_REPEATABLE_READ: + return 2; + case Connection::TRANSACTION_SERIALIZABLE: + return 3; + default: + throw new \InvalidArgumentException('Invalid isolation level:' . $level); + } + } + + /** + * {@inheritdoc} + */ + protected function doModifyLimitQuery($query, $limit, $offset) + { + $limitOffsetClause = ''; + + if ($limit > 0) { + $limitOffsetClause = 'TOP ' . $limit . ' '; + } + + if ($offset > 0) { + if ($limit == 0) { + $limitOffsetClause = 'TOP ALL '; + } + + $limitOffsetClause .= 'START AT ' . ($offset + 1) . ' '; + } + + if ($limitOffsetClause) { + return preg_replace('/^\s*(SELECT\s+(DISTINCT\s+)?)/i', '\1' . $limitOffsetClause, $query); + } + + return $query; + } + + /** + * Return the INDEX query section dealing with non-standard + * SQL Anywhere options. + * + * @param Index $index Index definition + * + * @return string + */ + protected function getAdvancedIndexOptionsSQL(Index $index) + { + $sql = ''; + + if ( ! $index->isPrimary() && $index->hasFlag('for_olap_workload')) { + $sql .= ' FOR OLAP WORKLOAD'; + } + + return $sql; + } + + /** + * Returns the SQL snippet for creating a table constraint. + * + * @param Constraint $constraint The table constraint to create the SQL snippet for. + * @param string|null $name The table constraint name to use if any. + * + * @return string + * + * @throws \InvalidArgumentException if the given table constraint type is not supported by this method. + */ + protected function getTableConstraintDeclarationSQL(Constraint $constraint, $name = null) + { + if ($constraint instanceof ForeignKeyConstraint) { + return $this->getForeignKeyDeclarationSQL($constraint); + } + + if ( ! $constraint instanceof Index) { + throw new \InvalidArgumentException('Unsupported constraint type: ' . get_class($constraint)); + } + + if ( ! $constraint->isPrimary() && ! $constraint->isUnique()) { + throw new \InvalidArgumentException( + 'Can only create primary, unique or foreign key constraint declarations, no common index declarations ' . + 'with getTableConstraintDeclarationSQL().' + ); + } + + $constraintColumns = $constraint->getQuotedColumns($this); + + if (empty($constraintColumns)) { + throw new \InvalidArgumentException("Incomplete definition. 'columns' required."); + } + + $sql = ''; + $flags = ''; + + if ( ! empty($name)) { + $sql .= 'CONSTRAINT ' . $name . ' '; + } + + if ($constraint->hasFlag('clustered')) { + $flags = 'CLUSTERED '; + } + + if ($constraint->isPrimary()) { + return $sql . 'PRIMARY KEY ' . $flags . '('. $this->getIndexFieldDeclarationListSQL($constraintColumns) . ')'; + } + + return $sql . 'UNIQUE ' . $flags . '('. $this->getIndexFieldDeclarationListSQL($constraintColumns) . ')'; + } + + /** + * {@inheritdoc} + */ + protected function getCreateIndexSQLFlags(Index $index) + { + $type = ''; + if ($index->hasFlag('virtual')) { + $type .= 'VIRTUAL '; + } + + if ($index->isUnique()) { + $type .= 'UNIQUE '; + } + + if ($index->hasFlag('clustered')) { + $type .= 'CLUSTERED '; + } + + return $type; + } + + /** + * {@inheritdoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\SQLAnywhereKeywords'; + } + + /** + * {@inheritdoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed + ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(' . $this->getVarcharDefaultLength() . ')') + : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(' . $this->getVarcharDefaultLength() . ')'); + } + + /** + * {@inheritdoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = array( + 'char' => 'string', + 'long nvarchar' => 'text', + 'long varchar' => 'text', + 'nchar' => 'string', + 'ntext' => 'text', + 'nvarchar' => 'string', + 'text' => 'text', + 'uniqueidentifierstr' => 'guid', + 'varchar' => 'string', + 'xml' => 'text', + 'bigint' => 'bigint', + 'unsigned bigint' => 'bigint', + 'bit' => 'boolean', + 'decimal' => 'decimal', + 'double' => 'float', + 'float' => 'float', + 'int' => 'integer', + 'unsigned int' => 'integer', + 'numeric' => 'decimal', + 'smallint' => 'smallint', + 'unsigned smallint', 'smallint', + 'tinyint' => 'smallint', + 'unsigned tinyint', 'smallint', + 'money' => 'decimal', + 'smallmoney' => 'decimal', + 'long varbit' => 'text', + 'varbit' => 'string', + 'date' => 'date', + 'datetime' => 'datetime', + 'smalldatetime' => 'datetime', + 'time' => 'time', + 'timestamp' => 'datetime', + 'binary' => 'blob', + 'image' => 'blob', + 'long binary' => 'blob', + 'uniqueidentifier' => 'guid', + 'varbinary' => 'blob', + ); + } +} diff --git a/lib/Doctrine/DBAL/Portability/Connection.php b/lib/Doctrine/DBAL/Portability/Connection.php index f6a38966904..332cb4d093d 100644 --- a/lib/Doctrine/DBAL/Portability/Connection.php +++ b/lib/Doctrine/DBAL/Portability/Connection.php @@ -42,6 +42,7 @@ class Connection extends \Doctrine\DBAL\Connection const PORTABILITY_SQLITE = 13; const PORTABILITY_OTHERVENDORS = 12; const PORTABILITY_DRIZZLE = 13; + const PORTABILITY_SQLANYWHERE = 13; const PORTABILITY_SQLSRV = 13; /** @@ -71,6 +72,8 @@ public function connect() $params['portability'] = $params['portability'] & self::PORTABILITY_SQLITE; } else if ($this->_platform->getName() === "drizzle") { $params['portability'] = self::PORTABILITY_DRIZZLE; + } else if ($this->_platform->getName() === 'sqlanywhere') { + $params['portability'] = self::PORTABILITY_SQLANYWHERE; } else if ($this->_platform->getName() === 'sqlsrv') { $params['portability'] = $params['portabililty'] & self::PORTABILITY_SQLSRV; } else { diff --git a/lib/Doctrine/DBAL/Schema/SQLAnywhereSchemaManager.php b/lib/Doctrine/DBAL/Schema/SQLAnywhereSchemaManager.php new file mode 100644 index 00000000000..4eebef6d9b6 --- /dev/null +++ b/lib/Doctrine/DBAL/Schema/SQLAnywhereSchemaManager.php @@ -0,0 +1,237 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Types\Type; + +/** + * SAP Sybase SQL Anywhere schema manager. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.5 + */ +class SQLAnywhereSchemaManager extends AbstractSchemaManager +{ + /** + * {@inheritdoc} + * + * Starts a database after creation + * as SQL Anywhere needs a database to be started + * before it can be used. + * + * @see startDatabase + */ + public function createDatabase($database) + { + parent::createDatabase($database); + $this->startDatabase($database); + } + + /** + * {@inheritdoc} + * + * Tries stopping a database before dropping + * as SQL Anywhere needs a database to be stopped + * before it can be dropped. + * + * @see stopDatabase + */ + public function dropDatabase($database) + { + $this->tryMethod('stopDatabase', $database); + parent::dropDatabase($database); + } + + /** + * Starts a database. + * + * @param string $database The name of the database to start. + */ + public function startDatabase($database) + { + $this->_execSql($this->_platform->getStartDatabaseSQL($database)); + } + + /** + * Stops a database. + * + * @param string $database The name of the database to stop. + */ + public function stopDatabase($database) + { + $this->_execSql($this->_platform->getStopDatabaseSQL($database)); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableDatabaseDefinition($database) + { + return $database['name']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableSequenceDefinition($sequence) + { + return new Sequence($sequence['sequence_name'], $sequence['increment_by'], $sequence['start_with']); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $type = $this->_platform->getDoctrineTypeMapping($tableColumn['type']); + $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); + $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); + $precision = null; + $scale = null; + $fixed = false; + $default = null; + + if ($tableColumn['default']) { + // Strip quotes from default value. + $default = preg_replace(array("/^'(.*)'$/", "/''/"), array("$1", "'"), $tableColumn['default']); + } + + switch ($tableColumn['type']) { + case 'binary': + case 'char': + case 'nchar': + $fixed = true; + } + + switch ($type) { + case 'decimal': + case 'float': + $precision = $tableColumn['length']; + $scale = $tableColumn['scale']; + } + + return new Column( + $tableColumn['column_name'], + Type::getType($type), + array( + 'length' => $type == 'string' ? $tableColumn['length'] : null, + 'precision' => $precision, + 'scale' => $scale, + 'unsigned' => (bool) $tableColumn['unsigned'], + 'fixed' => $fixed, + 'notnull' => (bool) $tableColumn['notnull'], + 'default' => $default, + 'autoincrement' => (bool) $tableColumn['autoincrement'], + 'comment' => $tableColumn['comment'] + )); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableDefinition($table) + { + return $table['table_name']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableForeignKeyDefinition($tableForeignKey) + { + return new ForeignKeyConstraint( + $tableForeignKey['local_columns'], + $tableForeignKey['foreign_table'], + $tableForeignKey['foreign_columns'], + $tableForeignKey['name'], + $tableForeignKey['options'] + ); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableForeignKeysList($tableForeignKeys) + { + $foreignKeys = array(); + + foreach ($tableForeignKeys as $tableForeignKey) { + if (!isset($foreignKeys[$tableForeignKey['index_name']])) { + $foreignKeys[$tableForeignKey['index_name']] = array( + 'local_columns' => array($tableForeignKey['local_column']), + 'foreign_table' => $tableForeignKey['foreign_table'], + 'foreign_columns' => array($tableForeignKey['foreign_column']), + 'name' => $tableForeignKey['index_name'], + 'options' => array( + 'notnull' => $tableForeignKey['notnull'], + 'match' => $tableForeignKey['match'], + 'onUpdate' => $tableForeignKey['on_update'], + 'onDelete' => $tableForeignKey['on_delete'], + 'check_on_commit' => $tableForeignKey['check_on_commit'], + 'clustered' => $tableForeignKey['clustered'], + 'for_olap_workload' => $tableForeignKey['for_olap_workload'] + ) + ); + } else { + $foreignKeys[$tableForeignKey['index_name']]['local_columns'][] = $tableForeignKey['local_column']; + $foreignKeys[$tableForeignKey['index_name']]['foreign_columns'][] = $tableForeignKey['foreign_column']; + } + } + + return parent::_getPortableTableForeignKeysList($foreignKeys); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableIndexesList($tableIndexRows, $tableName = null) + { + foreach ($tableIndexRows as &$tableIndex) { + $tableIndex['primary'] = (boolean) $tableIndex['primary']; + $tableIndex['flags'] = array(); + + if ($tableIndex['clustered']) { + $tableIndex['flags'][] = 'clustered'; + } + + if ($tableIndex['with_nulls_not_distinct']) { + $tableIndex['flags'][] = 'with_nulls_not_distinct'; + } + + if ($tableIndex['for_olap_workload']) { + $tableIndex['flags'][] = 'for_olap_workload'; + } + } + + return parent::_getPortableTableIndexesList($tableIndexRows, $tableName); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableViewDefinition($view) + { + return new View( + $view['table_name'], + preg_replace('/^.*\s+as\s+SELECT(.*)/i', "SELECT$1", $view['view_def']) + ); + } +} diff --git a/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php b/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php index 846acb82bab..59a38e58914 100644 --- a/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php +++ b/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php @@ -41,6 +41,10 @@ class ReservedWordsCommand extends Command 'pgsql' => 'Doctrine\DBAL\Platforms\Keywords\PostgreSQLKeywords', 'oracle' => 'Doctrine\DBAL\Platforms\Keywords\OracleKeywords', 'db2' => 'Doctrine\DBAL\Platforms\Keywords\DB2Keywords', + 'sqlanywhere' => 'Doctrine\DBAL\Platforms\Keywords\SQLAnywhereKeywords', + 'sqlanywhere11' => 'Doctrine\DBAL\Platforms\Keywords\SQLAnywhere11Keywords', + 'sqlanywhere12' => 'Doctrine\DBAL\Platforms\Keywords\SQLAnywhere12Keywords', + 'sqlanywhere16' => 'Doctrine\DBAL\Platforms\Keywords\SQLAnywhere16Keywords', ); /** @@ -73,8 +77,8 @@ protected function configure() Checks if the current database contains tables and columns with names that are identifiers in this dialect or in other SQL dialects. -By default SQLite, MySQL, PostgreSQL, Microsoft SQL Server and Oracle -keywords are checked: +By default SQLite, MySQL, PostgreSQL, Microsoft SQL Server, Oracle +and SQL Anywhere keywords are checked: %command.full_name% @@ -93,6 +97,10 @@ protected function configure() * sqlserver2005 * sqlserver2008 * sqlserver2012 + * sqlanywhere + * sqlanywhere11 + * sqlanywhere12 + * sqlanywhere16 * db2 (Not checked by default) EOT ); @@ -116,7 +124,11 @@ protected function execute(InputInterface $input, OutputInterface $output) 'sqlserver', 'sqlserver2005', 'sqlserver2008', - 'sqlserver2012' + 'sqlserver2012', + 'sqlanywhere', + 'sqlanywhere11', + 'sqlanywhere12', + 'sqlanywhere16', ); } diff --git a/run-all.sh b/run-all.sh index 80712eebc7e..d3576d3a3a3 100755 --- a/run-all.sh +++ b/run-all.sh @@ -4,18 +4,19 @@ # Just create the phpunit.xmls as described in the array below and configure the specific files section # to connect to that database. Just omit a file if you dont have that database and the tests will be skipped. -configs[1]="mysql.phpunit.xml" -configs[2]='postgres.phpunit.xml' +configs[1]="mysql.phpunit.xml" +configs[2]='postgres.phpunit.xml' configs[3]='sqlite.phpunit.xml' configs[4]='oracle.phpunit.xml' configs[5]='db2.phpunit.xml' configs[6]='pdo-ibm.phpunit.xml' configs[7]='sqlsrv.phpunit.xml' +configs[8]='sqlanywhere.phpunit.xml' for i in "${configs[@]}"; do if [ -f "$i" ]; then echo "RUNNING TESTS WITH CONFIG $i" - phpunit -c "$i" "$@" + phpunit -c "$i" "$@" fi; done diff --git a/tests/Doctrine/Tests/DBAL/Functional/Schema/SQLAnywhereSchemaManagerTest.php b/tests/Doctrine/Tests/DBAL/Functional/Schema/SQLAnywhereSchemaManagerTest.php new file mode 100644 index 00000000000..fa58cd4bf47 --- /dev/null +++ b/tests/Doctrine/Tests/DBAL/Functional/Schema/SQLAnywhereSchemaManagerTest.php @@ -0,0 +1,64 @@ +createTestTable('view_test_table'); + + $name = "doctrine_test_view"; + $sql = "SELECT * from DBA.view_test_table"; + + $view = new View($name, $sql); + + $this->_sm->dropAndCreateView($view); + + $views = $this->_sm->listViews(); + + $this->assertEquals(1, count($views), "Database has to have one view."); + $this->assertInstanceOf('Doctrine\DBAL\Schema\View', $views[$name]); + $this->assertEquals($name, $views[$name]->getName()); + $this->assertEquals($sql, $views[$name]->getSql()); + } + + public function testDropAndCreateAdvancedIndex() + { + $table = $this->getTestTable('test_create_advanced_index'); + $this->_sm->dropAndCreateTable($table); + $this->_sm->dropAndCreateIndex( + new Index('test', array('test'), true, false, array('clustered', 'with_nulls_not_distinct', 'for_olap_workload')), + $table->getName() + ); + + $tableIndexes = $this->_sm->listTableIndexes('test_create_advanced_index'); + $this->assertInternalType('array', $tableIndexes); + $this->assertEquals('test', $tableIndexes['test']->getName()); + $this->assertEquals(array('test'), $tableIndexes['test']->getColumns()); + $this->assertTrue($tableIndexes['test']->isUnique()); + $this->assertFalse($tableIndexes['test']->isPrimary()); + $this->assertTrue($tableIndexes['test']->hasFlag('clustered')); + $this->assertTrue($tableIndexes['test']->hasFlag('with_nulls_not_distinct')); + $this->assertTrue($tableIndexes['test']->hasFlag('for_olap_workload')); + } + + public function testListTableColumnsWithFixedStringTypeColumn() + { + $table = new Table('list_table_columns_char'); + $table->addColumn('id', 'integer', array('notnull' => true)); + $table->addColumn('test', 'string', array('fixed' => true)); + $table->setPrimaryKey(array('id')); + + $this->_sm->dropAndCreateTable($table); + + $columns = $this->_sm->listTableColumns('list_table_columns_char'); + + $this->assertArrayHasKey('test', $columns); + $this->assertTrue($columns['test']->getFixed()); + } +} diff --git a/tests/Doctrine/Tests/DBAL/Platforms/SQLAnywhere11PlatformTest.php b/tests/Doctrine/Tests/DBAL/Platforms/SQLAnywhere11PlatformTest.php new file mode 100644 index 00000000000..e3b6e232f68 --- /dev/null +++ b/tests/Doctrine/Tests/DBAL/Platforms/SQLAnywhere11PlatformTest.php @@ -0,0 +1,28 @@ +markTestSkipped('This version of the platform now supports regular expressions.'); + } + + public function testGeneratesRegularExpressionSQLSnippet() + { + $this->assertEquals('REGEXP', $this->_platform->getRegexpExpression()); + } +} diff --git a/tests/Doctrine/Tests/DBAL/Platforms/SQLAnywhere12PlatformTest.php b/tests/Doctrine/Tests/DBAL/Platforms/SQLAnywhere12PlatformTest.php new file mode 100644 index 00000000000..82e9dde9fc9 --- /dev/null +++ b/tests/Doctrine/Tests/DBAL/Platforms/SQLAnywhere12PlatformTest.php @@ -0,0 +1,143 @@ +markTestSkipped('This version of the platform now supports sequences.'); + } + + public function testSupportsSequences() + { + $this->assertTrue($this->_platform->supportsSequences()); + } + + public function testGeneratesSequenceSqlCommands() + { + $sequence = new Sequence('myseq', 20, 1); + $this->assertEquals( + 'CREATE SEQUENCE myseq INCREMENT BY 20 START WITH 1 MINVALUE 1', + $this->_platform->getCreateSequenceSQL($sequence) + ); + $this->assertEquals( + 'ALTER SEQUENCE myseq INCREMENT BY 20', + $this->_platform->getAlterSequenceSQL($sequence) + ); + $this->assertEquals( + 'DROP SEQUENCE myseq', + $this->_platform->getDropSequenceSQL('myseq') + ); + $this->assertEquals( + 'DROP SEQUENCE myseq', + $this->_platform->getDropSequenceSQL($sequence) + ); + $this->assertEquals( + "SELECT myseq.NEXTVAL", + $this->_platform->getSequenceNextValSQL('myseq') + ); + $this->assertEquals( + 'SELECT sequence_name, increment_by, start_with, min_value FROM SYS.SYSSEQUENCE', + $this->_platform->getListSequencesSQL(null) + ); + } + + public function testGeneratesDateTimeTzColumnTypeDeclarationSQL() + { + $this->assertEquals( + 'TIMESTAMP WITH TIME ZONE', + $this->_platform->getDateTimeTzTypeDeclarationSQL(array( + 'length' => 10, + 'fixed' => true, + 'unsigned' => true, + 'autoincrement' => true + )) + ); + } + + public function testHasCorrectDateTimeTzFormatString() + { + $this->assertEquals('Y-m-d H:i:s.uP', $this->_platform->getDateTimeTzFormatString()); + } + + public function testInitializesDateTimeTzTypeMapping() + { + $this->assertTrue($this->_platform->hasDoctrineTypeMappingFor('timestamp with time zone')); + $this->assertEquals('datetime', $this->_platform->getDoctrineTypeMapping('timestamp with time zone')); + } + + public function testGeneratesCreateIndexWithAdvancedPlatformOptionsSQL() + { + $this->assertEquals( + 'CREATE VIRTUAL UNIQUE CLUSTERED INDEX fooindex ON footable (a, b) WITH NULLS NOT DISTINCT FOR OLAP WORKLOAD', + $this->_platform->getCreateIndexSQL( + new Index( + 'fooindex', + array('a', 'b'), + true, + false, + array('virtual', 'clustered', 'with_nulls_not_distinct', 'for_olap_workload') + ), + 'footable' + ) + ); + $this->assertEquals( + 'CREATE VIRTUAL CLUSTERED INDEX fooindex ON footable (a, b) FOR OLAP WORKLOAD', + $this->_platform->getCreateIndexSQL( + new Index( + 'fooindex', + array('a', 'b'), + false, + false, + array('virtual', 'clustered', 'with_nulls_not_distinct', 'for_olap_workload') + ), + 'footable' + ) + ); + + // WITH NULLS NOT DISTINCT clause not available on primary indexes. + $this->assertEquals( + 'ALTER TABLE footable ADD PRIMARY KEY (a, b)', + $this->_platform->getCreateIndexSQL( + new Index( + 'fooindex', + array('a', 'b'), + false, + true, + array('with_nulls_not_distinct') + ), + 'footable' + ) + ); + + // WITH NULLS NOT DISTINCT clause not available on non-unique indexes. + $this->assertEquals( + 'CREATE INDEX fooindex ON footable (a, b)', + $this->_platform->getCreateIndexSQL( + new Index( + 'fooindex', + array('a', 'b'), + false, + false, + array('with_nulls_not_distinct') + ), + 'footable' + ) + ); + } +} diff --git a/tests/Doctrine/Tests/DBAL/Platforms/SQLAnywhere16PlatformTest.php b/tests/Doctrine/Tests/DBAL/Platforms/SQLAnywhere16PlatformTest.php new file mode 100644 index 00000000000..37f2a966f2d --- /dev/null +++ b/tests/Doctrine/Tests/DBAL/Platforms/SQLAnywhere16PlatformTest.php @@ -0,0 +1,80 @@ +assertEquals( + 'CREATE UNIQUE INDEX fooindex ON footable (a, b) WITH NULLS DISTINCT', + $this->_platform->getCreateIndexSQL( + new Index( + 'fooindex', + array('a', 'b'), + true, + false, + array('with_nulls_distinct') + ), + 'footable' + ) + ); + + // WITH NULLS DISTINCT clause not available on primary indexes. + $this->assertEquals( + 'ALTER TABLE footable ADD PRIMARY KEY (a, b)', + $this->_platform->getCreateIndexSQL( + new Index( + 'fooindex', + array('a', 'b'), + false, + true, + array('with_nulls_distinct') + ), + 'footable' + ) + ); + + // WITH NULLS DISTINCT clause not available on non-unique indexes. + $this->assertEquals( + 'CREATE INDEX fooindex ON footable (a, b)', + $this->_platform->getCreateIndexSQL( + new Index( + 'fooindex', + array('a', 'b'), + false, + false, + array('with_nulls_distinct') + ), + 'footable' + ) + ); + + parent::testGeneratesCreateIndexWithAdvancedPlatformOptionsSQL(); + } + + public function testThrowsExceptionOnInvalidWithNullsNotDistinctIndexOptions() + { + $this->setExpectedException('UnexpectedValueException'); + + $this->_platform->getCreateIndexSQL( + new Index( + 'fooindex', + array('a', 'b'), + false, + false, + array('with_nulls_distinct', 'with_nulls_not_distinct') + ), + 'footable' + ); + } +} diff --git a/tests/Doctrine/Tests/DBAL/Platforms/SQLAnywherePlatformTest.php b/tests/Doctrine/Tests/DBAL/Platforms/SQLAnywherePlatformTest.php new file mode 100644 index 00000000000..28b977dafdd --- /dev/null +++ b/tests/Doctrine/Tests/DBAL/Platforms/SQLAnywherePlatformTest.php @@ -0,0 +1,755 @@ +_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()); + } + + public function testGeneratesCreateTableSQLWithCommonIndexes() + { + $table = new Table('test'); + $table->addColumn('id', 'integer'); + $table->addColumn('name', 'string', array('length' => 50)); + $table->setPrimaryKey(array('id')); + $table->addIndex(array('name')); + $table->addIndex(array('id', 'name'), 'composite_idx'); + + $this->assertEquals( + array( + 'CREATE TABLE test (id INT NOT NULL, name VARCHAR(50) NOT NULL, PRIMARY KEY (id))', + 'CREATE INDEX IDX_D87F7E0C5E237E06 ON test (name)', + 'CREATE INDEX composite_idx ON test (id, name)' + ), + $this->_platform->getCreateTableSQL($table) + ); + } + + public function testGeneratesCreateTableSQLWithForeignKeyConstraints() + { + $table = new Table('test'); + $table->addColumn('id', 'integer'); + $table->addColumn('fk_1', 'integer'); + $table->addColumn('fk_2', 'integer'); + $table->setPrimaryKey(array('id')); + $table->addForeignKeyConstraint('foreign_table', array('fk_1', 'fk_2'), array('pk_1', 'pk_2')); + $table->addForeignKeyConstraint( + 'foreign_table2', + array('fk_1', 'fk_2'), + array('pk_1', 'pk_2'), + array(), + 'named_fk' + ); + + $this->assertEquals( + array( + 'CREATE TABLE test (id INT NOT NULL, fk_1 INT NOT NULL, fk_2 INT NOT NULL, ' . + 'CONSTRAINT FK_D87F7E0C177612A38E7F4319 FOREIGN KEY (fk_1, fk_2) REFERENCES foreign_table (pk_1, pk_2), ' . + 'CONSTRAINT named_fk FOREIGN KEY (fk_1, fk_2) REFERENCES foreign_table2 (pk_1, pk_2))' + ), + $this->_platform->getCreateTableSQL($table, AbstractPlatform::CREATE_FOREIGNKEYS) + ); + } + + public function testGeneratesCreateTableSQLWithCheckConstraints() + { + $table = new Table('test'); + $table->addColumn('id', 'integer'); + $table->addColumn('check_max', 'integer', array('platformOptions' => array('max' => 10))); + $table->addColumn('check_min', 'integer', array('platformOptions' => array('min' => 10))); + $table->setPrimaryKey(array('id')); + + $this->assertEquals( + array( + 'CREATE TABLE test (id INT NOT NULL, check_max INT NOT NULL, check_min INT NOT NULL, PRIMARY KEY (id), CHECK (check_max <= 10), CHECK (check_min >= 10))' + ), + $this->_platform->getCreateTableSQL($table) + ); + } + + public function testGeneratesTableAlterationWithRemovedColumnCommentSql() + { + $table = new Table('mytable'); + $table->addColumn('foo', 'string', array('comment' => 'foo comment')); + + $tableDiff = new TableDiff('mytable'); + $tableDiff->fromTable = $table; + $tableDiff->changedColumns['foo'] = new ColumnDiff( + 'foo', + new Column('foo', Type::getType('string')), + array('comment') + ); + + $this->assertEquals( + array( + "COMMENT ON COLUMN mytable.foo IS NULL" + ), + $this->_platform->getAlterTableSQL($tableDiff) + ); + } + + public function testAppendsLockHints() + { + $fromClause = 'SELECT * FROM lock_hints'; + + $this->assertEquals( + $fromClause . ' WITH (NOLOCK)', + $this->_platform->appendLockHint($fromClause, LockMode::NONE) + ); + $this->assertEquals( + $fromClause . ' WITH (UPDLOCK)', + $this->_platform->appendLockHint($fromClause, LockMode::PESSIMISTIC_READ) + ); + $this->assertEquals( + $fromClause . ' WITH (XLOCK)', + $this->_platform->appendLockHint($fromClause, LockMode::PESSIMISTIC_WRITE) + ); + } + + public function testCannotAppendInvalidLockHint() + { + $this->setExpectedException('\InvalidArgumentException'); + + $this->_platform->appendLockHint('SELECT * FROM lock_hints', 'invalid_lock_mode'); + } + + public function testHasCorrectMaxIdentifierLength() + { + $this->assertEquals(128, $this->_platform->getMaxIdentifierLength()); + } + + public function testFixesSchemaElementNames() + { + $maxIdentifierLength = $this->_platform->getMaxIdentifierLength(); + $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $schemaElementName = ''; + + for ($i = 0; $i < $maxIdentifierLength + 100; $i++) { + $schemaElementName .= $characters[mt_rand(0, strlen($characters) - 1)]; + } + + $fixedSchemaElementName = substr($schemaElementName, 0, $maxIdentifierLength); + + $this->assertEquals( + $fixedSchemaElementName, + $this->_platform->fixSchemaElementName($schemaElementName) + ); + $this->assertEquals( + $fixedSchemaElementName, + $this->_platform->fixSchemaElementName($fixedSchemaElementName) + ); + } + + public function testGeneratesColumnTypesDeclarationSQL() + { + $fullColumnDef = array( + 'length' => 10, + 'fixed' => true, + 'unsigned' => true, + 'autoincrement' => true + ); + + $this->assertEquals('SMALLINT', $this->_platform->getSmallIntTypeDeclarationSQL(array())); + $this->assertEquals('UNSIGNED SMALLINT', $this->_platform->getSmallIntTypeDeclarationSQL(array( + 'unsigned' => true + ))); + $this->assertEquals('UNSIGNED SMALLINT IDENTITY', $this->_platform->getSmallIntTypeDeclarationSQL($fullColumnDef)); + $this->assertEquals('INT', $this->_platform->getIntegerTypeDeclarationSQL(array())); + $this->assertEquals('UNSIGNED INT', $this->_platform->getIntegerTypeDeclarationSQL(array( + 'unsigned' => true + ))); + $this->assertEquals('UNSIGNED INT IDENTITY', $this->_platform->getIntegerTypeDeclarationSQL($fullColumnDef)); + $this->assertEquals('BIGINT', $this->_platform->getBigIntTypeDeclarationSQL(array())); + $this->assertEquals('UNSIGNED BIGINT', $this->_platform->getBigIntTypeDeclarationSQL(array( + 'unsigned' => true + ))); + $this->assertEquals('UNSIGNED BIGINT IDENTITY', $this->_platform->getBigIntTypeDeclarationSQL($fullColumnDef)); + $this->assertEquals('LONG BINARY', $this->_platform->getBlobTypeDeclarationSQL($fullColumnDef)); + $this->assertEquals('BIT', $this->_platform->getBooleanTypeDeclarationSQL($fullColumnDef)); + $this->assertEquals('TEXT', $this->_platform->getClobTypeDeclarationSQL($fullColumnDef)); + $this->assertEquals('DATE', $this->_platform->getDateTypeDeclarationSQL($fullColumnDef)); + $this->assertEquals('DATETIME', $this->_platform->getDateTimeTypeDeclarationSQL($fullColumnDef)); + $this->assertEquals('TIME', $this->_platform->getTimeTypeDeclarationSQL($fullColumnDef)); + $this->assertEquals('UNIQUEIDENTIFIER', $this->_platform->getGuidTypeDeclarationSQL($fullColumnDef)); + + $this->assertEquals(1, $this->_platform->getVarcharDefaultLength()); + $this->assertEquals(32767, $this->_platform->getVarcharMaxLength()); + } + + public function testHasNativeGuidType() + { + $this->assertTrue($this->_platform->hasNativeGuidType()); + } + + public function testGeneratesDDLSnippets() + { + $this->assertEquals("CREATE DATABASE 'foobar'", $this->_platform->getCreateDatabaseSQL('foobar')); + $this->assertEquals("DROP DATABASE 'foobar'", $this->_platform->getDropDatabaseSQL('foobar')); + $this->assertEquals('CREATE GLOBAL TEMPORARY TABLE', $this->_platform->getCreateTemporaryTableSnippetSQL()); + $this->assertEquals("START DATABASE 'foobar' AUTOSTOP OFF", $this->_platform->getStartDatabaseSQL('foobar')); + $this->assertEquals('STOP DATABASE "foobar" UNCONDITIONALLY', $this->_platform->getStopDatabaseSQL('foobar')); + $this->assertEquals('TRUNCATE TABLE foobar', $this->_platform->getTruncateTableSQL('foobar')); + $this->assertEquals('TRUNCATE TABLE foobar', $this->_platform->getTruncateTableSQL('foobar'), true); + + $viewSql = 'SELECT * FROM footable'; + $this->assertEquals('CREATE VIEW fooview AS ' . $viewSql, $this->_platform->getCreateViewSQL('fooview', $viewSql)); + $this->assertEquals('DROP VIEW fooview', $this->_platform->getDropViewSQL('fooview')); + } + + public function testGeneratesPrimaryKeyDeclarationSQL() + { + $this->assertEquals( + 'CONSTRAINT pk PRIMARY KEY CLUSTERED (a, b)', + $this->_platform->getPrimaryKeyDeclarationSQL( + new Index(null, array('a', 'b'), true, true, array('clustered')), + 'pk' + ) + ); + $this->assertEquals( + 'PRIMARY KEY (a, b)', + $this->_platform->getPrimaryKeyDeclarationSQL( + new Index(null, array('a', 'b'), true, true) + ) + ); + } + + public function testCannotGeneratePrimaryKeyDeclarationSQLWithEmptyColumns() + { + $this->setExpectedException('\InvalidArgumentException'); + + $this->_platform->getPrimaryKeyDeclarationSQL(new Index('pk', array(), true, true)); + } + + public function testGeneratesCreateUnnamedPrimaryKeySQL() + { + $this->assertEquals( + 'ALTER TABLE foo ADD PRIMARY KEY CLUSTERED (a, b)', + $this->_platform->getCreatePrimaryKeySQL( + new Index('pk', array('a', 'b'), true, true, array('clustered')), + 'foo' + ) + ); + $this->assertEquals( + 'ALTER TABLE foo ADD PRIMARY KEY (a, b)', + $this->_platform->getCreatePrimaryKeySQL( + new Index('any_pk_name', array('a', 'b'), true, true), + new Table('foo') + ) + ); + } + + public function testGeneratesUniqueConstraintDeclarationSQL() + { + $this->assertEquals( + 'CONSTRAINT unique_constraint UNIQUE CLUSTERED (a, b)', + $this->_platform->getUniqueConstraintDeclarationSQL( + 'unique_constraint', + new Index(null, array('a', 'b'), true, false, array('clustered')) + ) + ); + $this->assertEquals( + 'UNIQUE (a, b)', + $this->_platform->getUniqueConstraintDeclarationSQL(null, new Index(null, array('a', 'b'), true, false)) + ); + } + + public function testCannotGenerateUniqueConstraintDeclarationSQLWithEmptyColumns() + { + $this->setExpectedException('\InvalidArgumentException'); + + $this->_platform->getUniqueConstraintDeclarationSQL('constr', new Index('constr', array(), true)); + } + + public function testGeneratesForeignKeyConstraintsWithAdvancedPlatformOptionsSQL() + { + $this->assertEquals( + 'CONSTRAINT fk ' . + 'NOT NULL FOREIGN KEY (a, b) ' . + 'REFERENCES foreign_table (c, d) ' . + 'MATCH UNIQUE SIMPLE ON UPDATE CASCADE ON DELETE SET NULL CHECK ON COMMIT CLUSTERED FOR OLAP WORKLOAD', + $this->_platform->getForeignKeyDeclarationSQL( + new ForeignKeyConstraint(array('a', 'b'), 'foreign_table', array('c', 'd'), 'fk', array( + 'notnull' => true, + 'match' => SQLAnywherePlatform::FOREIGN_KEY_MATCH_SIMPLE_UNIQUE, + 'onUpdate' => 'CASCADE', + 'onDelete' => 'SET NULL', + 'check_on_commit' => true, + 'clustered' => true, + 'for_olap_workload' => true + )) + ) + ); + $this->assertEquals( + 'FOREIGN KEY (a, b) REFERENCES foreign_table (c, d)', + $this->_platform->getForeignKeyDeclarationSQL( + new ForeignKeyConstraint(array('a', 'b'), 'foreign_table', array('c', 'd')) + ) + ); + } + + public function testGeneratesForeignKeyMatchClausesSQL() + { + $this->assertEquals('SIMPLE', $this->_platform->getForeignKeyMatchClauseSQL(1)); + $this->assertEquals('FULL', $this->_platform->getForeignKeyMatchClauseSQL(2)); + $this->assertEquals('UNIQUE SIMPLE', $this->_platform->getForeignKeyMatchClauseSQL(129)); + $this->assertEquals('UNIQUE FULL', $this->_platform->getForeignKeyMatchClauseSQL(130)); + } + + public function testCannotGenerateInvalidForeignKeyMatchClauseSQL() + { + $this->setExpectedException('\InvalidArgumentException'); + + $this->_platform->getForeignKeyMatchCLauseSQL(3); + } + + public function testCannotGenerateNoActionForeignKeyReferentialActionClauseSQL() + { + $this->setExpectedException('\InvalidArgumentException'); + $this->_platform->getForeignKeyReferentialActionSQL('no action'); + } + + public function testCannotGenerateForeignKeyConstraintSQLWithEmptyLocalColumns() + { + $this->setExpectedException('\InvalidArgumentException'); + $this->_platform->getForeignKeyDeclarationSQL(new ForeignKeyConstraint(array(), 'foreign_tbl', array('c', 'd'))); + } + + public function testCannotGenerateForeignKeyConstraintSQLWithEmptyForeignColumns() + { + $this->setExpectedException('\InvalidArgumentException'); + $this->_platform->getForeignKeyDeclarationSQL(new ForeignKeyConstraint(array('a', 'b'), 'foreign_tbl', array())); + } + + public function testCannotGenerateForeignKeyConstraintSQLWithEmptyForeignTableName() + { + $this->setExpectedException('\InvalidArgumentException'); + $this->_platform->getForeignKeyDeclarationSQL(new ForeignKeyConstraint(array('a', 'b'), '', array('c', 'd'))); + } + + public function testCannotGenerateCommonIndexWithCreateConstraintSQL() + { + $this->setExpectedException('\InvalidArgumentException'); + + $this->_platform->getCreateConstraintSQL(new Index('fooindex', array()), new Table('footable')); + } + + public function testCannotGenerateCustomConstraintWithCreateConstraintSQL() + { + $this->setExpectedException('\InvalidArgumentException'); + + $this->_platform->getCreateConstraintSQL($this->getMock('\Doctrine\DBAL\Schema\Constraint'), 'footable'); + } + + public function testGeneratesCreateIndexWithAdvancedPlatformOptionsSQL() + { + $this->assertEquals( + 'CREATE VIRTUAL UNIQUE CLUSTERED INDEX fooindex ON footable (a, b) FOR OLAP WORKLOAD', + $this->_platform->getCreateIndexSQL( + new Index( + 'fooindex', + array('a', 'b'), + true, + false, + array('virtual', 'clustered', 'for_olap_workload') + ), + 'footable' + ) + ); + } + + public function testDoesNotSupportIndexDeclarationInCreateAlterTableStatements() + { + $this->setExpectedException('\Doctrine\DBAL\DBALException'); + + $this->_platform->getIndexDeclarationSQL('index', new Index('index', array())); + } + + public function testGeneratesDropIndexSQL() + { + $index = new Index('fooindex', array()); + + $this->assertEquals('DROP INDEX fooindex', $this->_platform->getDropIndexSQL($index)); + $this->assertEquals('DROP INDEX footable.fooindex', $this->_platform->getDropIndexSQL($index, 'footable')); + $this->assertEquals('DROP INDEX footable.fooindex', $this->_platform->getDropIndexSQL( + $index, + new Table('footable') + )); + } + + public function testCannotGenerateDropIndexSQLWithInvalidIndexParameter() + { + $this->setExpectedException('\InvalidArgumentException'); + + $this->_platform->getDropIndexSQL(array('index'), 'table'); + } + + public function testCannotGenerateDropIndexSQLWithInvalidTableParameter() + { + $this->setExpectedException('\InvalidArgumentException'); + + $this->_platform->getDropIndexSQL('index', array('table')); + } + + public function testGeneratesSQLSnippets() + { + $this->assertEquals('STRING(column1, "string1", column2, "string2")', $this->_platform->getConcatExpression( + 'column1', + '"string1"', + 'column2', + '"string2"' + )); + $this->assertEquals('CURRENT DATE', $this->_platform->getCurrentDateSQL()); + $this->assertEquals('CURRENT TIME', $this->_platform->getCurrentTimeSQL()); + $this->assertEquals('CURRENT TIMESTAMP', $this->_platform->getCurrentTimestampSQL()); + $this->assertEquals("DATEADD(day, 4, '1987/05/02')", $this->_platform->getDateAddDaysExpression("'1987/05/02'", 4)); + $this->assertEquals("DATEADD(hour, 12, '1987/05/02')", $this->_platform->getDateAddHourExpression("'1987/05/02'", 12)); + $this->assertEquals("DATEADD(month, 102, '1987/05/02')", $this->_platform->getDateAddMonthExpression("'1987/05/02'", 102)); + $this->assertEquals("DATEDIFF(day, '1987/04/01', '1987/05/02')", $this->_platform->getDateDiffExpression("'1987/05/02'", "'1987/04/01'")); + $this->assertEquals("DATEADD(day, -1 * 4, '1987/05/02')", $this->_platform->getDateSubDaysExpression("'1987/05/02'", 4)); + $this->assertEquals("DATEADD(hour, -1 * 12, '1987/05/02')", $this->_platform->getDateSubHourExpression("'1987/05/02'", 12)); + $this->assertEquals("DATEADD(month, -1 * 102, '1987/05/02')", $this->_platform->getDateSubMonthExpression("'1987/05/02'", 102)); + $this->assertEquals("Y-m-d H:i:s.u", $this->_platform->getDateTimeFormatString()); + $this->assertEquals("H:i:s.u", $this->_platform->getTimeFormatString()); + $this->assertEquals('FOR UPDATE BY LOCK', $this->_platform->getForUpdateSQL()); + $this->assertEquals('NEWID()', $this->_platform->getGuidExpression()); + $this->assertEquals('CHARINDEX(substring_column, string_column)', $this->_platform->getLocateExpression('string_column', 'substring_column')); + $this->assertEquals('CHARINDEX(substring_column, string_column)', $this->_platform->getLocateExpression('string_column', 'substring_column')); + $this->assertEquals('CHARINDEX(substring_column, SUBSTR(string_column, 2))', $this->_platform->getLocateExpression('string_column', 'substring_column', 1)); + $this->assertEquals("HASH(column, 'MD5')", $this->_platform->getMd5Expression('column')); + $this->assertEquals('SUBSTRING(column, 5)', $this->_platform->getSubstringExpression('column', 5)); + $this->assertEquals('SUBSTRING(column, 5, 2)', $this->_platform->getSubstringExpression('column', 5, 2)); + $this->assertEquals('GLOBAL TEMPORARY', $this->_platform->getTemporaryTableSQL()); + $this->assertEquals( + 'LTRIM(column)', + $this->_platform->getTrimExpression('column', AbstractPlatform::TRIM_LEADING) + ); + $this->assertEquals( + 'RTRIM(column)', + $this->_platform->getTrimExpression('column', AbstractPlatform::TRIM_TRAILING) + ); + $this->assertEquals( + 'TRIM(column)', + $this->_platform->getTrimExpression('column') + ); + $this->assertEquals( + 'TRIM(column)', + $this->_platform->getTrimExpression('column', AbstractPlatform::TRIM_UNSPECIFIED) + ); + $this->assertEquals( + "SUBSTR(column, PATINDEX('%[^c]%', column))", + $this->_platform->getTrimExpression('column', AbstractPlatform::TRIM_LEADING, 'c') + ); + $this->assertEquals( + "REVERSE(SUBSTR(REVERSE(column), PATINDEX('%[^c]%', REVERSE(column))))", + $this->_platform->getTrimExpression('column', AbstractPlatform::TRIM_TRAILING, 'c') + ); + $this->assertEquals( + "REVERSE(SUBSTR(REVERSE(SUBSTR(column, PATINDEX('%[^c]%', column))), PATINDEX('%[^c]%', REVERSE(SUBSTR(column, PATINDEX('%[^c]%', column))))))", + $this->_platform->getTrimExpression('column', null, 'c') + ); + $this->assertEquals( + "REVERSE(SUBSTR(REVERSE(SUBSTR(column, PATINDEX('%[^c]%', column))), PATINDEX('%[^c]%', REVERSE(SUBSTR(column, PATINDEX('%[^c]%', column))))))", + $this->_platform->getTrimExpression('column', AbstractPlatform::TRIM_UNSPECIFIED, 'c') + ); + } + + public function testDoesNotSupportRegexp() + { + $this->setExpectedException('\Doctrine\DBAL\DBALException'); + + $this->_platform->getRegexpExpression(); + } + + public function testHasCorrectDefaultTransactionIsolationLevel() + { + $this->assertEquals( + Connection::TRANSACTION_READ_UNCOMMITTED, + $this->_platform->getDefaultTransactionIsolationLevel() + ); + } + + public function testGeneratesTransactionsCommands() + { + $this->assertEquals( + 'SET TEMPORARY OPTION isolation_level = 0', + $this->_platform->getSetTransactionIsolationSQL(Connection::TRANSACTION_READ_UNCOMMITTED) + ); + $this->assertEquals( + 'SET TEMPORARY OPTION isolation_level = 1', + $this->_platform->getSetTransactionIsolationSQL(Connection::TRANSACTION_READ_COMMITTED) + ); + $this->assertEquals( + 'SET TEMPORARY OPTION isolation_level = 2', + $this->_platform->getSetTransactionIsolationSQL(Connection::TRANSACTION_REPEATABLE_READ) + ); + $this->assertEquals( + 'SET TEMPORARY OPTION isolation_level = 3', + $this->_platform->getSetTransactionIsolationSQL(Connection::TRANSACTION_SERIALIZABLE) + ); + } + + public function testCannotGenerateTransactionCommandWithInvalidIsolationLevel() + { + $this->setExpectedException('\InvalidArgumentException'); + + $this->_platform->getSetTransactionIsolationSQL('invalid_transaction_isolation_level'); + } + + public function testModifiesLimitQuery() + { + $this->assertEquals( + 'SELECT TOP 10 * FROM user', + $this->_platform->modifyLimitQuery('SELECT * FROM user', 10, 0) + ); + } + + public function testModifiesLimitQueryWithEmptyOffset() + { + $this->assertEquals( + 'SELECT TOP 10 * FROM user', + $this->_platform->modifyLimitQuery('SELECT * FROM user', 10) + ); + } + + public function testModifiesLimitQueryWithOffset() + { + $this->assertEquals( + 'SELECT TOP 10 START AT 6 * FROM user', + $this->_platform->modifyLimitQuery('SELECT * FROM user', 10, 5) + ); + $this->assertEquals( + 'SELECT TOP ALL START AT 6 * FROM user', + $this->_platform->modifyLimitQuery('SELECT * FROM user', 0, 5) + ); + } + + public function testModifiesLimitQueryWithSubSelect() + { + $this->assertEquals( + 'SELECT TOP 10 * FROM (SELECT u.id as uid, u.name as uname FROM user) AS doctrine_tbl', + $this->_platform->modifyLimitQuery('SELECT * FROM (SELECT u.id as uid, u.name as uname FROM user) AS doctrine_tbl', 10) + ); + } + + public function testPrefersIdentityColumns() + { + $this->assertTrue($this->_platform->prefersIdentityColumns()); + } + + public function testDoesNotPreferSequences() + { + $this->assertFalse($this->_platform->prefersSequences()); + } + + public function testSupportsIdentityColumns() + { + $this->assertTrue($this->_platform->supportsIdentityColumns()); + } + + public function testSupportsPrimaryConstraints() + { + $this->assertTrue($this->_platform->supportsPrimaryConstraints()); + } + + public function testSupportsForeignKeyConstraints() + { + $this->assertTrue($this->_platform->supportsForeignKeyConstraints()); + } + + public function testSupportsForeignKeyOnUpdate() + { + $this->assertTrue($this->_platform->supportsForeignKeyOnUpdate()); + } + + public function testSupportsAlterTable() + { + $this->assertTrue($this->_platform->supportsAlterTable()); + } + + public function testSupportsTransactions() + { + $this->assertTrue($this->_platform->supportsTransactions()); + } + + public function testSupportsSchemas() + { + $this->assertTrue($this->_platform->supportsSchemas()); + } + + public function testSupportsIndexes() + { + $this->assertTrue($this->_platform->supportsIndexes()); + } + + public function testSupportsCommentOnStatement() + { + $this->assertTrue($this->_platform->supportsCommentOnStatement()); + } + + public function testSupportsSavePoints() + { + $this->assertTrue($this->_platform->supportsSavepoints()); + } + + public function testSupportsReleasePoints() + { + $this->assertTrue($this->_platform->supportsReleaseSavepoints()); + } + + public function testSupportsCreateDropDatabase() + { + $this->assertTrue($this->_platform->supportsCreateDropDatabase()); + } + + public function testSupportsGettingAffectedRows() + { + $this->assertTrue($this->_platform->supportsGettingAffectedRows()); + } + + public function testDoesNotSupportSequences() + { + $this->assertFalse($this->_platform->supportsSequences()); + } + + public function testDoesNotSupportInlineColumnComments() + { + $this->assertFalse($this->_platform->supportsInlineColumnComments()); + } + + public function testCannotEmulateSchemas() + { + $this->assertFalse($this->_platform->canEmulateSchemas()); + } +} diff --git a/tests/Doctrine/Tests/TestUtil.php b/tests/Doctrine/Tests/TestUtil.php index 8701c0a263a..e7791f52bdf 100644 --- a/tests/Doctrine/Tests/TestUtil.php +++ b/tests/Doctrine/Tests/TestUtil.php @@ -17,6 +17,8 @@ class TestUtil * 'db_username' : The username to use for connecting. * 'db_password' : The password to use for connecting. * 'db_host' : The hostname of the database to connect to. + * 'db_server' : The server name of the database to connect to + * (optional, some vendors allow multiple server instances with different names on the same host). * 'db_name' : The name of the database to connect to. * 'db_port' : The port of the database to connect to. * @@ -53,10 +55,18 @@ public static function getConnection() 'port' => $GLOBALS['tmpdb_port'] ); + if (isset($GLOBALS['db_server'])) { + $realDbParams['server'] = $GLOBALS['db_server']; + } + if (isset($GLOBALS['db_unix_socket'])) { $realDbParams['unix_socket'] = $GLOBALS['db_unix_socket']; } + if (isset($GLOBALS['tmpdb_server'])) { + $tmpDbParams['server'] = $GLOBALS['tmpdb_server']; + } + if (isset($GLOBALS['tmpdb_unix_socket'])) { $tmpDbParams['unix_socket'] = $GLOBALS['tmpdb_unix_socket']; } @@ -124,6 +134,10 @@ public static function getTempConnection() 'port' => $GLOBALS['tmpdb_port'] ); + if (isset($GLOBALS['tmpdb_server'])) { + $tmpDbParams['server'] = $GLOBALS['tmpdb_server']; + } + // Connect to tmpdb in order to drop and create the real test db. return \Doctrine\DBAL\DriverManager::getConnection($tmpDbParams); }