1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace SilverStripe\MSSQL; |
4
|
|
|
|
5
|
|
|
use InvalidArgumentException; |
6
|
|
|
use SilverStripe\ORM\Queries\SQLSelect; |
7
|
|
|
use SilverStripe\ORM\Connect\DBQueryBuilder; |
8
|
|
|
use SilverStripe\ORM\Queries\SQLConditionalExpression; |
9
|
|
|
use SilverStripe\ORM\Queries\SQLUpdate; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* Builds a SQL query string from a SQLExpression object |
13
|
|
|
*/ |
14
|
|
|
class MSSQLQueryBuilder extends DBQueryBuilder |
15
|
|
|
{ |
16
|
|
|
|
17
|
|
|
protected function buildSelectQuery(SQLSelect $query, array &$parameters) |
18
|
|
|
{ |
19
|
|
|
list($limit, $offset) = $this->parseLimit($query); |
20
|
|
|
|
21
|
|
|
// If not using ofset then query generation is quite straightforward |
22
|
|
|
if (empty($offset)) { |
23
|
|
|
$sql = parent::buildSelectQuery($query, $parameters); |
24
|
|
|
// Inject limit into SELECT fragment |
25
|
|
|
if (!empty($limit)) { |
26
|
|
|
$sql = preg_replace('/^(SELECT (DISTINCT)?)/i', '${1} TOP '.$limit, $sql); |
27
|
|
|
} |
28
|
|
|
return $sql; |
29
|
|
|
} |
30
|
|
|
|
31
|
|
|
// When using offset we must use a subselect |
32
|
|
|
// @see http://stackoverflow.com/questions/2135418/equivalent-of-limit-and-offset-for-sql-server |
33
|
|
|
$orderby = $query->getOrderBy(); |
34
|
|
|
|
35
|
|
|
// workaround for subselect not working with alias functions |
36
|
|
|
// just use the function directly in the order by instead of the alias |
37
|
|
|
$selects = $query->getSelect(); |
38
|
|
|
foreach ($orderby as $field => $dir) { |
39
|
|
|
if (preg_match('/_SortColumn/', $field)) { |
40
|
|
|
unset($orderby[$field]); |
41
|
|
|
$orderby[$selects[str_replace('"', '', $field)]] = $dir; |
42
|
|
|
} |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
// Create order expression, using the first column if none explicitly specified |
46
|
|
|
if ($orderby) { |
|
|
|
|
47
|
|
|
// Simple implementation of buildOrderByFragment |
48
|
|
|
$statements = array(); |
49
|
|
|
foreach ($orderby as $clause => $dir) { |
50
|
|
|
$statements[] = trim("$clause $dir"); |
51
|
|
|
} |
52
|
|
|
$orderByClause = "ORDER BY " . implode(', ', $statements); |
53
|
|
|
} else { |
54
|
|
|
$selects = $query->getSelect(); |
55
|
|
|
$firstCol = reset($selects); |
56
|
|
|
$orderByClause = "ORDER BY $firstCol"; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
// Build main query SQL |
60
|
|
|
$sql = parent::buildSelectQuery($query, $parameters); |
61
|
|
|
|
62
|
|
|
// Inject row number into selection |
63
|
|
|
$sql = preg_replace('/^(SELECT (DISTINCT)?)/i', '${1} ROW_NUMBER() OVER ('.$orderByClause.') AS Number, ', $sql); |
64
|
|
|
|
65
|
|
|
// Sub-query this SQL |
66
|
|
|
if (empty($limit)) { |
67
|
|
|
$limitCondition = "Number > ?"; |
68
|
|
|
$parameters[] = $offset; |
69
|
|
|
} else { |
70
|
|
|
$limitCondition = "Number BETWEEN ? AND ?"; |
71
|
|
|
$parameters[] = $offset + 1; |
72
|
|
|
$parameters[] = $offset + $limit; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
return "SELECT * FROM ($sql) AS Numbered WHERE $limitCondition ORDER BY Number"; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
public function buildLimitFragment(SQLSelect $query, array &$parameters) |
79
|
|
|
{ |
80
|
|
|
// Limit is handled at the buildSelectQuery level |
81
|
|
|
return ''; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
public function buildOrderByFragment(SQLSelect $query, array &$parameters) |
85
|
|
|
{ |
86
|
|
|
// If doing a limit/offset at the same time then don't build the orde by fragment here |
87
|
|
|
list($offset, $limit) = $this->parseLimit($query); |
88
|
|
|
if (empty($offset) || empty($limit)) { |
89
|
|
|
return parent::buildOrderByFragment($query, $parameters); |
90
|
|
|
} |
91
|
|
|
return ''; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
public function buildWhereFragment(SQLConditionalExpression $query, array &$parameters) |
95
|
|
|
{ |
96
|
|
|
// Get parameterised elements |
97
|
|
|
$where = parent::buildWhereFragment($query, $parameters); |
98
|
|
|
|
99
|
|
|
if (!$where) { |
100
|
|
|
return ''; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
// remove explict order in where |
104
|
|
|
$where = preg_replace("/ORDER BY ([^\)])+ [ASC|DESC]+/", '', $where); |
105
|
|
|
|
106
|
|
|
return $where; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
/** |
110
|
|
|
* Extracts the limit and offset from the limit clause |
111
|
|
|
* |
112
|
|
|
* @param SQLSelect $query |
113
|
|
|
* @return array Two item array with $limit and $offset as values |
114
|
|
|
* @throws InvalidArgumentException |
115
|
|
|
*/ |
116
|
|
|
protected function parseLimit(SQLSelect $query) |
117
|
|
|
{ |
118
|
|
|
$limit = ''; |
119
|
|
|
$offset = '0'; |
120
|
|
|
|
121
|
|
|
if (is_array($query->getLimit())) { |
122
|
|
|
$limitArr = $query->getLimit(); |
123
|
|
|
if (isset($limitArr['limit'])) { |
124
|
|
|
$limit = $limitArr['limit']; |
125
|
|
|
} |
126
|
|
|
if (isset($limitArr['start'])) { |
127
|
|
|
$offset = $limitArr['start']; |
128
|
|
|
} |
129
|
|
|
} elseif (preg_match('/^([0-9]+) offset ([0-9]+)$/i', trim($query->getLimit()), $matches)) { |
130
|
|
|
$limit = $matches[1]; |
131
|
|
|
$offset = $matches[2]; |
132
|
|
|
} else { |
133
|
|
|
//could be a comma delimited string |
134
|
|
|
$bits = explode(',', $query->getLimit()); |
135
|
|
|
if (sizeof($bits) > 1) { |
136
|
|
|
list($offset, $limit) = $bits; |
137
|
|
|
} else { |
138
|
|
|
$limit = $bits[0]; |
139
|
|
|
} |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
return array($limit, $offset); |
143
|
|
|
} |
144
|
|
|
} |
145
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.