PageRenderTime 48ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/yii/framework/db/schema/mssql/CMssqlCommandBuilder.php

https://github.com/pittwt/myjackshop
PHP | 335 lines | 165 code | 18 blank | 152 comment | 24 complexity | 228f7d423027cf86bb61766c59a244c4 MD5 | raw file
  1. <?php
  2. /**
  3. * CMsCommandBuilder class file.
  4. *
  5. * @author Qiang Xue <qiang.xue@gmail.com>
  6. * @author Christophe Boulain <Christophe.Boulain@gmail.com>
  7. * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
  8. * @link http://www.yiiframework.com/
  9. * @copyright Copyright &copy; 2008-2011 Yii Software LLC
  10. * @license http://www.yiiframework.com/license/
  11. */
  12. /**
  13. * CMssqlCommandBuilder provides basic methods to create query commands for tables for Mssql Servers.
  14. *
  15. * @author Qiang Xue <qiang.xue@gmail.com>
  16. * @author Christophe Boulain <Christophe.Boulain@gmail.com>
  17. * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
  18. * @version $Id: CMssqlCommandBuilder.php 2821 2011-01-06 17:41:53Z qiang.xue $
  19. * @package system.db.schema.mssql
  20. * @since 1.0.4
  21. */
  22. class CMssqlCommandBuilder extends CDbCommandBuilder
  23. {
  24. /**
  25. * Creates a COUNT(*) command for a single table.
  26. * Override parent implementation to remove the order clause of criteria if it exists
  27. * @param CDbTableSchema $table the table metadata
  28. * @param CDbCriteria $criteria the query criteria
  29. * @param string $alias the alias name of the primary table. Defaults to 't'.
  30. * @return CDbCommand query command.
  31. */
  32. public function createCountCommand($table,$criteria,$alias='t')
  33. {
  34. $criteria->order='';
  35. return parent::createCountCommand($table, $criteria,$alias);
  36. }
  37. /**
  38. * Creates a SELECT command for a single table.
  39. * Override parent implementation to check if an orderby clause if specified when querying with an offset
  40. * @param CDbTableSchema $table the table metadata
  41. * @param CDbCriteria $criteria the query criteria
  42. * @param string $alias the alias name of the primary table. Defaults to 't'.
  43. * @return CDbCommand query command.
  44. */
  45. public function createFindCommand($table,$criteria,$alias='t')
  46. {
  47. $criteria=$this->checkCriteria($table,$criteria);
  48. return parent::createFindCommand($table,$criteria,$alias);
  49. }
  50. /**
  51. * Creates an UPDATE command.
  52. * Override parent implementation because mssql don't want to update an identity column
  53. * @param CDbTableSchema $table the table metadata
  54. * @param array $data list of columns to be updated (name=>value)
  55. * @param CDbCriteria $criteria the query criteria
  56. * @return CDbCommand update command.
  57. */
  58. public function createUpdateCommand($table,$data,$criteria)
  59. {
  60. $criteria=$this->checkCriteria($table,$criteria);
  61. $fields=array();
  62. $values=array();
  63. $bindByPosition=isset($criteria->params[0]);
  64. $i=0;
  65. foreach($data as $name=>$value)
  66. {
  67. if(($column=$table->getColumn($name))!==null)
  68. {
  69. if ($table->sequenceName !== null && $column->isPrimaryKey === true) continue;
  70. if ($column->dbType === 'timestamp') continue;
  71. if($value instanceof CDbExpression)
  72. $fields[]=$column->rawName.'='.$value->expression;
  73. else if($bindByPosition)
  74. {
  75. $fields[]=$column->rawName.'=?';
  76. $values[]=$column->typecast($value);
  77. }
  78. else
  79. {
  80. $fields[]=$column->rawName.'='.self::PARAM_PREFIX.$i;
  81. $values[self::PARAM_PREFIX.$i]=$column->typecast($value);
  82. $i++;
  83. }
  84. }
  85. }
  86. if($fields===array())
  87. throw new CDbException(Yii::t('yii','No columns are being updated for table "{table}".',
  88. array('{table}'=>$table->name)));
  89. $sql="UPDATE {$table->rawName} SET ".implode(', ',$fields);
  90. $sql=$this->applyJoin($sql,$criteria->join);
  91. $sql=$this->applyCondition($sql,$criteria->condition);
  92. $sql=$this->applyOrder($sql,$criteria->order);
  93. $sql=$this->applyLimit($sql,$criteria->limit,$criteria->offset);
  94. $command=$this->getDbConnection()->createCommand($sql);
  95. $this->bindValues($command,array_merge($values,$criteria->params));
  96. return $command;
  97. }
  98. /**
  99. * Creates a DELETE command.
  100. * Override parent implementation to check if an orderby clause if specified when querying with an offset
  101. * @param CDbTableSchema $table the table metadata
  102. * @param CDbCriteria $criteria the query criteria
  103. * @return CDbCommand delete command.
  104. */
  105. public function createDeleteCommand($table,$criteria)
  106. {
  107. $criteria=$this->checkCriteria($table, $criteria);
  108. return parent::createDeleteCommand($table, $criteria);
  109. }
  110. /**
  111. * Creates an UPDATE command that increments/decrements certain columns.
  112. * Override parent implementation to check if an orderby clause if specified when querying with an offset
  113. * @param CDbTableSchema $table the table metadata
  114. * @param CDbCriteria $counters the query criteria
  115. * @param array $criteria counters to be updated (counter increments/decrements indexed by column names.)
  116. * @return CDbCommand the created command
  117. * @throws CException if no counter is specified
  118. */
  119. public function createUpdateCounterCommand($table,$counters,$criteria)
  120. {
  121. $criteria=$this->checkCriteria($table, $criteria);
  122. return parent::createUpdateCounterCommand($table, $counters, $criteria);
  123. }
  124. /**
  125. * This is a port from Prado Framework.
  126. *
  127. * Overrides parent implementation. Alters the sql to apply $limit and $offset.
  128. * The idea for limit with offset is done by modifying the sql on the fly
  129. * with numerous assumptions on the structure of the sql string.
  130. * The modification is done with reference to the notes from
  131. * http://troels.arvin.dk/db/rdbms/#select-limit-offset
  132. *
  133. * <code>
  134. * SELECT * FROM (
  135. * SELECT TOP n * FROM (
  136. * SELECT TOP z columns -- (z=n+skip)
  137. * FROM tablename
  138. * ORDER BY key ASC
  139. * ) AS FOO ORDER BY key DESC -- ('FOO' may be anything)
  140. * ) AS BAR ORDER BY key ASC -- ('BAR' may be anything)
  141. * </code>
  142. *
  143. * <b>Regular expressions are used to alter the SQL query. The resulting SQL query
  144. * may be malformed for complex queries.</b> The following restrictions apply
  145. *
  146. * <ul>
  147. * <li>
  148. * In particular, <b>commas</b> should <b>NOT</b>
  149. * be used as part of the ordering expression or identifier. Commas must only be
  150. * used for separating the ordering clauses.
  151. * </li>
  152. * <li>
  153. * In the ORDER BY clause, the column name should NOT be be qualified
  154. * with a table name or view name. Alias the column names or use column index.
  155. * </li>
  156. * <li>
  157. * No clauses should follow the ORDER BY clause, e.g. no COMPUTE or FOR clauses.
  158. * </li>
  159. *
  160. * @param string $sql SQL query string.
  161. * @param integer $limit maximum number of rows, -1 to ignore limit.
  162. * @param integer $offset row offset, -1 to ignore offset.
  163. * @return string SQL with limit and offset.
  164. *
  165. * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
  166. */
  167. public function applyLimit($sql, $limit, $offset)
  168. {
  169. $limit = $limit!==null ? intval($limit) : -1;
  170. $offset = $offset!==null ? intval($offset) : -1;
  171. if ($limit > 0 && $offset <= 0) //just limit
  172. $sql = preg_replace('/^([\s(])*SELECT( DISTINCT)?(?!\s*TOP\s*\()/i',"\\1SELECT\\2 TOP $limit", $sql);
  173. else if($limit > 0 && $offset > 0)
  174. $sql = $this->rewriteLimitOffsetSql($sql, $limit,$offset);
  175. return $sql;
  176. }
  177. /**
  178. * Rewrite sql to apply $limit > and $offset > 0 for MSSQL database.
  179. * See http://troels.arvin.dk/db/rdbms/#select-limit-offset
  180. * @param string $sql sql query
  181. * @param integer $limit $limit > 0
  182. * @param integer $offset $offset > 0
  183. * @return sql modified sql query applied with limit and offset.
  184. *
  185. * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
  186. */
  187. protected function rewriteLimitOffsetSql($sql, $limit, $offset)
  188. {
  189. $fetch = $limit+$offset;
  190. $sql = preg_replace('/^([\s(])*SELECT( DISTINCT)?(?!\s*TOP\s*\()/i',"\\1SELECT\\2 TOP $fetch", $sql);
  191. $ordering = $this->findOrdering($sql);
  192. $orginalOrdering = $this->joinOrdering($ordering, '[__outer__]');
  193. $reverseOrdering = $this->joinOrdering($this->reverseDirection($ordering), '[__inner__]');
  194. $sql = "SELECT * FROM (SELECT TOP {$limit} * FROM ($sql) as [__inner__] {$reverseOrdering}) as [__outer__] {$orginalOrdering}";
  195. return $sql;
  196. }
  197. /**
  198. * Base on simplified syntax http://msdn2.microsoft.com/en-us/library/aa259187(SQL.80).aspx
  199. *
  200. * @param string $sql $sql
  201. * @return array ordering expression as key and ordering direction as value
  202. *
  203. * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
  204. */
  205. protected function findOrdering($sql)
  206. {
  207. if(!preg_match('/ORDER BY/i', $sql))
  208. return array();
  209. $matches=array();
  210. $ordering=array();
  211. preg_match_all('/(ORDER BY)[\s"\[](.*)(ASC|DESC)?(?:[\s"\[]|$|COMPUTE|FOR)/i', $sql, $matches);
  212. if(count($matches)>1 && count($matches[2]) > 0)
  213. {
  214. $parts = explode(',', $matches[2][0]);
  215. foreach($parts as $part)
  216. {
  217. $subs=array();
  218. if(preg_match_all('/(.*)[\s"\]](ASC|DESC)$/i', trim($part), $subs))
  219. {
  220. if(count($subs) > 1 && count($subs[2]) > 0)
  221. {
  222. $name='';
  223. foreach(explode('.', $subs[1][0]) as $p)
  224. {
  225. if($name!=='')
  226. $name.='.';
  227. $name.='[' . trim($p, '[]') . ']';
  228. }
  229. $ordering[$name] = $subs[2][0];
  230. }
  231. //else what?
  232. }
  233. else
  234. $ordering[trim($part)] = 'ASC';
  235. }
  236. }
  237. // replacing column names with their alias names
  238. foreach($ordering as $name => $direction)
  239. {
  240. $matches = array();
  241. $pattern = '/\s+'.str_replace(array('[',']'), array('\[','\]'), $name).'\s+AS\s+(\[[^\]]+\])/i';
  242. preg_match($pattern, $sql, $matches);
  243. if(isset($matches[1]))
  244. {
  245. $ordering[$matches[1]] = $ordering[$name];
  246. unset($ordering[$name]);
  247. }
  248. }
  249. return $ordering;
  250. }
  251. /**
  252. * @param array $orders ordering obtained from findOrdering()
  253. * @param string $newPrefix new table prefix to the ordering columns
  254. * @return string concat the orderings
  255. *
  256. * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
  257. */
  258. protected function joinOrdering($orders, $newPrefix)
  259. {
  260. if(count($orders)>0)
  261. {
  262. $str=array();
  263. foreach($orders as $column => $direction)
  264. $str[] = $column.' '.$direction;
  265. $orderBy = 'ORDER BY '.implode(', ', $str);
  266. return preg_replace('/\s+\[[^\]]+\]\.(\[[^\]]+\])/i', ' '.$newPrefix.'.\1', $orderBy);
  267. }
  268. }
  269. /**
  270. * @param array $orders original ordering
  271. * @return array ordering with reversed direction.
  272. *
  273. * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
  274. */
  275. protected function reverseDirection($orders)
  276. {
  277. foreach($orders as $column => $direction)
  278. $orders[$column] = strtolower(trim($direction))==='desc' ? 'ASC' : 'DESC';
  279. return $orders;
  280. }
  281. /**
  282. * Checks if the criteria has an order by clause when using offset/limit.
  283. * Override parent implementation to check if an orderby clause if specified when querying with an offset
  284. * If not, order it by pk.
  285. * @param CMssqlTableSchema $table table schema
  286. * @param CDbCriteria $criteria criteria
  287. * @return CDbCrireria the modified criteria
  288. */
  289. protected function checkCriteria($table, $criteria)
  290. {
  291. if ($criteria->offset > 0 && $criteria->order==='')
  292. {
  293. $criteria->order=is_array($table->primaryKey)?implode(',',$table->primaryKey):$table->primaryKey;
  294. }
  295. return $criteria;
  296. }
  297. /**
  298. * Generates the expression for selecting rows with specified composite key values.
  299. * @param CDbTableSchema $table the table schema
  300. * @param array $values list of primary key values to be selected within
  301. * @param string $prefix column prefix (ended with dot)
  302. * @return string the expression for selection
  303. * @since 1.0.4
  304. */
  305. protected function createCompositeInCondition($table,$values,$prefix)
  306. {
  307. $vs=array();
  308. foreach($values as $value)
  309. {
  310. $c=array();
  311. foreach($value as $k=>$v)
  312. $c[]=$prefix.$table->columns[$k]->rawName.'='.$v;
  313. $vs[]='('.implode(' AND ',$c).')';
  314. }
  315. return '('.implode(' OR ',$vs).')';
  316. }
  317. }