QueryBuilder.php 74 KB

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