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