123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394 |
- <?php
- /**
- * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
- * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
- *
- * Licensed under The MIT License
- * For full copyright and license information, please see the LICENSE.txt
- * Redistributions of files must retain the above copyright notice.
- *
- * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
- * @link https://cakephp.org CakePHP(tm) Project
- * @since 3.0.0
- * @license https://opensource.org/licenses/mit-license.php MIT License
- */
- namespace Cake\Database\Dialect;
- use Cake\Database\ExpressionInterface;
- use Cake\Database\Expression\FunctionExpression;
- use Cake\Database\Expression\OrderByExpression;
- use Cake\Database\Expression\UnaryExpression;
- use Cake\Database\Query;
- use Cake\Database\Schema\SqlserverSchema;
- use Cake\Database\SqlDialectTrait;
- use Cake\Database\SqlserverCompiler;
- use Cake\Database\ValueBinder;
- use PDO;
- /**
- * Contains functions that encapsulates the SQL dialect used by SQLServer,
- * including query translators and schema introspection.
- *
- * @internal
- */
- trait SqlserverDialectTrait
- {
- use SqlDialectTrait;
- use TupleComparisonTranslatorTrait;
- /**
- * String used to start a database identifier quoting to make it safe
- *
- * @var string
- */
- protected $_startQuote = '[';
- /**
- * String used to end a database identifier quoting to make it safe
- *
- * @var string
- */
- protected $_endQuote = ']';
- /**
- * Modify the limit/offset to TSQL
- *
- * @param \Cake\Database\Query $query The query to translate
- * @return \Cake\Database\Query The modified query
- */
- protected function _selectQueryTranslator($query)
- {
- $limit = $query->clause('limit');
- $offset = $query->clause('offset');
- if ($limit && $offset === null) {
- $query->modifier(['_auto_top_' => sprintf('TOP %d', $limit)]);
- }
- if ($offset !== null && !$query->clause('order')) {
- $query->order($query->newExpr()->add('(SELECT NULL)'));
- }
- if ($this->_version() < 11 && $offset !== null) {
- return $this->_pagingSubquery($query, $limit, $offset);
- }
- return $this->_transformDistinct($query);
- }
- /**
- * Get the version of SQLserver we are connected to.
- *
- * @return int
- */
- // @codingStandardsIgnoreLine
- public function _version()
- {
- $this->connect();
- return $this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
- }
- /**
- * Generate a paging subquery for older versions of SQLserver.
- *
- * Prior to SQLServer 2012 there was no equivalent to LIMIT OFFSET, so a subquery must
- * be used.
- *
- * @param \Cake\Database\Query $original The query to wrap in a subquery.
- * @param int $limit The number of rows to fetch.
- * @param int $offset The number of rows to offset.
- * @return \Cake\Database\Query Modified query object.
- */
- protected function _pagingSubquery($original, $limit, $offset)
- {
- $field = '_cake_paging_._cake_page_rownum_';
- if ($original->clause('order')) {
- // SQL server does not support column aliases in OVER clauses. But
- // the only practical way to specify the use of calculated columns
- // is with their alias. So substitute the select SQL in place of
- // any column aliases for those entries in the order clause.
- $select = $original->clause('select');
- $order = new OrderByExpression();
- $original
- ->clause('order')
- ->iterateParts(function ($direction, $orderBy) use ($select, $order) {
- $key = $orderBy;
- if (isset($select[$orderBy]) &&
- $select[$orderBy] instanceof ExpressionInterface
- ) {
- $key = $select[$orderBy]->sql(new ValueBinder());
- }
- $order->add([$key => $direction]);
- // Leave original order clause unchanged.
- return $orderBy;
- });
- } else {
- $order = new OrderByExpression('(SELECT NULL)');
- }
- $query = clone $original;
- $query->select([
- '_cake_page_rownum_' => new UnaryExpression('ROW_NUMBER() OVER', $order)
- ])->limit(null)
- ->offset(null)
- ->order([], true);
- $outer = new Query($query->getConnection());
- $outer->select('*')
- ->from(['_cake_paging_' => $query]);
- if ($offset) {
- $outer->where(["$field > " . (int)$offset]);
- }
- if ($limit) {
- $value = (int)$offset + (int)$limit;
- $outer->where(["$field <= $value"]);
- }
- // Decorate the original query as that is what the
- // end developer will be calling execute() on originally.
- $original->decorateResults(function ($row) {
- if (isset($row['_cake_page_rownum_'])) {
- unset($row['_cake_page_rownum_']);
- }
- return $row;
- });
- return $outer;
- }
- /**
- * Returns the passed query after rewriting the DISTINCT clause, so that drivers
- * that do not support the "ON" part can provide the actual way it should be done
- *
- * @param \Cake\Database\Query $original The query to be transformed
- * @return \Cake\Database\Query
- */
- protected function _transformDistinct($original)
- {
- if (!is_array($original->clause('distinct'))) {
- return $original;
- }
- $query = clone $original;
- $distinct = $query->clause('distinct');
- $query->distinct(false);
- $order = new OrderByExpression($distinct);
- $query
- ->select(function ($q) use ($distinct, $order) {
- $over = $q->newExpr('ROW_NUMBER() OVER')
- ->add('(PARTITION BY')
- ->add($q->newExpr()->add($distinct)->setConjunction(','))
- ->add($order)
- ->add(')')
- ->setConjunction(' ');
- return [
- '_cake_distinct_pivot_' => $over
- ];
- })
- ->limit(null)
- ->offset(null)
- ->order([], true);
- $outer = new Query($query->getConnection());
- $outer->select('*')
- ->from(['_cake_distinct_' => $query])
- ->where(['_cake_distinct_pivot_' => 1]);
- // Decorate the original query as that is what the
- // end developer will be calling execute() on originally.
- $original->decorateResults(function ($row) {
- if (isset($row['_cake_distinct_pivot_'])) {
- unset($row['_cake_distinct_pivot_']);
- }
- return $row;
- });
- return $outer;
- }
- /**
- * Returns a dictionary of expressions to be transformed when compiling a Query
- * to SQL. Array keys are method names to be called in this class
- *
- * @return array
- */
- protected function _expressionTranslators()
- {
- $namespace = 'Cake\Database\Expression';
- return [
- $namespace . '\FunctionExpression' => '_transformFunctionExpression',
- $namespace . '\TupleComparison' => '_transformTupleComparison'
- ];
- }
- /**
- * Receives a FunctionExpression and changes it so that it conforms to this
- * SQL dialect.
- *
- * @param \Cake\Database\Expression\FunctionExpression $expression The function expression to convert to TSQL.
- * @return void
- */
- protected function _transformFunctionExpression(FunctionExpression $expression)
- {
- switch ($expression->getName()) {
- case 'CONCAT':
- // CONCAT function is expressed as exp1 + exp2
- $expression->setName('')->setConjunction(' +');
- break;
- case 'DATEDIFF':
- $hasDay = false;
- $visitor = function ($value) use (&$hasDay) {
- if ($value === 'day') {
- $hasDay = true;
- }
- return $value;
- };
- $expression->iterateParts($visitor);
- if (!$hasDay) {
- $expression->add(['day' => 'literal'], [], true);
- }
- break;
- case 'CURRENT_DATE':
- $time = new FunctionExpression('GETUTCDATE');
- $expression->setName('CONVERT')->add(['date' => 'literal', $time]);
- break;
- case 'CURRENT_TIME':
- $time = new FunctionExpression('GETUTCDATE');
- $expression->setName('CONVERT')->add(['time' => 'literal', $time]);
- break;
- case 'NOW':
- $expression->setName('GETUTCDATE');
- break;
- case 'EXTRACT':
- $expression->setName('DATEPART')->setConjunction(' ,');
- break;
- case 'DATE_ADD':
- $params = [];
- $visitor = function ($p, $key) use (&$params) {
- if ($key === 0) {
- $params[2] = $p;
- } else {
- $valueUnit = explode(' ', $p);
- $params[0] = rtrim($valueUnit[1], 's');
- $params[1] = $valueUnit[0];
- }
- return $p;
- };
- $manipulator = function ($p, $key) use (&$params) {
- return $params[$key];
- };
- $expression
- ->setName('DATEADD')
- ->setConjunction(',')
- ->iterateParts($visitor)
- ->iterateParts($manipulator)
- ->add([$params[2] => 'literal']);
- break;
- case 'DAYOFWEEK':
- $expression
- ->setName('DATEPART')
- ->setConjunction(' ')
- ->add(['weekday, ' => 'literal'], [], true);
- break;
- case 'SUBSTR':
- $expression->setName('SUBSTRING');
- if (count($expression) < 4) {
- $params = [];
- $expression
- ->iterateParts(function ($p) use (&$params) {
- return $params[] = $p;
- })
- ->add([new FunctionExpression('LEN', [$params[0]]), ['string']]);
- }
- break;
- }
- }
- /**
- * Get the schema dialect.
- *
- * Used by Cake\Schema package to reflect schema and
- * generate schema.
- *
- * @return \Cake\Database\Schema\SqlserverSchema
- */
- public function schemaDialect()
- {
- return new SqlserverSchema($this);
- }
- /**
- * Returns a SQL snippet for creating a new transaction savepoint
- *
- * @param string $name save point name
- * @return string
- */
- public function savePointSQL($name)
- {
- return 'SAVE TRANSACTION t' . $name;
- }
- /**
- * Returns a SQL snippet for releasing a previously created save point
- *
- * @param string $name save point name
- * @return string
- */
- public function releaseSavePointSQL($name)
- {
- return 'COMMIT TRANSACTION t' . $name;
- }
- /**
- * Returns a SQL snippet for rollbacking a previously created save point
- *
- * @param string $name save point name
- * @return string
- */
- public function rollbackSavePointSQL($name)
- {
- return 'ROLLBACK TRANSACTION t' . $name;
- }
- /**
- * {@inheritDoc}
- *
- * @return \Cake\Database\SqlserverCompiler
- */
- public function newCompiler()
- {
- return new SqlserverCompiler();
- }
- /**
- * {@inheritDoc}
- */
- public function disableForeignKeySQL()
- {
- return 'EXEC sp_msforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT all"';
- }
- /**
- * {@inheritDoc}
- */
- public function enableForeignKeySQL()
- {
- return 'EXEC sp_msforeachtable "ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all"';
- }
- }
|