MSSQLQueryBuilder::buildLimitFragment()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $orderby of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
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