PageRenderTime 34ms CodeModel.GetById 9ms RepoModel.GetById 1ms app.codeStats 0ms

/baser/models/datasources/dbo/dbo_bc_sqlite3.php

https://github.com/hashing/basercms
PHP | 466 lines | 275 code | 51 blank | 140 comment | 51 complexity | e5089e3afce6926f24b8992a5afc598c MD5 | raw file
Possible License(s): MIT
  1. <?php
  2. /* SVN FILE: $Id$ */
  3. /**
  4. * SQLite3 DBO拡張
  5. *
  6. * PHP versions 5
  7. *
  8. * baserCMS : Based Website Development Project <http://basercms.net>
  9. * Copyright 2008 - 2012, baserCMS Users Community <http://sites.google.com/site/baserusers/>
  10. *
  11. * @copyright Copyright 2008 - 2012, baserCMS Users Community
  12. * @link http://basercms.net baserCMS Project
  13. * @package baser.models.datasources.dbo
  14. * @since baserCMS v 0.1.0
  15. * @version $Revision$
  16. * @modifiedby $LastChangedBy$
  17. * @lastmodified $Date$
  18. * @license http://basercms.net/license/index.html
  19. */
  20. /**
  21. * Include files
  22. */
  23. App::import('Core','DboSqlite3',array('file'=>BASER_MODELS.'datasources'.DS.'dbo'.DS.'dbo_sqlite3.php'));
  24. /**
  25. * SQLite3 DBO拡張
  26. *
  27. * @package baser.models.datasources.dbo
  28. */
  29. class DboBcSqlite3 extends DboSqlite3 {
  30. /**
  31. * Generate a MySQL Alter Table syntax for the given Schema comparison
  32. *
  33. * @param array $compare Result of a CakeSchema::compare()
  34. * @return array Array of alter statements to make.
  35. * @access public
  36. */
  37. function alterSchema($compare, $table = null) {
  38. if (!is_array($compare)) {
  39. return false;
  40. }
  41. $out = '';
  42. $colList = array();
  43. foreach ($compare as $curTable => $types) {
  44. $indexes = array();
  45. if (!$table || $table == $curTable) {
  46. $out .= 'ALTER TABLE ' . $this->fullTableName($curTable) . " \n";
  47. foreach ($types as $type => $column) {
  48. if (isset($column['indexes'])) {
  49. $indexes[$type] = $column['indexes'];
  50. unset($column['indexes']);
  51. }
  52. switch ($type) {
  53. case 'add':
  54. foreach ($column as $field => $col) {
  55. $col['name'] = $field;
  56. $alter = 'ADD '.$this->buildColumn($col);
  57. if (isset($col['after'])) {
  58. $alter .= ' AFTER '. $this->name($col['after']);
  59. }
  60. $colList[] = $alter;
  61. }
  62. break;
  63. case 'drop':
  64. foreach ($column as $field => $col) {
  65. $col['name'] = $field;
  66. $colList[] = 'DROP '.$this->name($field);
  67. }
  68. break;
  69. case 'change':
  70. foreach ($column as $field => $col) {
  71. if (!isset($col['name'])) {
  72. $col['name'] = $field;
  73. }
  74. $colList[] = 'CHANGE '. $this->name($field).' '.$this->buildColumn($col);
  75. }
  76. break;
  77. }
  78. }
  79. $colList = array_merge($colList, $this->_alterIndexes($curTable, $indexes));
  80. $out .= "\t" . implode(",\n\t", $colList) . ";\n\n";
  81. }
  82. }
  83. return $out;
  84. }
  85. /**
  86. * Overrides DboSource::index to handle SQLite indexe introspection
  87. * Returns an array of the indexes in given table name.
  88. *
  89. * @param string $model Name of model to inspect
  90. * @return array Fields in table. Keys are column and unique
  91. * @access public
  92. */
  93. function index(&$model) {
  94. $index = array();
  95. $table = $this->fullTableName($model, false);
  96. if ($table) {
  97. $tableInfo = $this->query('PRAGMA table_info(' . $table . ')');
  98. $primary = array();
  99. foreach($tableInfo as $info) {
  100. if(!empty($info[0]['pk'])){
  101. $primary = array('PRIMARY' => array('unique' => true, 'column' => $info[0]['name']));
  102. }
  103. }
  104. $indexes = $this->query('PRAGMA index_list(' . $table . ')');
  105. foreach ($indexes as $i => $info) {
  106. $key = array_pop($info);
  107. $keyInfo = $this->query('PRAGMA index_info("' . $key['name'] . '")');
  108. foreach ($keyInfo as $keyCol) {
  109. if (!isset($index[$key['name']])) {
  110. $col = array();
  111. $index[$key['name']]['column'] = $keyCol[0]['name'];
  112. $index[$key['name']]['unique'] = intval($key['unique'] == 1);
  113. } else {
  114. if (!is_array($index[$key['name']]['column'])) {
  115. $col[] = $index[$key['name']]['column'];
  116. }
  117. $col[] = $keyCol[0]['name'];
  118. $index[$key['name']]['column'] = $col;
  119. }
  120. }
  121. }
  122. $index = am($primary, $index);
  123. }
  124. return $index;
  125. }
  126. /**
  127. * Generate index alteration statements for a table.
  128. * TODO 未サポート
  129. *
  130. * @param string $table Table to alter indexes for
  131. * @param array $new Indexes to add and drop
  132. * @return array Index alteration statements
  133. * @access protected
  134. */
  135. function _alterIndexes($table, $indexes) {
  136. return array();
  137. }
  138. /**
  139. * テーブル構造を変更する
  140. *
  141. * @param array $options [ new / old ]
  142. * @return boolean
  143. * @access public
  144. */
  145. function alterTable($options) {
  146. extract($options);
  147. if(!isset($old) || !isset($new)){
  148. return false;
  149. }
  150. $Schema = ClassRegistry::init('CakeSchema');
  151. $Schema->connection = $this->configKeyName;
  152. $compare = $Schema->compare($old, $new);
  153. if(!$compare) {
  154. return false;
  155. }
  156. foreach($compare as $table => $types) {
  157. if(!$types){
  158. return false;
  159. }
  160. foreach($types as $type => $fields) {
  161. if(!$fields){
  162. return false;
  163. }
  164. foreach($fields as $fieldName => $column) {
  165. switch ($type) {
  166. case 'add':
  167. if(!$this->addColumn(array('field'=>$fieldName,'table'=>$table, 'column'=>$column))){
  168. return false;
  169. }
  170. break;
  171. case 'change':
  172. // TODO 未実装
  173. // SQLiteでは、SQLで実装できない?ので、フィールドの作り直しとなる可能性が高い
  174. // その場合、changeColumnメソッドをオーバライドして実装する
  175. return false;
  176. /*if(!$this->changeColumn(array('field'=>$fieldName,'table'=>$table, 'column'=>$column))){
  177. return false;
  178. }*/
  179. break;
  180. case 'drop':
  181. if(!$this->dropColumn(array('field'=>$fieldName,'table'=>$table))){
  182. return false;
  183. }
  184. break;
  185. }
  186. }
  187. }
  188. }
  189. return true;
  190. }
  191. /**
  192. * テーブル名のリネームステートメントを生成
  193. *
  194. * @param string $sourceName
  195. * @param string $targetName
  196. * @return string
  197. * @access public
  198. */
  199. function buildRenameTable($sourceName, $targetName) {
  200. return "ALTER TABLE ".$sourceName." RENAME TO ".$targetName;
  201. }
  202. /**
  203. * カラムを変更する
  204. *
  205. * @param array $options [ table / new / old ]
  206. * @return boolean
  207. * @access public
  208. */
  209. function renameColumn($options) {
  210. extract($options);
  211. if(!isset($table) || !isset($new) || !isset($old)) {
  212. return false;
  213. }
  214. $prefix = $this->config['prefix'];
  215. $_table = $table;
  216. $model = Inflector::classify(Inflector::singularize($table));
  217. $table = $prefix . $table;
  218. App::import('Model','Schema');
  219. $Schema = ClassRegistry::init('CakeSchema');
  220. $Schema->connection = $this->configKeyName;
  221. $schema = $Schema->read(array('models'=>array($model)));
  222. $schema = $schema['tables'][$_table];
  223. $this->execute('BEGIN TRANSACTION;');
  224. // リネームして一時テーブル作成
  225. if(!$this->renameTable(array('old'=>$_table, 'new'=>$_table.'_temp'))) {
  226. $this->execute('ROLLBACK;');
  227. return false;
  228. }
  229. // スキーマのキーを変更(並び順を変えないように)
  230. $newSchema = array();
  231. foreach($schema as $key => $field) {
  232. if($key == $old) {
  233. $key = $new;
  234. }
  235. $newSchema[$key] = $field;
  236. }
  237. // フィールドを変更した新しいテーブルを作成
  238. if(!$this->createTable(array('schema'=>$newSchema, 'table'=>$_table))) {
  239. $this->execute('ROLLBACK;');
  240. return false;
  241. }
  242. // データの移動
  243. unset($schema['indexes']);
  244. $sql = 'INSERT INTO '.$table.' SELECT '.$this->_convertCsvFieldsFromSchema($schema).' FROM '.$table.'_temp';
  245. $sql = str_replace($old,$old.' AS '.$new, $sql);
  246. if(!$this->execute($sql)) {
  247. $this->execute('ROLLBACK;');
  248. return false;
  249. }
  250. // 一時テーブルを削除
  251. // dropTableメソッドはモデルありきなので利用できない
  252. if(!$this->execute('DROP TABLE '.$table.'_temp')) {
  253. $this->execute('ROLLBACK;');
  254. return false;
  255. }
  256. $this->execute('COMMIT;');
  257. return true;
  258. }
  259. /**
  260. * カラムを削除する
  261. *
  262. * @param array $options [ table / field / prefix ]
  263. * @return boolean
  264. * @access public
  265. */
  266. function dropColumn($options) {
  267. extract($options);
  268. if(!isset($table) || !isset($field)) {
  269. return false;
  270. }
  271. if(!isset($prefix)){
  272. $prefix = $this->config['prefix'];
  273. }
  274. $_table = $table;
  275. $model = Inflector::classify(Inflector::singularize($table));
  276. $table = $prefix . $table;
  277. App::import('Model','Schema');
  278. $Schema = ClassRegistry::init('CakeSchema');
  279. $Schema->connection = $this->configKeyName;
  280. $schema = $Schema->read(array('models'=>array($model)));
  281. $schema = $schema['tables'][$_table];
  282. $this->execute('BEGIN TRANSACTION;');
  283. // リネームして一時テーブル作成
  284. if(!$this->renameTable(array('old'=>$_table, 'new'=>$_table.'_temp'))) {
  285. $this->execute('ROLLBACK;');
  286. return false;
  287. }
  288. // フィールドを削除した新しいテーブルを作成
  289. unset($schema[$field]);
  290. if(!$this->createTable(array('schema'=>$schema, 'table'=>$_table))) {
  291. $this->execute('ROLLBACK;');
  292. return false;
  293. }
  294. // データの移動
  295. unset($schema['indexes']);
  296. if(!$this->_moveData($table.'_temp',$table,$schema)) {
  297. $this->execute('ROLLBACK;');
  298. return false;
  299. }
  300. // 一時テーブルを削除
  301. // dropTableメソッドはモデルありきなので利用できない
  302. if(!$this->execute('DROP TABLE '.$table.'_temp')) {
  303. $this->execute('ROLLBACK;');
  304. return false;
  305. }
  306. $this->execute('COMMIT;');
  307. return true;
  308. }
  309. /**
  310. * テーブルからテーブルへデータを移動する
  311. * @param string $sourceTableName
  312. * @param string $targetTableName
  313. * @param array $schema
  314. * @return booelan
  315. * @access protected
  316. */
  317. function _moveData($sourceTableName,$targetTableName,$schema) {
  318. $sql = 'INSERT INTO '.$targetTableName.' SELECT '.$this->_convertCsvFieldsFromSchema($schema).' FROM '.$sourceTableName;
  319. return $this->execute($sql);
  320. }
  321. /**
  322. * スキーマ情報よりCSV形式のフィールドリストを取得する
  323. * @param array $schema
  324. * @return string
  325. * @access protected
  326. */
  327. function _convertCsvFieldsFromSchema($schema) {
  328. $fields = '';
  329. foreach($schema as $key => $field) {
  330. $fields .= '"'.$key.'",';
  331. }
  332. return substr($fields,0,strlen($fields)-1);
  333. }
  334. /**
  335. * Returns an array of the fields in given table name.
  336. *
  337. * @param string $tableName Name of database table to inspect
  338. * @return array Fields in table. Keys are name and type
  339. * @access public
  340. */
  341. function describe(&$model) {
  342. $cache = $this->__describe($model);
  343. if ($cache != null) {
  344. return $cache;
  345. }
  346. $fields = array();
  347. $result = $this->fetchAll('PRAGMA table_info(' . $model->tablePrefix . $model->table . ')');
  348. foreach ($result as $column) {
  349. $fields[$column[0]['name']] = array(
  350. 'type' => $this->column($column[0]['type']),
  351. 'null' => !$column[0]['notnull'],
  352. 'default' => $column[0]['dflt_value'],
  353. // >>> CUSTOMIZE MODIFY 2010/11/24 ryuring
  354. // sqlite_sequence テーブルの場合、typeがないのでエラーとなるので調整
  355. // 'length' => $this->length($column[0]['type'])
  356. // ---
  357. 'length' => ($column[0]['type'])? $this->length($column[0]['type']) : ''
  358. // <<<
  359. );
  360. // >>> CUSTOMIZE ADD 2010/10/27 ryuring
  361. // SQLiteではdefaultのNULLが文字列として扱われてしまう様子
  362. if($fields[$column[0]['name']]['default']=='NULL'){
  363. $fields[$column[0]['name']]['default'] = NULL;
  364. }
  365. // >>> CUSTOMIZE ADD 2011/08/22 ryuring
  366. if($fields[$column[0]['name']]['type']=='boolean' && $fields[$column[0]['name']]['default'] == "'1'") {
  367. $fields[$column[0]['name']]['default'] = 1;
  368. } elseif($fields[$column[0]['name']]['type']=='boolean' && $fields[$column[0]['name']]['default'] == "'0'") {
  369. $fields[$column[0]['name']]['default'] = 0;
  370. }
  371. // >>>
  372. if($column[0]['pk'] == 1) {
  373. $fields[$column[0]['name']] = array(
  374. 'type' => $fields[$column[0]['name']]['type'],
  375. 'null' => false,
  376. 'default' => $column[0]['dflt_value'],
  377. 'key' => $this->index['PRI'],
  378. // >>> CUSTOMIZE MODIFY 2010/03/23 ryuring
  379. // baserCMSのプライマリーキーの初期値は8バイトで統一
  380. //'length' => 11
  381. // ---
  382. 'length' => 8
  383. // <<<
  384. );
  385. }
  386. }
  387. $this->__cacheDescription($model->tablePrefix . $model->table, $fields);
  388. return $fields;
  389. }
  390. /**
  391. * Returns a Model description (metadata) or null if none found.
  392. * DboSQlite3のdescribeメソッドを呼び出さずにキャッシュを読み込む為に利用
  393. * Datasource::describe と同じ
  394. *
  395. * @param Model $model
  396. * @return mixed
  397. * @access private
  398. */
  399. function __describe($model) {
  400. if ($this->cacheSources === false) {
  401. return null;
  402. }
  403. $table = $this->fullTableName($model, false);
  404. if (isset($this->__descriptions[$table])) {
  405. return $this->__descriptions[$table];
  406. }
  407. $cache = $this->__cacheDescription($table);
  408. if ($cache !== null) {
  409. $this->__descriptions[$table] =& $cache;
  410. return $cache;
  411. }
  412. return null;
  413. }
  414. }
  415. ?>