mirror of
https://github.com/silverstripe/silverstripe-mssql
synced 2024-10-22 08:05:53 +02:00
121 lines
4.2 KiB
PHP
121 lines
4.2 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Builds a SQL query string from a SQLExpression object
|
|
*
|
|
* @package mssql
|
|
*/
|
|
class MSSQLQueryBuilder extends DBQueryBuilder
|
|
{
|
|
|
|
protected function buildSelectQuery(SQLSelect $query, array &$parameters)
|
|
{
|
|
list($limit, $offset) = $this->parseLimit($query);
|
|
|
|
// If not using ofset then query generation is quite straightforward
|
|
if (empty($offset)) {
|
|
$sql = parent::buildSelectQuery($query, $parameters);
|
|
// Inject limit into SELECT fragment
|
|
if (!empty($limit)) {
|
|
$sql = preg_replace('/^(SELECT (DISTINCT)?)/i', '${1} TOP '.$limit, $sql);
|
|
}
|
|
return $sql;
|
|
}
|
|
|
|
// When using offset we must use a subselect
|
|
// @see http://stackoverflow.com/questions/2135418/equivalent-of-limit-and-offset-for-sql-server
|
|
$orderby = $query->getOrderBy();
|
|
|
|
// workaround for subselect not working with alias functions
|
|
// just use the function directly in the order by instead of the alias
|
|
$selects = $query->getSelect();
|
|
foreach ($orderby as $field => $dir) {
|
|
if (preg_match('/_SortColumn/', $field)) {
|
|
unset($orderby[$field]);
|
|
$orderby[$selects[str_replace('"', '', $field)]] = $dir;
|
|
}
|
|
}
|
|
|
|
// Create order expression, using the first column if none explicitly specified
|
|
if ($orderby) {
|
|
// Simple implementation of buildOrderByFragment
|
|
$statements = array();
|
|
foreach ($orderby as $clause => $dir) {
|
|
$statements[] = trim("$clause $dir");
|
|
}
|
|
$orderByClause = "ORDER BY " . implode(', ', $statements);
|
|
} else {
|
|
$selects = $query->getSelect();
|
|
$firstCol = reset($selects);
|
|
$orderByClause = "ORDER BY $firstCol";
|
|
}
|
|
|
|
// Build main query SQL
|
|
$sql = parent::buildSelectQuery($query, $parameters);
|
|
|
|
// Inject row number into selection
|
|
$sql = preg_replace('/^(SELECT (DISTINCT)?)/i', '${1} ROW_NUMBER() OVER ('.$orderByClause.') AS Number, ', $sql);
|
|
|
|
// Sub-query this SQL
|
|
if (empty($limit)) {
|
|
$limitCondition = "Number > ?";
|
|
$parameters[] = $offset;
|
|
} else {
|
|
$limitCondition = "Number BETWEEN ? AND ?";
|
|
$parameters[] = $offset + 1;
|
|
$parameters[] = $offset + $limit;
|
|
}
|
|
return "SELECT * FROM ($sql) AS Numbered WHERE $limitCondition ORDER BY Number";
|
|
}
|
|
|
|
public function buildLimitFragment(SQLSelect $query, array &$parameters)
|
|
{
|
|
// Limit is handled at the buildSelectQuery level
|
|
return '';
|
|
}
|
|
|
|
public function buildOrderByFragment(SQLSelect $query, array &$parameters)
|
|
{
|
|
// If doing a limit/offset at the same time then don't build the orde by fragment here
|
|
list($offset, $limit) = $this->parseLimit($query);
|
|
if (empty($offset) || empty($limit)) {
|
|
return parent::buildOrderByFragment($query, $parameters);
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Extracts the limit and offset from the limit clause
|
|
*
|
|
* @param SQLSelect $query
|
|
* @return array Two item array with $limit and $offset as values
|
|
* @throws InvalidArgumentException
|
|
*/
|
|
protected function parseLimit(SQLSelect $query)
|
|
{
|
|
$limit = '';
|
|
$offset = '0';
|
|
if (is_array($query->getLimit())) {
|
|
$limitArr = $query->getLimit();
|
|
if (isset($limitArr['limit'])) {
|
|
$limit = $limitArr['limit'];
|
|
}
|
|
if (isset($limitArr['start'])) {
|
|
$offset = $limitArr['start'];
|
|
}
|
|
} elseif (preg_match('/^([0-9]+) offset ([0-9]+)$/i', trim($query->getLimit()), $matches)) {
|
|
$limit = $matches[1];
|
|
$offset = $matches[2];
|
|
} else {
|
|
//could be a comma delimited string
|
|
$bits = explode(',', $query->getLimit());
|
|
if (sizeof($bits) > 1) {
|
|
list($offset, $limit) = $bits;
|
|
} else {
|
|
$limit = $bits[0];
|
|
}
|
|
}
|
|
return array($limit, $offset);
|
|
}
|
|
}
|