[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/includes/db/ -> db_tools.php (source)

   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  /**
  12  * @ignore
  13  */
  14  if (!defined('IN_PHPBB'))
  15  {
  16      exit;
  17  }
  18  
  19  /**
  20  * Database Tools for handling cross-db actions such as altering columns, etc.
  21  * Currently not supported is returning SQL for creating tables.
  22  *
  23  * @package dbal
  24  * @note currently not used within phpBB3, but may be utilized later.
  25  */
  26  class phpbb_db_tools
  27  {
  28      /**
  29      * Current sql layer
  30      */
  31      var $sql_layer = '';
  32  
  33      /**
  34      * @var object DB object
  35      */
  36      var $db = NULL;
  37  
  38      /**
  39      * The Column types for every database we support
  40      * @var array
  41      */
  42      var $dbms_type_map = array(
  43          'mysql_41'    => array(
  44              'INT:'        => 'int(%d)',
  45              'BINT'        => 'bigint(20)',
  46              'UINT'        => 'mediumint(8) UNSIGNED',
  47              'UINT:'        => 'int(%d) UNSIGNED',
  48              'TINT:'        => 'tinyint(%d)',
  49              'USINT'        => 'smallint(4) UNSIGNED',
  50              'BOOL'        => 'tinyint(1) UNSIGNED',
  51              'VCHAR'        => 'varchar(255)',
  52              'VCHAR:'    => 'varchar(%d)',
  53              'CHAR:'        => 'char(%d)',
  54              'XSTEXT'    => 'text',
  55              'XSTEXT_UNI'=> 'varchar(100)',
  56              'STEXT'        => 'text',
  57              'STEXT_UNI'    => 'varchar(255)',
  58              'TEXT'        => 'text',
  59              'TEXT_UNI'    => 'text',
  60              'MTEXT'        => 'mediumtext',
  61              'MTEXT_UNI'    => 'mediumtext',
  62              'TIMESTAMP'    => 'int(11) UNSIGNED',
  63              'DECIMAL'    => 'decimal(5,2)',
  64              'DECIMAL:'    => 'decimal(%d,2)',
  65              'PDECIMAL'    => 'decimal(6,3)',
  66              'PDECIMAL:'    => 'decimal(%d,3)',
  67              'VCHAR_UNI'    => 'varchar(255)',
  68              'VCHAR_UNI:'=> 'varchar(%d)',
  69              'VCHAR_CI'    => 'varchar(255)',
  70              'VARBINARY'    => 'varbinary(255)',
  71          ),
  72  
  73          'mysql_40'    => array(
  74              'INT:'        => 'int(%d)',
  75              'BINT'        => 'bigint(20)',
  76              'UINT'        => 'mediumint(8) UNSIGNED',
  77              'UINT:'        => 'int(%d) UNSIGNED',
  78              'TINT:'        => 'tinyint(%d)',
  79              'USINT'        => 'smallint(4) UNSIGNED',
  80              'BOOL'        => 'tinyint(1) UNSIGNED',
  81              'VCHAR'        => 'varbinary(255)',
  82              'VCHAR:'    => 'varbinary(%d)',
  83              'CHAR:'        => 'binary(%d)',
  84              'XSTEXT'    => 'blob',
  85              'XSTEXT_UNI'=> 'blob',
  86              'STEXT'        => 'blob',
  87              'STEXT_UNI'    => 'blob',
  88              'TEXT'        => 'blob',
  89              'TEXT_UNI'    => 'blob',
  90              'MTEXT'        => 'mediumblob',
  91              'MTEXT_UNI'    => 'mediumblob',
  92              'TIMESTAMP'    => 'int(11) UNSIGNED',
  93              'DECIMAL'    => 'decimal(5,2)',
  94              'DECIMAL:'    => 'decimal(%d,2)',
  95              'PDECIMAL'    => 'decimal(6,3)',
  96              'PDECIMAL:'    => 'decimal(%d,3)',
  97              'VCHAR_UNI'    => 'blob',
  98              'VCHAR_UNI:'=> array('varbinary(%d)', 'limit' => array('mult', 3, 255, 'blob')),
  99              'VCHAR_CI'    => 'blob',
 100              'VARBINARY'    => 'varbinary(255)',
 101          ),
 102  
 103          'firebird'    => array(
 104              'INT:'        => 'INTEGER',
 105              'BINT'        => 'DOUBLE PRECISION',
 106              'UINT'        => 'INTEGER',
 107              'UINT:'        => 'INTEGER',
 108              'TINT:'        => 'INTEGER',
 109              'USINT'        => 'INTEGER',
 110              'BOOL'        => 'INTEGER',
 111              'VCHAR'        => 'VARCHAR(255) CHARACTER SET NONE',
 112              'VCHAR:'    => 'VARCHAR(%d) CHARACTER SET NONE',
 113              'CHAR:'        => 'CHAR(%d) CHARACTER SET NONE',
 114              'XSTEXT'    => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE',
 115              'STEXT'        => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE',
 116              'TEXT'        => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE',
 117              'MTEXT'        => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE',
 118              'XSTEXT_UNI'=> 'VARCHAR(100) CHARACTER SET UTF8',
 119              'STEXT_UNI'    => 'VARCHAR(255) CHARACTER SET UTF8',
 120              'TEXT_UNI'    => 'BLOB SUB_TYPE TEXT CHARACTER SET UTF8',
 121              'MTEXT_UNI'    => 'BLOB SUB_TYPE TEXT CHARACTER SET UTF8',
 122              'TIMESTAMP'    => 'INTEGER',
 123              'DECIMAL'    => 'DOUBLE PRECISION',
 124              'DECIMAL:'    => 'DOUBLE PRECISION',
 125              'PDECIMAL'    => 'DOUBLE PRECISION',
 126              'PDECIMAL:'    => 'DOUBLE PRECISION',
 127              'VCHAR_UNI'    => 'VARCHAR(255) CHARACTER SET UTF8',
 128              'VCHAR_UNI:'=> 'VARCHAR(%d) CHARACTER SET UTF8',
 129              'VCHAR_CI'    => 'VARCHAR(255) CHARACTER SET UTF8',
 130              'VARBINARY'    => 'CHAR(255) CHARACTER SET NONE',
 131          ),
 132  
 133          'mssql'        => array(
 134              'INT:'        => '[int]',
 135              'BINT'        => '[float]',
 136              'UINT'        => '[int]',
 137              'UINT:'        => '[int]',
 138              'TINT:'        => '[int]',
 139              'USINT'        => '[int]',
 140              'BOOL'        => '[int]',
 141              'VCHAR'        => '[varchar] (255)',
 142              'VCHAR:'    => '[varchar] (%d)',
 143              'CHAR:'        => '[char] (%d)',
 144              'XSTEXT'    => '[varchar] (1000)',
 145              'STEXT'        => '[varchar] (3000)',
 146              'TEXT'        => '[varchar] (8000)',
 147              'MTEXT'        => '[text]',
 148              'XSTEXT_UNI'=> '[varchar] (100)',
 149              'STEXT_UNI'    => '[varchar] (255)',
 150              'TEXT_UNI'    => '[varchar] (4000)',
 151              'MTEXT_UNI'    => '[text]',
 152              'TIMESTAMP'    => '[int]',
 153              'DECIMAL'    => '[float]',
 154              'DECIMAL:'    => '[float]',
 155              'PDECIMAL'    => '[float]',
 156              'PDECIMAL:'    => '[float]',
 157              'VCHAR_UNI'    => '[varchar] (255)',
 158              'VCHAR_UNI:'=> '[varchar] (%d)',
 159              'VCHAR_CI'    => '[varchar] (255)',
 160              'VARBINARY'    => '[varchar] (255)',
 161          ),
 162  
 163          'mssqlnative'    => array(
 164              'INT:'        => '[int]',
 165              'BINT'        => '[float]',
 166              'UINT'        => '[int]',
 167              'UINT:'        => '[int]',
 168              'TINT:'        => '[int]',
 169              'USINT'        => '[int]',
 170              'BOOL'        => '[int]',
 171              'VCHAR'        => '[varchar] (255)',
 172              'VCHAR:'    => '[varchar] (%d)',
 173              'CHAR:'        => '[char] (%d)',
 174              'XSTEXT'    => '[varchar] (1000)',
 175              'STEXT'        => '[varchar] (3000)',
 176              'TEXT'        => '[varchar] (8000)',
 177              'MTEXT'        => '[text]',
 178              'XSTEXT_UNI'=> '[varchar] (100)',
 179              'STEXT_UNI'    => '[varchar] (255)',
 180              'TEXT_UNI'    => '[varchar] (4000)',
 181              'MTEXT_UNI'    => '[text]',
 182              'TIMESTAMP'    => '[int]',
 183              'DECIMAL'    => '[float]',
 184              'DECIMAL:'    => '[float]',
 185              'PDECIMAL'    => '[float]',
 186              'PDECIMAL:'    => '[float]',
 187              'VCHAR_UNI'    => '[varchar] (255)',
 188              'VCHAR_UNI:'=> '[varchar] (%d)',
 189              'VCHAR_CI'    => '[varchar] (255)',
 190              'VARBINARY'    => '[varchar] (255)',
 191          ),
 192  
 193          'oracle'    => array(
 194              'INT:'        => 'number(%d)',
 195              'BINT'        => 'number(20)',
 196              'UINT'        => 'number(8)',
 197              'UINT:'        => 'number(%d)',
 198              'TINT:'        => 'number(%d)',
 199              'USINT'        => 'number(4)',
 200              'BOOL'        => 'number(1)',
 201              'VCHAR'        => 'varchar2(255)',
 202              'VCHAR:'    => 'varchar2(%d)',
 203              'CHAR:'        => 'char(%d)',
 204              'XSTEXT'    => 'varchar2(1000)',
 205              'STEXT'        => 'varchar2(3000)',
 206              'TEXT'        => 'clob',
 207              'MTEXT'        => 'clob',
 208              'XSTEXT_UNI'=> 'varchar2(300)',
 209              'STEXT_UNI'    => 'varchar2(765)',
 210              'TEXT_UNI'    => 'clob',
 211              'MTEXT_UNI'    => 'clob',
 212              'TIMESTAMP'    => 'number(11)',
 213              'DECIMAL'    => 'number(5, 2)',
 214              'DECIMAL:'    => 'number(%d, 2)',
 215              'PDECIMAL'    => 'number(6, 3)',
 216              'PDECIMAL:'    => 'number(%d, 3)',
 217              'VCHAR_UNI'    => 'varchar2(765)',
 218              'VCHAR_UNI:'=> array('varchar2(%d)', 'limit' => array('mult', 3, 765, 'clob')),
 219              'VCHAR_CI'    => 'varchar2(255)',
 220              'VARBINARY'    => 'raw(255)',
 221          ),
 222  
 223          'sqlite'    => array(
 224              'INT:'        => 'int(%d)',
 225              'BINT'        => 'bigint(20)',
 226              'UINT'        => 'INTEGER UNSIGNED', //'mediumint(8) UNSIGNED',
 227              'UINT:'        => 'INTEGER UNSIGNED', // 'int(%d) UNSIGNED',
 228              'TINT:'        => 'tinyint(%d)',
 229              'USINT'        => 'INTEGER UNSIGNED', //'mediumint(4) UNSIGNED',
 230              'BOOL'        => 'INTEGER UNSIGNED', //'tinyint(1) UNSIGNED',
 231              'VCHAR'        => 'varchar(255)',
 232              'VCHAR:'    => 'varchar(%d)',
 233              'CHAR:'        => 'char(%d)',
 234              'XSTEXT'    => 'text(65535)',
 235              'STEXT'        => 'text(65535)',
 236              'TEXT'        => 'text(65535)',
 237              'MTEXT'        => 'mediumtext(16777215)',
 238              'XSTEXT_UNI'=> 'text(65535)',
 239              'STEXT_UNI'    => 'text(65535)',
 240              'TEXT_UNI'    => 'text(65535)',
 241              'MTEXT_UNI'    => 'mediumtext(16777215)',
 242              'TIMESTAMP'    => 'INTEGER UNSIGNED', //'int(11) UNSIGNED',
 243              'DECIMAL'    => 'decimal(5,2)',
 244              'DECIMAL:'    => 'decimal(%d,2)',
 245              'PDECIMAL'    => 'decimal(6,3)',
 246              'PDECIMAL:'    => 'decimal(%d,3)',
 247              'VCHAR_UNI'    => 'varchar(255)',
 248              'VCHAR_UNI:'=> 'varchar(%d)',
 249              'VCHAR_CI'    => 'varchar(255)',
 250              'VARBINARY'    => 'blob',
 251          ),
 252  
 253          'postgres'    => array(
 254              'INT:'        => 'INT4',
 255              'BINT'        => 'INT8',
 256              'UINT'        => 'INT4', // unsigned
 257              'UINT:'        => 'INT4', // unsigned
 258              'USINT'        => 'INT2', // unsigned
 259              'BOOL'        => 'INT2', // unsigned
 260              'TINT:'        => 'INT2',
 261              'VCHAR'        => 'varchar(255)',
 262              'VCHAR:'    => 'varchar(%d)',
 263              'CHAR:'        => 'char(%d)',
 264              'XSTEXT'    => 'varchar(1000)',
 265              'STEXT'        => 'varchar(3000)',
 266              'TEXT'        => 'varchar(8000)',
 267              'MTEXT'        => 'TEXT',
 268              'XSTEXT_UNI'=> 'varchar(100)',
 269              'STEXT_UNI'    => 'varchar(255)',
 270              'TEXT_UNI'    => 'varchar(4000)',
 271              'MTEXT_UNI'    => 'TEXT',
 272              'TIMESTAMP'    => 'INT4', // unsigned
 273              'DECIMAL'    => 'decimal(5,2)',
 274              'DECIMAL:'    => 'decimal(%d,2)',
 275              'PDECIMAL'    => 'decimal(6,3)',
 276              'PDECIMAL:'    => 'decimal(%d,3)',
 277              'VCHAR_UNI'    => 'varchar(255)',
 278              'VCHAR_UNI:'=> 'varchar(%d)',
 279              'VCHAR_CI'    => 'varchar_ci',
 280              'VARBINARY'    => 'bytea',
 281          ),
 282      );
 283  
 284      /**
 285      * A list of types being unsigned for better reference in some db's
 286      * @var array
 287      */
 288      var $unsigned_types = array('UINT', 'UINT:', 'USINT', 'BOOL', 'TIMESTAMP');
 289  
 290      /**
 291      * A list of supported DBMS. We change this class to support more DBMS, the DBMS itself only need to follow some rules.
 292      * @var array
 293      */
 294      var $supported_dbms = array('firebird', 'mssql', 'mssqlnative', 'mysql_40', 'mysql_41', 'oracle', 'postgres', 'sqlite');
 295  
 296      /**
 297      * This is set to true if user only wants to return the 'to-be-executed' SQL statement(s) (as an array).
 298      * This mode has no effect on some methods (inserting of data for example). This is expressed within the methods command.
 299      */
 300      var $return_statements = false;
 301  
 302      /**
 303      * Constructor. Set DB Object and set {@link $return_statements return_statements}.
 304      *
 305      * @param phpbb_dbal    $db                    DBAL object
 306      * @param bool        $return_statements    True if only statements should be returned and no SQL being executed
 307      */
 308  	function phpbb_db_tools(&$db, $return_statements = false)
 309      {
 310          $this->db = $db;
 311          $this->return_statements = $return_statements;
 312  
 313          // Determine mapping database type
 314          switch ($this->db->sql_layer)
 315          {
 316              case 'mysql':
 317                  $this->sql_layer = 'mysql_40';
 318              break;
 319  
 320              case 'mysql4':
 321                  if (version_compare($this->db->sql_server_info(true), '4.1.3', '>='))
 322                  {
 323                      $this->sql_layer = 'mysql_41';
 324                  }
 325                  else
 326                  {
 327                      $this->sql_layer = 'mysql_40';
 328                  }
 329              break;
 330  
 331              case 'mysqli':
 332                  $this->sql_layer = 'mysql_41';
 333              break;
 334  
 335              case 'mssql':
 336              case 'mssql_odbc':
 337                  $this->sql_layer = 'mssql';
 338              break;
 339  
 340              case 'mssqlnative':
 341                  $this->sql_layer = 'mssqlnative';
 342              break;
 343  
 344              default:
 345                  $this->sql_layer = $this->db->sql_layer;
 346              break;
 347          }
 348      }
 349  
 350      /**
 351      * Gets a list of tables in the database.
 352      *
 353      * @return array        Array of table names  (all lower case)
 354      */
 355  	function sql_list_tables()
 356      {
 357          switch ($this->db->sql_layer)
 358          {
 359              case 'mysql':
 360              case 'mysql4':
 361              case 'mysqli':
 362                  $sql = 'SHOW TABLES';
 363              break;
 364  
 365              case 'sqlite':
 366                  $sql = 'SELECT name
 367                      FROM sqlite_master
 368                      WHERE type = "table"';
 369              break;
 370  
 371              case 'mssql':
 372              case 'mssql_odbc':
 373              case 'mssqlnative':
 374                  $sql = "SELECT name
 375                      FROM sysobjects
 376                      WHERE type='U'";
 377              break;
 378  
 379              case 'postgres':
 380                  $sql = 'SELECT relname
 381                      FROM pg_stat_user_tables';
 382              break;
 383  
 384              case 'firebird':
 385                  $sql = 'SELECT rdb$relation_name
 386                      FROM rdb$relations
 387                      WHERE rdb$view_source is null
 388                          AND rdb$system_flag = 0';
 389              break;
 390  
 391              case 'oracle':
 392                  $sql = 'SELECT table_name
 393                      FROM USER_TABLES';
 394              break;
 395          }
 396  
 397          $result = $this->db->sql_query($sql);
 398  
 399          $tables = array();
 400          while ($row = $this->db->sql_fetchrow($result))
 401          {
 402              $name = current($row);
 403              $tables[$name] = $name;
 404          }
 405          $this->db->sql_freeresult($result);
 406  
 407          return $tables;
 408      }
 409  
 410      /**
 411      * Check if table exists
 412      *
 413      *
 414      * @param string    $table_name    The table name to check for
 415      * @return bool true if table exists, else false
 416      */
 417  	function sql_table_exists($table_name)
 418      {
 419          $this->db->sql_return_on_error(true);
 420          $result = $this->db->sql_query_limit('SELECT * FROM ' . $table_name, 1);
 421          $this->db->sql_return_on_error(false);
 422  
 423          if ($result)
 424          {
 425              $this->db->sql_freeresult($result);
 426              return true;
 427          }
 428  
 429          return false;
 430      }
 431  
 432      /**
 433      * Create SQL Table
 434      *
 435      * @param string    $table_name    The table name to create
 436      * @param array    $table_data    Array containing table data.
 437      * @return array    Statements if $return_statements is true.
 438      */
 439  	function sql_create_table($table_name, $table_data)
 440      {
 441          // holds the DDL for a column
 442          $columns = $statements = array();
 443  
 444          if ($this->sql_table_exists($table_name))
 445          {
 446              return $this->_sql_run_sql($statements);
 447          }
 448  
 449          // Begin transaction
 450          $statements[] = 'begin';
 451  
 452          // Determine if we have created a PRIMARY KEY in the earliest
 453          $primary_key_gen = false;
 454  
 455          // Determine if the table must be created with TEXTIMAGE
 456          $create_textimage = false;
 457  
 458          // Determine if the table requires a sequence
 459          $create_sequence = false;
 460  
 461          // Begin table sql statement
 462          switch ($this->sql_layer)
 463          {
 464              case 'mssql':
 465              case 'mssqlnative':
 466                  $table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n";
 467              break;
 468  
 469              default:
 470                  $table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n";
 471              break;
 472          }
 473  
 474          // Iterate through the columns to create a table
 475          foreach ($table_data['COLUMNS'] as $column_name => $column_data)
 476          {
 477              // here lies an array, filled with information compiled on the column's data
 478              $prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
 479  
 480              if (isset($prepared_column['auto_increment']) && strlen($column_name) > 26) // "$column_name}_gen"
 481              {
 482                  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);
 483              }
 484  
 485              // here we add the definition of the new column to the list of columns
 486              switch ($this->sql_layer)
 487              {
 488                  case 'mssql':
 489                  case 'mssqlnative':
 490                      $columns[] = "\t [{$column_name}] " . $prepared_column['column_type_sql_default'];
 491                  break;
 492  
 493                  default:
 494                      $columns[] = "\t {$column_name} " . $prepared_column['column_type_sql'];
 495                  break;
 496              }
 497  
 498              // see if we have found a primary key set due to a column definition if we have found it, we can stop looking
 499              if (!$primary_key_gen)
 500              {
 501                  $primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set'];
 502              }
 503  
 504              // create textimage DDL based off of the existance of certain column types
 505              if (!$create_textimage)
 506              {
 507                  $create_textimage = isset($prepared_column['textimage']) && $prepared_column['textimage'];
 508              }
 509  
 510              // create sequence DDL based off of the existance of auto incrementing columns
 511              if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'])
 512              {
 513                  $create_sequence = $column_name;
 514              }
 515          }
 516  
 517          // this makes up all the columns in the create table statement
 518          $table_sql .= implode(",\n", $columns);
 519  
 520          // Close the table for two DBMS and add to the statements
 521          switch ($this->sql_layer)
 522          {
 523              case 'firebird':
 524                  $table_sql .= "\n);";
 525                  $statements[] = $table_sql;
 526              break;
 527  
 528              case 'mssql':
 529              case 'mssqlnative':
 530                  $table_sql .= "\n) ON [PRIMARY]" . (($create_textimage) ? ' TEXTIMAGE_ON [PRIMARY]' : '');
 531                  $statements[] = $table_sql;
 532              break;
 533          }
 534  
 535          // we have yet to create a primary key for this table,
 536          // this means that we can add the one we really wanted instead
 537          if (!$primary_key_gen)
 538          {
 539              // Write primary key
 540              if (isset($table_data['PRIMARY_KEY']))
 541              {
 542                  if (!is_array($table_data['PRIMARY_KEY']))
 543                  {
 544                      $table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']);
 545                  }
 546  
 547                  switch ($this->sql_layer)
 548                  {
 549                      case 'mysql_40':
 550                      case 'mysql_41':
 551                      case 'postgres':
 552                      case 'sqlite':
 553                          $table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')';
 554                      break;
 555  
 556                      case 'firebird':
 557                      case 'mssql':
 558                      case 'mssqlnative':
 559                          // We need the data here
 560                          $old_return_statements = $this->return_statements;
 561                          $this->return_statements = true;
 562  
 563                          $primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']);
 564                          foreach ($primary_key_stmts as $pk_stmt)
 565                          {
 566                              $statements[] = $pk_stmt;
 567                          }
 568  
 569                          $this->return_statements = $old_return_statements;
 570                      break;
 571  
 572                      case 'oracle':
 573                          $table_sql .= ",\n\t CONSTRAINT pk_{$table_name} PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')';
 574                      break;
 575                  }
 576              }
 577          }
 578  
 579          // close the table
 580          switch ($this->sql_layer)
 581          {
 582              case 'mysql_41':
 583                  // make sure the table is in UTF-8 mode
 584                  $table_sql .= "\n) CHARACTER SET `utf8` COLLATE `utf8_bin`;";
 585                  $statements[] = $table_sql;
 586              break;
 587  
 588              case 'mysql_40':
 589              case 'sqlite':
 590                  $table_sql .= "\n);";
 591                  $statements[] = $table_sql;
 592              break;
 593  
 594              case 'postgres':
 595                  // do we need to add a sequence for auto incrementing columns?
 596                  if ($create_sequence)
 597                  {
 598                      $statements[] = "CREATE SEQUENCE {$table_name}_seq;";
 599                  }
 600  
 601                  $table_sql .= "\n);";
 602                  $statements[] = $table_sql;
 603              break;
 604  
 605              case 'oracle':
 606                  $table_sql .= "\n)";
 607                  $statements[] = $table_sql;
 608  
 609                  // do we need to add a sequence and a tigger for auto incrementing columns?
 610                  if ($create_sequence)
 611                  {
 612                      // create the actual sequence
 613                      $statements[] = "CREATE SEQUENCE {$table_name}_seq";
 614  
 615                      // the trigger is the mechanism by which we increment the counter
 616                      $trigger = "CREATE OR REPLACE TRIGGER t_{$table_name}\n";
 617                      $trigger .= "BEFORE INSERT ON {$table_name}\n";
 618                      $trigger .= "FOR EACH ROW WHEN (\n";
 619                      $trigger .= "\tnew.{$create_sequence} IS NULL OR new.{$create_sequence} = 0\n";
 620                      $trigger .= ")\n";
 621                      $trigger .= "BEGIN\n";
 622                      $trigger .= "\tSELECT {$table_name}_seq.nextval\n";
 623                      $trigger .= "\tINTO :new.{$create_sequence}\n";
 624                      $trigger .= "\tFROM dual;\n";
 625                      $trigger .= "END;";
 626  
 627                      $statements[] = $trigger;
 628                  }
 629              break;
 630  
 631              case 'firebird':
 632                  if ($create_sequence)
 633                  {
 634                      $statements[] = "CREATE GENERATOR {$table_name}_gen;";
 635                      $statements[] = "SET GENERATOR {$table_name}_gen TO 0;";
 636  
 637                      $trigger = "CREATE TRIGGER t_$table_name FOR $table_name\n";
 638                      $trigger .= "BEFORE INSERT\nAS\nBEGIN\n";
 639                      $trigger .= "\tNEW.{$create_sequence} = GEN_ID({$table_name}_gen, 1);\nEND;";
 640                      $statements[] = $trigger;
 641                  }
 642              break;
 643          }
 644  
 645          // Write Keys
 646          if (isset($table_data['KEYS']))
 647          {
 648              foreach ($table_data['KEYS'] as $key_name => $key_data)
 649              {
 650                  if (!is_array($key_data[1]))
 651                  {
 652                      $key_data[1] = array($key_data[1]);
 653                  }
 654  
 655                  $old_return_statements = $this->return_statements;
 656                  $this->return_statements = true;
 657  
 658                  $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]);
 659  
 660                  foreach ($key_stmts as $key_stmt)
 661                  {
 662                      $statements[] = $key_stmt;
 663                  }
 664  
 665                  $this->return_statements = $old_return_statements;
 666              }
 667          }
 668  
 669          // Commit Transaction
 670          $statements[] = 'commit';
 671  
 672          return $this->_sql_run_sql($statements);
 673      }
 674  
 675      /**
 676      * Handle passed database update array.
 677      * Expected structure...
 678      * Key being one of the following
 679      *    change_columns: Column changes (only type, not name)
 680      *    add_columns: Add columns to a table
 681      *    drop_keys: Dropping keys
 682      *    drop_columns: Removing/Dropping columns
 683      *    add_primary_keys: adding primary keys
 684      *    add_unique_index: adding an unique index
 685      *    add_index: adding an index (can be column:index_size if you need to provide size)
 686      *
 687      * The values are in this format:
 688      *        {TABLE NAME}        => array(
 689      *            {COLUMN NAME}        => array({COLUMN TYPE}, {DEFAULT VALUE}, {OPTIONAL VARIABLES}),
 690      *            {KEY/INDEX NAME}    => array({COLUMN NAMES}),
 691      *        )
 692      *
 693      * For more information have a look at /develop/create_schema_files.php (only available through SVN)
 694      */
 695  	function perform_schema_changes($schema_changes)
 696      {
 697          if (empty($schema_changes))
 698          {
 699              return;
 700          }
 701  
 702          $statements = array();
 703          $sqlite = false;
 704  
 705          // For SQLite we need to perform the schema changes in a much more different way
 706          if ($this->db->sql_layer == 'sqlite' && $this->return_statements)
 707          {
 708              $sqlite_data = array();
 709              $sqlite = true;
 710          }
 711  
 712          // Drop tables?
 713          if (!empty($schema_changes['drop_tables']))
 714          {
 715              foreach ($schema_changes['drop_tables'] as $table)
 716              {
 717                  // only drop table if it exists
 718                  if ($this->sql_table_exists($table))
 719                  {
 720                      $result = $this->sql_table_drop($table);
 721                      if ($this->return_statements)
 722                      {
 723                          $statements = array_merge($statements, $result);
 724                      }
 725                  }
 726              }
 727          }
 728  
 729          // Add tables?
 730          if (!empty($schema_changes['add_tables']))
 731          {
 732              foreach ($schema_changes['add_tables'] as $table => $table_data)
 733              {
 734                  $result = $this->sql_create_table($table, $table_data);
 735                  if ($this->return_statements)
 736                  {
 737                      $statements = array_merge($statements, $result);
 738                  }
 739              }
 740          }
 741  
 742          // Change columns?
 743          if (!empty($schema_changes['change_columns']))
 744          {
 745              foreach ($schema_changes['change_columns'] as $table => $columns)
 746              {
 747                  foreach ($columns as $column_name => $column_data)
 748                  {
 749                      // If the column exists we change it, else we add it ;)
 750                      if ($column_exists = $this->sql_column_exists($table, $column_name))
 751                      {
 752                          $result = $this->sql_column_change($table, $column_name, $column_data, true);
 753                      }
 754                      else
 755                      {
 756                          $result = $this->sql_column_add($table, $column_name, $column_data, true);
 757                      }
 758  
 759                      if ($sqlite)
 760                      {
 761                          if ($column_exists)
 762                          {
 763                              $sqlite_data[$table]['change_columns'][] = $result;
 764                          }
 765                          else
 766                          {
 767                              $sqlite_data[$table]['add_columns'][] = $result;
 768                          }
 769                      }
 770                      else if ($this->return_statements)
 771                      {
 772                          $statements = array_merge($statements, $result);
 773                      }
 774                  }
 775              }
 776          }
 777  
 778          // Add columns?
 779          if (!empty($schema_changes['add_columns']))
 780          {
 781              foreach ($schema_changes['add_columns'] as $table => $columns)
 782              {
 783                  foreach ($columns as $column_name => $column_data)
 784                  {
 785                      // Only add the column if it does not exist yet
 786                      if ($column_exists = $this->sql_column_exists($table, $column_name))
 787                      {
 788                          continue;
 789                          // This is commented out here because it can take tremendous time on updates
 790  //                        $result = $this->sql_column_change($table, $column_name, $column_data, true);
 791                      }
 792                      else
 793                      {
 794                          $result = $this->sql_column_add($table, $column_name, $column_data, true);
 795                      }
 796  
 797                      if ($sqlite)
 798                      {
 799                          if ($column_exists)
 800                          {
 801                              continue;
 802  //                            $sqlite_data[$table]['change_columns'][] = $result;
 803                          }
 804                          else
 805                          {
 806                              $sqlite_data[$table]['add_columns'][] = $result;
 807                          }
 808                      }
 809                      else if ($this->return_statements)
 810                      {
 811                          $statements = array_merge($statements, $result);
 812                      }
 813                  }
 814              }
 815          }
 816  
 817          // Remove keys?
 818          if (!empty($schema_changes['drop_keys']))
 819          {
 820              foreach ($schema_changes['drop_keys'] as $table => $indexes)
 821              {
 822                  foreach ($indexes as $index_name)
 823                  {
 824                      if (!$this->sql_index_exists($table, $index_name))
 825                      {
 826                          continue;
 827                      }
 828  
 829                      $result = $this->sql_index_drop($table, $index_name);
 830  
 831                      if ($this->return_statements)
 832                      {
 833                          $statements = array_merge($statements, $result);
 834                      }
 835                  }
 836              }
 837          }
 838  
 839          // Drop columns?
 840          if (!empty($schema_changes['drop_columns']))
 841          {
 842              foreach ($schema_changes['drop_columns'] as $table => $columns)
 843              {
 844                  foreach ($columns as $column)
 845                  {
 846                      // Only remove the column if it exists...
 847                      if ($this->sql_column_exists($table, $column))
 848                      {
 849                          $result = $this->sql_column_remove($table, $column, true);
 850  
 851                          if ($sqlite)
 852                          {
 853                              $sqlite_data[$table]['drop_columns'][] = $result;
 854                          }
 855                          else if ($this->return_statements)
 856                          {
 857                              $statements = array_merge($statements, $result);
 858                          }
 859                      }
 860                  }
 861              }
 862          }
 863  
 864          // Add primary keys?
 865          if (!empty($schema_changes['add_primary_keys']))
 866          {
 867              foreach ($schema_changes['add_primary_keys'] as $table => $columns)
 868              {
 869                  $result = $this->sql_create_primary_key($table, $columns, true);
 870  
 871                  if ($sqlite)
 872                  {
 873                      $sqlite_data[$table]['primary_key'] = $result;
 874                  }
 875                  else if ($this->return_statements)
 876                  {
 877                      $statements = array_merge($statements, $result);
 878                  }
 879              }
 880          }
 881  
 882          // Add unqiue indexes?
 883          if (!empty($schema_changes['add_unique_index']))
 884          {
 885              foreach ($schema_changes['add_unique_index'] as $table => $index_array)
 886              {
 887                  foreach ($index_array as $index_name => $column)
 888                  {
 889                      if ($this->sql_unique_index_exists($table, $index_name))
 890                      {
 891                          continue;
 892                      }
 893  
 894                      $result = $this->sql_create_unique_index($table, $index_name, $column);
 895  
 896                      if ($this->return_statements)
 897                      {
 898                          $statements = array_merge($statements, $result);
 899                      }
 900                  }
 901              }
 902          }
 903  
 904          // Add indexes?
 905          if (!empty($schema_changes['add_index']))
 906          {
 907              foreach ($schema_changes['add_index'] as $table => $index_array)
 908              {
 909                  foreach ($index_array as $index_name => $column)
 910                  {
 911                      if ($this->sql_index_exists($table, $index_name))
 912                      {
 913                          continue;
 914                      }
 915  
 916                      $result = $this->sql_create_index($table, $index_name, $column);
 917  
 918                      if ($this->return_statements)
 919                      {
 920                          $statements = array_merge($statements, $result);
 921                      }
 922                  }
 923              }
 924          }
 925  
 926          if ($sqlite)
 927          {
 928              foreach ($sqlite_data as $table_name => $sql_schema_changes)
 929              {
 930                  // Create temporary table with original data
 931                  $statements[] = 'begin';
 932  
 933                  $sql = "SELECT sql
 934                      FROM sqlite_master
 935                      WHERE type = 'table'
 936                          AND name = '{$table_name}'
 937                      ORDER BY type DESC, name;";
 938                  $result = $this->db->sql_query($sql);
 939  
 940                  if (!$result)
 941                  {
 942                      continue;
 943                  }
 944  
 945                  $row = $this->db->sql_fetchrow($result);
 946                  $this->db->sql_freeresult($result);
 947  
 948                  // Create a backup table and populate it, destroy the existing one
 949                  $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']);
 950                  $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
 951                  $statements[] = 'DROP TABLE ' . $table_name;
 952  
 953                  // Get the columns...
 954                  preg_match('#\((.*)\)#s', $row['sql'], $matches);
 955  
 956                  $plain_table_cols = trim($matches[1]);
 957                  $new_table_cols = preg_split('/,(?![\s\w]+\))/m', $plain_table_cols);
 958                  $column_list = array();
 959  
 960                  foreach ($new_table_cols as $declaration)
 961                  {
 962                      $entities = preg_split('#\s+#', trim($declaration));
 963                      if ($entities[0] == 'PRIMARY')
 964                      {
 965                          continue;
 966                      }
 967                      $column_list[] = $entities[0];
 968                  }
 969  
 970                  // note down the primary key notation because sqlite only supports adding it to the end for the new table
 971                  $primary_key = false;
 972                  $_new_cols = array();
 973  
 974                  foreach ($new_table_cols as $key => $declaration)
 975                  {
 976                      $entities = preg_split('#\s+#', trim($declaration));
 977                      if ($entities[0] == 'PRIMARY')
 978                      {
 979                          $primary_key = $declaration;
 980                          continue;
 981                      }
 982                      $_new_cols[] = $declaration;
 983                  }
 984  
 985                  $new_table_cols = $_new_cols;
 986  
 987                  // First of all... change columns
 988                  if (!empty($sql_schema_changes['change_columns']))
 989                  {
 990                      foreach ($sql_schema_changes['change_columns'] as $column_sql)
 991                      {
 992                          foreach ($new_table_cols as $key => $declaration)
 993                          {
 994                              $entities = preg_split('#\s+#', trim($declaration));
 995                              if (strpos($column_sql, $entities[0] . ' ') === 0)
 996                              {
 997                                  $new_table_cols[$key] = $column_sql;
 998                              }
 999                          }
1000                      }
1001                  }
1002  
1003                  if (!empty($sql_schema_changes['add_columns']))
1004                  {
1005                      foreach ($sql_schema_changes['add_columns'] as $column_sql)
1006                      {
1007                          $new_table_cols[] = $column_sql;
1008                      }
1009                  }
1010  
1011                  // Now drop them...
1012                  if (!empty($sql_schema_changes['drop_columns']))
1013                  {
1014                      foreach ($sql_schema_changes['drop_columns'] as $column_name)
1015                      {
1016                          // Remove from column list...
1017                          $new_column_list = array();
1018                          foreach ($column_list as $key => $value)
1019                          {
1020                              if ($value === $column_name)
1021                              {
1022                                  continue;
1023                              }
1024  
1025                              $new_column_list[] = $value;
1026                          }
1027  
1028                          $column_list = $new_column_list;
1029  
1030                          // Remove from table...
1031                          $_new_cols = array();
1032                          foreach ($new_table_cols as $key => $declaration)
1033                          {
1034                              $entities = preg_split('#\s+#', trim($declaration));
1035                              if (strpos($column_name . ' ', $entities[0] . ' ') === 0)
1036                              {
1037                                  continue;
1038                              }
1039                              $_new_cols[] = $declaration;
1040                          }
1041                          $new_table_cols = $_new_cols;
1042                      }
1043                  }
1044  
1045                  // Primary key...
1046                  if (!empty($sql_schema_changes['primary_key']))
1047                  {
1048                      $new_table_cols[] = 'PRIMARY KEY (' . implode(', ', $sql_schema_changes['primary_key']) . ')';
1049                  }
1050                  // Add a new one or the old primary key
1051                  else if ($primary_key !== false)
1052                  {
1053                      $new_table_cols[] = $primary_key;
1054                  }
1055  
1056                  $columns = implode(',', $column_list);
1057  
1058                  // create a new table and fill it up. destroy the temp one
1059                  $statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $new_table_cols) . ');';
1060                  $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
1061                  $statements[] = 'DROP TABLE ' . $table_name . '_temp';
1062  
1063                  $statements[] = 'commit';
1064              }
1065          }
1066  
1067          if ($this->return_statements)
1068          {
1069              return $statements;
1070          }
1071      }
1072  
1073      /**
1074      * Gets a list of columns of a table.
1075      *
1076      * @param string $table        Table name
1077      *
1078      * @return array                Array of column names (all lower case)
1079      */
1080  	function sql_list_columns($table)
1081      {
1082          $columns = array();
1083  
1084          switch ($this->sql_layer)
1085          {
1086              case 'mysql_40':
1087              case 'mysql_41':
1088                  $sql = "SHOW COLUMNS FROM $table";
1089              break;
1090  
1091              // PostgreSQL has a way of doing this in a much simpler way but would
1092              // not allow us to support all versions of PostgreSQL
1093              case 'postgres':
1094                  $sql = "SELECT a.attname
1095                      FROM pg_class c, pg_attribute a
1096                      WHERE c.relname = '{$table}'
1097                          AND a.attnum > 0
1098                          AND a.attrelid = c.oid";
1099              break;
1100  
1101              // same deal with PostgreSQL, we must perform more complex operations than
1102              // we technically could
1103              case 'mssql':
1104              case 'mssqlnative':
1105                  $sql = "SELECT c.name
1106                      FROM syscolumns c
1107                      LEFT JOIN sysobjects o ON c.id = o.id
1108                      WHERE o.name = '{$table}'";
1109              break;
1110  
1111              case 'oracle':
1112                  $sql = "SELECT column_name
1113                      FROM user_tab_columns
1114                      WHERE LOWER(table_name) = '" . strtolower($table) . "'";
1115              break;
1116  
1117              case 'firebird':
1118                  $sql = "SELECT RDB\$FIELD_NAME as FNAME
1119                      FROM RDB\$RELATION_FIELDS
1120                      WHERE RDB\$RELATION_NAME = '" . strtoupper($table) . "'";
1121              break;
1122  
1123              case 'sqlite':
1124                  $sql = "SELECT sql
1125                      FROM sqlite_master
1126                      WHERE type = 'table'
1127                          AND name = '{$table}'";
1128  
1129                  $result = $this->db->sql_query($sql);
1130  
1131                  if (!$result)
1132                  {
1133                      return false;
1134                  }
1135  
1136                  $row = $this->db->sql_fetchrow($result);
1137                  $this->db->sql_freeresult($result);
1138  
1139                  preg_match('#\((.*)\)#s', $row['sql'], $matches);
1140  
1141                  $cols = trim($matches[1]);
1142                  $col_array = preg_split('/,(?![\s\w]+\))/m', $cols);
1143  
1144                  foreach ($col_array as $declaration)
1145                  {
1146                      $entities = preg_split('#\s+#', trim($declaration));
1147                      if ($entities[0] == 'PRIMARY')
1148                      {
1149                          continue;
1150                      }
1151  
1152                      $column = strtolower($entities[0]);
1153                      $columns[$column] = $column;
1154                  }
1155  
1156                  return $columns;
1157              break;
1158          }
1159  
1160          $result = $this->db->sql_query($sql);
1161  
1162          while ($row = $this->db->sql_fetchrow($result))
1163          {
1164              $column = strtolower(current($row));
1165              $columns[$column] = $column;
1166          }
1167          $this->db->sql_freeresult($result);
1168  
1169          return $columns;
1170      }
1171  
1172      /**
1173      * Check whether a specified column exist in a table
1174      *
1175      * @param string    $table            Table to check
1176      * @param string    $column_name    Column to check
1177      *
1178      * @return bool        True if column exists, false otherwise
1179      */
1180  	function sql_column_exists($table, $column_name)
1181      {
1182          $columns = $this->sql_list_columns($table);
1183  
1184          return isset($columns[$column_name]);
1185      }
1186  
1187      /**
1188      * Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes.
1189      *
1190      * @param string    $table_name        Table to check the index at
1191      * @param string    $index_name        The index name to check
1192      *
1193      * @return bool True if index exists, else false
1194      */
1195  	function sql_index_exists($table_name, $index_name)
1196      {
1197          if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative')
1198          {
1199              $sql = "EXEC sp_statistics '$table_name'";
1200              $result = $this->db->sql_query($sql);
1201  
1202              while ($row = $this->db->sql_fetchrow($result))
1203              {
1204                  if ($row['TYPE'] == 3)
1205                  {
1206                      if (strtolower($row['INDEX_NAME']) == strtolower($index_name))
1207                      {
1208                          $this->db->sql_freeresult($result);
1209                          return true;
1210                      }
1211                  }
1212              }
1213              $this->db->sql_freeresult($result);
1214  
1215              return false;
1216          }
1217  
1218          switch ($this->sql_layer)
1219          {
1220              case 'firebird':
1221                  $sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name
1222                      FROM RDB\$INDICES
1223                      WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "'
1224                          AND RDB\$UNIQUE_FLAG IS NULL
1225                          AND RDB\$FOREIGN_KEY IS NULL";
1226                  $col = 'index_name';
1227              break;
1228  
1229              case 'postgres':
1230                  $sql = "SELECT ic.relname as index_name
1231                      FROM pg_class bc, pg_class ic, pg_index i
1232                      WHERE (bc.oid = i.indrelid)
1233                          AND (ic.oid = i.indexrelid)
1234                          AND (bc.relname = '" . $table_name . "')
1235                          AND (i.indisunique != 't')
1236                          AND (i.indisprimary != 't')";
1237                  $col = 'index_name';
1238              break;
1239  
1240              case 'mysql_40':
1241              case 'mysql_41':
1242                  $sql = 'SHOW KEYS
1243                      FROM ' . $table_name;
1244                  $col = 'Key_name';
1245              break;
1246  
1247              case 'oracle':
1248                  $sql = "SELECT index_name
1249                      FROM user_indexes
1250                      WHERE table_name = '" . strtoupper($table_name) . "'
1251                          AND generated = 'N'
1252                          AND uniqueness = 'NONUNIQUE'";
1253                  $col = 'index_name';
1254              break;
1255  
1256              case 'sqlite':
1257                  $sql = "PRAGMA index_list('" . $table_name . "');";
1258                  $col = 'name';
1259              break;
1260          }
1261  
1262          $result = $this->db->sql_query($sql);
1263          while ($row = $this->db->sql_fetchrow($result))
1264          {
1265              if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique'])
1266              {
1267                  continue;
1268              }
1269  
1270              // These DBMS prefix index name with the table name
1271              switch ($this->sql_layer)
1272              {
1273                  case 'firebird':
1274                  case 'oracle':
1275                  case 'postgres':
1276                  case 'sqlite':
1277                      $row[$col] = substr($row[$col], strlen($table_name) + 1);
1278                  break;
1279              }
1280  
1281              if (strtolower($row[$col]) == strtolower($index_name))
1282              {
1283                  $this->db->sql_freeresult($result);
1284                  return true;
1285              }
1286          }
1287          $this->db->sql_freeresult($result);
1288  
1289          return false;
1290      }
1291  
1292      /**
1293      * Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes.
1294      *
1295      * @param string    $table_name        Table to check the index at
1296      * @param string    $index_name        The index name to check
1297      *
1298      * @return bool True if index exists, else false
1299      */
1300  	function sql_unique_index_exists($table_name, $index_name)
1301      {
1302          if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative')
1303          {
1304              $sql = "EXEC sp_statistics '$table_name'";
1305              $result = $this->db->sql_query($sql);
1306  
1307              while ($row = $this->db->sql_fetchrow($result))
1308              {
1309                  // Usually NON_UNIQUE is the column we want to check, but we allow for both
1310                  if ($row['TYPE'] == 3)
1311                  {
1312                      if (strtolower($row['INDEX_NAME']) == strtolower($index_name))
1313                      {
1314                          $this->db->sql_freeresult($result);
1315                          return true;
1316                      }
1317                  }
1318              }
1319              $this->db->sql_freeresult($result);
1320              return false;
1321          }
1322  
1323          switch ($this->sql_layer)
1324          {
1325              case 'firebird':
1326                  $sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name
1327                      FROM RDB\$INDICES
1328                      WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "'
1329                          AND RDB\$UNIQUE_FLAG IS NOT NULL
1330                          AND RDB\$FOREIGN_KEY IS NULL";
1331                  $col = 'index_name';
1332              break;
1333  
1334              case 'postgres':
1335                  $sql = "SELECT ic.relname as index_name, i.indisunique
1336                      FROM pg_class bc, pg_class ic, pg_index i
1337                      WHERE (bc.oid = i.indrelid)
1338                          AND (ic.oid = i.indexrelid)
1339                          AND (bc.relname = '" . $table_name . "')
1340                          AND (i.indisprimary != 't')";
1341                  $col = 'index_name';
1342              break;
1343  
1344              case 'mysql_40':
1345              case 'mysql_41':
1346                  $sql = 'SHOW KEYS
1347                      FROM ' . $table_name;
1348                  $col = 'Key_name';
1349              break;
1350  
1351              case 'oracle':
1352                  $sql = "SELECT index_name, table_owner
1353                      FROM user_indexes
1354                      WHERE table_name = '" . strtoupper($table_name) . "'
1355                          AND generated = 'N'
1356                          AND uniqueness = 'UNIQUE'";
1357                  $col = 'index_name';
1358              break;
1359  
1360              case 'sqlite':
1361                  $sql = "PRAGMA index_list('" . $table_name . "');";
1362                  $col = 'name';
1363              break;
1364          }
1365  
1366          $result = $this->db->sql_query($sql);
1367          while ($row = $this->db->sql_fetchrow($result))
1368          {
1369              if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && ($row['Non_unique'] || $row[$col] == 'PRIMARY'))
1370              {
1371                  continue;
1372              }
1373  
1374              if ($this->sql_layer == 'sqlite' && !$row['unique'])
1375              {
1376                  continue;
1377              }
1378  
1379              if ($this->sql_layer == 'postgres' && $row['indisunique'] != 't')
1380              {
1381                  continue;
1382              }
1383  
1384              // These DBMS prefix index name with the table name
1385              switch ($this->sql_layer)
1386              {
1387                  case 'oracle':
1388                      // Two cases here... prefixed with U_[table_owner] and not prefixed with table_name
1389                      if (strpos($row[$col], 'U_') === 0)
1390                      {
1391                          $row[$col] = substr($row[$col], strlen('U_' . $row['table_owner']) + 1);
1392                      }
1393                      else if (strpos($row[$col], strtoupper($table_name)) === 0)
1394                      {
1395                          $row[$col] = substr($row[$col], strlen($table_name) + 1);
1396                      }
1397                  break;
1398  
1399                  case 'firebird':
1400                  case 'postgres':
1401                  case 'sqlite':
1402                      $row[$col] = substr($row[$col], strlen($table_name) + 1);
1403                  break;
1404              }
1405  
1406              if (strtolower($row[$col]) == strtolower($index_name))
1407              {
1408                  $this->db->sql_freeresult($result);
1409                  return true;
1410              }
1411          }
1412          $this->db->sql_freeresult($result);
1413  
1414          return false;
1415      }
1416  
1417      /**
1418      * Private method for performing sql statements (either execute them or return them)
1419      * @access private
1420      */
1421  	function _sql_run_sql($statements)
1422      {
1423          if ($this->return_statements)
1424          {
1425              return $statements;
1426          }
1427  
1428          // We could add error handling here...
1429          foreach ($statements as $sql)
1430          {
1431              if ($sql === 'begin')
1432              {
1433                  $this->db->sql_transaction('begin');
1434              }
1435              else if ($sql === 'commit')
1436              {
1437                  $this->db->sql_transaction('commit');
1438              }
1439              else
1440              {
1441                  $this->db->sql_query($sql);
1442              }
1443          }
1444  
1445          return true;
1446      }
1447  
1448      /**
1449      * Function to prepare some column information for better usage
1450      * @access private
1451      */
1452  	function sql_prepare_column_data($table_name, $column_name, $column_data)
1453      {
1454          if (strlen($column_name) > 30)
1455          {
1456              trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR);
1457          }
1458  
1459          // Get type
1460          if (strpos($column_data[0], ':') !== false)
1461          {
1462              list($orig_column_type, $column_length) = explode(':', $column_data[0]);
1463              if (!is_array($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']))
1464              {
1465                  $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'], $column_length);
1466              }
1467              else
1468              {
1469                  if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule']))
1470                  {
1471                      switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][0])
1472                      {
1473                          case 'div':
1474                              $column_length /= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][1];
1475                              $column_length = ceil($column_length);
1476                              $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length);
1477                          break;
1478                      }
1479                  }
1480  
1481                  if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit']))
1482                  {
1483                      switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][0])
1484                      {
1485                          case 'mult':
1486                              $column_length *= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][1];
1487                              if ($column_length > $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][2])
1488                              {
1489                                  $column_type = $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][3];
1490                              }
1491                              else
1492                              {
1493                                  $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length);
1494                              }
1495                          break;
1496                      }
1497                  }
1498              }
1499              $orig_column_type .= ':';
1500          }
1501          else
1502          {
1503              $orig_column_type = $column_data[0];
1504              $column_type = $this->dbms_type_map[$this->sql_layer][$column_data[0]];
1505          }
1506  
1507          // Adjust default value if db-dependant specified
1508          if (is_array($column_data[1]))
1509          {
1510              $column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default'];
1511          }
1512  
1513          $sql = '';
1514  
1515          $return_array = array();
1516  
1517          switch ($this->sql_layer)
1518          {
1519              case 'firebird':
1520                  $sql .= " {$column_type} ";
1521                  $return_array['column_type_sql_type'] = " {$column_type} ";
1522  
1523                  if (!is_null($column_data[1]))
1524                  {
1525                      $sql .= 'DEFAULT ' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ' ';
1526                      $return_array['column_type_sql_default'] = ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ' ';
1527                  }
1528  
1529                  $sql .= 'NOT NULL';
1530  
1531                  // This is a UNICODE column and thus should be given it's fair share
1532                  if (preg_match('/^X?STEXT_UNI|VCHAR_(CI|UNI:?)/', $column_data[0]))
1533                  {
1534                      $sql .= ' COLLATE UNICODE';
1535                  }
1536  
1537                  $return_array['auto_increment'] = false;
1538                  if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
1539                  {
1540                      $return_array['auto_increment'] = true;
1541                  }
1542  
1543              break;
1544  
1545              case 'mssql':
1546              case 'mssqlnative':
1547                  $sql .= " {$column_type} ";
1548                  $sql_default = " {$column_type} ";
1549  
1550                  // For adding columns we need the default definition
1551                  if (!is_null($column_data[1]))
1552                  {
1553                      // For hexadecimal values do not use single quotes
1554                      if (strpos($column_data[1], '0x') === 0)
1555                      {
1556                          $return_array['default'] = 'DEFAULT (' . $column_data[1] . ') ';
1557                          $sql_default .= $return_array['default'];
1558                      }
1559                      else
1560                      {
1561                          $return_array['default'] = 'DEFAULT (' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ') ';
1562                          $sql_default .= $return_array['default'];
1563                      }
1564                  }
1565  
1566                  if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
1567                  {
1568  //                    $sql .= 'IDENTITY (1, 1) ';
1569                      $sql_default .= 'IDENTITY (1, 1) ';
1570                  }
1571  
1572                  $return_array['textimage'] = $column_type === '[text]';
1573  
1574                  $sql .= 'NOT NULL';
1575                  $sql_default .= 'NOT NULL';
1576  
1577                  $return_array['column_type_sql_default'] = $sql_default;
1578  
1579              break;
1580  
1581              case 'mysql_40':
1582              case 'mysql_41':
1583                  $sql .= " {$column_type} ";
1584  
1585                  // For hexadecimal values do not use single quotes
1586                  if (!is_null($column_data[1]) && substr($column_type, -4) !== 'text' && substr($column_type, -4) !== 'blob')
1587                  {
1588                      $sql .= (strpos($column_data[1], '0x') === 0) ? "DEFAULT {$column_data[1]} " : "DEFAULT '{$column_data[1]}' ";
1589                  }
1590                  $sql .= 'NOT NULL';
1591  
1592                  if (isset($column_data[2]))
1593                  {
1594                      if ($column_data[2] == 'auto_increment')
1595                      {
1596                          $sql .= ' auto_increment';
1597                      }
1598                      else if ($this->sql_layer === 'mysql_41' && $column_data[2] == 'true_sort')
1599                      {
1600                          $sql .= ' COLLATE utf8_unicode_ci';
1601                      }
1602                  }
1603  
1604              break;
1605  
1606              case 'oracle':
1607                  $sql .= " {$column_type} ";
1608                  $sql .= (!is_null($column_data[1])) ? "DEFAULT '{$column_data[1]}' " : '';
1609  
1610                  // In Oracle empty strings ('') are treated as NULL.
1611                  // Therefore in oracle we allow NULL's for all DEFAULT '' entries
1612                  // Oracle does not like setting NOT NULL on a column that is already NOT NULL (this happens only on number fields)
1613                  if (!preg_match('/number/i', $column_type))
1614                  {
1615                      $sql .= ($column_data[1] === '') ? '' : 'NOT NULL';
1616                  }
1617  
1618                  $return_array['auto_increment'] = false;
1619                  if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
1620                  {
1621                      $return_array['auto_increment'] = true;
1622                  }
1623  
1624              break;
1625  
1626              case 'postgres':
1627                  $return_array['column_type'] = $column_type;
1628  
1629                  $sql .= " {$column_type} ";
1630  
1631                  $return_array['auto_increment'] = false;
1632                  if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
1633                  {
1634                      $default_val = "nextval('{$table_name}_seq')";
1635                      $return_array['auto_increment'] = true;
1636                  }
1637                  else if (!is_null($column_data[1]))
1638                  {
1639                      $default_val = "'" . $column_data[1] . "'";
1640                      $return_array['null'] = 'NOT NULL';
1641                      $sql .= 'NOT NULL ';
1642                  }
1643  
1644                  $return_array['default'] = $default_val;
1645  
1646                  $sql .= "DEFAULT {$default_val}";
1647  
1648                  // Unsigned? Then add a CHECK contraint
1649                  if (in_array($orig_column_type, $this->unsigned_types))
1650                  {
1651                      $return_array['constraint'] = "CHECK ({$column_name} >= 0)";
1652                      $sql .= " CHECK ({$column_name} >= 0)";
1653                  }
1654  
1655              break;
1656  
1657              case 'sqlite':
1658                  $return_array['primary_key_set'] = false;
1659                  if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
1660                  {
1661                      $sql .= ' INTEGER PRIMARY KEY';
1662                      $return_array['primary_key_set'] = true;
1663                  }
1664                  else
1665                  {
1666                      $sql .= ' ' . $column_type;
1667                  }
1668  
1669                  $sql .= ' NOT NULL ';
1670                  $sql .= (!is_null($column_data[1])) ? "DEFAULT '{$column_data[1]}'" : '';
1671  
1672              break;
1673          }
1674  
1675          $return_array['column_type_sql'] = $sql;
1676  
1677          return $return_array;
1678      }
1679  
1680      /**
1681      * Add new column
1682      */
1683  	function sql_column_add($table_name, $column_name, $column_data, $inline = false)
1684      {
1685          $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
1686          $statements = array();
1687  
1688          switch ($this->sql_layer)
1689          {
1690              case 'firebird':
1691                  // Does not support AFTER statement, only POSITION (and there you need the column position)
1692                  $statements[] = 'ALTER TABLE ' . $table_name . ' ADD "' . strtoupper($column_name) . '" ' . $column_data['column_type_sql'];
1693              break;
1694  
1695              case 'mssql':
1696              case 'mssqlnative':
1697                  // Does not support AFTER, only through temporary table
1698                  $statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default'];
1699              break;
1700  
1701              case 'mysql_40':
1702              case 'mysql_41':
1703                  $after = (!empty($column_data['after'])) ? ' AFTER ' . $column_data['after'] : '';
1704                  $statements[] = 'ALTER TABLE `' . $table_name . '` ADD COLUMN `' . $column_name . '` ' . $column_data['column_type_sql'] . $after;
1705              break;
1706  
1707              case 'oracle':
1708                  // Does not support AFTER, only through temporary table
1709                  $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql'];
1710              break;
1711  
1712              case 'postgres':
1713                  // Does not support AFTER, only through temporary table
1714                  if (version_compare($this->db->sql_server_info(true), '8.0', '>='))
1715                  {
1716                      $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type_sql'];
1717                  }
1718                  else
1719                  {
1720                      // old versions cannot add columns with default and null information
1721                      $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type'] . ' ' . $column_data['constraint'];
1722  
1723                      if (isset($column_data['null']))
1724                      {
1725                          if ($column_data['null'] == 'NOT NULL')
1726                          {
1727                              $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET NOT NULL';
1728                          }
1729                      }
1730  
1731                      if (isset($column_data['default']))
1732                      {
1733                          $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default'];
1734                      }
1735                  }
1736  
1737              break;
1738  
1739              case 'sqlite':
1740  
1741                  if ($inline && $this->return_statements)
1742                  {
1743                      return $column_name . ' ' . $column_data['column_type_sql'];
1744                  }
1745  
1746                  if (version_compare(sqlite_libversion(), '3.0') == -1)
1747                  {
1748                      $sql = "SELECT sql
1749                          FROM sqlite_master
1750                          WHERE type = 'table'
1751                              AND name = '{$table_name}'
1752                          ORDER BY type DESC, name;";
1753                      $result = $this->db->sql_query($sql);
1754  
1755                      if (!$result)
1756                      {
1757                          break;
1758                      }
1759  
1760                      $row = $this->db->sql_fetchrow($result);
1761                      $this->db->sql_freeresult($result);
1762  
1763                      $statements[] = 'begin';
1764  
1765                      // Create a backup table and populate it, destroy the existing one
1766                      $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']);
1767                      $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
1768                      $statements[] = 'DROP TABLE ' . $table_name;
1769  
1770                      preg_match('#\((.*)\)#s', $row['sql'], $matches);
1771  
1772                      $new_table_cols = trim($matches[1]);
1773                      $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols);
1774                      $column_list = array();
1775  
1776                      foreach ($old_table_cols as $declaration)
1777                      {
1778                          $entities = preg_split('#\s+#', trim($declaration));
1779                          if ($entities[0] == 'PRIMARY')
1780                          {
1781                              continue;
1782                          }
1783                          $column_list[] = $entities[0];
1784                      }
1785  
1786                      $columns = implode(',', $column_list);
1787  
1788                      $new_table_cols = $column_name . ' ' . $column_data['column_type_sql'] . ',' . $new_table_cols;
1789  
1790                      // create a new table and fill it up. destroy the temp one
1791                      $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ');';
1792                      $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
1793                      $statements[] = 'DROP TABLE ' . $table_name . '_temp';
1794  
1795                      $statements[] = 'commit';
1796                  }
1797                  else
1798                  {
1799                      $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' [' . $column_data['column_type_sql'] . ']';
1800                  }
1801              break;
1802          }
1803  
1804          return $this->_sql_run_sql($statements);
1805      }
1806  
1807      /**
1808      * Drop column
1809      */
1810  	function sql_column_remove($table_name, $column_name, $inline = false)
1811      {
1812          $statements = array();
1813  
1814          switch ($this->sql_layer)
1815          {
1816              case 'firebird':
1817                  $statements[] = 'ALTER TABLE ' . $table_name . ' DROP "' . strtoupper($column_name) . '"';
1818              break;
1819  
1820              case 'mssql':
1821              case 'mssqlnative':
1822                  // remove default cosntraints first
1823                  // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx
1824                  $statements[] = "DECLARE @drop_default_name VARCHAR(100), @cmd VARCHAR(1000)
1825                      SET @drop_default_name =
1826                          (SELECT so.name FROM sysobjects so
1827                          JOIN sysconstraints sc ON so.id = sc.constid
1828                          WHERE object_name(so.parent_obj) = '{$table_name}'
1829                              AND so.xtype = 'D'
1830                              AND sc.colid = (SELECT colid FROM syscolumns
1831                                  WHERE id = object_id('{$table_name}')
1832                                      AND name = '{$column_name}'))
1833                      IF @drop_default_name <> ''
1834                      BEGIN
1835                          SET @cmd = 'ALTER TABLE [{$table_name}] DROP CONSTRAINT [' + @drop_default_name + ']'
1836                          EXEC(@cmd)
1837                      END";
1838                  $statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']';
1839              break;
1840  
1841              case 'mysql_40':
1842              case 'mysql_41':
1843                  $statements[] = 'ALTER TABLE `' . $table_name . '` DROP COLUMN `' . $column_name . '`';
1844              break;
1845  
1846              case 'oracle':
1847                  $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name;
1848              break;
1849  
1850              case 'postgres':
1851                  $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN "' . $column_name . '"';
1852              break;
1853  
1854              case 'sqlite':
1855  
1856                  if ($inline && $this->return_statements)
1857                  {
1858                      return $column_name;
1859                  }
1860  
1861                  if (version_compare(sqlite_libversion(), '3.0') == -1)
1862                  {
1863                      $sql = "SELECT sql
1864                          FROM sqlite_master
1865                          WHERE type = 'table'
1866                              AND name = '{$table_name}'
1867                          ORDER BY type DESC, name;";
1868                      $result = $this->db->sql_query($sql);
1869  
1870                      if (!$result)
1871                      {
1872                          break;
1873                      }
1874  
1875                      $row = $this->db->sql_fetchrow($result);
1876                      $this->db->sql_freeresult($result);
1877  
1878                      $statements[] = 'begin';
1879  
1880                      // Create a backup table and populate it, destroy the existing one
1881                      $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']);
1882                      $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
1883                      $statements[] = 'DROP TABLE ' . $table_name;
1884  
1885                      preg_match('#\((.*)\)#s', $row['sql'], $matches);
1886  
1887                      $new_table_cols = trim($matches[1]);
1888                      $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols);
1889                      $column_list = array();
1890  
1891                      foreach ($old_table_cols as $declaration)
1892                      {
1893                          $entities = preg_split('#\s+#', trim($declaration));
1894                          if ($entities[0] == 'PRIMARY' || $entities[0] === $column_name)
1895                          {
1896                              continue;
1897                          }
1898                          $column_list[] = $entities[0];
1899                      }
1900  
1901                      $columns = implode(',', $column_list);
1902  
1903                      $new_table_cols = preg_replace('/' . $column_name . '[^,]+(?:,|$)/m', '', $new_table_cols);
1904  
1905                      // create a new table and fill it up. destroy the temp one
1906                      $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ');';
1907                      $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
1908                      $statements[] = 'DROP TABLE ' . $table_name . '_temp';
1909  
1910                      $statements[] = 'commit';
1911                  }
1912                  else
1913                  {
1914                      $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name;
1915                  }
1916              break;
1917          }
1918  
1919          return $this->_sql_run_sql($statements);
1920      }
1921  
1922      /**
1923      * Drop Index
1924      */
1925  	function sql_index_drop($table_name, $index_name)
1926      {
1927          $statements = array();
1928  
1929          switch ($this->sql_layer)
1930          {
1931              case 'mssql':
1932              case 'mssqlnative':
1933                  $statements[] = 'DROP INDEX ' . $table_name . '.' . $index_name;
1934              break;
1935  
1936              case 'mysql_40':
1937              case 'mysql_41':
1938                  $statements[] = 'DROP INDEX ' . $index_name . ' ON ' . $table_name;
1939              break;
1940  
1941              case 'firebird':
1942              case 'oracle':
1943              case 'postgres':
1944              case 'sqlite':
1945                  $statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name;
1946              break;
1947          }
1948  
1949          return $this->_sql_run_sql($statements);
1950      }
1951  
1952      /**
1953      * Drop Table
1954      */
1955  	function sql_table_drop($table_name)
1956      {
1957          $statements = array();
1958  
1959          if (!$this->sql_table_exists($table_name))
1960          {
1961              return $this->_sql_run_sql($statements);
1962          }
1963  
1964          // the most basic operation, get rid of the table
1965          $statements[] = 'DROP TABLE ' . $table_name;
1966  
1967          switch ($this->sql_layer)
1968          {
1969              case 'firebird':
1970                  $sql = 'SELECT RDB$GENERATOR_NAME as gen
1971                      FROM RDB$GENERATORS
1972                      WHERE RDB$SYSTEM_FLAG = 0
1973                          AND RDB$GENERATOR_NAME = \'' . strtoupper($table_name) . "_GEN'";
1974                  $result = $this->db->sql_query($sql);
1975  
1976                  // does a generator exist?
1977                  if ($row = $this->db->sql_fetchrow($result))
1978                  {
1979                      $statements[] = "DROP GENERATOR {$row['gen']};";
1980                  }
1981                  $this->db->sql_freeresult($result);
1982              break;
1983  
1984              case 'oracle':
1985                  $sql = 'SELECT A.REFERENCED_NAME
1986                      FROM USER_DEPENDENCIES A, USER_TRIGGERS B
1987                      WHERE A.REFERENCED_TYPE = \'SEQUENCE\'
1988                          AND A.NAME = B.TRIGGER_NAME
1989                          AND B.TABLE_NAME = \'' . strtoupper($table_name) . "'";
1990                  $result = $this->db->sql_query($sql);
1991  
1992                  // any sequences ref'd to this table's triggers?
1993                  while ($row = $this->db->sql_fetchrow($result))
1994                  {
1995                      $statements[] = "DROP SEQUENCE {$row['referenced_name']}";
1996                  }
1997                  $this->db->sql_freeresult($result);
1998              break;
1999  
2000              case 'postgres':
2001                  // PGSQL does not "tightly" bind sequences and tables, we must guess...
2002                  $sql = "SELECT relname
2003                      FROM pg_class
2004                      WHERE relkind = 'S'
2005                          AND relname = '{$table_name}_seq'";
2006                  $result = $this->db->sql_query($sql);
2007  
2008                  // We don't even care about storing the results. We already know the answer if we get rows back.
2009                  if ($this->db->sql_fetchrow($result))
2010                  {
2011                      $statements[] =  "DROP SEQUENCE {$table_name}_seq;\n";
2012                  }
2013                  $this->db->sql_freeresult($result);
2014              break;
2015          }
2016  
2017          return $this->_sql_run_sql($statements);
2018      }
2019  
2020      /**
2021      * Add primary key
2022      */
2023  	function sql_create_primary_key($table_name, $column, $inline = false)
2024      {
2025          $statements = array();
2026  
2027          switch ($this->sql_layer)
2028          {
2029              case 'firebird':
2030              case 'postgres':
2031              case 'mysql_40':
2032              case 'mysql_41':
2033                  $statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')';
2034              break;
2035  
2036              case 'mssql':
2037              case 'mssqlnative':
2038                  $sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD ";
2039                  $sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY  CLUSTERED (";
2040                  $sql .= '[' . implode("],\n\t\t[", $column) . ']';
2041                  $sql .= ') ON [PRIMARY]';
2042  
2043                  $statements[] = $sql;
2044              break;
2045  
2046              case 'oracle':
2047                  $statements[] = 'ALTER TABLE ' . $table_name . 'add CONSTRAINT pk_' . $table_name . ' PRIMARY KEY (' . implode(', ', $column) . ')';
2048              break;
2049  
2050              case 'sqlite':
2051  
2052                  if ($inline && $this->return_statements)
2053                  {
2054                      return $column;
2055                  }
2056  
2057                  $sql = "SELECT sql
2058                      FROM sqlite_master
2059                      WHERE type = 'table'
2060                          AND name = '{$table_name}'
2061                      ORDER BY type DESC, name;";
2062                  $result = $this->db->sql_query($sql);
2063  
2064                  if (!$result)
2065                  {
2066                      break;
2067                  }
2068  
2069                  $row = $this->db->sql_fetchrow($result);
2070                  $this->db->sql_freeresult($result);
2071  
2072                  $statements[] = 'begin';
2073  
2074                  // Create a backup table and populate it, destroy the existing one
2075                  $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']);
2076                  $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
2077                  $statements[] = 'DROP TABLE ' . $table_name;
2078  
2079                  preg_match('#\((.*)\)#s', $row['sql'], $matches);
2080  
2081                  $new_table_cols = trim($matches[1]);
2082                  $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols);
2083                  $column_list = array();
2084  
2085                  foreach ($old_table_cols as $declaration)
2086                  {
2087                      $entities = preg_split('#\s+#', trim($declaration));
2088                      if ($entities[0] == 'PRIMARY')
2089                      {
2090                          continue;
2091                      }
2092                      $column_list[] = $entities[0];
2093                  }
2094  
2095                  $columns = implode(',', $column_list);
2096  
2097                  // create a new table and fill it up. destroy the temp one
2098                  $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ', PRIMARY KEY (' . implode(', ', $column) . '));';
2099                  $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
2100                  $statements[] = 'DROP TABLE ' . $table_name . '_temp';
2101  
2102                  $statements[] = 'commit';
2103              break;
2104          }
2105  
2106          return $this->_sql_run_sql($statements);
2107      }
2108  
2109      /**
2110      * Add unique index
2111      */
2112  	function sql_create_unique_index($table_name, $index_name, $column)
2113      {
2114          $statements = array();
2115  
2116          $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config)
2117          if (strlen($table_name . $index_name) - strlen($table_prefix) > 24)
2118          {
2119              $max_length = strlen($table_prefix) + 24;
2120              trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR);
2121          }
2122  
2123          switch ($this->sql_layer)
2124          {
2125              case 'firebird':
2126              case 'postgres':
2127              case 'oracle':
2128              case 'sqlite':
2129                  $statements[] = 'CREATE UNIQUE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')';
2130              break;
2131  
2132              case 'mysql_40':
2133              case 'mysql_41':
2134                  $statements[] = 'ALTER TABLE ' . $table_name . ' ADD UNIQUE INDEX ' . $index_name . '(' . implode(', ', $column) . ')';
2135              break;
2136  
2137              case 'mssql':
2138              case 'mssqlnative':
2139                  $statements[] = 'CREATE UNIQUE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ') ON [PRIMARY]';
2140              break;
2141          }
2142  
2143          return $this->_sql_run_sql($statements);
2144      }
2145  
2146      /**
2147      * Add index
2148      */
2149  	function sql_create_index($table_name, $index_name, $column)
2150      {
2151          $statements = array();
2152  
2153          $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config)
2154          if (strlen($table_name . $index_name) - strlen($table_prefix) > 24)
2155          {
2156              $max_length = strlen($table_prefix) + 24;
2157              trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR);
2158          }
2159  
2160          // remove index length unless MySQL4
2161          if ('mysql_40' != $this->sql_layer)
2162          {
2163              $column = preg_replace('#:.*$#', '', $column);
2164          }
2165  
2166          switch ($this->sql_layer)
2167          {
2168              case 'firebird':
2169              case 'postgres':
2170              case 'oracle':
2171              case 'sqlite':
2172                  $statements[] = 'CREATE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')';
2173              break;
2174  
2175              case 'mysql_40':
2176                  // add index size to definition as required by MySQL4
2177                  foreach ($column as $i => $col)
2178                  {
2179                      if (false !== strpos($col, ':'))
2180                      {
2181                          list($col, $index_size) = explode(':', $col);
2182                          $column[$i] = "$col($index_size)";
2183                      }
2184                  }
2185              // no break
2186              case 'mysql_41':
2187                  $statements[] = 'ALTER TABLE ' . $table_name . ' ADD INDEX ' . $index_name . '(' . implode(', ', $column) . ')';
2188              break;
2189  
2190              case 'mssql':
2191              case 'mssqlnative':
2192                  $statements[] = 'CREATE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ') ON [PRIMARY]';
2193              break;
2194          }
2195  
2196          return $this->_sql_run_sql($statements);
2197      }
2198  
2199      /**
2200      * List all of the indices that belong to a table,
2201      * does not count:
2202      * * UNIQUE indices
2203      * * PRIMARY keys
2204      */
2205  	function sql_list_index($table_name)
2206      {
2207          $index_array = array();
2208  
2209          if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative')
2210          {
2211              $sql = "EXEC sp_statistics '$table_name'";
2212              $result = $this->db->sql_query($sql);
2213              while ($row = $this->db->sql_fetchrow($result))
2214              {
2215                  if ($row['TYPE'] == 3)
2216                  {
2217                      $index_array[] = $row['INDEX_NAME'];
2218                  }
2219              }
2220              $this->db->sql_freeresult($result);
2221          }
2222          else
2223          {
2224              switch ($this->sql_layer)
2225              {
2226                  case 'firebird':
2227                      $sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name
2228                          FROM RDB\$INDICES
2229                          WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "'
2230                              AND RDB\$UNIQUE_FLAG IS NULL
2231                              AND RDB\$FOREIGN_KEY IS NULL";
2232                      $col = 'index_name';
2233                  break;
2234  
2235                  case 'postgres':
2236                      $sql = "SELECT ic.relname as index_name
2237                          FROM pg_class bc, pg_class ic, pg_index i
2238                          WHERE (bc.oid = i.indrelid)
2239                              AND (ic.oid = i.indexrelid)
2240                              AND (bc.relname = '" . $table_name . "')
2241                              AND (i.indisunique != 't')
2242                              AND (i.indisprimary != 't')";
2243                      $col = 'index_name';
2244                  break;
2245  
2246                  case 'mysql_40':
2247                  case 'mysql_41':
2248                      $sql = 'SHOW KEYS
2249                          FROM ' . $table_name;
2250                      $col = 'Key_name';
2251                  break;
2252  
2253                  case 'oracle':
2254                      $sql = "SELECT index_name
2255                          FROM user_indexes
2256                          WHERE table_name = '" . strtoupper($table_name) . "'
2257                              AND generated = 'N'
2258                              AND uniqueness = 'NONUNIQUE'";
2259                      $col = 'index_name';
2260                  break;
2261  
2262                  case 'sqlite':
2263                      $sql = "PRAGMA index_info('" . $table_name . "');";
2264                      $col = 'name';
2265                  break;
2266              }
2267  
2268              $result = $this->db->sql_query($sql);
2269              while ($row = $this->db->sql_fetchrow($result))
2270              {
2271                  if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique'])
2272                  {
2273                      continue;
2274                  }
2275  
2276                  switch ($this->sql_layer)
2277                  {
2278                      case 'firebird':
2279                      case 'oracle':
2280                      case 'postgres':
2281                      case 'sqlite':
2282                          $row[$col] = substr($row[$col], strlen($table_name) + 1);
2283                      break;
2284                  }
2285  
2286                  $index_array[] = $row[$col];
2287              }
2288              $this->db->sql_freeresult($result);
2289          }
2290  
2291          return array_map('strtolower', $index_array);
2292      }
2293  
2294      /**
2295      * Change column type (not name!)
2296      */
2297  	function sql_column_change($table_name, $column_name, $column_data, $inline = false)
2298      {
2299          $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
2300          $statements = array();
2301  
2302          switch ($this->sql_layer)
2303          {
2304              case 'firebird':
2305                  // Change type...
2306                  if (!empty($column_data['column_type_sql_default']))
2307                  {
2308                      $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN "' . strtoupper($column_name) . '" TYPE ' . ' ' . $column_data['column_type_sql_type'];
2309                      $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN "' . strtoupper($column_name) . '" SET DEFAULT ' . ' ' . $column_data['column_type_sql_default'];
2310                  }
2311                  else
2312                  {
2313                      // TODO: try to change pkey without removing trigger, generator or constraints. ATM this query may fail.
2314                      $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN "' . strtoupper($column_name) . '" TYPE ' . ' ' . $column_data['column_type_sql_type'];
2315                  }
2316              break;
2317  
2318              case 'mssql':
2319              case 'mssqlnative':
2320                  $statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql'];
2321  
2322                  if (!empty($column_data['default']))
2323                  {
2324                      // Using TRANSACT-SQL for this statement because we do not want to have colliding data if statements are executed at a later stage
2325                      $statements[] = "DECLARE @drop_default_name VARCHAR(100), @cmd VARCHAR(1000)
2326                          SET @drop_default_name =
2327                              (SELECT so.name FROM sysobjects so
2328                              JOIN sysconstraints sc ON so.id = sc.constid
2329                              WHERE object_name(so.parent_obj) = '{$table_name}'
2330                                  AND so.xtype = 'D'
2331                                  AND sc.colid = (SELECT colid FROM syscolumns
2332                                      WHERE id = object_id('{$table_name}')
2333                                          AND name = '{$column_name}'))
2334                          IF @drop_default_name <> ''
2335                          BEGIN
2336                              SET @cmd = 'ALTER TABLE [{$table_name}] DROP CONSTRAINT [' + @drop_default_name + ']'
2337                              EXEC(@cmd)
2338                          END
2339                          SET @cmd = 'ALTER TABLE [{$table_name}] ADD CONSTRAINT [DF_{$table_name}_{$column_name}_1] {$column_data['default']} FOR [{$column_name}]'
2340                          EXEC(@cmd)";
2341                  }
2342              break;
2343  
2344              case 'mysql_40':
2345              case 'mysql_41':
2346                  $statements[] = 'ALTER TABLE `' . $table_name . '` CHANGE `' . $column_name . '` `' . $column_name . '` ' . $column_data['column_type_sql'];
2347              break;
2348  
2349              case 'oracle':
2350                  $statements[] = 'ALTER TABLE ' . $table_name . ' MODIFY ' . $column_name . ' ' . $column_data['column_type_sql'];
2351              break;
2352  
2353              case 'postgres':
2354                  $sql = 'ALTER TABLE ' . $table_name . ' ';
2355  
2356                  $sql_array = array();
2357                  $sql_array[] = 'ALTER COLUMN ' . $column_name . ' TYPE ' . $column_data['column_type'];
2358  
2359                  if (isset($column_data['null']))
2360                  {
2361                      if ($column_data['null'] == 'NOT NULL')
2362                      {
2363                          $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET NOT NULL';
2364                      }
2365                      else if ($column_data['null'] == 'NULL')
2366                      {
2367                          $sql_array[] = 'ALTER COLUMN ' . $column_name . ' DROP NOT NULL';
2368                      }
2369                  }
2370  
2371                  if (isset($column_data['default']))
2372                  {
2373                      $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default'];
2374                  }
2375  
2376                  // we don't want to double up on constraints if we change different number data types
2377                  if (isset($column_data['constraint']))
2378                  {
2379                      $constraint_sql = "SELECT consrc as constraint_data
2380                                  FROM pg_constraint, pg_class bc
2381                                  WHERE conrelid = bc.oid
2382                                      AND bc.relname = '{$table_name}'
2383                                      AND NOT EXISTS (
2384                                          SELECT *
2385                                              FROM pg_constraint as c, pg_inherits as i
2386                                              WHERE i.inhrelid = pg_constraint.conrelid
2387                                                  AND c.conname = pg_constraint.conname
2388                                                  AND c.consrc = pg_constraint.consrc
2389                                                  AND c.conrelid = i.inhparent
2390                                      )";
2391  
2392                      $constraint_exists = false;
2393  
2394                      $result = $this->db->sql_query($constraint_sql);
2395                      while ($row = $this->db->sql_fetchrow($result))
2396                      {
2397                          if (trim($row['constraint_data']) == trim($column_data['constraint']))
2398                          {
2399                              $constraint_exists = true;
2400                              break;
2401                          }
2402                      }
2403                      $this->db->sql_freeresult($result);
2404  
2405                      if (!$constraint_exists)
2406                      {
2407                          $sql_array[] = 'ADD ' . $column_data['constraint'];
2408                      }
2409                  }
2410  
2411                  $sql .= implode(', ', $sql_array);
2412  
2413                  $statements[] = $sql;
2414              break;
2415  
2416              case 'sqlite':
2417  
2418                  if ($inline && $this->return_statements)
2419                  {
2420                      return $column_name . ' ' . $column_data['column_type_sql'];
2421                  }
2422  
2423                  $sql = "SELECT sql
2424                      FROM sqlite_master
2425                      WHERE type = 'table'
2426                          AND name = '{$table_name}'
2427                      ORDER BY type DESC, name;";
2428                  $result = $this->db->sql_query($sql);
2429  
2430                  if (!$result)
2431                  {
2432                      break;
2433                  }
2434  
2435                  $row = $this->db->sql_fetchrow($result);
2436                  $this->db->sql_freeresult($result);
2437  
2438                  $statements[] = 'begin';
2439  
2440                  // Create a temp table and populate it, destroy the existing one
2441                  $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']);
2442                  $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
2443                  $statements[] = 'DROP TABLE ' . $table_name;
2444  
2445                  preg_match('#\((.*)\)#s', $row['sql'], $matches);
2446  
2447                  $new_table_cols = trim($matches[1]);
2448                  $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols);
2449                  $column_list = array();
2450  
2451                  foreach ($old_table_cols as $key => $declaration)
2452                  {
2453                      $entities = preg_split('#\s+#', trim($declaration));
2454                      $column_list[] = $entities[0];
2455                      if ($entities[0] == $column_name)
2456                      {
2457                          $old_table_cols[$key] = $column_name . ' ' . $column_data['column_type_sql'];
2458                      }
2459                  }
2460  
2461                  $columns = implode(',', $column_list);
2462  
2463                  // create a new table and fill it up. destroy the temp one
2464                  $statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $old_table_cols) . ');';
2465                  $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
2466                  $statements[] = 'DROP TABLE ' . $table_name . '_temp';
2467  
2468                  $statements[] = 'commit';
2469  
2470              break;
2471          }
2472  
2473          return $this->_sql_run_sql($statements);
2474      }
2475  }
2476  
2477  ?>


Generated: Wed Oct 2 15:03:47 2013 Cross-referenced by PHPXref 0.7.1