QueryBuilder.php 73 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\db;
  8. use yii\base\InvalidArgumentException;
  9. use yii\base\NotSupportedException;
  10. use yii\db\conditions\ConditionInterface;
  11. use yii\db\conditions\HashCondition;
  12. use yii\helpers\StringHelper;
  13. /**
  14. * QueryBuilder builds a SELECT SQL statement based on the specification given as a [[Query]] object.
  15. *
  16. * SQL statements are created from [[Query]] objects using the [[build()]]-method.
  17. *
  18. * QueryBuilder is also used by [[Command]] to build SQL statements such as INSERT, UPDATE, DELETE, CREATE TABLE.
  19. *
  20. * For more details and usage information on QueryBuilder, see the [guide article on query builders](guide:db-query-builder).
  21. *
  22. * @property string[] $conditionClasses Map of condition aliases to condition classes. For example: ```php
  23. * ['LIKE' => yii\db\condition\LikeCondition::class] ``` . This property is write-only.
  24. * @property string[] $expressionBuilders Array of builders that should be merged with the pre-defined ones in
  25. * [[expressionBuilders]] property. This property is write-only.
  26. *
  27. * @author Qiang Xue <qiang.xue@gmail.com>
  28. * @since 2.0
  29. */
  30. class QueryBuilder extends \yii\base\BaseObject
  31. {
  32. /**
  33. * The prefix for automatically generated query binding parameters.
  34. */
  35. const PARAM_PREFIX = ':qp';
  36. /**
  37. * @var Connection the database connection.
  38. */
  39. public $db;
  40. /**
  41. * @var string the separator between different fragments of a SQL statement.
  42. * Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement.
  43. */
  44. public $separator = ' ';
  45. /**
  46. * @var array the abstract column types mapped to physical column types.
  47. * This is mainly used to support creating/modifying tables using DB-independent data type specifications.
  48. * Child classes should override this property to declare supported type mappings.
  49. */
  50. public $typeMap = [];
  51. /**
  52. * @var array map of query condition to builder methods.
  53. * These methods are used by [[buildCondition]] to build SQL conditions from array syntax.
  54. * @deprecated since 2.0.14. Is not used, will be dropped in 2.1.0.
  55. */
  56. protected $conditionBuilders = [];
  57. /**
  58. * @var array map of condition aliases to condition classes. For example:
  59. *
  60. * ```php
  61. * return [
  62. * 'LIKE' => yii\db\condition\LikeCondition::class,
  63. * ];
  64. * ```
  65. *
  66. * This property is used by [[createConditionFromArray]] method.
  67. * See default condition classes list in [[defaultConditionClasses()]] method.
  68. *
  69. * In case you want to add custom conditions support, use the [[setConditionClasses()]] method.
  70. *
  71. * @see setConditonClasses()
  72. * @see defaultConditionClasses()
  73. * @since 2.0.14
  74. */
  75. protected $conditionClasses = [];
  76. /**
  77. * @var string[]|ExpressionBuilderInterface[] maps expression class to expression builder class.
  78. * For example:
  79. *
  80. * ```php
  81. * [
  82. * yii\db\Expression::class => yii\db\ExpressionBuilder::class
  83. * ]
  84. * ```
  85. * This property is mainly used by [[buildExpression()]] to build SQL expressions form expression objects.
  86. * See default values in [[defaultExpressionBuilders()]] method.
  87. *
  88. *
  89. * To override existing builders or add custom, use [[setExpressionBuilder()]] method. New items will be added
  90. * to the end of this array.
  91. *
  92. * To find a builder, [[buildExpression()]] will check the expression class for its exact presence in this map.
  93. * In case it is NOT present, the array will be iterated in reverse direction, checking whether the expression
  94. * extends the class, defined in this map.
  95. *
  96. * @see setExpressionBuilders()
  97. * @see defaultExpressionBuilders()
  98. * @since 2.0.14
  99. */
  100. protected $expressionBuilders = [];
  101. /**
  102. * Constructor.
  103. * @param Connection $connection the database connection.
  104. * @param array $config name-value pairs that will be used to initialize the object properties
  105. */
  106. public function __construct($connection, $config = [])
  107. {
  108. $this->db = $connection;
  109. parent::__construct($config);
  110. }
  111. /**
  112. * {@inheritdoc}
  113. */
  114. public function init()
  115. {
  116. parent::init();
  117. $this->expressionBuilders = array_merge($this->defaultExpressionBuilders(), $this->expressionBuilders);
  118. $this->conditionClasses = array_merge($this->defaultConditionClasses(), $this->conditionClasses);
  119. }
  120. /**
  121. * Contains array of default condition classes. Extend this method, if you want to change
  122. * default condition classes for the query builder. See [[conditionClasses]] docs for details.
  123. *
  124. * @return array
  125. * @see conditionClasses
  126. * @since 2.0.14
  127. */
  128. protected function defaultConditionClasses()
  129. {
  130. return [
  131. 'NOT' => 'yii\db\conditions\NotCondition',
  132. 'AND' => 'yii\db\conditions\AndCondition',
  133. 'OR' => 'yii\db\conditions\OrCondition',
  134. 'BETWEEN' => 'yii\db\conditions\BetweenCondition',
  135. 'NOT BETWEEN' => 'yii\db\conditions\BetweenCondition',
  136. 'IN' => 'yii\db\conditions\InCondition',
  137. 'NOT IN' => 'yii\db\conditions\InCondition',
  138. 'LIKE' => 'yii\db\conditions\LikeCondition',
  139. 'NOT LIKE' => 'yii\db\conditions\LikeCondition',
  140. 'OR LIKE' => 'yii\db\conditions\LikeCondition',
  141. 'OR NOT LIKE' => 'yii\db\conditions\LikeCondition',
  142. 'EXISTS' => 'yii\db\conditions\ExistsCondition',
  143. 'NOT EXISTS' => 'yii\db\conditions\ExistsCondition',
  144. ];
  145. }
  146. /**
  147. * Contains array of default expression builders. Extend this method and override it, if you want to change
  148. * default expression builders for this query builder. See [[expressionBuilders]] docs for details.
  149. *
  150. * @return array
  151. * @see $expressionBuilders
  152. * @since 2.0.14
  153. */
  154. protected function defaultExpressionBuilders()
  155. {
  156. return [
  157. 'yii\db\Query' => 'yii\db\QueryExpressionBuilder',
  158. 'yii\db\PdoValue' => 'yii\db\PdoValueBuilder',
  159. 'yii\db\Expression' => 'yii\db\ExpressionBuilder',
  160. 'yii\db\conditions\ConjunctionCondition' => 'yii\db\conditions\ConjunctionConditionBuilder',
  161. 'yii\db\conditions\NotCondition' => 'yii\db\conditions\NotConditionBuilder',
  162. 'yii\db\conditions\AndCondition' => 'yii\db\conditions\ConjunctionConditionBuilder',
  163. 'yii\db\conditions\OrCondition' => 'yii\db\conditions\ConjunctionConditionBuilder',
  164. 'yii\db\conditions\BetweenCondition' => 'yii\db\conditions\BetweenConditionBuilder',
  165. 'yii\db\conditions\InCondition' => 'yii\db\conditions\InConditionBuilder',
  166. 'yii\db\conditions\LikeCondition' => 'yii\db\conditions\LikeConditionBuilder',
  167. 'yii\db\conditions\ExistsCondition' => 'yii\db\conditions\ExistsConditionBuilder',
  168. 'yii\db\conditions\SimpleCondition' => 'yii\db\conditions\SimpleConditionBuilder',
  169. 'yii\db\conditions\HashCondition' => 'yii\db\conditions\HashConditionBuilder',
  170. 'yii\db\conditions\BetweenColumnsCondition' => 'yii\db\conditions\BetweenColumnsConditionBuilder',
  171. ];
  172. }
  173. /**
  174. * Setter for [[expressionBuilders]] property.
  175. *
  176. * @param string[] $builders array of builders that should be merged with the pre-defined ones
  177. * in [[expressionBuilders]] property.
  178. * @since 2.0.14
  179. * @see expressionBuilders
  180. */
  181. public function setExpressionBuilders($builders)
  182. {
  183. $this->expressionBuilders = array_merge($this->expressionBuilders, $builders);
  184. }
  185. /**
  186. * Setter for [[conditionClasses]] property.
  187. *
  188. * @param string[] $classes map of condition aliases to condition classes. For example:
  189. *
  190. * ```php
  191. * ['LIKE' => yii\db\condition\LikeCondition::class]
  192. * ```
  193. *
  194. * @since 2.0.14.2
  195. * @see conditionClasses
  196. */
  197. public function setConditionClasses($classes)
  198. {
  199. $this->conditionClasses = array_merge($this->conditionClasses, $classes);
  200. }
  201. /**
  202. * Generates a SELECT SQL statement from a [[Query]] object.
  203. *
  204. * @param Query $query the [[Query]] object from which the SQL statement will be generated.
  205. * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
  206. * be included in the result with the additional parameters generated during the query building process.
  207. * @return array the generated SQL statement (the first array element) and the corresponding
  208. * parameters to be bound to the SQL statement (the second array element). The parameters returned
  209. * include those provided in `$params`.
  210. */
  211. public function build($query, $params = [])
  212. {
  213. $query = $query->prepare($this);
  214. $params = empty($params) ? $query->params : array_merge($params, $query->params);
  215. $clauses = [
  216. $this->buildSelect($query->select, $params, $query->distinct, $query->selectOption),
  217. $this->buildFrom($query->from, $params),
  218. $this->buildJoin($query->join, $params),
  219. $this->buildWhere($query->where, $params),
  220. $this->buildGroupBy($query->groupBy),
  221. $this->buildHaving($query->having, $params),
  222. ];
  223. $sql = implode($this->separator, array_filter($clauses));
  224. $sql = $this->buildOrderByAndLimit($sql, $query->orderBy, $query->limit, $query->offset);
  225. if (!empty($query->orderBy)) {
  226. foreach ($query->orderBy as $expression) {
  227. if ($expression instanceof ExpressionInterface) {
  228. $this->buildExpression($expression, $params);
  229. }
  230. }
  231. }
  232. if (!empty($query->groupBy)) {
  233. foreach ($query->groupBy as $expression) {
  234. if ($expression instanceof ExpressionInterface) {
  235. $this->buildExpression($expression, $params);
  236. }
  237. }
  238. }
  239. $union = $this->buildUnion($query->union, $params);
  240. if ($union !== '') {
  241. $sql = "($sql){$this->separator}$union";
  242. }
  243. return [$sql, $params];
  244. }
  245. /**
  246. * Builds given $expression
  247. *
  248. * @param ExpressionInterface $expression the expression to be built
  249. * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
  250. * be included in the result with the additional parameters generated during the expression building process.
  251. * @return string the SQL statement that will not be neither quoted nor encoded before passing to DBMS
  252. * @throws InvalidArgumentException when $expression building is not supported by this QueryBuilder.
  253. * @see ExpressionBuilderInterface
  254. * @see expressionBuilders
  255. * @since 2.0.14
  256. * @see ExpressionInterface
  257. */
  258. public function buildExpression(ExpressionInterface $expression, &$params = [])
  259. {
  260. $builder = $this->getExpressionBuilder($expression);
  261. return $builder->build($expression, $params);
  262. }
  263. /**
  264. * Gets object of [[ExpressionBuilderInterface]] that is suitable for $expression.
  265. * Uses [[expressionBuilders]] array to find a suitable builder class.
  266. *
  267. * @param ExpressionInterface $expression
  268. * @return ExpressionBuilderInterface
  269. * @throws InvalidArgumentException when $expression building is not supported by this QueryBuilder.
  270. * @since 2.0.14
  271. * @see expressionBuilders
  272. */
  273. public function getExpressionBuilder(ExpressionInterface $expression)
  274. {
  275. $className = get_class($expression);
  276. if (!isset($this->expressionBuilders[$className])) {
  277. foreach (array_reverse($this->expressionBuilders) as $expressionClass => $builderClass) {
  278. if (is_subclass_of($expression, $expressionClass)) {
  279. $this->expressionBuilders[$className] = $builderClass;
  280. break;
  281. }
  282. }
  283. if (!isset($this->expressionBuilders[$className])) {
  284. throw new InvalidArgumentException('Expression of class ' . $className . ' can not be built in ' . get_class($this));
  285. }
  286. }
  287. if ($this->expressionBuilders[$className] === __CLASS__) {
  288. return $this;
  289. }
  290. if (!is_object($this->expressionBuilders[$className])) {
  291. $this->expressionBuilders[$className] = new $this->expressionBuilders[$className]($this);
  292. }
  293. return $this->expressionBuilders[$className];
  294. }
  295. /**
  296. * Creates an INSERT SQL statement.
  297. * For example,
  298. * ```php
  299. * $sql = $queryBuilder->insert('user', [
  300. * 'name' => 'Sam',
  301. * 'age' => 30,
  302. * ], $params);
  303. * ```
  304. * The method will properly escape the table and column names.
  305. *
  306. * @param string $table the table that new rows will be inserted into.
  307. * @param array|Query $columns the column data (name => value) to be inserted into the table or instance
  308. * of [[yii\db\Query|Query]] to perform INSERT INTO ... SELECT SQL statement.
  309. * Passing of [[yii\db\Query|Query]] is available since version 2.0.11.
  310. * @param array $params the binding parameters that will be generated by this method.
  311. * They should be bound to the DB command later.
  312. * @return string the INSERT SQL
  313. */
  314. public function insert($table, $columns, &$params)
  315. {
  316. list($names, $placeholders, $values, $params) = $this->prepareInsertValues($table, $columns, $params);
  317. return 'INSERT INTO ' . $this->db->quoteTableName($table)
  318. . (!empty($names) ? ' (' . implode(', ', $names) . ')' : '')
  319. . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : $values);
  320. }
  321. /**
  322. * Prepares a `VALUES` part for an `INSERT` SQL statement.
  323. *
  324. * @param string $table the table that new rows will be inserted into.
  325. * @param array|Query $columns the column data (name => value) to be inserted into the table or instance
  326. * of [[yii\db\Query|Query]] to perform INSERT INTO ... SELECT SQL statement.
  327. * @param array $params the binding parameters that will be generated by this method.
  328. * They should be bound to the DB command later.
  329. * @return array array of column names, placeholders, values and params.
  330. * @since 2.0.14
  331. */
  332. protected function prepareInsertValues($table, $columns, $params = [])
  333. {
  334. $schema = $this->db->getSchema();
  335. $tableSchema = $schema->getTableSchema($table);
  336. $columnSchemas = $tableSchema !== null ? $tableSchema->columns : [];
  337. $names = [];
  338. $placeholders = [];
  339. $values = ' DEFAULT VALUES';
  340. if ($columns instanceof Query) {
  341. list($names, $values, $params) = $this->prepareInsertSelectSubQuery($columns, $schema, $params);
  342. } else {
  343. foreach ($columns as $name => $value) {
  344. $names[] = $schema->quoteColumnName($name);
  345. $value = isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
  346. if ($value instanceof ExpressionInterface) {
  347. $placeholders[] = $this->buildExpression($value, $params);
  348. } elseif ($value instanceof \yii\db\Query) {
  349. list($sql, $params) = $this->build($value, $params);
  350. $placeholders[] = "($sql)";
  351. } else {
  352. $placeholders[] = $this->bindParam($value, $params);
  353. }
  354. }
  355. }
  356. return [$names, $placeholders, $values, $params];
  357. }
  358. /**
  359. * Prepare select-subquery and field names for INSERT INTO ... SELECT SQL statement.
  360. *
  361. * @param Query $columns Object, which represents select query.
  362. * @param \yii\db\Schema $schema Schema object to quote column name.
  363. * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
  364. * be included in the result with the additional parameters generated during the query building process.
  365. * @return array array of column names, values and params.
  366. * @throws InvalidArgumentException if query's select does not contain named parameters only.
  367. * @since 2.0.11
  368. */
  369. protected function prepareInsertSelectSubQuery($columns, $schema, $params = [])
  370. {
  371. if (!is_array($columns->select) || empty($columns->select) || in_array('*', $columns->select)) {
  372. throw new InvalidArgumentException('Expected select query object with enumerated (named) parameters');
  373. }
  374. list($values, $params) = $this->build($columns, $params);
  375. $names = [];
  376. $values = ' ' . $values;
  377. foreach ($columns->select as $title => $field) {
  378. if (is_string($title)) {
  379. $names[] = $schema->quoteColumnName($title);
  380. } elseif (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $field, $matches)) {
  381. $names[] = $schema->quoteColumnName($matches[2]);
  382. } else {
  383. $names[] = $schema->quoteColumnName($field);
  384. }
  385. }
  386. return [$names, $values, $params];
  387. }
  388. /**
  389. * Generates a batch INSERT SQL statement.
  390. *
  391. * For example,
  392. *
  393. * ```php
  394. * $sql = $queryBuilder->batchInsert('user', ['name', 'age'], [
  395. * ['Tom', 30],
  396. * ['Jane', 20],
  397. * ['Linda', 25],
  398. * ]);
  399. * ```
  400. *
  401. * Note that the values in each row must match the corresponding column names.
  402. *
  403. * The method will properly escape the column names, and quote the values to be inserted.
  404. *
  405. * @param string $table the table that new rows will be inserted into.
  406. * @param array $columns the column names
  407. * @param array|\Generator $rows the rows to be batch inserted into the table
  408. * @param array $params the binding parameters. This parameter exists since 2.0.14
  409. * @return string the batch INSERT SQL statement
  410. */
  411. public function batchInsert($table, $columns, $rows, &$params = [])
  412. {
  413. if (empty($rows)) {
  414. return '';
  415. }
  416. $schema = $this->db->getSchema();
  417. if (($tableSchema = $schema->getTableSchema($table)) !== null) {
  418. $columnSchemas = $tableSchema->columns;
  419. } else {
  420. $columnSchemas = [];
  421. }
  422. $values = [];
  423. foreach ($rows as $row) {
  424. $vs = [];
  425. foreach ($row as $i => $value) {
  426. if (isset($columns[$i], $columnSchemas[$columns[$i]])) {
  427. $value = $columnSchemas[$columns[$i]]->dbTypecast($value);
  428. }
  429. if (is_string($value)) {
  430. $value = $schema->quoteValue($value);
  431. } elseif (is_float($value)) {
  432. // ensure type cast always has . as decimal separator in all locales
  433. $value = StringHelper::floatToString($value);
  434. } elseif ($value === false) {
  435. $value = 0;
  436. } elseif ($value === null) {
  437. $value = 'NULL';
  438. } elseif ($value instanceof ExpressionInterface) {
  439. $value = $this->buildExpression($value, $params);
  440. }
  441. $vs[] = $value;
  442. }
  443. $values[] = '(' . implode(', ', $vs) . ')';
  444. }
  445. if (empty($values)) {
  446. return '';
  447. }
  448. foreach ($columns as $i => $name) {
  449. $columns[$i] = $schema->quoteColumnName($name);
  450. }
  451. return 'INSERT INTO ' . $schema->quoteTableName($table)
  452. . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
  453. }
  454. /**
  455. * Creates an SQL statement to insert rows into a database table if
  456. * they do not already exist (matching unique constraints),
  457. * or update them if they do.
  458. *
  459. * For example,
  460. *
  461. * ```php
  462. * $sql = $queryBuilder->upsert('pages', [
  463. * 'name' => 'Front page',
  464. * 'url' => 'http://example.com/', // url is unique
  465. * 'visits' => 0,
  466. * ], [
  467. * 'visits' => new \yii\db\Expression('visits + 1'),
  468. * ], $params);
  469. * ```
  470. *
  471. * The method will properly escape the table and column names.
  472. *
  473. * @param string $table the table that new rows will be inserted into/updated in.
  474. * @param array|Query $insertColumns the column data (name => value) to be inserted into the table or instance
  475. * of [[Query]] to perform `INSERT INTO ... SELECT` SQL statement.
  476. * @param array|bool $updateColumns the column data (name => value) to be updated if they already exist.
  477. * If `true` is passed, the column data will be updated to match the insert column data.
  478. * If `false` is passed, no update will be performed if the column data already exists.
  479. * @param array $params the binding parameters that will be generated by this method.
  480. * They should be bound to the DB command later.
  481. * @return string the resulting SQL.
  482. * @throws NotSupportedException if this is not supported by the underlying DBMS.
  483. * @since 2.0.14
  484. */
  485. public function upsert($table, $insertColumns, $updateColumns, &$params)
  486. {
  487. throw new NotSupportedException($this->db->getDriverName() . ' does not support upsert statements.');
  488. }
  489. /**
  490. * @param string $table
  491. * @param array|Query $insertColumns
  492. * @param array|bool $updateColumns
  493. * @param Constraint[] $constraints this parameter recieves a matched constraint list.
  494. * The constraints will be unique by their column names.
  495. * @return array
  496. * @since 2.0.14
  497. */
  498. protected function prepareUpsertColumns($table, $insertColumns, $updateColumns, &$constraints = [])
  499. {
  500. if ($insertColumns instanceof Query) {
  501. list($insertNames) = $this->prepareInsertSelectSubQuery($insertColumns, $this->db->getSchema());
  502. } else {
  503. $insertNames = array_map([$this->db, 'quoteColumnName'], array_keys($insertColumns));
  504. }
  505. $uniqueNames = $this->getTableUniqueColumnNames($table, $insertNames, $constraints);
  506. $uniqueNames = array_map([$this->db, 'quoteColumnName'], $uniqueNames);
  507. if ($updateColumns !== true) {
  508. return [$uniqueNames, $insertNames, null];
  509. }
  510. return [$uniqueNames, $insertNames, array_diff($insertNames, $uniqueNames)];
  511. }
  512. /**
  513. * Returns all column names belonging to constraints enforcing uniqueness (`PRIMARY KEY`, `UNIQUE INDEX`, etc.)
  514. * for the named table removing constraints which did not cover the specified column list.
  515. * The column list will be unique by column names.
  516. *
  517. * @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
  518. * @param string[] $columns source column list.
  519. * @param Constraint[] $constraints this parameter optionally recieves a matched constraint list.
  520. * The constraints will be unique by their column names.
  521. * @return string[] column list.
  522. */
  523. private function getTableUniqueColumnNames($name, $columns, &$constraints = [])
  524. {
  525. $schema = $this->db->getSchema();
  526. if (!$schema instanceof ConstraintFinderInterface) {
  527. return [];
  528. }
  529. $constraints = [];
  530. $primaryKey = $schema->getTablePrimaryKey($name);
  531. if ($primaryKey !== null) {
  532. $constraints[] = $primaryKey;
  533. }
  534. foreach ($schema->getTableIndexes($name) as $constraint) {
  535. if ($constraint->isUnique) {
  536. $constraints[] = $constraint;
  537. }
  538. }
  539. $constraints = array_merge($constraints, $schema->getTableUniques($name));
  540. // Remove duplicates
  541. $constraints = array_combine(array_map(function (Constraint $constraint) {
  542. $columns = $constraint->columnNames;
  543. sort($columns, SORT_STRING);
  544. return json_encode($columns);
  545. }, $constraints), $constraints);
  546. $columnNames = [];
  547. // Remove all constraints which do not cover the specified column list
  548. $constraints = array_values(array_filter($constraints, function (Constraint $constraint) use ($schema, $columns, &$columnNames) {
  549. $constraintColumnNames = array_map([$schema, 'quoteColumnName'], $constraint->columnNames);
  550. $result = !array_diff($constraintColumnNames, $columns);
  551. if ($result) {
  552. $columnNames = array_merge($columnNames, $constraintColumnNames);
  553. }
  554. return $result;
  555. }));
  556. return array_unique($columnNames);
  557. }
  558. /**
  559. * Creates an UPDATE SQL statement.
  560. *
  561. * For example,
  562. *
  563. * ```php
  564. * $params = [];
  565. * $sql = $queryBuilder->update('user', ['status' => 1], 'age > 30', $params);
  566. * ```
  567. *
  568. * The method will properly escape the table and column names.
  569. *
  570. * @param string $table the table to be updated.
  571. * @param array $columns the column data (name => value) to be updated.
  572. * @param array|string $condition the condition that will be put in the WHERE part. Please
  573. * refer to [[Query::where()]] on how to specify condition.
  574. * @param array $params the binding parameters that will be modified by this method
  575. * so that they can be bound to the DB command later.
  576. * @return string the UPDATE SQL
  577. */
  578. public function update($table, $columns, $condition, &$params)
  579. {
  580. list($lines, $params) = $this->prepareUpdateSets($table, $columns, $params);
  581. $sql = 'UPDATE ' . $this->db->quoteTableName($table) . ' SET ' . implode(', ', $lines);
  582. $where = $this->buildWhere($condition, $params);
  583. return $where === '' ? $sql : $sql . ' ' . $where;
  584. }
  585. /**
  586. * Prepares a `SET` parts for an `UPDATE` SQL statement.
  587. * @param string $table the table to be updated.
  588. * @param array $columns the column data (name => value) to be updated.
  589. * @param array $params the binding parameters that will be modified by this method
  590. * so that they can be bound to the DB command later.
  591. * @return array an array `SET` parts for an `UPDATE` SQL statement (the first array element) and params (the second array element).
  592. * @since 2.0.14
  593. */
  594. protected function prepareUpdateSets($table, $columns, $params = [])
  595. {
  596. $tableSchema = $this->db->getTableSchema($table);
  597. $columnSchemas = $tableSchema !== null ? $tableSchema->columns : [];
  598. $sets = [];
  599. foreach ($columns as $name => $value) {
  600. $value = isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
  601. if ($value instanceof ExpressionInterface) {
  602. $placeholder = $this->buildExpression($value, $params);
  603. } else {
  604. $placeholder = $this->bindParam($value, $params);
  605. }
  606. $sets[] = $this->db->quoteColumnName($name) . '=' . $placeholder;
  607. }
  608. return [$sets, $params];
  609. }
  610. /**
  611. * Creates a DELETE SQL statement.
  612. *
  613. * For example,
  614. *
  615. * ```php
  616. * $sql = $queryBuilder->delete('user', 'status = 0');
  617. * ```
  618. *
  619. * The method will properly escape the table and column names.
  620. *
  621. * @param string $table the table where the data will be deleted from.
  622. * @param array|string $condition the condition that will be put in the WHERE part. Please
  623. * refer to [[Query::where()]] on how to specify condition.
  624. * @param array $params the binding parameters that will be modified by this method
  625. * so that they can be bound to the DB command later.
  626. * @return string the DELETE SQL
  627. */
  628. public function delete($table, $condition, &$params)
  629. {
  630. $sql = 'DELETE FROM ' . $this->db->quoteTableName($table);
  631. $where = $this->buildWhere($condition, $params);
  632. return $where === '' ? $sql : $sql . ' ' . $where;
  633. }
  634. /**
  635. * Builds a SQL statement for creating a new DB table.
  636. *
  637. * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'),
  638. * where name stands for a column name which will be properly quoted by the method, and definition
  639. * stands for the column type which can contain an abstract DB type.
  640. * The [[getColumnType()]] method will be invoked to convert any abstract type into a physical one.
  641. *
  642. * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
  643. * inserted into the generated SQL.
  644. *
  645. * For example,
  646. *
  647. * ```php
  648. * $sql = $queryBuilder->createTable('user', [
  649. * 'id' => 'pk',
  650. * 'name' => 'string',
  651. * 'age' => 'integer',
  652. * ]);
  653. * ```
  654. *
  655. * @param string $table the name of the table to be created. The name will be properly quoted by the method.
  656. * @param array $columns the columns (name => definition) in the new table.
  657. * @param string $options additional SQL fragment that will be appended to the generated SQL.
  658. * @return string the SQL statement for creating a new DB table.
  659. */
  660. public function createTable($table, $columns, $options = null)
  661. {
  662. $cols = [];
  663. foreach ($columns as $name => $type) {
  664. if (is_string($name)) {
  665. $cols[] = "\t" . $this->db->quoteColumnName($name) . ' ' . $this->getColumnType($type);
  666. } else {
  667. $cols[] = "\t" . $type;
  668. }
  669. }
  670. $sql = 'CREATE TABLE ' . $this->db->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)";
  671. return $options === null ? $sql : $sql . ' ' . $options;
  672. }
  673. /**
  674. * Builds a SQL statement for renaming a DB table.
  675. * @param string $oldName the table to be renamed. The name will be properly quoted by the method.
  676. * @param string $newName the new table name. The name will be properly quoted by the method.
  677. * @return string the SQL statement for renaming a DB table.
  678. */
  679. public function renameTable($oldName, $newName)
  680. {
  681. return 'RENAME TABLE ' . $this->db->quoteTableName($oldName) . ' TO ' . $this->db->quoteTableName($newName);
  682. }
  683. /**
  684. * Builds a SQL statement for dropping a DB table.
  685. * @param string $table the table to be dropped. The name will be properly quoted by the method.
  686. * @return string the SQL statement for dropping a DB table.
  687. */
  688. public function dropTable($table)
  689. {
  690. return 'DROP TABLE ' . $this->db->quoteTableName($table);
  691. }
  692. /**
  693. * Builds a SQL statement for adding a primary key constraint to an existing table.
  694. * @param string $name the name of the primary key constraint.
  695. * @param string $table the table that the primary key constraint will be added to.
  696. * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
  697. * @return string the SQL statement for adding a primary key constraint to an existing table.
  698. */
  699. public function addPrimaryKey($name, $table, $columns)
  700. {
  701. if (is_string($columns)) {
  702. $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
  703. }
  704. foreach ($columns as $i => $col) {
  705. $columns[$i] = $this->db->quoteColumnName($col);
  706. }
  707. return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
  708. . $this->db->quoteColumnName($name) . ' PRIMARY KEY ('
  709. . implode(', ', $columns) . ')';
  710. }
  711. /**
  712. * Builds a SQL statement for removing a primary key constraint to an existing table.
  713. * @param string $name the name of the primary key constraint to be removed.
  714. * @param string $table the table that the primary key constraint will be removed from.
  715. * @return string the SQL statement for removing a primary key constraint from an existing table.
  716. */
  717. public function dropPrimaryKey($name, $table)
  718. {
  719. return 'ALTER TABLE ' . $this->db->quoteTableName($table)
  720. . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
  721. }
  722. /**
  723. * Builds a SQL statement for truncating a DB table.
  724. * @param string $table the table to be truncated. The name will be properly quoted by the method.
  725. * @return string the SQL statement for truncating a DB table.
  726. */
  727. public function truncateTable($table)
  728. {
  729. return 'TRUNCATE TABLE ' . $this->db->quoteTableName($table);
  730. }
  731. /**
  732. * Builds a SQL statement for adding a new DB column.
  733. * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
  734. * @param string $column the name of the new column. The name will be properly quoted by the method.
  735. * @param string $type the column type. The [[getColumnType()]] method will be invoked to convert abstract column type (if any)
  736. * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
  737. * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
  738. * @return string the SQL statement for adding a new column.
  739. */
  740. public function addColumn($table, $column, $type)
  741. {
  742. return 'ALTER TABLE ' . $this->db->quoteTableName($table)
  743. . ' ADD ' . $this->db->quoteColumnName($column) . ' '
  744. . $this->getColumnType($type);
  745. }
  746. /**
  747. * Builds a SQL statement for dropping a DB column.
  748. * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
  749. * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
  750. * @return string the SQL statement for dropping a DB column.
  751. */
  752. public function dropColumn($table, $column)
  753. {
  754. return 'ALTER TABLE ' . $this->db->quoteTableName($table)
  755. . ' DROP COLUMN ' . $this->db->quoteColumnName($column);
  756. }
  757. /**
  758. * Builds a SQL statement for renaming a column.
  759. * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
  760. * @param string $oldName the old name of the column. The name will be properly quoted by the method.
  761. * @param string $newName the new name of the column. The name will be properly quoted by the method.
  762. * @return string the SQL statement for renaming a DB column.
  763. */
  764. public function renameColumn($table, $oldName, $newName)
  765. {
  766. return 'ALTER TABLE ' . $this->db->quoteTableName($table)
  767. . ' RENAME COLUMN ' . $this->db->quoteColumnName($oldName)
  768. . ' TO ' . $this->db->quoteColumnName($newName);
  769. }
  770. /**
  771. * Builds a SQL statement for changing the definition of a column.
  772. * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
  773. * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
  774. * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
  775. * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
  776. * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
  777. * will become 'varchar(255) not null'.
  778. * @return string the SQL statement for changing the definition of a column.
  779. */
  780. public function alterColumn($table, $column, $type)
  781. {
  782. return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' CHANGE '
  783. . $this->db->quoteColumnName($column) . ' '
  784. . $this->db->quoteColumnName($column) . ' '
  785. . $this->getColumnType($type);
  786. }
  787. /**
  788. * Builds a SQL statement for adding a foreign key constraint to an existing table.
  789. * The method will properly quote the table and column names.
  790. * @param string $name the name of the foreign key constraint.
  791. * @param string $table the table that the foreign key constraint will be added to.
  792. * @param string|array $columns the name of the column to that the constraint will be added on.
  793. * If there are multiple columns, separate them with commas or use an array to represent them.
  794. * @param string $refTable the table that the foreign key references to.
  795. * @param string|array $refColumns the name of the column that the foreign key references to.
  796. * If there are multiple columns, separate them with commas or use an array to represent them.
  797. * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
  798. * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
  799. * @return string the SQL statement for adding a foreign key constraint to an existing table.
  800. */
  801. public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
  802. {
  803. $sql = 'ALTER TABLE ' . $this->db->quoteTableName($table)
  804. . ' ADD CONSTRAINT ' . $this->db->quoteColumnName($name)
  805. . ' FOREIGN KEY (' . $this->buildColumns($columns) . ')'
  806. . ' REFERENCES ' . $this->db->quoteTableName($refTable)
  807. . ' (' . $this->buildColumns($refColumns) . ')';
  808. if ($delete !== null) {
  809. $sql .= ' ON DELETE ' . $delete;
  810. }
  811. if ($update !== null) {
  812. $sql .= ' ON UPDATE ' . $update;
  813. }
  814. return $sql;
  815. }
  816. /**
  817. * Builds a SQL statement for dropping a foreign key constraint.
  818. * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
  819. * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
  820. * @return string the SQL statement for dropping a foreign key constraint.
  821. */
  822. public function dropForeignKey($name, $table)
  823. {
  824. return 'ALTER TABLE ' . $this->db->quoteTableName($table)
  825. . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
  826. }
  827. /**
  828. * Builds a SQL statement for creating a new index.
  829. * @param string $name the name of the index. The name will be properly quoted by the method.
  830. * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
  831. * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns,
  832. * separate them with commas or use an array to represent them. Each column name will be properly quoted
  833. * by the method, unless a parenthesis is found in the name.
  834. * @param bool $unique whether to add UNIQUE constraint on the created index.
  835. * @return string the SQL statement for creating a new index.
  836. */
  837. public function createIndex($name, $table, $columns, $unique = false)
  838. {
  839. return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ')
  840. . $this->db->quoteTableName($name) . ' ON '
  841. . $this->db->quoteTableName($table)
  842. . ' (' . $this->buildColumns($columns) . ')';
  843. }
  844. /**
  845. * Builds a SQL statement for dropping an index.
  846. * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
  847. * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
  848. * @return string the SQL statement for dropping an index.
  849. */
  850. public function dropIndex($name, $table)
  851. {
  852. return 'DROP INDEX ' . $this->db->quoteTableName($name) . ' ON ' . $this->db->quoteTableName($table);
  853. }
  854. /**
  855. * Creates a SQL command for adding an unique constraint to an existing table.
  856. * @param string $name the name of the unique constraint.
  857. * The name will be properly quoted by the method.
  858. * @param string $table the table that the unique constraint will be added to.
  859. * The name will be properly quoted by the method.
  860. * @param string|array $columns the name of the column to that the constraint will be added on.
  861. * If there are multiple columns, separate them with commas.
  862. * The name will be properly quoted by the method.
  863. * @return string the SQL statement for adding an unique constraint to an existing table.
  864. * @since 2.0.13
  865. */
  866. public function addUnique($name, $table, $columns)
  867. {
  868. if (is_string($columns)) {
  869. $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
  870. }
  871. foreach ($columns as $i => $col) {
  872. $columns[$i] = $this->db->quoteColumnName($col);
  873. }
  874. return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
  875. . $this->db->quoteColumnName($name) . ' UNIQUE ('
  876. . implode(', ', $columns) . ')';
  877. }
  878. /**
  879. * Creates a SQL command for dropping an unique constraint.
  880. * @param string $name the name of the unique constraint to be dropped.
  881. * The name will be properly quoted by the method.
  882. * @param string $table the table whose unique constraint is to be dropped.
  883. * The name will be properly quoted by the method.
  884. * @return string the SQL statement for dropping an unique constraint.
  885. * @since 2.0.13
  886. */
  887. public function dropUnique($name, $table)
  888. {
  889. return 'ALTER TABLE ' . $this->db->quoteTableName($table)
  890. . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
  891. }
  892. /**
  893. * Creates a SQL command for adding a check constraint to an existing table.
  894. * @param string $name the name of the check constraint.
  895. * The name will be properly quoted by the method.
  896. * @param string $table the table that the check constraint will be added to.
  897. * The name will be properly quoted by the method.
  898. * @param string $expression the SQL of the `CHECK` constraint.
  899. * @return string the SQL statement for adding a check constraint to an existing table.
  900. * @since 2.0.13
  901. */
  902. public function addCheck($name, $table, $expression)
  903. {
  904. return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
  905. . $this->db->quoteColumnName($name) . ' CHECK (' . $this->db->quoteSql($expression) . ')';
  906. }
  907. /**
  908. * Creates a SQL command for dropping a check constraint.
  909. * @param string $name the name of the check constraint to be dropped.
  910. * The name will be properly quoted by the method.
  911. * @param string $table the table whose check constraint is to be dropped.
  912. * The name will be properly quoted by the method.
  913. * @return string the SQL statement for dropping a check constraint.
  914. * @since 2.0.13
  915. */
  916. public function dropCheck($name, $table)
  917. {
  918. return 'ALTER TABLE ' . $this->db->quoteTableName($table)
  919. . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
  920. }
  921. /**
  922. * Creates a SQL command for adding a default value constraint to an existing table.
  923. * @param string $name the name of the default value constraint.
  924. * The name will be properly quoted by the method.
  925. * @param string $table the table that the default value constraint will be added to.
  926. * The name will be properly quoted by the method.
  927. * @param string $column the name of the column to that the constraint will be added on.
  928. * The name will be properly quoted by the method.
  929. * @param mixed $value default value.
  930. * @return string the SQL statement for adding a default value constraint to an existing table.
  931. * @throws NotSupportedException if this is not supported by the underlying DBMS.
  932. * @since 2.0.13
  933. */
  934. public function addDefaultValue($name, $table, $column, $value)
  935. {
  936. throw new NotSupportedException($this->db->getDriverName() . ' does not support adding default value constraints.');
  937. }
  938. /**
  939. * Creates a SQL command for dropping a default value constraint.
  940. * @param string $name the name of the default value constraint to be dropped.
  941. * The name will be properly quoted by the method.
  942. * @param string $table the table whose default value constraint is to be dropped.
  943. * The name will be properly quoted by the method.
  944. * @return string the SQL statement for dropping a default value constraint.
  945. * @throws NotSupportedException if this is not supported by the underlying DBMS.
  946. * @since 2.0.13
  947. */
  948. public function dropDefaultValue($name, $table)
  949. {
  950. throw new NotSupportedException($this->db->getDriverName() . ' does not support dropping default value constraints.');
  951. }
  952. /**
  953. * Creates a SQL statement for resetting the sequence value of a table's primary key.
  954. * The sequence will be reset such that the primary key of the next new row inserted
  955. * will have the specified value or the maximum existing value +1.
  956. * @param string $table the name of the table whose primary key sequence will be reset
  957. * @param array|string $value the value for the primary key of the next new row inserted. If this is not set,
  958. * the next new row's primary key will have the maximum existing value +1.
  959. * @return string the SQL statement for resetting sequence
  960. * @throws NotSupportedException if this is not supported by the underlying DBMS
  961. */
  962. public function resetSequence($table, $value = null)
  963. {
  964. throw new NotSupportedException($this->db->getDriverName() . ' does not support resetting sequence.');
  965. }
  966. /**
  967. * Execute a SQL statement for resetting the sequence value of a table's primary key.
  968. * Reason for execute is that some databases (Oracle) need several queries to do so.
  969. * The sequence is reset such that the primary key of the next new row inserted
  970. * will have the specified value or the maximum existing value +1.
  971. * @param string $table the name of the table whose primary key sequence is reset
  972. * @param array|string $value the value for the primary key of the next new row inserted. If this is not set,
  973. * the next new row's primary key will have the maximum existing value +1.
  974. * @throws NotSupportedException if this is not supported by the underlying DBMS
  975. * @since 2.0.16
  976. */
  977. public function executeResetSequence($table, $value = null)
  978. {
  979. $this->db->createCommand()->resetSequence($table, $value)->execute();
  980. }
  981. /**
  982. * Builds a SQL statement for enabling or disabling integrity check.
  983. * @param bool $check whether to turn on or off the integrity check.
  984. * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
  985. * @param string $table the table name. Defaults to empty string, meaning that no table will be changed.
  986. * @return string the SQL statement for checking integrity
  987. * @throws NotSupportedException if this is not supported by the underlying DBMS
  988. */
  989. public function checkIntegrity($check = true, $schema = '', $table = '')
  990. {
  991. throw new NotSupportedException($this->db->getDriverName() . ' does not support enabling/disabling integrity check.');
  992. }
  993. /**
  994. * Builds a SQL command for adding comment to column.
  995. *
  996. * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
  997. * @param string $column the name of the column to be commented. The column name will be properly quoted by the method.
  998. * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method.
  999. * @return string the SQL statement for adding comment on column
  1000. * @since 2.0.8
  1001. */
  1002. public function addCommentOnColumn($table, $column, $comment)
  1003. {
  1004. return 'COMMENT ON COLUMN ' . $this->db->quoteTableName($table) . '.' . $this->db->quoteColumnName($column) . ' IS ' . $this->db->quoteValue($comment);
  1005. }
  1006. /**
  1007. * Builds a SQL command for adding comment to table.
  1008. *
  1009. * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
  1010. * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method.
  1011. * @return string the SQL statement for adding comment on table
  1012. * @since 2.0.8
  1013. */
  1014. public function addCommentOnTable($table, $comment)
  1015. {
  1016. return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . ' IS ' . $this->db->quoteValue($comment);
  1017. }
  1018. /**
  1019. * Builds a SQL command for adding comment to column.
  1020. *
  1021. * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
  1022. * @param string $column the name of the column to be commented. The column name will be properly quoted by the method.
  1023. * @return string the SQL statement for adding comment on column
  1024. * @since 2.0.8
  1025. */
  1026. public function dropCommentFromColumn($table, $column)
  1027. {
  1028. return 'COMMENT ON COLUMN ' . $this->db->quoteTableName($table) . '.' . $this->db->quoteColumnName($column) . ' IS NULL';
  1029. }
  1030. /**
  1031. * Builds a SQL command for adding comment to table.
  1032. *
  1033. * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
  1034. * @return string the SQL statement for adding comment on column
  1035. * @since 2.0.8
  1036. */
  1037. public function dropCommentFromTable($table)
  1038. {
  1039. return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . ' IS NULL';
  1040. }
  1041. /**
  1042. * Creates a SQL View.
  1043. *
  1044. * @param string $viewName the name of the view to be created.
  1045. * @param string|Query $subQuery the select statement which defines the view.
  1046. * This can be either a string or a [[Query]] object.
  1047. * @return string the `CREATE VIEW` SQL statement.
  1048. * @since 2.0.14
  1049. */
  1050. public function createView($viewName, $subQuery)
  1051. {
  1052. if ($subQuery instanceof Query) {
  1053. list($rawQuery, $params) = $this->build($subQuery);
  1054. array_walk(
  1055. $params,
  1056. function (&$param) {
  1057. $param = $this->db->quoteValue($param);
  1058. }
  1059. );
  1060. $subQuery = strtr($rawQuery, $params);
  1061. }
  1062. return 'CREATE VIEW ' . $this->db->quoteTableName($viewName) . ' AS ' . $subQuery;
  1063. }
  1064. /**
  1065. * Drops a SQL View.
  1066. *
  1067. * @param string $viewName the name of the view to be dropped.
  1068. * @return string the `DROP VIEW` SQL statement.
  1069. * @since 2.0.14
  1070. */
  1071. public function dropView($viewName)
  1072. {
  1073. return 'DROP VIEW ' . $this->db->quoteTableName($viewName);
  1074. }
  1075. /**
  1076. * Converts an abstract column type into a physical column type.
  1077. *
  1078. * The conversion is done using the type map specified in [[typeMap]].
  1079. * The following abstract column types are supported (using MySQL as an example to explain the corresponding
  1080. * physical types):
  1081. *
  1082. * - `pk`: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY"
  1083. * - `bigpk`: an auto-incremental primary key type, will be converted into "bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY"
  1084. * - `upk`: an unsigned auto-incremental primary key type, will be converted into "int(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY"
  1085. * - `char`: char type, will be converted into "char(1)"
  1086. * - `string`: string type, will be converted into "varchar(255)"
  1087. * - `text`: a long string type, will be converted into "text"
  1088. * - `smallint`: a small integer type, will be converted into "smallint(6)"
  1089. * - `integer`: integer type, will be converted into "int(11)"
  1090. * - `bigint`: a big integer type, will be converted into "bigint(20)"
  1091. * - `boolean`: boolean type, will be converted into "tinyint(1)"
  1092. * - `float``: float number type, will be converted into "float"
  1093. * - `decimal`: decimal number type, will be converted into "decimal"
  1094. * - `datetime`: datetime type, will be converted into "datetime"
  1095. * - `timestamp`: timestamp type, will be converted into "timestamp"
  1096. * - `time`: time type, will be converted into "time"
  1097. * - `date`: date type, will be converted into "date"
  1098. * - `money`: money type, will be converted into "decimal(19,4)"
  1099. * - `binary`: binary data type, will be converted into "blob"
  1100. *
  1101. * If the abstract type contains two or more parts separated by spaces (e.g. "string NOT NULL"), then only
  1102. * the first part will be converted, and the rest of the parts will be appended to the converted result.
  1103. * For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'.
  1104. *
  1105. * For some of the abstract types you can also specify a length or precision constraint
  1106. * by appending it in round brackets directly to the type.
  1107. * For example `string(32)` will be converted into "varchar(32)" on a MySQL database.
  1108. * If the underlying DBMS does not support these kind of constraints for a type it will
  1109. * be ignored.
  1110. *
  1111. * If a type cannot be found in [[typeMap]], it will be returned without any change.
  1112. * @param string|ColumnSchemaBuilder $type abstract column type
  1113. * @return string physical column type.
  1114. */
  1115. public function getColumnType($type)
  1116. {
  1117. if ($type instanceof ColumnSchemaBuilder) {
  1118. $type = $type->__toString();
  1119. }
  1120. if (isset($this->typeMap[$type])) {
  1121. return $this->typeMap[$type];
  1122. } elseif (preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches)) {
  1123. if (isset($this->typeMap[$matches[1]])) {
  1124. return preg_replace('/\(.+\)/', '(' . $matches[2] . ')', $this->typeMap[$matches[1]]) . $matches[3];
  1125. }
  1126. } elseif (preg_match('/^(\w+)\s+/', $type, $matches)) {
  1127. if (isset($this->typeMap[$matches[1]])) {
  1128. return preg_replace('/^\w+/', $this->typeMap[$matches[1]], $type);
  1129. }
  1130. }
  1131. return $type;
  1132. }
  1133. /**
  1134. * @param array $columns
  1135. * @param array $params the binding parameters to be populated
  1136. * @param bool $distinct
  1137. * @param string $selectOption
  1138. * @return string the SELECT clause built from [[Query::$select]].
  1139. */
  1140. public function buildSelect($columns, &$params, $distinct = false, $selectOption = null)
  1141. {
  1142. $select = $distinct ? 'SELECT DISTINCT' : 'SELECT';
  1143. if ($selectOption !== null) {
  1144. $select .= ' ' . $selectOption;
  1145. }
  1146. if (empty($columns)) {
  1147. return $select . ' *';
  1148. }
  1149. foreach ($columns as $i => $column) {
  1150. if ($column instanceof ExpressionInterface) {
  1151. if (is_int($i)) {
  1152. $columns[$i] = $this->buildExpression($column, $params);
  1153. } else {
  1154. $columns[$i] = $this->buildExpression($column, $params) . ' AS ' . $this->db->quoteColumnName($i);
  1155. }
  1156. } elseif ($column instanceof Query) {
  1157. list($sql, $params) = $this->build($column, $params);
  1158. $columns[$i] = "($sql) AS " . $this->db->quoteColumnName($i);
  1159. } elseif (is_string($i) && $i !== $column) {
  1160. if (strpos($column, '(') === false) {
  1161. $column = $this->db->quoteColumnName($column);
  1162. }
  1163. $columns[$i] = "$column AS " . $this->db->quoteColumnName($i);
  1164. } elseif (strpos($column, '(') === false) {
  1165. if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $column, $matches)) {
  1166. $columns[$i] = $this->db->quoteColumnName($matches[1]) . ' AS ' . $this->db->quoteColumnName($matches[2]);
  1167. } else {
  1168. $columns[$i] = $this->db->quoteColumnName($column);
  1169. }
  1170. }
  1171. }
  1172. return $select . ' ' . implode(', ', $columns);
  1173. }
  1174. /**
  1175. * @param array $tables
  1176. * @param array $params the binding parameters to be populated
  1177. * @return string the FROM clause built from [[Query::$from]].
  1178. */
  1179. public function buildFrom($tables, &$params)
  1180. {
  1181. if (empty($tables)) {
  1182. return '';
  1183. }
  1184. $tables = $this->quoteTableNames($tables, $params);
  1185. return 'FROM ' . implode(', ', $tables);
  1186. }
  1187. /**
  1188. * @param array $joins
  1189. * @param array $params the binding parameters to be populated
  1190. * @return string the JOIN clause built from [[Query::$join]].
  1191. * @throws Exception if the $joins parameter is not in proper format
  1192. */
  1193. public function buildJoin($joins, &$params)
  1194. {
  1195. if (empty($joins)) {
  1196. return '';
  1197. }
  1198. foreach ($joins as $i => $join) {
  1199. if (!is_array($join) || !isset($join[0], $join[1])) {
  1200. throw new Exception('A join clause must be specified as an array of join type, join table, and optionally join condition.');
  1201. }
  1202. // 0:join type, 1:join table, 2:on-condition (optional)
  1203. list($joinType, $table) = $join;
  1204. $tables = $this->quoteTableNames((array)$table, $params);
  1205. $table = reset($tables);
  1206. $joins[$i] = "$joinType $table";
  1207. if (isset($join[2])) {
  1208. $condition = $this->buildCondition($join[2], $params);
  1209. if ($condition !== '') {
  1210. $joins[$i] .= ' ON ' . $condition;
  1211. }
  1212. }
  1213. }
  1214. return implode($this->separator, $joins);
  1215. }
  1216. /**
  1217. * Quotes table names passed.
  1218. *
  1219. * @param array $tables
  1220. * @param array $params
  1221. * @return array
  1222. */
  1223. private function quoteTableNames($tables, &$params)
  1224. {
  1225. foreach ($tables as $i => $table) {
  1226. if ($table instanceof Query) {
  1227. list($sql, $params) = $this->build($table, $params);
  1228. $tables[$i] = "($sql) " . $this->db->quoteTableName($i);
  1229. } elseif (is_string($i)) {
  1230. if (strpos($table, '(') === false) {
  1231. $table = $this->db->quoteTableName($table);
  1232. }
  1233. $tables[$i] = "$table " . $this->db->quoteTableName($i);
  1234. } elseif (strpos($table, '(') === false) {
  1235. if ($tableWithAlias = $this->extractAlias($table)) { // with alias
  1236. $tables[$i] = $this->db->quoteTableName($tableWithAlias[1]) . ' ' . $this->db->quoteTableName($tableWithAlias[2]);
  1237. } else {
  1238. $tables[$i] = $this->db->quoteTableName($table);
  1239. }
  1240. }
  1241. }
  1242. return $tables;
  1243. }
  1244. /**
  1245. * @param string|array $condition
  1246. * @param array $params the binding parameters to be populated
  1247. * @return string the WHERE clause built from [[Query::$where]].
  1248. */
  1249. public function buildWhere($condition, &$params)
  1250. {
  1251. $where = $this->buildCondition($condition, $params);
  1252. return $where === '' ? '' : 'WHERE ' . $where;
  1253. }
  1254. /**
  1255. * @param array $columns
  1256. * @return string the GROUP BY clause
  1257. */
  1258. public function buildGroupBy($columns)
  1259. {
  1260. if (empty($columns)) {
  1261. return '';
  1262. }
  1263. foreach ($columns as $i => $column) {
  1264. if ($column instanceof ExpressionInterface) {
  1265. $columns[$i] = $this->buildExpression($column);
  1266. } elseif (strpos($column, '(') === false) {
  1267. $columns[$i] = $this->db->quoteColumnName($column);
  1268. }
  1269. }
  1270. return 'GROUP BY ' . implode(', ', $columns);
  1271. }
  1272. /**
  1273. * @param string|array $condition
  1274. * @param array $params the binding parameters to be populated
  1275. * @return string the HAVING clause built from [[Query::$having]].
  1276. */
  1277. public function buildHaving($condition, &$params)
  1278. {
  1279. $having = $this->buildCondition($condition, $params);
  1280. return $having === '' ? '' : 'HAVING ' . $having;
  1281. }
  1282. /**
  1283. * Builds the ORDER BY and LIMIT/OFFSET clauses and appends them to the given SQL.
  1284. * @param string $sql the existing SQL (without ORDER BY/LIMIT/OFFSET)
  1285. * @param array $orderBy the order by columns. See [[Query::orderBy]] for more details on how to specify this parameter.
  1286. * @param int $limit the limit number. See [[Query::limit]] for more details.
  1287. * @param int $offset the offset number. See [[Query::offset]] for more details.
  1288. * @return string the SQL completed with ORDER BY/LIMIT/OFFSET (if any)
  1289. */
  1290. public function buildOrderByAndLimit($sql, $orderBy, $limit, $offset)
  1291. {
  1292. $orderBy = $this->buildOrderBy($orderBy);
  1293. if ($orderBy !== '') {
  1294. $sql .= $this->separator . $orderBy;
  1295. }
  1296. $limit = $this->buildLimit($limit, $offset);
  1297. if ($limit !== '') {
  1298. $sql .= $this->separator . $limit;
  1299. }
  1300. return $sql;
  1301. }
  1302. /**
  1303. * @param array $columns
  1304. * @return string the ORDER BY clause built from [[Query::$orderBy]].
  1305. */
  1306. public function buildOrderBy($columns)
  1307. {
  1308. if (empty($columns)) {
  1309. return '';
  1310. }
  1311. $orders = [];
  1312. foreach ($columns as $name => $direction) {
  1313. if ($direction instanceof ExpressionInterface) {
  1314. $orders[] = $this->buildExpression($direction);
  1315. } else {
  1316. $orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : '');
  1317. }
  1318. }
  1319. return 'ORDER BY ' . implode(', ', $orders);
  1320. }
  1321. /**
  1322. * @param int $limit
  1323. * @param int $offset
  1324. * @return string the LIMIT and OFFSET clauses
  1325. */
  1326. public function buildLimit($limit, $offset)
  1327. {
  1328. $sql = '';
  1329. if ($this->hasLimit($limit)) {
  1330. $sql = 'LIMIT ' . $limit;
  1331. }
  1332. if ($this->hasOffset($offset)) {
  1333. $sql .= ' OFFSET ' . $offset;
  1334. }
  1335. return ltrim($sql);
  1336. }
  1337. /**
  1338. * Checks to see if the given limit is effective.
  1339. * @param mixed $limit the given limit
  1340. * @return bool whether the limit is effective
  1341. */
  1342. protected function hasLimit($limit)
  1343. {
  1344. return ($limit instanceof ExpressionInterface) || ctype_digit((string)$limit);
  1345. }
  1346. /**
  1347. * Checks to see if the given offset is effective.
  1348. * @param mixed $offset the given offset
  1349. * @return bool whether the offset is effective
  1350. */
  1351. protected function hasOffset($offset)
  1352. {
  1353. return ($offset instanceof ExpressionInterface) || ctype_digit((string)$offset) && (string)$offset !== '0';
  1354. }
  1355. /**
  1356. * @param array $unions
  1357. * @param array $params the binding parameters to be populated
  1358. * @return string the UNION clause built from [[Query::$union]].
  1359. */
  1360. public function buildUnion($unions, &$params)
  1361. {
  1362. if (empty($unions)) {
  1363. return '';
  1364. }
  1365. $result = '';
  1366. foreach ($unions as $i => $union) {
  1367. $query = $union['query'];
  1368. if ($query instanceof Query) {
  1369. list($unions[$i]['query'], $params) = $this->build($query, $params);
  1370. }
  1371. $result .= 'UNION ' . ($union['all'] ? 'ALL ' : '') . '( ' . $unions[$i]['query'] . ' ) ';
  1372. }
  1373. return trim($result);
  1374. }
  1375. /**
  1376. * Processes columns and properly quotes them if necessary.
  1377. * It will join all columns into a string with comma as separators.
  1378. * @param string|array $columns the columns to be processed
  1379. * @return string the processing result
  1380. */
  1381. public function buildColumns($columns)
  1382. {
  1383. if (!is_array($columns)) {
  1384. if (strpos($columns, '(') !== false) {
  1385. return $columns;
  1386. }
  1387. $rawColumns = $columns;
  1388. $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
  1389. if ($columns === false) {
  1390. throw new InvalidArgumentException("$rawColumns is not valid columns.");
  1391. }
  1392. }
  1393. foreach ($columns as $i => $column) {
  1394. if ($column instanceof ExpressionInterface) {
  1395. $columns[$i] = $this->buildExpression($column);
  1396. } elseif (strpos($column, '(') === false) {
  1397. $columns[$i] = $this->db->quoteColumnName($column);
  1398. }
  1399. }
  1400. return implode(', ', $columns);
  1401. }
  1402. /**
  1403. * Parses the condition specification and generates the corresponding SQL expression.
  1404. * @param string|array|ExpressionInterface $condition the condition specification. Please refer to [[Query::where()]]
  1405. * on how to specify a condition.
  1406. * @param array $params the binding parameters to be populated
  1407. * @return string the generated SQL expression
  1408. */
  1409. public function buildCondition($condition, &$params)
  1410. {
  1411. if (is_array($condition)) {
  1412. if (empty($condition)) {
  1413. return '';
  1414. }
  1415. $condition = $this->createConditionFromArray($condition);
  1416. }
  1417. if ($condition instanceof ExpressionInterface) {
  1418. return $this->buildExpression($condition, $params);
  1419. }
  1420. return (string)$condition;
  1421. }
  1422. /**
  1423. * Transforms $condition defined in array format (as described in [[Query::where()]]
  1424. * to instance of [[yii\db\condition\ConditionInterface|ConditionInterface]] according to
  1425. * [[conditionClasses]] map.
  1426. *
  1427. * @param string|array $condition
  1428. * @return ConditionInterface
  1429. * @see conditionClasses
  1430. * @since 2.0.14
  1431. */
  1432. public function createConditionFromArray($condition)
  1433. {
  1434. if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
  1435. $operator = strtoupper(array_shift($condition));
  1436. if (isset($this->conditionClasses[$operator])) {
  1437. $className = $this->conditionClasses[$operator];
  1438. } else {
  1439. $className = 'yii\db\conditions\SimpleCondition';
  1440. }
  1441. /** @var ConditionInterface $className */
  1442. return $className::fromArrayDefinition($operator, $condition);
  1443. }
  1444. // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
  1445. return new HashCondition($condition);
  1446. }
  1447. /**
  1448. * Creates a condition based on column-value pairs.
  1449. * @param array $condition the condition specification.
  1450. * @param array $params the binding parameters to be populated
  1451. * @return string the generated SQL expression
  1452. * @deprecated since 2.0.14. Use `buildCondition()` instead.
  1453. */
  1454. public function buildHashCondition($condition, &$params)
  1455. {
  1456. return $this->buildCondition(new HashCondition($condition), $params);
  1457. }
  1458. /**
  1459. * Connects two or more SQL expressions with the `AND` or `OR` operator.
  1460. * @param string $operator the operator to use for connecting the given operands
  1461. * @param array $operands the SQL expressions to connect.
  1462. * @param array $params the binding parameters to be populated
  1463. * @return string the generated SQL expression
  1464. * @deprecated since 2.0.14. Use `buildCondition()` instead.
  1465. */
  1466. public function buildAndCondition($operator, $operands, &$params)
  1467. {
  1468. array_unshift($operands, $operator);
  1469. return $this->buildCondition($operands, $params);
  1470. }
  1471. /**
  1472. * Inverts an SQL expressions with `NOT` operator.
  1473. * @param string $operator the operator to use for connecting the given operands
  1474. * @param array $operands the SQL expressions to connect.
  1475. * @param array $params the binding parameters to be populated
  1476. * @return string the generated SQL expression
  1477. * @throws InvalidArgumentException if wrong number of operands have been given.
  1478. * @deprecated since 2.0.14. Use `buildCondition()` instead.
  1479. */
  1480. public function buildNotCondition($operator, $operands, &$params)
  1481. {
  1482. array_unshift($operands, $operator);
  1483. return $this->buildCondition($operands, $params);
  1484. }
  1485. /**
  1486. * Creates an SQL expressions with the `BETWEEN` operator.
  1487. * @param string $operator the operator to use (e.g. `BETWEEN` or `NOT BETWEEN`)
  1488. * @param array $operands the first operand is the column name. The second and third operands
  1489. * describe the interval that column value should be in.
  1490. * @param array $params the binding parameters to be populated
  1491. * @return string the generated SQL expression
  1492. * @throws InvalidArgumentException if wrong number of operands have been given.
  1493. * @deprecated since 2.0.14. Use `buildCondition()` instead.
  1494. */
  1495. public function buildBetweenCondition($operator, $operands, &$params)
  1496. {
  1497. array_unshift($operands, $operator);
  1498. return $this->buildCondition($operands, $params);
  1499. }
  1500. /**
  1501. * Creates an SQL expressions with the `IN` operator.
  1502. * @param string $operator the operator to use (e.g. `IN` or `NOT IN`)
  1503. * @param array $operands the first operand is the column name. If it is an array
  1504. * a composite IN condition will be generated.
  1505. * The second operand is an array of values that column value should be among.
  1506. * If it is an empty array the generated expression will be a `false` value if
  1507. * operator is `IN` and empty if operator is `NOT IN`.
  1508. * @param array $params the binding parameters to be populated
  1509. * @return string the generated SQL expression
  1510. * @throws Exception if wrong number of operands have been given.
  1511. * @deprecated since 2.0.14. Use `buildCondition()` instead.
  1512. */
  1513. public function buildInCondition($operator, $operands, &$params)
  1514. {
  1515. array_unshift($operands, $operator);
  1516. return $this->buildCondition($operands, $params);
  1517. }
  1518. /**
  1519. * Creates an SQL expressions with the `LIKE` operator.
  1520. * @param string $operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`)
  1521. * @param array $operands an array of two or three operands
  1522. *
  1523. * - The first operand is the column name.
  1524. * - The second operand is a single value or an array of values that column value
  1525. * should be compared with. If it is an empty array the generated expression will
  1526. * be a `false` value if operator is `LIKE` or `OR LIKE`, and empty if operator
  1527. * is `NOT LIKE` or `OR NOT LIKE`.
  1528. * - An optional third operand can also be provided to specify how to escape special characters
  1529. * in the value(s). The operand should be an array of mappings from the special characters to their
  1530. * escaped counterparts. If this operand is not provided, a default escape mapping will be used.
  1531. * You may use `false` or an empty array to indicate the values are already escaped and no escape
  1532. * should be applied. Note that when using an escape mapping (or the third operand is not provided),
  1533. * the values will be automatically enclosed within a pair of percentage characters.
  1534. * @param array $params the binding parameters to be populated
  1535. * @return string the generated SQL expression
  1536. * @throws InvalidArgumentException if wrong number of operands have been given.
  1537. * @deprecated since 2.0.14. Use `buildCondition()` instead.
  1538. */
  1539. public function buildLikeCondition($operator, $operands, &$params)
  1540. {
  1541. array_unshift($operands, $operator);
  1542. return $this->buildCondition($operands, $params);
  1543. }
  1544. /**
  1545. * Creates an SQL expressions with the `EXISTS` operator.
  1546. * @param string $operator the operator to use (e.g. `EXISTS` or `NOT EXISTS`)
  1547. * @param array $operands contains only one element which is a [[Query]] object representing the sub-query.
  1548. * @param array $params the binding parameters to be populated
  1549. * @return string the generated SQL expression
  1550. * @throws InvalidArgumentException if the operand is not a [[Query]] object.
  1551. * @deprecated since 2.0.14. Use `buildCondition()` instead.
  1552. */
  1553. public function buildExistsCondition($operator, $operands, &$params)
  1554. {
  1555. array_unshift($operands, $operator);
  1556. return $this->buildCondition($operands, $params);
  1557. }
  1558. /**
  1559. * Creates an SQL expressions like `"column" operator value`.
  1560. * @param string $operator the operator to use. Anything could be used e.g. `>`, `<=`, etc.
  1561. * @param array $operands contains two column names.
  1562. * @param array $params the binding parameters to be populated
  1563. * @return string the generated SQL expression
  1564. * @throws InvalidArgumentException if wrong number of operands have been given.
  1565. * @deprecated since 2.0.14. Use `buildCondition()` instead.
  1566. */
  1567. public function buildSimpleCondition($operator, $operands, &$params)
  1568. {
  1569. array_unshift($operands, $operator);
  1570. return $this->buildCondition($operands, $params);
  1571. }
  1572. /**
  1573. * Creates a SELECT EXISTS() SQL statement.
  1574. * @param string $rawSql the subquery in a raw form to select from.
  1575. * @return string the SELECT EXISTS() SQL statement.
  1576. * @since 2.0.8
  1577. */
  1578. public function selectExists($rawSql)
  1579. {
  1580. return 'SELECT EXISTS(' . $rawSql . ')';
  1581. }
  1582. /**
  1583. * Helper method to add $value to $params array using [[PARAM_PREFIX]].
  1584. *
  1585. * @param string|null $value
  1586. * @param array $params passed by reference
  1587. * @return string the placeholder name in $params array
  1588. *
  1589. * @since 2.0.14
  1590. */
  1591. public function bindParam($value, &$params)
  1592. {
  1593. $phName = self::PARAM_PREFIX . count($params);
  1594. $params[$phName] = $value;
  1595. return $phName;
  1596. }
  1597. /**
  1598. * Extracts table alias if there is one or returns false
  1599. * @param $table
  1600. * @return bool|array
  1601. * @since 2.0.24
  1602. */
  1603. protected function extractAlias($table)
  1604. {
  1605. if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) {
  1606. return $matches;
  1607. }
  1608. return false;
  1609. }
  1610. }