| 1 |
lars |
1 |
<?php
|
|
|
2 |
/**
|
|
|
3 |
* TMsssqlCommandBuilder class file.
|
|
|
4 |
*
|
|
|
5 |
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
|
|
|
6 |
* @link http://www.pradosoft.com/
|
|
|
7 |
* @copyright Copyright © 2005-2008 PradoSoft
|
|
|
8 |
* @license http://www.pradosoft.com/license/
|
|
|
9 |
* @version $Id: TDbCommandBuilder.php 1863 2007-04-12 12:43:49Z wei $
|
|
|
10 |
* @package System.Data.Common
|
|
|
11 |
*/
|
|
|
12 |
|
|
|
13 |
Prado::using('System.Data.Common.TDbCommandBuilder');
|
|
|
14 |
|
|
|
15 |
/**
|
|
|
16 |
* TMssqlCommandBuilder provides specifics methods to create limit/offset query commands
|
|
|
17 |
* for MSSQL servers.
|
|
|
18 |
*
|
|
|
19 |
* @author Wei Zhuo <weizho[at]gmail[dot]com>
|
|
|
20 |
* @version $Id: TDbCommandBuilder.php 1863 2007-04-12 12:43:49Z wei $
|
|
|
21 |
* @package System.Data.Common
|
|
|
22 |
* @since 3.1
|
|
|
23 |
*/
|
|
|
24 |
class TMssqlCommandBuilder extends TDbCommandBuilder
|
|
|
25 |
{
|
|
|
26 |
/**
|
|
|
27 |
* Overrides parent implementation. Uses "SELECT @@Identity".
|
|
|
28 |
* @return integer last insert id, null if none is found.
|
|
|
29 |
*/
|
|
|
30 |
public function getLastInsertID()
|
|
|
31 |
{
|
|
|
32 |
foreach($this->getTableInfo()->getColumns() as $column)
|
|
|
33 |
{
|
|
|
34 |
if($column->hasSequence())
|
|
|
35 |
{
|
|
|
36 |
$command = $this->getDbConnection()->createCommand('SELECT @@Identity');
|
|
|
37 |
return intval($command->queryScalar());
|
|
|
38 |
}
|
|
|
39 |
}
|
|
|
40 |
}
|
|
|
41 |
|
|
|
42 |
/**
|
|
|
43 |
* Overrides parent implementation. Alters the sql to apply $limit and $offset.
|
|
|
44 |
* The idea for limit with offset is done by modifying the sql on the fly
|
|
|
45 |
* with numerous assumptions on the structure of the sql string.
|
|
|
46 |
* The modification is done with reference to the notes from
|
|
|
47 |
* http://troels.arvin.dk/db/rdbms/#select-limit-offset
|
|
|
48 |
*
|
|
|
49 |
* <code>
|
|
|
50 |
* SELECT * FROM (
|
|
|
51 |
* SELECT TOP n * FROM (
|
|
|
52 |
* SELECT TOP z columns -- (z=n+skip)
|
|
|
53 |
* FROM tablename
|
|
|
54 |
* ORDER BY key ASC
|
|
|
55 |
* ) AS FOO ORDER BY key DESC -- ('FOO' may be anything)
|
|
|
56 |
* ) AS BAR ORDER BY key ASC -- ('BAR' may be anything)
|
|
|
57 |
* </code>
|
|
|
58 |
*
|
|
|
59 |
* <b>Regular expressions are used to alter the SQL query. The resulting SQL query
|
|
|
60 |
* may be malformed for complex queries.</b> The following restrictions apply
|
|
|
61 |
*
|
|
|
62 |
* <ul>
|
|
|
63 |
* <li>
|
|
|
64 |
* In particular, <b>commas</b> should <b>NOT</b>
|
|
|
65 |
* be used as part of the ordering expression or identifier. Commas must only be
|
|
|
66 |
* used for separating the ordering clauses.
|
|
|
67 |
* </li>
|
|
|
68 |
* <li>
|
|
|
69 |
* In the ORDER BY clause, the column name should NOT be be qualified
|
|
|
70 |
* with a table name or view name. Alias the column names or use column index.
|
|
|
71 |
* </li>
|
|
|
72 |
* <li>
|
|
|
73 |
* No clauses should follow the ORDER BY clause, e.g. no COMPUTE or FOR clauses.
|
|
|
74 |
* </li>
|
|
|
75 |
*
|
|
|
76 |
* @param string SQL query string.
|
|
|
77 |
* @param integer maximum number of rows, -1 to ignore limit.
|
|
|
78 |
* @param integer row offset, -1 to ignore offset.
|
|
|
79 |
* @return string SQL with limit and offset.
|
|
|
80 |
*/
|
|
|
81 |
public function applyLimitOffset($sql, $limit=-1, $offset=-1)
|
|
|
82 |
{
|
|
|
83 |
$limit = $limit!==null ? intval($limit) : -1;
|
|
|
84 |
$offset = $offset!==null ? intval($offset) : -1;
|
|
|
85 |
if ($limit > 0 && $offset <= 0) //just limit
|
|
|
86 |
$sql = preg_replace('/^([\s(])*SELECT( DISTINCT)?(?!\s*TOP\s*\()/i',"\\1SELECT\\2 TOP $limit", $sql);
|
|
|
87 |
else if($limit > 0 && $offset > 0)
|
|
|
88 |
$sql = $this->rewriteLimitOffsetSql($sql, $limit,$offset);
|
|
|
89 |
return $sql;
|
|
|
90 |
}
|
|
|
91 |
|
|
|
92 |
/**
|
|
|
93 |
* Rewrite sql to apply $limit > and $offset > 0 for MSSQL database.
|
|
|
94 |
* See http://troels.arvin.dk/db/rdbms/#select-limit-offset
|
|
|
95 |
* @param string sql query
|
|
|
96 |
* @param integer $limit > 0
|
|
|
97 |
* @param integer $offset > 0
|
|
|
98 |
* @return sql modified sql query applied with limit and offset.
|
|
|
99 |
*/
|
|
|
100 |
protected function rewriteLimitOffsetSql($sql, $limit, $offset)
|
|
|
101 |
{
|
|
|
102 |
$fetch = $limit+$offset;
|
|
|
103 |
$sql = preg_replace('/^([\s(])*SELECT( DISTINCT)?(?!\s*TOP\s*\()/i',"\\1SELECT\\2 TOP $fetch", $sql);
|
|
|
104 |
$ordering = $this->findOrdering($sql);
|
|
|
105 |
|
|
|
106 |
$orginalOrdering = $this->joinOrdering($ordering);
|
|
|
107 |
$reverseOrdering = $this->joinOrdering($this->reverseDirection($ordering));
|
|
|
108 |
$sql = "SELECT * FROM (SELECT TOP {$limit} * FROM ($sql) as [__inner top table__] {$reverseOrdering}) as [__outer top table__] {$orginalOrdering}";
|
|
|
109 |
return $sql;
|
|
|
110 |
}
|
|
|
111 |
|
|
|
112 |
/**
|
|
|
113 |
* Base on simplified syntax http://msdn2.microsoft.com/en-us/library/aa259187(SQL.80).aspx
|
|
|
114 |
*
|
|
|
115 |
* @param string $sql
|
|
|
116 |
* @return array ordering expression as key and ordering direction as value
|
|
|
117 |
*/
|
|
|
118 |
protected function findOrdering($sql)
|
|
|
119 |
{
|
|
|
120 |
if(!preg_match('/ORDER BY/i', $sql))
|
|
|
121 |
return array();
|
|
|
122 |
$matches=array();
|
|
|
123 |
$ordering=array();
|
|
|
124 |
preg_match_all('/(ORDER BY)[\s"\[](.*)(ASC|DESC)?(?:[\s"\[]|$|COMPUTE|FOR)/i', $sql, $matches);
|
|
|
125 |
if(count($matches)>1 && count($matches[2]) > 0)
|
|
|
126 |
{
|
|
|
127 |
$parts = explode(',', $matches[2][0]);
|
|
|
128 |
foreach($parts as $part)
|
|
|
129 |
{
|
|
|
130 |
$subs=array();
|
|
|
131 |
if(preg_match_all('/(.*)[\s"\]](ASC|DESC)$/i', trim($part), $subs))
|
|
|
132 |
{
|
|
|
133 |
if(count($subs) > 1 && count($subs[2]) > 0)
|
|
|
134 |
{
|
|
|
135 |
$ordering[$subs[1][0]] = $subs[2][0];
|
|
|
136 |
}
|
|
|
137 |
//else what?
|
|
|
138 |
}
|
|
|
139 |
else
|
|
|
140 |
$ordering[trim($part)] = 'ASC';
|
|
|
141 |
}
|
|
|
142 |
}
|
|
|
143 |
return $ordering;
|
|
|
144 |
}
|
|
|
145 |
|
|
|
146 |
/**
|
|
|
147 |
* @param array ordering obtained from findOrdering()
|
|
|
148 |
* @return string concat the orderings
|
|
|
149 |
*/
|
|
|
150 |
protected function joinOrdering($orders)
|
|
|
151 |
{
|
|
|
152 |
if(count($orders)>0)
|
|
|
153 |
{
|
|
|
154 |
$str=array();
|
|
|
155 |
foreach($orders as $column => $direction)
|
|
|
156 |
$str[] = $column.' '.$direction;
|
|
|
157 |
return 'ORDER BY '.implode(', ', $str);
|
|
|
158 |
}
|
|
|
159 |
}
|
|
|
160 |
|
|
|
161 |
/**
|
|
|
162 |
* @param array original ordering
|
|
|
163 |
* @return array ordering with reversed direction.
|
|
|
164 |
*/
|
|
|
165 |
protected function reverseDirection($orders)
|
|
|
166 |
{
|
|
|
167 |
foreach($orders as $column => $direction)
|
|
|
168 |
$orders[$column] = strtolower(trim($direction))==='desc' ? 'ASC' : 'DESC';
|
|
|
169 |
return $orders;
|
|
|
170 |
}
|
|
|
171 |
}
|
|
|
172 |
|