123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- <?php
- /**
- * This file is part of the Propel package.
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- *
- * @license MIT License
- */
- /**
- * This is used to connect to a MSSQL database.
- *
- * @author Hans Lellelid <hans@xmpl.org> (Propel)
- * @version $Revision: 1700 $
- * @package propel.runtime.adapter
- */
- class DBMSSQL extends DBAdapter
- {
- /**
- * This method is used to ignore case.
- *
- * @param in The string to transform to upper case.
- * @return The upper case string.
- */
- public function toUpperCase($in)
- {
- return $this->ignoreCase($in);
- }
- /**
- * This method is used to ignore case.
- *
- * @param in The string whose case to ignore.
- * @return The string in a case that can be ignored.
- */
- public function ignoreCase($in)
- {
- return 'UPPER(' . $in . ')';
- }
- /**
- * Returns SQL which concatenates the second string to the first.
- *
- * @param string String to concatenate.
- * @param string String to append.
- * @return string
- */
- public function concatString($s1, $s2)
- {
- return '(' . $s1 . ' + ' . $s2 . ')';
- }
- /**
- * Returns SQL which extracts a substring.
- *
- * @param string String to extract from.
- * @param int Offset to start from.
- * @param int Number of characters to extract.
- * @return string
- */
- public function subString($s, $pos, $len)
- {
- return 'SUBSTRING(' . $s . ', ' . $pos . ', ' . $len . ')';
- }
- /**
- * Returns SQL which calculates the length (in chars) of a string.
- *
- * @param string String to calculate length of.
- * @return string
- */
- public function strLength($s)
- {
- return 'LEN(' . $s . ')';
- }
- /**
- * @see DBAdapter::quoteIdentifier()
- */
- public function quoteIdentifier($text)
- {
- return '[' . $text . ']';
- }
- /**
- * @see DBAdapter::random()
- */
- public function random($seed = null)
- {
- return 'RAND(' . ((int)$seed) . ')';
- }
- /**
- * Simulated Limit/Offset
- * This rewrites the $sql query to apply the offset and limit.
- * some of the ORDER BY logic borrowed from Doctrine MsSqlPlatform
- * @see DBAdapter::applyLimit()
- * @author Benjamin Runnels <kraven@kraven.org>
- */
- public function applyLimit(&$sql, $offset, $limit)
- {
- // make sure offset and limit are numeric
- if(! is_numeric($offset) || ! is_numeric($limit))
- {
- throw new PropelException('DBMSSQL::applyLimit() expects a number for argument 2 and 3');
- }
- //split the select and from clauses out of the original query
- $selectSegment = array();
- $selectText = 'SELECT ';
- if (preg_match('/\Aselect(\s+)distinct/i', $sql)) {
- $selectText .= 'DISTINCT ';
- }
- preg_match('/\Aselect(.*)from(.*)/si', $sql, $selectSegment);
- if(count($selectSegment) == 3) {
- $selectStatement = trim($selectSegment[1]);
- $fromStatement = trim($selectSegment[2]);
- } else {
- throw new Exception('DBMSSQL::applyLimit() could not locate the select statement at the start of the query.');
- }
- // if we're starting at offset 0 then theres no need to simulate limit,
- // just grab the top $limit number of rows
- if($offset == 0) {
- $sql = $selectText . 'TOP ' . $limit . ' ' . $selectStatement . ' FROM ' . $fromStatement;
- return;
- }
- //get the ORDER BY clause if present
- $orderStatement = stristr($fromStatement, 'ORDER BY');
- $orders = '';
- if($orderStatement !== false) {
- //remove order statement from the from statement
- $fromStatement = trim(str_replace($orderStatement, '', $fromStatement));
- $order = str_ireplace('ORDER BY', '', $orderStatement);
- $orders = explode(',', $order);
- for($i = 0; $i < count($orders); $i ++) {
- $orderArr[trim(preg_replace('/\s+(ASC|DESC)$/i', '', $orders[$i]))] = array(
- 'sort' => (stripos($orders[$i], ' DESC') !== false) ? 'DESC' : 'ASC',
- 'key' => $i
- );
- }
- }
- //setup inner and outer select selects
- $innerSelect = '';
- $outerSelect = '';
- foreach(explode(', ', $selectStatement) as $selCol) {
- $selColArr = explode(' ', $selCol);
- $selColCount = count($selColArr) - 1;
- //make sure the current column isn't * or an aggregate
- if($selColArr[0] != '*' && ! strstr($selColArr[0], '(')) {
- if(isset($orderArr[$selColArr[0]])) {
- $orders[$orderArr[$selColArr[0]]['key']] = $selColArr[0] . ' ' . $orderArr[$selColArr[0]]['sort'];
- }
- //use the alias if one was present otherwise use the column name
- $alias = (! stristr($selCol, ' AS ')) ? $this->quoteIdentifier($selColArr[0]) : $this->quoteIdentifier($selColArr[$selColCount]);
- //save the first non-aggregate column for use in ROW_NUMBER() if required
- if(! isset($firstColumnOrderStatement)) {
- $firstColumnOrderStatement = 'ORDER BY ' . $selColArr[0];
- }
- //add an alias to the inner select so all columns will be unique
- $innerSelect .= $selColArr[0] . ' AS ' . $alias . ', ';
- $outerSelect .= $alias . ', ';
- } else {
- //agregate columns must always have an alias clause
- if(! stristr($selCol, ' AS ')) {
- throw new Exception('DBMSSQL::applyLimit() requires aggregate columns to have an Alias clause');
- }
- //aggregate column alias can't be used as the count column you must use the entire aggregate statement
- if(isset($orderArr[$selColArr[$selColCount]])) {
- $orders[$orderArr[$selColArr[$selColCount]]['key']] = str_replace($selColArr[$selColCount - 1] . ' ' . $selColArr[$selColCount], '', $selCol) . $orderArr[$selColArr[$selColCount]]['sort'];
- }
- //quote the alias
- $alias = $this->quoteIdentifier($selColArr[$selColCount]);
- $innerSelect .= str_replace($selColArr[$selColCount], $alias, $selCol) . ', ';
- $outerSelect .= $alias . ', ';
- }
- }
- if(is_array($orders)) {
- $orderStatement = 'ORDER BY ' . implode(', ', $orders);
- } else {
- //use the first non aggregate column in our select statement if no ORDER BY clause present
- if(isset($firstColumnOrderStatement)) {
- $orderStatement = $firstColumnOrderStatement;
- } else {
- throw new Exception('DBMSSQL::applyLimit() unable to find column to use with ROW_NUMBER()');
- }
- }
- //substring the select strings to get rid of the last comma and add our FROM and SELECT clauses
- $innerSelect = $selectText . 'ROW_NUMBER() OVER(' . $orderStatement . ') AS RowNumber, ' . substr($innerSelect, 0, - 2) . ' FROM';
- //outer select can't use * because of the RowNumber column
- $outerSelect = 'SELECT ' . substr($outerSelect, 0, - 2) . ' FROM';
- //ROW_NUMBER() starts at 1 not 0
- $sql = $outerSelect . ' (' . $innerSelect . ' ' . $fromStatement . ') AS derivedb WHERE RowNumber BETWEEN ' . ($offset + 1) . ' AND ' . ($limit + $offset);
- return;
- }
- }
|