CMssqlCommandBuilder
CMssqlCommandBuilder provides basic methods to create query commands for tables for Mssql Servers.
Method Details
public string applyJoin(string $sql, string $join) |
$sql | string | the SQL statement to be altered |
$join | string | the JOIN clause (starting with join type, such as INNER JOIN) |
{return} | string | the altered SQL statement |
Alters the SQL to apply JOIN clause. Overrides parent implementation to comply with the DELETE command syntax required when multiple tables are referenced.
public string applyLimit(string $sql, integer $limit, integer $offset) |
$sql | string | SQL query string. |
$limit | integer | maximum number of rows, -1 to ignore limit. |
$offset | integer | row offset, -1 to ignore offset. |
{return} | string | SQL with limit and offset. |
Source Code: framework/db/schema/mssql/CMssqlCommandBuilder.php#194 (
show)
public function applyLimit($sql, $limit, $offset)
{
$limit = $limit!==null ? (int)$limit : -1;
$offset = $offset!==null ? (int)$offset : -1;
if ($limit > 0 && $offset <= 0) //just limit
$sql = preg_replace('/^([\s(])*SELECT( DISTINCT)?(?!\s*TOP\s*\()/i',"\\1SELECT\\2 TOP $limit", $sql);
elseif($limit > 0 && $offset > 0)
$sql = $this->rewriteLimitOffsetSql($sql, $limit,$offset);
return $sql;
}
This is a port from Prado Framework.
Overrides parent implementation. Alters the sql to apply $limit and $offset. The idea for limit with offset is done by modifying the sql on the fly with numerous assumptions on the structure of the sql string. The modification is done with reference to the notes from http://troels.arvin.dk/db/rdbms/#select-limit-offset
SELECT * FROM (
SELECT TOP n * FROM (
SELECT TOP z columns -- (z=n+skip)
FROM tablename
ORDER BY key ASC
) AS FOO ORDER BY key DESC -- ('FOO' may be anything)
) AS BAR ORDER BY key ASC -- ('BAR' may be anything)
Regular expressions are used to alter the SQL query. The resulting SQL query may be malformed for complex queries. The following restrictions apply
- In particular, commas should NOT be used as part of the ordering expression or identifier. Commas must only be used for separating the ordering clauses.
- In the ORDER BY clause, the column name should NOT be be qualified with a table name or view name. Alias the column names or use column index.
- No clauses should follow the ORDER BY clause, e.g. no COMPUTE or FOR clauses.
Source Code: framework/db/schema/mssql/CMssqlCommandBuilder.php#324 (
show)
protected function checkCriteria($table, $criteria)
{
if ($criteria->offset > 0 && $criteria->order==='')
{
$criteria->order=is_array($table->primaryKey)?implode(',',$table->primaryKey):$table->primaryKey;
}
return $criteria;
}
Checks if the criteria has an order by clause when using offset/limit. Override parent implementation to check if an orderby clause if specified when querying with an offset If not, order it by pk.
protected string createCompositeInCondition(CDbTableSchema $table, array $values, string $prefix) |
$table | CDbTableSchema | the table schema |
$values | array | list of primary key values to be selected within |
$prefix | string | column prefix (ended with dot) |
{return} | string | the expression for selection |
Source Code: framework/db/schema/mssql/CMssqlCommandBuilder.php#340 (
show)
protected function createCompositeInCondition($table,$values,$prefix)
{
$vs=array();
foreach($values as $value)
{
$c=array();
foreach($value as $k=>$v)
$c[]=$prefix.$table->columns[$k]->rawName.'='.$v;
$vs[]='('.implode(' AND ',$c).')';
}
return '('.implode(' OR ',$vs).')';
}
Generates the expression for selecting rows with specified composite key values.
createCountCommand()
Creates a COUNT(*) command for a single table. Override parent implementation to remove the order clause of criteria if it exists
createDeleteCommand()
Creates a DELETE command. Override parent implementation to check if an orderby clause if specified when querying with an offset
createFindCommand()
Creates a SELECT command for a single table. Override parent implementation to check if an orderby clause if specified when querying with an offset
createUpdateCommand()
Source Code: framework/db/schema/mssql/CMssqlCommandBuilder.php#61 (
show)
public function createUpdateCommand($table,$data,$criteria)
{
$criteria=$this->checkCriteria($table,$criteria);
$fields=array();
$values=array();
$bindByPosition=isset($criteria->params[0]);
$i=0;
foreach($data as $name=>$value)
{
if(($column=$table->getColumn($name))!==null)
{
if ($table->sequenceName !== null && $column->isPrimaryKey === true) continue;
if ($column->dbType === 'timestamp') continue;
if($value instanceof CDbExpression)
{
$fields[]=$column->rawName.'='.$value->expression;
foreach($value->params as $n=>$v)
$values[$n]=$v;
}
elseif($bindByPosition)
{
$fields[]=$column->rawName.'=?';
$values[]=$column->typecast($value);
}
else
{
$fields[]=$column->rawName.'='.self::PARAM_PREFIX.$i;
$values[self::PARAM_PREFIX.$i]=$column->typecast($value);
$i++;
}
}
}
if($fields===array())
throw new CDbException(Yii::t('yii','No columns are being updated for table "{table}".',
array('{table}'=>$table->name)));
$sql="UPDATE {$table->rawName} SET ".implode(', ',$fields);
$sql=$this->applyJoin($sql,$criteria->join);
$sql=$this->applyCondition($sql,$criteria->condition);
$sql=$this->applyOrder($sql,$criteria->order);
$sql=$this->applyLimit($sql,$criteria->limit,$criteria->offset);
$command=$this->getDbConnection()->createCommand($sql);
$this->bindValues($command,array_merge($values,$criteria->params));
return $command;
}
Creates an UPDATE command. Override parent implementation because mssql don't want to update an identity column
createUpdateCounterCommand()
Creates an UPDATE command that increments/decrements certain columns. Override parent implementation to check if an orderby clause if specified when querying with an offset
protected array findOrdering(string $sql) |
$sql | string | $sql |
{return} | array | ordering expression as key and ordering direction as value |
Source Code: framework/db/schema/mssql/CMssqlCommandBuilder.php#234 (
show)
protected function findOrdering($sql)
{
if(!preg_match('/ORDER BY/i', $sql))
return array();
$matches=array();
$ordering=array();
preg_match_all('/(ORDER BY)[\s"\[](.*)(ASC|DESC)?(?:[\s"\[]|$|COMPUTE|FOR)/i', $sql, $matches);
if(count($matches)>1 && count($matches[2]) > 0)
{
$parts = explode(',', $matches[2][0]);
foreach($parts as $part)
{
$subs=array();
if(preg_match_all('/(.*)[\s"\]](ASC|DESC)$/i', trim($part), $subs))
{
if(count($subs) > 1 && count($subs[2]) > 0)
{
$name='';
foreach(explode('.', $subs[1][0]) as $p)
{
if($name!=='')
$name.='.';
$name.='[' . trim($p, '[]') . ']';
}
$ordering[$name] = $subs[2][0];
}
//else what?
}
else
$ordering[trim($part)] = 'ASC';
}
}
// replacing column names with their alias names
foreach($ordering as $name => $direction)
{
$matches = array();
$pattern = '/\s+'.str_replace(array('[',']'), array('\[','\]'), $name).'\s+AS\s+(\[[^\]]+\])/i';
preg_match($pattern, $sql, $matches);
if(isset($matches[1]))
{
$ordering[$matches[1]] = $ordering[$name];
unset($ordering[$name]);
}
}
return $ordering;
}
Base on simplified syntax http://msdn2.microsoft.com/en-us/library/aa259187(SQL.80).aspx
protected string joinOrdering(array $orders, string $newPrefix) |
$orders | array | ordering obtained from findOrdering() |
$newPrefix | string | new table prefix to the ordering columns |
{return} | string | concat the orderings |
Source Code: framework/db/schema/mssql/CMssqlCommandBuilder.php#290 (
show)
protected function joinOrdering($orders, $newPrefix)
{
if(count($orders)>0)
{
$str=array();
foreach($orders as $column => $direction)
$str[] = $column.' '.$direction;
$orderBy = 'ORDER BY '.implode(', ', $str);
return preg_replace('/\s+\[[^\]]+\]\.(\[[^\]]+\])/i', ' '.$newPrefix.'.\1', $orderBy);
}
}
protected array reverseDirection(array $orders) |
$orders | array | original ordering |
{return} | array | ordering with reversed direction. |
protected string rewriteLimitOffsetSql(string $sql, integer $limit, integer $offset) |
$sql | string | sql query |
$limit | integer | $limit > 0 |
$offset | integer | $offset > 0 |
{return} | string | modified sql query applied with limit and offset. |
Source Code: framework/db/schema/mssql/CMssqlCommandBuilder.php#215 (
show)
protected function rewriteLimitOffsetSql($sql, $limit, $offset)
{
$fetch = $limit+$offset;
$sql = preg_replace('/^([\s(])*SELECT( DISTINCT)?(?!\s*TOP\s*\()/i',"\\1SELECT\\2 TOP $fetch", $sql);
$ordering = $this->findOrdering($sql);
$originalOrdering = $this->joinOrdering($ordering, '[__outer__]');
$reverseOrdering = $this->joinOrdering($this->reverseDirection($ordering), '[__inner__]');
$sql = "SELECT * FROM (SELECT TOP {$limit} * FROM ($sql) as [__inner__] {$reverseOrdering}) as [__outer__] {$originalOrdering}";
return $sql;
}
Rewrite sql to apply $limit > and $offset > 0 for MSSQL database. See http://troels.arvin.dk/db/rdbms/#select-limit-offset