You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1295 lines
51KB

  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\InvalidParamException;
  9. use yii\base\NotSupportedException;
  10. /**
  11. * QueryBuilder builds a SELECT SQL statement based on the specification given as a [[Query]] object.
  12. *
  13. * QueryBuilder can also be used to build SQL statements such as INSERT, UPDATE, DELETE, CREATE TABLE,
  14. * from a [[Query]] object.
  15. *
  16. * @author Qiang Xue <qiang.xue@gmail.com>
  17. * @since 2.0
  18. */
  19. class QueryBuilder extends \yii\base\Object
  20. {
  21. /**
  22. * The prefix for automatically generated query binding parameters.
  23. */
  24. const PARAM_PREFIX = ':qp';
  25. /**
  26. * @var Connection the database connection.
  27. */
  28. public $db;
  29. /**
  30. * @var string the separator between different fragments of a SQL statement.
  31. * Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement.
  32. */
  33. public $separator = " ";
  34. /**
  35. * @var array the abstract column types mapped to physical column types.
  36. * This is mainly used to support creating/modifying tables using DB-independent data type specifications.
  37. * Child classes should override this property to declare supported type mappings.
  38. */
  39. public $typeMap = [];
  40. /**
  41. * @var array map of query condition to builder methods.
  42. * These methods are used by [[buildCondition]] to build SQL conditions from array syntax.
  43. */
  44. protected $conditionBuilders = [
  45. 'NOT' => 'buildNotCondition',
  46. 'AND' => 'buildAndCondition',
  47. 'OR' => 'buildAndCondition',
  48. 'BETWEEN' => 'buildBetweenCondition',
  49. 'NOT BETWEEN' => 'buildBetweenCondition',
  50. 'IN' => 'buildInCondition',
  51. 'NOT IN' => 'buildInCondition',
  52. 'LIKE' => 'buildLikeCondition',
  53. 'NOT LIKE' => 'buildLikeCondition',
  54. 'OR LIKE' => 'buildLikeCondition',
  55. 'OR NOT LIKE' => 'buildLikeCondition',
  56. 'EXISTS' => 'buildExistsCondition',
  57. 'NOT EXISTS' => 'buildExistsCondition',
  58. ];
  59. /**
  60. * Constructor.
  61. * @param Connection $connection the database connection.
  62. * @param array $config name-value pairs that will be used to initialize the object properties
  63. */
  64. public function __construct($connection, $config = [])
  65. {
  66. $this->db = $connection;
  67. parent::__construct($config);
  68. }
  69. /**
  70. * Generates a SELECT SQL statement from a [[Query]] object.
  71. * @param Query $query the [[Query]] object from which the SQL statement will be generated.
  72. * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
  73. * be included in the result with the additional parameters generated during the query building process.
  74. * @return array the generated SQL statement (the first array element) and the corresponding
  75. * parameters to be bound to the SQL statement (the second array element). The parameters returned
  76. * include those provided in `$params`.
  77. */
  78. public function build($query, $params = [])
  79. {
  80. $query = $query->prepare($this);
  81. $params = empty($params) ? $query->params : array_merge($params, $query->params);
  82. $clauses = [
  83. $this->buildSelect($query->select, $params, $query->distinct, $query->selectOption),
  84. $this->buildFrom($query->from, $params),
  85. $this->buildJoin($query->join, $params),
  86. $this->buildWhere($query->where, $params),
  87. $this->buildGroupBy($query->groupBy),
  88. $this->buildHaving($query->having, $params),
  89. ];
  90. $sql = implode($this->separator, array_filter($clauses));
  91. $sql = $this->buildOrderByAndLimit($sql, $query->orderBy, $query->limit, $query->offset);
  92. $union = $this->buildUnion($query->union, $params);
  93. if ($union !== '') {
  94. $sql = "($sql){$this->separator}$union";
  95. }
  96. return [$sql, $params];
  97. }
  98. /**
  99. * Creates an INSERT SQL statement.
  100. * For example,
  101. *
  102. * ~~~
  103. * $sql = $queryBuilder->insert('user', [
  104. * 'name' => 'Sam',
  105. * 'age' => 30,
  106. * ], $params);
  107. * ~~~
  108. *
  109. * The method will properly escape the table and column names.
  110. *
  111. * @param string $table the table that new rows will be inserted into.
  112. * @param array $columns the column data (name => value) to be inserted into the table.
  113. * @param array $params the binding parameters that will be generated by this method.
  114. * They should be bound to the DB command later.
  115. * @return string the INSERT SQL
  116. */
  117. public function insert($table, $columns, &$params)
  118. {
  119. $schema = $this->db->getSchema();
  120. if (($tableSchema = $schema->getTableSchema($table)) !== null) {
  121. $columnSchemas = $tableSchema->columns;
  122. } else {
  123. $columnSchemas = [];
  124. }
  125. $names = [];
  126. $placeholders = [];
  127. foreach ($columns as $name => $value) {
  128. $names[] = $schema->quoteColumnName($name);
  129. if ($value instanceof Expression) {
  130. $placeholders[] = $value->expression;
  131. foreach ($value->params as $n => $v) {
  132. $params[$n] = $v;
  133. }
  134. } else {
  135. $phName = self::PARAM_PREFIX . count($params);
  136. $placeholders[] = $phName;
  137. $params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
  138. }
  139. }
  140. return 'INSERT INTO ' . $schema->quoteTableName($table)
  141. . ' (' . implode(', ', $names) . ') VALUES ('
  142. . implode(', ', $placeholders) . ')';
  143. }
  144. /**
  145. * Generates a batch INSERT SQL statement.
  146. * For example,
  147. *
  148. * ~~~
  149. * $sql = $queryBuilder->batchInsert('user', ['name', 'age'], [
  150. * ['Tom', 30],
  151. * ['Jane', 20],
  152. * ['Linda', 25],
  153. * ]);
  154. * ~~~
  155. *
  156. * Note that the values in each row must match the corresponding column names.
  157. *
  158. * @param string $table the table that new rows will be inserted into.
  159. * @param array $columns the column names
  160. * @param array $rows the rows to be batch inserted into the table
  161. * @return string the batch INSERT SQL statement
  162. */
  163. public function batchInsert($table, $columns, $rows)
  164. {
  165. $schema = $this->db->getSchema();
  166. if (($tableSchema = $schema->getTableSchema($table)) !== null) {
  167. $columnSchemas = $tableSchema->columns;
  168. } else {
  169. $columnSchemas = [];
  170. }
  171. $values = [];
  172. foreach ($rows as $row) {
  173. $vs = [];
  174. foreach ($row as $i => $value) {
  175. if (isset($columns[$i], $columnSchemas[$columns[$i]]) && !is_array($value)) {
  176. $value = $columnSchemas[$columns[$i]]->dbTypecast($value);
  177. }
  178. if (is_string($value)) {
  179. $value = $schema->quoteValue($value);
  180. } elseif ($value === false) {
  181. $value = 0;
  182. } elseif ($value === null) {
  183. $value = 'NULL';
  184. }
  185. $vs[] = $value;
  186. }
  187. $values[] = '(' . implode(', ', $vs) . ')';
  188. }
  189. foreach ($columns as $i => $name) {
  190. $columns[$i] = $schema->quoteColumnName($name);
  191. }
  192. return 'INSERT INTO ' . $schema->quoteTableName($table)
  193. . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
  194. }
  195. /**
  196. * Creates an UPDATE SQL statement.
  197. * For example,
  198. *
  199. * ~~~
  200. * $params = [];
  201. * $sql = $queryBuilder->update('user', ['status' => 1], 'age > 30', $params);
  202. * ~~~
  203. *
  204. * The method will properly escape the table and column names.
  205. *
  206. * @param string $table the table to be updated.
  207. * @param array $columns the column data (name => value) to be updated.
  208. * @param array|string $condition the condition that will be put in the WHERE part. Please
  209. * refer to [[Query::where()]] on how to specify condition.
  210. * @param array $params the binding parameters that will be modified by this method
  211. * so that they can be bound to the DB command later.
  212. * @return string the UPDATE SQL
  213. */
  214. public function update($table, $columns, $condition, &$params)
  215. {
  216. if (($tableSchema = $this->db->getTableSchema($table)) !== null) {
  217. $columnSchemas = $tableSchema->columns;
  218. } else {
  219. $columnSchemas = [];
  220. }
  221. $lines = [];
  222. foreach ($columns as $name => $value) {
  223. if ($value instanceof Expression) {
  224. $lines[] = $this->db->quoteColumnName($name) . '=' . $value->expression;
  225. foreach ($value->params as $n => $v) {
  226. $params[$n] = $v;
  227. }
  228. } else {
  229. $phName = self::PARAM_PREFIX . count($params);
  230. $lines[] = $this->db->quoteColumnName($name) . '=' . $phName;
  231. $params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
  232. }
  233. }
  234. $sql = 'UPDATE ' . $this->db->quoteTableName($table) . ' SET ' . implode(', ', $lines);
  235. $where = $this->buildWhere($condition, $params);
  236. return $where === '' ? $sql : $sql . ' ' . $where;
  237. }
  238. /**
  239. * Creates a DELETE SQL statement.
  240. * For example,
  241. *
  242. * ~~~
  243. * $sql = $queryBuilder->delete('user', 'status = 0');
  244. * ~~~
  245. *
  246. * The method will properly escape the table and column names.
  247. *
  248. * @param string $table the table where the data will be deleted from.
  249. * @param array|string $condition the condition that will be put in the WHERE part. Please
  250. * refer to [[Query::where()]] on how to specify condition.
  251. * @param array $params the binding parameters that will be modified by this method
  252. * so that they can be bound to the DB command later.
  253. * @return string the DELETE SQL
  254. */
  255. public function delete($table, $condition, &$params)
  256. {
  257. $sql = 'DELETE FROM ' . $this->db->quoteTableName($table);
  258. $where = $this->buildWhere($condition, $params);
  259. return $where === '' ? $sql : $sql . ' ' . $where;
  260. }
  261. /**
  262. * Builds a SQL statement for creating a new DB table.
  263. *
  264. * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'),
  265. * where name stands for a column name which will be properly quoted by the method, and definition
  266. * stands for the column type which can contain an abstract DB type.
  267. * The [[getColumnType()]] method will be invoked to convert any abstract type into a physical one.
  268. *
  269. * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
  270. * inserted into the generated SQL.
  271. *
  272. * For example,
  273. *
  274. * ~~~
  275. * $sql = $queryBuilder->createTable('user', [
  276. * 'id' => 'pk',
  277. * 'name' => 'string',
  278. * 'age' => 'integer',
  279. * ]);
  280. * ~~~
  281. *
  282. * @param string $table the name of the table to be created. The name will be properly quoted by the method.
  283. * @param array $columns the columns (name => definition) in the new table.
  284. * @param string $options additional SQL fragment that will be appended to the generated SQL.
  285. * @return string the SQL statement for creating a new DB table.
  286. */
  287. public function createTable($table, $columns, $options = null)
  288. {
  289. $cols = [];
  290. foreach ($columns as $name => $type) {
  291. if (is_string($name)) {
  292. $cols[] = "\t" . $this->db->quoteColumnName($name) . ' ' . $this->getColumnType($type);
  293. } else {
  294. $cols[] = "\t" . $type;
  295. }
  296. }
  297. $sql = "CREATE TABLE " . $this->db->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)";
  298. return $options === null ? $sql : $sql . ' ' . $options;
  299. }
  300. /**
  301. * Builds a SQL statement for renaming a DB table.
  302. * @param string $oldName the table to be renamed. The name will be properly quoted by the method.
  303. * @param string $newName the new table name. The name will be properly quoted by the method.
  304. * @return string the SQL statement for renaming a DB table.
  305. */
  306. public function renameTable($oldName, $newName)
  307. {
  308. return 'RENAME TABLE ' . $this->db->quoteTableName($oldName) . ' TO ' . $this->db->quoteTableName($newName);
  309. }
  310. /**
  311. * Builds a SQL statement for dropping a DB table.
  312. * @param string $table the table to be dropped. The name will be properly quoted by the method.
  313. * @return string the SQL statement for dropping a DB table.
  314. */
  315. public function dropTable($table)
  316. {
  317. return "DROP TABLE " . $this->db->quoteTableName($table);
  318. }
  319. /**
  320. * Builds a SQL statement for adding a primary key constraint to an existing table.
  321. * @param string $name the name of the primary key constraint.
  322. * @param string $table the table that the primary key constraint will be added to.
  323. * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
  324. * @return string the SQL statement for adding a primary key constraint to an existing table.
  325. */
  326. public function addPrimaryKey($name, $table, $columns)
  327. {
  328. if (is_string($columns)) {
  329. $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
  330. }
  331. foreach ($columns as $i => $col) {
  332. $columns[$i] = $this->db->quoteColumnName($col);
  333. }
  334. return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
  335. . $this->db->quoteColumnName($name) . ' PRIMARY KEY ('
  336. . implode(', ', $columns). ' )';
  337. }
  338. /**
  339. * Builds a SQL statement for removing a primary key constraint to an existing table.
  340. * @param string $name the name of the primary key constraint to be removed.
  341. * @param string $table the table that the primary key constraint will be removed from.
  342. * @return string the SQL statement for removing a primary key constraint from an existing table.
  343. */
  344. public function dropPrimaryKey($name, $table)
  345. {
  346. return 'ALTER TABLE ' . $this->db->quoteTableName($table)
  347. . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
  348. }
  349. /**
  350. * Builds a SQL statement for truncating a DB table.
  351. * @param string $table the table to be truncated. The name will be properly quoted by the method.
  352. * @return string the SQL statement for truncating a DB table.
  353. */
  354. public function truncateTable($table)
  355. {
  356. return "TRUNCATE TABLE " . $this->db->quoteTableName($table);
  357. }
  358. /**
  359. * Builds a SQL statement for adding a new DB column.
  360. * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
  361. * @param string $column the name of the new column. The name will be properly quoted by the method.
  362. * @param string $type the column type. The [[getColumnType()]] method will be invoked to convert abstract column type (if any)
  363. * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
  364. * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
  365. * @return string the SQL statement for adding a new column.
  366. */
  367. public function addColumn($table, $column, $type)
  368. {
  369. return 'ALTER TABLE ' . $this->db->quoteTableName($table)
  370. . ' ADD ' . $this->db->quoteColumnName($column) . ' '
  371. . $this->getColumnType($type);
  372. }
  373. /**
  374. * Builds a SQL statement for dropping a DB column.
  375. * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
  376. * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
  377. * @return string the SQL statement for dropping a DB column.
  378. */
  379. public function dropColumn($table, $column)
  380. {
  381. return "ALTER TABLE " . $this->db->quoteTableName($table)
  382. . " DROP COLUMN " . $this->db->quoteColumnName($column);
  383. }
  384. /**
  385. * Builds a SQL statement for renaming a column.
  386. * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
  387. * @param string $oldName the old name of the column. The name will be properly quoted by the method.
  388. * @param string $newName the new name of the column. The name will be properly quoted by the method.
  389. * @return string the SQL statement for renaming a DB column.
  390. */
  391. public function renameColumn($table, $oldName, $newName)
  392. {
  393. return "ALTER TABLE " . $this->db->quoteTableName($table)
  394. . " RENAME COLUMN " . $this->db->quoteColumnName($oldName)
  395. . " TO " . $this->db->quoteColumnName($newName);
  396. }
  397. /**
  398. * Builds a SQL statement for changing the definition of a column.
  399. * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
  400. * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
  401. * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
  402. * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
  403. * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
  404. * will become 'varchar(255) not null'.
  405. * @return string the SQL statement for changing the definition of a column.
  406. */
  407. public function alterColumn($table, $column, $type)
  408. {
  409. return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' CHANGE '
  410. . $this->db->quoteColumnName($column) . ' '
  411. . $this->db->quoteColumnName($column) . ' '
  412. . $this->getColumnType($type);
  413. }
  414. /**
  415. * Builds a SQL statement for adding a foreign key constraint to an existing table.
  416. * The method will properly quote the table and column names.
  417. * @param string $name the name of the foreign key constraint.
  418. * @param string $table the table that the foreign key constraint will be added to.
  419. * @param string|array $columns the name of the column to that the constraint will be added on.
  420. * If there are multiple columns, separate them with commas or use an array to represent them.
  421. * @param string $refTable the table that the foreign key references to.
  422. * @param string|array $refColumns the name of the column that the foreign key references to.
  423. * If there are multiple columns, separate them with commas or use an array to represent them.
  424. * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
  425. * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
  426. * @return string the SQL statement for adding a foreign key constraint to an existing table.
  427. */
  428. public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
  429. {
  430. $sql = 'ALTER TABLE ' . $this->db->quoteTableName($table)
  431. . ' ADD CONSTRAINT ' . $this->db->quoteColumnName($name)
  432. . ' FOREIGN KEY (' . $this->buildColumns($columns) . ')'
  433. . ' REFERENCES ' . $this->db->quoteTableName($refTable)
  434. . ' (' . $this->buildColumns($refColumns) . ')';
  435. if ($delete !== null) {
  436. $sql .= ' ON DELETE ' . $delete;
  437. }
  438. if ($update !== null) {
  439. $sql .= ' ON UPDATE ' . $update;
  440. }
  441. return $sql;
  442. }
  443. /**
  444. * Builds a SQL statement for dropping a foreign key constraint.
  445. * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
  446. * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
  447. * @return string the SQL statement for dropping a foreign key constraint.
  448. */
  449. public function dropForeignKey($name, $table)
  450. {
  451. return 'ALTER TABLE ' . $this->db->quoteTableName($table)
  452. . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
  453. }
  454. /**
  455. * Builds a SQL statement for creating a new index.
  456. * @param string $name the name of the index. The name will be properly quoted by the method.
  457. * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
  458. * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns,
  459. * separate them with commas or use an array to represent them. Each column name will be properly quoted
  460. * by the method, unless a parenthesis is found in the name.
  461. * @param boolean $unique whether to add UNIQUE constraint on the created index.
  462. * @return string the SQL statement for creating a new index.
  463. */
  464. public function createIndex($name, $table, $columns, $unique = false)
  465. {
  466. return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ')
  467. . $this->db->quoteTableName($name) . ' ON '
  468. . $this->db->quoteTableName($table)
  469. . ' (' . $this->buildColumns($columns) . ')';
  470. }
  471. /**
  472. * Builds a SQL statement for dropping an index.
  473. * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
  474. * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
  475. * @return string the SQL statement for dropping an index.
  476. */
  477. public function dropIndex($name, $table)
  478. {
  479. return 'DROP INDEX ' . $this->db->quoteTableName($name) . ' ON ' . $this->db->quoteTableName($table);
  480. }
  481. /**
  482. * Creates a SQL statement for resetting the sequence value of a table's primary key.
  483. * The sequence will be reset such that the primary key of the next new row inserted
  484. * will have the specified value or 1.
  485. * @param string $table the name of the table whose primary key sequence will be reset
  486. * @param array|string $value the value for the primary key of the next new row inserted. If this is not set,
  487. * the next new row's primary key will have a value 1.
  488. * @return string the SQL statement for resetting sequence
  489. * @throws NotSupportedException if this is not supported by the underlying DBMS
  490. */
  491. public function resetSequence($table, $value = null)
  492. {
  493. throw new NotSupportedException($this->db->getDriverName() . ' does not support resetting sequence.');
  494. }
  495. /**
  496. * Builds a SQL statement for enabling or disabling integrity check.
  497. * @param boolean $check whether to turn on or off the integrity check.
  498. * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
  499. * @param string $table the table name. Defaults to empty string, meaning that no table will be changed.
  500. * @return string the SQL statement for checking integrity
  501. * @throws NotSupportedException if this is not supported by the underlying DBMS
  502. */
  503. public function checkIntegrity($check = true, $schema = '', $table = '')
  504. {
  505. throw new NotSupportedException($this->db->getDriverName() . ' does not support enabling/disabling integrity check.');
  506. }
  507. /**
  508. * Converts an abstract column type into a physical column type.
  509. * The conversion is done using the type map specified in [[typeMap]].
  510. * The following abstract column types are supported (using MySQL as an example to explain the corresponding
  511. * physical types):
  512. *
  513. * - `pk`: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY"
  514. * - `bigpk`: an auto-incremental primary key type, will be converted into "bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY"
  515. * - `string`: string type, will be converted into "varchar(255)"
  516. * - `text`: a long string type, will be converted into "text"
  517. * - `smallint`: a small integer type, will be converted into "smallint(6)"
  518. * - `integer`: integer type, will be converted into "int(11)"
  519. * - `bigint`: a big integer type, will be converted into "bigint(20)"
  520. * - `boolean`: boolean type, will be converted into "tinyint(1)"
  521. * - `float``: float number type, will be converted into "float"
  522. * - `decimal`: decimal number type, will be converted into "decimal"
  523. * - `datetime`: datetime type, will be converted into "datetime"
  524. * - `timestamp`: timestamp type, will be converted into "timestamp"
  525. * - `time`: time type, will be converted into "time"
  526. * - `date`: date type, will be converted into "date"
  527. * - `money`: money type, will be converted into "decimal(19,4)"
  528. * - `binary`: binary data type, will be converted into "blob"
  529. *
  530. * If the abstract type contains two or more parts separated by spaces (e.g. "string NOT NULL"), then only
  531. * the first part will be converted, and the rest of the parts will be appended to the converted result.
  532. * For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'.
  533. *
  534. * For some of the abstract types you can also specify a length or precision constraint
  535. * by appending it in round brackets directly to the type.
  536. * For example `string(32)` will be converted into "varchar(32)" on a MySQL database.
  537. * If the underlying DBMS does not support these kind of constraints for a type it will
  538. * be ignored.
  539. *
  540. * If a type cannot be found in [[typeMap]], it will be returned without any change.
  541. * @param string $type abstract column type
  542. * @return string physical column type.
  543. */
  544. public function getColumnType($type)
  545. {
  546. if (isset($this->typeMap[$type])) {
  547. return $this->typeMap[$type];
  548. } elseif (preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches)) {
  549. if (isset($this->typeMap[$matches[1]])) {
  550. return preg_replace('/\(.+\)/', '(' . $matches[2] . ')', $this->typeMap[$matches[1]]) . $matches[3];
  551. }
  552. } elseif (preg_match('/^(\w+)\s+/', $type, $matches)) {
  553. if (isset($this->typeMap[$matches[1]])) {
  554. return preg_replace('/^\w+/', $this->typeMap[$matches[1]], $type);
  555. }
  556. }
  557. return $type;
  558. }
  559. /**
  560. * @param array $columns
  561. * @param array $params the binding parameters to be populated
  562. * @param boolean $distinct
  563. * @param string $selectOption
  564. * @return string the SELECT clause built from [[Query::$select]].
  565. */
  566. public function buildSelect($columns, &$params, $distinct = false, $selectOption = null)
  567. {
  568. $select = $distinct ? 'SELECT DISTINCT' : 'SELECT';
  569. if ($selectOption !== null) {
  570. $select .= ' ' . $selectOption;
  571. }
  572. if (empty($columns)) {
  573. return $select . ' *';
  574. }
  575. foreach ($columns as $i => $column) {
  576. if ($column instanceof Expression) {
  577. if (is_int($i)) {
  578. $columns[$i] = $column->expression;
  579. } else {
  580. $columns[$i] = $column->expression . ' AS ' . $this->db->quoteColumnName($i);
  581. }
  582. $params = array_merge($params, $column->params);
  583. } elseif ($column instanceof Query) {
  584. list($sql, $params) = $this->build($column, $params);
  585. $columns[$i] = "($sql) AS " . $this->db->quoteColumnName($i);
  586. } elseif (is_string($i)) {
  587. if (strpos($column, '(') === false) {
  588. $column = $this->db->quoteColumnName($column);
  589. }
  590. $columns[$i] = "$column AS " . $this->db->quoteColumnName($i);
  591. } elseif (strpos($column, '(') === false) {
  592. if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $column, $matches)) {
  593. $columns[$i] = $this->db->quoteColumnName($matches[1]) . ' AS ' . $this->db->quoteColumnName($matches[2]);
  594. } else {
  595. $columns[$i] = $this->db->quoteColumnName($column);
  596. }
  597. }
  598. }
  599. return $select . ' ' . implode(', ', $columns);
  600. }
  601. /**
  602. * @param array $tables
  603. * @param array $params the binding parameters to be populated
  604. * @return string the FROM clause built from [[Query::$from]].
  605. */
  606. public function buildFrom($tables, &$params)
  607. {
  608. if (empty($tables)) {
  609. return '';
  610. }
  611. $tables = $this->quoteTableNames($tables, $params);
  612. return 'FROM ' . implode(', ', $tables);
  613. }
  614. /**
  615. * @param array $joins
  616. * @param array $params the binding parameters to be populated
  617. * @return string the JOIN clause built from [[Query::$join]].
  618. * @throws Exception if the $joins parameter is not in proper format
  619. */
  620. public function buildJoin($joins, &$params)
  621. {
  622. if (empty($joins)) {
  623. return '';
  624. }
  625. foreach ($joins as $i => $join) {
  626. if (!is_array($join) || !isset($join[0], $join[1])) {
  627. throw new Exception('A join clause must be specified as an array of join type, join table, and optionally join condition.');
  628. }
  629. // 0:join type, 1:join table, 2:on-condition (optional)
  630. list ($joinType, $table) = $join;
  631. $tables = $this->quoteTableNames((array) $table, $params);
  632. $table = reset($tables);
  633. $joins[$i] = "$joinType $table";
  634. if (isset($join[2])) {
  635. $condition = $this->buildCondition($join[2], $params);
  636. if ($condition !== '') {
  637. $joins[$i] .= ' ON ' . $condition;
  638. }
  639. }
  640. }
  641. return implode($this->separator, $joins);
  642. }
  643. /**
  644. * Quotes table names passed
  645. *
  646. * @param array $tables
  647. * @param array $params
  648. * @return array
  649. */
  650. private function quoteTableNames($tables, &$params)
  651. {
  652. foreach ($tables as $i => $table) {
  653. if ($table instanceof Query) {
  654. list($sql, $params) = $this->build($table, $params);
  655. $tables[$i] = "($sql) " . $this->db->quoteTableName($i);
  656. } elseif (is_string($i)) {
  657. if (strpos($table, '(') === false) {
  658. $table = $this->db->quoteTableName($table);
  659. }
  660. $tables[$i] = "$table " . $this->db->quoteTableName($i);
  661. } elseif (strpos($table, '(') === false) {
  662. if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias
  663. $tables[$i] = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]);
  664. } else {
  665. $tables[$i] = $this->db->quoteTableName($table);
  666. }
  667. }
  668. }
  669. return $tables;
  670. }
  671. /**
  672. * @param string|array $condition
  673. * @param array $params the binding parameters to be populated
  674. * @return string the WHERE clause built from [[Query::$where]].
  675. */
  676. public function buildWhere($condition, &$params)
  677. {
  678. $where = $this->buildCondition($condition, $params);
  679. return $where === '' ? '' : 'WHERE ' . $where;
  680. }
  681. /**
  682. * @param array $columns
  683. * @return string the GROUP BY clause
  684. */
  685. public function buildGroupBy($columns)
  686. {
  687. return empty($columns) ? '' : 'GROUP BY ' . $this->buildColumns($columns);
  688. }
  689. /**
  690. * @param string|array $condition
  691. * @param array $params the binding parameters to be populated
  692. * @return string the HAVING clause built from [[Query::$having]].
  693. */
  694. public function buildHaving($condition, &$params)
  695. {
  696. $having = $this->buildCondition($condition, $params);
  697. return $having === '' ? '' : 'HAVING ' . $having;
  698. }
  699. /**
  700. * Builds the ORDER BY and LIMIT/OFFSET clauses and appends them to the given SQL.
  701. * @param string $sql the existing SQL (without ORDER BY/LIMIT/OFFSET)
  702. * @param array $orderBy the order by columns. See [[Query::orderBy]] for more details on how to specify this parameter.
  703. * @param integer $limit the limit number. See [[Query::limit]] for more details.
  704. * @param integer $offset the offset number. See [[Query::offset]] for more details.
  705. * @return string the SQL completed with ORDER BY/LIMIT/OFFSET (if any)
  706. */
  707. public function buildOrderByAndLimit($sql, $orderBy, $limit, $offset)
  708. {
  709. $orderBy = $this->buildOrderBy($orderBy);
  710. if ($orderBy !== '') {
  711. $sql .= $this->separator . $orderBy;
  712. }
  713. $limit = $this->buildLimit($limit, $offset);
  714. if ($limit !== '') {
  715. $sql .= $this->separator . $limit;
  716. }
  717. return $sql;
  718. }
  719. /**
  720. * @param array $columns
  721. * @return string the ORDER BY clause built from [[Query::$orderBy]].
  722. */
  723. public function buildOrderBy($columns)
  724. {
  725. if (empty($columns)) {
  726. return '';
  727. }
  728. $orders = [];
  729. foreach ($columns as $name => $direction) {
  730. if ($direction instanceof Expression) {
  731. $orders[] = $direction->expression;
  732. } else {
  733. $orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : '');
  734. }
  735. }
  736. return 'ORDER BY ' . implode(', ', $orders);
  737. }
  738. /**
  739. * @param integer $limit
  740. * @param integer $offset
  741. * @return string the LIMIT and OFFSET clauses
  742. */
  743. public function buildLimit($limit, $offset)
  744. {
  745. $sql = '';
  746. if ($this->hasLimit($limit)) {
  747. $sql = 'LIMIT ' . $limit;
  748. }
  749. if ($this->hasOffset($offset)) {
  750. $sql .= ' OFFSET ' . $offset;
  751. }
  752. return ltrim($sql);
  753. }
  754. /**
  755. * Checks to see if the given limit is effective.
  756. * @param mixed $limit the given limit
  757. * @return boolean whether the limit is effective
  758. */
  759. protected function hasLimit($limit)
  760. {
  761. return is_string($limit) && ctype_digit($limit) || is_integer($limit) && $limit >= 0;
  762. }
  763. /**
  764. * Checks to see if the given offset is effective.
  765. * @param mixed $offset the given offset
  766. * @return boolean whether the offset is effective
  767. */
  768. protected function hasOffset($offset)
  769. {
  770. return is_integer($offset) && $offset > 0 || is_string($offset) && ctype_digit($offset) && $offset !== '0';
  771. }
  772. /**
  773. * @param array $unions
  774. * @param array $params the binding parameters to be populated
  775. * @return string the UNION clause built from [[Query::$union]].
  776. */
  777. public function buildUnion($unions, &$params)
  778. {
  779. if (empty($unions)) {
  780. return '';
  781. }
  782. $result = '';
  783. foreach ($unions as $i => $union) {
  784. $query = $union['query'];
  785. if ($query instanceof Query) {
  786. list($unions[$i]['query'], $params) = $this->build($query, $params);
  787. }
  788. $result .= 'UNION ' . ($union['all'] ? 'ALL ' : '') . '( ' . $unions[$i]['query'] . ' ) ';
  789. }
  790. return trim($result);
  791. }
  792. /**
  793. * Processes columns and properly quote them if necessary.
  794. * It will join all columns into a string with comma as separators.
  795. * @param string|array $columns the columns to be processed
  796. * @return string the processing result
  797. */
  798. public function buildColumns($columns)
  799. {
  800. if (!is_array($columns)) {
  801. if (strpos($columns, '(') !== false) {
  802. return $columns;
  803. } else {
  804. $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
  805. }
  806. }
  807. foreach ($columns as $i => $column) {
  808. if ($column instanceof Expression) {
  809. $columns[$i] = $column->expression;
  810. } elseif (strpos($column, '(') === false) {
  811. $columns[$i] = $this->db->quoteColumnName($column);
  812. }
  813. }
  814. return is_array($columns) ? implode(', ', $columns) : $columns;
  815. }
  816. /**
  817. * Parses the condition specification and generates the corresponding SQL expression.
  818. * @param string|array $condition the condition specification. Please refer to [[Query::where()]]
  819. * on how to specify a condition.
  820. * @param array $params the binding parameters to be populated
  821. * @return string the generated SQL expression
  822. */
  823. public function buildCondition($condition, &$params)
  824. {
  825. if (!is_array($condition)) {
  826. return (string) $condition;
  827. } elseif (empty($condition)) {
  828. return '';
  829. }
  830. if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
  831. $operator = strtoupper($condition[0]);
  832. if (isset($this->conditionBuilders[$operator])) {
  833. $method = $this->conditionBuilders[$operator];
  834. } else {
  835. $method = 'buildSimpleCondition';
  836. }
  837. array_shift($condition);
  838. return $this->$method($operator, $condition, $params);
  839. } else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
  840. return $this->buildHashCondition($condition, $params);
  841. }
  842. }
  843. /**
  844. * Creates a condition based on column-value pairs.
  845. * @param array $condition the condition specification.
  846. * @param array $params the binding parameters to be populated
  847. * @return string the generated SQL expression
  848. */
  849. public function buildHashCondition($condition, &$params)
  850. {
  851. $parts = [];
  852. foreach ($condition as $column => $value) {
  853. if (is_array($value) || $value instanceof Query) {
  854. // IN condition
  855. $parts[] = $this->buildInCondition('IN', [$column, $value], $params);
  856. } else {
  857. if (strpos($column, '(') === false) {
  858. $column = $this->db->quoteColumnName($column);
  859. }
  860. if ($value === null) {
  861. $parts[] = "$column IS NULL";
  862. } elseif ($value instanceof Expression) {
  863. $parts[] = "$column=" . $value->expression;
  864. foreach ($value->params as $n => $v) {
  865. $params[$n] = $v;
  866. }
  867. } else {
  868. $phName = self::PARAM_PREFIX . count($params);
  869. $parts[] = "$column=$phName";
  870. $params[$phName] = $value;
  871. }
  872. }
  873. }
  874. return count($parts) === 1 ? $parts[0] : '(' . implode(') AND (', $parts) . ')';
  875. }
  876. /**
  877. * Connects two or more SQL expressions with the `AND` or `OR` operator.
  878. * @param string $operator the operator to use for connecting the given operands
  879. * @param array $operands the SQL expressions to connect.
  880. * @param array $params the binding parameters to be populated
  881. * @return string the generated SQL expression
  882. */
  883. public function buildAndCondition($operator, $operands, &$params)
  884. {
  885. $parts = [];
  886. foreach ($operands as $operand) {
  887. if (is_array($operand)) {
  888. $operand = $this->buildCondition($operand, $params);
  889. }
  890. if ($operand !== '') {
  891. $parts[] = $operand;
  892. }
  893. }
  894. if (!empty($parts)) {
  895. return '(' . implode(") $operator (", $parts) . ')';
  896. } else {
  897. return '';
  898. }
  899. }
  900. /**
  901. * Inverts an SQL expressions with `NOT` operator.
  902. * @param string $operator the operator to use for connecting the given operands
  903. * @param array $operands the SQL expressions to connect.
  904. * @param array $params the binding parameters to be populated
  905. * @return string the generated SQL expression
  906. * @throws InvalidParamException if wrong number of operands have been given.
  907. */
  908. public function buildNotCondition($operator, $operands, &$params)
  909. {
  910. if (count($operands) != 1) {
  911. throw new InvalidParamException("Operator '$operator' requires exactly one operand.");
  912. }
  913. $operand = reset($operands);
  914. if (is_array($operand)) {
  915. $operand = $this->buildCondition($operand, $params);
  916. }
  917. if ($operand === '') {
  918. return '';
  919. }
  920. return "$operator ($operand)";
  921. }
  922. /**
  923. * Creates an SQL expressions with the `BETWEEN` operator.
  924. * @param string $operator the operator to use (e.g. `BETWEEN` or `NOT BETWEEN`)
  925. * @param array $operands the first operand is the column name. The second and third operands
  926. * describe the interval that column value should be in.
  927. * @param array $params the binding parameters to be populated
  928. * @return string the generated SQL expression
  929. * @throws InvalidParamException if wrong number of operands have been given.
  930. */
  931. public function buildBetweenCondition($operator, $operands, &$params)
  932. {
  933. if (!isset($operands[0], $operands[1], $operands[2])) {
  934. throw new InvalidParamException("Operator '$operator' requires three operands.");
  935. }
  936. list($column, $value1, $value2) = $operands;
  937. if (strpos($column, '(') === false) {
  938. $column = $this->db->quoteColumnName($column);
  939. }
  940. if ($value1 instanceof Expression) {
  941. foreach ($value1->params as $n => $v) {
  942. $params[$n] = $v;
  943. }
  944. $phName1 = $value1->expression;
  945. } else {
  946. $phName1 = self::PARAM_PREFIX . count($params);
  947. $params[$phName1] = $value1;
  948. }
  949. if ($value2 instanceof Expression) {
  950. foreach ($value2->params as $n => $v) {
  951. $params[$n] = $v;
  952. }
  953. $phName2 = $value2->expression;
  954. } else {
  955. $phName2 = self::PARAM_PREFIX . count($params);
  956. $params[$phName2] = $value2;
  957. }
  958. return "$column $operator $phName1 AND $phName2";
  959. }
  960. /**
  961. * Creates an SQL expressions with the `IN` operator.
  962. * @param string $operator the operator to use (e.g. `IN` or `NOT IN`)
  963. * @param array $operands the first operand is the column name. If it is an array
  964. * a composite IN condition will be generated.
  965. * The second operand is an array of values that column value should be among.
  966. * If it is an empty array the generated expression will be a `false` value if
  967. * operator is `IN` and empty if operator is `NOT IN`.
  968. * @param array $params the binding parameters to be populated
  969. * @return string the generated SQL expression
  970. * @throws Exception if wrong number of operands have been given.
  971. */
  972. public function buildInCondition($operator, $operands, &$params)
  973. {
  974. if (!isset($operands[0], $operands[1])) {
  975. throw new Exception("Operator '$operator' requires two operands.");
  976. }
  977. list($column, $values) = $operands;
  978. if ($values === [] || $column === []) {
  979. return $operator === 'IN' ? '0=1' : '';
  980. }
  981. if ($values instanceof Query) {
  982. return $this->buildSubqueryInCondition($operator, $column, $values, $params);
  983. }
  984. $values = (array) $values;
  985. if (count($column) > 1) {
  986. return $this->buildCompositeInCondition($operator, $column, $values, $params);
  987. }
  988. if (is_array($column)) {
  989. $column = reset($column);
  990. }
  991. foreach ($values as $i => $value) {
  992. if (is_array($value)) {
  993. $value = isset($value[$column]) ? $value[$column] : null;
  994. }
  995. if ($value === null) {
  996. $values[$i] = 'NULL';
  997. } elseif ($value instanceof Expression) {
  998. $values[$i] = $value->expression;
  999. foreach ($value->params as $n => $v) {
  1000. $params[$n] = $v;
  1001. }
  1002. } else {
  1003. $phName = self::PARAM_PREFIX . count($params);
  1004. $params[$phName] = $value;
  1005. $values[$i] = $phName;
  1006. }
  1007. }
  1008. if (strpos($column, '(') === false) {
  1009. $column = $this->db->quoteColumnName($column);
  1010. }
  1011. if (count($values) > 1) {
  1012. return "$column $operator (" . implode(', ', $values) . ')';
  1013. } else {
  1014. $operator = $operator === 'IN' ? '=' : '<>';
  1015. return $column . $operator . reset($values);
  1016. }
  1017. }
  1018. /**
  1019. * Builds SQL for IN condition
  1020. *
  1021. * @param string $operator
  1022. * @param array $columns
  1023. * @param Query $values
  1024. * @param array $params
  1025. * @return string SQL
  1026. */
  1027. protected function buildSubqueryInCondition($operator, $columns, $values, &$params)
  1028. {
  1029. list($sql, $params) = $this->build($values, $params);
  1030. if (is_array($columns)) {
  1031. foreach ($columns as $i => $col) {
  1032. if (strpos($col, '(') === false) {
  1033. $columns[$i] = $this->db->quoteColumnName($col);
  1034. }
  1035. }
  1036. return '(' . implode(', ', $columns) . ") $operator ($sql)";
  1037. } else {
  1038. if (strpos($columns, '(') === false) {
  1039. $columns = $this->db->quoteColumnName($columns);
  1040. }
  1041. return "$columns $operator ($sql)";
  1042. }
  1043. }
  1044. /**
  1045. * Builds SQL for IN condition
  1046. *
  1047. * @param string $operator
  1048. * @param array $columns
  1049. * @param array $values
  1050. * @param array $params
  1051. * @return string SQL
  1052. */
  1053. protected function buildCompositeInCondition($operator, $columns, $values, &$params)
  1054. {
  1055. $vss = [];
  1056. foreach ($values as $value) {
  1057. $vs = [];
  1058. foreach ($columns as $column) {
  1059. if (isset($value[$column])) {
  1060. $phName = self::PARAM_PREFIX . count($params);
  1061. $params[$phName] = $value[$column];
  1062. $vs[] = $phName;
  1063. } else {
  1064. $vs[] = 'NULL';
  1065. }
  1066. }
  1067. $vss[] = '(' . implode(', ', $vs) . ')';
  1068. }
  1069. foreach ($columns as $i => $column) {
  1070. if (strpos($column, '(') === false) {
  1071. $columns[$i] = $this->db->quoteColumnName($column);
  1072. }
  1073. }
  1074. return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')';
  1075. }
  1076. /**
  1077. * Creates an SQL expressions with the `LIKE` operator.
  1078. * @param string $operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`)
  1079. * @param array $operands an array of two or three operands
  1080. *
  1081. * - The first operand is the column name.
  1082. * - The second operand is a single value or an array of values that column value
  1083. * should be compared with. If it is an empty array the generated expression will
  1084. * be a `false` value if operator is `LIKE` or `OR LIKE`, and empty if operator
  1085. * is `NOT LIKE` or `OR NOT LIKE`.
  1086. * - An optional third operand can also be provided to specify how to escape special characters
  1087. * in the value(s). The operand should be an array of mappings from the special characters to their
  1088. * escaped counterparts. If this operand is not provided, a default escape mapping will be used.
  1089. * You may use `false` or an empty array to indicate the values are already escaped and no escape
  1090. * should be applied. Note that when using an escape mapping (or the third operand is not provided),
  1091. * the values will be automatically enclosed within a pair of percentage characters.
  1092. * @param array $params the binding parameters to be populated
  1093. * @return string the generated SQL expression
  1094. * @throws InvalidParamException if wrong number of operands have been given.
  1095. */
  1096. public function buildLikeCondition($operator, $operands, &$params)
  1097. {
  1098. if (!isset($operands[0], $operands[1])) {
  1099. throw new InvalidParamException("Operator '$operator' requires two operands.");
  1100. }
  1101. $escape = isset($operands[2]) ? $operands[2] : ['%'=>'\%', '_'=>'\_', '\\'=>'\\\\'];
  1102. unset($operands[2]);
  1103. if (!preg_match('/^(AND |OR |)(((NOT |))I?LIKE)/', $operator, $matches)) {
  1104. throw new InvalidParamException("Invalid operator '$operator'.");
  1105. }
  1106. $andor = ' ' . (!empty($matches[1]) ? $matches[1] : 'AND ');
  1107. $not = !empty($matches[3]);
  1108. $operator = $matches[2];
  1109. list($column, $values) = $operands;
  1110. if (!is_array($values)) {
  1111. $values = [$values];
  1112. }
  1113. if (empty($values)) {
  1114. return $not ? '' : '0=1';
  1115. }
  1116. if (strpos($column, '(') === false) {
  1117. $column = $this->db->quoteColumnName($column);
  1118. }
  1119. $parts = [];
  1120. foreach ($values as $value) {
  1121. if ($value instanceof Expression) {
  1122. foreach ($value->params as $n => $v) {
  1123. $params[$n] = $v;
  1124. }
  1125. $phName = $value->expression;
  1126. } else {
  1127. $phName = self::PARAM_PREFIX . count($params);
  1128. $params[$phName] = empty($escape) ? $value : ('%' . strtr($value, $escape) . '%');
  1129. }
  1130. $parts[] = "$column $operator $phName";
  1131. }
  1132. return implode($andor, $parts);
  1133. }
  1134. /**
  1135. * Creates an SQL expressions with the `EXISTS` operator.
  1136. * @param string $operator the operator to use (e.g. `EXISTS` or `NOT EXISTS`)
  1137. * @param array $operands contains only one element which is a [[Query]] object representing the sub-query.
  1138. * @param array $params the binding parameters to be populated
  1139. * @return string the generated SQL expression
  1140. * @throws InvalidParamException if the operand is not a [[Query]] object.
  1141. */
  1142. public function buildExistsCondition($operator, $operands, &$params)
  1143. {
  1144. if ($operands[0] instanceof Query) {
  1145. list($sql, $params) = $this->build($operands[0], $params);
  1146. return "$operator ($sql)";
  1147. } else {
  1148. throw new InvalidParamException('Subquery for EXISTS operator must be a Query object.');
  1149. }
  1150. }
  1151. /**
  1152. * Creates an SQL expressions like `"column" operator value`.
  1153. * @param string $operator the operator to use. Anything could be used e.g. `>`, `<=`, etc.
  1154. * @param array $operands contains two column names.
  1155. * @param array $params the binding parameters to be populated
  1156. * @return string the generated SQL expression
  1157. * @throws InvalidParamException if wrong number of operands have been given.
  1158. */
  1159. public function buildSimpleCondition($operator, $operands, &$params)
  1160. {
  1161. if (count($operands) !== 2) {
  1162. throw new InvalidParamException("Operator '$operator' requires two operands.");
  1163. }
  1164. list($column, $value) = $operands;
  1165. if (strpos($column, '(') === false) {
  1166. $column = $this->db->quoteColumnName($column);
  1167. }
  1168. if ($value === null) {
  1169. return "$column $operator NULL";
  1170. } elseif ($value instanceof Expression) {
  1171. foreach ($value->params as $n => $v) {
  1172. $params[$n] = $v;
  1173. }
  1174. return "$column $operator {$value->expression}";
  1175. } else {
  1176. $phName = self::PARAM_PREFIX . count($params);
  1177. $params[$phName] = $value;
  1178. return "$column $operator $phName";
  1179. }
  1180. }
  1181. }