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.

347 lines
15KB

  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\sqlite;
  8. use yii\db\Connection;
  9. use yii\db\Exception;
  10. use yii\base\InvalidParamException;
  11. use yii\base\NotSupportedException;
  12. /**
  13. * QueryBuilder is the query builder for SQLite databases.
  14. *
  15. * @author Qiang Xue <qiang.xue@gmail.com>
  16. * @since 2.0
  17. */
  18. class QueryBuilder extends \yii\db\QueryBuilder
  19. {
  20. /**
  21. * @var array mapping from abstract column types (keys) to physical column types (values).
  22. */
  23. public $typeMap = [
  24. Schema::TYPE_PK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
  25. Schema::TYPE_BIGPK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
  26. Schema::TYPE_STRING => 'varchar(255)',
  27. Schema::TYPE_TEXT => 'text',
  28. Schema::TYPE_SMALLINT => 'smallint',
  29. Schema::TYPE_INTEGER => 'integer',
  30. Schema::TYPE_BIGINT => 'bigint',
  31. Schema::TYPE_FLOAT => 'float',
  32. Schema::TYPE_DOUBLE => 'double',
  33. Schema::TYPE_DECIMAL => 'decimal(10,0)',
  34. Schema::TYPE_DATETIME => 'datetime',
  35. Schema::TYPE_TIMESTAMP => 'timestamp',
  36. Schema::TYPE_TIME => 'time',
  37. Schema::TYPE_DATE => 'date',
  38. Schema::TYPE_BINARY => 'blob',
  39. Schema::TYPE_BOOLEAN => 'boolean',
  40. Schema::TYPE_MONEY => 'decimal(19,4)',
  41. ];
  42. /**
  43. * Generates a batch INSERT SQL statement.
  44. * For example,
  45. *
  46. * ~~~
  47. * $connection->createCommand()->batchInsert('user', ['name', 'age'], [
  48. * ['Tom', 30],
  49. * ['Jane', 20],
  50. * ['Linda', 25],
  51. * ])->execute();
  52. * ~~~
  53. *
  54. * Note that the values in each row must match the corresponding column names.
  55. *
  56. * @param string $table the table that new rows will be inserted into.
  57. * @param array $columns the column names
  58. * @param array $rows the rows to be batch inserted into the table
  59. * @return string the batch INSERT SQL statement
  60. */
  61. public function batchInsert($table, $columns, $rows)
  62. {
  63. // SQLite supports batch insert natively since 3.7.11
  64. // http://www.sqlite.org/releaselog/3_7_11.html
  65. $this->db->open(); // ensure pdo is not null
  66. if (version_compare($this->db->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '3.7.11', '>=')) {
  67. return parent::batchInsert($table, $columns, $rows);
  68. }
  69. $schema = $this->db->getSchema();
  70. if (($tableSchema = $schema->getTableSchema($table)) !== null) {
  71. $columnSchemas = $tableSchema->columns;
  72. } else {
  73. $columnSchemas = [];
  74. }
  75. $values = [];
  76. foreach ($rows as $row) {
  77. $vs = [];
  78. foreach ($row as $i => $value) {
  79. if (!is_array($value) && isset($columnSchemas[$columns[$i]])) {
  80. $value = $columnSchemas[$columns[$i]]->dbTypecast($value);
  81. }
  82. if (is_string($value)) {
  83. $value = $schema->quoteValue($value);
  84. } elseif ($value === false) {
  85. $value = 0;
  86. } elseif ($value === null) {
  87. $value = 'NULL';
  88. }
  89. $vs[] = $value;
  90. }
  91. $values[] = implode(', ', $vs);
  92. }
  93. foreach ($columns as $i => $name) {
  94. $columns[$i] = $schema->quoteColumnName($name);
  95. }
  96. return 'INSERT INTO ' . $schema->quoteTableName($table)
  97. . ' (' . implode(', ', $columns) . ') SELECT ' . implode(' UNION SELECT ', $values);
  98. }
  99. /**
  100. * Creates a SQL statement for resetting the sequence value of a table's primary key.
  101. * The sequence will be reset such that the primary key of the next new row inserted
  102. * will have the specified value or 1.
  103. * @param string $tableName the name of the table whose primary key sequence will be reset
  104. * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
  105. * the next new row's primary key will have a value 1.
  106. * @return string the SQL statement for resetting sequence
  107. * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table.
  108. */
  109. public function resetSequence($tableName, $value = null)
  110. {
  111. $db = $this->db;
  112. $table = $db->getTableSchema($tableName);
  113. if ($table !== null && $table->sequenceName !== null) {
  114. if ($value === null) {
  115. $key = reset($table->primaryKey);
  116. $tableName = $db->quoteTableName($tableName);
  117. $value = $this->db->useMaster(function (Connection $db) use ($key, $tableName) {
  118. return $db->createCommand("SELECT MAX('$key') FROM $tableName")->queryScalar();
  119. });
  120. } else {
  121. $value = (int) $value - 1;
  122. }
  123. try {
  124. $db->createCommand("UPDATE sqlite_sequence SET seq='$value' WHERE name='{$table->name}'")->execute();
  125. } catch (Exception $e) {
  126. // it's possible that sqlite_sequence does not exist
  127. }
  128. } elseif ($table === null) {
  129. throw new InvalidParamException("Table not found: $tableName");
  130. } else {
  131. throw new InvalidParamException("There is not sequence associated with table '$tableName'.'");
  132. }
  133. }
  134. /**
  135. * Enables or disables integrity check.
  136. * @param boolean $check whether to turn on or off the integrity check.
  137. * @param string $schema the schema of the tables. Meaningless for SQLite.
  138. * @param string $table the table name. Meaningless for SQLite.
  139. * @return string the SQL statement for checking integrity
  140. * @throws NotSupportedException this is not supported by SQLite
  141. */
  142. public function checkIntegrity($check = true, $schema = '', $table = '')
  143. {
  144. return 'PRAGMA foreign_keys='.(int) $check;
  145. }
  146. /**
  147. * Builds a SQL statement for truncating a DB table.
  148. * @param string $table the table to be truncated. The name will be properly quoted by the method.
  149. * @return string the SQL statement for truncating a DB table.
  150. */
  151. public function truncateTable($table)
  152. {
  153. return "DELETE FROM " . $this->db->quoteTableName($table);
  154. }
  155. /**
  156. * Builds a SQL statement for dropping an index.
  157. * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
  158. * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
  159. * @return string the SQL statement for dropping an index.
  160. */
  161. public function dropIndex($name, $table)
  162. {
  163. return 'DROP INDEX ' . $this->db->quoteTableName($name);
  164. }
  165. /**
  166. * Builds a SQL statement for dropping a DB column.
  167. * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
  168. * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
  169. * @return string the SQL statement for dropping a DB column.
  170. * @throws NotSupportedException this is not supported by SQLite
  171. */
  172. public function dropColumn($table, $column)
  173. {
  174. throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
  175. }
  176. /**
  177. * Builds a SQL statement for renaming a column.
  178. * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
  179. * @param string $oldName the old name of the column. The name will be properly quoted by the method.
  180. * @param string $newName the new name of the column. The name will be properly quoted by the method.
  181. * @return string the SQL statement for renaming a DB column.
  182. * @throws NotSupportedException this is not supported by SQLite
  183. */
  184. public function renameColumn($table, $oldName, $newName)
  185. {
  186. throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
  187. }
  188. /**
  189. * Builds a SQL statement for adding a foreign key constraint to an existing table.
  190. * The method will properly quote the table and column names.
  191. * @param string $name the name of the foreign key constraint.
  192. * @param string $table the table that the foreign key constraint will be added to.
  193. * @param string|array $columns the name of the column to that the constraint will be added on.
  194. * If there are multiple columns, separate them with commas or use an array to represent them.
  195. * @param string $refTable the table that the foreign key references to.
  196. * @param string|array $refColumns the name of the column that the foreign key references to.
  197. * If there are multiple columns, separate them with commas or use an array to represent them.
  198. * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
  199. * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
  200. * @return string the SQL statement for adding a foreign key constraint to an existing table.
  201. * @throws NotSupportedException this is not supported by SQLite
  202. */
  203. public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
  204. {
  205. throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
  206. }
  207. /**
  208. * Builds a SQL statement for dropping a foreign key constraint.
  209. * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
  210. * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
  211. * @return string the SQL statement for dropping a foreign key constraint.
  212. * @throws NotSupportedException this is not supported by SQLite
  213. */
  214. public function dropForeignKey($name, $table)
  215. {
  216. throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
  217. }
  218. /**
  219. * Builds a SQL statement for changing the definition of a column.
  220. * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
  221. * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
  222. * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
  223. * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
  224. * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
  225. * will become 'varchar(255) not null'.
  226. * @return string the SQL statement for changing the definition of a column.
  227. * @throws NotSupportedException this is not supported by SQLite
  228. */
  229. public function alterColumn($table, $column, $type)
  230. {
  231. throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
  232. }
  233. /**
  234. * Builds a SQL statement for adding a primary key constraint to an existing table.
  235. * @param string $name the name of the primary key constraint.
  236. * @param string $table the table that the primary key constraint will be added to.
  237. * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
  238. * @return string the SQL statement for adding a primary key constraint to an existing table.
  239. * @throws NotSupportedException this is not supported by SQLite
  240. */
  241. public function addPrimaryKey($name, $table, $columns)
  242. {
  243. throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
  244. }
  245. /**
  246. * Builds a SQL statement for removing a primary key constraint to an existing table.
  247. * @param string $name the name of the primary key constraint to be removed.
  248. * @param string $table the table that the primary key constraint will be removed from.
  249. * @return string the SQL statement for removing a primary key constraint from an existing table.
  250. * @throws NotSupportedException this is not supported by SQLite
  251. */
  252. public function dropPrimaryKey($name, $table)
  253. {
  254. throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
  255. }
  256. /**
  257. * @inheritdoc
  258. */
  259. public function buildLimit($limit, $offset)
  260. {
  261. $sql = '';
  262. if ($this->hasLimit($limit)) {
  263. $sql = 'LIMIT ' . $limit;
  264. if ($this->hasOffset($offset)) {
  265. $sql .= ' OFFSET ' . $offset;
  266. }
  267. } elseif ($this->hasOffset($offset)) {
  268. // limit is not optional in SQLite
  269. // http://www.sqlite.org/syntaxdiagrams.html#select-stmt
  270. $sql = "LIMIT 9223372036854775807 OFFSET $offset"; // 2^63-1
  271. }
  272. return $sql;
  273. }
  274. /**
  275. * Builds SQL for IN condition
  276. *
  277. * @param string $operator
  278. * @param array $columns
  279. * @param array $values
  280. * @param array $params
  281. * @return string SQL
  282. */
  283. protected function buildSubqueryInCondition($operator, $columns, $values, &$params)
  284. {
  285. if (is_array($columns)) {
  286. throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
  287. }
  288. return parent::buildSubqueryInCondition($operator, $columns, $values, $params);
  289. }
  290. /**
  291. * Builds SQL for IN condition
  292. *
  293. * @param string $operator
  294. * @param array $columns
  295. * @param array $values
  296. * @param array $params
  297. * @return string SQL
  298. */
  299. protected function buildCompositeInCondition($operator, $columns, $values, &$params)
  300. {
  301. $quotedColumns = [];
  302. foreach ($columns as $i => $column) {
  303. $quotedColumns[$i] = strpos($column, '(') === false ? $this->db->quoteColumnName($column) : $column;
  304. }
  305. $vss = [];
  306. foreach ($values as $value) {
  307. $vs = [];
  308. foreach ($columns as $i => $column) {
  309. if (isset($value[$column])) {
  310. $phName = self::PARAM_PREFIX . count($params);
  311. $params[$phName] = $value[$column];
  312. $vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' = ' : ' != ') . $phName;
  313. } else {
  314. $vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' IS' : ' IS NOT') . ' NULL';
  315. }
  316. }
  317. $vss[] = '(' . implode($operator === 'IN' ? ' AND ' : ' OR ', $vs) . ')';
  318. }
  319. return '(' . implode($operator === 'IN' ? ' OR ' : ' AND ', $vss) . ')';
  320. }
  321. }