PageRenderTime 46ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/inc/libs/clearbricks/dbschema/class.sqlite.dbschema.php

https://bitbucket.org/dotclear/dotclear/
PHP | 514 lines | 410 code | 70 blank | 34 comment | 49 complexity | 0e6a54cbad7faa258e7cf3bafe95b9ca MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, Apache-2.0
  1. <?php
  2. # ***** BEGIN LICENSE BLOCK *****
  3. # This file is part of Clearbricks.
  4. # Copyright (c) 2003-2011 Olivier Meunier & Association Dotclear
  5. # All rights reserved.
  6. #
  7. # Clearbricks is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation; either version 2 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # Clearbricks is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with Clearbricks; if not, write to the Free Software
  19. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  20. #
  21. # ***** END LICENSE BLOCK *****
  22. if (class_exists('dbSchema'))
  23. {
  24. class sqliteSchema extends dbSchema implements i_dbSchema
  25. {
  26. private $table_hist = array();
  27. private $table_stack = array(); // Stack for tables creation
  28. private $x_stack = array(); // Execution stack
  29. public function dbt2udt($type,&$len,&$default)
  30. {
  31. $type = parent::dbt2udt($type,$len,$default);
  32. switch ($type)
  33. {
  34. case 'float':
  35. return 'real';
  36. case 'double':
  37. return 'float';
  38. case 'timestamp':
  39. # DATETIME real type is TIMESTAMP
  40. if ($default == "'1970-01-01 00:00:00'") {
  41. # Bad hack
  42. $default = 'now()';
  43. }
  44. return 'timestamp';
  45. case 'integer':
  46. case 'mediumint':
  47. case 'bigint':
  48. case 'tinyint':
  49. case 'smallint':
  50. case 'numeric':
  51. return 'integer';
  52. case 'tinytext':
  53. case 'longtext':
  54. return 'text';
  55. }
  56. return $type;
  57. }
  58. public function udt2dbt($type,&$len,&$default)
  59. {
  60. $type = parent::udt2dbt($type,$len,$default);
  61. switch ($type)
  62. {
  63. case 'integer':
  64. case 'smallint':
  65. case 'bigint':
  66. return 'integer';
  67. case 'real':
  68. case 'float:':
  69. return 'real';
  70. case 'date':
  71. case 'time':
  72. return 'timestamp';
  73. case 'timestamp':
  74. if ($default == 'now()') {
  75. # SQLite does not support now() default value...
  76. $default = "'1970-01-01 00:00:00'";
  77. }
  78. return $type;
  79. }
  80. return $type;
  81. }
  82. public function flushStack()
  83. {
  84. foreach ($this->table_stack as $table => $def)
  85. {
  86. $sql = 'CREATE TABLE '.$table." (\n".implode(",\n",$def)."\n)\n ";
  87. $this->con->execute($sql);
  88. }
  89. foreach ($this->x_stack as $x)
  90. {
  91. $this->con->execute($x);
  92. }
  93. }
  94. public function db_get_tables()
  95. {
  96. $res = array();
  97. $sql = "SELECT * FROM sqlite_master WHERE type = 'table'";
  98. $rs = $this->con->select($sql);
  99. $res = array();
  100. while ($rs->fetch()) {
  101. $res[] = $rs->tbl_name;
  102. }
  103. return $res;
  104. }
  105. public function db_get_columns($table)
  106. {
  107. $sql = 'PRAGMA table_info('.$this->con->escapeSystem($table).')';
  108. $rs = $this->con->select($sql);
  109. $res = array();
  110. while ($rs->fetch())
  111. {
  112. $field = trim($rs->name);
  113. $type = trim($rs->type);
  114. $null = trim($rs->notnull) == 0;
  115. $default = trim($rs->dflt_value);
  116. $len = null;
  117. if (preg_match('/^(.+?)\(([\d,]+)\)$/si',$type,$m)) {
  118. $type = $m[1];
  119. $len = (integer) $m[2];
  120. }
  121. $res[$field] = array(
  122. 'type' => $type,
  123. 'len' => $len,
  124. 'null' => $null,
  125. 'default' => $default
  126. );
  127. }
  128. return $res;
  129. }
  130. public function db_get_keys($table)
  131. {
  132. $t = array();
  133. $res = array();
  134. # Get primary keys first
  135. $sql = "SELECT sql FROM sqlite_master WHERE type='table' AND name='".$this->con->escape($table)."'";
  136. $rs = $this->con->select($sql);
  137. if ($rs->isEmpty()) {
  138. return array();
  139. }
  140. # Get primary keys
  141. $n = preg_match_all('/^\s*CONSTRAINT\s+([^,]+?)\s+PRIMARY\s+KEY\s+\((.+?)\)/msi',$rs->sql,$match);
  142. if ($n > 0)
  143. {
  144. foreach ($match[1] as $i => $name)
  145. {
  146. $cols = preg_split('/\s*,\s*/',$match[2][$i]);
  147. $res[] = array(
  148. 'name' => $name,
  149. 'primary' => true,
  150. 'unique' => false,
  151. 'cols' => $cols
  152. );
  153. }
  154. }
  155. # Get unique keys
  156. $n = preg_match_all('/^\s*CONSTRAINT\s+([^,]+?)\s+UNIQUE\s+\((.+?)\)/msi',$rs->sql,$match);
  157. if ($n > 0)
  158. {
  159. foreach ($match[1] as $i => $name)
  160. {
  161. $cols = preg_split('/\s*,\s*/',$match[2][$i]);
  162. $res[] = array(
  163. 'name' => $name,
  164. 'primary' => false,
  165. 'unique' => true,
  166. 'cols' => $cols
  167. );
  168. }
  169. }
  170. return $res;
  171. }
  172. public function db_get_indexes($table)
  173. {
  174. $sql = 'PRAGMA index_list('.$this->con->escapeSystem($table).')';
  175. $rs = $this->con->select($sql);
  176. $res = array();
  177. while ($rs->fetch())
  178. {
  179. if (preg_match('/^sqlite_/',$rs->name)) {
  180. continue;
  181. }
  182. $idx = $this->con->select('PRAGMA index_info('.$this->con->escapeSystem($rs->name).')');
  183. $cols = array();
  184. while ($idx->fetch()) {
  185. $cols[] = $idx->name;
  186. }
  187. $res[] = array(
  188. 'name' => $rs->name,
  189. 'type' => 'btree',
  190. 'cols' => $cols
  191. );
  192. }
  193. return $res;
  194. }
  195. public function db_get_references($table)
  196. {
  197. $sql = 'SELECT * FROM sqlite_master WHERE type=\'trigger\' AND tbl_name = \'%1$s\' AND name LIKE \'%2$s_%%\' ';
  198. $res = array();
  199. # Find constraints on table
  200. $bir = $this->con->select(sprintf($sql,$this->con->escape($table),'bir'));
  201. $bur = $this->con->select(sprintf($sql,$this->con->escape($table),'bur'));
  202. if ($bir->isEmpty() || $bur->isempty()) {
  203. return $res;
  204. }
  205. while ($bir->fetch())
  206. {
  207. # Find child column and parent table and column
  208. if (!preg_match('/FROM\s+(.+?)\s+WHERE\s+(.+?)\s+=\s+NEW\.(.+?)\s*?\) IS\s+NULL/msi',$bir->sql,$m)) {
  209. continue;
  210. }
  211. $c_col = $m[3];
  212. $p_table = $m[1];
  213. $p_col = $m[2];
  214. # Find on update
  215. $on_update = 'restrict';
  216. $aur = $this->con->select(sprintf($sql,$this->con->escape($p_table),'aur'));
  217. while ($aur->fetch())
  218. {
  219. if (!preg_match('/AFTER\s+UPDATE/msi',$aur->sql)) {
  220. continue;
  221. }
  222. if (preg_match('/UPDATE\s+'.$table.'\s+SET\s+'.$c_col.'\s*=\s*NEW.'.$p_col.
  223. '\s+WHERE\s+'.$c_col.'\s*=\s*OLD\.'.$p_col.'/msi',$aur->sql)) {
  224. $on_update = 'cascade';
  225. break;
  226. }
  227. if (preg_match('/UPDATE\s+'.$table.'\s+SET\s+'.$c_col.'\s*=\s*NULL'.
  228. '\s+WHERE\s+'.$c_col.'\s*=\s*OLD\.'.$p_col.'/msi',$aur->sql)) {
  229. $on_update = 'set null';
  230. break;
  231. }
  232. }
  233. # Find on delete
  234. $on_delete = 'restrict';
  235. $bdr = $this->con->select(sprintf($sql,$this->con->escape($p_table),'bdr'));
  236. while ($bdr->fetch())
  237. {
  238. if (!preg_match('/BEFORE\s+DELETE/msi',$bdr->sql)) {
  239. continue;
  240. }
  241. if (preg_match('/DELETE\s+FROM\s+'.$table.'\s+WHERE\s+'.$c_col.'\s*=\s*OLD\.'.$p_col.'/msi',$bdr->sql)) {
  242. $on_delete = 'cascade';
  243. break;
  244. }
  245. if (preg_match('/UPDATE\s+'.$table.'\s+SET\s+'.$c_col.'\s*=\s*NULL'.
  246. '\s+WHERE\s+'.$c_col.'\s*=\s*OLD\.'.$p_col.'/msi',$bdr->sql)) {
  247. $on_update = 'set null';
  248. break;
  249. }
  250. }
  251. $res[] = array(
  252. 'name' => substr($bir->name,4),
  253. 'c_cols' => array($c_col),
  254. 'p_table' => $p_table,
  255. 'p_cols' => array($p_col),
  256. 'update' => $on_update,
  257. 'delete' => $on_delete
  258. );
  259. }
  260. return $res;
  261. }
  262. public function db_create_table($name,$fields)
  263. {
  264. $a = array();
  265. foreach ($fields as $n => $f)
  266. {
  267. $type = $f['type'];
  268. $len = (integer) $f['len'];
  269. $default = $f['default'];
  270. $null = $f['null'];
  271. $type = $this->udt2dbt($type,$len,$default);
  272. $len = $len > 0 ? '('.$len.')' : '';
  273. $null = $null ? 'NULL' : 'NOT NULL';
  274. if ($default === null) {
  275. $default = 'DEFAULT NULL';
  276. } elseif ($default !== false) {
  277. $default = 'DEFAULT '.$default.' ';
  278. } else {
  279. $default = '';
  280. }
  281. $a[] = $n.' '.$type.$len.' '.$null.' '.$default;
  282. }
  283. $this->table_stack[$name][] = implode(",\n",$a);
  284. $this->table_hist[$name] = $fields;
  285. }
  286. public function db_create_field($table,$name,$type,$len,$null,$default)
  287. {
  288. $type = $this->udt2dbt($type,$len,$default);
  289. $len = $len > 0 ? '('.$len.')' : '';
  290. $null = $null ? 'NULL' : 'NOT NULL';
  291. if ($default === null) {
  292. $default = 'DEFAULT NULL';
  293. } elseif ($default !== false) {
  294. $default = 'DEFAULT '.$default.' ';
  295. } else {
  296. $default = '';
  297. }
  298. $sql =
  299. 'ALTER TABLE '.$this->con->escapeSystem($table).' '.
  300. 'ADD COLUMN '.$this->con->escapeSystem($name).' '.
  301. $type.$len.' '.$null.' '.$default;
  302. $this->con->execute($sql);
  303. }
  304. public function db_create_primary($table,$name,$cols)
  305. {
  306. $this->table_stack[$table][] = 'CONSTRAINT '.$name.' PRIMARY KEY ('.implode(',',$cols).') ';
  307. }
  308. public function db_create_unique($table,$name,$cols)
  309. {
  310. $this->table_stack[$table][] = 'CONSTRAINT '.$name.' UNIQUE ('.implode(',',$cols).') ';
  311. }
  312. public function db_create_index($table,$name,$type,$cols)
  313. {
  314. $this->x_stack[] = 'CREATE INDEX '.$name.' ON '.$table.' ('.implode(',',$cols).') ';
  315. }
  316. public function db_create_reference($name,$c_table,$c_cols,$p_table,$p_cols,$update,$delete)
  317. {
  318. if (!isset($this->table_hist[$c_table])) {
  319. return;
  320. }
  321. if (count($c_cols) > 1 || count($p_cols) > 1) {
  322. throw new Exception('SQLite UDBS does not support multiple columns foreign keys');
  323. }
  324. $c_col = $c_cols[0];
  325. $p_col = $p_cols[0];
  326. $update = strtolower($update);
  327. $delete = strtolower($delete);
  328. $cnull = $this->table_hist[$c_table][$c_col]['null'];
  329. # Create constraint
  330. $this->x_stack[] =
  331. 'CREATE TRIGGER bir_'.$name."\n".
  332. 'BEFORE INSERT ON '.$c_table."\n".
  333. "FOR EACH ROW BEGIN\n".
  334. ' SELECT RAISE(ROLLBACK,\'insert on table "'.$c_table.'" violates foreign key constraint "'.$name.'"\')'."\n".
  335. ' WHERE '.
  336. ($cnull ? 'NEW.'.$c_col." IS NOT NULL\n AND " : '').
  337. '(SELECT '.$p_col.' FROM '.$p_table.' WHERE '.$p_col.' = NEW.'.$c_col.") IS NULL;\n".
  338. "END;\n";
  339. # Update constraint
  340. $this->x_stack[] =
  341. 'CREATE TRIGGER bur_'.$name."\n".
  342. 'BEFORE UPDATE ON '.$c_table."\n".
  343. "FOR EACH ROW BEGIN\n".
  344. ' SELECT RAISE(ROLLBACK,\'update on table "'.$c_table.'" violates foreign key constraint "'.$name.'"\')'."\n".
  345. ' WHERE '.
  346. ($cnull ? 'NEW.'.$c_col." IS NOT NULL\n AND " : '').
  347. '(SELECT '.$p_col.' FROM '.$p_table.' WHERE '.$p_col.' = NEW.'.$c_col.") IS NULL;\n".
  348. "END;\n";
  349. # ON UPDATE
  350. if ($update == 'cascade')
  351. {
  352. $this->x_stack[] =
  353. 'CREATE TRIGGER aur_'.$name."\n".
  354. 'AFTER UPDATE ON '.$p_table."\n".
  355. "FOR EACH ROW BEGIN\n".
  356. ' UPDATE '.$c_table.' SET '.$c_col.' = NEW.'.$p_col.' WHERE '.$c_col.' = OLD.'.$p_col.";\n".
  357. "END;\n";
  358. }
  359. elseif ($update == 'set null')
  360. {
  361. $this->x_stack[] =
  362. 'CREATE TRIGGER aur_'.$name."\n".
  363. 'AFTER UPDATE ON '.$p_table."\n".
  364. "FOR EACH ROW BEGIN\n".
  365. ' UPDATE '.$c_table.' SET '.$c_col.' = NULL WHERE '.$c_col.' = OLD.'.$p_col.";\n".
  366. "END;\n";
  367. }
  368. else # default on restrict
  369. {
  370. $this->x_stack[] =
  371. 'CREATE TRIGGER burp_'.$name."\n".
  372. 'BEFORE UPDATE ON '.$p_table."\n".
  373. "FOR EACH ROW BEGIN\n".
  374. ' SELECT RAISE (ROLLBACK,\'update on table "'.$p_table.'" violates foreign key constraint "'.$name.'"\')'."\n".
  375. ' WHERE (SELECT '.$c_col.' FROM '.$c_table.' WHERE '.$c_col.' = OLD.'.$p_col.") IS NOT NULL;\n".
  376. "END;\n";
  377. }
  378. # ON DELETE
  379. if ($delete == 'cascade')
  380. {
  381. $this->x_stack[] =
  382. 'CREATE TRIGGER bdr_'.$name."\n".
  383. 'BEFORE DELETE ON '.$p_table."\n".
  384. "FOR EACH ROW BEGIN\n".
  385. ' DELETE FROM '.$c_table.' WHERE '.$c_col.' = OLD.'.$p_col.";\n".
  386. "END;\n";
  387. }
  388. elseif ($delete == 'set null')
  389. {
  390. $this->x_stack[] =
  391. 'CREATE TRIGGER bdr_'.$name."\n".
  392. 'BEFORE DELETE ON '.$p_table."\n".
  393. "FOR EACH ROW BEGIN\n".
  394. ' UPDATE '.$c_table.' SET '.$c_col.' = NULL WHERE '.$c_col.' = OLD.'.$p_col.";\n".
  395. "END;\n";
  396. }
  397. else
  398. {
  399. $this->x_stack[] =
  400. 'CREATE TRIGGER bdr_'.$name."\n".
  401. 'BEFORE DELETE ON '.$p_table."\n".
  402. "FOR EACH ROW BEGIN\n".
  403. ' SELECT RAISE (ROLLBACK,\'delete on table "'.$p_table.'" violates foreign key constraint "'.$name.'"\')'."\n".
  404. ' WHERE (SELECT '.$c_col.' FROM '.$c_table.' WHERE '.$c_col.' = OLD.'.$p_col.") IS NOT NULL;\n".
  405. "END;\n";
  406. }
  407. }
  408. public function db_alter_field($table,$name,$type,$len,$null,$default)
  409. {
  410. $type = $this->udt2dbt($type,$len,$default);
  411. if ($type != 'integer' && $type != 'text' && $type != 'timestamp') {
  412. throw new Exception('SQLite fields cannot be changed.');
  413. }
  414. }
  415. public function db_alter_primary($table,$name,$newname,$cols)
  416. {
  417. throw new Exception('SQLite primary key cannot be changed.');
  418. }
  419. public function db_alter_unique($table,$name,$newname,$cols)
  420. {
  421. throw new Exception('SQLite unique index cannot be changed.');
  422. }
  423. public function db_alter_index($table,$name,$newname,$type,$cols)
  424. {
  425. $this->con->execute('DROP INDEX IF EXISTS '.$name);
  426. $this->con->execute('CREATE INDEX '.$newname.' ON '.$table.' ('.implode(',',$cols).') ');
  427. }
  428. public function db_alter_reference($name,$newname,$c_table,$c_cols,$p_table,$p_cols,$update,$delete)
  429. {
  430. $this->con->execute('DROP TRIGGER IF EXISTS bur_'.$name);
  431. $this->con->execute('DROP TRIGGER IF EXISTS burp_'.$name);
  432. $this->con->execute('DROP TRIGGER IF EXISTS bir_'.$name);
  433. $this->con->execute('DROP TRIGGER IF EXISTS aur_'.$name);
  434. $this->con->execute('DROP TRIGGER IF EXISTS bdr_'.$name);
  435. $this->table_hist[$c_table] = $this->db_get_columns($c_table);
  436. $this->db_create_reference($newname,$c_table,$c_cols,$p_table,$p_cols,$update,$delete);
  437. }
  438. public function db_drop_unique($table,$name)
  439. {
  440. throw new Exception('SQLite unique index cannot be removed.');
  441. }
  442. }
  443. }
  444. ?>