Unverified Commit a22303c1 authored by Sergei Morozov's avatar Sergei Morozov Committed by GitHub

Merge pull request #4077 from morozov/remove-sql-anywhere

Remove SQL Anywhere platform and driver
parents 97fc7ffd 7def4eb3
......@@ -53,19 +53,9 @@ In order to fetch a column with an index other than `0`, use `FetchMode::NUMERIC
`EchoSQLLogger` is no longer available as part of the package.
## BC BREAK: Removed support for SQL Anywhere 12 and older
## BC BREAK: Removed support for SQL Anywhere
DBAL now requires SQL Anywhere 16 or newer, support for unmaintained versions has been dropped.
If you are using any of the legacy versions, you have to upgrade to a newer SQL Anywhere version (16+).
The following classes have been removed:
* `Doctrine\DBAL\Platforms\SQLAnywherePlatform`
* `Doctrine\DBAL\Platforms\SQLAnywhere11Platform`
* `Doctrine\DBAL\Platforms\SQLAnywhere12Platform`
* `Doctrine\DBAL\Platforms\Keywords\SQLAnywhereKeywords`
* `Doctrine\DBAL\Platforms\Keywords\SQLAnywhere11Keywords`
* `Doctrine\DBAL\Platforms\Keywords\SQLAnywhere12Keywords`
The support for the SQL Anywhere database platform and the corresponding driver has been removed.
## BC BREAK: Removed support for PostgreSQL 9.3 and older
......
......@@ -18,7 +18,6 @@
"queryobject",
"sasql",
"sql",
"sqlanywhere",
"sqlite",
"sqlserver",
"sqlsrv"
......
......@@ -133,7 +133,6 @@ interfaces to use. It can be configured in one of three ways:
- ``pdo_sqlsrv``: A Microsoft SQL Server driver that uses pdo_sqlsrv PDO
- ``sqlsrv``: A Microsoft SQL Server driver that uses the sqlsrv PHP extension.
- ``oci8``: An Oracle driver that uses the oci8 PHP extension.
- ``sqlanywhere``: A SAP Sybase SQL Anywhere driver that uses the sqlanywhere PHP extension.
- ``driverClass``: Specifies a custom driver implementation if no
'driver' is specified. This allows the use of custom drivers that
......@@ -292,27 +291,6 @@ pdo_sqlsrv / sqlsrv
- ``port`` (integer): Port of the database to connect to.
- ``dbname`` (string): Name of the database/schema to connect to.
sqlanywhere
^^^^^^^^^^^
- ``user`` (string): Username to use when connecting to the
database.
- ``password`` (string): Password to use when connecting to the
database.
- ``server`` (string): Name of a running database server to connect to.
- ``host`` (string): Hostname of the database to connect to.
- ``port`` (integer): Port of the database to connect to.
- ``dbname`` (string): Name of the database/schema to connect to.
- ``persistent`` (boolean): Whether to establish a persistent connection.
Depending on the used underlying platform version, you can specify
any other connection parameter that is supported by the particular
platform version via the ``driverOptions`` option.
You can find a list of supported connection parameters for the
currently supported platform here:
- `SAP Sybase SQL Anywhere 16.0 <http://dcx.sybase.com/index.html#sa160/en/dbadmin/da-conparm.html>`_
Automatic platform version detection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
......@@ -337,11 +315,7 @@ without any extra configuration required:
- ``sqlsrv``
Some drivers cannot provide the version of the underlying database server without
having to query for it explicitly. For performance reasons (to save one extra query
on every connect), Doctrine does not enable automatic database platform version
detection for the following drivers:
- ``sqlanywhere``
having to query for it explicitly.
If you still want to tell Doctrine which database server version you are using in
order to choose the appropriate platform implementation, you can pass the
......
......@@ -19,7 +19,6 @@ The following database vendors are currently supported:
- Oracle
- Microsoft SQL Server
- PostgreSQL
- SAP Sybase SQL Anywhere
- SQLite
The Doctrine 2 database layer can be used independently of the
......
......@@ -58,11 +58,6 @@ PostgreSQL
- ``PostgreSQL94Platform`` for version 9.4 and above.
- ``PostgreSQL100Platform`` for version 10.0 and above.
SAP Sybase SQL Anywhere
^^^^^^^^^^^^^^^^^^^^^^^
- ``SQLAnywhere16Platform`` for version 16 and above.
SQLite
^^^^^^
......
......@@ -4,7 +4,7 @@ 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, SAP SQL Anywhere and Microsoft SQL Server). If you want to use the DBAL
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.
......
......@@ -116,10 +116,10 @@ The following options are not completely portable but are supported by most of t
vendors:
- **unsigned** (boolean): Whether a ``smallint``, ``integer`` or ``bigint`` Doctrine
type column should allow unsigned values only. Supported by MySQL and SQL Anywhere.
type column should allow unsigned values only. Supported only by MySQL.
Defaults to ``false``.
- **comment** (integer|string): The column comment. Supported by MySQL, PostgreSQL,
Oracle, SQL Server and SQL Anywhere. Defaults to ``null``.
Oracle and SQL Server. Defaults to ``null``.
Vendor specific options
^^^^^^^^^^^^^^^^^^^^^^^
......
......@@ -523,8 +523,6 @@ Please also notice the mapping specific footnotes for additional information.
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Server** | *all* | ``SMALLINT`` ``IDENTITY`` [11]_ |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Anywhere** | *all* | ``UNSIGNED`` [10]_ ``SMALLINT`` ``IDENTITY`` [11]_ |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQLite** | *all* | ``INTEGER`` [15]_ |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **integer** | ``integer`` | **MySQL** | *all* | ``INT`` ``UNSIGNED`` [10]_ ``AUTO_INCREMENT`` [11]_ |
......@@ -537,8 +535,6 @@ Please also notice the mapping specific footnotes for additional information.
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Server** | *all* | ``INT`` ``IDENTITY`` [11]_ |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Anywhere** | *all* | ``UNSIGNED`` [10]_ ``INT`` ``IDENTITY`` [11]_ |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQLite** | *all* | ``INTEGER`` [15]_ |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **bigint** | ``string`` | **MySQL** | *all* | ``BIGINT`` ``UNSIGNED`` [10]_ ``AUTO_INCREMENT`` [11]_ |
......@@ -551,8 +547,6 @@ Please also notice the mapping specific footnotes for additional information.
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Server** | *all* | ``BIGINT`` ``IDENTITY`` [11]_ |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Anywhere** | *all* | ``UNSIGNED`` [10]_ ``BIGINT`` ``IDENTITY`` [11]_ |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQLite** | *all* | ``INTEGER`` [15]_ |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **decimal** [7]_ | ``string`` | **MySQL** | *all* | ``NUMERIC(p, s)`` ``UNSIGNED`` [10]_ |
......@@ -563,8 +557,6 @@ Please also notice the mapping specific footnotes for additional information.
| | +--------------------------+ | |
| | | **SQL Server** | | |
| | +--------------------------+ | |
| | | **SQL Anywhere** | | |
| | +--------------------------+ | |
| | | **SQLite** | | |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **float** | ``float`` | **MySQL** | *all* | ``DOUBLE PRECISION`` ``UNSIGNED`` [10]_ |
......@@ -575,16 +567,12 @@ Please also notice the mapping specific footnotes for additional information.
| | +--------------------------+ | |
| | | **SQL Server** | | |
| | +--------------------------+ | |
| | | **SQL Anywhere** | | |
| | +--------------------------+ | |
| | | **SQLite** | | |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **string** | ``string`` | **MySQL** | *all* | ``VARCHAR(n)`` [3]_ |
| [2]_ [5]_ | +--------------------------+ | |
| | | **PostgreSQL** | | |
| | +--------------------------+ +----------------------------------------------------------+
| | | **SQL Anywhere** | | ``CHAR(n)`` [4]_ |
| | +--------------------------+ | |
| | | **SQLite** | | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **Oracle** | *all* | ``VARCHAR2(n)`` [3]_ |
......@@ -605,8 +593,6 @@ Please also notice the mapping specific footnotes for additional information.
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``TEXT`` |
| | +--------------------------+ | |
| | | **SQL Anywhere** | | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **Oracle** | *all* | ``CLOB`` |
| | +--------------------------+ | |
| | | **SQLite** | | |
......@@ -621,16 +607,12 @@ Please also notice the mapping specific footnotes for additional information.
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Server** | *all* | ``UNIQUEIDENTIFIER`` |
| | +--------------------------+ | |
| | | **SQL Anywhere** | | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``UUID`` |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **binary** | ``resource`` | **MySQL** | *all* | ``VARBINARY(n)`` [3]_ |
| [2]_ [6]_ | +--------------------------+ | |
| | | **SQL Server** | +----------------------------------------------------------+
| | +--------------------------+ | ``BINARY(n)`` [4]_ |
| | | **SQL Anywhere** | | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **Oracle** | *all* | ``RAW(n)`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``BYTEA`` [15]_ |
......@@ -651,8 +633,6 @@ Please also notice the mapping specific footnotes for additional information.
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Server** | *all* | ``VARBINARY(MAX)`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Anywhere** | *all* | ``LONG BINARY`` |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``BYTEA`` |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **boolean** | ``boolean`` | **MySQL** | *all* | ``TINYINT(1)`` |
......@@ -663,8 +643,6 @@ Please also notice the mapping specific footnotes for additional information.
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Server** | *all* | ``BIT`` |
| | +--------------------------+ | |
| | | **SQL Anywhere** | | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **Oracle** | *all* | ``NUMBER(1)`` |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **date** | ``\DateTime`` | **MySQL** | *all* | ``DATE`` |
......@@ -673,8 +651,6 @@ Please also notice the mapping specific footnotes for additional information.
| | +--------------------------+ | |
| | | **Oracle** | | |
| | +--------------------------+ | |
| | | **SQL Anywhere** | | |
| | +--------------------------+ | |
| | | **SQLite** | | |
| | +--------------------------+---------+ |
| | | **SQL Server** | "all" | |
......@@ -683,8 +659,6 @@ Please also notice the mapping specific footnotes for additional information.
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Server** | *all* | ``DATETIME`` |
| | +--------------------------+ | |
| | | **SQL Anywhere** | | |
| | +--------------------------+ | |
| | | **SQLite** | | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``TIMESTAMP(0) WITHOUT TIME ZONE`` |
......@@ -700,13 +674,9 @@ Please also notice the mapping specific footnotes for additional information.
| | | **PostgreSQL** | *all* | ``TIMESTAMP(0) WITH TIME ZONE`` |
| | +--------------------------+ | |
| | | **Oracle** | | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Anywhere** | "all" | ``TIMESTAMP WITH TIME ZONE`` |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **time** | ``\DateTime`` | **MySQL** | *all* | ``TIME`` |
| | +--------------------------+ | |
| | | **SQL Anywhere** | | |
| | +--------------------------+ | |
| | | **SQLite** | | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``TIME(0) WITHOUT TIME ZONE`` |
......@@ -725,8 +695,6 @@ Please also notice the mapping specific footnotes for additional information.
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``TEXT`` |
| | +--------------------------+ | |
| | | **SQL Anywhere** | | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **Oracle** | *all* | ``CLOB`` |
| | +--------------------------+ | |
| | | **SQLite** | | |
......@@ -745,8 +713,6 @@ Please also notice the mapping specific footnotes for additional information.
| | | | +----------------------------------------------------------+
| | | | | ``JSONB`` [21]_ |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **SQL Anywhere** | *all* | ``TEXT`` [1]_ |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **Oracle** | *all* | ``CLOB`` [1]_ |
| | +--------------------------+ | |
| | | **SQLite** | | |
......@@ -763,8 +729,6 @@ Please also notice the mapping specific footnotes for additional information.
| | +--------------------------+---------+----------------------------------------------------------+
| | | **PostgreSQL** | *all* | ``TEXT`` |
| | +--------------------------+ | |
| | | **SQL Anywhere** | | |
| | +--------------------------+---------+----------------------------------------------------------+
| | | **Oracle** | *all* | ``CLOB`` |
| | +--------------------------+ | |
| | | **SQLite** | | |
......
......@@ -43,7 +43,6 @@
</TooFewArguments>
<UndefinedConstant>
<errorLevel type="suppress">
<directory name="src/Driver/SQLAnywhere"/>
<!--
Requires a release of
https://github.com/JetBrains/phpstorm-stubs/pull/732
......@@ -51,11 +50,6 @@
<file name="tests/Driver/PDOPgSql/DriverTest.php" />
</errorLevel>
</UndefinedConstant>
<UndefinedFunction>
<errorLevel type="suppress">
<directory name="src/Driver/SQLAnywhere"/>
</errorLevel>
</UndefinedFunction>
<UndefinedClass>
<errorLevel type="suppress">
<!-- Contains references to optional dependencies -->
......
<?php
namespace Doctrine\DBAL\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\SQLAnywhere16Platform;
use Doctrine\DBAL\Schema\SQLAnywhereSchemaManager;
use Doctrine\DBAL\VersionAwarePlatformDriver;
use function preg_match;
/**
* Abstract base implementation of the {@link Doctrine\DBAL\Driver} interface for SAP Sybase SQL Anywhere based drivers.
*/
abstract class AbstractSQLAnywhereDriver implements ExceptionConverterDriver, VersionAwarePlatformDriver
{
/**
* {@inheritdoc}
*
* @link http://dcx.sybase.com/index.html#sa160/en/saerrors/sqlerror.html
*/
public function convertException($message, DriverException $exception)
{
switch ($exception->getErrorCode()) {
case '-306':
case '-307':
case '-684':
return new Exception\DeadlockException($message, $exception);
case '-210':
case '-1175':
case '-1281':
return new Exception\LockWaitTimeoutException($message, $exception);
case '-100':
case '-103':
case '-832':
return new Exception\ConnectionException($message, $exception);
case '-143':
return new Exception\InvalidFieldNameException($message, $exception);
case '-193':
case '-196':
return new Exception\UniqueConstraintViolationException($message, $exception);
case '-194':
case '-198':
return new Exception\ForeignKeyConstraintViolationException($message, $exception);
case '-144':
return new Exception\NonUniqueFieldNameException($message, $exception);
case '-184':
case '-195':
return new Exception\NotNullConstraintViolationException($message, $exception);
case '-131':
return new Exception\SyntaxErrorException($message, $exception);
case '-110':
return new Exception\TableExistsException($message, $exception);
case '-141':
case '-1041':
return new Exception\TableNotFoundException($message, $exception);
}
return new Exception\DriverException($message, $exception);
}
/**
* {@inheritdoc}
*/
public function createDatabasePlatformForVersion($version)
{
if (
preg_match(
'/^(?P<major>\d+)(?:\.(?P<minor>\d+)(?:\.(?P<patch>\d+)(?:\.(?P<build>\d+))?)?)?/',
$version,
$versionParts
) === 0
) {
throw DBALException::invalidPlatformVersionSpecified(
$version,
'<major_version>.<minor_version>.<patch_version>.<build_version>'
);
}
$majorVersion = $versionParts['major'];
$minorVersion = $versionParts['minor'] ?? 0;
$patchVersion = $versionParts['patch'] ?? 0;
$buildVersion = $versionParts['build'] ?? 0;
$version = $majorVersion . '.' . $minorVersion . '.' . $patchVersion . '.' . $buildVersion;
switch (true) {
default:
return new SQLAnywhere16Platform();
}
}
/**
* {@inheritdoc}
*
* @deprecated Use Connection::getDatabase() instead.
*/
public function getDatabase(Connection $conn)
{
$params = $conn->getParams();
return $params['dbname'] ?? $conn->query('SELECT DB_NAME()')->fetchOne();
}
/**
* {@inheritdoc}
*/
public function getDatabasePlatform()
{
return new SQLAnywhere16Platform();
}
/**
* {@inheritdoc}
*/
public function getSchemaManager(Connection $conn)
{
return new SQLAnywhereSchemaManager($conn);
}
}
<?php
namespace Doctrine\DBAL\Driver\SQLAnywhere;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\AbstractSQLAnywhereDriver;
use function array_keys;
use function array_map;
use function implode;
/**
* A Doctrine DBAL driver for the SAP Sybase SQL Anywhere PHP extension.
*/
class Driver extends AbstractSQLAnywhereDriver
{
/**
* {@inheritdoc}
*
* @throws DBALException If there was a problem establishing the connection.
*/
public function connect(array $params, $username = null, $password = null, array $driverOptions = [])
{
return new SQLAnywhereConnection(
$this->buildDsn(
$params['host'] ?? null,
$params['port'] ?? null,
$params['server'] ?? null,
$params['dbname'] ?? null,
$username,
$password,
$driverOptions
),
$params['persistent'] ?? false
);
}
/**
* {@inheritdoc}
*
* @deprecated
*/
public function getName()
{
return 'sqlanywhere';
}
/**
* Build the connection string for given connection parameters and driver options.
*
* @param string|null $host Host address to connect to.
* @param int|null $port Port to use for the connection (default to SQL Anywhere standard port 2638).
* @param string|null $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|null $dbname Name of the database on the server instance to connect to.
* @param string|null $username User name to use for connection authentication.
* @param string|null $password Password to use for connection authentication.
* @param mixed[] $driverOptions Additional parameters to use for the connection.
*
* @return string
*/
private function buildDsn($host, $port, $server, $dbname, $username = null, $password = null, array $driverOptions = [])
{
$host = $host ?? 'localhost';
$port = $port ?? 2638;
if (! empty($server)) {
$server = ';ServerName=' . $server;
}
return 'HOST=' . $host . ':' . $port .
$server .
';DBN=' . $dbname .
';UID=' . $username .
';PWD=' . $password .
';' . implode(
';',
array_map(static function ($key, $value): string {
return $key . '=' . $value;
}, array_keys($driverOptions), $driverOptions)
);
}
}
<?php
declare(strict_types=1);
namespace Doctrine\DBAL\Driver\SQLAnywhere;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use function sasql_fetch_assoc;
use function sasql_fetch_row;
use function sasql_stmt_affected_rows;
use function sasql_stmt_field_count;
use function sasql_stmt_reset;
use function sasql_stmt_result_metadata;
final class Result implements ResultInterface
{
/** @var resource */
private $statement;
/** @var resource */
private $result;
/**
* @param resource $statement
*/
public function __construct($statement)
{
$this->statement = $statement;
$this->result = sasql_stmt_result_metadata($statement);
}
/**
* {@inheritDoc}
*/
public function fetchNumeric()
{
return sasql_fetch_row($this->result);
}
/**
* {@inheritDoc}
*/
public function fetchAssociative()
{
return sasql_fetch_assoc($this->result);
}
/**
* {@inheritDoc}
*/
public function fetchOne()
{
return FetchUtils::fetchOne($this);
}
/**
* {@inheritDoc}
*/
public function fetchAllNumeric(): array
{
return FetchUtils::fetchAllNumeric($this);
}
/**
* {@inheritDoc}
*/
public function fetchAllAssociative(): array
{
return FetchUtils::fetchAllAssociative($this);
}
/**
* {@inheritDoc}
*/
public function fetchFirstColumn(): array
{
return FetchUtils::fetchFirstColumn($this);
}
public function rowCount(): int
{
return sasql_stmt_affected_rows($this->statement);
}
public function columnCount(): int
{
return sasql_stmt_field_count($this->statement);
}
public function free(): void
{
sasql_stmt_reset($this->statement);
}
}
<?php
namespace Doctrine\DBAL\Driver\SQLAnywhere;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\ParameterType;
use function assert;
use function is_float;
use function is_int;
use function is_resource;
use function is_string;
use function sasql_affected_rows;
use function sasql_commit;
use function sasql_connect;
use function sasql_escape_string;
use function sasql_insert_id;
use function sasql_pconnect;
use function sasql_real_query;
use function sasql_rollback;
use function sasql_set_option;
/**
* SAP Sybase SQL Anywhere implementation of the Connection interface.
*/
class SQLAnywhereConnection implements ServerInfoAwareConnection
{
/** @var resource The SQL Anywhere connection resource. */
private $connection;
/**
* Connects to database with given connection string.
*
* @param string $dsn The connection string.
* @param bool $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)) {
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);
}
}
/**
* {@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;
}
public function exec(string $statement): int
{
if (sasql_real_query($this->connection, $statement) === false) {
throw SQLAnywhereException::fromSQLAnywhereError($this->connection);
}
return sasql_affected_rows($this->connection);
}
/**
* {@inheritdoc}
*/
public function getServerVersion()
{
$version = $this->query("SELECT PROPERTY('ProductVersion')")->fetchOne();
assert(is_string($version));
return $version;
}
/**
* {@inheritdoc}
*/
public function lastInsertId($name = null)
{
if ($name === null) {
return sasql_insert_id($this->connection);
}
return $this->query('SELECT ' . $name . '.CURRVAL')->fetchOne();
}
public function prepare(string $sql): Statement
{
return new SQLAnywhereStatement($this->connection, $sql);
}
public function query(string $sql): ResultInterface
{
return $this->prepare($sql)->execute();
}
/**
* {@inheritdoc}
*/
public function quote($input, $type = ParameterType::STRING)
{
if (is_int($input) || is_float($input)) {
return $input;
}
return "'" . sasql_escape_string($this->connection, $input) . "'";
}
/**
* {@inheritdoc}
*/
public function requiresQueryForServerVersion()
{
return true;
}
/**
* {@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.
*
* @return bool Whether or not ending transactional mode succeeded.
*
* @throws SQLAnywhereException
*/
private function endTransaction()
{
if (! sasql_set_option($this->connection, 'auto_commit', 'on')) {
throw SQLAnywhereException::fromSQLAnywhereError($this->connection);
}
return true;
}
}
<?php
namespace Doctrine\DBAL\Driver\SQLAnywhere;
use Doctrine\DBAL\Driver\AbstractDriverException;
use InvalidArgumentException;
use function sasql_error;
use function sasql_errorcode;
use function sasql_sqlstate;
use function sasql_stmt_errno;
use function sasql_stmt_error;
/**
* SAP Sybase SQL Anywhere driver exception.
*
* @psalm-immutable
*/
class SQLAnywhereException extends AbstractDriverException
{
/**
* 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)
{
$state = $conn !== null ? sasql_sqlstate($conn) : sasql_sqlstate();
$code = 0;
$message = null;
/**
* Try retrieving the last error from statement resource if given
*/
if ($stmt !== null) {
$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 !== null && $code === 0) {
$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 === null || $code === 0) {
$code = sasql_errorcode();
$message = sasql_error();
}
if ($message) {
return new self('SQLSTATE [' . $state . '] [' . $code . '] ' . $message, $state, $code);
}
return new self('SQL Anywhere error occurred but no error message was retrieved from driver.', $state, $code);
}
}
<?php
namespace Doctrine\DBAL\Driver\SQLAnywhere;
use Doctrine\DBAL\Driver\Result as ResultInterface;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\ParameterType;
use function array_key_exists;
use function assert;
use function is_int;
use function is_resource;
/**
* SAP SQL Anywhere implementation of the Statement interface.
*/
class SQLAnywhereStatement implements Statement
{
/** @var resource The connection resource. */
private $conn;
/** @var resource The prepared SQL statement to execute. */
private $stmt;
/** @var mixed[] The references to bound parameter values. */
private $boundValues = [];
/**
* 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)) {
throw new SQLAnywhereException('Invalid SQL Anywhere connection resource: ' . $conn);
}
$this->conn = $conn;
$this->stmt = sasql_prepare($conn, $sql);
if (! is_resource($this->stmt)) {
throw SQLAnywhereException::fromSQLAnywhereError($conn);
}
}
/**
* {@inheritdoc}
*
* @throws SQLAnywhereException
*/
public function bindParam($column, &$variable, $type = ParameterType::STRING, $length = null)
{
assert(is_int($column));
switch ($type) {
case ParameterType::INTEGER:
case ParameterType::BOOLEAN:
$type = 'i';
break;
case ParameterType::LARGE_OBJECT:
$type = 'b';
break;
case ParameterType::NULL:
case ParameterType::STRING:
case ParameterType::BINARY:
$type = 's';
break;
default:
throw new SQLAnywhereException('Unknown type: ' . $type);
}
$this->boundValues[$column] =& $variable;
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 = ParameterType::STRING)
{
return $this->bindParam($param, $value, $type);
}
/**
* {@inheritdoc}
*
* @throws SQLAnywhereException
*/
public function execute($params = null): ResultInterface
{
if ($params !== null) {
$hasZeroIndex = array_key_exists(0, $params);
foreach ($params as $key => $val) {
if ($hasZeroIndex && is_int($key)) {
$this->bindValue($key + 1, $val);
} else {
$this->bindValue($key, $val);
}
}
}
if (! sasql_stmt_execute($this->stmt)) {
throw SQLAnywhereException::fromSQLAnywhereError($this->conn, $this->stmt);
}
return new Result($this->stmt);
}
public function free(): void
{
sasql_stmt_reset($this->stmt);
}
}
......@@ -11,7 +11,6 @@ use Doctrine\DBAL\Driver\PDOOracle\Driver as PDOOCIDriver;
use Doctrine\DBAL\Driver\PDOPgSql\Driver as PDOPgSQLDriver;
use Doctrine\DBAL\Driver\PDOSqlite\Driver as PDOSQLiteDriver;
use Doctrine\DBAL\Driver\PDOSqlsrv\Driver as PDOSQLSrvDriver;
use Doctrine\DBAL\Driver\SQLAnywhere\Driver as SQLAnywhereDriver;
use Doctrine\DBAL\Driver\SQLSrv\Driver as SQLSrvDriver;
use function array_keys;
......@@ -51,7 +50,6 @@ final class DriverManager
'ibm_db2' => DB2Driver::class,
'pdo_sqlsrv' => PDOSQLSrvDriver::class,
'mysqli' => MySQLiDriver::class,
'sqlanywhere' => SQLAnywhereDriver::class,
'sqlsrv' => SQLSrvDriver::class,
];
......
<?php
namespace Doctrine\DBAL\Platforms\Keywords;
/**
* SAP Sybase SQL Anywhere 16 reserved keywords list.
*/
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 [
'ADD',
'ALL',
'ALTER',
'AND',
'ANY',
'ARRAY',
'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',
'DATETIMEOFFSET',
'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',
'INNER',
'INOUT',
'INSENSITIVE',
'INSERT',
'INSERTING',
'INSTALL',
'INSTEAD',
'INT',
'INTEGER',
'INTEGRATED',
'INTERSECT',
'INTO',
'IS',
'ISOLATION',
'JOIN',
'JSON',
'KERBEROS',
'KEY',
'LATERAL',
'LEFT',
'LIKE',
'LIMIT',
'LOCK',
'LOGIN',
'LONG',
'MATCH',
'MEMBERSHIP',
'MERGE',
'MESSAGE',
'MODE',
'MODIFY',
'NATURAL',
'NCHAR',
'NEW',
'NO',
'NOHOLDLOCK',
'NOT',
'NOTIFY',
'NULL',
'NUMERIC',
'NVARCHAR',
'OF',
'OFF',
'ON',
'OPEN',
'OPENSTRING',
'OPENXML',
'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',
'ROW',
'ROWTYPE',
'SAVE',
'SAVEPOINT',
'SCROLL',
'SELECT',
'SENSITIVE',
'SESSION',
'SET',
'SETUSER',
'SHARE',
'SMALLINT',
'SOME',
'SPATIAL',
'SQLCODE',
'SQLSTATE',
'START',
'STOP',
'SUBTRANS',
'SUBTRANSACTION',
'SYNCHRONIZE',
'TABLE',
'TEMPORARY',
'THEN',
'TIME',
'TIMESTAMP',
'TINYINT',
'TO',
'TOP',
'TRAN',
'TREAT',
'TRIGGER',
'TRUNCATE',
'TSEQUAL',
'UNBOUNDED',
'UNION',
'UNIQUE',
'UNIQUEIDENTIFIER',
'UNKNOWN',
'UNNEST',
'UNSIGNED',
'UPDATE',
'UPDATING',
'USER',
'USING',
'VALIDATE',
'VALUES',
'VARBINARY',
'VARBIT',
'VARCHAR',
'VARIABLE',
'VARRAY',
'VARYING',
'VIEW',
'WAIT',
'WAITFOR',
'WHEN',
'WHERE',
'WHILE',
'WINDOW',
'WITH',
'WITHIN',
'WORK',
'WRITETEXT',
'XML',
];
}
}
<?php
namespace Doctrine\DBAL\Platforms;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\LockMode;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\ColumnDiff;
use Doctrine\DBAL\Schema\Constraint;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\Identifier;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Sequence;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\TransactionIsolationLevel;
use InvalidArgumentException;
use UnexpectedValueException;
use function array_merge;
use function array_unique;
use function array_values;
use function assert;
use function count;
use function explode;
use function func_get_args;
use function get_class;
use function implode;
use function is_string;
use function preg_match;
use function sprintf;
use function strlen;
use function strpos;
use function strtoupper;
use function substr;
/**
* Provides the behavior, features and SQL dialect of the SAP Sybase SQL Anywhere 16 database platform.
*/
class SQLAnywhere16Platform extends AbstractPlatform
{
public const FOREIGN_KEY_MATCH_SIMPLE = 1;
public const FOREIGN_KEY_MATCH_FULL = 2;
public const FOREIGN_KEY_MATCH_SIMPLE_UNIQUE = 129;
public const FOREIGN_KEY_MATCH_FULL_UNIQUE = 130;
/**
* {@inheritdoc}
*/
public function appendLockHint($fromClause, $lockMode)
{
switch (true) {
case $lockMode === LockMode::NONE:
return $fromClause . ' WITH (NOLOCK)';
case $lockMode === LockMode::PESSIMISTIC_READ:
return $fromClause . ' WITH (UPDLOCK)';
case $lockMode === LockMode::PESSIMISTIC_WRITE:
return $fromClause . ' WITH (XLOCK)';
default:
return $fromClause;
}
}
/**
* {@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') && (bool) $foreignKey->getOption('check_on_commit')) {
$query .= ' CHECK ON COMMIT';
}
if ($foreignKey->hasOption('clustered') && (bool) $foreignKey->getOption('clustered')) {
$query .= ' CLUSTERED';
}
if ($foreignKey->hasOption('for_olap_workload') && (bool) $foreignKey->getOption('for_olap_workload')) {
$query .= ' FOR OLAP WORKLOAD';
}
return $query;
}
/**
* {@inheritdoc}
*/
public function getAlterTableSQL(TableDiff $diff)
{
$sql = [];
$columnSql = [];
$commentsSQL = [];
$tableSql = [];
$alterClauses = [];
foreach ($diff->addedColumns as $column) {
if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
continue;
}
$alterClauses[] = $this->getAlterTableAddColumnClause($column);
$comment = $this->getColumnComment($column);
if ($comment === null || $comment === '') {
continue;
}
$commentsSQL[] = $this->getCommentOnColumnSQL(
$diff->getName($this)->getQuotedName($this),
$column->getQuotedName($this),
$comment
);
}
foreach ($diff->removedColumns as $column) {
if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
continue;
}
$alterClauses[] = $this->getAlterTableRemoveColumnClause($column);
}
foreach ($diff->changedColumns as $columnDiff) {
if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
continue;
}
$alterClause = $this->getAlterTableChangeColumnClause($columnDiff);
if ($alterClause !== null) {
$alterClauses[] = $alterClause;
}
if (! $columnDiff->hasChanged('comment')) {
continue;
}
$column = $columnDiff->column;
$commentsSQL[] = $this->getCommentOnColumnSQL(
$diff->getName($this)->getQuotedName($this),
$column->getQuotedName($this),
$this->getColumnComment($column)
);
}
foreach ($diff->renamedColumns as $oldColumnName => $column) {
if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
continue;
}
$sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' .
$this->getAlterTableRenameColumnClause($oldColumnName, $column);
}
if (! $this->onSchemaAlterTable($diff, $tableSql)) {
if (! empty($alterClauses)) {
$sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' . implode(', ', $alterClauses);
}
$sql = array_merge($sql, $commentsSQL);
$newName = $diff->getNewName();
if ($newName !== false) {
$sql[] = $this->getAlterTableClause($diff->getName($this)) . ' ' .
$this->getAlterTableRenameTableClause($newName);
}
$sql = array_merge(
$this->getPreAlterTableIndexForeignKeySQL($diff),
$sql,
$this->getPostAlterTableIndexForeignKeySQL($diff)
);
}
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 Identifier $tableName The quoted name of the table to alter.
*
* @return string
*/
protected function getAlterTableClause(Identifier $tableName)
{
return 'ALTER TABLE ' . $tableName->getQuotedName($this);
}
/**
* 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)
{
$oldColumnName = new Identifier($oldColumnName);
return 'RENAME ' . $oldColumnName->getQuotedName($this) . ' TO ' . $column->getQuotedName($this);
}
/**
* Returns the SQL clause for renaming a table in a table alteration.
*
* @param Identifier $newTableName The quoted name of the table to rename to.
*
* @return string
*/
protected function getAlterTableRenameTableClause(Identifier $newTableName)
{
return 'RENAME ' . $newTableName->getQuotedName($this);
}
/**
* 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)) {
$columnAlterationClause = 'ALTER ' .
$this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
if ($columnDiff->hasChanged('default') && $column->getDefault() === null) {
$columnAlterationClause .= ', ALTER ' . $column->getQuotedName($this) . ' DROP DEFAULT';
}
return $columnAlterationClause;
}
return null;
}
/**
* {@inheritdoc}
*/
public function getBigIntTypeDeclarationSQL(array $columnDef)
{
$columnDef['integer_type'] = 'BIGINT';
return $this->_getCommonIntegerTypeDeclarationSQL($columnDef);
}
/**
* {@inheritdoc}
*/
public function getBinaryDefaultLength()
{
return 1;
}
/**
* {@inheritdoc}
*/
public function getBinaryMaxLength()
{
return 32767;
}
/**
* {@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']) && (bool) $columnDef['notnull'] === false ? ' NULL' : '';
return 'BIT' . $nullClause;
}
/**
* {@inheritdoc}
*/
public function getClobTypeDeclarationSQL(array $field)
{
return 'TEXT';
}
/**
* {@inheritdoc}
*/
public function getCommentOnColumnSQL($tableName, $columnName, $comment)
{
$tableName = new Identifier($tableName);
$columnName = new Identifier($columnName);
$comment = $comment === null ? 'NULL' : $this->quoteStringLiteral($comment);
return sprintf(
'COMMENT ON COLUMN %s.%s IS %s',
$tableName->getQuotedName($this),
$columnName->getQuotedName($this),
$comment
);
}
/**
* {@inheritdoc}
*/
public function getConcatExpression()
{
return 'STRING(' . implode(', ', 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)
{
$database = new Identifier($database);
return "CREATE DATABASE '" . $database->getName() . "'";
}
/**
* {@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 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}
*/
protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
{
$factorClause = '';
if ($operator === '-') {
$factorClause = '-1 * ';
}
return 'DATEADD(' . $unit . ', ' . $factorClause . $interval . ', ' . $date . ')';
}
/**
* {@inheritdoc}
*/
public function getDateDiffExpression($date1, $date2)
{
return 'DATEDIFF(day, ' . $date2 . ', ' . $date1 . ')';
}
/**
* {@inheritdoc}
*/
public function getDateTimeFormatString()
{
return 'Y-m-d H:i:s.u';
}
/**
* {@inheritdoc}
*/
public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
{
return 'DATETIME';
}
/**
* {@inheritdoc}
*/
public function getDateTimeTzFormatString()
{
return 'Y-m-d H:i:s.uP';
}
/**
* {@inheritdoc}
*/
public function getDateTypeDeclarationSQL(array $fieldDeclaration)
{
return 'DATE';
}
/**
* {@inheritdoc}
*/
public function getDefaultTransactionIsolationLevel()
{
return TransactionIsolationLevel::READ_UNCOMMITTED;
}
/**
* {@inheritdoc}
*/
public function getDropDatabaseSQL($database)
{
$database = new Identifier($database);
return "DROP DATABASE '" . $database->getName() . "'";
}
/**
* {@inheritdoc}
*/
public function getDropIndexSQL($index, $table = null)
{
if ($index instanceof Index) {
$index = $index->getQuotedName($this);
}
if (! is_string($index)) {
throw new InvalidArgumentException(
'AbstractPlatform::getDropIndexSQL() expects $index parameter to be string or ' . Index::class . '.'
);
}
if (! isset($table)) {
return 'DROP INDEX ' . $index;
}
if ($table instanceof Table) {
$table = $table->getQuotedName($this);
}
if (! is_string($table)) {
throw new InvalidArgumentException(
'AbstractPlatform::getDropIndexSQL() expects $table parameter to be string or ' . Index::class . '.'
);
}
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') && (bool) $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 int $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';
case self::FOREIGN_KEY_MATCH_FULL:
return 'FULL';
case self::FOREIGN_KEY_MATCH_SIMPLE_UNIQUE:
return 'UNIQUE SIMPLE';
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)
{
// NO ACTION is not supported, therefore falling back to RESTRICT.
if (strtoupper($action) === 'NO ACTION') {
return 'RESTRICT';
}
return parent::getForeignKeyReferentialActionSQL($action);
}
/**
* {@inheritdoc}
*/
public function getForUpdateSQL()
{
return '';
}
/**
* {@inheritdoc}
*
* @deprecated Use application-generated UUIDs instead
*/
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)
{
$user = 'USER_NAME()';
if (strpos($table, '.') !== false) {
[$user, $table] = explode('.', $table);
$user = $this->quoteStringLiteral($user);
}
return sprintf(
<<<'SQL'
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 "%s"') 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
WHERE def.base_owner_name = %s
ORDER BY def.base_column_id ASC
SQL
,
$table,
$user
);
}
/**
* {@inheritdoc}
*
* @todo Where is this used? Which information should be retrieved?
*/
public function getListTableConstraintsSQL($table)
{
$user = '';
if (strpos($table, '.') !== false) {
[$user, $table] = explode('.', $table);
$user = $this->quoteStringLiteral($user);
$table = $this->quoteStringLiteral($table);
} else {
$table = $this->quoteStringLiteral($table);
}
return sprintf(
<<<'SQL'
SELECT con.*
FROM SYS.SYSCONSTRAINT AS con
JOIN SYS.SYSTAB AS tab ON con.table_object_id = tab.object_id
WHERE tab.table_name = %s
AND tab.creator = USER_ID(%s)
SQL
,
$table,
$user
);
}
/**
* {@inheritdoc}
*/
public function getListTableForeignKeysSQL($table)
{
$user = '';
if (strpos($table, '.') !== false) {
[$user, $table] = explode('.', $table);
$user = $this->quoteStringLiteral($user);
$table = $this->quoteStringLiteral($table);
} else {
$table = $this->quoteStringLiteral($table);
}
return sprintf(
<<<'SQL'
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 = %s
AND ftbl.creator = USER_ID(%s)
ORDER BY fk.foreign_index_id ASC, idxcol.sequence ASC
SQL
,
$table,
$user
);
}
/**
* {@inheritdoc}
*/
public function getListTableIndexesSQL($table, $currentDatabase = null)
{
$user = '';
if (strpos($table, '.') !== false) {
[$user, $table] = explode('.', $table);
$user = $this->quoteStringLiteral($user);
$table = $this->quoteStringLiteral($table);
} else {
$table = $this->quoteStringLiteral($table);
}
return sprintf(
<<<'SQL'
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 = %s
AND tbl.creator = USER_ID(%s)
AND idx.index_category != 2 -- exclude indexes implicitly created by foreign key constraints
ORDER BY idx.index_id ASC, idxcol.sequence ASC
SQL
,
$table,
$user
);
}
/**
* {@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 'LOCATE(' . $str . ', ' . $substr . ')';
}
return 'LOCATE(' . $str . ', ' . $substr . ', ' . $startPos . ')';
}
/**
* {@inheritdoc}
*/
public function getMaxIdentifierLength()
{
return 128;
}
/**
* {@inheritdoc}
*/
public function getMd5Expression($column)
{
return 'HASH(' . $column . ", 'MD5')";
}
/**
* {@inheritdoc}
*/
public function getRegexpExpression()
{
return 'REGEXP';
}
/**
* {@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)
{
$database = new Identifier($database);
return "START DATABASE '" . $database->getName() . "' 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)
{
$database = new Identifier($database);
return 'STOP DATABASE "' . $database->getName() . '" UNCONDITIONALLY';
}
/**
* {@inheritdoc}
*/
public function getSubstringExpression($value, $from, $length = null)
{
if ($length === null) {
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 = TrimMode::UNSPECIFIED, $char = false)
{
if ($char === false) {
switch ($pos) {
case TrimMode::LEADING:
return $this->getLtrimExpression($str);
case TrimMode::TRAILING:
return $this->getRtrimExpression($str);
default:
return 'TRIM(' . $str . ')';
}
}
$pattern = "'%[^' + " . $char . " + ']%'";
switch ($pos) {
case TrimMode::LEADING:
return 'SUBSTR(' . $str . ', PATINDEX(' . $pattern . ', ' . $str . '))';
case TrimMode::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)
{
$tableIdentifier = new Identifier($tableName);
return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this);
}
/**
* {@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 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 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}
*/
public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration)
{
return 'TIMESTAMP WITH TIME ZONE';
}
/**
* {@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 supportsCommentOnStatement()
{
return true;
}
/**
* {@inheritdoc}
*/
public function supportsIdentityColumns()
{
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 = [])
{
$columnListSql = $this->getColumnDeclarationListSQL($columns);
$indexSql = [];
if (! empty($options['uniqueConstraints'])) {
foreach ((array) $options['uniqueConstraints'] as $name => $definition) {
$columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
}
}
if (! empty($options['indexes'])) {
foreach ((array) $options['indexes'] as $index) {
assert($index instanceof Index);
$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([$query], $indexSql);
}
/**
* {@inheritdoc}
*/
protected function _getTransactionIsolationLevelSQL($level)
{
switch ($level) {
case TransactionIsolationLevel::READ_UNCOMMITTED:
return '0';
case TransactionIsolationLevel::READ_COMMITTED:
return '1';
case TransactionIsolationLevel::REPEATABLE_READ:
return '2';
case TransactionIsolationLevel::SERIALIZABLE:
return '3';
default:
throw new InvalidArgumentException('Invalid isolation level:' . $level);
}
}
/**
* {@inheritdoc}
*/
protected function doModifyLimitQuery($query, $limit, $offset)
{
$limitOffsetClause = $this->getTopClauseSQL($limit, $offset);
if ($limitOffsetClause === '') {
return $query;
}
if (preg_match('/^\s*(SELECT\s+(DISTINCT\s+)?)(.*)/i', $query, $matches) === 0) {
return $query;
}
return $matches[1] . $limitOffsetClause . ' ' . $matches[3];
}
private function getTopClauseSQL(?int $limit, ?int $offset): string
{
if ($offset > 0) {
return sprintf('TOP %s START AT %d', $limit ?? 'ALL', $offset + 1);
}
return $limit === null ? '' : 'TOP ' . $limit;
}
/**
* Return the INDEX query section dealing with non-standard
* SQL Anywhere options.
*
* @param Index $index Index definition
*
* @return string
*/
protected function getAdvancedIndexOptionsSQL(Index $index)
{
if ($index->hasFlag('with_nulls_distinct') && $index->hasFlag('with_nulls_not_distinct')) {
throw new UnexpectedValueException(
'An Index can either have a "with_nulls_distinct" or "with_nulls_not_distinct" flag but not both.'
);
}
$sql = '';
if (! $index->isPrimary() && $index->hasFlag('for_olap_workload')) {
$sql .= ' FOR OLAP WORKLOAD';
}
if (! $index->isPrimary() && $index->isUnique() && $index->hasFlag('with_nulls_not_distinct')) {
return ' WITH NULLS NOT DISTINCT' . $sql;
}
if (! $index->isPrimary() && $index->isUnique() && $index->hasFlag('with_nulls_distinct')) {
return ' WITH NULLS DISTINCT' . $sql;
}
return $sql;
}
/**
* {@inheritdoc}
*/
protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
{
return $fixed
? 'BINARY(' . ($length > 0 ? $length : $this->getBinaryDefaultLength()) . ')'
: 'VARBINARY(' . ($length > 0 ? $length : $this->getBinaryDefaultLength()) . ')';
}
/**
* 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)) {
$name = new Identifier($name);
$sql .= 'CONSTRAINT ' . $name->getQuotedName($this) . ' ';
}
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 getRenameIndexSQL($oldIndexName, Index $index, $tableName)
{
return ['ALTER INDEX ' . $oldIndexName . ' ON ' . $tableName . ' RENAME TO ' . $index->getQuotedName($this)];
}
/**
* {@inheritdoc}
*/
protected function getReservedKeywordsClass()
{
return Keywords\SQLAnywhereKeywords::class;
}
/**
* {@inheritdoc}
*/
protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
{
return $fixed
? ($length > 0 ? 'CHAR(' . $length . ')' : 'CHAR(' . $this->getVarcharDefaultLength() . ')')
: ($length > 0 ? 'VARCHAR(' . $length . ')' : 'VARCHAR(' . $this->getVarcharDefaultLength() . ')');
}
/**
* {@inheritdoc}
*/
protected function initializeDoctrineTypeMappings()
{
$this->doctrineTypeMapping = [
'bigint' => 'bigint',
'binary' => 'binary',
'bit' => 'boolean',
'char' => 'string',
'decimal' => 'decimal',
'date' => 'date',
'datetime' => 'datetime',
'double' => 'float',
'float' => 'float',
'image' => 'blob',
'int' => 'integer',
'integer' => 'integer',
'long binary' => 'blob',
'long nvarchar' => 'text',
'long varbit' => 'text',
'long varchar' => 'text',
'money' => 'decimal',
'nchar' => 'string',
'ntext' => 'text',
'numeric' => 'decimal',
'nvarchar' => 'string',
'smalldatetime' => 'datetime',
'smallint' => 'smallint',
'smallmoney' => 'decimal',
'text' => 'text',
'time' => 'time',
'timestamp' => 'datetime',
'timestamp with time zone' => 'datetime',
'tinyint' => 'smallint',
'uniqueidentifier' => 'guid',
'uniqueidentifierstr' => 'guid',
'unsigned bigint' => 'bigint',
'unsigned int' => 'integer',
'unsigned smallint' => 'smallint',
'unsigned tinyint' => 'smallint',
'varbinary' => 'binary',
'varbit' => 'string',
'varchar' => 'string',
'xml' => 'text',
];
}
}
......@@ -8,7 +8,6 @@ use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\DB2Platform;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
use Doctrine\DBAL\Platforms\SQLAnywhere16Platform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Platforms\SQLServer2012Platform;
......@@ -24,7 +23,6 @@ final class OptimizeFlags
DB2Platform::class => 0,
OraclePlatform::class => Connection::PORTABILITY_EMPTY_TO_NULL,
PostgreSQL94Platform::class => 0,
SQLAnywhere16Platform::class => 0,
SqlitePlatform::class => 0,
SQLServer2012Platform::class => 0,
];
......
<?php
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Platforms\SQLAnywhere16Platform;
use Doctrine\DBAL\Types\Type;
use function assert;
use function is_string;
use function preg_replace;
/**
* SAP Sybase SQL Anywhere schema manager.
*/
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.
*
* @return void
*/
public function startDatabase($database)
{
assert($this->_platform instanceof SQLAnywhere16Platform);
$this->_execSql($this->_platform->getStartDatabaseSQL($database));
}
/**
* Stops a database.
*
* @param string $database The name of the database to stop.
*
* @return void
*/
public function stopDatabase($database)
{
assert($this->_platform instanceof SQLAnywhere16Platform);
$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'] !== null) {
// Strip quotes from default value.
$default = preg_replace(["/^'(.*)'$/", "/''/"], ['$1', "'"], $tableColumn['default']);
if ($default === 'autoincrement') {
$default = null;
}
}
switch ($tableColumn['type']) {
case 'binary':
case 'char':
case 'nchar':
$fixed = true;
break;
}
switch ($type) {
case 'decimal':
case 'float':
$precision = $tableColumn['length'];
$scale = $tableColumn['scale'];
break;
}
return new Column(
$tableColumn['column_name'],
Type::getType($type),
[
'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' => isset($tableColumn['comment']) && $tableColumn['comment'] !== ''
? $tableColumn['comment']
: null,
]
);
}
/**
* {@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 = [];
foreach ($tableForeignKeys as $tableForeignKey) {
if (! isset($foreignKeys[$tableForeignKey['index_name']])) {
$foreignKeys[$tableForeignKey['index_name']] = [
'local_columns' => [$tableForeignKey['local_column']],
'foreign_table' => $tableForeignKey['foreign_table'],
'foreign_columns' => [$tableForeignKey['foreign_column']],
'name' => $tableForeignKey['index_name'],
'options' => [
'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'] = (bool) $tableIndex['primary'];
$tableIndex['flags'] = [];
if ($tableIndex['clustered']) {
$tableIndex['flags'][] = 'clustered';
}
if ($tableIndex['with_nulls_not_distinct']) {
$tableIndex['flags'][] = 'with_nulls_not_distinct';
}
if (! $tableIndex['for_olap_workload']) {
continue;
}
$tableIndex['flags'][] = 'for_olap_workload';
}
return parent::_getPortableTableIndexesList($tableIndexRows, $tableName);
}
/**
* {@inheritdoc}
*/
protected function _getPortableViewDefinition($view)
{
$definition = preg_replace('/^.*\s+as\s+SELECT(.*)/i', 'SELECT$1', $view['view_def']);
assert(is_string($definition));
return new View($view['table_name'], $definition);
}
}
......@@ -12,7 +12,6 @@ use Doctrine\DBAL\Platforms\Keywords\OracleKeywords;
use Doctrine\DBAL\Platforms\Keywords\PostgreSQL100Keywords;
use Doctrine\DBAL\Platforms\Keywords\PostgreSQL94Keywords;
use Doctrine\DBAL\Platforms\Keywords\ReservedKeywordsValidator;
use Doctrine\DBAL\Platforms\Keywords\SQLAnywhereKeywords;
use Doctrine\DBAL\Platforms\Keywords\SQLiteKeywords;
use Doctrine\DBAL\Platforms\Keywords\SQLServer2012Keywords;
use Doctrine\DBAL\Tools\Console\ConnectionProvider;
......@@ -40,7 +39,6 @@ class ReservedWordsCommand extends Command
'oracle' => OracleKeywords::class,
'pgsql' => PostgreSQL94Keywords::class,
'pgsql100' => PostgreSQL100Keywords::class,
'sqlanywhere' => SQLAnywhereKeywords::class,
'sqlite' => SQLiteKeywords::class,
'sqlserver' => SQLServer2012Keywords::class,
];
......@@ -86,8 +84,8 @@ class ReservedWordsCommand extends Command
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, Oracle
and SQL Anywhere keywords are checked:
By default SQLite, MySQL, PostgreSQL, Microsoft SQL Server and Oracle
keywords are checked:
<info>%command.full_name%</info>
......@@ -108,7 +106,6 @@ The following keyword lists are currently shipped with Doctrine:
* oracle
* sqlserver
* sqlserver2012
* sqlanywhere
* db2 (Not checked by default)
EOT
);
......
<?php
namespace Doctrine\DBAL\Tests\Driver;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\AbstractSQLAnywhereDriver;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\SQLAnywhere16Platform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\SQLAnywhereSchemaManager;
class AbstractSQLAnywhereDriverTest extends AbstractDriverTest
{
protected function createDriver(): Driver
{
return $this->getMockForAbstractClass(AbstractSQLAnywhereDriver::class);
}
protected function createPlatform(): AbstractPlatform
{
return new SQLAnywhere16Platform();
}
protected function createSchemaManager(Connection $connection): AbstractSchemaManager
{
return new SQLAnywhereSchemaManager($connection);
}
/**
* {@inheritDoc}
*/
protected function getDatabasePlatformsForVersions(): array
{
return [
['16', SQLAnywhere16Platform::class],
['16.0', SQLAnywhere16Platform::class],
['16.0.0', SQLAnywhere16Platform::class],
['16.0.0.0', SQLAnywhere16Platform::class],
['16.1.2.3', SQLAnywhere16Platform::class],
['16.9.9.9', SQLAnywhere16Platform::class],
['17', SQLAnywhere16Platform::class],
];
}
/**
* {@inheritDoc}
*/
protected static function getExceptionConversionData(): array
{
return [
self::EXCEPTION_CONNECTION => [
['-100', null, null],
['-103', null, null],
['-832', null, null],
],
self::EXCEPTION_FOREIGN_KEY_CONSTRAINT_VIOLATION => [
['-198', null, null],
],
self::EXCEPTION_INVALID_FIELD_NAME => [
['-143', null, null],
],
self::EXCEPTION_NON_UNIQUE_FIELD_NAME => [
['-144', null, null],
],
self::EXCEPTION_NOT_NULL_CONSTRAINT_VIOLATION => [
['-184', null, null],
['-195', null, null],
],
self::EXCEPTION_SYNTAX_ERROR => [
['-131', null, null],
],
self::EXCEPTION_TABLE_EXISTS => [
['-110', null, null],
],
self::EXCEPTION_TABLE_NOT_FOUND => [
['-141', null, null],
['-1041', null, null],
],
self::EXCEPTION_UNIQUE_CONSTRAINT_VIOLATION => [
['-193', null, null],
['-196', null, null],
],
self::EXCEPTION_DEADLOCK => [
['-306', null, null],
['-307', null, null],
['-684', null, null],
],
self::EXCEPTION_LOCK_WAIT_TIMEOUT => [
['-210', null, null],
['-1175', null, null],
['-1281', null, null],
],
];
}
}
<?php
namespace Doctrine\DBAL\Tests\Driver\SQLAnywhere;
use Doctrine\DBAL\Driver as DriverInterface;
use Doctrine\DBAL\Driver\SQLAnywhere\Driver;
use Doctrine\DBAL\Tests\Driver\AbstractSQLAnywhereDriverTest;
class DriverTest extends AbstractSQLAnywhereDriverTest
{
public function testReturnsName(): void
{
self::assertSame('sqlanywhere', $this->driver->getName());
}
protected function createDriver(): DriverInterface
{
return new Driver();
}
}
<?php
namespace Doctrine\DBAL\Tests\Driver\SQLAnywhere;
use Doctrine\DBAL\Driver\SQLAnywhere\SQLAnywhereConnection;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use function extension_loaded;
class SQLAnywhereConnectionTest extends TestCase
{
/**
* The sqlanywhere driver connection mock under test.
*
* @var SQLAnywhereConnection|MockObject
*/
private $connectionMock;
protected function setUp(): void
{
if (! extension_loaded('sqlanywhere')) {
$this->markTestSkipped('sqlanywhere is not installed.');
}
parent::setUp();
$this->connectionMock = $this->getMockBuilder(SQLAnywhereConnection::class)
->disableOriginalConstructor()
->getMockForAbstractClass();
}
public function testRequiresQueryForServerVersion(): void
{
self::assertTrue($this->connectionMock->requiresQueryForServerVersion());
}
}
<?php
namespace Doctrine\DBAL\Tests\Functional\Driver\SQLAnywhere;
use Doctrine\DBAL\Driver\SQLAnywhere\Driver;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Tests\FunctionalTestCase;
use function extension_loaded;
class ConnectionTest extends FunctionalTestCase
{
protected function setUp(): void
{
if (! extension_loaded('sqlanywhere')) {
self::markTestSkipped('sqlanywhere is not installed.');
}
parent::setUp();
if ($this->connection->getDriver() instanceof Driver) {
return;
}
self::markTestSkipped('sqlanywhere only test.');
}
public function testNonPersistentConnection(): void
{
$params = $this->connection->getParams();
$params['persistent'] = false;
$conn = DriverManager::getConnection($params);
$conn->connect();
self::assertTrue($conn->isConnected(), 'No SQLAnywhere-nonpersistent connection established');
}
public function testPersistentConnection(): void
{
$params = $this->connection->getParams();
$params['persistent'] = true;
$conn = DriverManager::getConnection($params);
$conn->connect();
self::assertTrue($conn->isConnected(), 'No SQLAnywhere-persistent connection established');
}
}
<?php
namespace Doctrine\DBAL\Tests\Functional\Driver\SQLAnywhere;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver as DriverInterface;
use Doctrine\DBAL\Driver\SQLAnywhere\Driver;
use Doctrine\DBAL\Tests\Functional\Driver\AbstractDriverTest;
use function extension_loaded;
class DriverTest extends AbstractDriverTest
{
protected function setUp(): void
{
if (! extension_loaded('sqlanywhere')) {
self::markTestSkipped('sqlanywhere is not installed.');
}
parent::setUp();
if ($this->connection->getDriver() instanceof Driver) {
return;
}
self::markTestSkipped('sqlanywhere only test.');
}
public function testReturnsDatabaseNameWithoutDatabaseNameParameter(): void
{
$params = $this->connection->getParams();
unset($params['dbname']);
$connection = new Connection(
$params,
$this->connection->getDriver(),
$this->connection->getConfiguration(),
$this->connection->getEventManager()
);
// SQL Anywhere has no "default" database. The name of the default database
// is defined on server startup and therefore can be arbitrary.
self::assertIsString($this->driver->getDatabase($connection));
}
protected function createDriver(): DriverInterface
{
return new Driver();
}
}
<?php
namespace Doctrine\DBAL\Tests\Functional\Driver\SQLAnywhere;
use Doctrine\DBAL\Driver\SQLAnywhere\Driver;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Tests\FunctionalTestCase;
use function extension_loaded;
class StatementTest extends FunctionalTestCase
{
protected function setUp(): void
{
if (! extension_loaded('sqlanywhere')) {
self::markTestSkipped('sqlanywhere is not installed.');
}
parent::setUp();
if ($this->connection->getDriver() instanceof Driver) {
return;
}
self::markTestSkipped('sqlanywhere only test.');
}
public function testNonPersistentStatement(): void
{
$params = $this->connection->getParams();
$params['persistent'] = false;
$conn = DriverManager::getConnection($params);
$conn->connect();
self::assertTrue($conn->isConnected(), 'No SQLAnywhere-Connection established');
$prepStmt = $conn->prepare('SELECT 1');
self::assertTrue($prepStmt->execute(), ' Statement non-persistent failed');
}
public function testPersistentStatement(): void
{
$params = $this->connection->getParams();
$params['persistent'] = true;
$conn = DriverManager::getConnection($params);
$conn->connect();
self::assertTrue($conn->isConnected(), 'No SQLAnywhere-Connection established');
$prepStmt = $conn->prepare('SELECT 1');
self::assertTrue($prepStmt->execute(), ' Statement persistent failed');
}
}
<?php
namespace Doctrine\DBAL\Tests\Functional\Schema;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\View;
class SQLAnywhereSchemaManagerTest extends SchemaManagerFunctionalTestCase
{
public function testCreateAndListViews(): void
{
$this->createTestTable('view_test_table');
$name = 'doctrine_test_view';
$sql = 'SELECT * from DBA.view_test_table';
$view = new View($name, $sql);
$this->schemaManager->dropAndCreateView($view);
$views = $this->schemaManager->listViews();
self::assertCount(1, $views, 'Database has to have one view.');
self::assertInstanceOf(View::class, $views[$name]);
self::assertEquals($name, $views[$name]->getName());
self::assertMatchesRegularExpression(
'/^SELECT \* from "?DBA"?\."?view_test_table"?$/',
$views[$name]->getSql()
);
}
public function testDropAndCreateAdvancedIndex(): void
{
$table = $this->getTestTable('test_create_advanced_index');
$this->schemaManager->dropAndCreateTable($table);
$this->schemaManager->dropAndCreateIndex(
new Index('test', ['test'], true, false, ['clustered', 'with_nulls_not_distinct', 'for_olap_workload']),
$table->getName()
);
$tableIndexes = $this->schemaManager->listTableIndexes('test_create_advanced_index');
self::assertIsArray($tableIndexes);
self::assertEquals('test', $tableIndexes['test']->getName());
self::assertEquals(['test'], $tableIndexes['test']->getColumns());
self::assertTrue($tableIndexes['test']->isUnique());
self::assertFalse($tableIndexes['test']->isPrimary());
self::assertTrue($tableIndexes['test']->hasFlag('clustered'));
self::assertTrue($tableIndexes['test']->hasFlag('with_nulls_not_distinct'));
self::assertTrue($tableIndexes['test']->hasFlag('for_olap_workload'));
}
public function testListTableColumnsWithFixedStringTypeColumn(): void
{
$table = new Table('list_table_columns_char');
$table->addColumn('id', 'integer', ['notnull' => true]);
$table->addColumn('test', 'string', ['fixed' => true]);
$table->setPrimaryKey(['id']);
$this->schemaManager->dropAndCreateTable($table);
$columns = $this->schemaManager->listTableColumns('list_table_columns_char');
self::assertArrayHasKey('test', $columns);
self::assertTrue($columns['test']->getFixed());
}
public function testCommentInTable(): void
{
self::markTestSkipped('Table level comments are not supported on SQLAnywhere');
}
}
......@@ -36,11 +36,8 @@ class TemporaryTableTest extends FunctionalTestCase
*/
public function testDropTemporaryTableNotAutoCommitTransaction(): void
{
if (
$this->connection->getDatabasePlatform()->getName() === 'sqlanywhere' ||
$this->connection->getDatabasePlatform()->getName() === 'oracle'
) {
self::markTestSkipped('Test does not work on Oracle and SQL Anywhere.');
if ($this->connection->getDatabasePlatform()->getName() === 'oracle') {
self::markTestSkipped('Test does not work on Oracle.');
}
$platform = $this->connection->getDatabasePlatform();
......@@ -73,11 +70,8 @@ class TemporaryTableTest extends FunctionalTestCase
*/
public function testCreateTemporaryTableNotAutoCommitTransaction(): void
{
if (
$this->connection->getDatabasePlatform()->getName() === 'sqlanywhere' ||
$this->connection->getDatabasePlatform()->getName() === 'oracle'
) {
self::markTestSkipped('Test does not work on Oracle and SQL Anywhere.');
if ($this->connection->getDatabasePlatform()->getName() === 'oracle') {
self::markTestSkipped('Test does not work on Oracle.');
}
$platform = $this->connection->getDatabasePlatform();
......
<?php
namespace Doctrine\DBAL\Tests\Platforms;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\LockMode;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\SQLAnywhere16Platform;
use Doctrine\DBAL\Platforms\TrimMode;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\ColumnDiff;
use Doctrine\DBAL\Schema\Comparator;
use Doctrine\DBAL\Schema\Constraint;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Sequence;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\TransactionIsolationLevel;
use Doctrine\DBAL\Types\Type;
use InvalidArgumentException;
use function mt_rand;
use function strlen;
use function substr;
class SQLAnywhere16PlatformTest extends AbstractPlatformTestCase
{
/** @var SQLAnywhere16Platform */
protected $platform;
public function createPlatform(): AbstractPlatform
{
return new SQLAnywhere16Platform();
}
/**
* {@inheritDoc}
*/
public function getGenerateAlterTableSql(): array
{
return [
"ALTER TABLE mytable ADD quota INT DEFAULT NULL, DROP foo, ALTER baz VARCHAR(1) DEFAULT 'def' NOT NULL, ALTER bloo BIT DEFAULT '0' NOT NULL",
'ALTER TABLE mytable RENAME userlist',
];
}
public function getGenerateForeignKeySql(): string
{
return 'ALTER TABLE test ADD FOREIGN KEY (fk_name_id) REFERENCES other_table (id)';
}
public function getGenerateIndexSql(): string
{
return 'CREATE INDEX my_idx ON mytable (user_name, last_login)';
}
public function getGenerateTableSql(): string
{
return 'CREATE TABLE test (id INT IDENTITY NOT NULL, test VARCHAR(255) DEFAULT NULL, PRIMARY KEY (id))';
}
/**
* {@inheritDoc}
*/
public function getGenerateTableWithMultiColumnUniqueIndexSql(): array
{
return [
'CREATE TABLE test (foo VARCHAR(255) DEFAULT NULL, bar VARCHAR(255) DEFAULT NULL)',
'CREATE UNIQUE INDEX UNIQ_D87F7E0C8C73652176FF8CAA ON test (foo, bar)',
];
}
public function getGenerateUniqueIndexSql(): string
{
return 'CREATE UNIQUE INDEX index_name ON test (test, test2)';
}
/**
* {@inheritDoc}
*/
protected function getQuotedColumnInForeignKeySQL(): array
{
return ['CREATE TABLE "quoted" ("create" VARCHAR(255) NOT NULL, foo VARCHAR(255) NOT NULL, "bar" VARCHAR(255) NOT NULL, CONSTRAINT FK_WITH_RESERVED_KEYWORD FOREIGN KEY ("create", foo, "bar") REFERENCES "foreign" ("create", bar, "foo-bar"), CONSTRAINT FK_WITH_NON_RESERVED_KEYWORD FOREIGN KEY ("create", foo, "bar") REFERENCES foo ("create", bar, "foo-bar"), CONSTRAINT FK_WITH_INTENDED_QUOTATION FOREIGN KEY ("create", foo, "bar") REFERENCES "foo-bar" ("create", bar, "foo-bar"))'];
}
/**
* {@inheritDoc}
*/
protected function getQuotedColumnInIndexSQL(): array
{
return [
'CREATE TABLE "quoted" ("create" VARCHAR(255) NOT NULL)',
'CREATE INDEX IDX_22660D028FD6E0FB ON "quoted" ("create")',
];
}
/**
* {@inheritDoc}
*/
protected function getQuotedNameInIndexSQL(): array
{
return [
'CREATE TABLE test (column1 VARCHAR(255) NOT NULL)',
'CREATE INDEX "key" ON test (column1)',
];
}
/**
* {@inheritDoc}
*/
protected function getQuotedColumnInPrimaryKeySQL(): array
{
return ['CREATE TABLE "quoted" ("create" VARCHAR(255) NOT NULL, PRIMARY KEY ("create"))'];
}
/**
* {@inheritDoc}
*/
public function getCreateTableColumnCommentsSQL(): array
{
return [
'CREATE TABLE test (id INT NOT NULL, PRIMARY KEY (id))',
"COMMENT ON COLUMN test.id IS 'This is a comment'",
];
}
/**
* {@inheritDoc}
*/
public function getAlterTableColumnCommentsSQL(): array
{
return [
'ALTER TABLE mytable ADD quota INT NOT NULL',
"COMMENT ON COLUMN mytable.quota IS 'A comment'",
'COMMENT ON COLUMN mytable.foo IS NULL',
"COMMENT ON COLUMN mytable.baz IS 'B comment'",
];
}
/**
* {@inheritDoc}
*/
public function getCreateTableColumnTypeCommentsSQL(): array
{
return [
'CREATE TABLE test (id INT NOT NULL, data TEXT NOT NULL, PRIMARY KEY (id))',
"COMMENT ON COLUMN test.data IS '(DC2Type:array)'",
];
}
public function testHasCorrectPlatformName(): void
{
self::assertEquals('sqlanywhere', $this->platform->getName());
}
public function testGeneratesCreateTableSQLWithCommonIndexes(): void
{
$table = new Table('test');
$table->addColumn('id', 'integer');
$table->addColumn('name', 'string', ['length' => 50]);
$table->setPrimaryKey(['id']);
$table->addIndex(['name']);
$table->addIndex(['id', 'name'], 'composite_idx');
self::assertEquals(
[
'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(): void
{
$table = new Table('test');
$table->addColumn('id', 'integer');
$table->addColumn('fk_1', 'integer');
$table->addColumn('fk_2', 'integer');
$table->setPrimaryKey(['id']);
$table->addForeignKeyConstraint('foreign_table', ['fk_1', 'fk_2'], ['pk_1', 'pk_2']);
$table->addForeignKeyConstraint(
'foreign_table2',
['fk_1', 'fk_2'],
['pk_1', 'pk_2'],
[],
'named_fk'
);
self::assertEquals(
['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(): void
{
$table = new Table('test');
$table->addColumn('id', 'integer');
$table->addColumn('check_max', 'integer', ['platformOptions' => ['max' => 10]]);
$table->addColumn('check_min', 'integer', ['platformOptions' => ['min' => 10]]);
$table->setPrimaryKey(['id']);
self::assertEquals(
['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(): void
{
$table = new Table('mytable');
$table->addColumn('foo', 'string', ['comment' => 'foo comment']);
$tableDiff = new TableDiff('mytable');
$tableDiff->fromTable = $table;
$tableDiff->changedColumns['foo'] = new ColumnDiff(
'foo',
new Column('foo', Type::getType('string')),
['comment']
);
self::assertEquals(
['COMMENT ON COLUMN mytable.foo IS NULL'],
$this->platform->getAlterTableSQL($tableDiff)
);
}
/**
* @param int|bool|null $lockMode
*
* @dataProvider getLockHints
*/
public function testAppendsLockHint($lockMode, string $lockHint): void
{
$fromClause = 'FROM users';
$expectedResult = $fromClause . $lockHint;
self::assertSame($expectedResult, $this->platform->appendLockHint($fromClause, $lockMode));
}
/**
* @return mixed[][]
*/
public static function getLockHints(): iterable
{
return [
[null, ''],
[false, ''],
[true, ''],
[LockMode::NONE, ' WITH (NOLOCK)'],
[LockMode::OPTIMISTIC, ''],
[LockMode::PESSIMISTIC_READ, ' WITH (UPDLOCK)'],
[LockMode::PESSIMISTIC_WRITE, ' WITH (XLOCK)'],
];
}
public function testHasCorrectMaxIdentifierLength(): void
{
self::assertEquals(128, $this->platform->getMaxIdentifierLength());
}
public function testFixesSchemaElementNames(): void
{
$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);
self::assertEquals(
$fixedSchemaElementName,
$this->platform->fixSchemaElementName($schemaElementName)
);
self::assertEquals(
$fixedSchemaElementName,
$this->platform->fixSchemaElementName($fixedSchemaElementName)
);
}
public function testGeneratesColumnTypesDeclarationSQL(): void
{
$fullColumnDef = [
'length' => 10,
'fixed' => true,
'unsigned' => true,
'autoincrement' => true,
];
self::assertEquals('SMALLINT', $this->platform->getSmallIntTypeDeclarationSQL([]));
self::assertEquals('UNSIGNED SMALLINT', $this->platform->getSmallIntTypeDeclarationSQL(['unsigned' => true]));
self::assertEquals('UNSIGNED SMALLINT IDENTITY', $this->platform->getSmallIntTypeDeclarationSQL($fullColumnDef));
self::assertEquals('INT', $this->platform->getIntegerTypeDeclarationSQL([]));
self::assertEquals('UNSIGNED INT', $this->platform->getIntegerTypeDeclarationSQL(['unsigned' => true]));
self::assertEquals('UNSIGNED INT IDENTITY', $this->platform->getIntegerTypeDeclarationSQL($fullColumnDef));
self::assertEquals('BIGINT', $this->platform->getBigIntTypeDeclarationSQL([]));
self::assertEquals('UNSIGNED BIGINT', $this->platform->getBigIntTypeDeclarationSQL(['unsigned' => true]));
self::assertEquals('UNSIGNED BIGINT IDENTITY', $this->platform->getBigIntTypeDeclarationSQL($fullColumnDef));
self::assertEquals('LONG BINARY', $this->platform->getBlobTypeDeclarationSQL($fullColumnDef));
self::assertEquals('BIT', $this->platform->getBooleanTypeDeclarationSQL($fullColumnDef));
self::assertEquals('TEXT', $this->platform->getClobTypeDeclarationSQL($fullColumnDef));
self::assertEquals('DATE', $this->platform->getDateTypeDeclarationSQL($fullColumnDef));
self::assertEquals('DATETIME', $this->platform->getDateTimeTypeDeclarationSQL($fullColumnDef));
self::assertEquals('TIME', $this->platform->getTimeTypeDeclarationSQL($fullColumnDef));
self::assertEquals('UNIQUEIDENTIFIER', $this->platform->getGuidTypeDeclarationSQL($fullColumnDef));
self::assertEquals(1, $this->platform->getVarcharDefaultLength());
self::assertEquals(32767, $this->platform->getVarcharMaxLength());
}
public function testHasNativeGuidType(): void
{
self::assertTrue($this->platform->hasNativeGuidType());
}
public function testGeneratesDDLSnippets(): void
{
self::assertEquals("CREATE DATABASE 'foobar'", $this->platform->getCreateDatabaseSQL('foobar'));
self::assertEquals("CREATE DATABASE 'foobar'", $this->platform->getCreateDatabaseSQL('"foobar"'));
self::assertEquals("CREATE DATABASE 'create'", $this->platform->getCreateDatabaseSQL('create'));
self::assertEquals("DROP DATABASE 'foobar'", $this->platform->getDropDatabaseSQL('foobar'));
self::assertEquals("DROP DATABASE 'foobar'", $this->platform->getDropDatabaseSQL('"foobar"'));
self::assertEquals("DROP DATABASE 'create'", $this->platform->getDropDatabaseSQL('create'));
self::assertEquals('CREATE GLOBAL TEMPORARY TABLE', $this->platform->getCreateTemporaryTableSnippetSQL());
self::assertEquals("START DATABASE 'foobar' AUTOSTOP OFF", $this->platform->getStartDatabaseSQL('foobar'));
self::assertEquals("START DATABASE 'foobar' AUTOSTOP OFF", $this->platform->getStartDatabaseSQL('"foobar"'));
self::assertEquals("START DATABASE 'create' AUTOSTOP OFF", $this->platform->getStartDatabaseSQL('create'));
self::assertEquals('STOP DATABASE "foobar" UNCONDITIONALLY', $this->platform->getStopDatabaseSQL('foobar'));
self::assertEquals('STOP DATABASE "foobar" UNCONDITIONALLY', $this->platform->getStopDatabaseSQL('"foobar"'));
self::assertEquals('STOP DATABASE "create" UNCONDITIONALLY', $this->platform->getStopDatabaseSQL('create'));
self::assertEquals('TRUNCATE TABLE foobar', $this->platform->getTruncateTableSQL('foobar'));
self::assertEquals('TRUNCATE TABLE foobar', $this->platform->getTruncateTableSQL('foobar'), true);
$viewSql = 'SELECT * FROM footable';
self::assertEquals('CREATE VIEW fooview AS ' . $viewSql, $this->platform->getCreateViewSQL('fooview', $viewSql));
self::assertEquals('DROP VIEW fooview', $this->platform->getDropViewSQL('fooview'));
}
public function testGeneratesPrimaryKeyDeclarationSQL(): void
{
self::assertEquals(
'CONSTRAINT pk PRIMARY KEY CLUSTERED (a, b)',
$this->platform->getPrimaryKeyDeclarationSQL(
new Index('', ['a', 'b'], true, true, ['clustered']),
'pk'
)
);
self::assertEquals(
'PRIMARY KEY (a, b)',
$this->platform->getPrimaryKeyDeclarationSQL(
new Index('', ['a', 'b'], true, true)
)
);
}
public function testCannotGeneratePrimaryKeyDeclarationSQLWithEmptyColumns(): void
{
$this->expectException(InvalidArgumentException::class);
$this->platform->getPrimaryKeyDeclarationSQL(new Index('pk', [], true, true));
}
public function testGeneratesCreateUnnamedPrimaryKeySQL(): void
{
self::assertEquals(
'ALTER TABLE foo ADD PRIMARY KEY CLUSTERED (a, b)',
$this->platform->getCreatePrimaryKeySQL(
new Index('pk', ['a', 'b'], true, true, ['clustered']),
'foo'
)
);
self::assertEquals(
'ALTER TABLE foo ADD PRIMARY KEY (a, b)',
$this->platform->getCreatePrimaryKeySQL(
new Index('any_pk_name', ['a', 'b'], true, true),
new Table('foo')
)
);
}
public function testGeneratesUniqueConstraintDeclarationSQL(): void
{
self::assertEquals(
'CONSTRAINT unique_constraint UNIQUE CLUSTERED (a, b)',
$this->platform->getUniqueConstraintDeclarationSQL(
'unique_constraint',
new Index('', ['a', 'b'], true, false, ['clustered'])
)
);
self::assertEquals(
'UNIQUE (a, b)',
$this->platform->getUniqueConstraintDeclarationSQL('', new Index('', ['a', 'b'], true, false))
);
}
public function testCannotGenerateUniqueConstraintDeclarationSQLWithEmptyColumns(): void
{
$this->expectException(InvalidArgumentException::class);
$this->platform->getUniqueConstraintDeclarationSQL('constr', new Index('constr', [], true));
}
public function testGeneratesForeignKeyConstraintsWithAdvancedPlatformOptionsSQL(): void
{
self::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(['a', 'b'], 'foreign_table', ['c', 'd'], 'fk', [
'notnull' => true,
'match' => SQLAnywhere16Platform::FOREIGN_KEY_MATCH_SIMPLE_UNIQUE,
'onUpdate' => 'CASCADE',
'onDelete' => 'SET NULL',
'check_on_commit' => true,
'clustered' => true,
'for_olap_workload' => true,
])
)
);
self::assertEquals(
'FOREIGN KEY (a, b) REFERENCES foreign_table (c, d)',
$this->platform->getForeignKeyDeclarationSQL(
new ForeignKeyConstraint(['a', 'b'], 'foreign_table', ['c', 'd'])
)
);
}
public function testGeneratesForeignKeyMatchClausesSQL(): void
{
self::assertEquals('SIMPLE', $this->platform->getForeignKeyMatchClauseSQL(1));
self::assertEquals('FULL', $this->platform->getForeignKeyMatchClauseSQL(2));
self::assertEquals('UNIQUE SIMPLE', $this->platform->getForeignKeyMatchClauseSQL(129));
self::assertEquals('UNIQUE FULL', $this->platform->getForeignKeyMatchClauseSQL(130));
}
public function testCannotGenerateInvalidForeignKeyMatchClauseSQL(): void
{
$this->expectException(InvalidArgumentException::class);
$this->platform->getForeignKeyMatchClauseSQL(3);
}
public function testCannotGenerateForeignKeyConstraintSQLWithEmptyLocalColumns(): void
{
$this->expectException(InvalidArgumentException::class);
$this->platform->getForeignKeyDeclarationSQL(new ForeignKeyConstraint([], 'foreign_tbl', ['c', 'd']));
}
public function testCannotGenerateForeignKeyConstraintSQLWithEmptyForeignColumns(): void
{
$this->expectException(InvalidArgumentException::class);
$this->platform->getForeignKeyDeclarationSQL(new ForeignKeyConstraint(['a', 'b'], 'foreign_tbl', []));
}
public function testCannotGenerateForeignKeyConstraintSQLWithEmptyForeignTableName(): void
{
$this->expectException(InvalidArgumentException::class);
$this->platform->getForeignKeyDeclarationSQL(new ForeignKeyConstraint(['a', 'b'], '', ['c', 'd']));
}
public function testCannotGenerateCommonIndexWithCreateConstraintSQL(): void
{
$this->expectException(InvalidArgumentException::class);
$this->platform->getCreateConstraintSQL(new Index('fooindex', []), new Table('footable'));
}
public function testCannotGenerateCustomConstraintWithCreateConstraintSQL(): void
{
$this->expectException(InvalidArgumentException::class);
$this->platform->getCreateConstraintSQL($this->createMock(Constraint::class), 'footable');
}
public function testGeneratesCreateIndexWithAdvancedPlatformOptionsSQL(): void
{
self::assertEquals(
'CREATE UNIQUE INDEX fooindex ON footable (a, b) WITH NULLS DISTINCT',
$this->platform->getCreateIndexSQL(
new Index(
'fooindex',
['a', 'b'],
true,
false,
['with_nulls_distinct']
),
'footable'
)
);
// WITH NULLS DISTINCT clause not available on primary indexes.
self::assertEquals(
'ALTER TABLE footable ADD PRIMARY KEY (a, b)',
$this->platform->getCreateIndexSQL(
new Index(
'fooindex',
['a', 'b'],
false,
true,
['with_nulls_distinct']
),
'footable'
)
);
// WITH NULLS DISTINCT clause not available on non-unique indexes.
self::assertEquals(
'CREATE INDEX fooindex ON footable (a, b)',
$this->platform->getCreateIndexSQL(
new Index(
'fooindex',
['a', 'b'],
false,
false,
['with_nulls_distinct']
),
'footable'
)
);
self::assertEquals(
'CREATE VIRTUAL UNIQUE CLUSTERED INDEX fooindex ON footable (a, b) WITH NULLS NOT DISTINCT FOR OLAP WORKLOAD',
$this->platform->getCreateIndexSQL(
new Index(
'fooindex',
['a', 'b'],
true,
false,
['virtual', 'clustered', 'with_nulls_not_distinct', 'for_olap_workload']
),
'footable'
)
);
self::assertEquals(
'CREATE VIRTUAL CLUSTERED INDEX fooindex ON footable (a, b) FOR OLAP WORKLOAD',
$this->platform->getCreateIndexSQL(
new Index(
'fooindex',
['a', 'b'],
false,
false,
['virtual', 'clustered', 'with_nulls_not_distinct', 'for_olap_workload']
),
'footable'
)
);
// WITH NULLS NOT DISTINCT clause not available on primary indexes.
self::assertEquals(
'ALTER TABLE footable ADD PRIMARY KEY (a, b)',
$this->platform->getCreateIndexSQL(
new Index(
'fooindex',
['a', 'b'],
false,
true,
['with_nulls_not_distinct']
),
'footable'
)
);
// WITH NULLS NOT DISTINCT clause not available on non-unique indexes.
self::assertEquals(
'CREATE INDEX fooindex ON footable (a, b)',
$this->platform->getCreateIndexSQL(
new Index(
'fooindex',
['a', 'b'],
false,
false,
['with_nulls_not_distinct']
),
'footable'
)
);
}
public function testThrowsExceptionOnInvalidWithNullsNotDistinctIndexOptions(): void
{
$this->expectException('UnexpectedValueException');
$this->platform->getCreateIndexSQL(
new Index(
'fooindex',
['a', 'b'],
false,
false,
['with_nulls_distinct', 'with_nulls_not_distinct']
),
'footable'
);
}
public function testDoesNotSupportIndexDeclarationInCreateAlterTableStatements(): void
{
$this->expectException(DBALException::class);
$this->platform->getIndexDeclarationSQL('index', new Index('index', []));
}
public function testGeneratesDropIndexSQL(): void
{
$index = new Index('fooindex', []);
self::assertEquals('DROP INDEX fooindex', $this->platform->getDropIndexSQL($index));
self::assertEquals('DROP INDEX footable.fooindex', $this->platform->getDropIndexSQL($index, 'footable'));
self::assertEquals('DROP INDEX footable.fooindex', $this->platform->getDropIndexSQL(
$index,
new Table('footable')
));
}
/**
* @psalm-suppress InvalidArgument
*/
public function testCannotGenerateDropIndexSQLWithInvalidIndexParameter(): void
{
$this->expectException(InvalidArgumentException::class);
$this->platform->getDropIndexSQL(['index'], 'table');
}
/**
* @psalm-suppress InvalidArgument
*/
public function testCannotGenerateDropIndexSQLWithInvalidTableParameter(): void
{
$this->expectException(InvalidArgumentException::class);
$this->platform->getDropIndexSQL('index', ['table']);
}
public function testGeneratesSQLSnippets(): void
{
self::assertEquals('STRING(column1, "string1", column2, "string2")', $this->platform->getConcatExpression(
'column1',
'"string1"',
'column2',
'"string2"'
));
self::assertEquals('CURRENT DATE', $this->platform->getCurrentDateSQL());
self::assertEquals('CURRENT TIME', $this->platform->getCurrentTimeSQL());
self::assertEquals('CURRENT TIMESTAMP', $this->platform->getCurrentTimestampSQL());
self::assertEquals("DATEADD(DAY, 4, '1987/05/02')", $this->platform->getDateAddDaysExpression("'1987/05/02'", 4));
self::assertEquals("DATEADD(HOUR, 12, '1987/05/02')", $this->platform->getDateAddHourExpression("'1987/05/02'", 12));
self::assertEquals("DATEADD(MINUTE, 2, '1987/05/02')", $this->platform->getDateAddMinutesExpression("'1987/05/02'", 2));
self::assertEquals("DATEADD(MONTH, 102, '1987/05/02')", $this->platform->getDateAddMonthExpression("'1987/05/02'", 102));
self::assertEquals("DATEADD(QUARTER, 5, '1987/05/02')", $this->platform->getDateAddQuartersExpression("'1987/05/02'", 5));
self::assertEquals("DATEADD(SECOND, 1, '1987/05/02')", $this->platform->getDateAddSecondsExpression("'1987/05/02'", 1));
self::assertEquals("DATEADD(WEEK, 3, '1987/05/02')", $this->platform->getDateAddWeeksExpression("'1987/05/02'", 3));
self::assertEquals("DATEADD(YEAR, 10, '1987/05/02')", $this->platform->getDateAddYearsExpression("'1987/05/02'", 10));
self::assertEquals("DATEDIFF(day, '1987/04/01', '1987/05/02')", $this->platform->getDateDiffExpression("'1987/05/02'", "'1987/04/01'"));
self::assertEquals("DATEADD(DAY, -1 * 4, '1987/05/02')", $this->platform->getDateSubDaysExpression("'1987/05/02'", 4));
self::assertEquals("DATEADD(HOUR, -1 * 12, '1987/05/02')", $this->platform->getDateSubHourExpression("'1987/05/02'", 12));
self::assertEquals("DATEADD(MINUTE, -1 * 2, '1987/05/02')", $this->platform->getDateSubMinutesExpression("'1987/05/02'", 2));
self::assertEquals("DATEADD(MONTH, -1 * 102, '1987/05/02')", $this->platform->getDateSubMonthExpression("'1987/05/02'", 102));
self::assertEquals("DATEADD(QUARTER, -1 * 5, '1987/05/02')", $this->platform->getDateSubQuartersExpression("'1987/05/02'", 5));
self::assertEquals("DATEADD(SECOND, -1 * 1, '1987/05/02')", $this->platform->getDateSubSecondsExpression("'1987/05/02'", 1));
self::assertEquals("DATEADD(WEEK, -1 * 3, '1987/05/02')", $this->platform->getDateSubWeeksExpression("'1987/05/02'", 3));
self::assertEquals("DATEADD(YEAR, -1 * 10, '1987/05/02')", $this->platform->getDateSubYearsExpression("'1987/05/02'", 10));
self::assertEquals('Y-m-d H:i:s.u', $this->platform->getDateTimeFormatString());
self::assertEquals('H:i:s.u', $this->platform->getTimeFormatString());
self::assertEquals('', $this->platform->getForUpdateSQL());
self::assertEquals('NEWID()', $this->platform->getGuidExpression());
self::assertEquals('LOCATE(string_column, substring_column)', $this->platform->getLocateExpression('string_column', 'substring_column'));
self::assertEquals('LOCATE(string_column, substring_column, 1)', $this->platform->getLocateExpression('string_column', 'substring_column', 1));
self::assertEquals("HASH(column, 'MD5')", $this->platform->getMd5Expression('column'));
self::assertEquals('SUBSTRING(column, 5)', $this->platform->getSubstringExpression('column', 5));
self::assertEquals('SUBSTRING(column, 5, 2)', $this->platform->getSubstringExpression('column', 5, 2));
self::assertEquals('GLOBAL TEMPORARY', $this->platform->getTemporaryTableSQL());
self::assertEquals(
'LTRIM(column)',
$this->platform->getTrimExpression('column', TrimMode::LEADING)
);
self::assertEquals(
'RTRIM(column)',
$this->platform->getTrimExpression('column', TrimMode::TRAILING)
);
self::assertEquals(
'TRIM(column)',
$this->platform->getTrimExpression('column')
);
self::assertEquals(
'TRIM(column)',
$this->platform->getTrimExpression('column', TrimMode::UNSPECIFIED)
);
self::assertEquals(
"SUBSTR(column, PATINDEX('%[^' + c + ']%', column))",
$this->platform->getTrimExpression('column', TrimMode::LEADING, 'c')
);
self::assertEquals(
"REVERSE(SUBSTR(REVERSE(column), PATINDEX('%[^' + c + ']%', REVERSE(column))))",
$this->platform->getTrimExpression('column', TrimMode::TRAILING, 'c')
);
self::assertEquals(
"REVERSE(SUBSTR(REVERSE(SUBSTR(column, PATINDEX('%[^' + c + ']%', column))), PATINDEX('%[^' + c + ']%', " .
"REVERSE(SUBSTR(column, PATINDEX('%[^' + c + ']%', column))))))",
$this->platform->getTrimExpression('column', TrimMode::UNSPECIFIED, 'c')
);
self::assertEquals(
"REVERSE(SUBSTR(REVERSE(SUBSTR(column, PATINDEX('%[^' + c + ']%', column))), PATINDEX('%[^' + c + ']%', " .
"REVERSE(SUBSTR(column, PATINDEX('%[^' + c + ']%', column))))))",
$this->platform->getTrimExpression('column', TrimMode::UNSPECIFIED, 'c')
);
}
public function testHasCorrectDateTimeTzFormatString(): void
{
self::assertEquals('Y-m-d H:i:s.uP', $this->platform->getDateTimeTzFormatString());
}
public function testGeneratesDateTimeTzColumnTypeDeclarationSQL(): void
{
self::assertEquals(
'TIMESTAMP WITH TIME ZONE',
$this->platform->getDateTimeTzTypeDeclarationSQL([
'length' => 10,
'fixed' => true,
'unsigned' => true,
'autoincrement' => true,
])
);
}
public function testInitializesDateTimeTzTypeMapping(): void
{
self::assertTrue($this->platform->hasDoctrineTypeMappingFor('timestamp with time zone'));
self::assertEquals('datetime', $this->platform->getDoctrineTypeMapping('timestamp with time zone'));
}
public function testHasCorrectDefaultTransactionIsolationLevel(): void
{
self::assertEquals(
TransactionIsolationLevel::READ_UNCOMMITTED,
$this->platform->getDefaultTransactionIsolationLevel()
);
}
public function testGeneratesTransactionsCommands(): void
{
self::assertEquals(
'SET TEMPORARY OPTION isolation_level = 0',
$this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::READ_UNCOMMITTED)
);
self::assertEquals(
'SET TEMPORARY OPTION isolation_level = 1',
$this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::READ_COMMITTED)
);
self::assertEquals(
'SET TEMPORARY OPTION isolation_level = 2',
$this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::REPEATABLE_READ)
);
self::assertEquals(
'SET TEMPORARY OPTION isolation_level = 3',
$this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::SERIALIZABLE)
);
}
public function testCannotGenerateTransactionCommandWithInvalidIsolationLevel(): void
{
$this->expectException(InvalidArgumentException::class);
$this->platform->getSetTransactionIsolationSQL('invalid_transaction_isolation_level');
}
public function testModifiesLimitQuery(): void
{
self::assertEquals(
'SELECT TOP 10 * FROM user',
$this->platform->modifyLimitQuery('SELECT * FROM user', 10, 0)
);
}
public function testModifiesLimitQueryWithEmptyOffset(): void
{
self::assertEquals(
'SELECT TOP 10 * FROM user',
$this->platform->modifyLimitQuery('SELECT * FROM user', 10)
);
}
public function testModifiesLimitQueryWithOffset(): void
{
self::assertEquals(
'SELECT TOP 10 START AT 6 * FROM user',
$this->platform->modifyLimitQuery('SELECT * FROM user', 10, 5)
);
self::assertEquals(
'SELECT TOP 0 START AT 6 * FROM user',
$this->platform->modifyLimitQuery('SELECT * FROM user', 0, 5)
);
}
public function testModifiesLimitQueryWithSubSelect(): void
{
self::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 testModifiesLimitQueryWithoutLimit(): void
{
self::assertEquals(
'SELECT TOP ALL START AT 11 n FROM Foo',
$this->platform->modifyLimitQuery('SELECT n FROM Foo', null, 10)
);
}
public function testPrefersIdentityColumns(): void
{
self::assertTrue($this->platform->prefersIdentityColumns());
}
public function testDoesNotPreferSequences(): void
{
self::assertFalse($this->platform->prefersSequences());
}
public function testSupportsIdentityColumns(): void
{
self::assertTrue($this->platform->supportsIdentityColumns());
}
public function testSupportsPrimaryConstraints(): void
{
self::assertTrue($this->platform->supportsPrimaryConstraints());
}
public function testSupportsForeignKeyConstraints(): void
{
self::assertTrue($this->platform->supportsForeignKeyConstraints());
}
public function testSupportsForeignKeyOnUpdate(): void
{
self::assertTrue($this->platform->supportsForeignKeyOnUpdate());
}
public function testSupportsAlterTable(): void
{
self::assertTrue($this->platform->supportsAlterTable());
}
public function testSupportsTransactions(): void
{
self::assertTrue($this->platform->supportsTransactions());
}
public function testSupportsSchemas(): void
{
self::assertFalse($this->platform->supportsSchemas());
}
public function testSupportsIndexes(): void
{
self::assertTrue($this->platform->supportsIndexes());
}
public function testSupportsCommentOnStatement(): void
{
self::assertTrue($this->platform->supportsCommentOnStatement());
}
public function testSupportsSavePoints(): void
{
self::assertTrue($this->platform->supportsSavepoints());
}
public function testSupportsReleasePoints(): void
{
self::assertTrue($this->platform->supportsReleaseSavepoints());
}
public function testSupportsCreateDropDatabase(): void
{
self::assertTrue($this->platform->supportsCreateDropDatabase());
}
public function testSupportsGettingAffectedRows(): void
{
self::assertTrue($this->platform->supportsGettingAffectedRows());
}
public function testDoesNotSupportSequences(): void
{
self::markTestSkipped('This version of the platform now supports sequences.');
}
public function testSupportsSequences(): void
{
self::assertTrue($this->platform->supportsSequences());
}
public function testGeneratesSequenceSqlCommands(): void
{
$sequence = new Sequence('myseq', 20, 1);
self::assertEquals(
'CREATE SEQUENCE myseq INCREMENT BY 20 START WITH 1 MINVALUE 1',
$this->platform->getCreateSequenceSQL($sequence)
);
self::assertEquals(
'ALTER SEQUENCE myseq INCREMENT BY 20',
$this->platform->getAlterSequenceSQL($sequence)
);
self::assertEquals(
'DROP SEQUENCE myseq',
$this->platform->getDropSequenceSQL('myseq')
);
self::assertEquals(
'DROP SEQUENCE myseq',
$this->platform->getDropSequenceSQL($sequence)
);
self::assertEquals(
'SELECT myseq.NEXTVAL',
$this->platform->getSequenceNextValSQL('myseq')
);
self::assertEquals(
'SELECT sequence_name, increment_by, start_with, min_value FROM SYS.SYSSEQUENCE',
$this->platform->getListSequencesSQL('')
);
}
public function testDoesNotSupportInlineColumnComments(): void
{
self::assertFalse($this->platform->supportsInlineColumnComments());
}
public function testCannotEmulateSchemas(): void
{
self::assertFalse($this->platform->canEmulateSchemas());
}
public function testInitializesDoctrineTypeMappings(): void
{
self::assertTrue($this->platform->hasDoctrineTypeMappingFor('integer'));
self::assertSame('integer', $this->platform->getDoctrineTypeMapping('integer'));
self::assertTrue($this->platform->hasDoctrineTypeMappingFor('binary'));
self::assertSame('binary', $this->platform->getDoctrineTypeMapping('binary'));
self::assertTrue($this->platform->hasDoctrineTypeMappingFor('varbinary'));
self::assertSame('binary', $this->platform->getDoctrineTypeMapping('varbinary'));
}
protected function getBinaryDefaultLength(): int
{
return 1;
}
protected function getBinaryMaxLength(): int
{
return 32767;
}
public function testReturnsBinaryTypeDeclarationSQL(): void
{
self::assertSame('VARBINARY(1)', $this->platform->getBinaryTypeDeclarationSQL([]));
self::assertSame('VARBINARY(1)', $this->platform->getBinaryTypeDeclarationSQL(['length' => 0]));
self::assertSame('VARBINARY(32767)', $this->platform->getBinaryTypeDeclarationSQL(['length' => 32767]));
self::assertSame('BINARY(1)', $this->platform->getBinaryTypeDeclarationSQL(['fixed' => true]));
self::assertSame('BINARY(1)', $this->platform->getBinaryTypeDeclarationSQL(['fixed' => true, 'length' => 0]));
self::assertSame('BINARY(32767)', $this->platform->getBinaryTypeDeclarationSQL(['fixed' => true, 'length' => 32767]));
}
/**
* @group legacy
* @expectedDeprecation Binary field length 32768 is greater than supported by the platform (32767). Reduce the field length or use a BLOB field instead.
*/
public function testReturnsBinaryTypeLongerThanMaxDeclarationSQL(): void
{
self::assertSame('LONG BINARY', $this->platform->getBinaryTypeDeclarationSQL(['length' => 32768]));
self::assertSame('LONG BINARY', $this->platform->getBinaryTypeDeclarationSQL(['fixed' => true, 'length' => 32768]));
}
/**
* {@inheritDoc}
*
* @group DBAL-234
*/
protected function getAlterTableRenameIndexSQL(): array
{
return ['ALTER INDEX idx_foo ON mytable RENAME TO idx_bar'];
}
/**
* {@inheritDoc}
*
* @group DBAL-234
*/
protected function getQuotedAlterTableRenameIndexSQL(): array
{
return [
'ALTER INDEX "create" ON "table" RENAME TO "select"',
'ALTER INDEX "foo" ON "table" RENAME TO "bar"',
];
}
/**
* {@inheritdoc}
*/
protected function getQuotedAlterTableRenameColumnSQL(): array
{
return [
'ALTER TABLE mytable RENAME unquoted1 TO unquoted',
'ALTER TABLE mytable RENAME unquoted2 TO "where"',
'ALTER TABLE mytable RENAME unquoted3 TO "foo"',
'ALTER TABLE mytable RENAME "create" TO reserved_keyword',
'ALTER TABLE mytable RENAME "table" TO "from"',
'ALTER TABLE mytable RENAME "select" TO "bar"',
'ALTER TABLE mytable RENAME quoted1 TO quoted',
'ALTER TABLE mytable RENAME quoted2 TO "and"',
'ALTER TABLE mytable RENAME quoted3 TO "baz"',
];
}
/**
* {@inheritdoc}
*/
protected function getQuotedAlterTableChangeColumnLengthSQL(): array
{
self::markTestIncomplete('Not implemented yet');
}
/**
* {@inheritDoc}
*
* @group DBAL-807
*/
protected function getAlterTableRenameIndexInSchemaSQL(): array
{
return ['ALTER INDEX idx_foo ON myschema.mytable RENAME TO idx_bar'];
}
/**
* {@inheritDoc}
*
* @group DBAL-807
*/
protected function getQuotedAlterTableRenameIndexInSchemaSQL(): array
{
return [
'ALTER INDEX "create" ON "schema"."table" RENAME TO "select"',
'ALTER INDEX "foo" ON "schema"."table" RENAME TO "bar"',
];
}
/**
* @group DBAL-423
*/
public function testReturnsGuidTypeDeclarationSQL(): void
{
self::assertSame('UNIQUEIDENTIFIER', $this->platform->getGuidTypeDeclarationSQL([]));
}
/**
* {@inheritdoc}
*/
public function getAlterTableRenameColumnSQL(): array
{
return ['ALTER TABLE foo RENAME bar TO baz'];
}
/**
* {@inheritdoc}
*/
protected function getQuotesTableIdentifiersInAlterTableSQL(): array
{
return [
'ALTER TABLE "foo" DROP FOREIGN KEY fk1',
'ALTER TABLE "foo" DROP FOREIGN KEY fk2',
'ALTER TABLE "foo" RENAME id TO war',
'ALTER TABLE "foo" ADD bloo INT NOT NULL, DROP baz, ALTER bar INT DEFAULT NULL',
'ALTER TABLE "foo" RENAME "table"',
'ALTER TABLE "table" ADD CONSTRAINT fk_add FOREIGN KEY (fk3) REFERENCES fk_table (id)',
'ALTER TABLE "table" ADD CONSTRAINT fk2 FOREIGN KEY (fk2) REFERENCES fk_table2 (id)',
];
}
/**
* {@inheritdoc}
*/
protected function getCommentOnColumnSQL(): array
{
return [
'COMMENT ON COLUMN foo.bar IS \'comment\'',
'COMMENT ON COLUMN "Foo"."BAR" IS \'comment\'',
'COMMENT ON COLUMN "select"."from" IS \'comment\'',
];
}
/**
* @group DBAL-1004
*/
public function testAltersTableColumnCommentWithExplicitlyQuotedIdentifiers(): void
{
$table1 = new Table('"foo"', [new Column('"bar"', Type::getType('integer'))]);
$table2 = new Table('"foo"', [new Column('"bar"', Type::getType('integer'), ['comment' => 'baz'])]);
$comparator = new Comparator();
$tableDiff = $comparator->diffTable($table1, $table2);
self::assertInstanceOf(TableDiff::class, $tableDiff);
self::assertSame(
['COMMENT ON COLUMN "foo"."bar" IS \'baz\''],
$this->platform->getAlterTableSQL($tableDiff)
);
}
/**
* {@inheritdoc}
*/
public static function getReturnsForeignKeyReferentialActionSQL(): iterable
{
return [
['CASCADE', 'CASCADE'],
['SET NULL', 'SET NULL'],
['NO ACTION', 'RESTRICT'],
['RESTRICT', 'RESTRICT'],
['SET DEFAULT', 'SET DEFAULT'],
['CaScAdE', 'CASCADE'],
];
}
protected function getQuotesReservedKeywordInUniqueConstraintDeclarationSQL(): string
{
return 'CONSTRAINT "select" UNIQUE (foo)';
}
protected function getQuotesReservedKeywordInIndexDeclarationSQL(): string
{
return ''; // not supported by this platform
}
protected function getQuotesReservedKeywordInTruncateTableSQL(): string
{
return 'TRUNCATE TABLE "select"';
}
protected function supportsInlineIndexDeclaration(): bool
{
return false;
}
/**
* {@inheritdoc}
*/
protected function getAlterStringToFixedStringSQL(): array
{
return ['ALTER TABLE mytable ALTER name CHAR(2) NOT NULL'];
}
/**
* {@inheritdoc}
*/
protected function getGeneratesAlterTableRenameIndexUsedByForeignKeySQL(): array
{
return ['ALTER INDEX idx_foo ON mytable RENAME TO idx_foo_renamed'];
}
/**
* @group DBAL-2436
*/
public function testQuotesSchemaNameInListTableColumnsSQL(): void
{
self::assertStringContainsStringIgnoringCase(
"'Foo''Bar\\'",
$this->platform->getListTableColumnsSQL("Foo'Bar\\.baz_table")
);
}
/**
* @group DBAL-2436
*/
public function testQuotesTableNameInListTableConstraintsSQL(): void
{
self::assertStringContainsStringIgnoringCase("'Foo''Bar\\'", $this->platform->getListTableConstraintsSQL("Foo'Bar\\"), '', true);
}
/**
* @group DBAL-2436
*/
public function testQuotesSchemaNameInListTableConstraintsSQL(): void
{
self::assertStringContainsStringIgnoringCase(
"'Foo''Bar\\'",
$this->platform->getListTableConstraintsSQL("Foo'Bar\\.baz_table")
);
}
/**
* @group DBAL-2436
*/
public function testQuotesTableNameInListTableForeignKeysSQL(): void
{
self::assertStringContainsStringIgnoringCase(
"'Foo''Bar\\'",
$this->platform->getListTableForeignKeysSQL("Foo'Bar\\")
);
}
/**
* @group DBAL-2436
*/
public function testQuotesSchemaNameInListTableForeignKeysSQL(): void
{
self::assertStringContainsStringIgnoringCase(
"'Foo''Bar\\'",
$this->platform->getListTableForeignKeysSQL("Foo'Bar\\.baz_table")
);
}
/**
* @group DBAL-2436
*/
public function testQuotesTableNameInListTableIndexesSQL(): void
{
self::assertStringContainsStringIgnoringCase(
"'Foo''Bar\\'",
$this->platform->getListTableIndexesSQL("Foo'Bar\\")
);
}
/**
* @group DBAL-2436
*/
public function testQuotesSchemaNameInListTableIndexesSQL(): void
{
self::assertStringContainsStringIgnoringCase(
"'Foo''Bar\\'",
$this->platform->getListTableIndexesSQL("Foo'Bar\\.baz_table")
);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment