diff --git a/framework/db/mssql/QueryBuilder.php b/framework/db/mssql/QueryBuilder.php index 448235e..9a571d9 100644 --- a/framework/db/mssql/QueryBuilder.php +++ b/framework/db/mssql/QueryBuilder.php @@ -139,4 +139,91 @@ class QueryBuilder extends \yii\db\QueryBuilder return "ALTER TABLE {$table} {$enable} CONSTRAINT ALL"; } + + public function buildOrderBy($columns) + { + if (empty($columns)) { + return 'ORDER BY (SELECT NULL)'; // hack so limit will work if no order by is specified + } else { + return parent::buildOrderBy($columns); + } + } + + public function build($query, $params = []) + { + $query->prepareBuild($this); + + $params = empty($params) ? $query->params : array_merge($params, $query->params); + + $clauses = [ + $this->buildSelect($query->select, $params, $query->distinct, $query->selectOption), + $this->buildFrom($query->from, $params), + $this->buildJoin($query->join, $params), + $this->buildWhere($query->where, $params), + $this->buildGroupBy($query->groupBy), + $this->buildHaving($query->having, $params), + $this->buildOrderBy($query->orderBy), + $this->olderMssql() ? '' : $this->buildLimit($query->limit, $query->offset), + ]; + + $sql = implode($this->separator, array_filter($clauses)); + if ($this->olderMssql()) + $sql = $this->applyLimit($sql, $query); + $union = $this->buildUnion($query->union, $params); + if ($union !== '') { + $sql = "($sql){$this->separator}$union"; + } + + + return [$sql, $params]; + } + + public function applyLimit($sql, $query) + { + $limit = $query->limit !== null ? (int)$query->limit : -1; + $offset = $query->offset !== null ? (int)$query->offset : -1; + if ($limit > 0 || $offset >= 0) + $sql = $this->rewriteLimitOffsetSql($sql, $limit, $offset, $query); + return $sql; + } + + protected function rewriteLimitOffsetSql($sql, $limit, $offset, $query) + { + $originalOrdering = $this->buildOrderBy($query->orderBy); + if ($query->select) { + $select = implode(', ', $query->select); + } + else { + $select = $query->select = '*'; + } + if ($select === '*') { + $columns = $this->getAllColumnNames($query->modelClass); + if ($columns && is_array($columns)) + $select = implode(', ', $columns); + else + $select = $columns; + } + $sql = str_replace($originalOrdering, '', $sql); + $sql = preg_replace('/^([\s(])*SELECT( DISTINCT)?(?!\s*TOP\s*\()/i', "\\1SELECT\\2 rowNum = ROW_NUMBER() over ({$originalOrdering}),", $sql); + $sql = "SELECT TOP {$limit} {$select} FROM ($sql) sub WHERE rowNum > {$offset}"; + return $sql; + } + + protected function getAllColumnNames($modelClass = null) + { + if (!$modelClass) { + return null; + } + $model = new $modelClass; + $schema = $model->getTableSchema(); + $columns = array_keys($schema->columns); + return $columns; + } + + protected function olderMssql() + { + $this->db->open(); + $version = preg_split("/\./", $this->db->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION)); + return $version[0] < 11; + } }