PageRenderTime 31ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/forum/includes/db/db_tools.php

https://bitbucket.org/itoxable/chiron-gaming
PHP | 2420 lines | 1852 code | 323 blank | 245 comment | 278 complexity | 9f51d57c4320c77eaf3575b5a0b0aad3 MD5 | raw file
Possible License(s): AGPL-1.0, GPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. *
  4. * @package dbal
  5. * @version $Id$
  6. * @copyright (c) 2007 phpBB Group
  7. * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  8. *
  9. */
  10. /**
  11. * @ignore
  12. */
  13. if (!defined('IN_PHPBB'))
  14. {
  15. exit;
  16. }
  17. /**
  18. * Database Tools for handling cross-db actions such as altering columns, etc.
  19. * Currently not supported is returning SQL for creating tables.
  20. *
  21. * @package dbal
  22. * @note currently not used within phpBB3, but may be utilized later.
  23. */
  24. class phpbb_db_tools
  25. {
  26. /**
  27. * Current sql layer
  28. */
  29. var $sql_layer = '';
  30. /**
  31. * @var object DB object
  32. */
  33. var $db = NULL;
  34. /**
  35. * The Column types for every database we support
  36. * @var array
  37. */
  38. var $dbms_type_map = array(
  39. 'mysql_41' => array(
  40. 'INT:' => 'int(%d)',
  41. 'BINT' => 'bigint(20)',
  42. 'UINT' => 'mediumint(8) UNSIGNED',
  43. 'UINT:' => 'int(%d) UNSIGNED',
  44. 'TINT:' => 'tinyint(%d)',
  45. 'USINT' => 'smallint(4) UNSIGNED',
  46. 'BOOL' => 'tinyint(1) UNSIGNED',
  47. 'VCHAR' => 'varchar(255)',
  48. 'VCHAR:' => 'varchar(%d)',
  49. 'CHAR:' => 'char(%d)',
  50. 'XSTEXT' => 'text',
  51. 'XSTEXT_UNI'=> 'varchar(100)',
  52. 'STEXT' => 'text',
  53. 'STEXT_UNI' => 'varchar(255)',
  54. 'TEXT' => 'text',
  55. 'TEXT_UNI' => 'text',
  56. 'MTEXT' => 'mediumtext',
  57. 'MTEXT_UNI' => 'mediumtext',
  58. 'TIMESTAMP' => 'int(11) UNSIGNED',
  59. 'DECIMAL' => 'decimal(5,2)',
  60. 'DECIMAL:' => 'decimal(%d,2)',
  61. 'PDECIMAL' => 'decimal(6,3)',
  62. 'PDECIMAL:' => 'decimal(%d,3)',
  63. 'VCHAR_UNI' => 'varchar(255)',
  64. 'VCHAR_UNI:'=> 'varchar(%d)',
  65. 'VCHAR_CI' => 'varchar(255)',
  66. 'VARBINARY' => 'varbinary(255)',
  67. ),
  68. 'mysql_40' => array(
  69. 'INT:' => 'int(%d)',
  70. 'BINT' => 'bigint(20)',
  71. 'UINT' => 'mediumint(8) UNSIGNED',
  72. 'UINT:' => 'int(%d) UNSIGNED',
  73. 'TINT:' => 'tinyint(%d)',
  74. 'USINT' => 'smallint(4) UNSIGNED',
  75. 'BOOL' => 'tinyint(1) UNSIGNED',
  76. 'VCHAR' => 'varbinary(255)',
  77. 'VCHAR:' => 'varbinary(%d)',
  78. 'CHAR:' => 'binary(%d)',
  79. 'XSTEXT' => 'blob',
  80. 'XSTEXT_UNI'=> 'blob',
  81. 'STEXT' => 'blob',
  82. 'STEXT_UNI' => 'blob',
  83. 'TEXT' => 'blob',
  84. 'TEXT_UNI' => 'blob',
  85. 'MTEXT' => 'mediumblob',
  86. 'MTEXT_UNI' => 'mediumblob',
  87. 'TIMESTAMP' => 'int(11) UNSIGNED',
  88. 'DECIMAL' => 'decimal(5,2)',
  89. 'DECIMAL:' => 'decimal(%d,2)',
  90. 'PDECIMAL' => 'decimal(6,3)',
  91. 'PDECIMAL:' => 'decimal(%d,3)',
  92. 'VCHAR_UNI' => 'blob',
  93. 'VCHAR_UNI:'=> array('varbinary(%d)', 'limit' => array('mult', 3, 255, 'blob')),
  94. 'VCHAR_CI' => 'blob',
  95. 'VARBINARY' => 'varbinary(255)',
  96. ),
  97. 'firebird' => array(
  98. 'INT:' => 'INTEGER',
  99. 'BINT' => 'DOUBLE PRECISION',
  100. 'UINT' => 'INTEGER',
  101. 'UINT:' => 'INTEGER',
  102. 'TINT:' => 'INTEGER',
  103. 'USINT' => 'INTEGER',
  104. 'BOOL' => 'INTEGER',
  105. 'VCHAR' => 'VARCHAR(255) CHARACTER SET NONE',
  106. 'VCHAR:' => 'VARCHAR(%d) CHARACTER SET NONE',
  107. 'CHAR:' => 'CHAR(%d) CHARACTER SET NONE',
  108. 'XSTEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE',
  109. 'STEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE',
  110. 'TEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE',
  111. 'MTEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE',
  112. 'XSTEXT_UNI'=> 'VARCHAR(100) CHARACTER SET UTF8',
  113. 'STEXT_UNI' => 'VARCHAR(255) CHARACTER SET UTF8',
  114. 'TEXT_UNI' => 'BLOB SUB_TYPE TEXT CHARACTER SET UTF8',
  115. 'MTEXT_UNI' => 'BLOB SUB_TYPE TEXT CHARACTER SET UTF8',
  116. 'TIMESTAMP' => 'INTEGER',
  117. 'DECIMAL' => 'DOUBLE PRECISION',
  118. 'DECIMAL:' => 'DOUBLE PRECISION',
  119. 'PDECIMAL' => 'DOUBLE PRECISION',
  120. 'PDECIMAL:' => 'DOUBLE PRECISION',
  121. 'VCHAR_UNI' => 'VARCHAR(255) CHARACTER SET UTF8',
  122. 'VCHAR_UNI:'=> 'VARCHAR(%d) CHARACTER SET UTF8',
  123. 'VCHAR_CI' => 'VARCHAR(255) CHARACTER SET UTF8',
  124. 'VARBINARY' => 'CHAR(255) CHARACTER SET NONE',
  125. ),
  126. 'mssql' => array(
  127. 'INT:' => '[int]',
  128. 'BINT' => '[float]',
  129. 'UINT' => '[int]',
  130. 'UINT:' => '[int]',
  131. 'TINT:' => '[int]',
  132. 'USINT' => '[int]',
  133. 'BOOL' => '[int]',
  134. 'VCHAR' => '[varchar] (255)',
  135. 'VCHAR:' => '[varchar] (%d)',
  136. 'CHAR:' => '[char] (%d)',
  137. 'XSTEXT' => '[varchar] (1000)',
  138. 'STEXT' => '[varchar] (3000)',
  139. 'TEXT' => '[varchar] (8000)',
  140. 'MTEXT' => '[text]',
  141. 'XSTEXT_UNI'=> '[varchar] (100)',
  142. 'STEXT_UNI' => '[varchar] (255)',
  143. 'TEXT_UNI' => '[varchar] (4000)',
  144. 'MTEXT_UNI' => '[text]',
  145. 'TIMESTAMP' => '[int]',
  146. 'DECIMAL' => '[float]',
  147. 'DECIMAL:' => '[float]',
  148. 'PDECIMAL' => '[float]',
  149. 'PDECIMAL:' => '[float]',
  150. 'VCHAR_UNI' => '[varchar] (255)',
  151. 'VCHAR_UNI:'=> '[varchar] (%d)',
  152. 'VCHAR_CI' => '[varchar] (255)',
  153. 'VARBINARY' => '[varchar] (255)',
  154. ),
  155. 'mssqlnative' => array(
  156. 'INT:' => '[int]',
  157. 'BINT' => '[float]',
  158. 'UINT' => '[int]',
  159. 'UINT:' => '[int]',
  160. 'TINT:' => '[int]',
  161. 'USINT' => '[int]',
  162. 'BOOL' => '[int]',
  163. 'VCHAR' => '[varchar] (255)',
  164. 'VCHAR:' => '[varchar] (%d)',
  165. 'CHAR:' => '[char] (%d)',
  166. 'XSTEXT' => '[varchar] (1000)',
  167. 'STEXT' => '[varchar] (3000)',
  168. 'TEXT' => '[varchar] (8000)',
  169. 'MTEXT' => '[text]',
  170. 'XSTEXT_UNI'=> '[varchar] (100)',
  171. 'STEXT_UNI' => '[varchar] (255)',
  172. 'TEXT_UNI' => '[varchar] (4000)',
  173. 'MTEXT_UNI' => '[text]',
  174. 'TIMESTAMP' => '[int]',
  175. 'DECIMAL' => '[float]',
  176. 'DECIMAL:' => '[float]',
  177. 'PDECIMAL' => '[float]',
  178. 'PDECIMAL:' => '[float]',
  179. 'VCHAR_UNI' => '[varchar] (255)',
  180. 'VCHAR_UNI:'=> '[varchar] (%d)',
  181. 'VCHAR_CI' => '[varchar] (255)',
  182. 'VARBINARY' => '[varchar] (255)',
  183. ),
  184. 'oracle' => array(
  185. 'INT:' => 'number(%d)',
  186. 'BINT' => 'number(20)',
  187. 'UINT' => 'number(8)',
  188. 'UINT:' => 'number(%d)',
  189. 'TINT:' => 'number(%d)',
  190. 'USINT' => 'number(4)',
  191. 'BOOL' => 'number(1)',
  192. 'VCHAR' => 'varchar2(255)',
  193. 'VCHAR:' => 'varchar2(%d)',
  194. 'CHAR:' => 'char(%d)',
  195. 'XSTEXT' => 'varchar2(1000)',
  196. 'STEXT' => 'varchar2(3000)',
  197. 'TEXT' => 'clob',
  198. 'MTEXT' => 'clob',
  199. 'XSTEXT_UNI'=> 'varchar2(300)',
  200. 'STEXT_UNI' => 'varchar2(765)',
  201. 'TEXT_UNI' => 'clob',
  202. 'MTEXT_UNI' => 'clob',
  203. 'TIMESTAMP' => 'number(11)',
  204. 'DECIMAL' => 'number(5, 2)',
  205. 'DECIMAL:' => 'number(%d, 2)',
  206. 'PDECIMAL' => 'number(6, 3)',
  207. 'PDECIMAL:' => 'number(%d, 3)',
  208. 'VCHAR_UNI' => 'varchar2(765)',
  209. 'VCHAR_UNI:'=> array('varchar2(%d)', 'limit' => array('mult', 3, 765, 'clob')),
  210. 'VCHAR_CI' => 'varchar2(255)',
  211. 'VARBINARY' => 'raw(255)',
  212. ),
  213. 'sqlite' => array(
  214. 'INT:' => 'int(%d)',
  215. 'BINT' => 'bigint(20)',
  216. 'UINT' => 'INTEGER UNSIGNED', //'mediumint(8) UNSIGNED',
  217. 'UINT:' => 'INTEGER UNSIGNED', // 'int(%d) UNSIGNED',
  218. 'TINT:' => 'tinyint(%d)',
  219. 'USINT' => 'INTEGER UNSIGNED', //'mediumint(4) UNSIGNED',
  220. 'BOOL' => 'INTEGER UNSIGNED', //'tinyint(1) UNSIGNED',
  221. 'VCHAR' => 'varchar(255)',
  222. 'VCHAR:' => 'varchar(%d)',
  223. 'CHAR:' => 'char(%d)',
  224. 'XSTEXT' => 'text(65535)',
  225. 'STEXT' => 'text(65535)',
  226. 'TEXT' => 'text(65535)',
  227. 'MTEXT' => 'mediumtext(16777215)',
  228. 'XSTEXT_UNI'=> 'text(65535)',
  229. 'STEXT_UNI' => 'text(65535)',
  230. 'TEXT_UNI' => 'text(65535)',
  231. 'MTEXT_UNI' => 'mediumtext(16777215)',
  232. 'TIMESTAMP' => 'INTEGER UNSIGNED', //'int(11) UNSIGNED',
  233. 'DECIMAL' => 'decimal(5,2)',
  234. 'DECIMAL:' => 'decimal(%d,2)',
  235. 'PDECIMAL' => 'decimal(6,3)',
  236. 'PDECIMAL:' => 'decimal(%d,3)',
  237. 'VCHAR_UNI' => 'varchar(255)',
  238. 'VCHAR_UNI:'=> 'varchar(%d)',
  239. 'VCHAR_CI' => 'varchar(255)',
  240. 'VARBINARY' => 'blob',
  241. ),
  242. 'postgres' => array(
  243. 'INT:' => 'INT4',
  244. 'BINT' => 'INT8',
  245. 'UINT' => 'INT4', // unsigned
  246. 'UINT:' => 'INT4', // unsigned
  247. 'USINT' => 'INT2', // unsigned
  248. 'BOOL' => 'INT2', // unsigned
  249. 'TINT:' => 'INT2',
  250. 'VCHAR' => 'varchar(255)',
  251. 'VCHAR:' => 'varchar(%d)',
  252. 'CHAR:' => 'char(%d)',
  253. 'XSTEXT' => 'varchar(1000)',
  254. 'STEXT' => 'varchar(3000)',
  255. 'TEXT' => 'varchar(8000)',
  256. 'MTEXT' => 'TEXT',
  257. 'XSTEXT_UNI'=> 'varchar(100)',
  258. 'STEXT_UNI' => 'varchar(255)',
  259. 'TEXT_UNI' => 'varchar(4000)',
  260. 'MTEXT_UNI' => 'TEXT',
  261. 'TIMESTAMP' => 'INT4', // unsigned
  262. 'DECIMAL' => 'decimal(5,2)',
  263. 'DECIMAL:' => 'decimal(%d,2)',
  264. 'PDECIMAL' => 'decimal(6,3)',
  265. 'PDECIMAL:' => 'decimal(%d,3)',
  266. 'VCHAR_UNI' => 'varchar(255)',
  267. 'VCHAR_UNI:'=> 'varchar(%d)',
  268. 'VCHAR_CI' => 'varchar_ci',
  269. 'VARBINARY' => 'bytea',
  270. ),
  271. );
  272. /**
  273. * A list of types being unsigned for better reference in some db's
  274. * @var array
  275. */
  276. var $unsigned_types = array('UINT', 'UINT:', 'USINT', 'BOOL', 'TIMESTAMP');
  277. /**
  278. * A list of supported DBMS. We change this class to support more DBMS, the DBMS itself only need to follow some rules.
  279. * @var array
  280. */
  281. var $supported_dbms = array('firebird', 'mssql', 'mssqlnative', 'mysql_40', 'mysql_41', 'oracle', 'postgres', 'sqlite');
  282. /**
  283. * This is set to true if user only wants to return the 'to-be-executed' SQL statement(s) (as an array).
  284. * This mode has no effect on some methods (inserting of data for example). This is expressed within the methods command.
  285. */
  286. var $return_statements = false;
  287. /**
  288. * Constructor. Set DB Object and set {@link $return_statements return_statements}.
  289. *
  290. * @param phpbb_dbal $db DBAL object
  291. * @param bool $return_statements True if only statements should be returned and no SQL being executed
  292. */
  293. function phpbb_db_tools(&$db, $return_statements = false)
  294. {
  295. $this->db = $db;
  296. $this->return_statements = $return_statements;
  297. // Determine mapping database type
  298. switch ($this->db->sql_layer)
  299. {
  300. case 'mysql':
  301. $this->sql_layer = 'mysql_40';
  302. break;
  303. case 'mysql4':
  304. if (version_compare($this->db->sql_server_info(true), '4.1.3', '>='))
  305. {
  306. $this->sql_layer = 'mysql_41';
  307. }
  308. else
  309. {
  310. $this->sql_layer = 'mysql_40';
  311. }
  312. break;
  313. case 'mysqli':
  314. $this->sql_layer = 'mysql_41';
  315. break;
  316. case 'mssql':
  317. case 'mssql_odbc':
  318. $this->sql_layer = 'mssql';
  319. break;
  320. case 'mssqlnative':
  321. $this->sql_layer = 'mssqlnative';
  322. break;
  323. default:
  324. $this->sql_layer = $this->db->sql_layer;
  325. break;
  326. }
  327. }
  328. /**
  329. * Check if table exists
  330. *
  331. *
  332. * @param string $table_name The table name to check for
  333. * @return bool true if table exists, else false
  334. */
  335. function sql_table_exists($table_name)
  336. {
  337. $this->db->sql_return_on_error(true);
  338. $result = $this->db->sql_query_limit('SELECT * FROM ' . $table_name, 1);
  339. $this->db->sql_return_on_error(false);
  340. if ($result)
  341. {
  342. $this->db->sql_freeresult($result);
  343. return true;
  344. }
  345. return false;
  346. }
  347. /**
  348. * Create SQL Table
  349. *
  350. * @param string $table_name The table name to create
  351. * @param array $table_data Array containing table data.
  352. * @return array Statements if $return_statements is true.
  353. */
  354. function sql_create_table($table_name, $table_data)
  355. {
  356. // holds the DDL for a column
  357. $columns = $statements = array();
  358. if ($this->sql_table_exists($table_name))
  359. {
  360. return $this->_sql_run_sql($statements);
  361. }
  362. // Begin transaction
  363. $statements[] = 'begin';
  364. // Determine if we have created a PRIMARY KEY in the earliest
  365. $primary_key_gen = false;
  366. // Determine if the table must be created with TEXTIMAGE
  367. $create_textimage = false;
  368. // Determine if the table requires a sequence
  369. $create_sequence = false;
  370. // Begin table sql statement
  371. switch ($this->sql_layer)
  372. {
  373. case 'mssql':
  374. case 'mssqlnative':
  375. $table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n";
  376. break;
  377. default:
  378. $table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n";
  379. break;
  380. }
  381. // Iterate through the columns to create a table
  382. foreach ($table_data['COLUMNS'] as $column_name => $column_data)
  383. {
  384. // here lies an array, filled with information compiled on the column's data
  385. $prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
  386. if (isset($prepared_column['auto_increment']) && strlen($column_name) > 26) // "${column_name}_gen"
  387. {
  388. trigger_error("Index name '${column_name}_gen' on table '$table_name' is too long. The maximum auto increment column length is 26 characters.", E_USER_ERROR);
  389. }
  390. // here we add the definition of the new column to the list of columns
  391. switch ($this->sql_layer)
  392. {
  393. case 'mssql':
  394. case 'mssqlnative':
  395. $columns[] = "\t [{$column_name}] " . $prepared_column['column_type_sql_default'];
  396. break;
  397. default:
  398. $columns[] = "\t {$column_name} " . $prepared_column['column_type_sql'];
  399. break;
  400. }
  401. // see if we have found a primary key set due to a column definition if we have found it, we can stop looking
  402. if (!$primary_key_gen)
  403. {
  404. $primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set'];
  405. }
  406. // create textimage DDL based off of the existance of certain column types
  407. if (!$create_textimage)
  408. {
  409. $create_textimage = isset($prepared_column['textimage']) && $prepared_column['textimage'];
  410. }
  411. // create sequence DDL based off of the existance of auto incrementing columns
  412. if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'])
  413. {
  414. $create_sequence = $column_name;
  415. }
  416. }
  417. // this makes up all the columns in the create table statement
  418. $table_sql .= implode(",\n", $columns);
  419. // Close the table for two DBMS and add to the statements
  420. switch ($this->sql_layer)
  421. {
  422. case 'firebird':
  423. $table_sql .= "\n);";
  424. $statements[] = $table_sql;
  425. break;
  426. case 'mssql':
  427. case 'mssqlnative':
  428. $table_sql .= "\n) ON [PRIMARY]" . (($create_textimage) ? ' TEXTIMAGE_ON [PRIMARY]' : '');
  429. $statements[] = $table_sql;
  430. break;
  431. }
  432. // we have yet to create a primary key for this table,
  433. // this means that we can add the one we really wanted instead
  434. if (!$primary_key_gen)
  435. {
  436. // Write primary key
  437. if (isset($table_data['PRIMARY_KEY']))
  438. {
  439. if (!is_array($table_data['PRIMARY_KEY']))
  440. {
  441. $table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']);
  442. }
  443. switch ($this->sql_layer)
  444. {
  445. case 'mysql_40':
  446. case 'mysql_41':
  447. case 'postgres':
  448. case 'sqlite':
  449. $table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')';
  450. break;
  451. case 'firebird':
  452. case 'mssql':
  453. case 'mssqlnative':
  454. // We need the data here
  455. $old_return_statements = $this->return_statements;
  456. $this->return_statements = true;
  457. $primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']);
  458. foreach ($primary_key_stmts as $pk_stmt)
  459. {
  460. $statements[] = $pk_stmt;
  461. }
  462. $this->return_statements = $old_return_statements;
  463. break;
  464. case 'oracle':
  465. $table_sql .= ",\n\t CONSTRAINT pk_{$table_name} PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')';
  466. break;
  467. }
  468. }
  469. }
  470. // close the table
  471. switch ($this->sql_layer)
  472. {
  473. case 'mysql_41':
  474. // make sure the table is in UTF-8 mode
  475. $table_sql .= "\n) CHARACTER SET `utf8` COLLATE `utf8_bin`;";
  476. $statements[] = $table_sql;
  477. break;
  478. case 'mysql_40':
  479. case 'sqlite':
  480. $table_sql .= "\n);";
  481. $statements[] = $table_sql;
  482. break;
  483. case 'postgres':
  484. // do we need to add a sequence for auto incrementing columns?
  485. if ($create_sequence)
  486. {
  487. $statements[] = "CREATE SEQUENCE {$table_name}_seq;";
  488. }
  489. $table_sql .= "\n);";
  490. $statements[] = $table_sql;
  491. break;
  492. case 'oracle':
  493. $table_sql .= "\n)";
  494. $statements[] = $table_sql;
  495. // do we need to add a sequence and a tigger for auto incrementing columns?
  496. if ($create_sequence)
  497. {
  498. // create the actual sequence
  499. $statements[] = "CREATE SEQUENCE {$table_name}_seq";
  500. // the trigger is the mechanism by which we increment the counter
  501. $trigger = "CREATE OR REPLACE TRIGGER t_{$table_name}\n";
  502. $trigger .= "BEFORE INSERT ON {$table_name}\n";
  503. $trigger .= "FOR EACH ROW WHEN (\n";
  504. $trigger .= "\tnew.{$create_sequence} IS NULL OR new.{$create_sequence} = 0\n";
  505. $trigger .= ")\n";
  506. $trigger .= "BEGIN\n";
  507. $trigger .= "\tSELECT {$table_name}_seq.nextval\n";
  508. $trigger .= "\tINTO :new.{$create_sequence}\n";
  509. $trigger .= "\tFROM dual;\n";
  510. $trigger .= "END;";
  511. $statements[] = $trigger;
  512. }
  513. break;
  514. case 'firebird':
  515. if ($create_sequence)
  516. {
  517. $statements[] = "CREATE GENERATOR {$table_name}_gen;";
  518. $statements[] = "SET GENERATOR {$table_name}_gen TO 0;";
  519. $trigger = "CREATE TRIGGER t_$table_name FOR $table_name\n";
  520. $trigger .= "BEFORE INSERT\nAS\nBEGIN\n";
  521. $trigger .= "\tNEW.{$create_sequence} = GEN_ID({$table_name}_gen, 1);\nEND;";
  522. $statements[] = $trigger;
  523. }
  524. break;
  525. }
  526. // Write Keys
  527. if (isset($table_data['KEYS']))
  528. {
  529. foreach ($table_data['KEYS'] as $key_name => $key_data)
  530. {
  531. if (!is_array($key_data[1]))
  532. {
  533. $key_data[1] = array($key_data[1]);
  534. }
  535. $old_return_statements = $this->return_statements;
  536. $this->return_statements = true;
  537. $key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]);
  538. foreach ($key_stmts as $key_stmt)
  539. {
  540. $statements[] = $key_stmt;
  541. }
  542. $this->return_statements = $old_return_statements;
  543. }
  544. }
  545. // Commit Transaction
  546. $statements[] = 'commit';
  547. return $this->_sql_run_sql($statements);
  548. }
  549. /**
  550. * Handle passed database update array.
  551. * Expected structure...
  552. * Key being one of the following
  553. * change_columns: Column changes (only type, not name)
  554. * add_columns: Add columns to a table
  555. * drop_keys: Dropping keys
  556. * drop_columns: Removing/Dropping columns
  557. * add_primary_keys: adding primary keys
  558. * add_unique_index: adding an unique index
  559. * add_index: adding an index (can be column:index_size if you need to provide size)
  560. *
  561. * The values are in this format:
  562. * {TABLE NAME} => array(
  563. * {COLUMN NAME} => array({COLUMN TYPE}, {DEFAULT VALUE}, {OPTIONAL VARIABLES}),
  564. * {KEY/INDEX NAME} => array({COLUMN NAMES}),
  565. * )
  566. *
  567. * For more information have a look at /develop/create_schema_files.php (only available through SVN)
  568. */
  569. function perform_schema_changes($schema_changes)
  570. {
  571. if (empty($schema_changes))
  572. {
  573. return;
  574. }
  575. $statements = array();
  576. $sqlite = false;
  577. // For SQLite we need to perform the schema changes in a much more different way
  578. if ($this->db->sql_layer == 'sqlite' && $this->return_statements)
  579. {
  580. $sqlite_data = array();
  581. $sqlite = true;
  582. }
  583. // Add tables?
  584. if (!empty($schema_changes['add_tables']))
  585. {
  586. foreach ($schema_changes['add_tables'] as $table => $table_data)
  587. {
  588. $result = $this->sql_create_table($table, $table_data);
  589. if ($this->return_statements)
  590. {
  591. $statements = array_merge($statements, $result);
  592. }
  593. }
  594. }
  595. // Change columns?
  596. if (!empty($schema_changes['change_columns']))
  597. {
  598. foreach ($schema_changes['change_columns'] as $table => $columns)
  599. {
  600. foreach ($columns as $column_name => $column_data)
  601. {
  602. // If the column exists we change it, else we add it ;)
  603. if ($column_exists = $this->sql_column_exists($table, $column_name))
  604. {
  605. $result = $this->sql_column_change($table, $column_name, $column_data, true);
  606. }
  607. else
  608. {
  609. $result = $this->sql_column_add($table, $column_name, $column_data, true);
  610. }
  611. if ($sqlite)
  612. {
  613. if ($column_exists)
  614. {
  615. $sqlite_data[$table]['change_columns'][] = $result;
  616. }
  617. else
  618. {
  619. $sqlite_data[$table]['add_columns'][] = $result;
  620. }
  621. }
  622. else if ($this->return_statements)
  623. {
  624. $statements = array_merge($statements, $result);
  625. }
  626. }
  627. }
  628. }
  629. // Add columns?
  630. if (!empty($schema_changes['add_columns']))
  631. {
  632. foreach ($schema_changes['add_columns'] as $table => $columns)
  633. {
  634. foreach ($columns as $column_name => $column_data)
  635. {
  636. // Only add the column if it does not exist yet
  637. if ($column_exists = $this->sql_column_exists($table, $column_name))
  638. {
  639. continue;
  640. // This is commented out here because it can take tremendous time on updates
  641. // $result = $this->sql_column_change($table, $column_name, $column_data, true);
  642. }
  643. else
  644. {
  645. $result = $this->sql_column_add($table, $column_name, $column_data, true);
  646. }
  647. if ($sqlite)
  648. {
  649. if ($column_exists)
  650. {
  651. continue;
  652. // $sqlite_data[$table]['change_columns'][] = $result;
  653. }
  654. else
  655. {
  656. $sqlite_data[$table]['add_columns'][] = $result;
  657. }
  658. }
  659. else if ($this->return_statements)
  660. {
  661. $statements = array_merge($statements, $result);
  662. }
  663. }
  664. }
  665. }
  666. // Remove keys?
  667. if (!empty($schema_changes['drop_keys']))
  668. {
  669. foreach ($schema_changes['drop_keys'] as $table => $indexes)
  670. {
  671. foreach ($indexes as $index_name)
  672. {
  673. if (!$this->sql_index_exists($table, $index_name))
  674. {
  675. continue;
  676. }
  677. $result = $this->sql_index_drop($table, $index_name);
  678. if ($this->return_statements)
  679. {
  680. $statements = array_merge($statements, $result);
  681. }
  682. }
  683. }
  684. }
  685. // Drop columns?
  686. if (!empty($schema_changes['drop_columns']))
  687. {
  688. foreach ($schema_changes['drop_columns'] as $table => $columns)
  689. {
  690. foreach ($columns as $column)
  691. {
  692. // Only remove the column if it exists...
  693. if ($this->sql_column_exists($table, $column))
  694. {
  695. $result = $this->sql_column_remove($table, $column, true);
  696. if ($sqlite)
  697. {
  698. $sqlite_data[$table]['drop_columns'][] = $result;
  699. }
  700. else if ($this->return_statements)
  701. {
  702. $statements = array_merge($statements, $result);
  703. }
  704. }
  705. }
  706. }
  707. }
  708. // Add primary keys?
  709. if (!empty($schema_changes['add_primary_keys']))
  710. {
  711. foreach ($schema_changes['add_primary_keys'] as $table => $columns)
  712. {
  713. $result = $this->sql_create_primary_key($table, $columns, true);
  714. if ($sqlite)
  715. {
  716. $sqlite_data[$table]['primary_key'] = $result;
  717. }
  718. else if ($this->return_statements)
  719. {
  720. $statements = array_merge($statements, $result);
  721. }
  722. }
  723. }
  724. // Add unqiue indexes?
  725. if (!empty($schema_changes['add_unique_index']))
  726. {
  727. foreach ($schema_changes['add_unique_index'] as $table => $index_array)
  728. {
  729. foreach ($index_array as $index_name => $column)
  730. {
  731. if ($this->sql_unique_index_exists($table, $index_name))
  732. {
  733. continue;
  734. }
  735. $result = $this->sql_create_unique_index($table, $index_name, $column);
  736. if ($this->return_statements)
  737. {
  738. $statements = array_merge($statements, $result);
  739. }
  740. }
  741. }
  742. }
  743. // Add indexes?
  744. if (!empty($schema_changes['add_index']))
  745. {
  746. foreach ($schema_changes['add_index'] as $table => $index_array)
  747. {
  748. foreach ($index_array as $index_name => $column)
  749. {
  750. if ($this->sql_index_exists($table, $index_name))
  751. {
  752. continue;
  753. }
  754. $result = $this->sql_create_index($table, $index_name, $column);
  755. if ($this->return_statements)
  756. {
  757. $statements = array_merge($statements, $result);
  758. }
  759. }
  760. }
  761. }
  762. if ($sqlite)
  763. {
  764. foreach ($sqlite_data as $table_name => $sql_schema_changes)
  765. {
  766. // Create temporary table with original data
  767. $statements[] = 'begin';
  768. $sql = "SELECT sql
  769. FROM sqlite_master
  770. WHERE type = 'table'
  771. AND name = '{$table_name}'
  772. ORDER BY type DESC, name;";
  773. $result = $this->db->sql_query($sql);
  774. if (!$result)
  775. {
  776. continue;
  777. }
  778. $row = $this->db->sql_fetchrow($result);
  779. $this->db->sql_freeresult($result);
  780. // Create a backup table and populate it, destroy the existing one
  781. $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']);
  782. $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
  783. $statements[] = 'DROP TABLE ' . $table_name;
  784. // Get the columns...
  785. preg_match('#\((.*)\)#s', $row['sql'], $matches);
  786. $plain_table_cols = trim($matches[1]);
  787. $new_table_cols = preg_split('/,(?![\s\w]+\))/m', $plain_table_cols);
  788. $column_list = array();
  789. foreach ($new_table_cols as $declaration)
  790. {
  791. $entities = preg_split('#\s+#', trim($declaration));
  792. if ($entities[0] == 'PRIMARY')
  793. {
  794. continue;
  795. }
  796. $column_list[] = $entities[0];
  797. }
  798. // note down the primary key notation because sqlite only supports adding it to the end for the new table
  799. $primary_key = false;
  800. $_new_cols = array();
  801. foreach ($new_table_cols as $key => $declaration)
  802. {
  803. $entities = preg_split('#\s+#', trim($declaration));
  804. if ($entities[0] == 'PRIMARY')
  805. {
  806. $primary_key = $declaration;
  807. continue;
  808. }
  809. $_new_cols[] = $declaration;
  810. }
  811. $new_table_cols = $_new_cols;
  812. // First of all... change columns
  813. if (!empty($sql_schema_changes['change_columns']))
  814. {
  815. foreach ($sql_schema_changes['change_columns'] as $column_sql)
  816. {
  817. foreach ($new_table_cols as $key => $declaration)
  818. {
  819. $entities = preg_split('#\s+#', trim($declaration));
  820. if (strpos($column_sql, $entities[0] . ' ') === 0)
  821. {
  822. $new_table_cols[$key] = $column_sql;
  823. }
  824. }
  825. }
  826. }
  827. if (!empty($sql_schema_changes['add_columns']))
  828. {
  829. foreach ($sql_schema_changes['add_columns'] as $column_sql)
  830. {
  831. $new_table_cols[] = $column_sql;
  832. }
  833. }
  834. // Now drop them...
  835. if (!empty($sql_schema_changes['drop_columns']))
  836. {
  837. foreach ($sql_schema_changes['drop_columns'] as $column_name)
  838. {
  839. // Remove from column list...
  840. $new_column_list = array();
  841. foreach ($column_list as $key => $value)
  842. {
  843. if ($value === $column_name)
  844. {
  845. continue;
  846. }
  847. $new_column_list[] = $value;
  848. }
  849. $column_list = $new_column_list;
  850. // Remove from table...
  851. $_new_cols = array();
  852. foreach ($new_table_cols as $key => $declaration)
  853. {
  854. $entities = preg_split('#\s+#', trim($declaration));
  855. if (strpos($column_name . ' ', $entities[0] . ' ') === 0)
  856. {
  857. continue;
  858. }
  859. $_new_cols[] = $declaration;
  860. }
  861. $new_table_cols = $_new_cols;
  862. }
  863. }
  864. // Primary key...
  865. if (!empty($sql_schema_changes['primary_key']))
  866. {
  867. $new_table_cols[] = 'PRIMARY KEY (' . implode(', ', $sql_schema_changes['primary_key']) . ')';
  868. }
  869. // Add a new one or the old primary key
  870. else if ($primary_key !== false)
  871. {
  872. $new_table_cols[] = $primary_key;
  873. }
  874. $columns = implode(',', $column_list);
  875. // create a new table and fill it up. destroy the temp one
  876. $statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $new_table_cols) . ');';
  877. $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
  878. $statements[] = 'DROP TABLE ' . $table_name . '_temp';
  879. $statements[] = 'commit';
  880. }
  881. }
  882. if ($this->return_statements)
  883. {
  884. return $statements;
  885. }
  886. }
  887. /**
  888. * Check if a specified column exist
  889. *
  890. * @param string $table Table to check the column at
  891. * @param string $column_name The column to check
  892. *
  893. * @return bool True if column exists, else false
  894. */
  895. function sql_column_exists($table, $column_name)
  896. {
  897. switch ($this->sql_layer)
  898. {
  899. case 'mysql_40':
  900. case 'mysql_41':
  901. $sql = "SHOW COLUMNS FROM $table";
  902. $result = $this->db->sql_query($sql);
  903. while ($row = $this->db->sql_fetchrow($result))
  904. {
  905. // lower case just in case
  906. if (strtolower($row['Field']) == $column_name)
  907. {
  908. $this->db->sql_freeresult($result);
  909. return true;
  910. }
  911. }
  912. $this->db->sql_freeresult($result);
  913. return false;
  914. break;
  915. // PostgreSQL has a way of doing this in a much simpler way but would
  916. // not allow us to support all versions of PostgreSQL
  917. case 'postgres':
  918. $sql = "SELECT a.attname
  919. FROM pg_class c, pg_attribute a
  920. WHERE c.relname = '{$table}'
  921. AND a.attnum > 0
  922. AND a.attrelid = c.oid";
  923. $result = $this->db->sql_query($sql);
  924. while ($row = $this->db->sql_fetchrow($result))
  925. {
  926. // lower case just in case
  927. if (strtolower($row['attname']) == $column_name)
  928. {
  929. $this->db->sql_freeresult($result);
  930. return true;
  931. }
  932. }
  933. $this->db->sql_freeresult($result);
  934. return false;
  935. break;
  936. // same deal with PostgreSQL, we must perform more complex operations than
  937. // we technically could
  938. case 'mssql':
  939. case 'mssqlnative':
  940. $sql = "SELECT c.name
  941. FROM syscolumns c
  942. LEFT JOIN sysobjects o ON c.id = o.id
  943. WHERE o.name = '{$table}'";
  944. $result = $this->db->sql_query($sql);
  945. while ($row = $this->db->sql_fetchrow($result))
  946. {
  947. // lower case just in case
  948. if (strtolower($row['name']) == $column_name)
  949. {
  950. $this->db->sql_freeresult($result);
  951. return true;
  952. }
  953. }
  954. $this->db->sql_freeresult($result);
  955. return false;
  956. break;
  957. case 'oracle':
  958. $sql = "SELECT column_name
  959. FROM user_tab_columns
  960. WHERE LOWER(table_name) = '" . strtolower($table) . "'";
  961. $result = $this->db->sql_query($sql);
  962. while ($row = $this->db->sql_fetchrow($result))
  963. {
  964. // lower case just in case
  965. if (strtolower($row['column_name']) == $column_name)
  966. {
  967. $this->db->sql_freeresult($result);
  968. return true;
  969. }
  970. }
  971. $this->db->sql_freeresult($result);
  972. return false;
  973. break;
  974. case 'firebird':
  975. $sql = "SELECT RDB\$FIELD_NAME as FNAME
  976. FROM RDB\$RELATION_FIELDS
  977. WHERE RDB\$RELATION_NAME = '" . strtoupper($table) . "'";
  978. $result = $this->db->sql_query($sql);
  979. while ($row = $this->db->sql_fetchrow($result))
  980. {
  981. // lower case just in case
  982. if (strtolower($row['fname']) == $column_name)
  983. {
  984. $this->db->sql_freeresult($result);
  985. return true;
  986. }
  987. }
  988. $this->db->sql_freeresult($result);
  989. return false;
  990. break;
  991. // ugh, SQLite
  992. case 'sqlite':
  993. $sql = "SELECT sql
  994. FROM sqlite_master
  995. WHERE type = 'table'
  996. AND name = '{$table}'";
  997. $result = $this->db->sql_query($sql);
  998. if (!$result)
  999. {
  1000. return false;
  1001. }
  1002. $row = $this->db->sql_fetchrow($result);
  1003. $this->db->sql_freeresult($result);
  1004. preg_match('#\((.*)\)#s', $row['sql'], $matches);
  1005. $cols = trim($matches[1]);
  1006. $col_array = preg_split('/,(?![\s\w]+\))/m', $cols);
  1007. foreach ($col_array as $declaration)
  1008. {
  1009. $entities = preg_split('#\s+#', trim($declaration));
  1010. if ($entities[0] == 'PRIMARY')
  1011. {
  1012. continue;
  1013. }
  1014. if (strtolower($entities[0]) == $column_name)
  1015. {
  1016. return true;
  1017. }
  1018. }
  1019. return false;
  1020. break;
  1021. }
  1022. }
  1023. /**
  1024. * Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes.
  1025. *
  1026. * @param string $table_name Table to check the index at
  1027. * @param string $index_name The index name to check
  1028. *
  1029. * @return bool True if index exists, else false
  1030. */
  1031. function sql_index_exists($table_name, $index_name)
  1032. {
  1033. if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative')
  1034. {
  1035. $sql = "EXEC sp_statistics '$table_name'";
  1036. $result = $this->db->sql_query($sql);
  1037. while ($row = $this->db->sql_fetchrow($result))
  1038. {
  1039. if ($row['TYPE'] == 3)
  1040. {
  1041. if (strtolower($row['INDEX_NAME']) == strtolower($index_name))
  1042. {
  1043. $this->db->sql_freeresult($result);
  1044. return true;
  1045. }
  1046. }
  1047. }
  1048. $this->db->sql_freeresult($result);
  1049. return false;
  1050. }
  1051. switch ($this->sql_layer)
  1052. {
  1053. case 'firebird':
  1054. $sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name
  1055. FROM RDB\$INDICES
  1056. WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "'
  1057. AND RDB\$UNIQUE_FLAG IS NULL
  1058. AND RDB\$FOREIGN_KEY IS NULL";
  1059. $col = 'index_name';
  1060. break;
  1061. case 'postgres':
  1062. $sql = "SELECT ic.relname as index_name
  1063. FROM pg_class bc, pg_class ic, pg_index i
  1064. WHERE (bc.oid = i.indrelid)
  1065. AND (ic.oid = i.indexrelid)
  1066. AND (bc.relname = '" . $table_name . "')
  1067. AND (i.indisunique != 't')
  1068. AND (i.indisprimary != 't')";
  1069. $col = 'index_name';
  1070. break;
  1071. case 'mysql_40':
  1072. case 'mysql_41':
  1073. $sql = 'SHOW KEYS
  1074. FROM ' . $table_name;
  1075. $col = 'Key_name';
  1076. break;
  1077. case 'oracle':
  1078. $sql = "SELECT index_name
  1079. FROM user_indexes
  1080. WHERE table_name = '" . strtoupper($table_name) . "'
  1081. AND generated = 'N'
  1082. AND uniqueness = 'NONUNIQUE'";
  1083. $col = 'index_name';
  1084. break;
  1085. case 'sqlite':
  1086. $sql = "PRAGMA index_list('" . $table_name . "');";
  1087. $col = 'name';
  1088. break;
  1089. }
  1090. $result = $this->db->sql_query($sql);
  1091. while ($row = $this->db->sql_fetchrow($result))
  1092. {
  1093. if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique'])
  1094. {
  1095. continue;
  1096. }
  1097. // These DBMS prefix index name with the table name
  1098. switch ($this->sql_layer)
  1099. {
  1100. case 'firebird':
  1101. case 'oracle':
  1102. case 'postgres':
  1103. case 'sqlite':
  1104. $row[$col] = substr($row[$col], strlen($table_name) + 1);
  1105. break;
  1106. }
  1107. if (strtolower($row[$col]) == strtolower($index_name))
  1108. {
  1109. $this->db->sql_freeresult($result);
  1110. return true;
  1111. }
  1112. }
  1113. $this->db->sql_freeresult($result);
  1114. return false;
  1115. }
  1116. /**
  1117. * Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes.
  1118. *
  1119. * @param string $table_name Table to check the index at
  1120. * @param string $index_name The index name to check
  1121. *
  1122. * @return bool True if index exists, else false
  1123. */
  1124. function sql_unique_index_exists($table_name, $index_name)
  1125. {
  1126. if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative')
  1127. {
  1128. $sql = "EXEC sp_statistics '$table_name'";
  1129. $result = $this->db->sql_query($sql);
  1130. while ($row = $this->db->sql_fetchrow($result))
  1131. {
  1132. // Usually NON_UNIQUE is the column we want to check, but we allow for both
  1133. if ($row['TYPE'] == 3)
  1134. {
  1135. if (strtolower($row['INDEX_NAME']) == strtolower($index_name))
  1136. {
  1137. $this->db->sql_freeresult($result);
  1138. return true;
  1139. }
  1140. }
  1141. }
  1142. $this->db->sql_freeresult($result);
  1143. return false;
  1144. }
  1145. switch ($this->sql_layer)
  1146. {
  1147. case 'firebird':
  1148. $sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name
  1149. FROM RDB\$INDICES
  1150. WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "'
  1151. AND RDB\$UNIQUE_FLAG IS NOT NULL
  1152. AND RDB\$FOREIGN_KEY IS NULL";
  1153. $col = 'index_name';
  1154. break;
  1155. case 'postgres':
  1156. $sql = "SELECT ic.relname as index_name, i.indisunique
  1157. FROM pg_class bc, pg_class ic, pg_index i
  1158. WHERE (bc.oid = i.indrelid)
  1159. AND (ic.oid = i.indexrelid)
  1160. AND (bc.relname = '" . $table_name . "')
  1161. AND (i.indisprimary != 't')";
  1162. $col = 'index_name';
  1163. break;
  1164. case 'mysql_40':
  1165. case 'mysql_41':
  1166. $sql = 'SHOW KEYS
  1167. FROM ' . $table_name;
  1168. $col = 'Key_name';
  1169. break;
  1170. case 'oracle':
  1171. $sql = "SELECT index_name, table_owner
  1172. FROM user_indexes
  1173. WHERE table_name = '" . strtoupper($table_name) . "'
  1174. AND generated = 'N'
  1175. AND uniqueness = 'UNIQUE'";
  1176. $col = 'index_name';
  1177. break;
  1178. case 'sqlite':
  1179. $sql = "PRAGMA index_list('" . $table_name . "');";
  1180. $col = 'name';
  1181. break;
  1182. }
  1183. $result = $this->db->sql_query($sql);
  1184. while ($row = $this->db->sql_fetchrow($result))
  1185. {
  1186. if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && ($row['Non_unique'] || $row[$col] == 'PRIMARY'))
  1187. {
  1188. continue;
  1189. }
  1190. if ($this->sql_layer == 'sqlite' && !$row['unique'])
  1191. {
  1192. continue;
  1193. }
  1194. if ($this->sql_layer == 'postgres' && $row['indisunique'] != 't')
  1195. {
  1196. continue;
  1197. }
  1198. // These DBMS prefix index name with the table name
  1199. switch ($this->sql_layer)
  1200. {
  1201. case 'oracle':
  1202. // Two cases here... prefixed with U_[table_owner] and not prefixed with table_name
  1203. if (strpos($row[$col], 'U_') === 0)
  1204. {
  1205. $row[$col] = substr($row[$col], strlen('U_' . $row['table_owner']) + 1);
  1206. }
  1207. else if (strpos($row[$col], strtoupper($table_name)) === 0)
  1208. {
  1209. $row[$col] = substr($row[$col], strlen($table_name) + 1);
  1210. }
  1211. break;
  1212. case 'firebird':
  1213. case 'postgres':
  1214. case 'sqlite':
  1215. $row[$col] = substr($row[$col], strlen($table_name) + 1);
  1216. break;
  1217. }
  1218. if (strtolower($row[$col]) == strtolower($index_name))
  1219. {
  1220. $this->db->sql_freeresult($result);
  1221. return true;
  1222. }
  1223. }
  1224. $this->db->sql_freeresult($result);
  1225. return false;
  1226. }
  1227. /**
  1228. * Private method for performing sql statements (either execute them or return them)
  1229. * @access private
  1230. */
  1231. function _sql_run_sql($statements)
  1232. {
  1233. if ($this->return_statements)
  1234. {
  1235. return $statements;
  1236. }
  1237. // We could add error handling here...
  1238. foreach ($statements as $sql)
  1239. {
  1240. if ($sql === 'begin')
  1241. {
  1242. $this->db->sql_transaction('begin');
  1243. }
  1244. else if ($sql === 'commit')
  1245. {
  1246. $this->db->sql_transaction('commit');
  1247. }
  1248. else
  1249. {
  1250. $this->db->sql_query($sql);
  1251. }
  1252. }
  1253. return true;
  1254. }
  1255. /**
  1256. * Function to prepare some column information for better usage
  1257. * @access private
  1258. */
  1259. function sql_prepare_column_data($table_name, $column_name, $column_data)
  1260. {
  1261. if (strlen($column_name) > 30)
  1262. {
  1263. trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR);
  1264. }
  1265. // Get type
  1266. if (strpos($column_data[0], ':') !== false)
  1267. {
  1268. list($orig_column_type, $column_length) = explode(':', $column_data[0]);
  1269. if (!is_array($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']))
  1270. {
  1271. $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'], $column_length);
  1272. }
  1273. else
  1274. {
  1275. if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule']))
  1276. {
  1277. switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][0])
  1278. {
  1279. case 'div':
  1280. $column_length /= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][1];
  1281. $column_length = ceil($column_length);
  1282. $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length);
  1283. break;
  1284. }
  1285. }
  1286. if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit']))
  1287. {
  1288. switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][0])
  1289. {
  1290. case 'mult':
  1291. $column_length *= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][1];
  1292. if ($column_length > $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][2])
  1293. {
  1294. $column_type = $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][3];
  1295. }
  1296. else
  1297. {
  1298. $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length);
  1299. }
  1300. break;
  1301. }
  1302. }
  1303. }
  1304. $orig_column_type .= ':';
  1305. }
  1306. else
  1307. {
  1308. $orig_column_type = $column_data[0];
  1309. $column_type = $this->dbms_type_map[$this->sql_layer][$column_data[0]];
  1310. }
  1311. // Adjust default value if db-dependant specified
  1312. if (is_array($column_data[1]))
  1313. {
  1314. $column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default'];
  1315. }
  1316. $sql = '';
  1317. $return_array = array();
  1318. switch ($this->sql_layer)
  1319. {
  1320. case 'firebird':
  1321. $sql .= " {$column_type} ";
  1322. $return_array['column_type_sql_type'] = " {$column_type} ";
  1323. if (!is_null($column_data[1]))
  1324. {
  1325. $sql .= 'DEFAULT ' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ' ';
  1326. $return_array['column_type_sql_default'] = ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ' ';
  1327. }
  1328. $sql .= 'NOT NULL';
  1329. // This is a UNICODE column and thus should be given it's fair share
  1330. if (preg_match('/^X?STEXT_UNI|VCHAR_(CI|UNI:?)/', $column_data[0]))
  1331. {
  1332. $sql .= ' COLLATE UNICODE';
  1333. }
  1334. $return_array['auto_increment'] = false;
  1335. if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
  1336. {
  1337. $return_array['auto_increment'] = true;
  1338. }
  1339. break;
  1340. case 'mssql':
  1341. case 'mssqlnative':
  1342. $sql .= " {$column_type} ";
  1343. $sql_default = " {$column_type} ";
  1344. // For adding columns we need the default definition
  1345. if (!is_null($column_data[1]))
  1346. {
  1347. // For hexadecimal values do not use single quotes
  1348. if (strpos($column_data[1], '0x') === 0)
  1349. {
  1350. $return_array['default'] = 'DEFAULT (' . $column_data[1] . ') ';
  1351. $sql_default .= $return_array['default'];
  1352. }
  1353. else
  1354. {
  1355. $return_array['default'] = 'DEFAULT (' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ') ';
  1356. $sql_default .= $return_array['default'];
  1357. }
  1358. }
  1359. if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
  1360. {
  1361. // $sql .= 'IDENTITY (1, 1) ';
  1362. $sql_default .= 'IDENTITY (1, 1) ';
  1363. }
  1364. $return_array['textimage'] = $column_type === '[text]';
  1365. $sql .= 'NOT NULL';
  1366. $sql_default .= 'NOT NULL';
  1367. $return_array['column_type_sql_default'] = $sql_default;
  1368. break;
  1369. case 'mysql_40':
  1370. case 'mysql_41':
  1371. $sql .= " {$column_type} ";
  1372. // For hexadecimal values do not use single quotes
  1373. if (!is_null($column_data[1]) && substr($column_type, -4) !== 'text' && substr($column_type, -4) !== 'blob')
  1374. {
  1375. $sql .= (strpos($column_data[1], '0x') === 0) ? "DEFAULT {$column_data[1]} " : "DEFAULT '{$column_data[1]}' ";
  1376. }
  1377. $sql .= 'NOT NULL';
  1378. if (isset($column_data[2]))
  1379. {
  1380. if ($column_data[2] == 'auto_increment')
  1381. {
  1382. $sql .= ' auto_increment';
  1383. }
  1384. else if ($this->sql_layer === 'mysql_41' && $column_data[2] == 'true_sort')
  1385. {
  1386. $sql .= ' COLLATE utf8_unicode_ci';
  1387. }
  1388. }
  1389. break;
  1390. case 'oracle':
  1391. $sql .= " {$column_type} ";
  1392. $sql .= (!is_null($column_data[1])) ? "DEFAULT '{$column_data[1]}' " : '';
  1393. // In Oracle empty strings ('') are treated as NULL.
  1394. // Therefore in oracle we allow NULL's for all DEFAULT '' entries
  1395. // Oracle does not like setting NOT NULL on a column that is already NOT NULL (this happens only on number fields)
  1396. if (!preg_match('/number/i', $column_type))
  1397. {
  1398. $sql .= ($column_data[1] === '') ? '' : 'NOT NULL';
  1399. }
  1400. $return_array['auto_increment'] = false;
  1401. if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
  1402. {
  1403. $return_array['auto_increment'] = true;
  1404. }
  1405. break;
  1406. case 'postgres':
  1407. $return_array['column_type'] = $column_type;
  1408. $sql .= " {$column_type} ";
  1409. $return_array['auto_increment'] = false;
  1410. if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
  1411. {
  1412. $default_val = "nextval('{$table_name}_seq')";
  1413. $return_array['auto_increment'] = true;
  1414. }
  1415. else if (!is_null($column_data[1]))
  1416. {
  1417. $default_val = "'" . $column_data[1] . "'";
  1418. $return_array['null'] = 'NOT NULL';
  1419. $sql .= 'NOT NULL ';
  1420. }
  1421. $return_array['default'] = $default_val;
  1422. $sql .= "DEFAULT {$default_val}";
  1423. // Unsigned? Then add a CHECK contraint
  1424. if (in_array($orig_column_type, $this->unsigned_types))
  1425. {
  1426. $return_array['constraint'] = "CHECK ({$column_name} >= 0)";
  1427. $sql .= " CHECK ({$column_name} >= 0)";
  1428. }
  1429. break;
  1430. case 'sqlite':
  1431. $return_array['primary_key_set'] = false;
  1432. if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
  1433. {
  1434. $sql .= ' INTEGER PRIMARY KEY';
  1435. $return_array['primary_key_set'] = true;
  1436. }
  1437. else
  1438. {
  1439. $sql .= ' ' . $column_type;
  1440. }
  1441. $sql .= ' NOT NULL ';
  1442. $sql .= (!is_null($column_data[1])) ? "DEFAULT '{$column_data[1]}'" : '';
  1443. break;
  1444. }
  1445. $return_array['column_type_sql'] = $sql;
  1446. return $return_array;
  1447. }
  1448. /**
  1449. * Add new column
  1450. */
  1451. function sql_column_add($table_name, $column_name, $column_data, $inline = false)
  1452. {
  1453. $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
  1454. $statements = array();
  1455. switch ($this->sql_layer)
  1456. {
  1457. case 'firebird':
  1458. // Does not support AFTER statement, only POSITION (and there you need the column position)
  1459. $statements[] = 'ALTER TABLE ' . $table_name . ' ADD "' . strtoupper($column_name) . '" ' . $column_data['column_type_sql'];
  1460. break;
  1461. case 'mssql':
  1462. case 'mssqlnative':
  1463. // Does not support AFTER, only through temporary table
  1464. $statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default'];
  1465. break;
  1466. case 'mysql_40':
  1467. case 'mysql_41':
  1468. $after = (!empty($column_data['after'])) ? ' AFTER ' . $column_data['after'] : '';
  1469. $statements[] = 'ALTER TABLE `' . $table_name . '` ADD COLUMN `' . $column_name . '` ' . $column_data['column_type_sql'] . $after;
  1470. break;
  1471. case 'oracle':
  1472. // Does not support AFTER, only through temporary table
  1473. $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql'];
  1474. break;
  1475. case 'postgres':
  1476. // Does not support AFTER, only through temporary table
  1477. if (version_compare($this->db->sql_server_info(true), '8.0', '>='))
  1478. {
  1479. $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type_sql'];
  1480. }
  1481. else
  1482. {
  1483. // old versions cannot add columns with default and null information
  1484. $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type'] . ' ' . $column_data['constraint'];
  1485. if (isset($column_data['null']))
  1486. {
  1487. if ($column_data['null'] == 'NOT NULL')
  1488. {
  1489. $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET NOT NULL';
  1490. }
  1491. }
  1492. if (isset($column_data['default']))
  1493. {
  1494. $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default'];
  1495. }
  1496. }
  1497. break;
  1498. case 'sqlite':
  1499. if ($inline && $this->return_statements)
  1500. {
  1501. return $column_name . ' ' . $column_data['column_type_sql'];
  1502. }
  1503. if (version_compare(sqlite_libversion(), '3.0') == -1)
  1504. {
  1505. $sql = "SELECT sql
  1506. FROM sqlite_master
  1507. WHERE type = 'table'
  1508. AND name = '{$table_name}'
  1509. ORDER BY type DESC, name;";
  1510. $result = $this->db->sql_query($sql);
  1511. if (!$result)
  1512. {
  1513. break;
  1514. }
  1515. $row = $this->db->sql_fetchrow($result);
  1516. $this->db->sql_freeresult($result);
  1517. $statements[] = 'begin';
  1518. // Create a backup table and populate it, destroy the existing one
  1519. $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']);
  1520. $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
  1521. $statements[] = 'DROP TABLE ' . $table_name;
  1522. preg_match('#\((.*)\)#s', $row['sql'], $matches);
  1523. $new_table_cols = trim($matches[1]);
  1524. $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols);
  1525. $column_list = array();
  1526. foreach ($old_table_cols as $declaration)
  1527. {
  1528. $entities = preg_split('#\s+#', trim($declaration));
  1529. if ($entities[0] == 'PRIMARY')
  1530. {
  1531. continue;
  1532. }
  1533. $column_list[] = $entities[0];
  1534. }
  1535. $columns = implode(',', $column_list);
  1536. $new_table_cols = $column_name . ' ' . $column_data['column_type_sql'] . ',' . $new_table_cols;
  1537. // create a new table and fill it up. destroy the temp one
  1538. $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ');';
  1539. $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
  1540. $statements[] = 'DROP TABLE ' . $table_name . '_temp';
  1541. $statements[] = 'commit';
  1542. }
  1543. else
  1544. {
  1545. $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' [' . $column_data['column_type_sql'] . ']';
  1546. }
  1547. break;
  1548. }
  1549. return $this->_sql_run_sql($statements);
  1550. }
  1551. /**
  1552. * Drop column
  1553. */
  1554. function sql_column_remove($table_name, $column_name, $inline = false)
  1555. {
  1556. $statements = array();
  1557. switch ($this->sql_layer)
  1558. {
  1559. case 'firebird':
  1560. $statements[] = 'ALTER TABLE ' . $t

Large files files are truncated, but you can click here to view the full file