Verzeichnisstruktur phpBB-3.1.0


Veröffentlicht
27.10.2014

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

      (Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

tools.php

Zuletzt modifiziert: 09.10.2024, 12:52 - Dateigröße: 76.25 KiB


 0001  <?php
 0002  /**
 0003  *
 0004  * This file is part of the phpBB Forum Software package.
 0005  *
 0006  * @copyright (c) phpBB Limited <https://www.phpbb.com>
 0007  * @license GNU General Public License, version 2 (GPL-2.0)
 0008  *
 0009  * For full copyright and license information, please see
 0010  * the docs/CREDITS.txt file.
 0011  *
 0012  */
 0013   
 0014  namespace phpbb\db;
 0015   
 0016  /**
 0017  * Database Tools for handling cross-db actions such as altering columns, etc.
 0018  * Currently not supported is returning SQL for creating tables.
 0019  */
 0020  class tools
 0021  {
 0022      /**
 0023      * Current sql layer
 0024      */
 0025      var $sql_layer = '';
 0026   
 0027      /**
 0028      * @var object DB object
 0029      */
 0030      var $db = null;
 0031   
 0032      /**
 0033      * The Column types for every database we support
 0034      * @var array
 0035      */
 0036      var $dbms_type_map = array();
 0037   
 0038      /**
 0039      * Is the used MS SQL Server a SQL Server 2000?
 0040      * @var bool
 0041      */
 0042      protected $is_sql_server_2000;
 0043   
 0044      /**
 0045      * Get the column types for every database we support
 0046      *
 0047      * @return array
 0048      */
 0049      public static function get_dbms_type_map()
 0050      {
 0051          return array(
 0052              'mysql_41'    => array(
 0053                  'INT:'        => 'int(%d)',
 0054                  'BINT'        => 'bigint(20)',
 0055                  'UINT'        => 'mediumint(8) UNSIGNED',
 0056                  'UINT:'        => 'int(%d) UNSIGNED',
 0057                  'TINT:'        => 'tinyint(%d)',
 0058                  'USINT'        => 'smallint(4) UNSIGNED',
 0059                  'BOOL'        => 'tinyint(1) UNSIGNED',
 0060                  'VCHAR'        => 'varchar(255)',
 0061                  'VCHAR:'    => 'varchar(%d)',
 0062                  'CHAR:'        => 'char(%d)',
 0063                  'XSTEXT'    => 'text',
 0064                  'XSTEXT_UNI'=> 'varchar(100)',
 0065                  'STEXT'        => 'text',
 0066                  'STEXT_UNI'    => 'varchar(255)',
 0067                  'TEXT'        => 'text',
 0068                  'TEXT_UNI'    => 'text',
 0069                  'MTEXT'        => 'mediumtext',
 0070                  'MTEXT_UNI'    => 'mediumtext',
 0071                  'TIMESTAMP'    => 'int(11) UNSIGNED',
 0072                  'DECIMAL'    => 'decimal(5,2)',
 0073                  'DECIMAL:'    => 'decimal(%d,2)',
 0074                  'PDECIMAL'    => 'decimal(6,3)',
 0075                  'PDECIMAL:'    => 'decimal(%d,3)',
 0076                  'VCHAR_UNI'    => 'varchar(255)',
 0077                  'VCHAR_UNI:'=> 'varchar(%d)',
 0078                  'VCHAR_CI'    => 'varchar(255)',
 0079                  'VARBINARY'    => 'varbinary(255)',
 0080              ),
 0081   
 0082              'mysql_40'    => array(
 0083                  'INT:'        => 'int(%d)',
 0084                  'BINT'        => 'bigint(20)',
 0085                  'UINT'        => 'mediumint(8) UNSIGNED',
 0086                  'UINT:'        => 'int(%d) UNSIGNED',
 0087                  'TINT:'        => 'tinyint(%d)',
 0088                  'USINT'        => 'smallint(4) UNSIGNED',
 0089                  'BOOL'        => 'tinyint(1) UNSIGNED',
 0090                  'VCHAR'        => 'varbinary(255)',
 0091                  'VCHAR:'    => 'varbinary(%d)',
 0092                  'CHAR:'        => 'binary(%d)',
 0093                  'XSTEXT'    => 'blob',
 0094                  'XSTEXT_UNI'=> 'blob',
 0095                  'STEXT'        => 'blob',
 0096                  'STEXT_UNI'    => 'blob',
 0097                  'TEXT'        => 'blob',
 0098                  'TEXT_UNI'    => 'blob',
 0099                  'MTEXT'        => 'mediumblob',
 0100                  'MTEXT_UNI'    => 'mediumblob',
 0101                  'TIMESTAMP'    => 'int(11) UNSIGNED',
 0102                  'DECIMAL'    => 'decimal(5,2)',
 0103                  'DECIMAL:'    => 'decimal(%d,2)',
 0104                  'PDECIMAL'    => 'decimal(6,3)',
 0105                  'PDECIMAL:'    => 'decimal(%d,3)',
 0106                  'VCHAR_UNI'    => 'blob',
 0107                  'VCHAR_UNI:'=> array('varbinary(%d)', 'limit' => array('mult', 3, 255, 'blob')),
 0108                  'VCHAR_CI'    => 'blob',
 0109                  'VARBINARY'    => 'varbinary(255)',
 0110              ),
 0111   
 0112              'mssql'        => array(
 0113                  'INT:'        => '[int]',
 0114                  'BINT'        => '[float]',
 0115                  'UINT'        => '[int]',
 0116                  'UINT:'        => '[int]',
 0117                  'TINT:'        => '[int]',
 0118                  'USINT'        => '[int]',
 0119                  'BOOL'        => '[int]',
 0120                  'VCHAR'        => '[varchar] (255)',
 0121                  'VCHAR:'    => '[varchar] (%d)',
 0122                  'CHAR:'        => '[char] (%d)',
 0123                  'XSTEXT'    => '[varchar] (1000)',
 0124                  'STEXT'        => '[varchar] (3000)',
 0125                  'TEXT'        => '[varchar] (8000)',
 0126                  'MTEXT'        => '[text]',
 0127                  'XSTEXT_UNI'=> '[varchar] (100)',
 0128                  'STEXT_UNI'    => '[varchar] (255)',
 0129                  'TEXT_UNI'    => '[varchar] (4000)',
 0130                  'MTEXT_UNI'    => '[text]',
 0131                  'TIMESTAMP'    => '[int]',
 0132                  'DECIMAL'    => '[float]',
 0133                  'DECIMAL:'    => '[float]',
 0134                  'PDECIMAL'    => '[float]',
 0135                  'PDECIMAL:'    => '[float]',
 0136                  'VCHAR_UNI'    => '[varchar] (255)',
 0137                  'VCHAR_UNI:'=> '[varchar] (%d)',
 0138                  'VCHAR_CI'    => '[varchar] (255)',
 0139                  'VARBINARY'    => '[varchar] (255)',
 0140              ),
 0141   
 0142              'mssqlnative'    => array(
 0143                  'INT:'        => '[int]',
 0144                  'BINT'        => '[float]',
 0145                  'UINT'        => '[int]',
 0146                  'UINT:'        => '[int]',
 0147                  'TINT:'        => '[int]',
 0148                  'USINT'        => '[int]',
 0149                  'BOOL'        => '[int]',
 0150                  'VCHAR'        => '[varchar] (255)',
 0151                  'VCHAR:'    => '[varchar] (%d)',
 0152                  'CHAR:'        => '[char] (%d)',
 0153                  'XSTEXT'    => '[varchar] (1000)',
 0154                  'STEXT'        => '[varchar] (3000)',
 0155                  'TEXT'        => '[varchar] (8000)',
 0156                  'MTEXT'        => '[text]',
 0157                  'XSTEXT_UNI'=> '[varchar] (100)',
 0158                  'STEXT_UNI'    => '[varchar] (255)',
 0159                  'TEXT_UNI'    => '[varchar] (4000)',
 0160                  'MTEXT_UNI'    => '[text]',
 0161                  'TIMESTAMP'    => '[int]',
 0162                  'DECIMAL'    => '[float]',
 0163                  'DECIMAL:'    => '[float]',
 0164                  'PDECIMAL'    => '[float]',
 0165                  'PDECIMAL:'    => '[float]',
 0166                  'VCHAR_UNI'    => '[varchar] (255)',
 0167                  'VCHAR_UNI:'=> '[varchar] (%d)',
 0168                  'VCHAR_CI'    => '[varchar] (255)',
 0169                  'VARBINARY'    => '[varchar] (255)',
 0170              ),
 0171   
 0172              'oracle'    => array(
 0173                  'INT:'        => 'number(%d)',
 0174                  'BINT'        => 'number(20)',
 0175                  'UINT'        => 'number(8)',
 0176                  'UINT:'        => 'number(%d)',
 0177                  'TINT:'        => 'number(%d)',
 0178                  'USINT'        => 'number(4)',
 0179                  'BOOL'        => 'number(1)',
 0180                  'VCHAR'        => 'varchar2(255)',
 0181                  'VCHAR:'    => 'varchar2(%d)',
 0182                  'CHAR:'        => 'char(%d)',
 0183                  'XSTEXT'    => 'varchar2(1000)',
 0184                  'STEXT'        => 'varchar2(3000)',
 0185                  'TEXT'        => 'clob',
 0186                  'MTEXT'        => 'clob',
 0187                  'XSTEXT_UNI'=> 'varchar2(300)',
 0188                  'STEXT_UNI'    => 'varchar2(765)',
 0189                  'TEXT_UNI'    => 'clob',
 0190                  'MTEXT_UNI'    => 'clob',
 0191                  'TIMESTAMP'    => 'number(11)',
 0192                  'DECIMAL'    => 'number(5, 2)',
 0193                  'DECIMAL:'    => 'number(%d, 2)',
 0194                  'PDECIMAL'    => 'number(6, 3)',
 0195                  'PDECIMAL:'    => 'number(%d, 3)',
 0196                  'VCHAR_UNI'    => 'varchar2(765)',
 0197                  'VCHAR_UNI:'=> array('varchar2(%d)', 'limit' => array('mult', 3, 765, 'clob')),
 0198                  'VCHAR_CI'    => 'varchar2(255)',
 0199                  'VARBINARY'    => 'raw(255)',
 0200              ),
 0201   
 0202              'sqlite'    => array(
 0203                  'INT:'        => 'int(%d)',
 0204                  'BINT'        => 'bigint(20)',
 0205                  'UINT'        => 'INTEGER UNSIGNED', //'mediumint(8) UNSIGNED',
 0206                  'UINT:'        => 'INTEGER UNSIGNED', // 'int(%d) UNSIGNED',
 0207                  'TINT:'        => 'tinyint(%d)',
 0208                  'USINT'        => 'INTEGER UNSIGNED', //'mediumint(4) UNSIGNED',
 0209                  'BOOL'        => 'INTEGER UNSIGNED', //'tinyint(1) UNSIGNED',
 0210                  'VCHAR'        => 'varchar(255)',
 0211                  'VCHAR:'    => 'varchar(%d)',
 0212                  'CHAR:'        => 'char(%d)',
 0213                  'XSTEXT'    => 'text(65535)',
 0214                  'STEXT'        => 'text(65535)',
 0215                  'TEXT'        => 'text(65535)',
 0216                  'MTEXT'        => 'mediumtext(16777215)',
 0217                  'XSTEXT_UNI'=> 'text(65535)',
 0218                  'STEXT_UNI'    => 'text(65535)',
 0219                  'TEXT_UNI'    => 'text(65535)',
 0220                  'MTEXT_UNI'    => 'mediumtext(16777215)',
 0221                  'TIMESTAMP'    => 'INTEGER UNSIGNED', //'int(11) UNSIGNED',
 0222                  'DECIMAL'    => 'decimal(5,2)',
 0223                  'DECIMAL:'    => 'decimal(%d,2)',
 0224                  'PDECIMAL'    => 'decimal(6,3)',
 0225                  'PDECIMAL:'    => 'decimal(%d,3)',
 0226                  'VCHAR_UNI'    => 'varchar(255)',
 0227                  'VCHAR_UNI:'=> 'varchar(%d)',
 0228                  'VCHAR_CI'    => 'varchar(255)',
 0229                  'VARBINARY'    => 'blob',
 0230              ),
 0231   
 0232              'sqlite3'    => array(
 0233                  'INT:'        => 'INT(%d)',
 0234                  'BINT'        => 'BIGINT(20)',
 0235                  'UINT'        => 'INTEGER UNSIGNED',
 0236                  'UINT:'        => 'INTEGER UNSIGNED',
 0237                  'TINT:'        => 'TINYINT(%d)',
 0238                  'USINT'        => 'INTEGER UNSIGNED',
 0239                  'BOOL'        => 'INTEGER UNSIGNED',
 0240                  'VCHAR'        => 'VARCHAR(255)',
 0241                  'VCHAR:'    => 'VARCHAR(%d)',
 0242                  'CHAR:'        => 'CHAR(%d)',
 0243                  'XSTEXT'    => 'TEXT(65535)',
 0244                  'STEXT'        => 'TEXT(65535)',
 0245                  'TEXT'        => 'TEXT(65535)',
 0246                  'MTEXT'        => 'MEDIUMTEXT(16777215)',
 0247                  'XSTEXT_UNI'=> 'TEXT(65535)',
 0248                  'STEXT_UNI'    => 'TEXT(65535)',
 0249                  'TEXT_UNI'    => 'TEXT(65535)',
 0250                  'MTEXT_UNI'    => 'MEDIUMTEXT(16777215)',
 0251                  'TIMESTAMP'    => 'INTEGER UNSIGNED', //'int(11) UNSIGNED',
 0252                  'DECIMAL'    => 'DECIMAL(5,2)',
 0253                  'DECIMAL:'    => 'DECIMAL(%d,2)',
 0254                  'PDECIMAL'    => 'DECIMAL(6,3)',
 0255                  'PDECIMAL:'    => 'DECIMAL(%d,3)',
 0256                  'VCHAR_UNI'    => 'VARCHAR(255)',
 0257                  'VCHAR_UNI:'=> 'VARCHAR(%d)',
 0258                  'VCHAR_CI'    => 'VARCHAR(255)',
 0259                  'VARBINARY'    => 'BLOB',
 0260              ),
 0261   
 0262              'postgres'    => array(
 0263                  'INT:'        => 'INT4',
 0264                  'BINT'        => 'INT8',
 0265                  'UINT'        => 'INT4', // unsigned
 0266                  'UINT:'        => 'INT4', // unsigned
 0267                  'USINT'        => 'INT2', // unsigned
 0268                  'BOOL'        => 'INT2', // unsigned
 0269                  'TINT:'        => 'INT2',
 0270                  'VCHAR'        => 'varchar(255)',
 0271                  'VCHAR:'    => 'varchar(%d)',
 0272                  'CHAR:'        => 'char(%d)',
 0273                  'XSTEXT'    => 'varchar(1000)',
 0274                  'STEXT'        => 'varchar(3000)',
 0275                  'TEXT'        => 'varchar(8000)',
 0276                  'MTEXT'        => 'TEXT',
 0277                  'XSTEXT_UNI'=> 'varchar(100)',
 0278                  'STEXT_UNI'    => 'varchar(255)',
 0279                  'TEXT_UNI'    => 'varchar(4000)',
 0280                  'MTEXT_UNI'    => 'TEXT',
 0281                  'TIMESTAMP'    => 'INT4', // unsigned
 0282                  'DECIMAL'    => 'decimal(5,2)',
 0283                  'DECIMAL:'    => 'decimal(%d,2)',
 0284                  'PDECIMAL'    => 'decimal(6,3)',
 0285                  'PDECIMAL:'    => 'decimal(%d,3)',
 0286                  'VCHAR_UNI'    => 'varchar(255)',
 0287                  'VCHAR_UNI:'=> 'varchar(%d)',
 0288                  'VCHAR_CI'    => 'varchar_ci',
 0289                  'VARBINARY'    => 'bytea',
 0290              ),
 0291          );
 0292      }
 0293   
 0294      /**
 0295      * A list of types being unsigned for better reference in some db's
 0296      * @var array
 0297      */
 0298      var $unsigned_types = array('UINT', 'UINT:', 'USINT', 'BOOL', 'TIMESTAMP');
 0299   
 0300      /**
 0301      * A list of supported DBMS. We change this class to support more DBMS, the DBMS itself only need to follow some rules.
 0302      * @var array
 0303      */
 0304      var $supported_dbms = array('mssql', 'mssqlnative', 'mysql_40', 'mysql_41', 'oracle', 'postgres', 'sqlite', 'sqlite3');
 0305   
 0306      /**
 0307      * This is set to true if user only wants to return the 'to-be-executed' SQL statement(s) (as an array).
 0308      * This mode has no effect on some methods (inserting of data for example). This is expressed within the methods command.
 0309      */
 0310      var $return_statements = false;
 0311   
 0312      /**
 0313      * Constructor. Set DB Object and set {@link $return_statements return_statements}.
 0314      *
 0315      * @param \phpbb\db\driver\driver_interface    $db                    Database connection
 0316      * @param bool        $return_statements    True if only statements should be returned and no SQL being executed
 0317      */
 0318      public function __construct(\phpbb\db\driver\driver_interface $db, $return_statements = false)
 0319      {
 0320          $this->db = $db;
 0321          $this->return_statements = $return_statements;
 0322   
 0323          $this->dbms_type_map = self::get_dbms_type_map();
 0324   
 0325          // Determine mapping database type
 0326          switch ($this->db->get_sql_layer())
 0327          {
 0328              case 'mysql':
 0329                  $this->sql_layer = 'mysql_40';
 0330              break;
 0331   
 0332              case 'mysql4':
 0333                  if (version_compare($this->db->sql_server_info(true), '4.1.3', '>='))
 0334                  {
 0335                      $this->sql_layer = 'mysql_41';
 0336                  }
 0337                  else
 0338                  {
 0339                      $this->sql_layer = 'mysql_40';
 0340                  }
 0341              break;
 0342   
 0343              case 'mysqli':
 0344                  $this->sql_layer = 'mysql_41';
 0345              break;
 0346   
 0347              case 'mssql':
 0348              case 'mssql_odbc':
 0349                  $this->sql_layer = 'mssql';
 0350              break;
 0351   
 0352              case 'mssqlnative':
 0353                  $this->sql_layer = 'mssqlnative';
 0354              break;
 0355   
 0356              default:
 0357                  $this->sql_layer = $this->db->get_sql_layer();
 0358              break;
 0359          }
 0360      }
 0361   
 0362      /**
 0363      * Setter for {@link $return_statements return_statements}.
 0364      *
 0365      * @param bool $return_statements True if SQL should not be executed but returned as strings
 0366      * @return null
 0367      */
 0368      public function set_return_statements($return_statements)
 0369      {
 0370          $this->return_statements = $return_statements;
 0371      }
 0372   
 0373      /**
 0374      * Gets a list of tables in the database.
 0375      *
 0376      * @return array        Array of table names  (all lower case)
 0377      */
 0378      function sql_list_tables()
 0379      {
 0380          switch ($this->db->get_sql_layer())
 0381          {
 0382              case 'mysql':
 0383              case 'mysql4':
 0384              case 'mysqli':
 0385                  $sql = 'SHOW TABLES';
 0386              break;
 0387   
 0388              case 'sqlite':
 0389                  $sql = 'SELECT name
 0390                      FROM sqlite_master
 0391                      WHERE type = "table"';
 0392              break;
 0393   
 0394              case 'sqlite3':
 0395                  $sql = 'SELECT name
 0396                      FROM sqlite_master
 0397                      WHERE type = "table"
 0398                          AND name <> "sqlite_sequence"';
 0399              break;
 0400   
 0401              case 'mssql':
 0402              case 'mssql_odbc':
 0403              case 'mssqlnative':
 0404                  $sql = "SELECT name
 0405                      FROM sysobjects
 0406                      WHERE type='U'";
 0407              break;
 0408   
 0409              case 'postgres':
 0410                  $sql = 'SELECT relname
 0411                      FROM pg_stat_user_tables';
 0412              break;
 0413   
 0414              case 'oracle':
 0415                  $sql = 'SELECT table_name
 0416                      FROM USER_TABLES';
 0417              break;
 0418          }
 0419   
 0420          $result = $this->db->sql_query($sql);
 0421   
 0422          $tables = array();
 0423          while ($row = $this->db->sql_fetchrow($result))
 0424          {
 0425              $name = current($row);
 0426              $tables[$name] = $name;
 0427          }
 0428          $this->db->sql_freeresult($result);
 0429   
 0430          return $tables;
 0431      }
 0432   
 0433      /**
 0434      * Check if table exists
 0435      *
 0436      *
 0437      * @param string    $table_name    The table name to check for
 0438      * @return bool true if table exists, else false
 0439      */
 0440      function sql_table_exists($table_name)
 0441      {
 0442          $this->db->sql_return_on_error(true);
 0443          $result = $this->db->sql_query_limit('SELECT * FROM ' . $table_name, 1);
 0444          $this->db->sql_return_on_error(false);
 0445   
 0446          if ($result)
 0447          {
 0448              $this->db->sql_freeresult($result);
 0449              return true;
 0450          }
 0451   
 0452          return false;
 0453      }
 0454   
 0455      /**
 0456      * Create SQL Table
 0457      *
 0458      * @param string    $table_name    The table name to create
 0459      * @param array    $table_data    Array containing table data.
 0460      * @return array    Statements if $return_statements is true.
 0461      */
 0462      function sql_create_table($table_name, $table_data)
 0463      {
 0464          // holds the DDL for a column
 0465          $columns = $statements = array();
 0466   
 0467          if ($this->sql_table_exists($table_name))
 0468          {
 0469              return $this->_sql_run_sql($statements);
 0470          }
 0471   
 0472          // Begin transaction
 0473          $statements[] = 'begin';
 0474   
 0475          // Determine if we have created a PRIMARY KEY in the earliest
 0476          $primary_key_gen = false;
 0477   
 0478          // Determine if the table requires a sequence
 0479          $create_sequence = false;
 0480   
 0481          // Begin table sql statement
 0482          switch ($this->sql_layer)
 0483          {
 0484              case 'mssql':
 0485              case 'mssqlnative':
 0486                  $table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n";
 0487              break;
 0488   
 0489              default:
 0490                  $table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n";
 0491              break;
 0492          }
 0493   
 0494          if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative')
 0495          {
 0496              if (!isset($table_data['PRIMARY_KEY']))
 0497              {
 0498                  $table_data['COLUMNS']['mssqlindex'] = array('UINT', null, 'auto_increment');
 0499                  $table_data['PRIMARY_KEY'] = 'mssqlindex';
 0500              }
 0501          }
 0502   
 0503          // Iterate through the columns to create a table
 0504          foreach ($table_data['COLUMNS'] as $column_name => $column_data)
 0505          {
 0506              // here lies an array, filled with information compiled on the column's data
 0507              $prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
 0508   
 0509              if (isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'] && strlen($column_name) > 26) // "${column_name}_gen"
 0510              {
 0511                  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);
 0512              }
 0513   
 0514              // here we add the definition of the new column to the list of columns
 0515              switch ($this->sql_layer)
 0516              {
 0517                  case 'mssql':
 0518                  case 'mssqlnative':
 0519                      $columns[] = "\t [{$column_name}] " . $prepared_column['column_type_sql_default'];
 0520                  break;
 0521   
 0522                  default:
 0523                      $columns[] = "\t {$column_name} " . $prepared_column['column_type_sql'];
 0524                  break;
 0525              }
 0526   
 0527              // see if we have found a primary key set due to a column definition if we have found it, we can stop looking
 0528              if (!$primary_key_gen)
 0529              {
 0530                  $primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set'];
 0531              }
 0532   
 0533              // create sequence DDL based off of the existance of auto incrementing columns
 0534              if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'])
 0535              {
 0536                  $create_sequence = $column_name;
 0537              }
 0538          }
 0539   
 0540          // this makes up all the columns in the create table statement
 0541          $table_sql .= implode(",\n", $columns);
 0542   
 0543          // Close the table for two DBMS and add to the statements
 0544          switch ($this->sql_layer)
 0545          {
 0546              case 'mssql':
 0547              case 'mssqlnative':
 0548                  $table_sql .= "\n);";
 0549                  $statements[] = $table_sql;
 0550              break;
 0551          }
 0552   
 0553          // we have yet to create a primary key for this table,
 0554          // this means that we can add the one we really wanted instead
 0555          if (!$primary_key_gen)
 0556          {
 0557              // Write primary key
 0558              if (isset($table_data['PRIMARY_KEY']))
 0559              {
 0560                  if (!is_array($table_data['PRIMARY_KEY']))
 0561                  {
 0562                      $table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']);
 0563                  }
 0564   
 0565                  switch ($this->sql_layer)
 0566                  {
 0567                      case 'mysql_40':
 0568                      case 'mysql_41':
 0569                      case 'postgres':
 0570                      case 'sqlite':
 0571                      case 'sqlite3':
 0572                          $table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')';
 0573                      break;
 0574   
 0575                      case 'mssql':
 0576                      case 'mssqlnative':
 0577                          // We need the data here
 0578                          $old_return_statements = $this->return_statements;
 0579                          $this->return_statements = true;
 0580   
 0581                          $primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']);
 0582                          foreach ($primary_key_stmts as $pk_stmt)
 0583                          {
 0584                              $statements[] = $pk_stmt;
 0585                          }
 0586   
 0587                          $this->return_statements = $old_return_statements;
 0588                      break;
 0589   
 0590                      case 'oracle':
 0591                          $table_sql .= ",\n\t CONSTRAINT pk_{$table_name} PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')';
 0592                      break;
 0593                  }
 0594              }
 0595          }
 0596   
 0597          // close the table
 0598          switch ($this->sql_layer)
 0599          {
 0600              case 'mysql_41':
 0601                  // make sure the table is in UTF-8 mode
 0602                  $table_sql .= "\n) CHARACTER SET `utf8` COLLATE `utf8_bin`;";
 0603                  $statements[] = $table_sql;
 0604              break;
 0605   
 0606              case 'mysql_40':
 0607              case 'sqlite':
 0608              case 'sqlite3':
 0609                  $table_sql .= "\n);";
 0610                  $statements[] = $table_sql;
 0611              break;
 0612   
 0613              case 'postgres':
 0614                  // do we need to add a sequence for auto incrementing columns?
 0615                  if ($create_sequence)
 0616                  {
 0617                      $statements[] = "CREATE SEQUENCE {$table_name}_seq;";
 0618                  }
 0619   
 0620                  $table_sql .= "\n);";
 0621                  $statements[] = $table_sql;
 0622              break;
 0623   
 0624              case 'oracle':
 0625                  $table_sql .= "\n)";
 0626                  $statements[] = $table_sql;
 0627   
 0628                  // do we need to add a sequence and a tigger for auto incrementing columns?
 0629                  if ($create_sequence)
 0630                  {
 0631                      // create the actual sequence
 0632                      $statements[] = "CREATE SEQUENCE {$table_name}_seq";
 0633   
 0634                      // the trigger is the mechanism by which we increment the counter
 0635                      $trigger = "CREATE OR REPLACE TRIGGER t_{$table_name}\n";
 0636                      $trigger .= "BEFORE INSERT ON {$table_name}\n";
 0637                      $trigger .= "FOR EACH ROW WHEN (\n";
 0638                      $trigger .= "\tnew.{$create_sequence} IS NULL OR new.{$create_sequence} = 0\n";
 0639                      $trigger .= ")\n";
 0640                      $trigger .= "BEGIN\n";
 0641                      $trigger .= "\tSELECT {$table_name}_seq.nextval\n";
 0642                      $trigger .= "\tINTO :new.{$create_sequence}\n";
 0643                      $trigger .= "\tFROM dual;\n";
 0644                      $trigger .= "END;";
 0645   
 0646                      $statements[] = $trigger;
 0647                  }
 0648              break;
 0649          }
 0650   
 0651          // Write Keys
 0652          if (isset($table_data['KEYS']))
 0653          {
 0654              foreach ($table_data['KEYS'] as $key_name => $key_data)
 0655              {
 0656                  if (!is_array($key_data[1]))
 0657                  {
 0658                      $key_data[1] = array($key_data[1]);
 0659                  }
 0660   
 0661                  $old_return_statements = $this->return_statements;
 0662                  $this->return_statements = true;
 0663   
 0664                  $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]);
 0665   
 0666                  foreach ($key_stmts as $key_stmt)
 0667                  {
 0668                      $statements[] = $key_stmt;
 0669                  }
 0670   
 0671                  $this->return_statements = $old_return_statements;
 0672              }
 0673          }
 0674   
 0675          // Commit Transaction
 0676          $statements[] = 'commit';
 0677   
 0678          return $this->_sql_run_sql($statements);
 0679      }
 0680   
 0681      /**
 0682      * Handle passed database update array.
 0683      * Expected structure...
 0684      * Key being one of the following
 0685      *    drop_tables: Drop tables
 0686      *    add_tables: Add tables
 0687      *    change_columns: Column changes (only type, not name)
 0688      *    add_columns: Add columns to a table
 0689      *    drop_keys: Dropping keys
 0690      *    drop_columns: Removing/Dropping columns
 0691      *    add_primary_keys: adding primary keys
 0692      *    add_unique_index: adding an unique index
 0693      *    add_index: adding an index (can be column:index_size if you need to provide size)
 0694      *
 0695      * The values are in this format:
 0696      *        {TABLE NAME}        => array(
 0697      *            {COLUMN NAME}        => array({COLUMN TYPE}, {DEFAULT VALUE}, {OPTIONAL VARIABLES}),
 0698      *            {KEY/INDEX NAME}    => array({COLUMN NAMES}),
 0699      *        )
 0700      *
 0701      * For more information have a look at /develop/create_schema_files.php (only available through SVN)
 0702      */
 0703      function perform_schema_changes($schema_changes)
 0704      {
 0705          if (empty($schema_changes))
 0706          {
 0707              return;
 0708          }
 0709   
 0710          $statements = array();
 0711          $sqlite = false;
 0712   
 0713          // For SQLite we need to perform the schema changes in a much more different way
 0714          if (($this->db->get_sql_layer() == 'sqlite' || $this->db->get_sql_layer() == 'sqlite3') && $this->return_statements)
 0715          {
 0716              $sqlite_data = array();
 0717              $sqlite = true;
 0718          }
 0719   
 0720          // Drop tables?
 0721          if (!empty($schema_changes['drop_tables']))
 0722          {
 0723              foreach ($schema_changes['drop_tables'] as $table)
 0724              {
 0725                  // only drop table if it exists
 0726                  if ($this->sql_table_exists($table))
 0727                  {
 0728                      $result = $this->sql_table_drop($table);
 0729                      if ($this->return_statements)
 0730                      {
 0731                          $statements = array_merge($statements, $result);
 0732                      }
 0733                  }
 0734              }
 0735          }
 0736   
 0737          // Add tables?
 0738          if (!empty($schema_changes['add_tables']))
 0739          {
 0740              foreach ($schema_changes['add_tables'] as $table => $table_data)
 0741              {
 0742                  $result = $this->sql_create_table($table, $table_data);
 0743                  if ($this->return_statements)
 0744                  {
 0745                      $statements = array_merge($statements, $result);
 0746                  }
 0747              }
 0748          }
 0749   
 0750          // Change columns?
 0751          if (!empty($schema_changes['change_columns']))
 0752          {
 0753              foreach ($schema_changes['change_columns'] as $table => $columns)
 0754              {
 0755                  foreach ($columns as $column_name => $column_data)
 0756                  {
 0757                      // If the column exists we change it, else we add it ;)
 0758                      if ($column_exists = $this->sql_column_exists($table, $column_name))
 0759                      {
 0760                          $result = $this->sql_column_change($table, $column_name, $column_data, true);
 0761                      }
 0762                      else
 0763                      {
 0764                          $result = $this->sql_column_add($table, $column_name, $column_data, true);
 0765                      }
 0766   
 0767                      if ($sqlite)
 0768                      {
 0769                          if ($column_exists)
 0770                          {
 0771                              $sqlite_data[$table]['change_columns'][] = $result;
 0772                          }
 0773                          else
 0774                          {
 0775                              $sqlite_data[$table]['add_columns'][] = $result;
 0776                          }
 0777                      }
 0778                      else if ($this->return_statements)
 0779                      {
 0780                          $statements = array_merge($statements, $result);
 0781                      }
 0782                  }
 0783              }
 0784          }
 0785   
 0786          // Add columns?
 0787          if (!empty($schema_changes['add_columns']))
 0788          {
 0789              foreach ($schema_changes['add_columns'] as $table => $columns)
 0790              {
 0791                  foreach ($columns as $column_name => $column_data)
 0792                  {
 0793                      // Only add the column if it does not exist yet
 0794                      if ($column_exists = $this->sql_column_exists($table, $column_name))
 0795                      {
 0796                          continue;
 0797                          // This is commented out here because it can take tremendous time on updates
 0798  //                        $result = $this->sql_column_change($table, $column_name, $column_data, true);
 0799                      }
 0800                      else
 0801                      {
 0802                          $result = $this->sql_column_add($table, $column_name, $column_data, true);
 0803                      }
 0804   
 0805                      if ($sqlite)
 0806                      {
 0807                          if ($column_exists)
 0808                          {
 0809                              continue;
 0810  //                            $sqlite_data[$table]['change_columns'][] = $result;
 0811                          }
 0812                          else
 0813                          {
 0814                              $sqlite_data[$table]['add_columns'][] = $result;
 0815                          }
 0816                      }
 0817                      else if ($this->return_statements)
 0818                      {
 0819                          $statements = array_merge($statements, $result);
 0820                      }
 0821                  }
 0822              }
 0823          }
 0824   
 0825          // Remove keys?
 0826          if (!empty($schema_changes['drop_keys']))
 0827          {
 0828              foreach ($schema_changes['drop_keys'] as $table => $indexes)
 0829              {
 0830                  foreach ($indexes as $index_name)
 0831                  {
 0832                      if (!$this->sql_index_exists($table, $index_name))
 0833                      {
 0834                          continue;
 0835                      }
 0836   
 0837                      $result = $this->sql_index_drop($table, $index_name);
 0838   
 0839                      if ($this->return_statements)
 0840                      {
 0841                          $statements = array_merge($statements, $result);
 0842                      }
 0843                  }
 0844              }
 0845          }
 0846   
 0847          // Drop columns?
 0848          if (!empty($schema_changes['drop_columns']))
 0849          {
 0850              foreach ($schema_changes['drop_columns'] as $table => $columns)
 0851              {
 0852                  foreach ($columns as $column)
 0853                  {
 0854                      // Only remove the column if it exists...
 0855                      if ($this->sql_column_exists($table, $column))
 0856                      {
 0857                          $result = $this->sql_column_remove($table, $column, true);
 0858   
 0859                          if ($sqlite)
 0860                          {
 0861                              $sqlite_data[$table]['drop_columns'][] = $result;
 0862                          }
 0863                          else if ($this->return_statements)
 0864                          {
 0865                              $statements = array_merge($statements, $result);
 0866                          }
 0867                      }
 0868                  }
 0869              }
 0870          }
 0871   
 0872          // Add primary keys?
 0873          if (!empty($schema_changes['add_primary_keys']))
 0874          {
 0875              foreach ($schema_changes['add_primary_keys'] as $table => $columns)
 0876              {
 0877                  $result = $this->sql_create_primary_key($table, $columns, true);
 0878   
 0879                  if ($sqlite)
 0880                  {
 0881                      $sqlite_data[$table]['primary_key'] = $result;
 0882                  }
 0883                  else if ($this->return_statements)
 0884                  {
 0885                      $statements = array_merge($statements, $result);
 0886                  }
 0887              }
 0888          }
 0889   
 0890          // Add unique indexes?
 0891          if (!empty($schema_changes['add_unique_index']))
 0892          {
 0893              foreach ($schema_changes['add_unique_index'] as $table => $index_array)
 0894              {
 0895                  foreach ($index_array as $index_name => $column)
 0896                  {
 0897                      if ($this->sql_unique_index_exists($table, $index_name))
 0898                      {
 0899                          continue;
 0900                      }
 0901   
 0902                      $result = $this->sql_create_unique_index($table, $index_name, $column);
 0903   
 0904                      if ($this->return_statements)
 0905                      {
 0906                          $statements = array_merge($statements, $result);
 0907                      }
 0908                  }
 0909              }
 0910          }
 0911   
 0912          // Add indexes?
 0913          if (!empty($schema_changes['add_index']))
 0914          {
 0915              foreach ($schema_changes['add_index'] as $table => $index_array)
 0916              {
 0917                  foreach ($index_array as $index_name => $column)
 0918                  {
 0919                      if ($this->sql_index_exists($table, $index_name))
 0920                      {
 0921                          continue;
 0922                      }
 0923   
 0924                      $result = $this->sql_create_index($table, $index_name, $column);
 0925   
 0926                      if ($this->return_statements)
 0927                      {
 0928                          $statements = array_merge($statements, $result);
 0929                      }
 0930                  }
 0931              }
 0932          }
 0933   
 0934          if ($sqlite)
 0935          {
 0936              foreach ($sqlite_data as $table_name => $sql_schema_changes)
 0937              {
 0938                  // Create temporary table with original data
 0939                  $statements[] = 'begin';
 0940   
 0941                  $sql = "SELECT sql
 0942                      FROM sqlite_master
 0943                      WHERE type = 'table'
 0944                          AND name = '{$table_name}'
 0945                      ORDER BY type DESC, name;";
 0946                  $result = $this->db->sql_query($sql);
 0947   
 0948                  if (!$result)
 0949                  {
 0950                      continue;
 0951                  }
 0952   
 0953                  $row = $this->db->sql_fetchrow($result);
 0954                  $this->db->sql_freeresult($result);
 0955   
 0956                  // Create a backup table and populate it, destroy the existing one
 0957                  $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']);
 0958                  $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
 0959                  $statements[] = 'DROP TABLE ' . $table_name;
 0960   
 0961                  // Get the columns...
 0962                  preg_match('#\((.*)\)#s', $row['sql'], $matches);
 0963   
 0964                  $plain_table_cols = trim($matches[1]);
 0965                  $new_table_cols = preg_split('/,(?![\s\w]+\))/m', $plain_table_cols);
 0966                  $column_list = array();
 0967   
 0968                  foreach ($new_table_cols as $declaration)
 0969                  {
 0970                      $entities = preg_split('#\s+#', trim($declaration));
 0971                      if ($entities[0] == 'PRIMARY')
 0972                      {
 0973                          continue;
 0974                      }
 0975                      $column_list[] = $entities[0];
 0976                  }
 0977   
 0978                  // note down the primary key notation because sqlite only supports adding it to the end for the new table
 0979                  $primary_key = false;
 0980                  $_new_cols = array();
 0981   
 0982                  foreach ($new_table_cols as $key => $declaration)
 0983                  {
 0984                      $entities = preg_split('#\s+#', trim($declaration));
 0985                      if ($entities[0] == 'PRIMARY')
 0986                      {
 0987                          $primary_key = $declaration;
 0988                          continue;
 0989                      }
 0990                      $_new_cols[] = $declaration;
 0991                  }
 0992   
 0993                  $new_table_cols = $_new_cols;
 0994   
 0995                  // First of all... change columns
 0996                  if (!empty($sql_schema_changes['change_columns']))
 0997                  {
 0998                      foreach ($sql_schema_changes['change_columns'] as $column_sql)
 0999                      {
 1000                          foreach ($new_table_cols as $key => $declaration)
 1001                          {
 1002                              $entities = preg_split('#\s+#', trim($declaration));
 1003                              if (strpos($column_sql, $entities[0] . ' ') === 0)
 1004                              {
 1005                                  $new_table_cols[$key] = $column_sql;
 1006                              }
 1007                          }
 1008                      }
 1009                  }
 1010   
 1011                  if (!empty($sql_schema_changes['add_columns']))
 1012                  {
 1013                      foreach ($sql_schema_changes['add_columns'] as $column_sql)
 1014                      {
 1015                          $new_table_cols[] = $column_sql;
 1016                      }
 1017                  }
 1018   
 1019                  // Now drop them...
 1020                  if (!empty($sql_schema_changes['drop_columns']))
 1021                  {
 1022                      foreach ($sql_schema_changes['drop_columns'] as $column_name)
 1023                      {
 1024                          // Remove from column list...
 1025                          $new_column_list = array();
 1026                          foreach ($column_list as $key => $value)
 1027                          {
 1028                              if ($value === $column_name)
 1029                              {
 1030                                  continue;
 1031                              }
 1032   
 1033                              $new_column_list[] = $value;
 1034                          }
 1035   
 1036                          $column_list = $new_column_list;
 1037   
 1038                          // Remove from table...
 1039                          $_new_cols = array();
 1040                          foreach ($new_table_cols as $key => $declaration)
 1041                          {
 1042                              $entities = preg_split('#\s+#', trim($declaration));
 1043                              if (strpos($column_name . ' ', $entities[0] . ' ') === 0)
 1044                              {
 1045                                  continue;
 1046                              }
 1047                              $_new_cols[] = $declaration;
 1048                          }
 1049                          $new_table_cols = $_new_cols;
 1050                      }
 1051                  }
 1052   
 1053                  // Primary key...
 1054                  if (!empty($sql_schema_changes['primary_key']))
 1055                  {
 1056                      $new_table_cols[] = 'PRIMARY KEY (' . implode(', ', $sql_schema_changes['primary_key']) . ')';
 1057                  }
 1058                  // Add a new one or the old primary key
 1059                  else if ($primary_key !== false)
 1060                  {
 1061                      $new_table_cols[] = $primary_key;
 1062                  }
 1063   
 1064                  $columns = implode(',', $column_list);
 1065   
 1066                  // create a new table and fill it up. destroy the temp one
 1067                  $statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $new_table_cols) . ');';
 1068                  $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
 1069                  $statements[] = 'DROP TABLE ' . $table_name . '_temp';
 1070   
 1071                  $statements[] = 'commit';
 1072              }
 1073          }
 1074   
 1075          if ($this->return_statements)
 1076          {
 1077              return $statements;
 1078          }
 1079      }
 1080   
 1081      /**
 1082      * Gets a list of columns of a table.
 1083      *
 1084      * @param string $table        Table name
 1085      *
 1086      * @return array                Array of column names (all lower case)
 1087      */
 1088      function sql_list_columns($table)
 1089      {
 1090          $columns = array();
 1091   
 1092          switch ($this->sql_layer)
 1093          {
 1094              case 'mysql_40':
 1095              case 'mysql_41':
 1096                  $sql = "SHOW COLUMNS FROM $table";
 1097              break;
 1098   
 1099              // PostgreSQL has a way of doing this in a much simpler way but would
 1100              // not allow us to support all versions of PostgreSQL
 1101              case 'postgres':
 1102                  $sql = "SELECT a.attname
 1103                      FROM pg_class c, pg_attribute a
 1104                      WHERE c.relname = '{$table}'
 1105                          AND a.attnum > 0
 1106                          AND a.attrelid = c.oid";
 1107              break;
 1108   
 1109              // same deal with PostgreSQL, we must perform more complex operations than
 1110              // we technically could
 1111              case 'mssql':
 1112              case 'mssqlnative':
 1113                  $sql = "SELECT c.name
 1114                      FROM syscolumns c
 1115                      LEFT JOIN sysobjects o ON c.id = o.id
 1116                      WHERE o.name = '{$table}'";
 1117              break;
 1118   
 1119              case 'oracle':
 1120                  $sql = "SELECT column_name
 1121                      FROM user_tab_columns
 1122                      WHERE LOWER(table_name) = '" . strtolower($table) . "'";
 1123              break;
 1124   
 1125              case 'sqlite':
 1126              case 'sqlite3':
 1127                  $sql = "SELECT sql
 1128                      FROM sqlite_master
 1129                      WHERE type = 'table'
 1130                          AND name = '{$table}'";
 1131   
 1132                  $result = $this->db->sql_query($sql);
 1133   
 1134                  if (!$result)
 1135                  {
 1136                      return false;
 1137                  }
 1138   
 1139                  $row = $this->db->sql_fetchrow($result);
 1140                  $this->db->sql_freeresult($result);
 1141   
 1142                  preg_match('#\((.*)\)#s', $row['sql'], $matches);
 1143   
 1144                  $cols = trim($matches[1]);
 1145                  $col_array = preg_split('/,(?![\s\w]+\))/m', $cols);
 1146   
 1147                  foreach ($col_array as $declaration)
 1148                  {
 1149                      $entities = preg_split('#\s+#', trim($declaration));
 1150                      if ($entities[0] == 'PRIMARY')
 1151                      {
 1152                          continue;
 1153                      }
 1154   
 1155                      $column = strtolower($entities[0]);
 1156                      $columns[$column] = $column;
 1157                  }
 1158   
 1159                  return $columns;
 1160              break;
 1161          }
 1162   
 1163          $result = $this->db->sql_query($sql);
 1164   
 1165          while ($row = $this->db->sql_fetchrow($result))
 1166          {
 1167              $column = strtolower(current($row));
 1168              $columns[$column] = $column;
 1169          }
 1170          $this->db->sql_freeresult($result);
 1171   
 1172          return $columns;
 1173      }
 1174   
 1175      /**
 1176      * Check whether a specified column exist in a table
 1177      *
 1178      * @param string    $table            Table to check
 1179      * @param string    $column_name    Column to check
 1180      *
 1181      * @return bool        True if column exists, false otherwise
 1182      */
 1183      function sql_column_exists($table, $column_name)
 1184      {
 1185          $columns = $this->sql_list_columns($table);
 1186   
 1187          return isset($columns[$column_name]);
 1188      }
 1189   
 1190      /**
 1191      * Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes.
 1192      *
 1193      * @param string    $table_name        Table to check the index at
 1194      * @param string    $index_name        The index name to check
 1195      *
 1196      * @return bool True if index exists, else false
 1197      */
 1198      function sql_index_exists($table_name, $index_name)
 1199      {
 1200          if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative')
 1201          {
 1202              $sql = "EXEC sp_statistics '$table_name'";
 1203              $result = $this->db->sql_query($sql);
 1204   
 1205              while ($row = $this->db->sql_fetchrow($result))
 1206              {
 1207                  if ($row['TYPE'] == 3)
 1208                  {
 1209                      if (strtolower($row['INDEX_NAME']) == strtolower($index_name))
 1210                      {
 1211                          $this->db->sql_freeresult($result);
 1212                          return true;
 1213                      }
 1214                  }
 1215              }
 1216              $this->db->sql_freeresult($result);
 1217   
 1218              return false;
 1219          }
 1220   
 1221          switch ($this->sql_layer)
 1222          {
 1223              case 'postgres':
 1224                  $sql = "SELECT ic.relname as index_name
 1225                      FROM pg_class bc, pg_class ic, pg_index i
 1226                      WHERE (bc.oid = i.indrelid)
 1227                          AND (ic.oid = i.indexrelid)
 1228                          AND (bc.relname = '" . $table_name . "')
 1229                          AND (i.indisunique != 't')
 1230                          AND (i.indisprimary != 't')";
 1231                  $col = 'index_name';
 1232              break;
 1233   
 1234              case 'mysql_40':
 1235              case 'mysql_41':
 1236                  $sql = 'SHOW KEYS
 1237                      FROM ' . $table_name;
 1238                  $col = 'Key_name';
 1239              break;
 1240   
 1241              case 'oracle':
 1242                  $sql = "SELECT index_name
 1243                      FROM user_indexes
 1244                      WHERE table_name = '" . strtoupper($table_name) . "'
 1245                          AND generated = 'N'
 1246                          AND uniqueness = 'NONUNIQUE'";
 1247                  $col = 'index_name';
 1248              break;
 1249   
 1250              case 'sqlite':
 1251              case 'sqlite3':
 1252                  $sql = "PRAGMA index_list('" . $table_name . "');";
 1253                  $col = 'name';
 1254              break;
 1255          }
 1256   
 1257          $result = $this->db->sql_query($sql);
 1258          while ($row = $this->db->sql_fetchrow($result))
 1259          {
 1260              if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique'])
 1261              {
 1262                  continue;
 1263              }
 1264   
 1265              // These DBMS prefix index name with the table name
 1266              switch ($this->sql_layer)
 1267              {
 1268                  case 'oracle':
 1269                  case 'postgres':
 1270                  case 'sqlite':
 1271                  case 'sqlite3':
 1272                      $row[$col] = substr($row[$col], strlen($table_name) + 1);
 1273                  break;
 1274              }
 1275   
 1276              if (strtolower($row[$col]) == strtolower($index_name))
 1277              {
 1278                  $this->db->sql_freeresult($result);
 1279                  return true;
 1280              }
 1281          }
 1282          $this->db->sql_freeresult($result);
 1283   
 1284          return false;
 1285      }
 1286   
 1287      /**
 1288      * Check if a specified index exists in table. Does not return PRIMARY KEY indexes.
 1289      *
 1290      * @param string    $table_name        Table to check the index at
 1291      * @param string    $index_name        The index name to check
 1292      *
 1293      * @return bool True if index exists, else false
 1294      */
 1295      function sql_unique_index_exists($table_name, $index_name)
 1296      {
 1297          if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative')
 1298          {
 1299              $sql = "EXEC sp_statistics '$table_name'";
 1300              $result = $this->db->sql_query($sql);
 1301   
 1302              while ($row = $this->db->sql_fetchrow($result))
 1303              {
 1304                  // Usually NON_UNIQUE is the column we want to check, but we allow for both
 1305                  if ($row['TYPE'] == 3)
 1306                  {
 1307                      if (strtolower($row['INDEX_NAME']) == strtolower($index_name))
 1308                      {
 1309                          $this->db->sql_freeresult($result);
 1310                          return true;
 1311                      }
 1312                  }
 1313              }
 1314              $this->db->sql_freeresult($result);
 1315              return false;
 1316          }
 1317   
 1318          switch ($this->sql_layer)
 1319          {
 1320              case 'postgres':
 1321                  $sql = "SELECT ic.relname as index_name, i.indisunique
 1322                      FROM pg_class bc, pg_class ic, pg_index i
 1323                      WHERE (bc.oid = i.indrelid)
 1324                          AND (ic.oid = i.indexrelid)
 1325                          AND (bc.relname = '" . $table_name . "')
 1326                          AND (i.indisprimary != 't')";
 1327                  $col = 'index_name';
 1328              break;
 1329   
 1330              case 'mysql_40':
 1331              case 'mysql_41':
 1332                  $sql = 'SHOW KEYS
 1333                      FROM ' . $table_name;
 1334                  $col = 'Key_name';
 1335              break;
 1336   
 1337              case 'oracle':
 1338                  $sql = "SELECT index_name, table_owner
 1339                      FROM user_indexes
 1340                      WHERE table_name = '" . strtoupper($table_name) . "'
 1341                          AND generated = 'N'
 1342                          AND uniqueness = 'UNIQUE'";
 1343                  $col = 'index_name';
 1344              break;
 1345   
 1346              case 'sqlite':
 1347              case 'sqlite3':
 1348                  $sql = "PRAGMA index_list('" . $table_name . "');";
 1349                  $col = 'name';
 1350              break;
 1351          }
 1352   
 1353          $result = $this->db->sql_query($sql);
 1354          while ($row = $this->db->sql_fetchrow($result))
 1355          {
 1356              if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && ($row['Non_unique'] || $row[$col] == 'PRIMARY'))
 1357              {
 1358                  continue;
 1359              }
 1360   
 1361              if (($this->sql_layer == 'sqlite' || $this->sql_layer == 'sqlite3') && !$row['unique'])
 1362              {
 1363                  continue;
 1364              }
 1365   
 1366              if ($this->sql_layer == 'postgres' && $row['indisunique'] != 't')
 1367              {
 1368                  continue;
 1369              }
 1370   
 1371              // These DBMS prefix index name with the table name
 1372              switch ($this->sql_layer)
 1373              {
 1374                  case 'oracle':
 1375                      // Two cases here... prefixed with U_[table_owner] and not prefixed with table_name
 1376                      if (strpos($row[$col], 'U_') === 0)
 1377                      {
 1378                          $row[$col] = substr($row[$col], strlen('U_' . $row['table_owner']) + 1);
 1379                      }
 1380                      else if (strpos($row[$col], strtoupper($table_name)) === 0)
 1381                      {
 1382                          $row[$col] = substr($row[$col], strlen($table_name) + 1);
 1383                      }
 1384                  break;
 1385   
 1386                  case 'postgres':
 1387                  case 'sqlite':
 1388                  case 'sqlite3':
 1389                      $row[$col] = substr($row[$col], strlen($table_name) + 1);
 1390                  break;
 1391              }
 1392   
 1393              if (strtolower($row[$col]) == strtolower($index_name))
 1394              {
 1395                  $this->db->sql_freeresult($result);
 1396                  return true;
 1397              }
 1398          }
 1399          $this->db->sql_freeresult($result);
 1400   
 1401          return false;
 1402      }
 1403   
 1404      /**
 1405      * Private method for performing sql statements (either execute them or return them)
 1406      * @access private
 1407      */
 1408      function _sql_run_sql($statements)
 1409      {
 1410          if ($this->return_statements)
 1411          {
 1412              return $statements;
 1413          }
 1414   
 1415          // We could add error handling here...
 1416          foreach ($statements as $sql)
 1417          {
 1418              if ($sql === 'begin')
 1419              {
 1420                  $this->db->sql_transaction('begin');
 1421              }
 1422              else if ($sql === 'commit')
 1423              {
 1424                  $this->db->sql_transaction('commit');
 1425              }
 1426              else
 1427              {
 1428                  $this->db->sql_query($sql);
 1429              }
 1430          }
 1431   
 1432          return true;
 1433      }
 1434   
 1435      /**
 1436      * Function to prepare some column information for better usage
 1437      * @access private
 1438      */
 1439      function sql_prepare_column_data($table_name, $column_name, $column_data)
 1440      {
 1441          if (strlen($column_name) > 30)
 1442          {
 1443              trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR);
 1444          }
 1445   
 1446          // Get type
 1447          list($column_type, $orig_column_type) = $this->get_column_type($column_data[0]);
 1448   
 1449          // Adjust default value if db-dependent specified
 1450          if (is_array($column_data[1]))
 1451          {
 1452              $column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default'];
 1453          }
 1454   
 1455          $sql = '';
 1456   
 1457          $return_array = array();
 1458   
 1459          switch ($this->sql_layer)
 1460          {
 1461              case 'mssql':
 1462              case 'mssqlnative':
 1463                  $sql .= " {$column_type} ";
 1464                  $sql_default = " {$column_type} ";
 1465   
 1466                  // For adding columns we need the default definition
 1467                  if (!is_null($column_data[1]))
 1468                  {
 1469                      // For hexadecimal values do not use single quotes
 1470                      if (strpos($column_data[1], '0x') === 0)
 1471                      {
 1472                          $return_array['default'] = 'DEFAULT (' . $column_data[1] . ') ';
 1473                          $sql_default .= $return_array['default'];
 1474                      }
 1475                      else
 1476                      {
 1477                          $return_array['default'] = 'DEFAULT (' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ') ';
 1478                          $sql_default .= $return_array['default'];
 1479                      }
 1480                  }
 1481   
 1482                  if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
 1483                  {
 1484  //                    $sql .= 'IDENTITY (1, 1) ';
 1485                      $sql_default .= 'IDENTITY (1, 1) ';
 1486                  }
 1487   
 1488                  $return_array['textimage'] = $column_type === '[text]';
 1489   
 1490                  if (!is_null($column_data[1]) || (isset($column_data[2]) && $column_data[2] == 'auto_increment'))
 1491                  {
 1492                      $sql .= 'NOT NULL';
 1493                      $sql_default .= 'NOT NULL';
 1494                  }
 1495                  else
 1496                  {
 1497                      $sql .= 'NULL';
 1498                      $sql_default .= 'NULL';
 1499                  }
 1500   
 1501                  $return_array['column_type_sql_default'] = $sql_default;
 1502   
 1503              break;
 1504   
 1505              case 'mysql_40':
 1506              case 'mysql_41':
 1507                  $sql .= " {$column_type} ";
 1508   
 1509                  // For hexadecimal values do not use single quotes
 1510                  if (!is_null($column_data[1]) && substr($column_type, -4) !== 'text' && substr($column_type, -4) !== 'blob')
 1511                  {
 1512                      $sql .= (strpos($column_data[1], '0x') === 0) ? "DEFAULT {$column_data[1]} " : "DEFAULT '{$column_data[1]}' ";
 1513                  }
 1514   
 1515                  if (!is_null($column_data[1]) || (isset($column_data[2]) && $column_data[2] == 'auto_increment'))
 1516                  {
 1517                      $sql .= 'NOT NULL';
 1518                  }
 1519                  else
 1520                  {
 1521                      $sql .= 'NULL';
 1522                  }
 1523   
 1524                  if (isset($column_data[2]))
 1525                  {
 1526                      if ($column_data[2] == 'auto_increment')
 1527                      {
 1528                          $sql .= ' auto_increment';
 1529                      }
 1530                      else if ($this->sql_layer === 'mysql_41' && $column_data[2] == 'true_sort')
 1531                      {
 1532                          $sql .= ' COLLATE utf8_unicode_ci';
 1533                      }
 1534                  }
 1535   
 1536              break;
 1537   
 1538              case 'oracle':
 1539                  $sql .= " {$column_type} ";
 1540                  $sql .= (!is_null($column_data[1])) ? "DEFAULT '{$column_data[1]}' " : '';
 1541   
 1542                  // In Oracle empty strings ('') are treated as NULL.
 1543                  // Therefore in oracle we allow NULL's for all DEFAULT '' entries
 1544                  // Oracle does not like setting NOT NULL on a column that is already NOT NULL (this happens only on number fields)
 1545                  if (!preg_match('/number/i', $column_type))
 1546                  {
 1547                      $sql .= ($column_data[1] === '' || $column_data[1] === null) ? '' : 'NOT NULL';
 1548                  }
 1549   
 1550                  $return_array['auto_increment'] = false;
 1551                  if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
 1552                  {
 1553                      $return_array['auto_increment'] = true;
 1554                  }
 1555   
 1556              break;
 1557   
 1558              case 'postgres':
 1559                  $return_array['column_type'] = $column_type;
 1560   
 1561                  $sql .= " {$column_type} ";
 1562   
 1563                  $return_array['auto_increment'] = false;
 1564                  if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
 1565                  {
 1566                      $default_val = "nextval('{$table_name}_seq')";
 1567                      $return_array['auto_increment'] = true;
 1568                  }
 1569                  else if (!is_null($column_data[1]))
 1570                  {
 1571                      $default_val = "'" . $column_data[1] . "'";
 1572                      $return_array['null'] = 'NOT NULL';
 1573                      $sql .= 'NOT NULL ';
 1574                  }
 1575                  else
 1576                  {
 1577                      $default_val = "'" . $column_data[1] . "'";
 1578                      $return_array['null'] = 'NULL';
 1579                      $sql .= 'NULL ';
 1580                  }
 1581   
 1582                  $return_array['default'] = $default_val;
 1583   
 1584                  $sql .= "DEFAULT {$default_val}";
 1585   
 1586                  // Unsigned? Then add a CHECK contraint
 1587                  if (in_array($orig_column_type, $this->unsigned_types))
 1588                  {
 1589                      $return_array['constraint'] = "CHECK ({$column_name} >= 0)";
 1590                      $sql .= " CHECK ({$column_name} >= 0)";
 1591                  }
 1592   
 1593              break;
 1594   
 1595              case 'sqlite':
 1596              case 'sqlite3':
 1597                  $return_array['primary_key_set'] = false;
 1598                  if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
 1599                  {
 1600                      $sql .= ' INTEGER PRIMARY KEY';
 1601                      $return_array['primary_key_set'] = true;
 1602   
 1603                      if ($this->sql_layer === 'sqlite3')
 1604                      {
 1605                          $sql .= ' AUTOINCREMENT';
 1606                      }
 1607                  }
 1608                  else
 1609                  {
 1610                      $sql .= ' ' . $column_type;
 1611                  }
 1612   
 1613                  if (!is_null($column_data[1]))
 1614                  {
 1615                      $sql .= ' NOT NULL ';
 1616                      $sql .= "DEFAULT '{$column_data[1]}'";
 1617                  }
 1618   
 1619              break;
 1620          }
 1621   
 1622          $return_array['column_type_sql'] = $sql;
 1623   
 1624          return $return_array;
 1625      }
 1626   
 1627      /**
 1628      * Get the column's database type from the type map
 1629      *
 1630      * @param string $column_map_type
 1631      * @return array        column type for this database
 1632      *                    and map type without length
 1633      */
 1634      function get_column_type($column_map_type)
 1635      {
 1636          if (strpos($column_map_type, ':') !== false)
 1637          {
 1638              list($orig_column_type, $column_length) = explode(':', $column_map_type);
 1639              if (!is_array($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']))
 1640              {
 1641                  $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'], $column_length);
 1642              }
 1643              else
 1644              {
 1645                  if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule']))
 1646                  {
 1647                      switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][0])
 1648                      {
 1649                          case 'div':
 1650                              $column_length /= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][1];
 1651                              $column_length = ceil($column_length);
 1652                              $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length);
 1653                          break;
 1654                      }
 1655                  }
 1656   
 1657                  if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit']))
 1658                  {
 1659                      switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][0])
 1660                      {
 1661                          case 'mult':
 1662                              $column_length *= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][1];
 1663                              if ($column_length > $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][2])
 1664                              {
 1665                                  $column_type = $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][3];
 1666                              }
 1667                              else
 1668                              {
 1669                                  $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length);
 1670                              }
 1671                          break;
 1672                      }
 1673                  }
 1674              }
 1675              $orig_column_type .= ':';
 1676          }
 1677          else
 1678          {
 1679              $orig_column_type = $column_map_type;
 1680              $column_type = $this->dbms_type_map[$this->sql_layer][$column_map_type];
 1681          }
 1682   
 1683          return array($column_type, $orig_column_type);
 1684      }
 1685   
 1686      /**
 1687      * Add new column
 1688      */
 1689      function sql_column_add($table_name, $column_name, $column_data, $inline = false)
 1690      {
 1691          $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
 1692          $statements = array();
 1693   
 1694          switch ($this->sql_layer)
 1695          {
 1696              case 'mssql':
 1697              case 'mssqlnative':
 1698                  // Does not support AFTER, only through temporary table
 1699                  $statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default'];
 1700              break;
 1701   
 1702              case 'mysql_40':
 1703              case 'mysql_41':
 1704                  $after = (!empty($column_data['after'])) ? ' AFTER ' . $column_data['after'] : '';
 1705                  $statements[] = 'ALTER TABLE `' . $table_name . '` ADD COLUMN `' . $column_name . '` ' . $column_data['column_type_sql'] . $after;
 1706              break;
 1707   
 1708              case 'oracle':
 1709                  // Does not support AFTER, only through temporary table
 1710                  $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql'];
 1711              break;
 1712   
 1713              case 'postgres':
 1714                  // Does not support AFTER, only through temporary table
 1715                  if (version_compare($this->db->sql_server_info(true), '8.0', '>='))
 1716                  {
 1717                      $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type_sql'];
 1718                  }
 1719                  else
 1720                  {
 1721                      // old versions cannot add columns with default and null information
 1722                      $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type'] . ' ' . $column_data['constraint'];
 1723   
 1724                      if (isset($column_data['null']))
 1725                      {
 1726                          if ($column_data['null'] == 'NOT NULL')
 1727                          {
 1728                              $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET NOT NULL';
 1729                          }
 1730                      }
 1731   
 1732                      if (isset($column_data['default']))
 1733                      {
 1734                          $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default'];
 1735                      }
 1736                  }
 1737   
 1738              break;
 1739   
 1740              case 'sqlite':
 1741                  if ($inline && $this->return_statements)
 1742                  {
 1743                      return $column_name . ' ' . $column_data['column_type_sql'];
 1744                  }
 1745   
 1746                  $recreate_queries = $this->sqlite_get_recreate_table_queries($table_name);
 1747                  if (empty($recreate_queries))
 1748                  {
 1749                      break;
 1750                  }
 1751   
 1752                  $statements[] = 'begin';
 1753   
 1754                  $sql_create_table = array_shift($recreate_queries);
 1755   
 1756                  // Create a backup table and populate it, destroy the existing one
 1757                  $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $sql_create_table);
 1758                  $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
 1759                  $statements[] = 'DROP TABLE ' . $table_name;
 1760   
 1761                  preg_match('#\((.*)\)#s', $sql_create_table, $matches);
 1762   
 1763                  $new_table_cols = trim($matches[1]);
 1764                  $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols);
 1765                  $column_list = array();
 1766   
 1767                  foreach ($old_table_cols as $declaration)
 1768                  {
 1769                      $entities = preg_split('#\s+#', trim($declaration));
 1770                      if ($entities[0] == 'PRIMARY')
 1771                      {
 1772                          continue;
 1773                      }
 1774                      $column_list[] = $entities[0];
 1775                  }
 1776   
 1777                  $columns = implode(',', $column_list);
 1778   
 1779                  $new_table_cols = $column_name . ' ' . $column_data['column_type_sql'] . ',' . $new_table_cols;
 1780   
 1781                  // create a new table and fill it up. destroy the temp one
 1782                  $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ');';
 1783                  $statements = array_merge($statements, $recreate_queries);
 1784   
 1785                  $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
 1786                  $statements[] = 'DROP TABLE ' . $table_name . '_temp';
 1787   
 1788                  $statements[] = 'commit';
 1789              break;
 1790   
 1791              case 'sqlite3':
 1792                  if ($inline && $this->return_statements)
 1793                  {
 1794                      return $column_name . ' ' . $column_data['column_type_sql'];
 1795                  }
 1796   
 1797                  $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql'];
 1798              break;
 1799          }
 1800   
 1801          return $this->_sql_run_sql($statements);
 1802      }
 1803   
 1804      /**
 1805      * Drop column
 1806      */
 1807      function sql_column_remove($table_name, $column_name, $inline = false)
 1808      {
 1809          $statements = array();
 1810   
 1811          switch ($this->sql_layer)
 1812          {
 1813              case 'mssql':
 1814              case 'mssqlnative':
 1815                  // We need the data here
 1816                  $old_return_statements = $this->return_statements;
 1817                  $this->return_statements = true;
 1818   
 1819                  $indexes = $this->get_existing_indexes($table_name, $column_name);
 1820                  $indexes = array_merge($indexes, $this->get_existing_indexes($table_name, $column_name, true));
 1821   
 1822                  // Drop any indexes
 1823                  $recreate_indexes = array();
 1824                  if (!empty($indexes))
 1825                  {
 1826                      foreach ($indexes as $index_name => $index_data)
 1827                      {
 1828                          $result = $this->sql_index_drop($table_name, $index_name);
 1829                          $statements = array_merge($statements, $result);
 1830                          if (sizeof($index_data) > 1)
 1831                          {
 1832                              // Remove this column from the index and recreate it
 1833                              $recreate_indexes[$index_name] = array_diff($index_data, array($column_name));
 1834                          }
 1835                      }
 1836                  }
 1837   
 1838                  // Drop default value constraint
 1839                  $result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name);
 1840                  $statements = array_merge($statements, $result);
 1841   
 1842                  // Remove the column
 1843                  $statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']';
 1844   
 1845                  if (!empty($recreate_indexes))
 1846                  {
 1847                      // Recreate indexes after we removed the column
 1848                      foreach ($recreate_indexes as $index_name => $index_data)
 1849                      {
 1850                          $result = $this->sql_create_index($table_name, $index_name, $index_data);
 1851                          $statements = array_merge($statements, $result);
 1852                      }
 1853                  }
 1854   
 1855                  $this->return_statements = $old_return_statements;
 1856              break;
 1857   
 1858              case 'mysql_40':
 1859              case 'mysql_41':
 1860                  $statements[] = 'ALTER TABLE `' . $table_name . '` DROP COLUMN `' . $column_name . '`';
 1861              break;
 1862   
 1863              case 'oracle':
 1864                  $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name;
 1865              break;
 1866   
 1867              case 'postgres':
 1868                  $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN "' . $column_name . '"';
 1869              break;
 1870   
 1871              case 'sqlite':
 1872              case 'sqlite3':
 1873   
 1874                  if ($inline && $this->return_statements)
 1875                  {
 1876                      return $column_name;
 1877                  }
 1878   
 1879                  $recreate_queries = $this->sqlite_get_recreate_table_queries($table_name, $column_name);
 1880                  if (empty($recreate_queries))
 1881                  {
 1882                      break;
 1883                  }
 1884   
 1885                  $statements[] = 'begin';
 1886   
 1887                  $sql_create_table = array_shift($recreate_queries);
 1888   
 1889                  // Create a backup table and populate it, destroy the existing one
 1890                  $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $sql_create_table);
 1891                  $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
 1892                  $statements[] = 'DROP TABLE ' . $table_name;
 1893   
 1894                  preg_match('#\((.*)\)#s', $sql_create_table, $matches);
 1895   
 1896                  $new_table_cols = trim($matches[1]);
 1897                  $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols);
 1898                  $column_list = array();
 1899   
 1900                  foreach ($old_table_cols as $declaration)
 1901                  {
 1902                      $entities = preg_split('#\s+#', trim($declaration));
 1903                      if ($entities[0] == 'PRIMARY' || $entities[0] === $column_name)
 1904                      {
 1905                          continue;
 1906                      }
 1907                      $column_list[] = $entities[0];
 1908                  }
 1909   
 1910                  $columns = implode(',', $column_list);
 1911   
 1912                  $new_table_cols = trim(preg_replace('/' . $column_name . '\b[^,]+(?:,|$)/m', '', $new_table_cols));
 1913                  if (substr($new_table_cols, -1) === ',')
 1914                  {
 1915                      // Remove the comma from the last entry again
 1916                      $new_table_cols = substr($new_table_cols, 0, -1);
 1917                  }
 1918   
 1919                  // create a new table and fill it up. destroy the temp one
 1920                  $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ');';
 1921                  $statements = array_merge($statements, $recreate_queries);
 1922   
 1923                  $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
 1924                  $statements[] = 'DROP TABLE ' . $table_name . '_temp';
 1925   
 1926                  $statements[] = 'commit';
 1927              break;
 1928          }
 1929   
 1930          return $this->_sql_run_sql($statements);
 1931      }
 1932   
 1933      /**
 1934      * Drop Index
 1935      */
 1936      function sql_index_drop($table_name, $index_name)
 1937      {
 1938          $statements = array();
 1939   
 1940          switch ($this->sql_layer)
 1941          {
 1942              case 'mssql':
 1943              case 'mssqlnative':
 1944                  $statements[] = 'DROP INDEX ' . $table_name . '.' . $index_name;
 1945              break;
 1946   
 1947              case 'mysql_40':
 1948              case 'mysql_41':
 1949                  $statements[] = 'DROP INDEX ' . $index_name . ' ON ' . $table_name;
 1950              break;
 1951   
 1952              case 'oracle':
 1953              case 'postgres':
 1954              case 'sqlite':
 1955              case 'sqlite3':
 1956                  $statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name;
 1957              break;
 1958          }
 1959   
 1960          return $this->_sql_run_sql($statements);
 1961      }
 1962   
 1963      /**
 1964      * Drop Table
 1965      */
 1966      function sql_table_drop($table_name)
 1967      {
 1968          $statements = array();
 1969   
 1970          if (!$this->sql_table_exists($table_name))
 1971          {
 1972              return $this->_sql_run_sql($statements);
 1973          }
 1974   
 1975          // the most basic operation, get rid of the table
 1976          $statements[] = 'DROP TABLE ' . $table_name;
 1977   
 1978          switch ($this->sql_layer)
 1979          {
 1980              case 'oracle':
 1981                  $sql = 'SELECT A.REFERENCED_NAME
 1982                      FROM USER_DEPENDENCIES A, USER_TRIGGERS B
 1983                      WHERE A.REFERENCED_TYPE = \'SEQUENCE\'
 1984                          AND A.NAME = B.TRIGGER_NAME
 1985                          AND B.TABLE_NAME = \'' . strtoupper($table_name) . "'";
 1986                  $result = $this->db->sql_query($sql);
 1987   
 1988                  // any sequences ref'd to this table's triggers?
 1989                  while ($row = $this->db->sql_fetchrow($result))
 1990                  {
 1991                      $statements[] = "DROP SEQUENCE {$row['referenced_name']}";
 1992                  }
 1993                  $this->db->sql_freeresult($result);
 1994              break;
 1995   
 1996              case 'postgres':
 1997                  // PGSQL does not "tightly" bind sequences and tables, we must guess...
 1998                  $sql = "SELECT relname
 1999                      FROM pg_class
 2000                      WHERE relkind = 'S'
 2001                          AND relname = '{$table_name}_seq'";
 2002                  $result = $this->db->sql_query($sql);
 2003   
 2004                  // We don't even care about storing the results. We already know the answer if we get rows back.
 2005                  if ($this->db->sql_fetchrow($result))
 2006                  {
 2007                      $statements[] =  "DROP SEQUENCE {$table_name}_seq;\n";
 2008                  }
 2009                  $this->db->sql_freeresult($result);
 2010              break;
 2011          }
 2012   
 2013          return $this->_sql_run_sql($statements);
 2014      }
 2015   
 2016      /**
 2017      * Add primary key
 2018      */
 2019      function sql_create_primary_key($table_name, $column, $inline = false)
 2020      {
 2021          $statements = array();
 2022   
 2023          switch ($this->sql_layer)
 2024          {
 2025              case 'postgres':
 2026              case 'mysql_40':
 2027              case 'mysql_41':
 2028                  $statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')';
 2029              break;
 2030   
 2031              case 'mssql':
 2032              case 'mssqlnative':
 2033                  $sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD ";
 2034                  $sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY  CLUSTERED (";
 2035                  $sql .= '[' . implode("],\n\t\t[", $column) . ']';
 2036                  $sql .= ')';
 2037   
 2038                  $statements[] = $sql;
 2039              break;
 2040   
 2041              case 'oracle':
 2042                  $statements[] = 'ALTER TABLE ' . $table_name . ' add CONSTRAINT pk_' . $table_name . ' PRIMARY KEY (' . implode(', ', $column) . ')';
 2043              break;
 2044   
 2045              case 'sqlite':
 2046              case 'sqlite3':
 2047   
 2048                  if ($inline && $this->return_statements)
 2049                  {
 2050                      return $column;
 2051                  }
 2052   
 2053                  $recreate_queries = $this->sqlite_get_recreate_table_queries($table_name);
 2054                  if (empty($recreate_queries))
 2055                  {
 2056                      break;
 2057                  }
 2058   
 2059                  $statements[] = 'begin';
 2060   
 2061                  $sql_create_table = array_shift($recreate_queries);
 2062   
 2063                  // Create a backup table and populate it, destroy the existing one
 2064                  $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $sql_create_table);
 2065                  $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
 2066                  $statements[] = 'DROP TABLE ' . $table_name;
 2067   
 2068                  preg_match('#\((.*)\)#s', $sql_create_table, $matches);
 2069   
 2070                  $new_table_cols = trim($matches[1]);
 2071                  $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols);
 2072                  $column_list = array();
 2073   
 2074                  foreach ($old_table_cols as $declaration)
 2075                  {
 2076                      $entities = preg_split('#\s+#', trim($declaration));
 2077                      if ($entities[0] == 'PRIMARY')
 2078                      {
 2079                          continue;
 2080                      }
 2081                      $column_list[] = $entities[0];
 2082                  }
 2083   
 2084                  $columns = implode(',', $column_list);
 2085   
 2086                  // create a new table and fill it up. destroy the temp one
 2087                  $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ', PRIMARY KEY (' . implode(', ', $column) . '));';
 2088                  $statements = array_merge($statements, $recreate_queries);
 2089   
 2090                  $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
 2091                  $statements[] = 'DROP TABLE ' . $table_name . '_temp';
 2092   
 2093                  $statements[] = 'commit';
 2094              break;
 2095          }
 2096   
 2097          return $this->_sql_run_sql($statements);
 2098      }
 2099   
 2100      /**
 2101      * Add unique index
 2102      */
 2103      function sql_create_unique_index($table_name, $index_name, $column)
 2104      {
 2105          $statements = array();
 2106   
 2107          $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config)
 2108          if (strlen($table_name . '_' . $index_name) - strlen($table_prefix) > 24)
 2109          {
 2110              $max_length = strlen($table_prefix) + 24;
 2111              trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR);
 2112          }
 2113   
 2114          switch ($this->sql_layer)
 2115          {
 2116              case 'postgres':
 2117              case 'oracle':
 2118              case 'sqlite':
 2119              case 'sqlite3':
 2120                  $statements[] = 'CREATE UNIQUE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')';
 2121              break;
 2122   
 2123              case 'mysql_40':
 2124              case 'mysql_41':
 2125                  $statements[] = 'ALTER TABLE ' . $table_name . ' ADD UNIQUE INDEX ' . $index_name . '(' . implode(', ', $column) . ')';
 2126              break;
 2127   
 2128              case 'mssql':
 2129              case 'mssqlnative':
 2130                  $statements[] = 'CREATE UNIQUE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])';
 2131              break;
 2132          }
 2133   
 2134          return $this->_sql_run_sql($statements);
 2135      }
 2136   
 2137      /**
 2138      * Add index
 2139      */
 2140      function sql_create_index($table_name, $index_name, $column)
 2141      {
 2142          $statements = array();
 2143   
 2144          $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config)
 2145          if (strlen($table_name . $index_name) - strlen($table_prefix) > 24)
 2146          {
 2147              $max_length = strlen($table_prefix) + 24;
 2148              trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR);
 2149          }
 2150   
 2151          // remove index length unless MySQL4
 2152          if ('mysql_40' != $this->sql_layer)
 2153          {
 2154              $column = preg_replace('#:.*$#', '', $column);
 2155          }
 2156   
 2157          switch ($this->sql_layer)
 2158          {
 2159              case 'postgres':
 2160              case 'oracle':
 2161              case 'sqlite':
 2162              case 'sqlite3':
 2163                  $statements[] = 'CREATE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')';
 2164              break;
 2165   
 2166              case 'mysql_40':
 2167                  // add index size to definition as required by MySQL4
 2168                  foreach ($column as $i => $col)
 2169                  {
 2170                      if (false !== strpos($col, ':'))
 2171                      {
 2172                          list($col, $index_size) = explode(':', $col);
 2173                          $column[$i] = "$col($index_size)";
 2174                      }
 2175                  }
 2176              // no break
 2177              case 'mysql_41':
 2178                  $statements[] = 'ALTER TABLE ' . $table_name . ' ADD INDEX ' . $index_name . '(' . implode(', ', $column) . ')';
 2179              break;
 2180   
 2181              case 'mssql':
 2182              case 'mssqlnative':
 2183                  $statements[] = 'CREATE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])';
 2184              break;
 2185          }
 2186   
 2187          return $this->_sql_run_sql($statements);
 2188      }
 2189   
 2190      /**
 2191      * List all of the indices that belong to a table,
 2192      * does not count:
 2193      * * UNIQUE indices
 2194      * * PRIMARY keys
 2195      */
 2196      function sql_list_index($table_name)
 2197      {
 2198          $index_array = array();
 2199   
 2200          if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative')
 2201          {
 2202              $sql = "EXEC sp_statistics '$table_name'";
 2203              $result = $this->db->sql_query($sql);
 2204              while ($row = $this->db->sql_fetchrow($result))
 2205              {
 2206                  if ($row['TYPE'] == 3)
 2207                  {
 2208                      $index_array[] = $row['INDEX_NAME'];
 2209                  }
 2210              }
 2211              $this->db->sql_freeresult($result);
 2212          }
 2213          else
 2214          {
 2215              switch ($this->sql_layer)
 2216              {
 2217                  case 'postgres':
 2218                      $sql = "SELECT ic.relname as index_name
 2219                          FROM pg_class bc, pg_class ic, pg_index i
 2220                          WHERE (bc.oid = i.indrelid)
 2221                              AND (ic.oid = i.indexrelid)
 2222                              AND (bc.relname = '" . $table_name . "')
 2223                              AND (i.indisunique != 't')
 2224                              AND (i.indisprimary != 't')";
 2225                      $col = 'index_name';
 2226                  break;
 2227   
 2228                  case 'mysql_40':
 2229                  case 'mysql_41':
 2230                      $sql = 'SHOW KEYS
 2231                          FROM ' . $table_name;
 2232                      $col = 'Key_name';
 2233                  break;
 2234   
 2235                  case 'oracle':
 2236                      $sql = "SELECT index_name
 2237                          FROM user_indexes
 2238                          WHERE table_name = '" . strtoupper($table_name) . "'
 2239                              AND generated = 'N'
 2240                              AND uniqueness = 'NONUNIQUE'";
 2241                      $col = 'index_name';
 2242                  break;
 2243   
 2244                  case 'sqlite':
 2245                  case 'sqlite3':
 2246                      $sql = "PRAGMA index_info('" . $table_name . "');";
 2247                      $col = 'name';
 2248                  break;
 2249              }
 2250   
 2251              $result = $this->db->sql_query($sql);
 2252              while ($row = $this->db->sql_fetchrow($result))
 2253              {
 2254                  if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique'])
 2255                  {
 2256                      continue;
 2257                  }
 2258   
 2259                  switch ($this->sql_layer)
 2260                  {
 2261                      case 'oracle':
 2262                      case 'postgres':
 2263                      case 'sqlite':
 2264                      case 'sqlite3':
 2265                          $row[$col] = substr($row[$col], strlen($table_name) + 1);
 2266                      break;
 2267                  }
 2268   
 2269                  $index_array[] = $row[$col];
 2270              }
 2271              $this->db->sql_freeresult($result);
 2272          }
 2273   
 2274          return array_map('strtolower', $index_array);
 2275      }
 2276   
 2277      /**
 2278       * Removes table_name from the index_name if it is at the beginning
 2279       *
 2280       * @param $table_name
 2281       * @param $index_name
 2282       * @return string
 2283       */
 2284      protected function strip_table_name_from_index_name($table_name, $index_name)
 2285      {
 2286          return (strpos(strtoupper($index_name), strtoupper($table_name)) === 0) ? substr($index_name, strlen($table_name) + 1) : $index_name;
 2287      }
 2288   
 2289      /**
 2290      * Change column type (not name!)
 2291      */
 2292      function sql_column_change($table_name, $column_name, $column_data, $inline = false)
 2293      {
 2294          $original_column_data = $column_data;
 2295          $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
 2296          $statements = array();
 2297   
 2298          switch ($this->sql_layer)
 2299          {
 2300              case 'mssql':
 2301              case 'mssqlnative':
 2302                  // We need the data here
 2303                  $old_return_statements = $this->return_statements;
 2304                  $this->return_statements = true;
 2305   
 2306                  $indexes = $this->get_existing_indexes($table_name, $column_name);
 2307                  $unique_indexes = $this->get_existing_indexes($table_name, $column_name, true);
 2308   
 2309                  // Drop any indexes
 2310                  if (!empty($indexes) || !empty($unique_indexes))
 2311                  {
 2312                      $drop_indexes = array_merge(array_keys($indexes), array_keys($unique_indexes));
 2313                      foreach ($drop_indexes as $index_name)
 2314                      {
 2315                          $result = $this->sql_index_drop($table_name, $index_name);
 2316                          $statements = array_merge($statements, $result);
 2317                      }
 2318                  }
 2319   
 2320                  // Drop default value constraint
 2321                  $result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name);
 2322                  $statements = array_merge($statements, $result);
 2323   
 2324                  // Change the column
 2325                  $statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql'];
 2326   
 2327                  if (!empty($column_data['default']))
 2328                  {
 2329                      // Add new default value constraint
 2330                      $statements[] = 'ALTER TABLE [' . $table_name . '] ADD CONSTRAINT [DF_' . $table_name . '_' . $column_name . '_1] ' . $this->db->sql_escape($column_data['default']) . ' FOR [' . $column_name . ']';
 2331                  }
 2332   
 2333                  if (!empty($indexes))
 2334                  {
 2335                      // Recreate indexes after we changed the column
 2336                      foreach ($indexes as $index_name => $index_data)
 2337                      {
 2338                          $result = $this->sql_create_index($table_name, $index_name, $index_data);
 2339                          $statements = array_merge($statements, $result);
 2340                      }
 2341                  }
 2342   
 2343                  if (!empty($unique_indexes))
 2344                  {
 2345                      // Recreate unique indexes after we changed the column
 2346                      foreach ($unique_indexes as $index_name => $index_data)
 2347                      {
 2348                          $result = $this->sql_create_unique_index($table_name, $index_name, $index_data);
 2349                          $statements = array_merge($statements, $result);
 2350                      }
 2351                  }
 2352   
 2353                  $this->return_statements = $old_return_statements;
 2354              break;
 2355   
 2356              case 'mysql_40':
 2357              case 'mysql_41':
 2358                  $statements[] = 'ALTER TABLE `' . $table_name . '` CHANGE `' . $column_name . '` `' . $column_name . '` ' . $column_data['column_type_sql'];
 2359              break;
 2360   
 2361              case 'oracle':
 2362                  // We need the data here
 2363                  $old_return_statements = $this->return_statements;
 2364                  $this->return_statements = true;
 2365   
 2366                  // Get list of existing indexes
 2367                  $indexes = $this->get_existing_indexes($table_name, $column_name);
 2368                  $unique_indexes = $this->get_existing_indexes($table_name, $column_name, true);
 2369   
 2370                  // Drop any indexes
 2371                  if (!empty($indexes) || !empty($unique_indexes))
 2372                  {
 2373                      $drop_indexes = array_merge(array_keys($indexes), array_keys($unique_indexes));
 2374                      foreach ($drop_indexes as $index_name)
 2375                      {
 2376                          $result = $this->sql_index_drop($table_name, $this->strip_table_name_from_index_name($table_name, $index_name));
 2377                          $statements = array_merge($statements, $result);
 2378                      }
 2379                  }
 2380   
 2381                  $temp_column_name = 'temp_' . substr(md5($column_name), 0, 25);
 2382                  // Add a temporary table with the new type
 2383                  $result = $this->sql_column_add($table_name, $temp_column_name, $original_column_data);
 2384                  $statements = array_merge($statements, $result);
 2385   
 2386                  // Copy the data to the new column
 2387                  $statements[] = 'UPDATE ' . $table_name . ' SET ' . $temp_column_name . ' = ' . $column_name;
 2388   
 2389                  // Drop the original column
 2390                  $result = $this->sql_column_remove($table_name, $column_name);
 2391                  $statements = array_merge($statements, $result);
 2392   
 2393                  // Recreate the original column with the new type
 2394                  $result = $this->sql_column_add($table_name, $column_name, $original_column_data);
 2395                  $statements = array_merge($statements, $result);
 2396   
 2397                  if (!empty($indexes))
 2398                  {
 2399                      // Recreate indexes after we changed the column
 2400                      foreach ($indexes as $index_name => $index_data)
 2401                      {
 2402                          $result = $this->sql_create_index($table_name, $this->strip_table_name_from_index_name($table_name, $index_name), $index_data);
 2403                          $statements = array_merge($statements, $result);
 2404                      }
 2405                  }
 2406   
 2407                  if (!empty($unique_indexes))
 2408                  {
 2409                      // Recreate unique indexes after we changed the column
 2410                      foreach ($unique_indexes as $index_name => $index_data)
 2411                      {
 2412                          $result = $this->sql_create_unique_index($table_name, $this->strip_table_name_from_index_name($table_name, $index_name), $index_data);
 2413                          $statements = array_merge($statements, $result);
 2414                      }
 2415                  }
 2416   
 2417                  // Copy the data to the original column
 2418                  $statements[] = 'UPDATE ' . $table_name . ' SET ' . $column_name . ' = ' . $temp_column_name;
 2419   
 2420                  // Drop the temporary column again
 2421                  $result = $this->sql_column_remove($table_name, $temp_column_name);
 2422                  $statements = array_merge($statements, $result);
 2423   
 2424                  $this->return_statements = $old_return_statements;
 2425              break;
 2426   
 2427              case 'postgres':
 2428                  $sql = 'ALTER TABLE ' . $table_name . ' ';
 2429   
 2430                  $sql_array = array();
 2431                  $sql_array[] = 'ALTER COLUMN ' . $column_name . ' TYPE ' . $column_data['column_type'];
 2432   
 2433                  if (isset($column_data['null']))
 2434                  {
 2435                      if ($column_data['null'] == 'NOT NULL')
 2436                      {
 2437                          $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET NOT NULL';
 2438                      }
 2439                      else if ($column_data['null'] == 'NULL')
 2440                      {
 2441                          $sql_array[] = 'ALTER COLUMN ' . $column_name . ' DROP NOT NULL';
 2442                      }
 2443                  }
 2444   
 2445                  if (isset($column_data['default']))
 2446                  {
 2447                      $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default'];
 2448                  }
 2449   
 2450                  // we don't want to double up on constraints if we change different number data types
 2451                  if (isset($column_data['constraint']))
 2452                  {
 2453                      $constraint_sql = "SELECT consrc as constraint_data
 2454                                  FROM pg_constraint, pg_class bc
 2455                                  WHERE conrelid = bc.oid
 2456                                      AND bc.relname = '{$table_name}'
 2457                                      AND NOT EXISTS (
 2458                                          SELECT *
 2459                                              FROM pg_constraint as c, pg_inherits as i
 2460                                              WHERE i.inhrelid = pg_constraint.conrelid
 2461                                                  AND c.conname = pg_constraint.conname
 2462                                                  AND c.consrc = pg_constraint.consrc
 2463                                                  AND c.conrelid = i.inhparent
 2464                                      )";
 2465   
 2466                      $constraint_exists = false;
 2467   
 2468                      $result = $this->db->sql_query($constraint_sql);
 2469                      while ($row = $this->db->sql_fetchrow($result))
 2470                      {
 2471                          if (trim($row['constraint_data']) == trim($column_data['constraint']))
 2472                          {
 2473                              $constraint_exists = true;
 2474                              break;
 2475                          }
 2476                      }
 2477                      $this->db->sql_freeresult($result);
 2478   
 2479                      if (!$constraint_exists)
 2480                      {
 2481                          $sql_array[] = 'ADD ' . $column_data['constraint'];
 2482                      }
 2483                  }
 2484   
 2485                  $sql .= implode(', ', $sql_array);
 2486   
 2487                  $statements[] = $sql;
 2488              break;
 2489   
 2490              case 'sqlite':
 2491              case 'sqlite3':
 2492   
 2493                  if ($inline && $this->return_statements)
 2494                  {
 2495                      return $column_name . ' ' . $column_data['column_type_sql'];
 2496                  }
 2497   
 2498                  $recreate_queries = $this->sqlite_get_recreate_table_queries($table_name);
 2499                  if (empty($recreate_queries))
 2500                  {
 2501                      break;
 2502                  }
 2503   
 2504                  $statements[] = 'begin';
 2505   
 2506                  $sql_create_table = array_shift($recreate_queries);
 2507   
 2508                  // Create a temp table and populate it, destroy the existing one
 2509                  $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $sql_create_table);
 2510                  $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
 2511                  $statements[] = 'DROP TABLE ' . $table_name;
 2512   
 2513                  preg_match('#\((.*)\)#s', $sql_create_table, $matches);
 2514   
 2515                  $new_table_cols = trim($matches[1]);
 2516                  $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols);
 2517                  $column_list = array();
 2518   
 2519                  foreach ($old_table_cols as $key => $declaration)
 2520                  {
 2521                      $declaration = trim($declaration);
 2522   
 2523                      // Check for the beginning of the constraint section and stop
 2524                      if (preg_match('/[^\(]*\s*PRIMARY KEY\s+\(/', $declaration) ||
 2525                          preg_match('/[^\(]*\s*UNIQUE\s+\(/', $declaration) ||
 2526                          preg_match('/[^\(]*\s*FOREIGN KEY\s+\(/', $declaration) ||
 2527                          preg_match('/[^\(]*\s*CHECK\s+\(/', $declaration))
 2528                      {
 2529                          break;
 2530                      }
 2531   
 2532                      $entities = preg_split('#\s+#', $declaration);
 2533                      $column_list[] = $entities[0];
 2534                      if ($entities[0] == $column_name)
 2535                      {
 2536                          $old_table_cols[$key] = $column_name . ' ' . $column_data['column_type_sql'];
 2537                      }
 2538                  }
 2539   
 2540                  $columns = implode(',', $column_list);
 2541   
 2542                  // Create a new table and fill it up. destroy the temp one
 2543                  $statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $old_table_cols) . ');';
 2544                  $statements = array_merge($statements, $recreate_queries);
 2545   
 2546                  $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
 2547                  $statements[] = 'DROP TABLE ' . $table_name . '_temp';
 2548   
 2549                  $statements[] = 'commit';
 2550   
 2551              break;
 2552          }
 2553   
 2554          return $this->_sql_run_sql($statements);
 2555      }
 2556   
 2557      /**
 2558      * Get queries to drop the default constraints of a column
 2559      *
 2560      * We need to drop the default constraints of a column,
 2561      * before being able to change their type or deleting them.
 2562      *
 2563      * @param string $table_name
 2564      * @param string $column_name
 2565      * @return array        Array with SQL statements
 2566      */
 2567      protected function mssql_get_drop_default_constraints_queries($table_name, $column_name)
 2568      {
 2569          $statements = array();
 2570          if ($this->mssql_is_sql_server_2000())
 2571          {
 2572              // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx
 2573              // Deprecated in SQL Server 2005
 2574              $sql = "SELECT so.name AS def_name
 2575                  FROM sysobjects so
 2576                  JOIN sysconstraints sc ON so.id = sc.constid
 2577                  WHERE object_name(so.parent_obj) = '{$table_name}'
 2578                      AND so.xtype = 'D'
 2579                      AND sc.colid = (SELECT colid FROM syscolumns
 2580                          WHERE id = object_id('{$table_name}')
 2581                              AND name = '{$column_name}')";
 2582          }
 2583          else
 2584          {
 2585              $sql = "SELECT dobj.name AS def_name
 2586                  FROM sys.columns col
 2587                      LEFT OUTER JOIN sys.objects dobj ON (dobj.object_id = col.default_object_id AND dobj.type = 'D')
 2588                  WHERE col.object_id = object_id('{$table_name}')
 2589                      AND col.name = '{$column_name}'
 2590                      AND dobj.name IS NOT NULL";
 2591          }
 2592   
 2593          $result = $this->db->sql_query($sql);
 2594          while ($row = $this->db->sql_fetchrow($result))
 2595          {
 2596              $statements[] = 'ALTER TABLE [' . $table_name . '] DROP CONSTRAINT [' . $row['def_name'] . ']';
 2597          }
 2598          $this->db->sql_freeresult($result);
 2599   
 2600          return $statements;
 2601      }
 2602   
 2603      /**
 2604      * Get a list with existing indexes for the column
 2605      *
 2606      * @param string $table_name
 2607      * @param string $column_name
 2608      * @param bool $unique Should we get unique indexes or normal ones
 2609      * @return array        Array with Index name => columns
 2610      */
 2611      public function get_existing_indexes($table_name, $column_name, $unique = false)
 2612      {
 2613          switch ($this->sql_layer)
 2614          {
 2615              case 'mysql_40':
 2616              case 'mysql_41':
 2617              case 'postgres':
 2618              case 'sqlite':
 2619              case 'sqlite3':
 2620                  // Not supported
 2621                  throw new \Exception('DBMS is not supported');
 2622              break;
 2623          }
 2624   
 2625          $sql = '';
 2626          $existing_indexes = array();
 2627   
 2628          switch ($this->sql_layer)
 2629          {
 2630              case 'mssql':
 2631              case 'mssqlnative':
 2632                  if ($this->mssql_is_sql_server_2000())
 2633                  {
 2634                      // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx
 2635                      // Deprecated in SQL Server 2005
 2636                      $sql = "SELECT DISTINCT ix.name AS phpbb_index_name
 2637                      FROM sysindexes ix
 2638                      INNER JOIN sysindexkeys ixc
 2639                          ON ixc.id = ix.id
 2640                              AND ixc.indid = ix.indid
 2641                      INNER JOIN syscolumns cols
 2642                          ON cols.colid = ixc.colid
 2643                              AND cols.id = ix.id
 2644                      WHERE ix.id = object_id('{$table_name}')
 2645                          AND cols.name = '{$column_name}'
 2646                          AND INDEXPROPERTY(ix.id, ix.name, 'IsUnique') = " . ($unique) ? '1' : '0';
 2647                  }
 2648                  else
 2649                  {
 2650                      $sql = "SELECT DISTINCT ix.name AS phpbb_index_name
 2651                      FROM sys.indexes ix
 2652                      INNER JOIN sys.index_columns ixc
 2653                          ON ixc.object_id = ix.object_id
 2654                              AND ixc.index_id = ix.index_id
 2655                      INNER JOIN sys.columns cols
 2656                          ON cols.column_id = ixc.column_id
 2657                              AND cols.object_id = ix.object_id
 2658                      WHERE ix.object_id = object_id('{$table_name}')
 2659                          AND cols.name = '{$column_name}'
 2660                          AND ix.is_unique = " . ($unique) ? '1' : '0';
 2661                  }
 2662              break;
 2663   
 2664              case 'oracle':
 2665                  $sql = "SELECT ix.index_name  AS phpbb_index_name, ix.uniqueness AS is_unique
 2666                      FROM all_ind_columns ixc, all_indexes ix
 2667                      WHERE ix.index_name = ixc.index_name
 2668                          AND ixc.table_name = '" . strtoupper($table_name) . "'
 2669                          AND ixc.column_name = '" . strtoupper($column_name) . "'";
 2670              break;
 2671          }
 2672   
 2673          $result = $this->db->sql_query($sql);
 2674          while ($row = $this->db->sql_fetchrow($result))
 2675          {
 2676              if (!isset($row['is_unique']) || ($unique && $row['is_unique'] == 'UNIQUE') || (!$unique && $row['is_unique'] == 'NONUNIQUE'))
 2677              {
 2678                  $existing_indexes[$row['phpbb_index_name']] = array();
 2679              }
 2680          }
 2681          $this->db->sql_freeresult($result);
 2682   
 2683          if (empty($existing_indexes))
 2684          {
 2685              return array();
 2686          }
 2687   
 2688          switch ($this->sql_layer)
 2689          {
 2690              case 'mssql':
 2691              case 'mssqlnative':
 2692                  if ($this->mssql_is_sql_server_2000())
 2693                  {
 2694                      $sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name
 2695                          FROM sysindexes ix
 2696                          INNER JOIN sysindexkeys ixc
 2697                              ON ixc.id = ix.id
 2698                                  AND ixc.indid = ix.indid
 2699                          INNER JOIN syscolumns cols
 2700                              ON cols.colid = ixc.colid
 2701                                  AND cols.id = ix.id
 2702                          WHERE ix.id = object_id('{$table_name}')
 2703                              AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes));
 2704                  }
 2705                  else
 2706                  {
 2707                      $sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name
 2708                          FROM sys.indexes ix
 2709                          INNER JOIN sys.index_columns ixc
 2710                              ON ixc.object_id = ix.object_id
 2711                                  AND ixc.index_id = ix.index_id
 2712                          INNER JOIN sys.columns cols
 2713                              ON cols.column_id = ixc.column_id
 2714                                  AND cols.object_id = ix.object_id
 2715                          WHERE ix.object_id = object_id('{$table_name}')
 2716                              AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes));
 2717                  }
 2718              break;
 2719   
 2720              case 'oracle':
 2721                  $sql = "SELECT index_name AS phpbb_index_name, column_name AS phpbb_column_name
 2722                      FROM all_ind_columns
 2723                      WHERE table_name = '" . strtoupper($table_name) . "'
 2724                          AND " . $this->db->sql_in_set('index_name', array_keys($existing_indexes));
 2725              break;
 2726          }
 2727   
 2728          $result = $this->db->sql_query($sql);
 2729          while ($row = $this->db->sql_fetchrow($result))
 2730          {
 2731              $existing_indexes[$row['phpbb_index_name']][] = $row['phpbb_column_name'];
 2732          }
 2733          $this->db->sql_freeresult($result);
 2734   
 2735          return $existing_indexes;
 2736      }
 2737   
 2738      /**
 2739      * Is the used MS SQL Server a SQL Server 2000?
 2740      *
 2741      * @return bool
 2742      */
 2743      protected function mssql_is_sql_server_2000()
 2744      {
 2745          if ($this->is_sql_server_2000 === null)
 2746          {
 2747              $sql = "SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR(25)) AS mssql_version";
 2748              $result = $this->db->sql_query($sql);
 2749              $properties = $this->db->sql_fetchrow($result);
 2750              $this->db->sql_freeresult($result);
 2751              $this->is_sql_server_2000 = $properties['mssql_version'][0] == '8';
 2752          }
 2753   
 2754          return $this->is_sql_server_2000;
 2755      }
 2756   
 2757      /**
 2758      * Returns the Queries which are required to recreate a table including indexes
 2759      *
 2760      * @param string $table_name
 2761      * @param string $remove_column    When we drop a column, we remove the column
 2762      *                                from all indexes. If the index has no other
 2763      *                                column, we drop it completly.
 2764      * @return array
 2765      */
 2766      protected function sqlite_get_recreate_table_queries($table_name, $remove_column = '')
 2767      {
 2768          $queries = array();
 2769   
 2770          $sql = "SELECT sql
 2771              FROM sqlite_master
 2772              WHERE type = 'table'
 2773                  AND name = '{$table_name}'";
 2774          $result = $this->db->sql_query($sql);
 2775          $sql_create_table = $this->db->sql_fetchfield('sql');
 2776          $this->db->sql_freeresult($result);
 2777   
 2778          if (!$sql_create_table)
 2779          {
 2780              return array();
 2781          }
 2782          $queries[] = $sql_create_table;
 2783   
 2784          $sql = "SELECT sql
 2785              FROM sqlite_master
 2786              WHERE type = 'index'
 2787                  AND tbl_name = '{$table_name}'";
 2788          $result = $this->db->sql_query($sql);
 2789          while ($sql_create_index = $this->db->sql_fetchfield('sql'))
 2790          {
 2791              if ($remove_column)
 2792              {
 2793                  $match = array();
 2794                  preg_match('#(?:[\w ]+)\((.*)\)#', $sql_create_index, $match);
 2795                  if (!isset($match[1]))
 2796                  {
 2797                      continue;
 2798                  }
 2799   
 2800                  // Find and remove $remove_column from the index
 2801                  $columns = explode(', ', $match[1]);
 2802                  $found_column = array_search($remove_column, $columns);
 2803                  if ($found_column !== false)
 2804                  {
 2805                      unset($columns[$found_column]);
 2806   
 2807                      // If the column list is not empty add the index to the list
 2808                      if (!empty($columns))
 2809                      {
 2810                          $queries[] = str_replace($match[1], implode(', ', $columns), $sql_create_index);
 2811                      }
 2812                  }
 2813                  else
 2814                  {
 2815                      $queries[] = $sql_create_index;
 2816                  }
 2817              }
 2818              else
 2819              {
 2820                  $queries[] = $sql_create_index;
 2821              }
 2822          }
 2823          $this->db->sql_freeresult($result);
 2824   
 2825          return $queries;
 2826      }
 2827  }
 2828