sintonia/library/propel/runtime/lib/adapter/DBMSSQL.php

216 lines
6.6 KiB
PHP

<?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;
}
}